// Copyright 2012 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 #include #include #include #include #include #include #include #include #ifdef ENABLE_VTUNE_JIT_INTERFACE #include "src/third_party/vtune/v8-vtune.h" #endif #include "src/d8.h" #include "src/ostreams.h" #include "include/libplatform/libplatform.h" #include "include/libplatform/v8-tracing.h" #include "src/api.h" #include "src/base/cpu.h" #include "src/base/debug/stack_trace.h" #include "src/base/logging.h" #include "src/base/platform/platform.h" #include "src/base/platform/time.h" #include "src/base/sys-info.h" #include "src/basic-block-profiler.h" #include "src/debug/debug-interface.h" #include "src/interpreter/interpreter.h" #include "src/list-inl.h" #include "src/msan.h" #include "src/objects-inl.h" #include "src/snapshot/natives.h" #include "src/utils.h" #include "src/v8.h" #ifdef V8_INSPECTOR_ENABLED #include "include/v8-inspector.h" #endif // V8_INSPECTOR_ENABLED #if !defined(_WIN32) && !defined(_WIN64) #include // NOLINT #else #include // NOLINT #if defined(_MSC_VER) #include // NOLINT #endif // defined(_MSC_VER) #endif // !defined(_WIN32) && !defined(_WIN64) #ifndef DCHECK #define DCHECK(condition) assert(condition) #endif #ifndef CHECK #define CHECK(condition) assert(condition) #endif namespace v8 { namespace { const int MB = 1024 * 1024; const int kMaxWorkers = 50; const int kMaxSerializerMemoryUsage = 1 * MB; // Arbitrary maximum for testing. #define USE_VM 1 #define VM_THRESHOLD 65536 // TODO(titzer): allocations should fail if >= 2gb because of // array buffers storing the lengths as a SMI internally. #define TWO_GB (2u * 1024u * 1024u * 1024u) class ShellArrayBufferAllocator : public v8::ArrayBuffer::Allocator { public: virtual void* Allocate(size_t length) { #if USE_VM if (RoundToPageSize(&length)) { void* data = VirtualMemoryAllocate(length); #if DEBUG if (data) { // In debug mode, check the memory is zero-initialized. size_t limit = length / sizeof(uint64_t); uint64_t* ptr = reinterpret_cast(data); for (size_t i = 0; i < limit; i++) { DCHECK_EQ(0u, ptr[i]); } } #endif return data; } #endif void* data = AllocateUninitialized(length); return data == NULL ? data : memset(data, 0, length); } virtual void* AllocateUninitialized(size_t length) { #if USE_VM if (RoundToPageSize(&length)) return VirtualMemoryAllocate(length); #endif // Work around for GCC bug on AIX // See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79839 #if V8_OS_AIX && _LINUX_SOURCE_COMPAT return __linux_malloc(length); #else return malloc(length); #endif } virtual void Free(void* data, size_t length) { #if USE_VM if (RoundToPageSize(&length)) { base::VirtualMemory::ReleaseRegion(data, length); return; } #endif free(data); } // If {length} is at least {VM_THRESHOLD}, round up to next page size // and return {true}. Otherwise return {false}. bool RoundToPageSize(size_t* length) { const size_t kPageSize = base::OS::CommitPageSize(); if (*length >= VM_THRESHOLD && *length < TWO_GB) { *length = ((*length + kPageSize - 1) / kPageSize) * kPageSize; return true; } return false; } #if USE_VM void* VirtualMemoryAllocate(size_t length) { void* data = base::VirtualMemory::ReserveRegion(length); if (data && !base::VirtualMemory::CommitRegion(data, length, false)) { base::VirtualMemory::ReleaseRegion(data, length); return nullptr; } MSAN_MEMORY_IS_INITIALIZED(data, length); return data; } #endif }; class MockArrayBufferAllocator : public v8::ArrayBuffer::Allocator { public: void* Allocate(size_t length) override { size_t actual_length = length > 10 * MB ? 1 : length; void* data = AllocateUninitialized(actual_length); return data == NULL ? data : memset(data, 0, actual_length); } void* AllocateUninitialized(size_t length) override { return length > 10 * MB ? malloc(1) : malloc(length); } void Free(void* p, size_t) override { free(p); } }; // Predictable v8::Platform implementation. All background and foreground // tasks are run immediately, delayed tasks are not executed at all. class PredictablePlatform : public Platform { public: PredictablePlatform() {} void CallOnBackgroundThread(Task* task, ExpectedRuntime expected_runtime) override { task->Run(); delete task; } void CallOnForegroundThread(v8::Isolate* isolate, Task* task) override { task->Run(); delete task; } void CallDelayedOnForegroundThread(v8::Isolate* isolate, Task* task, double delay_in_seconds) override { delete task; } void CallIdleOnForegroundThread(v8::Isolate* isolate, IdleTask* task) override { UNREACHABLE(); } bool IdleTasksEnabled(v8::Isolate* isolate) override { return false; } double MonotonicallyIncreasingTime() override { return synthetic_time_in_sec_ += 0.00001; } using Platform::AddTraceEvent; uint64_t AddTraceEvent(char phase, const uint8_t* categoryEnabledFlag, const char* name, const char* scope, uint64_t id, uint64_t bind_id, int numArgs, const char** argNames, const uint8_t* argTypes, const uint64_t* argValues, unsigned int flags) override { return 0; } void UpdateTraceEventDuration(const uint8_t* categoryEnabledFlag, const char* name, uint64_t handle) override {} const uint8_t* GetCategoryGroupEnabled(const char* name) override { static uint8_t no = 0; return &no; } const char* GetCategoryGroupName( const uint8_t* categoryEnabledFlag) override { static const char* dummy = "dummy"; return dummy; } private: double synthetic_time_in_sec_ = 0.0; DISALLOW_COPY_AND_ASSIGN(PredictablePlatform); }; v8::Platform* g_platform = NULL; static Local Throw(Isolate* isolate, const char* message) { return isolate->ThrowException( String::NewFromUtf8(isolate, message, NewStringType::kNormal) .ToLocalChecked()); } Worker* GetWorkerFromInternalField(Isolate* isolate, Local object) { if (object->InternalFieldCount() != 1) { Throw(isolate, "this is not a Worker"); return NULL; } Worker* worker = static_cast(object->GetAlignedPointerFromInternalField(0)); if (worker == NULL) { Throw(isolate, "Worker is defunct because main thread is terminating"); return NULL; } return worker; } } // namespace namespace tracing { namespace { // String options that can be used to initialize TraceOptions. const char kRecordUntilFull[] = "record-until-full"; const char kRecordContinuously[] = "record-continuously"; const char kRecordAsMuchAsPossible[] = "record-as-much-as-possible"; const char kRecordModeParam[] = "record_mode"; const char kEnableSystraceParam[] = "enable_systrace"; const char kEnableArgumentFilterParam[] = "enable_argument_filter"; const char kIncludedCategoriesParam[] = "included_categories"; class TraceConfigParser { public: static void FillTraceConfig(v8::Isolate* isolate, platform::tracing::TraceConfig* trace_config, const char* json_str) { HandleScope outer_scope(isolate); Local context = Context::New(isolate); Context::Scope context_scope(context); HandleScope inner_scope(isolate); Local source = String::NewFromUtf8(isolate, json_str, NewStringType::kNormal) .ToLocalChecked(); Local result = JSON::Parse(context, source).ToLocalChecked(); Local trace_config_object = Local::Cast(result); trace_config->SetTraceRecordMode( GetTraceRecordMode(isolate, context, trace_config_object)); if (GetBoolean(isolate, context, trace_config_object, kEnableSystraceParam)) { trace_config->EnableSystrace(); } if (GetBoolean(isolate, context, trace_config_object, kEnableArgumentFilterParam)) { trace_config->EnableArgumentFilter(); } UpdateIncludedCategoriesList(isolate, context, trace_config_object, trace_config); } private: static bool GetBoolean(v8::Isolate* isolate, Local context, Local object, const char* property) { Local value = GetValue(isolate, context, object, property); if (value->IsNumber()) { Local v8_boolean = value->ToBoolean(context).ToLocalChecked(); return v8_boolean->Value(); } return false; } static int UpdateIncludedCategoriesList( v8::Isolate* isolate, Local context, Local object, platform::tracing::TraceConfig* trace_config) { Local value = GetValue(isolate, context, object, kIncludedCategoriesParam); if (value->IsArray()) { Local v8_array = Local::Cast(value); for (int i = 0, length = v8_array->Length(); i < length; ++i) { Local v = v8_array->Get(context, i) .ToLocalChecked() ->ToString(context) .ToLocalChecked(); String::Utf8Value str(v->ToString(context).ToLocalChecked()); trace_config->AddIncludedCategory(*str); } return v8_array->Length(); } return 0; } static platform::tracing::TraceRecordMode GetTraceRecordMode( v8::Isolate* isolate, Local context, Local object) { Local value = GetValue(isolate, context, object, kRecordModeParam); if (value->IsString()) { Local v8_string = value->ToString(context).ToLocalChecked(); String::Utf8Value str(v8_string); if (strcmp(kRecordUntilFull, *str) == 0) { return platform::tracing::TraceRecordMode::RECORD_UNTIL_FULL; } else if (strcmp(kRecordContinuously, *str) == 0) { return platform::tracing::TraceRecordMode::RECORD_CONTINUOUSLY; } else if (strcmp(kRecordAsMuchAsPossible, *str) == 0) { return platform::tracing::TraceRecordMode::RECORD_AS_MUCH_AS_POSSIBLE; } } return platform::tracing::TraceRecordMode::RECORD_UNTIL_FULL; } static Local GetValue(v8::Isolate* isolate, Local context, Local object, const char* property) { Local v8_str = String::NewFromUtf8(isolate, property, NewStringType::kNormal) .ToLocalChecked(); return object->Get(context, v8_str).ToLocalChecked(); } }; } // namespace static platform::tracing::TraceConfig* CreateTraceConfigFromJSON( v8::Isolate* isolate, const char* json_str) { platform::tracing::TraceConfig* trace_config = new platform::tracing::TraceConfig(); TraceConfigParser::FillTraceConfig(isolate, trace_config, json_str); return trace_config; } } // namespace tracing class PerIsolateData { public: explicit PerIsolateData(Isolate* isolate) : isolate_(isolate), realms_(NULL) { HandleScope scope(isolate); isolate->SetData(0, this); } ~PerIsolateData() { isolate_->SetData(0, NULL); // Not really needed, just to be sure... } inline static PerIsolateData* Get(Isolate* isolate) { return reinterpret_cast(isolate->GetData(0)); } class RealmScope { public: explicit RealmScope(PerIsolateData* data); ~RealmScope(); private: PerIsolateData* data_; }; private: friend class Shell; friend class RealmScope; Isolate* isolate_; int realm_count_; int realm_current_; int realm_switch_; Global* realms_; Global realm_shared_; int RealmIndexOrThrow(const v8::FunctionCallbackInfo& args, int arg_offset); int RealmFind(Local context); }; CounterMap* Shell::counter_map_; base::OS::MemoryMappedFile* Shell::counters_file_ = NULL; CounterCollection Shell::local_counters_; CounterCollection* Shell::counters_ = &local_counters_; base::LazyMutex Shell::context_mutex_; const base::TimeTicks Shell::kInitialTicks = base::TimeTicks::HighResolutionNow(); Global Shell::stringify_function_; base::LazyMutex Shell::workers_mutex_; bool Shell::allow_new_workers_ = true; i::List Shell::workers_; std::vector Shell::externalized_contents_; Global Shell::evaluation_context_; ArrayBuffer::Allocator* Shell::array_buffer_allocator; ShellOptions Shell::options; base::OnceType Shell::quit_once_ = V8_ONCE_INIT; bool CounterMap::Match(void* key1, void* key2) { const char* name1 = reinterpret_cast(key1); const char* name2 = reinterpret_cast(key2); return strcmp(name1, name2) == 0; } ScriptCompiler::CachedData* CompileForCachedData( Local source, Local name, ScriptCompiler::CompileOptions compile_options) { int source_length = source->Length(); uint16_t* source_buffer = new uint16_t[source_length]; source->Write(source_buffer, 0, source_length); int name_length = 0; uint16_t* name_buffer = NULL; if (name->IsString()) { Local name_string = Local::Cast(name); name_length = name_string->Length(); name_buffer = new uint16_t[name_length]; name_string->Write(name_buffer, 0, name_length); } Isolate::CreateParams create_params; create_params.array_buffer_allocator = Shell::array_buffer_allocator; Isolate* temp_isolate = Isolate::New(create_params); ScriptCompiler::CachedData* result = NULL; { Isolate::Scope isolate_scope(temp_isolate); HandleScope handle_scope(temp_isolate); Context::Scope context_scope(Context::New(temp_isolate)); Local source_copy = v8::String::NewFromTwoByte(temp_isolate, source_buffer, v8::NewStringType::kNormal, source_length).ToLocalChecked(); Local name_copy; if (name_buffer) { name_copy = v8::String::NewFromTwoByte(temp_isolate, name_buffer, v8::NewStringType::kNormal, name_length).ToLocalChecked(); } else { name_copy = v8::Undefined(temp_isolate); } ScriptCompiler::Source script_source(source_copy, ScriptOrigin(name_copy)); if (!ScriptCompiler::CompileUnboundScript(temp_isolate, &script_source, compile_options).IsEmpty() && script_source.GetCachedData()) { int length = script_source.GetCachedData()->length; uint8_t* cache = new uint8_t[length]; memcpy(cache, script_source.GetCachedData()->data, length); result = new ScriptCompiler::CachedData( cache, length, ScriptCompiler::CachedData::BufferOwned); } } temp_isolate->Dispose(); delete[] source_buffer; delete[] name_buffer; return result; } // Compile a string within the current v8 context. MaybeLocal