Bug 1246215 - Console prevents let re-declaration even when first was an error; r=jryans,shu

--HG--
extra : rebase_source : c911f3cbb74624b8e71297d975cd0aa3f055081b
This commit is contained in:
Morgan Phillips 2016-02-24 12:16:16 -06:00
Родитель 1ccd72789b
Коммит fd4ebed1e5
6 изменённых файлов: 145 добавлений и 2 удалений

Просмотреть файл

@ -19,6 +19,7 @@ loader.lazyRequireGetter(this, "ConsoleProgressListener", "devtools/shared/webco
loader.lazyRequireGetter(this, "events", "sdk/event/core");
loader.lazyRequireGetter(this, "ServerLoggingListener", "devtools/shared/webconsole/server-logger", true);
loader.lazyRequireGetter(this, "JSPropertyProvider", "devtools/shared/webconsole/js-property-provider", true);
loader.lazyRequireGetter(this, "Parser", "resource://devtools/shared/Parser.jsm", true);
for (let name of ["WebConsoleUtils", "ConsoleServiceListener",
"ConsoleAPIListener", "addWebConsoleCommands",
@ -1277,6 +1278,27 @@ WebConsoleActor.prototype =
}
else {
result = dbgWindow.executeInGlobalWithBindings(aString, bindings, evalOptions);
// Attempt to initialize any declarations found in the evaluated string
// since they may now be stuck in an "initializing" state due to the
// error. Already-initialized bindings will be ignored.
if ("throw" in result) {
let ast;
// Parse errors will raise an exception. We can/should ignore the error
// since it's already being handled elsewhere and we are only interested
// in initializing bindings.
try {
ast = Parser.reflectionAPI.parse(aString);
} catch (ex) {
ast = {"body": []};
}
for (let line of ast.body) {
if (line.type == "VariableDeclaration" &&
(line.kind == "let" || line.kind == "const")) {
for (let decl of line.declarations)
dbgWindow.forceLexicalInitializationByName(decl.id.name);
}
}
}
}
let helperResult = helpers.helperResult;

Просмотреть файл

@ -1,5 +1,5 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

Просмотреть файл

@ -66,7 +66,8 @@ function onAttach(aState, aResponse)
let tests = [doSimpleEval, doWindowEval, doEvalWithException,
doEvalWithHelper, doEvalString, doEvalLongString,
doEvalWithBinding, doEvalWithBindingFrame].map(t => {
doEvalWithBinding, doEvalWithBindingFrame,
forceLexicalInit].map(t => {
return Task.async(t);
});
@ -223,6 +224,25 @@ function* doEvalWithBindingFrame() {
nextTest()
}
function* forceLexicalInit() {
info("test `let x = SomeError` results in x being initialized to undefined");
let response = yield evaluateJS("let foopie = wubbalubadubdub;");
checkObject(response, {
from: gState.actor,
input: "let foopie = wubbalubadubdub;",
result: undefined,
});
ok(response.exception, "expected exception");
let response2 = yield evaluateJS("foopie;");
checkObject(response2, {
from: gState.actor,
input: "foopie;",
result: undefined,
});
ok(!response2.exception, "unexpected exception");
nextTest();
}
function testEnd()
{
// If this is the first run, reload the page and do it again.

Просмотреть файл

@ -544,3 +544,7 @@ code), the call throws a [`Debugger.DebuggeeWouldRun`][wouldrun] exception.
Debugger API: adapted portions of the code can use `Debugger.Object`
instances, but use this method to pass direct object references to code
that has not yet been updated.
<code>forceLexicalInitializationByName(<i>binding</i>)</code>
: If <i>binding</i> is in an uninitialized state initialize it to undefined
and return true, otherwise do nothing and return false.

Просмотреть файл

@ -0,0 +1,53 @@
load(libdir + "asserts.js");
var g = newGlobal();
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);
let errorOne, errorTwo;
function evalErrorStr(global, evalString) {
try {
global.evaluate(evalString);
return undefined;
} catch (e) {
return e.toString();
}
}
assertEq(evalErrorStr(g, "let y = IDONTEXIST;"), "ReferenceError: IDONTEXIST is not defined");
assertEq(evalErrorStr(g, "y = 1;"),
"ReferenceError: can't access lexical declaration `y' before initialization");
// Here we flip the uninitialized binding to undfined.
assertEq(gw.forceLexicalInitializationByName("y"), true);
assertEq(g.evaluate("y"), undefined);
g.evaluate("y = 1;");
assertEq(g.evaluate("y"), 1);
// Ensure that bogus bindings return false, but otherwise trigger no error or
// side effect.
assertEq(gw.forceLexicalInitializationByName("idontexist"), false);
assertEq(evalErrorStr(g, "idontexist"), "ReferenceError: idontexist is not defined");
// Ensure that only strings are accepted by forceLexicalInitializationByName
const bad_types = [
2112,
{geddy: "lee"},
() => 1,
[],
Array
]
for (var badType of bad_types) {
assertThrowsInstanceOf(() => {
gw.forceLexicalInitializationByName(badType);
}, TypeError);
}
// Finally, supplying no arguments should throw a type error
assertThrowsInstanceOf(() => {
Debugger.isCompilableUnit();
}, TypeError);

Просмотреть файл

@ -4767,6 +4767,7 @@ Debugger::isCompilableUnit(JSContext* cx, unsigned argc, Value* vp)
return true;
}
bool
Debugger::drainTraceLoggerScriptCalls(JSContext* cx, unsigned argc, Value* vp)
{
@ -8032,6 +8033,48 @@ RequireGlobalObject(JSContext* cx, HandleValue dbgobj, HandleObject referent)
return true;
}
// Lookup a binding on the referent's global scope and change it to undefined
// if it is an uninitialized lexical, otherwise do nothing. The method's return
// value is true _only_ when an uninitialized lexical has been altered, otherwise
// it is false.
bool
DebuggerObject_forceLexicalInitializationByName(JSContext *cx, unsigned argc, Value* vp)
{
THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "forceLexicalInitializationByname", args, referent);
if (!args.requireAtLeast(cx, "Debugger.Object.prototype.forceLexicalInitializationByName", 1))
return false;
if (!RequireGlobalObject(cx, args.thisv(), referent))
return false;
RootedObject globalLexical(cx, &referent->as<GlobalObject>().lexicalScope());
if (!args[0].isString()) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
JSMSG_NOT_EXPECTED_TYPE, "Debugger.Object.prototype.forceLexicalInitializationByName",
"string", InformalValueTypeName(args[0]));
return false;
}
PropertyName* name = args[0].toString()->asAtom().asPropertyName();
bool initialized = false;
Shape* s = nullptr;
JSObject* scope = nullptr;
JSObject* pobj = nullptr;
if (LookupNameNoGC(cx, name, globalLexical, &scope, &pobj, &s)) {
Value v = globalLexical->as<NativeObject>().getSlot(s->slot());
if (s->hasSlot() && v.isMagic() && v.whyMagic() == JS_UNINITIALIZED_LEXICAL) {
globalLexical->as<NativeObject>().setSlot(s->slot(), UndefinedValue());
initialized = true;
}
}
args.rval().setBoolean(initialized);
return true;
}
static bool
DebuggerObject_executeInGlobal(JSContext* cx, unsigned argc, Value* vp)
{
@ -8151,6 +8194,7 @@ static const JSFunctionSpec DebuggerObject_methods[] = {
JS_FN("freeze", DebuggerObject_freeze, 0, 0),
JS_FN("preventExtensions", DebuggerObject_preventExtensions, 0, 0),
JS_FN("isSealed", DebuggerObject_isSealed, 0, 0),
JS_FN("forceLexicalInitializationByName", DebuggerObject_forceLexicalInitializationByName, 1, 0),
JS_FN("isFrozen", DebuggerObject_isFrozen, 0, 0),
JS_FN("isExtensible", DebuggerObject_isExtensible, 0, 0),
JS_FN("apply", DebuggerObject_apply, 0, 0),