// Copyright 2018 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/api/api-inl.h" #include "src/heap/spaces.h" #include "test/cctest/cctest.h" #include "tools/debug_helper/debug-helper.h" namespace v8 { namespace internal { namespace { namespace d = v8::debug_helper; uintptr_t memory_fail_start = 0; uintptr_t memory_fail_end = 0; class MemoryFailureRegion { public: MemoryFailureRegion(uintptr_t start, uintptr_t end) { memory_fail_start = start; memory_fail_end = end; } ~MemoryFailureRegion() { memory_fail_start = 0; memory_fail_end = 0; } }; // Implement the memory-reading callback. This one just fetches memory from the // current process, but a real implementation for a debugging extension would // fetch memory from the debuggee process or crash dump. d::MemoryAccessResult ReadMemory(uintptr_t address, uint8_t* destination, size_t byte_count) { if (address >= memory_fail_start && address <= memory_fail_end) { // Simulate failure to read debuggee memory. return d::MemoryAccessResult::kAddressValidButInaccessible; } memcpy(destination, reinterpret_cast(address), byte_count); return d::MemoryAccessResult::kOk; } void CheckProp(const d::ObjectProperty& property, const char* expected_type, const char* expected_name, d::PropertyKind expected_kind = d::PropertyKind::kSingle, size_t expected_num_values = 1) { CHECK_EQ(property.num_values, expected_num_values); CHECK(property.type == std::string("v8::internal::TaggedValue") || property.type == std::string(expected_type)); CHECK(property.decompressed_type == std::string(expected_type)); CHECK(property.kind == expected_kind); CHECK(property.name == std::string(expected_name)); } template void CheckProp(const d::ObjectProperty& property, const char* expected_type, const char* expected_name, TValue expected_value) { CheckProp(property, expected_type, expected_name); CHECK(*reinterpret_cast(property.address) == expected_value); } } // namespace TEST(GetObjectProperties) { CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); LocalContext context; d::Roots roots{0, 0, 0, 0}; // We don't know the heap roots. v8::Local v = CompileRun("42"); Handle o = v8::Utils::OpenHandle(*v); d::ObjectPropertiesResultPtr props = d::GetObjectProperties(o->ptr(), &ReadMemory, roots); CHECK(props->type_check_result == d::TypeCheckResult::kSmi); CHECK(props->brief == std::string("42 (0x2a)")); CHECK(props->type == std::string("v8::internal::Smi")); CHECK_EQ(props->num_properties, 0); v = CompileRun("[\"a\", \"b\"]"); o = v8::Utils::OpenHandle(*v); props = d::GetObjectProperties(o->ptr(), &ReadMemory, roots); CHECK(props->type_check_result == d::TypeCheckResult::kUsedMap); CHECK(props->type == std::string("v8::internal::JSArray")); CHECK_EQ(props->num_properties, 4); CheckProp(*props->properties[0], "v8::internal::Map", "map"); CheckProp(*props->properties[1], "v8::internal::Object", "properties_or_hash"); CheckProp(*props->properties[2], "v8::internal::FixedArrayBase", "elements"); CheckProp(*props->properties[3], "v8::internal::Object", "length", static_cast(IntToSmi(2))); // We need to supply a root address for decompression before reading the // elements from the JSArray. roots.any_heap_pointer = o->ptr(); i::Tagged_t properties_or_hash = *reinterpret_cast(props->properties[1]->address); i::Tagged_t elements = *reinterpret_cast(props->properties[2]->address); // The properties_or_hash_code field should be an empty fixed array. Since // that is at a known offset, we should be able to detect it even without // any ability to read memory. { MemoryFailureRegion failure(0, UINTPTR_MAX); props = d::GetObjectProperties(properties_or_hash, &ReadMemory, roots); CHECK(props->type_check_result == d::TypeCheckResult::kObjectPointerValidButInaccessible); CHECK(props->type == std::string("v8::internal::Object")); CHECK_EQ(props->num_properties, 0); CHECK(std::string(props->brief).substr(0, 21) == std::string("maybe EmptyFixedArray")); // Provide a heap root so the API can be more sure. roots.read_only_space = reinterpret_cast(reinterpret_cast(isolate) ->heap() ->read_only_space() ->first_page()); props = d::GetObjectProperties(properties_or_hash, &ReadMemory, roots); CHECK(props->type_check_result == d::TypeCheckResult::kObjectPointerValidButInaccessible); CHECK(props->type == std::string("v8::internal::Object")); CHECK_EQ(props->num_properties, 0); CHECK(std::string(props->brief).substr(0, 15) == std::string("EmptyFixedArray")); } props = d::GetObjectProperties(elements, &ReadMemory, roots); CHECK(props->type_check_result == d::TypeCheckResult::kUsedMap); CHECK(props->type == std::string("v8::internal::FixedArray")); CHECK_EQ(props->num_properties, 3); CheckProp(*props->properties[0], "v8::internal::Map", "map"); CheckProp(*props->properties[1], "v8::internal::Object", "length", static_cast(IntToSmi(2))); CheckProp(*props->properties[2], "v8::internal::Object", "objects", d::PropertyKind::kArrayOfKnownSize, 2); // Get the second string value from the FixedArray. i::Tagged_t second_string_address = *reinterpret_cast( props->properties[2]->address + sizeof(i::Tagged_t)); props = d::GetObjectProperties(second_string_address, &ReadMemory, roots); CHECK(props->type_check_result == d::TypeCheckResult::kUsedMap); CHECK(props->type == std::string("v8::internal::String")); CHECK_EQ(props->num_properties, 3); CheckProp(*props->properties[0], "v8::internal::Map", "map"); CheckProp(*props->properties[1], "uint32_t", "hash_field"); CheckProp(*props->properties[2], "int32_t", "length", 1); // Read the second string again, using a type hint instead of the map. All of // its properties should match what we read last time. d::ObjectPropertiesResultPtr props2; { uintptr_t map_address = d::GetObjectProperties( *reinterpret_cast(props->properties[0]->address), &ReadMemory, roots) ->properties[0] ->address; MemoryFailureRegion failure(map_address, map_address + i::Map::kSize); props2 = d::GetObjectProperties(second_string_address, &ReadMemory, roots, "v8::internal::String"); CHECK(props2->type_check_result == d::TypeCheckResult::kUsedTypeHint); CHECK(props2->type == std::string("v8::internal::String")); CHECK_EQ(props2->num_properties, 3); CheckProp(*props2->properties[0], "v8::internal::Map", "map", *reinterpret_cast(props->properties[0]->address)); CheckProp(*props2->properties[1], "uint32_t", "hash_field", *reinterpret_cast(props->properties[1]->address)); CheckProp(*props2->properties[2], "int32_t", "length", 1); } // Try a weak reference. props2 = d::GetObjectProperties(second_string_address | kWeakHeapObjectMask, &ReadMemory, roots); std::string weak_ref_prefix = "weak ref to "; CHECK(weak_ref_prefix + props->brief == props2->brief); CHECK(props2->type_check_result == d::TypeCheckResult::kUsedMap); CHECK(props2->type == std::string("v8::internal::String")); CHECK_EQ(props2->num_properties, 3); CheckProp(*props2->properties[0], "v8::internal::Map", "map", *reinterpret_cast(props->properties[0]->address)); CheckProp(*props2->properties[1], "uint32_t", "hash_field", *reinterpret_cast(props->properties[1]->address)); CheckProp(*props2->properties[2], "int32_t", "length", 1); } } // namespace internal } // namespace v8