From 3a761eccbca16252bec638274acc882182e443ee Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Wed, 26 Feb 2014 15:20:00 -0800 Subject: [PATCH] Bug 969786: Implement Debugger.Source.prototype.introductionScript. r=sfink --- .../debug/Source-introductionScript-01.js | 118 ++++++++++++++++++ .../debug/Source-introductionScript-02.js | 31 +++++ js/src/jsscript.cpp | 2 +- js/src/vm/Debugger.cpp | 25 +++- .../server/tests/mochitest/chrome.ini | 1 + ...r.Source.prototype.introductionScript.html | 97 ++++++++++++++ 6 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 js/src/jit-test/tests/debug/Source-introductionScript-01.js create mode 100644 js/src/jit-test/tests/debug/Source-introductionScript-02.js create mode 100644 toolkit/devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionScript.html diff --git a/js/src/jit-test/tests/debug/Source-introductionScript-01.js b/js/src/jit-test/tests/debug/Source-introductionScript-01.js new file mode 100644 index 000000000000..7df7c86aa488 --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-introductionScript-01.js @@ -0,0 +1,118 @@ +// Dynamically generated sources should have their introduction script and +// offset set correctly. + +var g = newGlobal(); +var dbg = new Debugger; +var gDO = dbg.addDebuggee(g); +var log; + +// Direct eval, while the frame is live. +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + var source = frame.script.source; + var introducer = frame.older; + assertEq(source.introductionScript, introducer.script); + assertEq(source.introductionOffset, introducer.offset); +}; +log = ''; +g.eval('\n\neval("\\n\\ndebugger;");'); +assertEq(log, 'd'); + +// Direct eval, after the frame has been popped. +var introducer, introduced; +dbg.onDebuggerStatement = function (frame) { + log += 'de1'; + introducer = frame.script; + dbg.onDebuggerStatement = function (frame) { + log += 'de2'; + introduced = frame.script.source; + }; +}; +log = ''; +g.evaluate('debugger; eval("\\n\\ndebugger;");', { lineNumber: 1812 }); +assertEq(log, 'de1de2'); +assertEq(introduced.introductionScript, introducer); +assertEq(introducer.getOffsetLine(introduced.introductionOffset), 1812); + +// Indirect eval, while the frame is live. +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + var source = frame.script.source; + var introducer = frame.older; + assertEq(source.introductionScript, introducer.script); + assertEq(source.introductionOffset, introducer.offset); +}; +log = ''; +g.eval('\n\n(0,eval)("\\n\\ndebugger;");'); +assertEq(log, 'd'); + +// Indirect eval, after the frame has been popped. +var introducer, introduced; +dbg.onDebuggerStatement = function (frame) { + log += 'de1'; + introducer = frame.script; + dbg.onDebuggerStatement = function (frame) { + log += 'de2'; + introduced = frame.script.source; + }; +}; +log = ''; +g.evaluate('debugger; (0,eval)("\\n\\ndebugger;");', { lineNumber: 1066 }); +assertEq(log, 'de1de2'); +assertEq(introduced.introductionScript, introducer); +assertEq(introducer.getOffsetLine(introduced.introductionOffset), 1066); + +// Function constructor. +dbg.onDebuggerStatement = function (frame) { + log += 'o'; + var outerScript = frame.script; + var outerOffset = frame.offset; + dbg.onDebuggerStatement = function (frame) { + log += 'i'; + var source = frame.script.source; + assertEq(source.introductionScript, outerScript); + assertEq(outerScript.getOffsetLine(source.introductionOffset), + outerScript.getOffsetLine(outerOffset)); + }; +}; +log = ''; +g.eval('\n\n\ndebugger; Function("debugger;")()'); +assertEq(log, 'oi'); + +// Function constructor, after the the introduction call's frame has been +// popped. +var introducer; +dbg.onDebuggerStatement = function (frame) { + log += 'F2'; + introducer = frame.script; +}; +log = ''; +var fDO = gDO.evalInGlobal('debugger; Function("origami;")', { lineNumber: 1685 }).return; +var source = fDO.script.source; +assertEq(log, 'F2'); +assertEq(source.introductionScript, introducer); +assertEq(introducer.getOffsetLine(source.introductionOffset), 1685); + +// If the introduction script is in a different global from the script it +// introduced, we don't record it. +dbg.onDebuggerStatement = function (frame) { + log += 'x'; + var source = frame.script.source; + assertEq(source.introductionScript, undefined); + assertEq(source.introductionOffset, undefined); +}; +log = ''; +g.eval('debugger;'); // introduction script is this top-level script +assertEq(log, 'x'); + +// If the code is introduced by a function that doesn't provide +// introduction information, that shouldn't be a problem. +dbg.onDebuggerStatement = function (frame) { + log += 'x'; + var source = frame.script.source; + assertEq(source.introductionScript, undefined); + assertEq(source.introductionOffset, undefined); +}; +log = ''; +g.eval('evaluate("debugger;", { lineNumber: 1729 });'); +assertEq(log, 'x'); diff --git a/js/src/jit-test/tests/debug/Source-introductionScript-02.js b/js/src/jit-test/tests/debug/Source-introductionScript-02.js new file mode 100644 index 000000000000..a149978fc85f --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-introductionScript-02.js @@ -0,0 +1,31 @@ +// Calls to 'eval', etc. by JS primitives get attributed to the point of +// the primitive's call. + +var g = newGlobal(); +var dbg = new Debugger; +var gDO = dbg.addDebuggee(g); +var log = ''; + +function outerHandler(frame) { + log += 'o'; + var outerScript = frame.script; + + dbg.onDebuggerStatement = function (frame) { + log += 'i'; + var source = frame.script.source; + var introScript = source.introductionScript; + assertEq(introScript, outerScript); + assertEq(introScript.getOffsetLine(source.introductionOffset), 1234); + }; +}; + +log = ''; +dbg.onDebuggerStatement = outerHandler; +g.evaluate('debugger; ["debugger;"].map(eval)', { lineNumber: 1234 }); +assertEq(log, 'oi'); + +log = ''; +dbg.onDebuggerStatement = outerHandler; +g.evaluate('debugger; "debugger;".replace(/.*/, eval);', + { lineNumber: 1234 }); +assertEq(log, 'oi'); diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index e5c395358745..3a071812cce0 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -1826,7 +1826,7 @@ ScriptSource::initFromOptions(ExclusiveContext *cx, const ReadOnlyCompileOptions JS_HoldPrincipals(originPrincipals_); introductionType_ = options.introductionType; - introductionOffset_ = options.introductionOffset; + setIntroductionOffset(options.introductionOffset); if (options.hasIntroductionInfo) { JS_ASSERT(options.introductionType != nullptr); diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index d991634b4196..b2240ab5be25 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -3920,13 +3920,33 @@ DebuggerSource_getElementProperty(JSContext *cx, unsigned argc, Value *vp) return Debugger::fromChildJSObject(obj)->wrapDebuggeeValue(cx, args.rval()); } +static bool +DebuggerSource_getIntroductionScript(JSContext *cx, unsigned argc, Value *vp) +{ + THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get introductionScript)", args, obj, sourceObject); + + RootedScript script(cx, sourceObject->introductionScript()); + if (script) { + RootedObject scriptDO(cx, Debugger::fromChildJSObject(obj)->wrapScript(cx, script)); + if (!scriptDO) + return false; + args.rval().setObject(*scriptDO); + } else { + args.rval().setUndefined(); + } + return true; +} + static bool DebuggerSource_getIntroductionOffset(JSContext *cx, unsigned argc, Value *vp) { THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get introductionOffset)", args, obj, sourceObject); + // Regardless of what's recorded in the ScriptSourceObject and + // ScriptSource, only hand out the introduction offset if we also have + // the script within which it applies. ScriptSource *ss = sourceObject->source(); - if (ss->hasIntroductionOffset()) + if (ss->hasIntroductionOffset() && sourceObject->introductionScript()) args.rval().setInt32(ss->introductionOffset()); else args.rval().setUndefined(); @@ -3936,7 +3956,7 @@ DebuggerSource_getIntroductionOffset(JSContext *cx, unsigned argc, Value *vp) static bool DebuggerSource_getIntroductionType(JSContext *cx, unsigned argc, Value *vp) { - THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get introductionOffset)", args, obj, sourceObject); + THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get introductionType)", args, obj, sourceObject); ScriptSource *ss = sourceObject->source(); if (ss->hasIntroductionType()) { @@ -3955,6 +3975,7 @@ static const JSPropertySpec DebuggerSource_properties[] = { JS_PSG("url", DebuggerSource_getUrl, 0), JS_PSG("element", DebuggerSource_getElement, 0), JS_PSG("displayURL", DebuggerSource_getDisplayURL, 0), + JS_PSG("introductionScript", DebuggerSource_getIntroductionScript, 0), JS_PSG("introductionOffset", DebuggerSource_getIntroductionOffset, 0), JS_PSG("introductionType", DebuggerSource_getIntroductionType, 0), JS_PSG("elementAttributeName", DebuggerSource_getElementProperty, 0), diff --git a/toolkit/devtools/server/tests/mochitest/chrome.ini b/toolkit/devtools/server/tests/mochitest/chrome.ini index 4f6180a2923c..861e10ad87d9 100644 --- a/toolkit/devtools/server/tests/mochitest/chrome.ini +++ b/toolkit/devtools/server/tests/mochitest/chrome.ini @@ -12,6 +12,7 @@ support-files = Debugger.Source.prototype.element-2.js Debugger.Source.prototype.element.html +[test_Debugger.Source.prototype.introductionScript.html] [test_Debugger.Source.prototype.introductionType.html] [test_Debugger.Source.prototype.element.html] [test_Debugger.Script.prototype.global.html] diff --git a/toolkit/devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionScript.html b/toolkit/devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionScript.html new file mode 100644 index 000000000000..1978da31e573 --- /dev/null +++ b/toolkit/devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionScript.html @@ -0,0 +1,97 @@ + + + + + + Debugger.Source.prototype.introductionScript with no caller + + + + +
+
+
+ +