From c7c6203e61cb6bb85051b96eabd6deae7995a787 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 2 Aug 2018 13:13:32 -0400 Subject: [PATCH] Source map support (#429) This change increases size: out/debug/obj/libdeno/from_snapshot.o 19M -> 34M out/release/deno 32M -> 47M --- BUILD.gn | 1 + build_extra/deno.gni | 9 +++++---- js/lib.deno.d.ts | 9 +++++++-- js/main.ts | 2 ++ js/runtime.ts | 33 +++++++++++++++++++++++---------- js/v8_source_maps.ts | 20 +++++++++++++++++--- src/binding.cc | 13 +++++++++++-- src/from_filesystem.cc | 2 +- src/internal.h | 4 +++- src/snapshot_creator.cc | 17 +++++++++++++---- tests/008_stack_trace.ts | 7 +++++++ 11 files changed, 90 insertions(+), 27 deletions(-) create mode 100644 tests/008_stack_trace.ts diff --git a/BUILD.gn b/BUILD.gn index bab2005477..b200c2cfe1 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -264,6 +264,7 @@ rust_flatbuffer("msg_rs") { # Generates $target_gen_dir/snapshot_deno.cc create_snapshot("deno") { js = "$target_gen_dir/bundle/main.js" + source_map = "$target_gen_dir/bundle/main.js.map" deps = [ ":bundle", ] diff --git a/build_extra/deno.gni b/build_extra/deno.gni index 5f3f4aa480..08cccfdea9 100644 --- a/build_extra/deno.gni +++ b/build_extra/deno.gni @@ -32,13 +32,14 @@ template("create_snapshot") { inputs = [ invoker.js, ] + if (defined(invoker.source_map)) { + inputs += [ invoker.source_map ] + } outputs = [ snapshot_out_bin, ] - args = [ - rebase_path(snapshot_out_bin, root_build_dir), - rebase_path(invoker.js, root_build_dir), - ] + args = rebase_path(outputs, root_build_dir) + + rebase_path(inputs, root_build_dir) # To debug snapshotting problems: # args += ["--trace-serializer"] diff --git a/js/lib.deno.d.ts b/js/lib.deno.d.ts index 81e8daf4e4..b11a08edfb 100644 --- a/js/lib.deno.d.ts +++ b/js/lib.deno.d.ts @@ -27,10 +27,15 @@ declare class Console { interface Window { console: Console; - mainSource: string; // TODO(ry) This shouldn't be global. + // TODO(ry) These shouldn't be global. + mainSource: string; + setMainSourceMap(sm: string): void; } // Globals in the runtime environment declare let console: Console; -declare let mainSource: string; // TODO(ry) This shouldn't be global. declare const window: Window; + +// TODO(ry) These shouldn't be global. +declare let mainSource: string; +declare function setMainSourceMap(sm: string): void; diff --git a/js/main.ts b/js/main.ts index 50f73a4c67..81dabab331 100644 --- a/js/main.ts +++ b/js/main.ts @@ -20,6 +20,8 @@ function startMsg(cmdId: number): Uint8Array { /* tslint:disable-next-line:no-default-export */ export default function denoMain() { + runtime.setup(); + // First we send an empty "Start" message to let the privlaged side know we // are ready. The response should be a "StartRes" message containing the CLI // argv and other info. diff --git a/js/runtime.ts b/js/runtime.ts index 79f4637593..4e9cb657de 100644 --- a/js/runtime.ts +++ b/js/runtime.ts @@ -12,7 +12,7 @@ import * as util from "./util"; import { log } from "./util"; import { assetSourceCode } from "./assets"; import * as os from "./os"; -//import * as sourceMaps from "./v8_source_maps"; +import * as sourceMaps from "./v8_source_maps"; import { window, globalEval } from "./globals"; //import * as deno from "./deno"; @@ -39,26 +39,39 @@ window.onerror = ( os.exit(1); }; -/* -export function setup(mainJs: string, mainMap: string): void { +// This is called during snapshot creation with the contents of +// out/debug/gen/bundle/main.js.map. +import { RawSourceMap } from "source-map"; +let mainSourceMap: RawSourceMap = null; +function setMainSourceMap(rawSourceMap: RawSourceMap) { + util.assert(Number(rawSourceMap.version) === 3); + mainSourceMap = rawSourceMap; +} +window["setMainSourceMap"] = setMainSourceMap; + +export function setup(): void { sourceMaps.install({ installPrepareStackTrace: true, - getGeneratedContents: (filename: string): string => { - if (filename === "/main.js") { - return mainJs; - } else if (filename === "/main.map") { - return mainMap; + getGeneratedContents: (filename: string): string | RawSourceMap => { + util.log("getGeneratedContents", filename); + if (filename === "gen/bundle/main.js") { + util.assert(window["mainSource"].length > 0); + return window["mainSource"]; + } else if (filename === "main.js.map") { + return mainSourceMap; + } else if (filename === "deno_main.js") { + return ""; } else { const mod = FileModule.load(filename); if (!mod) { - console.error("getGeneratedContents cannot find", filename); + util.log("getGeneratedContents cannot find", filename); + return null; } return mod.outputCode; } } }); } -*/ // This class represents a module. We call it FileModule to make it explicit // that each module represents a single file. diff --git a/js/v8_source_maps.ts b/js/v8_source_maps.ts index 956ffbe822..d5feeb1c05 100644 --- a/js/v8_source_maps.ts +++ b/js/v8_source_maps.ts @@ -1,7 +1,15 @@ // Copyright 2014 Evan Wallace // Copyright 2018 the Deno authors. All rights reserved. MIT license. // Originated from source-map-support but has been heavily modified for deno. + +// Because NodeJS.CallSite and Error.prepareStackTrace are used we add a +// dependency on the Node types. +// TODO(ry) Ideally this triple slash directive should be removed as we only +// need CallSite and Error.prepareStackTrace but nothing else. +/// + import { SourceMapConsumer, MappedPosition } from "source-map"; +import { RawSourceMap } from "source-map"; import * as base64 from "base64-js"; import { arrayToStr } from "./util"; @@ -24,7 +32,7 @@ interface Position { line: number; } -type GetGeneratedContentsCallback = (fileName: string) => string; +type GetGeneratedContentsCallback = (fileName: string) => string | RawSourceMap; let getGeneratedContents: GetGeneratedContentsCallback; @@ -190,13 +198,16 @@ function loadConsumer(source: string): SourceMapConsumer { if (!code) { return null; } + if (typeof code !== "string") { + throw new Error("expected string"); + } let sourceMappingURL = retrieveSourceMapURL(code); if (!sourceMappingURL) { throw Error("No source map?"); } - let sourceMapData: string; + let sourceMapData: string | RawSourceMap; if (reSourceMap.test(sourceMappingURL)) { // Support source map URL as a data url const rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(",") + 1); @@ -209,8 +220,11 @@ function loadConsumer(source: string): SourceMapConsumer { sourceMapData = getGeneratedContents(sourceMappingURL); } + const rawSourceMap = + typeof sourceMapData === "string" + ? JSON.parse(sourceMapData) + : sourceMapData; //console.log("sourceMapData", sourceMapData); - const rawSourceMap = JSON.parse(sourceMapData); consumer = new SourceMapConsumer(rawSourceMap); consumers.set(source, consumer); } diff --git a/src/binding.cc b/src/binding.cc index 1c4deaa239..19448ca2a0 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -270,7 +270,8 @@ bool Execute(v8::Local context, const char* js_filename, } void InitializeContext(v8::Isolate* isolate, v8::Local context, - const char* js_filename, const char* js_source) { + const char* js_filename, const std::string& js_source, + const std::string* source_map) { v8::HandleScope handle_scope(isolate); v8::Context::Scope context_scope(context); @@ -293,11 +294,19 @@ void InitializeContext(v8::Isolate* isolate, v8::Local context, skip_onerror = true; { - auto source = deno::v8_str(js_source); + auto source = deno::v8_str(js_source.c_str()); CHECK(global->Set(context, deno::v8_str("mainSource"), source).FromJust()); bool r = deno::ExecuteV8StringSource(context, js_filename, source); CHECK(r); + + if (source_map != nullptr) { + CHECK_GT(source_map->length(), 1u); + std::string set_source_map = "setMainSourceMap( " + *source_map + " )"; + CHECK_GT(set_source_map.length(), source_map->length()); + r = deno::Execute(context, "set_source_map.js", set_source_map.c_str()); + CHECK(r); + } } skip_onerror = false; } diff --git a/src/from_filesystem.cc b/src/from_filesystem.cc index a47808b823..797659de09 100644 --- a/src/from_filesystem.cc +++ b/src/from_filesystem.cc @@ -32,7 +32,7 @@ Deno* NewFromFileSystem(void* data, deno_recv_cb cb) { { v8::HandleScope handle_scope(isolate); auto context = v8::Context::New(isolate); - InitializeContext(isolate, context, BUNDLE_LOCATION, js_source.c_str()); + InitializeContext(isolate, context, BUNDLE_LOCATION, js_source, nullptr); d->context.Reset(d->isolate, context); } diff --git a/src/internal.h b/src/internal.h index dc28112dc5..0719bdc3ed 100644 --- a/src/internal.h +++ b/src/internal.h @@ -20,6 +20,7 @@ struct deno_s { // TODO(ry) Remove these when we call deno_reply_start from Rust. char** deno_argv(); int deno_argc(); +struct deno_s* deno_from_isolate(v8::Isolate* isolate); } namespace deno { @@ -38,7 +39,8 @@ static intptr_t external_references[] = {reinterpret_cast(Print), Deno* NewFromSnapshot(void* data, deno_recv_cb cb); void InitializeContext(v8::Isolate* isolate, v8::Local context, - const char* js_filename, const char* js_source); + const char* js_filename, const std::string& js_source, + const std::string* source_map); void AddIsolate(Deno* d, v8::Isolate* isolate); diff --git a/src/snapshot_creator.cc b/src/snapshot_creator.cc index f821565e5f..8038c9b13e 100644 --- a/src/snapshot_creator.cc +++ b/src/snapshot_creator.cc @@ -22,14 +22,16 @@ v8::StartupData SerializeInternalFields(v8::Local holder, int index, return {payload, size}; } -v8::StartupData MakeSnapshot(const char* js_filename, const char* js_source) { +v8::StartupData MakeSnapshot(const char* js_filename, + const std::string& js_source, + const std::string* source_map) { auto* creator = new v8::SnapshotCreator(external_references); auto* isolate = creator->GetIsolate(); v8::Isolate::Scope isolate_scope(isolate); { v8::HandleScope handle_scope(isolate); auto context = v8::Context::New(isolate); - InitializeContext(isolate, context, js_filename, js_source); + InitializeContext(isolate, context, js_filename, js_source, source_map); creator->SetDefaultContext(context, v8::SerializeInternalFieldsCallback( SerializeInternalFields, nullptr)); } @@ -45,18 +47,25 @@ v8::StartupData MakeSnapshot(const char* js_filename, const char* js_source) { int main(int argc, char** argv) { const char* snapshot_out_bin = argv[1]; const char* js_fn = argv[2]; + const char* source_map_fn = argv[3]; // Optional. v8::V8::SetFlagsFromCommandLine(&argc, argv, true); - CHECK_EQ(argc, 3); CHECK_NE(js_fn, nullptr); CHECK_NE(snapshot_out_bin, nullptr); std::string js_source; CHECK(deno::ReadFileToString(js_fn, &js_source)); + std::string source_map; + if (source_map_fn != nullptr) { + CHECK_EQ(argc, 4); + CHECK(deno::ReadFileToString(source_map_fn, &source_map)); + } + deno_init(); - auto snapshot_blob = deno::MakeSnapshot(js_fn, js_source.c_str()); + auto snapshot_blob = deno::MakeSnapshot( + js_fn, js_source, source_map_fn != nullptr ? &source_map : nullptr); std::string snapshot_str(snapshot_blob.data, snapshot_blob.raw_size); std::ofstream file_(snapshot_out_bin, std::ios::binary); diff --git a/tests/008_stack_trace.ts b/tests/008_stack_trace.ts new file mode 100644 index 0000000000..6aa0fcc3b6 --- /dev/null +++ b/tests/008_stack_trace.ts @@ -0,0 +1,7 @@ +import { throwsError } from "./subdir/mod1.ts"; + +function foo() { + throwsError(); +} + +foo();