From d6db11fd181061f9ffce4431af5afec0dba2c82c Mon Sep 17 00:00:00 2001 From: kozyatinskiy Date: Mon, 30 Jan 2017 16:19:41 -0800 Subject: [PATCH] [inspector] added test infrastructure and test for es6 modules Test just checks that all basic features are working correctly with modules. BUG=v8:1569 R=dgozman@chromium.org,alph@chromium.org,adamk@chromium.org Review-Url: https://codereview.chromium.org/2663743002 Cr-Commit-Position: refs/heads/master@{#42796} --- src/ast/scopes.cc | 5 + .../get-possible-breakpoints-array-literal.js | 2 +- .../debugger/get-possible-breakpoints.js | 2 +- test/inspector/inspector-test.cc | 19 +- test/inspector/protocol-test.js | 14 +- .../inspector/runtime/es6-module-expected.txt | 236 ++++++++++++++++++ test/inspector/runtime/es6-module.js | 64 +++++ test/inspector/task-runner.cc | 78 ++++-- test/inspector/task-runner.h | 30 ++- 9 files changed, 406 insertions(+), 44 deletions(-) create mode 100644 test/inspector/runtime/es6-module-expected.txt create mode 100644 test/inspector/runtime/es6-module.js diff --git a/src/ast/scopes.cc b/src/ast/scopes.cc index ad1c137753..767e2953dd 100644 --- a/src/ast/scopes.cc +++ b/src/ast/scopes.cc @@ -1883,6 +1883,11 @@ void Scope::ResolveVariablesRecursively(ParseInfo* info) { VariableProxy* Scope::FetchFreeVariables(DeclarationScope* max_outer_scope, ParseInfo* info, VariableProxy* stack) { + // Module variables must be allocated before variable resolution + // to ensure that AccessNeedsHoleCheck() can detect import variables. + if (info != nullptr && is_module_scope()) { + AsModuleScope()->AllocateModuleVariables(); + } // Lazy parsed declaration scopes are already partially analyzed. If there are // unresolved references remaining, they just need to be resolved in outer // scopes. diff --git a/test/inspector/debugger/get-possible-breakpoints-array-literal.js b/test/inspector/debugger/get-possible-breakpoints-array-literal.js index e574f69c01..13e2920cc7 100644 --- a/test/inspector/debugger/get-possible-breakpoints-array-literal.js +++ b/test/inspector/debugger/get-possible-breakpoints-array-literal.js @@ -9,4 +9,4 @@ Protocol.Debugger.onceScriptParsed().then(message => message.params.scriptId) .then(InspectorTest.logMessage) .then(InspectorTest.completeTest); -compileAndRunWithOrigin("() => []", "", 0, 0); +InspectorTest.addScript("() => []"); diff --git a/test/inspector/debugger/get-possible-breakpoints.js b/test/inspector/debugger/get-possible-breakpoints.js index b09c08de14..bfa081935d 100644 --- a/test/inspector/debugger/get-possible-breakpoints.js +++ b/test/inspector/debugger/get-possible-breakpoints.js @@ -163,7 +163,7 @@ function foo6() { Promise.resolve().then(() => 42) }`; function compileScript(source, origin) { var promise = Protocol.Debugger.onceScriptParsed().then(message => message.params.scriptId); if (!origin) origin = { name: "", line_offset: 0, column_offset: 0 }; - compileAndRunWithOrigin(source, origin.name, origin.line_offset, origin.column_offset); + compileAndRunWithOrigin(source, origin.name, origin.line_offset, origin.column_offset, false); return promise; } diff --git a/test/inspector/inspector-test.cc b/test/inspector/inspector-test.cc index 3899ec7e76..0459687a60 100644 --- a/test/inspector/inspector-test.cc +++ b/test/inspector/inspector-test.cc @@ -198,17 +198,18 @@ class UtilsExtension : public v8::Extension { static void CompileAndRunWithOrigin( const v8::FunctionCallbackInfo& args) { - if (args.Length() != 4 || !args[0]->IsString() || !args[1]->IsString() || - !args[2]->IsInt32() || !args[3]->IsInt32()) { + if (args.Length() != 5 || !args[0]->IsString() || !args[1]->IsString() || + !args[2]->IsInt32() || !args[3]->IsInt32() || !args[4]->IsBoolean()) { fprintf(stderr, "Internal error: compileAndRunWithOrigin(source, name, line, " - "column)."); + "column, is_module)."); Exit(); } backend_runner_->Append(new ExecuteStringTask( ToVector(args[0].As()), args[1].As(), - args[2].As(), args[3].As(), nullptr, nullptr)); + args[2].As(), args[3].As(), + args[4].As(), nullptr, nullptr)); } static void SetCurrentTimeMSForTest( @@ -313,7 +314,7 @@ class SetTimeoutExtension : public v8::Extension { task.reset(new ExecuteStringTask( ToVector(args[0].As()), v8::String::Empty(isolate), v8::Integer::New(isolate, 0), v8::Integer::New(isolate, 0), - "setTimeout", inspector)); + v8::Boolean::New(isolate, false), "setTimeout", inspector)); } TaskRunner::FromContext(context)->Append(task.release()); } @@ -455,10 +456,10 @@ class FrontendChannelImpl : public InspectorClientImpl::FrontendChannel { v8::Local result = v8::String::Concat(prefix, message_string); result = v8::String::Concat(result, suffix); - frontend_task_runner_->Append( - new ExecuteStringTask(ToVector(result), v8::String::Empty(isolate), - v8::Integer::New(isolate, 0), - v8::Integer::New(isolate, 0), nullptr, nullptr)); + frontend_task_runner_->Append(new ExecuteStringTask( + ToVector(result), v8::String::Empty(isolate), + v8::Integer::New(isolate, 0), v8::Integer::New(isolate, 0), + v8::Boolean::New(isolate, false), nullptr, nullptr)); } private: diff --git a/test/inspector/protocol-test.js b/test/inspector/protocol-test.js index 5a12f84994..d04d951d59 100644 --- a/test/inspector/protocol-test.js +++ b/test/inspector/protocol-test.js @@ -135,13 +135,17 @@ InspectorTest.completeTest = function() InspectorTest.completeTestAfterPendingTimeouts = function() { - Protocol.Runtime.evaluate({ - expression: "new Promise(resolve => setTimeout(resolve, 0))", - awaitPromise: true }).then(InspectorTest.completeTest); + InspectorTest.waitPendingTasks().then(InspectorTest.completeTest); } -InspectorTest.addScript = (string, lineOffset, columnOffset) => compileAndRunWithOrigin(string, "", lineOffset || 0, columnOffset || 0); -InspectorTest.addScriptWithUrl = (string, url) => compileAndRunWithOrigin(string, url, 0, 0); +InspectorTest.waitPendingTasks = function() +{ + return Protocol.Runtime.evaluate({ expression: "new Promise(r => setTimeout(r, 0))//# sourceURL=wait-pending-tasks.js", awaitPromise: true }); +} + +InspectorTest.addScript = (string, lineOffset, columnOffset) => compileAndRunWithOrigin(string, "", lineOffset || 0, columnOffset || 0, false); +InspectorTest.addScriptWithUrl = (string, url) => compileAndRunWithOrigin(string, url, 0, 0, false); +InspectorTest.addModule = (string, url, lineOffset, columnOffset) => compileAndRunWithOrigin(string, url, lineOffset || 0, columnOffset || 0, true); InspectorTest.startDumpingProtocolMessages = function() { diff --git a/test/inspector/runtime/es6-module-expected.txt b/test/inspector/runtime/es6-module-expected.txt new file mode 100644 index 0000000000..60f4d5adb5 --- /dev/null +++ b/test/inspector/runtime/es6-module-expected.txt @@ -0,0 +1,236 @@ +Checks basic ES6 modules support. +{ + method : Debugger.scriptParsed + params : { + endColumn : 17 + endLine : 5 + executionContextId : + hasSourceURL : false + hash : 9C014F7249BAFA12B91017817AD15091D01A9155 + isLiveEdit : false + scriptId : + sourceMapURL : + startColumn : 0 + startLine : 0 + url : module1 + } +} +{ + method : Debugger.scriptParsed + params : { + endColumn : 17 + endLine : 5 + executionContextId : + hasSourceURL : false + hash : 443A2FA24A6112E6B9101781E6A19B56BDC396D4 + isLiveEdit : false + scriptId : + sourceMapURL : + startColumn : 0 + startLine : 0 + url : module2 + } +} +{ + method : Debugger.scriptParsed + params : { + endColumn : 0 + endLine : 9 + executionContextId : + hasSourceURL : false + hash : 54D834614FBF9B389082DAE06CD3EFC499BEBF13 + isLiveEdit : false + scriptId : + sourceMapURL : + startColumn : 0 + startLine : 0 + url : module3 + } +} +console.log(module1) +foo (module1:2:10) +(anonymous) (module3:3:12) + +console.log(42) +(anonymous) (module3:3:8) + +console.log(module2) +foo (module2:2:10) +(anonymous) (module3:4:12) + +console.log(239) +(anonymous) (module3:4:8) + +{ + method : Debugger.paused + params : { + callFrames : [ + [0] : { + callFrameId : + functionLocation : { + columnNumber : 0 + lineNumber : 0 + scriptId : + } + functionName : + location : { + columnNumber : 0 + lineNumber : 7 + scriptId : + } + scopeChain : [ + [0] : { + endLocation : { + columnNumber : 0 + lineNumber : 8 + scriptId : + } + object : { + className : Object + description : Object + objectId : + type : object + } + startLocation : { + columnNumber : 0 + lineNumber : 0 + scriptId : + } + type : module + } + [1] : { + object : { + className : global + description : global + objectId : + type : object + } + type : global + } + ] + this : { + type : undefined + } + } + ] + hitBreakpoints : [ + ] + reason : other + } +} +{ + id : + result : { + result : [ + [0] : { + configurable : true + enumerable : true + isOwn : true + name : foo1 + value : { + className : Function + description : function foo() { console.log('module1'); return 42; } + objectId : + type : function + } + writable : true + } + [1] : { + configurable : true + enumerable : true + isOwn : true + name : foo2 + value : { + className : Function + description : function foo() { console.log('module2'); return 239; } + objectId : + type : function + } + writable : true + } + [2] : { + configurable : true + enumerable : true + isOwn : true + name : a1 + value : { + description : 1 + type : number + value : 1 + } + writable : true + } + [3] : { + configurable : true + enumerable : true + isOwn : true + name : a2 + value : { + description : 2 + type : number + value : 2 + } + writable : true + } + ] + } +} +{ + method : Debugger.scriptFailedToParse + params : { + endColumn : 1 + endLine : 0 + executionContextId : + hasSourceURL : false + hash : FF746120E4E4F1BA4CB5762843D429DC872EBA18 + scriptId : + sourceMapURL : + startColumn : 0 + startLine : 0 + url : module4 + } +} +{ + method : Runtime.exceptionThrown + params : { + exceptionDetails : { + columnNumber : 0 + exception : { + className : SyntaxError + description : SyntaxError: Unexpected token } + objectId : + preview : { + description : SyntaxError: Unexpected token } + overflow : false + properties : [ + [0] : { + name : stack + type : string + value : SyntaxError: Unexpected token } + } + [1] : { + name : message + type : string + value : Unexpected token } + } + ] + subtype : error + type : object + } + subtype : error + type : object + } + exceptionId : + executionContextId : + lineNumber : 0 + scriptId : + stackTrace : { + callFrames : [ + ] + } + text : Uncaught SyntaxError: Unexpected token } + url : module4 + } + timestamp : + } +} diff --git a/test/inspector/runtime/es6-module.js b/test/inspector/runtime/es6-module.js new file mode 100644 index 0000000000..362ca3b72d --- /dev/null +++ b/test/inspector/runtime/es6-module.js @@ -0,0 +1,64 @@ +// Copyright 2017 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. + +print('Checks basic ES6 modules support.'); + +var module1 = ` +export function foo() { + console.log('module1'); + return 42; +} +export let a1 = 1`; + +var module2 = ` +export function foo() { + console.log('module2'); + return 239; +} +export let a2 = 2`; + +var module3 = ` +import { foo as foo1 } from 'module1'; +import { foo as foo2 } from 'module2'; +console.log(foo1()); +console.log(foo2()); +import { a1 } from 'module1'; +import { a2 } from 'module2'; +debugger; +`; + +var module4 = '}'; + +InspectorTest.setupScriptMap(); +// We get scriptParsed events for modules .. +Protocol.Debugger.onScriptParsed(message => { + if (message.params.url === 'wait-pending-tasks.js') return; + InspectorTest.logMessage(message); +}); +// .. scriptFailed to parse for modules with syntax error .. +Protocol.Debugger.onScriptFailedToParse(InspectorTest.logMessage); +// .. API messages from modules contain correct stack trace .. +Protocol.Runtime.onConsoleAPICalled(message => { + InspectorTest.log(`console.log(${message.params.args[0].value})`); + InspectorTest.logCallFrames(message.params.stackTrace.callFrames); + InspectorTest.log(''); +}); +// .. we could break inside module and scope contains correct list of variables .. +Protocol.Debugger.onPaused(message => { + InspectorTest.logMessage(message); + Protocol.Runtime.getProperties({ objectId: message.params.callFrames[0].scopeChain[0].object.objectId}) + .then(InspectorTest.logMessage) + .then(() => Protocol.Debugger.resume()); +}); +// .. we process uncaught errors from modules correctly. +Protocol.Runtime.onExceptionThrown(InspectorTest.logMessage); + +Protocol.Runtime.enable(); +Protocol.Debugger.enable() + .then(() => InspectorTest.addModule(module1, "module1")) + .then(() => InspectorTest.addModule(module2, "module2")) + .then(() => InspectorTest.addModule(module3, "module3")) + .then(() => InspectorTest.addModule(module4, "module4")) + .then(() => InspectorTest.waitPendingTasks()) + .then(InspectorTest.completeTest); diff --git a/test/inspector/task-runner.cc b/test/inspector/task-runner.cc index 14270177f0..72c6a44c84 100644 --- a/test/inspector/task-runner.cc +++ b/test/inspector/task-runner.cc @@ -22,6 +22,13 @@ void ReportUncaughtException(v8::Isolate* isolate, fprintf(stderr, "Unhandle exception: %s\n", message.data()); } +v8::internal::Vector ToVector(v8::Local str) { + v8::internal::Vector buffer = + v8::internal::Vector::New(str->Length()); + str->Write(buffer.start(), 0, str->Length()); + return buffer; +} + } // namespace TaskRunner::TaskRunner(v8::ExtensionConfiguration* extensions, @@ -101,6 +108,19 @@ void TaskRunner::Terminate() { process_queue_semaphore_.Signal(); } +void TaskRunner::RegisterModule(v8::internal::Vector name, + v8::Local module) { + modules_[name] = v8::Global(isolate_, module); +} + +v8::MaybeLocal TaskRunner::ModuleResolveCallback( + v8::Local context, v8::Local specifier, + v8::Local referrer) { + std::string str = *v8::String::Utf8Value(specifier); + TaskRunner* runner = TaskRunner::FromContext(context); + return runner->modules_[ToVector(specifier)].Get(runner->isolate_); +} + TaskRunner::Task* TaskRunner::GetNext(bool only_protocol) { for (;;) { if (is_terminated_.Value()) return nullptr; @@ -125,17 +145,6 @@ TaskRunner* TaskRunner::FromContext(v8::Local context) { context->GetAlignedPointerFromEmbedderData(kTaskRunnerIndex)); } -namespace { - -v8::internal::Vector ToVector(v8::Local str) { - v8::internal::Vector buffer = - v8::internal::Vector::New(str->Length()); - str->Write(buffer.start(), 0, str->Length()); - return buffer; -} - -} // namespace - AsyncTask::AsyncTask(const char* task_name, v8_inspector::V8Inspector* inspector) : inspector_(task_name ? inspector : nullptr) { @@ -157,20 +166,18 @@ void AsyncTask::Run(v8::Isolate* isolate, ExecuteStringTask::ExecuteStringTask( const v8::internal::Vector& expression, v8::Local name, v8::Local line_offset, - v8::Local column_offset, const char* task_name, - v8_inspector::V8Inspector* inspector) + v8::Local column_offset, v8::Local is_module, + const char* task_name, v8_inspector::V8Inspector* inspector) : AsyncTask(task_name, inspector), expression_(expression), name_(ToVector(name)), line_offset_(line_offset.As()->Value()), - column_offset_(column_offset.As()->Value()) {} + column_offset_(column_offset.As()->Value()), + is_module_(is_module->Value()) {} ExecuteStringTask::ExecuteStringTask( const v8::internal::Vector& expression) - : AsyncTask(nullptr, nullptr), - expression_utf8_(expression), - line_offset_(0), - column_offset_(0) {} + : AsyncTask(nullptr, nullptr), expression_utf8_(expression) {} void ExecuteStringTask::AsyncRun(v8::Isolate* isolate, const v8::Global& context) { @@ -188,7 +195,14 @@ void ExecuteStringTask::AsyncRun(v8::Isolate* isolate, v8::Local column_offset = v8::Integer::New(isolate, column_offset_); - v8::ScriptOrigin origin(name, line_offset, column_offset); + v8::ScriptOrigin origin( + name, line_offset, column_offset, + /* resource_is_shared_cross_origin */ v8::Local(), + /* script_id */ v8::Local(), + /* source_map_url */ v8::Local(), + /* resource_is_opaque */ v8::Local(), + /* is_wasm */ v8::Local(), + v8::Boolean::New(isolate, is_module_)); v8::Local source; if (expression_.length()) { source = v8::String::NewFromTwoByte(isolate, expression_.start(), @@ -203,10 +217,24 @@ void ExecuteStringTask::AsyncRun(v8::Isolate* isolate, } v8::ScriptCompiler::Source scriptSource(source, origin); - v8::Local script; - if (!v8::ScriptCompiler::Compile(local_context, &scriptSource) - .ToLocal(&script)) - return; - v8::MaybeLocal result; - result = script->Run(local_context); + if (!is_module_) { + v8::Local script; + if (!v8::ScriptCompiler::Compile(local_context, &scriptSource) + .ToLocal(&script)) + return; + v8::MaybeLocal result; + result = script->Run(local_context); + } else { + v8::Local module; + if (!v8::ScriptCompiler::CompileModule(isolate, &scriptSource) + .ToLocal(&module)) { + return; + } + if (!module->Instantiate(local_context, &TaskRunner::ModuleResolveCallback)) + return; + v8::Local result; + if (!module->Evaluate(local_context).ToLocal(&result)) return; + TaskRunner* runner = TaskRunner::FromContext(local_context); + runner->RegisterModule(name_, module); + } } diff --git a/test/inspector/task-runner.h b/test/inspector/task-runner.h index 045bba0d25..0f980fbfaa 100644 --- a/test/inspector/task-runner.h +++ b/test/inspector/task-runner.h @@ -5,6 +5,8 @@ #ifndef V8_TEST_INSPECTOR_PROTOCOL_TASK_RUNNER_H_ #define V8_TEST_INSPECTOR_PROTOCOL_TASK_RUNNER_H_ +#include + #include "include/v8-inspector.h" #include "include/v8-platform.h" #include "include/v8.h" @@ -14,6 +16,16 @@ #include "src/locked-queue-inl.h" #include "src/vector.h" +struct VectorCompare { + bool operator()(const v8::internal::Vector& lhs, + const v8::internal::Vector& rhs) const { + for (int i = 0; i < lhs.length() && i < rhs.length(); ++i) { + if (lhs[i] != rhs[i]) return lhs[i] < rhs[i]; + } + return false; + } +}; + class TaskRunner : public v8::base::Thread { public: class Task { @@ -42,6 +54,12 @@ class TaskRunner : public v8::base::Thread { void Terminate(); + void RegisterModule(v8::internal::Vector name, + v8::Local module); + static v8::MaybeLocal ModuleResolveCallback( + v8::Local context, v8::Local specifier, + v8::Local referrer); + private: void InitializeContext(); Task* GetNext(bool only_protocol); @@ -60,6 +78,10 @@ class TaskRunner : public v8::base::Thread { v8::internal::LockedQueue deffered_queue_; v8::base::Semaphore process_queue_semaphore_; + std::map, v8::Global, + VectorCompare> + modules_; + int nested_loop_count_; v8::base::AtomicNumber is_terminated_; @@ -86,7 +108,8 @@ class ExecuteStringTask : public AsyncTask { ExecuteStringTask(const v8::internal::Vector& expression, v8::Local name, v8::Local line_offset, - v8::Local column_offset, const char* task_name, + v8::Local column_offset, + v8::Local is_module, const char* task_name, v8_inspector::V8Inspector* inspector); explicit ExecuteStringTask( const v8::internal::Vector& expression); @@ -99,8 +122,9 @@ class ExecuteStringTask : public AsyncTask { v8::internal::Vector expression_; v8::internal::Vector expression_utf8_; v8::internal::Vector name_; - int32_t line_offset_; - int32_t column_offset_; + int32_t line_offset_ = 0; + int32_t column_offset_ = 0; + bool is_module_ = false; DISALLOW_COPY_AND_ASSIGN(ExecuteStringTask); };