DISALLOW_COPY_AND_ASSIGN(ParallelCompilationManager);
};
-static bool SkipClassCheckClassPath(const char* descriptor, const DexFile& dex_file,
- const std::vector<const DexFile*>& classpath) {
- DexFile::ClassPathEntry pair = DexFile::FindInClassPath(descriptor, classpath);
- CHECK(pair.second != NULL);
- if (pair.first != &dex_file) {
- LOG(WARNING) << "Skipping class " << descriptor << " from " << dex_file.GetLocation()
- << " previously found in " << pair.first->GetLocation();
- return true;
- }
- return false;
-}
-
-// Return true if the class should be skipped during compilation.
-//
-// The first case where we skip is for redundant class definitions in
-// the boot classpath. We skip all but the first definition in that case.
-//
-// The second case where we skip is when an app bundles classes found
-// in the boot classpath. Since at runtime we will select the class from
-// the boot classpath, we ignore the one from the app.
-//
-// The third case is if the app itself has the class defined in multiple dex files. Then we skip
-// it if it is not the first occurrence.
-static bool SkipClass(ClassLinker* class_linker, jobject class_loader, const DexFile& dex_file,
- const std::vector<const DexFile*>& dex_files,
- const DexFile::ClassDef& class_def) {
- const char* descriptor = dex_file.GetClassDescriptor(class_def);
-
- if (class_loader == NULL) {
- return SkipClassCheckClassPath(descriptor, dex_file, class_linker->GetBootClassPath());
- }
-
- if (class_linker->IsInBootClassPath(descriptor)) {
- return true;
- }
-
- if (dex_files.size() > 1) {
- // Multi-dex compilation, only take first class.
- return SkipClassCheckClassPath(descriptor, dex_file, dex_files);
- } else {
- // Single dex, take everything.
- return false;
- }
-}
-
// A fast version of SkipClass above if the class pointer is available
// that avoids the expensive FindInClassPath search.
static bool SkipClass(jobject class_loader, const DexFile& dex_file, mirror::Class* klass)
// definitions, since many of them many never be referenced by
// generated code.
const DexFile::ClassDef& class_def = dex_file.GetClassDef(class_def_index);
- if (!SkipClass(class_linker, jclass_loader, dex_file, manager->GetDexFiles(), class_def)) {
- ScopedObjectAccess soa(self);
- StackHandleScope<2> hs(soa.Self());
- Handle<mirror::ClassLoader> class_loader(
- hs.NewHandle(soa.Decode<mirror::ClassLoader*>(jclass_loader)));
- Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache(dex_file)));
- // Resolve the class.
- mirror::Class* klass = class_linker->ResolveType(dex_file, class_def.class_idx_, dex_cache,
- class_loader);
- bool resolve_fields_and_methods;
- if (klass == NULL) {
- // Class couldn't be resolved, for example, super-class is in a different dex file. Don't
- // attempt to resolve methods and fields when there is no declaring class.
- CheckAndClearResolveException(soa.Self());
- resolve_fields_and_methods = false;
- } else {
- resolve_fields_and_methods = manager->GetCompiler()->IsImage();
+ ScopedObjectAccess soa(self);
+ StackHandleScope<2> hs(soa.Self());
+ Handle<mirror::ClassLoader> class_loader(
+ hs.NewHandle(soa.Decode<mirror::ClassLoader*>(jclass_loader)));
+ Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache(dex_file)));
+ // Resolve the class.
+ mirror::Class* klass = class_linker->ResolveType(dex_file, class_def.class_idx_, dex_cache,
+ class_loader);
+ bool resolve_fields_and_methods;
+ if (klass == nullptr) {
+ // Class couldn't be resolved, for example, super-class is in a different dex file. Don't
+ // attempt to resolve methods and fields when there is no declaring class.
+ CheckAndClearResolveException(soa.Self());
+ resolve_fields_and_methods = false;
+ } else {
+ // We successfully resolved a class, should we skip it?
+ if (SkipClass(jclass_loader, dex_file, klass)) {
+ return;
}
- // Note the class_data pointer advances through the headers,
- // static fields, instance fields, direct methods, and virtual
- // methods.
- const byte* class_data = dex_file.GetClassData(class_def);
- if (class_data == NULL) {
- // Empty class such as a marker interface.
- requires_constructor_barrier = false;
- } else {
- ClassDataItemIterator it(dex_file, class_data);
- while (it.HasNextStaticField()) {
- if (resolve_fields_and_methods) {
- mirror::ArtField* field = class_linker->ResolveField(dex_file, it.GetMemberIndex(),
- dex_cache, class_loader, true);
- if (field == NULL) {
- CheckAndClearResolveException(soa.Self());
- }
+ // We want to resolve the methods and fields eagerly.
+ resolve_fields_and_methods = true;
+ }
+ // Note the class_data pointer advances through the headers,
+ // static fields, instance fields, direct methods, and virtual
+ // methods.
+ const byte* class_data = dex_file.GetClassData(class_def);
+ if (class_data == NULL) {
+ // Empty class such as a marker interface.
+ requires_constructor_barrier = false;
+ } else {
+ ClassDataItemIterator it(dex_file, class_data);
+ while (it.HasNextStaticField()) {
+ if (resolve_fields_and_methods) {
+ mirror::ArtField* field = class_linker->ResolveField(dex_file, it.GetMemberIndex(),
+ dex_cache, class_loader, true);
+ if (field == NULL) {
+ CheckAndClearResolveException(soa.Self());
}
- it.Next();
}
- // We require a constructor barrier if there are final instance fields.
- requires_constructor_barrier = false;
- while (it.HasNextInstanceField()) {
- if ((it.GetMemberAccessFlags() & kAccFinal) != 0) {
- requires_constructor_barrier = true;
+ it.Next();
+ }
+ // We require a constructor barrier if there are final instance fields.
+ requires_constructor_barrier = false;
+ while (it.HasNextInstanceField()) {
+ if ((it.GetMemberAccessFlags() & kAccFinal) != 0) {
+ requires_constructor_barrier = true;
+ }
+ if (resolve_fields_and_methods) {
+ mirror::ArtField* field = class_linker->ResolveField(dex_file, it.GetMemberIndex(),
+ dex_cache, class_loader, false);
+ if (field == NULL) {
+ CheckAndClearResolveException(soa.Self());
}
- if (resolve_fields_and_methods) {
- mirror::ArtField* field = class_linker->ResolveField(dex_file, it.GetMemberIndex(),
- dex_cache, class_loader, false);
- if (field == NULL) {
- CheckAndClearResolveException(soa.Self());
- }
+ }
+ it.Next();
+ }
+ if (resolve_fields_and_methods) {
+ while (it.HasNextDirectMethod()) {
+ mirror::ArtMethod* method = class_linker->ResolveMethod(dex_file, it.GetMemberIndex(),
+ dex_cache, class_loader,
+ NullHandle<mirror::ArtMethod>(),
+ it.GetMethodInvokeType(class_def));
+ if (method == NULL) {
+ CheckAndClearResolveException(soa.Self());
}
it.Next();
}
- if (resolve_fields_and_methods) {
- while (it.HasNextDirectMethod()) {
- mirror::ArtMethod* method = class_linker->ResolveMethod(dex_file, it.GetMemberIndex(),
- dex_cache, class_loader,
- NullHandle<mirror::ArtMethod>(),
- it.GetMethodInvokeType(class_def));
- if (method == NULL) {
- CheckAndClearResolveException(soa.Self());
- }
- it.Next();
+ while (it.HasNextVirtualMethod()) {
+ mirror::ArtMethod* method = class_linker->ResolveMethod(dex_file, it.GetMemberIndex(),
+ dex_cache, class_loader,
+ NullHandle<mirror::ArtMethod>(),
+ it.GetMethodInvokeType(class_def));
+ if (method == NULL) {
+ CheckAndClearResolveException(soa.Self());
}
- while (it.HasNextVirtualMethod()) {
- mirror::ArtMethod* method = class_linker->ResolveMethod(dex_file, it.GetMemberIndex(),
- dex_cache, class_loader,
- NullHandle<mirror::ArtMethod>(),
- it.GetMethodInvokeType(class_def));
- if (method == NULL) {
- CheckAndClearResolveException(soa.Self());
- }
- it.Next();
- }
- DCHECK(!it.HasNext());
+ it.Next();
}
+ DCHECK(!it.HasNext());
}
}
if (requires_constructor_barrier) {
} else if (type->IsStringClass()) {
mirror::String* string = value->AsString();
os << StringPrintf("%p String: %s\n", string,
- PrintableString(string->ToModifiedUtf8()).c_str());
+ PrintableString(string->ToModifiedUtf8().c_str()).c_str());
} else if (type->IsClassClass()) {
mirror::Class* klass = value->AsClass();
os << StringPrintf("%p Class: %s\n", klass, PrettyDescriptor(klass).c_str());
PrettyMethod(obj->AsArtMethod()).c_str());
} else if (obj_class->IsStringClass()) {
os << StringPrintf("%p: java.lang.String %s\n", obj,
- PrintableString(obj->AsString()->ToModifiedUtf8()).c_str());
+ PrintableString(obj->AsString()->ToModifiedUtf8().c_str()).c_str());
} else {
os << StringPrintf("%p: %s\n", obj, PrettyDescriptor(obj_class).c_str());
}
namespace art {
-inline bool ClassLinker::IsInBootClassPath(const char* descriptor) {
- DexFile::ClassPathEntry pair = DexFile::FindInClassPath(descriptor, boot_class_path_);
- return pair.second != nullptr;
-}
-
inline mirror::Class* ClassLinker::FindSystemClass(Thread* self, const char* descriptor) {
return FindClass(self, descriptor, NullHandle<mirror::ClassLoader>());
}
return klass;
}
+typedef std::pair<const DexFile*, const DexFile::ClassDef*> ClassPathEntry;
+
+// Search a collection of DexFiles for a descriptor
+ClassPathEntry FindInClassPath(const char* descriptor,
+ const std::vector<const DexFile*>& class_path) {
+ for (size_t i = 0; i != class_path.size(); ++i) {
+ const DexFile* dex_file = class_path[i];
+ const DexFile::ClassDef* dex_class_def = dex_file->FindClassDef(descriptor);
+ if (dex_class_def != NULL) {
+ return ClassPathEntry(dex_file, dex_class_def);
+ }
+ }
+ // TODO: remove reinterpret_cast when issue with -std=gnu++0x host issue resolved
+ return ClassPathEntry(reinterpret_cast<const DexFile*>(NULL),
+ reinterpret_cast<const DexFile::ClassDef*>(NULL));
+}
+
mirror::Class* ClassLinker::FindClass(Thread* self, const char* descriptor,
Handle<mirror::ClassLoader> class_loader) {
DCHECK_NE(*descriptor, '\0') << "descriptor is empty string";
}
// Find the class in the loaded classes table.
mirror::Class* klass = LookupClass(descriptor, class_loader.Get());
- if (klass != NULL) {
+ if (klass != nullptr) {
return EnsureResolved(self, descriptor, klass);
}
// Class is not yet loaded.
if (descriptor[0] == '[') {
return CreateArrayClass(self, descriptor, class_loader);
} else if (class_loader.Get() == nullptr) {
- DexFile::ClassPathEntry pair = DexFile::FindInClassPath(descriptor, boot_class_path_);
- if (pair.second != NULL) {
+ ClassPathEntry pair = FindInClassPath(descriptor, boot_class_path_);
+ if (pair.second != nullptr) {
StackHandleScope<1> hs(self);
return DefineClass(descriptor, NullHandle<mirror::ClassLoader>(), *pair.first, *pair.second);
}
} else if (Runtime::Current()->UseCompileTimeClassPath()) {
- // First try the boot class path, we check the descriptor first to avoid an unnecessary
- // throw of a NoClassDefFoundError.
- if (IsInBootClassPath(descriptor)) {
- mirror::Class* system_class = FindSystemClass(self, descriptor);
- CHECK(system_class != NULL);
- return system_class;
+ // First try with the bootstrap class loader.
+ if (class_loader.Get() != nullptr) {
+ klass = LookupClass(descriptor, nullptr);
+ if (klass != nullptr) {
+ return EnsureResolved(self, descriptor, klass);
+ }
+ }
+ // If the lookup failed search the boot class path. We don't perform a recursive call to avoid a NoClassDefFoundError being allocated.
+ ClassPathEntry pair = FindInClassPath(descriptor, boot_class_path_);
+ if (pair.second != nullptr) {
+ StackHandleScope<1> hs(self);
+ return DefineClass(descriptor, NullHandle<mirror::ClassLoader>(), *pair.first, *pair.second);
}
// Next try the compile time class path.
const std::vector<const DexFile*>* class_path;
soa.AddLocalReference<jobject>(class_loader.Get()));
class_path = &Runtime::Current()->GetCompileTimeClassPath(jclass_loader.get());
}
-
- DexFile::ClassPathEntry pair = DexFile::FindInClassPath(descriptor, *class_path);
- if (pair.second != NULL) {
+ pair = FindInClassPath(descriptor, *class_path);
+ if (pair.second != nullptr) {
return DefineClass(descriptor, class_loader, *pair.first, *pair.second);
}
-
} else {
ScopedObjectAccessUnchecked soa(self);
ScopedLocalRef<jobject> class_loader_object(soa.Env(),
soa.AddLocalReference<jobject>(class_loader.Get()));
std::string class_name_string(DescriptorToDot(descriptor));
- ScopedLocalRef<jobject> result(soa.Env(), NULL);
+ ScopedLocalRef<jobject> result(soa.Env(), nullptr);
{
ScopedThreadStateChange tsc(self, kNative);
ScopedLocalRef<jobject> class_name_object(soa.Env(),
soa.Env()->NewStringUTF(class_name_string.c_str()));
- if (class_name_object.get() == NULL) {
- return NULL;
+ if (class_name_object.get() == nullptr) {
+ return nullptr;
}
- CHECK(class_loader_object.get() != NULL);
+ CHECK(class_loader_object.get() != nullptr);
result.reset(soa.Env()->CallObjectMethod(class_loader_object.get(),
WellKnownClasses::java_lang_ClassLoader_loadClass,
class_name_object.get()));
}
if (self->IsExceptionPending()) {
// If the ClassLoader threw, pass that exception up.
- return NULL;
- } else if (result.get() == NULL) {
+ return nullptr;
+ } else if (result.get() == nullptr) {
// broken loader - throw NPE to be compatible with Dalvik
- ThrowNullPointerException(NULL, StringPrintf("ClassLoader.loadClass returned null for %s",
+ ThrowNullPointerException(nullptr, StringPrintf("ClassLoader.loadClass returned null for %s",
class_name_string.c_str()).c_str());
- return NULL;
+ return nullptr;
} else {
// success, return mirror::Class*
return soa.Decode<mirror::Class*>(result.get());
}
ThrowNoClassDefFoundError("Class %s not found", PrintableString(descriptor).c_str());
- return NULL;
+ return nullptr;
}
mirror::Class* ClassLinker::DefineClass(const char* descriptor,
// Searching the image dex files/caches failed, we don't want to get into this situation
// often as map searches are faster, so after kMaxFailedDexCacheLookups move all image
// classes into the class table.
- const int32_t kMaxFailedDexCacheLookups = 1000;
+ constexpr uint32_t kMaxFailedDexCacheLookups = 1000;
if (++failed_dex_cache_class_lookups_ > kMaxFailedDexCacheLookups) {
MoveImageClassesToClassTable();
}
// Initialize class linker from one or more images.
void InitFromImage() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
- bool IsInBootClassPath(const char* descriptor);
-
// Finds a class by its descriptor, loading it if necessary.
// If class_loader is null, searches boot_class_path_.
mirror::Class* FindClass(Thread* self, const char* descriptor,
bool dex_cache_image_class_lookup_required_;
// Number of times we've searched dex caches for a class. After a certain number of misses we move
// the classes into the class_table_ to avoid dex cache based searches.
- AtomicInteger failed_dex_cache_class_lookups_;
+ Atomic<uint32_t> failed_dex_cache_class_lookups_;
mirror::Class* LookupClassFromTableLocked(const char* descriptor,
const mirror::ClassLoader* class_loader,
const byte DexFile::kDexMagic[] = { 'd', 'e', 'x', '\n' };
const byte DexFile::kDexMagicVersion[] = { '0', '3', '5', '\0' };
-DexFile::ClassPathEntry DexFile::FindInClassPath(const char* descriptor,
- const ClassPath& class_path) {
- for (size_t i = 0; i != class_path.size(); ++i) {
- const DexFile* dex_file = class_path[i];
- const DexFile::ClassDef* dex_class_def = dex_file->FindClassDef(descriptor);
- if (dex_class_def != NULL) {
- return ClassPathEntry(dex_file, dex_class_def);
- }
- }
- // TODO: remove reinterpret_cast when issue with -std=gnu++0x host issue resolved
- return ClassPathEntry(reinterpret_cast<const DexFile*>(NULL),
- reinterpret_cast<const DexFile::ClassDef*>(NULL));
-}
-
static int OpenAndReadMagic(const char* filename, uint32_t* magic, std::string* error_msg) {
CHECK(magic != NULL);
ScopedFd fd(open(filename, O_RDONLY, 0));
field_ids_(reinterpret_cast<const FieldId*>(base + header_->field_ids_off_)),
method_ids_(reinterpret_cast<const MethodId*>(base + header_->method_ids_off_)),
proto_ids_(reinterpret_cast<const ProtoId*>(base + header_->proto_ids_off_)),
- class_defs_(reinterpret_cast<const ClassDef*>(base + header_->class_defs_off_)) {
+ class_defs_(reinterpret_cast<const ClassDef*>(base + header_->class_defs_off_)),
+ find_class_def_misses_(0),
+ class_def_index_(nullptr),
+ build_class_def_index_mutex_("DexFile index creation mutex") {
CHECK(begin_ != NULL) << GetLocation();
CHECK_GT(size_, 0U) << GetLocation();
}
// that's only called after DetachCurrentThread, which means there's no JNIEnv. We could
// re-attach, but cleaning up these global references is not obviously useful. It's not as if
// the global reference table is otherwise empty!
+ // Remove the index if one were created.
+ delete class_def_index_.LoadRelaxed();
}
bool DexFile::Init(std::string* error_msg) {
}
const DexFile::ClassDef* DexFile::FindClassDef(const char* descriptor) const {
- size_t num_class_defs = NumClassDefs();
+ // If we have an index lookup the descriptor via that as its constant time to search.
+ Index* index = class_def_index_.LoadSequentiallyConsistent();
+ if (index != nullptr) {
+ auto it = index->find(descriptor);
+ return (it == index->end()) ? nullptr : it->second;
+ }
+ // Fast path for rate no class defs case.
+ uint32_t num_class_defs = NumClassDefs();
if (num_class_defs == 0) {
- return NULL;
+ return nullptr;
}
+ // Search for class def with 2 binary searches and then a linear search.
const StringId* string_id = FindStringId(descriptor);
- if (string_id == NULL) {
- return NULL;
- }
- const TypeId* type_id = FindTypeId(GetIndexForStringId(*string_id));
- if (type_id == NULL) {
- return NULL;
+ if (string_id != nullptr) {
+ const TypeId* type_id = FindTypeId(GetIndexForStringId(*string_id));
+ if (type_id != nullptr) {
+ uint16_t type_idx = GetIndexForTypeId(*type_id);
+ for (size_t i = 0; i < num_class_defs; ++i) {
+ const ClassDef& class_def = GetClassDef(i);
+ if (class_def.class_idx_ == type_idx) {
+ return &class_def;
+ }
+ }
+ }
}
- uint16_t type_idx = GetIndexForTypeId(*type_id);
- for (size_t i = 0; i < num_class_defs; ++i) {
- const ClassDef& class_def = GetClassDef(i);
- if (class_def.class_idx_ == type_idx) {
- return &class_def;
+ // A miss. If we've had kMaxFailedDexClassDefLookups misses then build an index to speed things
+ // up. This isn't done eagerly at construction as construction is not performed in multi-threaded
+ // sections of tools like dex2oat. If we're lazy we hopefully increase the chance of balancing
+ // out which thread builds the index.
+ find_class_def_misses_++;
+ const uint32_t kMaxFailedDexClassDefLookups = 100;
+ if (find_class_def_misses_ > kMaxFailedDexClassDefLookups) {
+ MutexLock mu(Thread::Current(), build_class_def_index_mutex_);
+ // Are we the first ones building the index?
+ if (class_def_index_.LoadSequentiallyConsistent() == nullptr) {
+ index = new Index(num_class_defs);
+ for (uint32_t i = 0; i < num_class_defs; ++i) {
+ const ClassDef& class_def = GetClassDef(i);
+ const char* descriptor = GetClassDescriptor(class_def);
+ index->insert(std::make_pair(descriptor, &class_def));
+ }
+ class_def_index_.StoreSequentiallyConsistent(index);
}
}
- return NULL;
+ return nullptr;
}
const DexFile::ClassDef* DexFile::FindClassDef(uint16_t type_idx) const {
#include <memory>
#include <string>
+#include <unordered_map>
#include <vector>
#include "base/logging.h"
#include "invoke_type.h"
#include "jni.h"
#include "modifiers.h"
-#include "safe_map.h"
+#include "utf.h"
namespace art {
DISALLOW_COPY_AND_ASSIGN(AnnotationItem);
};
- typedef std::pair<const DexFile*, const DexFile::ClassDef*> ClassPathEntry;
- typedef std::vector<const DexFile*> ClassPath;
-
- // Search a collection of DexFiles for a descriptor
- static ClassPathEntry FindInClassPath(const char* descriptor,
- const ClassPath& class_path);
-
// Returns the checksum of a file for comparison with GetLocationChecksum().
// For .dex files, this is the header checksum.
// For zip files, this is the classes.dex zip entry CRC32 checksum.
const StringId* FindStringId(const uint16_t* string) const;
// Returns the number of type identifiers in the .dex file.
- size_t NumTypeIds() const {
+ uint32_t NumTypeIds() const {
DCHECK(header_ != NULL) << GetLocation();
return header_->type_ids_size_;
}
return StringDataAndUtf16LengthByIdx(GetProtoId(method_id.proto_idx_).shorty_idx_, length);
}
// Returns the number of class definitions in the .dex file.
- size_t NumClassDefs() const {
+ uint32_t NumClassDefs() const {
DCHECK(header_ != NULL) << GetLocation();
return header_->class_defs_size_;
}
// Points to the base of the class definition list.
const ClassDef* const class_defs_;
+
+ // Number of misses finding a class def from a descriptor.
+ mutable Atomic<uint32_t> find_class_def_misses_;
+
+ struct UTF16HashCmp {
+ // Hash function.
+ size_t operator()(const char* key) const {
+ return ComputeUtf8Hash(key);
+ }
+ // std::equal function.
+ bool operator()(const char* a, const char* b) const {
+ return CompareModifiedUtf8ToModifiedUtf8AsUtf16CodePointValues(a, b) == 0;
+ }
+ };
+ typedef std::unordered_map<const char*, const ClassDef*, UTF16HashCmp, UTF16HashCmp> Index;
+ mutable Atomic<Index*> class_def_index_;
+ mutable Mutex build_class_def_index_mutex_ DEFAULT_MUTEX_ACQUIRED_AFTER;
};
std::ostream& operator<<(std::ostream& os, const DexFile& dex_file);
ObjectId stringObject = request.ReadObjectId();
std::string str(Dbg::StringToUtf8(stringObject));
- VLOG(jdwp) << StringPrintf(" --> %s", PrintableString(str).c_str());
+ VLOG(jdwp) << StringPrintf(" --> %s", PrintableString(str.c_str()).c_str());
expandBufAddUtf8String(pReply, str);
return hash;
}
+int32_t ComputeUtf8Hash(const char* chars) {
+ int32_t hash = 0;
+ while (*chars != '\0') {
+ hash = hash * 31 + GetUtf16FromUtf8(&chars);
+ }
+ return hash;
+}
+
int CompareModifiedUtf8ToUtf16AsCodePointValues(const char* utf8_1, const uint16_t* utf8_2) {
for (;;) {
if (*utf8_1 == '\0') {
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
int32_t ComputeUtf16Hash(const uint16_t* chars, size_t char_count);
+// Compute a hash code of a UTF-8 string.
+int32_t ComputeUtf8Hash(const char* chars);
+
/*
* Retrieve the next UTF-16 character from a UTF-8 string.
*
return result;
}
-std::string PrintableString(const std::string& utf) {
+std::string PrintableString(const char* utf) {
std::string result;
result += '"';
- const char* p = utf.c_str();
+ const char* p = utf;
size_t char_count = CountModifiedUtf8Chars(p);
for (size_t i = 0; i < char_count; ++i) {
uint16_t ch = GetUtf16FromUtf8(&p);
// Returns an ASCII string corresponding to the given UTF-8 string.
// Java escapes are used for non-ASCII characters.
-std::string PrintableString(const std::string& utf8);
+std::string PrintableString(const char* utf8);
// Tests whether 's' starts with 'prefix'.
bool StartsWith(const std::string& s, const char* prefix);