| // Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file | 
 | // for details. All rights reserved. Use of this source code is governed by a | 
 | // BSD-style license that can be found in the LICENSE file. | 
 |  | 
 | #include <map> | 
 | #include <set> | 
 | #include <sstream> | 
 |  | 
 | #include "include/analyze_snapshot_api.h" | 
 | #include "vm/compiler/runtime_api.h" | 
 | #include "vm/dart_api_impl.h" | 
 | #include "vm/json_writer.h" | 
 | #include "vm/object.h" | 
 | #include "vm/object_store.h" | 
 | #include "vm/thread.h" | 
 |  | 
 | namespace dart { | 
 | namespace snapshot_analyzer { | 
 |  | 
 | constexpr intptr_t kSnapshotAnalyzerVersion = 2; | 
 | constexpr intptr_t kStartIndex = 1; | 
 |  | 
 | class FieldVisitor : public ObjectPointerVisitor { | 
 |  public: | 
 |   explicit FieldVisitor(IsolateGroup* isolate_group) | 
 |       : ObjectPointerVisitor(isolate_group) {} | 
 |  | 
 |   void init(std::function<void(ObjectPtr)>* fun) { callback_ = fun; } | 
 |  | 
 |   void VisitPointers(ObjectPtr* first, ObjectPtr* last) override { | 
 |     for (ObjectPtr* current = first; current <= last; current++) { | 
 |       (*callback_)(*current); | 
 |     } | 
 |   } | 
 |  | 
 | #if defined(DART_COMPRESSED_POINTERS) | 
 |   void VisitCompressedPointers(uword heap_base, | 
 |                                CompressedObjectPtr* first, | 
 |                                CompressedObjectPtr* last) override { | 
 |     for (CompressedObjectPtr* current = first; current <= last; current++) { | 
 |       (*callback_)(current->Decompress(heap_base)); | 
 |     } | 
 |   } | 
 | #endif | 
 |  | 
 |  private: | 
 |   std::function<void(ObjectPtr object)>* callback_ = nullptr; | 
 | }; | 
 |  | 
 | class SnapshotAnalyzer { | 
 |  public: | 
 |   explicit SnapshotAnalyzer(const Dart_SnapshotAnalyzerInformation& info) | 
 |       : info_(info) {} | 
 |  | 
 |   // Saves JSON format snapshot information in the output character buffer. | 
 |   void DumpSnapshotInformation(char** buffer, intptr_t* buffer_length); | 
 |  | 
 |  private: | 
 |   void DumpLibrary(const Library& library); | 
 |   void DumpArray(const Array& array, const char* name); | 
 |   void DumpClass(const Class& klass); | 
 |   void DumpFunction(const Function& function); | 
 |   void DumpCode(const Code& code); | 
 |   void DumpField(const Field& field); | 
 |   void DumpString(const String& string); | 
 |   void DumpInstance(const Object& object); | 
 |   void DumpType(const Type& type); | 
 |   void DumpObjectPool(const ObjectPool& pool); | 
 |  | 
 |   void DumpInterestingObjects(); | 
 |   void DumpMetadata(); | 
 |  | 
 |   intptr_t GetObjectId(ObjectPtr obj) { return heap_->GetObjectId(obj); } | 
 |  | 
 |   const Dart_SnapshotAnalyzerInformation& info_; | 
 |   JSONWriter js_; | 
 |   Thread* thread_; | 
 |   Heap* heap_; | 
 | }; | 
 |  | 
 | void SnapshotAnalyzer::DumpLibrary(const Library& library) { | 
 |   js_.PrintProperty("type", "Library"); | 
 |   js_.PrintProperty("url", String::Handle(library.url()).ToCString()); | 
 |  | 
 |   js_.PrintProperty("toplevel_class", | 
 |                     GetObjectId(Object::RawCast(library.toplevel_class()))); | 
 | } | 
 |  | 
 | void SnapshotAnalyzer::DumpArray(const Array& array, const char* name) { | 
 |   js_.OpenArray(name); | 
 |   for (intptr_t i = 0; i < array.Length(); ++i) { | 
 |     js_.PrintValue64(GetObjectId(array.At(i))); | 
 |   } | 
 |   js_.CloseArray(); | 
 | } | 
 |  | 
 | void SnapshotAnalyzer::DumpClass(const Class& klass) { | 
 |   js_.PrintProperty("type", "Class"); | 
 |  | 
 |   js_.PrintProperty("class_id", klass.id()); | 
 |   js_.PrintProperty("name", String::Handle(klass.Name()).ToCString()); | 
 |   js_.PrintProperty("super_class", GetObjectId(klass.SuperClass())); | 
 |  | 
 |   Zone* zone = thread_->zone(); | 
 |   Array& array = Array::Handle(zone); | 
 |  | 
 |   // To avoid depending on layout of VM internal classes we don't use | 
 |   //   js_.PrintProperty("fields", GetObjectId(heap, klass.fields()); | 
 |   // here and instead iterate and refer to them manually. | 
 |   array = klass.fields(); | 
 |   if (!array.IsNull()) { | 
 |     DumpArray(array, "fields"); | 
 |   } | 
 |  | 
 |   array = klass.functions(); | 
 |   if (!array.IsNull()) { | 
 |     DumpArray(array, "functions"); | 
 |   } | 
 |  | 
 |   array = klass.interfaces(); | 
 |   if (!array.IsNull()) { | 
 |     DumpArray(array, "interfaces"); | 
 |   } | 
 |  | 
 |   Library& library = Library::Handle(klass.library()); | 
 |   if (!library.IsNull()) { | 
 |     js_.PrintProperty("library", GetObjectId(klass.library())); | 
 |   } | 
 | } | 
 |  | 
 | void SnapshotAnalyzer::DumpFunction(const Function& function) { | 
 |   js_.PrintProperty("type", "Function"); | 
 |   js_.PrintProperty("name", function.ToCString()); | 
 |  | 
 |   js_.PrintProperty("signature", | 
 |                     String::Handle(function.InternalSignature()).ToCString()); | 
 |  | 
 |   js_.PrintProperty("code", GetObjectId(function.CurrentCode())); | 
 |   if (function.IsClosureFunction()) { | 
 |     js_.PrintProperty("parent_function", | 
 |                       GetObjectId(function.parent_function())); | 
 |   } | 
 | } | 
 |  | 
 | void SnapshotAnalyzer::DumpCode(const Code& code) { | 
 |   js_.PrintProperty("type", "Code"); | 
 |   const auto instruction_base = | 
 |       reinterpret_cast<uint64_t>(info_.vm_isolate_instructions); | 
 |  | 
 |   // On different architectures the type of the underlying | 
 |   // dart::uword can result in an unsigned long long vs unsigned long | 
 |   // mismatch. | 
 |   const auto code_addr = static_cast<uint64_t>(code.PayloadStart()); | 
 |   // Invoking code.PayloadStart() for _kDartVmSnapshotInstructions | 
 |   // when the tree has been shaken always returns 0 | 
 |   if (code_addr == 0) { | 
 |     js_.PrintProperty64("offset", 0); | 
 |     js_.PrintProperty64("size", 0); | 
 |     js_.PrintProperty("section", "_kDartVmSnapshotInstructions"); | 
 |   } else { | 
 |     js_.PrintProperty64("offset", code_addr - instruction_base); | 
 |     js_.PrintProperty64("size", static_cast<uint64_t>(code.Size())); | 
 |     js_.PrintProperty("section", "_kDartIsolateSnapshotInstructions"); | 
 |   } | 
 | } | 
 |  | 
 | void SnapshotAnalyzer::DumpField(const Field& field) { | 
 |   js_.PrintProperty("type", "Field"); | 
 |   js_.PrintProperty("name", String::Handle(field.name()).ToCString()); | 
 |   js_.PrintProperty64("type_class", GetObjectId(field.type())); | 
 |   if (field.is_static()) { | 
 |     js_.PrintProperty("instance", GetObjectId(field.StaticValue())); | 
 |   } | 
 |   if (field.HasInitializerFunction()) { | 
 |     js_.PrintProperty("initializer_function", | 
 |                       GetObjectId(field.InitializerFunction())); | 
 |   } | 
 | } | 
 |  | 
 | void SnapshotAnalyzer::DumpString(const String& string) { | 
 |   js_.PrintProperty("type", "String"); | 
 |   js_.PrintProperty("value", string.ToCString()); | 
 | } | 
 |  | 
 | void SnapshotAnalyzer::DumpInstance(const Object& object) { | 
 |   js_.PrintProperty("type", "Instance"); | 
 |  | 
 |   js_.PrintProperty("class", GetObjectId(object.clazz())); | 
 |  | 
 |   FieldVisitor visitor(thread_->isolate_group()); | 
 |   // Two phase algorithm, first discover all relevant objects | 
 |   // and assign ids, then write them out. | 
 |   std::function<void(ObjectPtr)> print_reference = [&](ObjectPtr value) { | 
 |     if (!value.IsHeapObject()) return; | 
 |     intptr_t index = GetObjectId(value); | 
 |     js_.PrintValue64(index); | 
 |   }; | 
 |   visitor.init(&print_reference); | 
 |  | 
 |   js_.OpenArray("references"); | 
 |   object.ptr().untag()->VisitPointers(&visitor); | 
 |   js_.CloseArray(); | 
 | } | 
 |  | 
 | void SnapshotAnalyzer::DumpType(const Type& type) { | 
 |   js_.PrintProperty("type", "Type"); | 
 |  | 
 |   js_.PrintProperty("type_class", GetObjectId(type.type_class())); | 
 |  | 
 |   const TypeArguments& arguments = TypeArguments::Handle(type.arguments()); | 
 |   js_.OpenArray("type_arguments"); | 
 |   for (intptr_t i = 0; i < arguments.Length(); ++i) { | 
 |     js_.PrintValue64(GetObjectId(arguments.TypeAt(i))); | 
 |   } | 
 |   js_.CloseArray(); | 
 | } | 
 |  | 
 | void SnapshotAnalyzer::DumpObjectPool(const ObjectPool& pool) { | 
 |   js_.PrintProperty("type", "ObjectPool"); | 
 |   js_.OpenArray("references"); | 
 |   for (intptr_t i = 0; i < pool.Length(); ++i) { | 
 |     if (pool.TypeAt(i) == ObjectPool::EntryType::kTaggedObject) { | 
 |       // We write (index, offset, value) triplets. | 
 |       js_.PrintValue64(i); | 
 |       js_.PrintValue64(pool.OffsetFromIndex(i)); | 
 |       js_.PrintValue64(GetObjectId(pool.ObjectAt(i))); | 
 |     } | 
 |   } | 
 |   js_.CloseArray(); | 
 | } | 
 |  | 
 | void SnapshotAnalyzer::DumpInterestingObjects() { | 
 |   Zone* zone = thread_->zone(); | 
 |  | 
 |   heap_->ResetObjectIdTable(); | 
 |   std::vector<const Object*> discovered_objects; | 
 |   Object& object = Object::Handle(zone); | 
 |   { | 
 |     NoSafepointScope ns(thread_); | 
 |  | 
 |     FieldVisitor visitor(thread_->isolate_group()); | 
 |     std::function<void(ObjectPtr)> handle_object = [&](ObjectPtr value) { | 
 |       if (!value.IsHeapObject()) return; | 
 |  | 
 |       // Ensure we never handle an object more than once. | 
 |       if (heap_->GetObjectId(value) != 0) return; | 
 |  | 
 |       heap_->SetObjectId(value, kStartIndex + discovered_objects.size()); | 
 |       discovered_objects.push_back(&Object::Handle(zone, value)); | 
 |  | 
 |       // Ensure all references of this object are visited first. | 
 |       value->untag()->VisitPointers(&visitor); | 
 |     }; | 
 |     visitor.init(&handle_object); | 
 |  | 
 |     // BEGIN Visit all things we are interested in. | 
 |  | 
 |     // - All constants reachable via object pool | 
 |     object = thread_->isolate_group()->object_store()->global_object_pool(); | 
 |     handle_object(object.ptr()); | 
 |  | 
 |     // - All libraries | 
 |     object = thread_->isolate_group()->object_store()->libraries(); | 
 |     object = GrowableObjectArray::Cast(object).data(); | 
 |     object.ptr().untag()->VisitPointers(&visitor); | 
 |  | 
 |     // - All classes | 
 |     auto class_table = thread_->isolate_group()->class_table(); | 
 |     for (intptr_t cid = 0; cid < class_table->NumCids(); ++cid) { | 
 |       if (!class_table->HasValidClassAt(cid)) continue; | 
 |       object = class_table->At(cid); | 
 |       handle_object(object.ptr()); | 
 |     } | 
 |   } | 
 |  | 
 |   // Print information about objects | 
 |   js_.OpenArray("objects"); | 
 |  | 
 |   // The 0 object id is used in the VM's weak hashmap implementation | 
 |   // to indicate no value. | 
 |   js_.OpenObject(); | 
 |   js_.PrintProperty("type", "NoValue"); | 
 |   js_.CloseObject(); | 
 |  | 
 |   for (size_t id = 0; id < discovered_objects.size(); ++id) { | 
 |     const auto* object = discovered_objects[id]; | 
 |     js_.OpenObject(); | 
 |     // TODO(balid): Remove this as it can be inferred from the array position. | 
 |     // Used for manual debugging at the moment. | 
 |     js_.PrintProperty64("id", id + kStartIndex); | 
 |     // Order matters here, Strings are a subtype of Instance, for example. | 
 |     if (object->IsNull()) { | 
 |       js_.PrintProperty("type", "Null"); | 
 |     } else if (object->IsLibrary()) { | 
 |       DumpLibrary(Library::Cast(*object)); | 
 |     } else if (object->IsObjectPool()) { | 
 |       DumpObjectPool(ObjectPool::Cast(*object)); | 
 |     } else if (object->IsClass()) { | 
 |       DumpClass(Class::Cast(*object)); | 
 |     } else if (object->IsFunction()) { | 
 |       DumpFunction(Function::Cast(*object)); | 
 |     } else if (object->IsCode()) { | 
 |       DumpCode(Code::Cast(*object)); | 
 |     } else if (object->IsField()) { | 
 |       DumpField(Field::Cast(*object)); | 
 |     } else if (object->IsString()) { | 
 |       DumpString(String::Cast(*object)); | 
 |     } else if (object->IsArray()) { | 
 |       js_.PrintProperty("type", "Array"); | 
 |       const Array& array = Array::Handle(Array::RawCast(object->ptr())); | 
 |       DumpArray(array, "elements"); | 
 |     } else if (object->IsType()) { | 
 |       DumpType(Type::Cast(*object)); | 
 |     } else if (object->IsInstance()) { | 
 |       DumpInstance(*object); | 
 |     } | 
 |  | 
 |     js_.CloseObject(); | 
 |   } | 
 |   js_.CloseArray(); | 
 | } | 
 |  | 
 | void SnapshotAnalyzer::DumpMetadata() { | 
 |   js_.OpenObject("metadata"); | 
 |   js_.OpenObject("offsets"); | 
 |   js_.OpenObject("thread"); | 
 |   // TODO(balid): Use `dart::compiler::target::` versions. | 
 |   js_.PrintProperty("isolate", Thread::isolate_offset()); | 
 |   js_.PrintProperty("isolate_group", Thread::isolate_group_offset()); | 
 |   js_.PrintProperty("dispatch_table_array", | 
 |                     Thread::dispatch_table_array_offset()); | 
 |   js_.CloseObject(); | 
 |   js_.OpenObject("isolate_group"); | 
 |   js_.PrintProperty("class_table", IsolateGroup::class_table_offset()); | 
 |   js_.PrintProperty("cached_class_table", | 
 |                     IsolateGroup::cached_class_table_table_offset()); | 
 |   js_.PrintProperty("object_store_offset", IsolateGroup::object_store_offset()); | 
 |   js_.CloseObject(); | 
 |   js_.CloseObject(); | 
 |   js_.PrintProperty64("word_size", dart::compiler::target::kWordSize); | 
 |   js_.PrintProperty64("compressed_word_size", | 
 |                       dart::compiler::target::kCompressedWordSize); | 
 |   js_.PrintProperty64("analyzer_version", kSnapshotAnalyzerVersion); | 
 |   js_.CloseObject(); | 
 | } | 
 |  | 
 | // TODO(#47924): Add processing of the entires in the dispatch table. | 
 | // Below is an example skeleton | 
 | // void DumpDispatchTable(dart::Thread* thread) { | 
 | //   auto dispatch = thread->isolate_group()->dispatch_table(); | 
 | //   auto length = dispatch->length(); | 
 | // We must unbias the array entries so we don't crash on null access. | 
 | //   auto entries = dispatch->ArrayOrigin() - DispatchTable::kOriginElement; | 
 | //   for (intptr_t i = 0; i < length; i++) { | 
 | //     OS::Print("0x%lx at %ld\n", entries[i], i); | 
 | //   } | 
 | // } | 
 |  | 
 | void SnapshotAnalyzer::DumpSnapshotInformation(char** buffer, | 
 |                                                intptr_t* buffer_length) { | 
 |   thread_ = Thread::Current(); | 
 |   heap_ = thread_->isolate_group()->heap(); | 
 |   DARTSCOPE(thread_); | 
 |  | 
 |   // Open empty object so output is valid/parsable JSON. | 
 |   js_.OpenObject(); | 
 |   js_.OpenObject("snapshot_data"); | 
 |   // Base addresses of the snapshot data, useful to calculate relative offsets. | 
 |   js_.PrintfProperty("vm_data", "%p", info_.vm_snapshot_data); | 
 |   js_.PrintfProperty("vm_instructions", "%p", info_.vm_snapshot_instructions); | 
 |   js_.PrintfProperty("isolate_data", "%p", info_.vm_isolate_data); | 
 |   js_.PrintfProperty("isolate_instructions", "%p", | 
 |                      info_.vm_isolate_instructions); | 
 |   js_.CloseObject(); | 
 |  | 
 |   { | 
 |     // Debug builds assert that our thread has a lock before accessing | 
 |     // vm internal fields. | 
 |     SafepointReadRwLocker ml(thread_, thread_->isolate_group()->program_lock()); | 
 |     DumpInterestingObjects(); | 
 |     DumpMetadata(); | 
 |   } | 
 |  | 
 |   // Close our empty object. | 
 |   js_.CloseObject(); | 
 |  | 
 |   // Give ownership to caller. | 
 |   js_.Steal(buffer, buffer_length); | 
 | } | 
 |  | 
 | void Dart_DumpSnapshotInformationAsJson( | 
 |     const Dart_SnapshotAnalyzerInformation& info, | 
 |     char** out, | 
 |     intptr_t* out_len) { | 
 |   SnapshotAnalyzer analyzer(info); | 
 |   analyzer.DumpSnapshotInformation(out, out_len); | 
 | } | 
 |  | 
 | }  // namespace snapshot_analyzer | 
 | }  // namespace dart |