0x00 前言
d8自己封裝了一個js代碼執行器,上一篇我們的代碼執行到options.isolate_sources[0].Execute(isolate
,本文將做進一步分析。
0x01 調用棧
#0 v8::SourceGroup::Execute (this=0x5555556542a8, isolate=0x1e9d00000000) at ../../src/d8/d8.cc:2567
#1 0x000055555560008b in v8::Shell::RunMain (isolate=0x1e9d00000000, argc=2, argv=0x7fffffffdf38, last_run=true) at ../../src/d8/d8.cc:3100
#2 0x00005555556013a6 in v8::Shell::Main (argc=2, argv=0x7fffffffdf38) at ../../src/d8/d8.cc:3741
#3 0x00005555556016e2 in main (argc=2, argv=0x7fffffffdf38) at ../../src/d8/d8.cc:3777
0x02 SourceGroup::Execute函數
bool SourceGroup::Execute(Isolate* isolate) {
bool success = true;
for (int i = begin_offset_; i < end_offset_; ++i) {
const char* arg = argv_[i];
//解析-e參數,d8需要執行一段js代碼字符串的話,走這個條件分支
if (strcmp(arg, "-e") == 0 && i + 1 < end_offset_) {
// Execute argument given to -e option directly.
//創建作用域
HandleScope handle_scope(isolate);
//創建匿名文件名
Local<String> file_name =
String::NewFromUtf8(isolate, "unnamed", NewStringType::kNormal)
.ToLocalChecked();
//讀取js代碼
Local<String> source =
String::NewFromUtf8(isolate, argv_[i + 1], NewStringType::kNormal)
.ToLocalChecked();
Shell::set_script_executed();
//執行js代碼字符串
if (!Shell::ExecuteString(isolate, source, file_name,
Shell::kNoPrintResult, Shell::kReportExceptions,
Shell::kNoProcessMessageQueue)) {
success = false;
break;
}
++i;
continue;
//判斷是否爲js的module文件,規則後綴名必須爲.mjs與--module參數連用
} else if (ends_with(arg, ".mjs")) {
Shell::set_script_executed();
if (!Shell::ExecuteModule(isolate, arg)) {
success = false;
break;
}
continue;
// 判斷是否爲module執行模式
} else if (strcmp(arg, "--module") == 0 && i + 1 < end_offset_) {
// Treat the next file as a module.
arg = argv_[++i];
Shell::set_script_executed();
if (!Shell::ExecuteModule(isolate, arg)) {
success = false;
break;
}
continue;
} else if (arg[0] == '-') {
// Ignore other options. They have been parsed already.
continue;
}
//LZ這裏的執行命令爲d8 test.js,所以前面的邏輯都會跳過,真正的入口位置在這裏
// Use all other arguments as names of files to load and run.
//定義作用域
HandleScope handle_scope(isolate);
//創建文件名字符串
Local<String> file_name =
String::NewFromUtf8(isolate, arg, NewStringType::kNormal)
.ToLocalChecked();
//從文件中讀取文件內容
Local<String> source = ReadFile(isolate, arg);
if (source.IsEmpty()) {
printf("Error reading '%s'\n", arg);
base::OS::ExitProcess(1);
}
//設置執行狀態爲true,該靜態函數在d8.h中定義
Shell::set_script_executed();
//執行js代碼
if (!Shell::ExecuteString(isolate, source, file_name, Shell::kNoPrintResult,
Shell::kReportExceptions,
Shell::kProcessMessageQueue)) {
success = false;
break;
}
}
return success;
}
0x03 Shell::ExecuteString函數
// Executes a string within the current v8 context.
bool Shell::ExecuteString(Isolate* isolate, Local<String> source,
Local<Value> name, PrintResult print_result,
ReportExceptions report_exceptions,
ProcessMessageQueue process_message_queue) {
//i::FLAG_parse_only 爲false
if (i::FLAG_parse_only) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
i::VMState<PARSER> state(i_isolate);
i::Handle<i::String> str = Utils::OpenHandle(*(source));
// Set up ParseInfo.
i::ParseInfo parse_info(i_isolate);
parse_info.set_toplevel();
parse_info.set_allow_lazy_parsing();
parse_info.set_language_mode(
i::construct_language_mode(i::FLAG_use_strict));
parse_info.set_script(
parse_info.CreateScript(i_isolate, str, options.compile_options));
if (!i::parsing::ParseProgram(&parse_info, i_isolate)) {
fprintf(stderr, "Failed parsing\n");
return false;
}
return true;
}
HandleScope handle_scope(isolate);
TryCatch try_catch(isolate);
try_catch.SetVerbose(true);
MaybeLocal<Value> maybe_result;
bool success = true;
{
//獲取自定義數據,get後爲null。
PerIsolateData* data = PerIsolateData::Get(isolate);
//創建realm變量
Local<Context> realm =
Local<Context>::New(isolate, data->realms_[data->realm_current_]);
Context::Scope context_scope(realm);
MaybeLocal<Script> maybe_script;
//創建當前上下文context
Local<Context> context(isolate->GetCurrentContext());
//創建ScriptOrigin對象origin
ScriptOrigin origin(name);
//v8有code caching功能,輸入的js代碼第一次被編譯後,會生成一份cache,再次運行這份js代碼時,會優先加載cache,減少重複編譯帶來的開銷。
//編譯選項如果爲ScriptCompiler::kConsumeCodeCache,則尋找cache並加載
if (options.compile_options == ScriptCompiler::kConsumeCodeCache) {
//根據js代碼字符串搜索cache
ScriptCompiler::CachedData* cached_code =
LookupCodeCache(isolate, source);
//如果cache不爲空
if (cached_code != nullptr) {
ScriptCompiler::Source script_source(source, origin, cached_code);
maybe_script = ScriptCompiler::Compile(context, &script_source,
options.compile_options);
CHECK(!cached_code->rejected);
} else {
ScriptCompiler::Source script_source(source, origin);
maybe_script = ScriptCompiler::Compile(
context, &script_source, ScriptCompiler::kNoCompileOptions);
}
// options.stress_background_compile爲true,則後臺編譯
} else if (options.stress_background_compile) {
// 啓動一個後臺線程用於編譯js腳本,後臺編譯就是script streaming 的優化方式
// 同code cache的地位一樣重要。瀏覽器加載多個js腳本時,邊下載邊編譯的過程稱爲script streaming
// Start a background thread compiling the script.
BackgroundCompileThread background_compile_thread(isolate, source);
//檢查線程是否已經就緒
CHECK(background_compile_thread.Start());
// In parallel, compile on the main thread to flush out any data races.
{
TryCatch ignore_try_catch(isolate);
ScriptCompiler::Source script_source(source, origin);
USE(ScriptCompiler::Compile(context, &script_source,
ScriptCompiler::kNoCompileOptions));
}
// Join with background thread and finalize compilation.
background_compile_thread.Join();
maybe_script = v8::ScriptCompiler::Compile(
context, background_compile_thread.streamed_source(), source, origin);
} else {
//沒有任何優化的編譯方式
ScriptCompiler::Source script_source(source, origin);
maybe_script = ScriptCompiler::Compile(context, &script_source,
options.compile_options);
}
//定義script變量
Local<Script> script;
if (!maybe_script.ToLocal(&script)) {
//打印編譯過程中的所有報錯
// Print errors that happened during compilation.
if (report_exceptions) ReportException(isolate, &try_catch);
return false;
}
//如果kProduceCache選項開啓,則將cache落地。
if (options.code_cache_options ==
ShellOptions::CodeCacheOptions::kProduceCache) {
// Serialize and store it in memory for the next execution.
ScriptCompiler::CachedData* cached_data =
ScriptCompiler::CreateCodeCache(script->GetUnboundScript());
StoreInCodeCache(isolate, source, cached_data);
delete cached_data;
}
//【重點】運行js腳本
maybe_result = script->Run(realm);
//處理選項,如果是kProduceCacheAfterExecute,則運行後在落地cache
if (options.code_cache_options ==
ShellOptions::CodeCacheOptions::kProduceCacheAfterExecute) {
// Serialize and store it in memory for the next execution.
ScriptCompiler::CachedData* cached_data =
ScriptCompiler::CreateCodeCache(script->GetUnboundScript());
StoreInCodeCache(isolate, source, cached_data);
delete cached_data;
}
if (process_message_queue && !EmptyMessageQueues(isolate)) success = false;
data->realm_current_ = data->realm_switch_;
}
Local<Value> result;
//讀取結果數據
if (!maybe_result.ToLocal(&result)) {
DCHECK(try_catch.HasCaught());
// Print errors that happened during execution.
if (report_exceptions) ReportException(isolate, &try_catch);
return false;
}
DCHECK(!try_catch.HasCaught());
if (print_result) {
//如果配置了test_shell選項,則把結果重定向到標準輸出
if (options.test_shell) {
//如果結果格式未定義,需要Stringify和格式化
if (!result->IsUndefined()) {
// If all went well and the result wasn't undefined then print
// the returned value.
v8::String::Utf8Value str(isolate, result);
fwrite(*str, sizeof(**str), str.length(), stdout);
printf("\n");
}
} else {
v8::String::Utf8Value str(isolate, Stringify(isolate, result));
fwrite(*str, sizeof(**str), str.length(), stdout);
printf("\n");
}
}
return success;
}
0x04 小結
Shell::ExecuteString函數說明了v8運行的大致流程,如果寫demo可以參考這個函數。