// 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. // Defined when linking against shared lib on Windows. #if defined(USING_V8_SHARED) && !defined(V8_SHARED) #define V8_SHARED #endif #ifdef COMPRESS_STARTUP_DATA_BZ2 #include #endif #include #include #include #include #ifdef V8_SHARED #include #endif // V8_SHARED #ifndef V8_SHARED #include #endif // !V8_SHARED #ifdef V8_SHARED #include "include/v8-testing.h" #endif // V8_SHARED #if !defined(V8_SHARED) && defined(ENABLE_GDB_JIT_INTERFACE) #include "src/gdb-jit.h" #endif #ifdef ENABLE_VTUNE_JIT_INTERFACE #include "src/third_party/vtune/v8-vtune.h" #endif #include "src/d8.h" #include "include/libplatform/libplatform.h" #ifndef V8_SHARED #include "src/api.h" #include "src/base/cpu.h" #include "src/base/logging.h" #include "src/base/platform/platform.h" #include "src/base/sys-info.h" #include "src/basic-block-profiler.h" #include "src/d8-debug.h" #include "src/debug.h" #include "src/natives.h" #include "src/v8.h" #endif // !V8_SHARED #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 namespace v8 { static Handle Throw(Isolate* isolate, const char* message) { return isolate->ThrowException(String::NewFromUtf8(isolate, message)); } 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_; Persistent* realms_; Persistent realm_shared_; int RealmIndexOrThrow(const v8::FunctionCallbackInfo& args, int arg_offset); int RealmFind(Handle context); }; LineEditor *LineEditor::current_ = NULL; LineEditor::LineEditor(Type type, const char* name) : type_(type), name_(name) { if (current_ == NULL || current_->type_ < type) current_ = this; } class DumbLineEditor: public LineEditor { public: explicit DumbLineEditor(Isolate* isolate) : LineEditor(LineEditor::DUMB, "dumb"), isolate_(isolate) { } virtual Handle Prompt(const char* prompt); private: Isolate* isolate_; }; Handle DumbLineEditor::Prompt(const char* prompt) { printf("%s", prompt); #if defined(__native_client__) // Native Client libc is used to being embedded in Chrome and // has trouble recognizing when to flush. fflush(stdout); #endif return Shell::ReadFromStdin(isolate_); } #ifndef V8_SHARED CounterMap* Shell::counter_map_; base::OS::MemoryMappedFile* Shell::counters_file_ = NULL; CounterCollection Shell::local_counters_; CounterCollection* Shell::counters_ = &local_counters_; base::Mutex Shell::context_mutex_; const base::TimeTicks Shell::kInitialTicks = base::TimeTicks::HighResolutionNow(); Persistent Shell::utility_context_; #endif // !V8_SHARED Persistent Shell::evaluation_context_; ShellOptions Shell::options; const char* Shell::kPrompt = "d8> "; #ifndef V8_SHARED const int MB = 1024 * 1024; bool CounterMap::Match(void* key1, void* key2) { const char* name1 = reinterpret_cast(key1); const char* name2 = reinterpret_cast(key2); return strcmp(name1, name2) == 0; } #endif // !V8_SHARED // Converts a V8 value to a C string. const char* Shell::ToCString(const v8::String::Utf8Value& value) { return *value ? *value : ""; } // Compile a string within the current v8 context. Local Shell::CompileString( Isolate* isolate, Local source, Local name, v8::ScriptCompiler::CompileOptions compile_options) { ScriptOrigin origin(name); ScriptCompiler::Source script_source(source, origin); Local script = ScriptCompiler::CompileUnbound(isolate, &script_source, compile_options); // Was caching requested & successful? Then compile again, now with cache. if (script_source.GetCachedData()) { if (compile_options == ScriptCompiler::kProduceCodeCache) { compile_options = ScriptCompiler::kConsumeCodeCache; } else if (compile_options == ScriptCompiler::kProduceParserCache) { compile_options = ScriptCompiler::kConsumeParserCache; } else { DCHECK(false); // A new compile option? } ScriptCompiler::Source cached_source( source, origin, new v8::ScriptCompiler::CachedData( script_source.GetCachedData()->data, script_source.GetCachedData()->length, v8::ScriptCompiler::CachedData::BufferNotOwned)); script = ScriptCompiler::CompileUnbound(isolate, &cached_source, compile_options); } return script; } // Executes a string within the current v8 context. bool Shell::ExecuteString(Isolate* isolate, Handle source, Handle name, bool print_result, bool report_exceptions) { #ifndef V8_SHARED bool FLAG_debugger = i::FLAG_debugger; #else bool FLAG_debugger = false; #endif // !V8_SHARED HandleScope handle_scope(isolate); TryCatch try_catch; options.script_executed = true; if (FLAG_debugger) { // When debugging make exceptions appear to be uncaught. try_catch.SetVerbose(true); } Handle script = Shell::CompileString(isolate, source, name, options.compile_options); if (script.IsEmpty()) { // Print errors that happened during compilation. if (report_exceptions && !FLAG_debugger) ReportException(isolate, &try_catch); return false; } else { PerIsolateData* data = PerIsolateData::Get(isolate); Local realm = Local::New(isolate, data->realms_[data->realm_current_]); realm->Enter(); Handle result = script->BindToCurrentContext()->Run(); realm->Exit(); data->realm_current_ = data->realm_switch_; if (result.IsEmpty()) { DCHECK(try_catch.HasCaught()); // Print errors that happened during execution. if (report_exceptions && !FLAG_debugger) ReportException(isolate, &try_catch); return false; } else { DCHECK(!try_catch.HasCaught()); if (print_result) { #if !defined(V8_SHARED) if (options.test_shell) { #endif if (!result->IsUndefined()) { // If all went well and the result wasn't undefined then print // the returned value. v8::String::Utf8Value str(result); fwrite(*str, sizeof(**str), str.length(), stdout); printf("\n"); } #if !defined(V8_SHARED) } else { v8::TryCatch try_catch; v8::Local context = v8::Local::New(isolate, utility_context_); v8::Context::Scope context_scope(context); Handle global = context->Global(); Handle fun = global->Get(String::NewFromUtf8(isolate, "Stringify")); Handle argv[1] = { result }; Handle s = Handle::Cast(fun)->Call(global, 1, argv); if (try_catch.HasCaught()) return true; v8::String::Utf8Value str(s); fwrite(*str, sizeof(**str), str.length(), stdout); printf("\n"); } #endif } return true; } } } PerIsolateData::RealmScope::RealmScope(PerIsolateData* data) : data_(data) { data_->realm_count_ = 1; data_->realm_current_ = 0; data_->realm_switch_ = 0; data_->realms_ = new Persistent[1]; data_->realms_[0].Reset(data_->isolate_, data_->isolate_->GetEnteredContext()); } PerIsolateData::RealmScope::~RealmScope() { // Drop realms to avoid keeping them alive. for (int i = 0; i < data_->realm_count_; ++i) data_->realms_[i].Reset(); delete[] data_->realms_; if (!data_->realm_shared_.IsEmpty()) data_->realm_shared_.Reset(); } int PerIsolateData::RealmFind(Handle context) { for (int i = 0; i < realm_count_; ++i) { if (realms_[i] == context) return i; } return -1; } int PerIsolateData::RealmIndexOrThrow( const v8::FunctionCallbackInfo& args, int arg_offset) { if (args.Length() < arg_offset || !args[arg_offset]->IsNumber()) { Throw(args.GetIsolate(), "Invalid argument"); return -1; } int index = args[arg_offset]->Int32Value(); if (index < 0 || index >= realm_count_ || realms_[index].IsEmpty()) { Throw(args.GetIsolate(), "Invalid realm index"); return -1; } return index; } #ifndef V8_SHARED // performance.now() returns a time stamp as double, measured in milliseconds. // When FLAG_verify_predictable mode is enabled it returns current value // of Heap::allocations_count(). void Shell::PerformanceNow(const v8::FunctionCallbackInfo& args) { if (i::FLAG_verify_predictable) { Isolate* v8_isolate = args.GetIsolate(); i::Heap* heap = reinterpret_cast(v8_isolate)->heap(); args.GetReturnValue().Set(heap->synthetic_time()); } else { base::TimeDelta delta = base::TimeTicks::HighResolutionNow() - kInitialTicks; args.GetReturnValue().Set(delta.InMillisecondsF()); } } #endif // !V8_SHARED // Realm.current() returns the index of the currently active realm. void Shell::RealmCurrent(const v8::FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); PerIsolateData* data = PerIsolateData::Get(isolate); int index = data->RealmFind(isolate->GetEnteredContext()); if (index == -1) return; args.GetReturnValue().Set(index); } // Realm.owner(o) returns the index of the realm that created o. void Shell::RealmOwner(const v8::FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); PerIsolateData* data = PerIsolateData::Get(isolate); if (args.Length() < 1 || !args[0]->IsObject()) { Throw(args.GetIsolate(), "Invalid argument"); return; } int index = data->RealmFind(args[0]->ToObject()->CreationContext()); if (index == -1) return; args.GetReturnValue().Set(index); } // Realm.global(i) returns the global object of realm i. // (Note that properties of global objects cannot be read/written cross-realm.) void Shell::RealmGlobal(const v8::FunctionCallbackInfo& args) { PerIsolateData* data = PerIsolateData::Get(args.GetIsolate()); int index = data->RealmIndexOrThrow(args, 0); if (index == -1) return; args.GetReturnValue().Set( Local::New(args.GetIsolate(), data->realms_[index])->Global()); } // Realm.create() creates a new realm and returns its index. void Shell::RealmCreate(const v8::FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); PerIsolateData* data = PerIsolateData::Get(isolate); Persistent* old_realms = data->realms_; int index = data->realm_count_; data->realms_ = new Persistent[++data->realm_count_]; for (int i = 0; i < index; ++i) { data->realms_[i].Reset(isolate, old_realms[i]); } delete[] old_realms; Handle global_template = CreateGlobalTemplate(isolate); data->realms_[index].Reset( isolate, Context::New(isolate, NULL, global_template)); args.GetReturnValue().Set(index); } // Realm.dispose(i) disposes the reference to the realm i. void Shell::RealmDispose(const v8::FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); PerIsolateData* data = PerIsolateData::Get(isolate); int index = data->RealmIndexOrThrow(args, 0); if (index == -1) return; if (index == 0 || index == data->realm_current_ || index == data->realm_switch_) { Throw(args.GetIsolate(), "Invalid realm index"); return; } data->realms_[index].Reset(); } // Realm.switch(i) switches to the realm i for consecutive interactive inputs. void Shell::RealmSwitch(const v8::FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); PerIsolateData* data = PerIsolateData::Get(isolate); int index = data->RealmIndexOrThrow(args, 0); if (index == -1) return; data->realm_switch_ = index; } // Realm.eval(i, s) evaluates s in realm i and returns the result. void Shell::RealmEval(const v8::FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); PerIsolateData* data = PerIsolateData::Get(isolate); int index = data->RealmIndexOrThrow(args, 0); if (index == -1) return; if (args.Length() < 2 || !args[1]->IsString()) { Throw(args.GetIsolate(), "Invalid argument"); return; } ScriptCompiler::Source script_source(args[1]->ToString()); Handle script = ScriptCompiler::CompileUnbound( isolate, &script_source); if (script.IsEmpty()) return; Local realm = Local::New(isolate, data->realms_[index]); realm->Enter(); Handle result = script->BindToCurrentContext()->Run(); realm->Exit(); args.GetReturnValue().Set(result); } // Realm.shared is an accessor for a single shared value across realms. void Shell::RealmSharedGet(Local property, const PropertyCallbackInfo& info) { Isolate* isolate = info.GetIsolate(); PerIsolateData* data = PerIsolateData::Get(isolate); if (data->realm_shared_.IsEmpty()) return; info.GetReturnValue().Set(data->realm_shared_); } void Shell::RealmSharedSet(Local property, Local value, const PropertyCallbackInfo& info) { Isolate* isolate = info.GetIsolate(); PerIsolateData* data = PerIsolateData::Get(isolate); data->realm_shared_.Reset(isolate, value); } void Shell::Print(const v8::FunctionCallbackInfo& args) { Write(args); printf("\n"); fflush(stdout); } void Shell::Write(const v8::FunctionCallbackInfo& args) { for (int i = 0; i < args.Length(); i++) { HandleScope handle_scope(args.GetIsolate()); if (i != 0) { printf(" "); } // Explicitly catch potential exceptions in toString(). v8::TryCatch try_catch; Handle str_obj = args[i]->ToString(); if (try_catch.HasCaught()) { try_catch.ReThrow(); return; } v8::String::Utf8Value str(str_obj); int n = static_cast(fwrite(*str, sizeof(**str), str.length(), stdout)); if (n != str.length()) { printf("Error in fwrite\n"); Exit(1); } } } void Shell::Read(const v8::FunctionCallbackInfo& args) { String::Utf8Value file(args[0]); if (*file == NULL) { Throw(args.GetIsolate(), "Error loading file"); return; } Handle source = ReadFile(args.GetIsolate(), *file); if (source.IsEmpty()) { Throw(args.GetIsolate(), "Error loading file"); return; } args.GetReturnValue().Set(source); } Handle Shell::ReadFromStdin(Isolate* isolate) { static const int kBufferSize = 256; char buffer[kBufferSize]; Handle accumulator = String::NewFromUtf8(isolate, ""); int length; while (true) { // Continue reading if the line ends with an escape '\\' or the line has // not been fully read into the buffer yet (does not end with '\n'). // If fgets gets an error, just give up. char* input = NULL; input = fgets(buffer, kBufferSize, stdin); if (input == NULL) return Handle(); length = static_cast(strlen(buffer)); if (length == 0) { return accumulator; } else if (buffer[length-1] != '\n') { accumulator = String::Concat( accumulator, String::NewFromUtf8(isolate, buffer, String::kNormalString, length)); } else if (length > 1 && buffer[length-2] == '\\') { buffer[length-2] = '\n'; accumulator = String::Concat( accumulator, String::NewFromUtf8(isolate, buffer, String::kNormalString, length - 1)); } else { return String::Concat( accumulator, String::NewFromUtf8(isolate, buffer, String::kNormalString, length - 1)); } } } void Shell::Load(const v8::FunctionCallbackInfo& args) { for (int i = 0; i < args.Length(); i++) { HandleScope handle_scope(args.GetIsolate()); String::Utf8Value file(args[i]); if (*file == NULL) { Throw(args.GetIsolate(), "Error loading file"); return; } Handle source = ReadFile(args.GetIsolate(), *file); if (source.IsEmpty()) { Throw(args.GetIsolate(), "Error loading file"); return; } if (!ExecuteString(args.GetIsolate(), source, String::NewFromUtf8(args.GetIsolate(), *file), false, true)) { Throw(args.GetIsolate(), "Error executing file"); return; } } } void Shell::Quit(const v8::FunctionCallbackInfo& args) { int exit_code = args[0]->Int32Value(); OnExit(); exit(exit_code); } void Shell::Version(const v8::FunctionCallbackInfo& args) { args.GetReturnValue().Set( String::NewFromUtf8(args.GetIsolate(), V8::GetVersion())); } void Shell::ReportException(Isolate* isolate, v8::TryCatch* try_catch) { HandleScope handle_scope(isolate); #ifndef V8_SHARED Handle utility_context; bool enter_context = !isolate->InContext(); if (enter_context) { utility_context = Local::New(isolate, utility_context_); utility_context->Enter(); } #endif // !V8_SHARED v8::String::Utf8Value exception(try_catch->Exception()); const char* exception_string = ToCString(exception); Handle message = try_catch->Message(); if (message.IsEmpty()) { // V8 didn't provide any extra information about this error; just // print the exception. printf("%s\n", exception_string); } else { // Print (filename):(line number): (message). v8::String::Utf8Value filename(message->GetScriptOrigin().ResourceName()); const char* filename_string = ToCString(filename); int linenum = message->GetLineNumber(); printf("%s:%i: %s\n", filename_string, linenum, exception_string); // Print line of source code. v8::String::Utf8Value sourceline(message->GetSourceLine()); const char* sourceline_string = ToCString(sourceline); printf("%s\n", sourceline_string); // Print wavy underline (GetUnderline is deprecated). int start = message->GetStartColumn(); for (int i = 0; i < start; i++) { printf(" "); } int end = message->GetEndColumn(); for (int i = start; i < end; i++) { printf("^"); } printf("\n"); v8::String::Utf8Value stack_trace(try_catch->StackTrace()); if (stack_trace.length() > 0) { const char* stack_trace_string = ToCString(stack_trace); printf("%s\n", stack_trace_string); } } printf("\n"); #ifndef V8_SHARED if (enter_context) utility_context->Exit(); #endif // !V8_SHARED } #ifndef V8_SHARED Handle Shell::GetCompletions(Isolate* isolate, Handle text, Handle full) { EscapableHandleScope handle_scope(isolate); v8::Local utility_context = v8::Local::New(isolate, utility_context_); v8::Context::Scope context_scope(utility_context); Handle global = utility_context->Global(); Local fun = global->Get(String::NewFromUtf8(isolate, "GetCompletions")); static const int kArgc = 3; v8::Local evaluation_context = v8::Local::New(isolate, evaluation_context_); Handle argv[kArgc] = { evaluation_context->Global(), text, full }; Local val = Local::Cast(fun)->Call(global, kArgc, argv); return handle_scope.Escape(Local::Cast(val)); } Local Shell::DebugMessageDetails(Isolate* isolate, Handle message) { EscapableHandleScope handle_scope(isolate); v8::Local context = v8::Local::New(isolate, utility_context_); v8::Context::Scope context_scope(context); Handle global = context->Global(); Handle fun = global->Get(String::NewFromUtf8(isolate, "DebugMessageDetails")); static const int kArgc = 1; Handle argv[kArgc] = { message }; Handle val = Handle::Cast(fun)->Call(global, kArgc, argv); return handle_scope.Escape(Local(Handle::Cast(val))); } Local Shell::DebugCommandToJSONRequest(Isolate* isolate, Handle command) { EscapableHandleScope handle_scope(isolate); v8::Local context = v8::Local::New(isolate, utility_context_); v8::Context::Scope context_scope(context); Handle global = context->Global(); Handle fun = global->Get(String::NewFromUtf8(isolate, "DebugCommandToJSONRequest")); static const int kArgc = 1; Handle argv[kArgc] = { command }; Handle val = Handle::Cast(fun)->Call(global, kArgc, argv); return handle_scope.Escape(Local(val)); } int32_t* Counter::Bind(const char* name, bool is_histogram) { int i; for (i = 0; i < kMaxNameSize - 1 && name[i]; i++) name_[i] = static_cast(name[i]); name_[i] = '\0'; is_histogram_ = is_histogram; return ptr(); } void Counter::AddSample(int32_t sample) { count_++; sample_total_ += sample; } CounterCollection::CounterCollection() { magic_number_ = 0xDEADFACE; max_counters_ = kMaxCounters; max_name_size_ = Counter::kMaxNameSize; counters_in_use_ = 0; } Counter* CounterCollection::GetNextCounter() { if (counters_in_use_ == kMaxCounters) return NULL; return &counters_[counters_in_use_++]; } void Shell::MapCounters(v8::Isolate* isolate, const char* name) { counters_file_ = base::OS::MemoryMappedFile::create( name, sizeof(CounterCollection), &local_counters_); void* memory = (counters_file_ == NULL) ? NULL : counters_file_->memory(); if (memory == NULL) { printf("Could not map counters file %s\n", name); Exit(1); } counters_ = static_cast(memory); isolate->SetCounterFunction(LookupCounter); isolate->SetCreateHistogramFunction(CreateHistogram); isolate->SetAddHistogramSampleFunction(AddHistogramSample); } int CounterMap::Hash(const char* name) { int h = 0; int c; while ((c = *name++) != 0) { h += h << 5; h += c; } return h; } Counter* Shell::GetCounter(const char* name, bool is_histogram) { Counter* counter = counter_map_->Lookup(name); if (counter == NULL) { counter = counters_->GetNextCounter(); if (counter != NULL) { counter_map_->Set(name, counter); counter->Bind(name, is_histogram); } } else { DCHECK(counter->is_histogram() == is_histogram); } return counter; } int* Shell::LookupCounter(const char* name) { Counter* counter = GetCounter(name, false); if (counter != NULL) { return counter->ptr(); } else { return NULL; } } void* Shell::CreateHistogram(const char* name, int min, int max, size_t buckets) { return GetCounter(name, true); } void Shell::AddHistogramSample(void* histogram, int sample) { Counter* counter = reinterpret_cast(histogram); counter->AddSample(sample); } void Shell::InstallUtilityScript(Isolate* isolate) { HandleScope scope(isolate); // If we use the utility context, we have to set the security tokens so that // utility, evaluation and debug context can all access each other. v8::Local utility_context = v8::Local::New(isolate, utility_context_); v8::Local evaluation_context = v8::Local::New(isolate, evaluation_context_); utility_context->SetSecurityToken(Undefined(isolate)); evaluation_context->SetSecurityToken(Undefined(isolate)); v8::Context::Scope context_scope(utility_context); if (i::FLAG_debugger) printf("JavaScript debugger enabled\n"); // Install the debugger object in the utility scope i::Debug* debug = reinterpret_cast(isolate)->debug(); debug->Load(); i::Handle debug_context = debug->debug_context(); i::Handle js_debug = i::Handle(debug_context->global_object()); utility_context->Global()->Set(String::NewFromUtf8(isolate, "$debug"), Utils::ToLocal(js_debug)); debug_context->set_security_token( reinterpret_cast(isolate)->heap()->undefined_value()); // Run the d8 shell utility script in the utility context int source_index = i::NativesCollection::GetIndex("d8"); i::Vector shell_source = i::NativesCollection::GetRawScriptSource(source_index); i::Vector shell_source_name = i::NativesCollection::GetScriptName(source_index); Handle source = String::NewFromUtf8(isolate, shell_source.start(), String::kNormalString, shell_source.length()); Handle name = String::NewFromUtf8(isolate, shell_source_name.start(), String::kNormalString, shell_source_name.length()); ScriptOrigin origin(name); Handle