Bug 1235590 - Allow redeclaring block-scoped functions and warn about deprecation for now. (r=jorendorff)

This commit is contained in:
Shu-yu Guo 2016-01-23 13:28:45 -08:00
parent 49a9478019
commit 8f3e583a52
11 changed files with 215 additions and 8 deletions

View File

@ -2415,6 +2415,14 @@ ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst)
return declaration(pn, dst);
case PNK_ANNEXB_FUNCTION:
// XXXshu NOP check used only for phasing in block-scope function
// XXXshu early errors.
// XXXshu
// XXXshu Back out when major version >= 50. See [1].
// XXXshu
// XXXshu [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1235590#c10
if (pn->pn_left->isKind(PNK_NOP))
return builder.emptyStatement(&pn->pn_pos, dst);
return declaration(pn->pn_left, dst);
case PNK_LETBLOCK:

View File

@ -2344,6 +2344,18 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
case PNK_ANNEXB_FUNCTION:
MOZ_ASSERT(pn->isArity(PN_BINARY));
// XXXshu NOP check used only for phasing in block-scope function
// XXXshu early errors.
// XXXshu
// XXXshu Back out when major version >= 50. See [1].
// XXXshu
// XXXshu [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1235590#c10
if (pn->pn_left->isKind(PNK_NOP)) {
*answer = false;
return true;
}
return checkSideEffects(pn->pn_left, answer);
case PNK_ARGSBODY:
@ -8543,7 +8555,19 @@ BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote)
switch (pn->getKind()) {
case PNK_FUNCTION:
if (!emitFunction(pn))
return false;
break;
case PNK_ANNEXB_FUNCTION:
// XXXshu NOP check used only for phasing in block-scope function
// XXXshu early errors.
// XXXshu
// XXXshu Back out when major version >= 50. See [1].
// XXXshu
// XXXshu [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1235590#c10
if (pn->pn_left->isKind(PNK_NOP))
break;
if (!emitFunction(pn))
return false;
break;

View File

@ -1789,6 +1789,14 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
return FoldFunction(cx, pn, parser, inGenexpLambda);
case PNK_ANNEXB_FUNCTION:
// XXXshu NOP check used only for phasing in block-scope function
// XXXshu early errors.
// XXXshu
// XXXshu Back out when major version >= 50. See [1].
// XXXshu
// XXXshu [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1235590#c10
if (pn->pn_left->isKind(PNK_NOP))
return true;
return FoldFunction(cx, pn->pn_left, parser, inGenexpLambda);
case PNK_MODULE:

View File

@ -441,6 +441,18 @@ class AtomDecls
return p.value().front<ParseHandler>();
}
/* Return the definition at the tail of the chain for |atom|. */
DefinitionNode lookupLast(JSAtom* atom) const {
MOZ_ASSERT(map);
DefinitionList::Range range = lookupMulti(atom);
DefinitionNode dn = ParseHandler::nullDefinition();
while (!range.empty()) {
dn = range.front<ParseHandler>();
range.popFront();
}
return dn;
}
/* Perform a lookup that can iterate over the definitions associated with |atom|. */
DefinitionList::Range lookupMulti(JSAtom* atom) const {
MOZ_ASSERT(map);

View File

@ -392,6 +392,24 @@ ParseContext<ParseHandler>::updateDecl(TokenStream& ts, JSAtom* atom, Node pn)
Definition* newDecl = &pn->template as<Definition>();
decls_.updateFirst(atom, newDecl);
if (oldDecl->isOp(JSOP_INITLEXICAL)) {
// XXXshu Special case used only for phasing in block-scope function
// XXXshu early errors.
// XXXshu
// XXXshu Back out when major version >= 50. See [1].
// XXXshu
// XXXshu [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1235590#c10
MOZ_ASSERT(oldDecl->getKind() == PNK_FUNCTION);
MOZ_ASSERT(newDecl->getKind() == PNK_FUNCTION);
MOZ_ASSERT(!sc->strict());
MOZ_ASSERT(oldDecl->isBound());
MOZ_ASSERT(!oldDecl->pn_scopecoord.isFree());
newDecl->pn_scopecoord = oldDecl->pn_scopecoord;
newDecl->pn_dflags |= PND_BOUND;
newDecl->setOp(JSOP_INITLEXICAL);
return;
}
if (sc->isGlobalContext() || oldDecl->isDeoptimized()) {
MOZ_ASSERT(newDecl->isFreeVar());
// Global 'var' bindings have no slots, but are still tracked for
@ -2409,9 +2427,31 @@ Parser<FullParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
if (annexDef->kind() == Definition::CONSTANT ||
annexDef->kind() == Definition::LET)
{
// Do not emit Annex B assignment if we would've
// thrown a redeclaration error.
annexDef = nullptr;
if (annexDef->isKind(PNK_FUNCTION)) {
// XXXshu Code used only for phasing in block-scope
// XXXshu function early errors. Ignore redeclarations
// XXXshu here and generate Annex B assignments for
// XXXshu block-scoped functions that redeclare other
// XXXshu block-scoped functions.
// XXXshu
// XXXshu Get the possibly-synthesized var that was
// XXXshu already made for the first of the block-scoped
// XXXshu functions.
// XXXshu
// XXXshu Back out when major version >= 50. See [1].
// XXXshu
// XXXshu [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1235590#c10
annexDef = pc->decls().lookupLast(funName);
if (annexDef->kind() == Definition::CONSTANT ||
annexDef->kind() == Definition::LET)
{
annexDef = nullptr;
}
} else {
// Do not emit Annex B assignment if we would've
// thrown a redeclaration error.
annexDef = nullptr;
}
}
} else {
// Synthesize a new 'var' binding if one does not exist.
@ -3653,8 +3693,41 @@ Parser<FullParseHandler>::bindLexical(BindData<FullParseHandler>* data,
// The reason we compare using >= instead of == on the block id is to
// detect redeclarations where a 'var' binding first appeared in a
// nested block: |{ var x; } let x;|
if (dn && dn->pn_blockid >= pc->blockid())
if (dn && dn->pn_blockid >= pc->blockid()) {
// XXXshu Used only for phasing in block-scope function early
// XXXshu errors.
// XXXshu
// XXXshu Back out when major version >= 50. See [1].
// XXXshu
// XXXshu [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1235590#c10
if (pn->isKind(PNK_FUNCTION) && dn->isKind(PNK_FUNCTION) && !pc->sc->strict()) {
if (!parser->makeDefIntoUse(dn, pn, name))
return false;
MOZ_ASSERT(blockScope);
Shape* shape = blockScope->lastProperty()->search(cx, NameToId(name));
MOZ_ASSERT(shape);
uint32_t oldDefIndex = blockScope->shapeToIndex(*shape);
blockScope->updateDefinitionParseNode(oldDefIndex, dn,
reinterpret_cast<Definition*>(pn));
parser->addTelemetry(JSCompartment::DeprecatedBlockScopeFunRedecl);
JSAutoByteString bytes;
if (!AtomToPrintableString(cx, name, &bytes))
return false;
if (!parser->report(ParseWarning, false, null(),
JSMSG_DEPRECATED_BLOCK_SCOPE_FUN_REDECL,
bytes.ptr()))
{
return false;
}
return true;
}
return parser->reportRedeclaration(pn, dn->kind(), name);
}
if (!pc->define(parser->tokenStream, name, pn, bindingKind))
return false;
}

View File

@ -252,6 +252,7 @@ MSG_DEF(JSMSG_DEPRECATED_FLAGS_ARG, 0, JSEXN_NONE, "flags argument of String.
MSG_DEF(JSMSG_DEPRECATED_FOR_EACH, 0, JSEXN_NONE, "JavaScript 1.6's for-each-in loops are deprecated; consider using ES6 for-of instead")
MSG_DEF(JSMSG_DEPRECATED_OCTAL, 0, JSEXN_SYNTAXERR, "octal literals and octal escape sequences are deprecated")
MSG_DEF(JSMSG_DEPRECATED_PRAGMA, 1, JSEXN_NONE, "Using //@ to indicate {0} pragmas is deprecated. Use //# instead")
MSG_DEF(JSMSG_DEPRECATED_BLOCK_SCOPE_FUN_REDECL, 1, JSEXN_NONE, "redeclaration of block-scoped function `{0}' is deprecated")
MSG_DEF(JSMSG_DUPLICATE_EXPORT_NAME, 1, JSEXN_SYNTAXERR, "duplicate export name '{0}'")
MSG_DEF(JSMSG_DUPLICATE_FORMAL, 1, JSEXN_SYNTAXERR, "duplicate formal argument {0}")
MSG_DEF(JSMSG_DUPLICATE_LABEL, 0, JSEXN_SYNTAXERR, "duplicate label")

View File

@ -753,6 +753,7 @@ struct JSCompartment
DeprecatedFlagsArgument = 7, // JS 1.3 or older
// NO LONGER USING 8
// NO LONGER USING 9
DeprecatedBlockScopeFunRedecl = 10,
DeprecatedLanguageExtensionCount
};

View File

@ -0,0 +1,66 @@
{
assertEq(f(), 4);
function f() { return 3; }
assertEq(f(), 4);
function f() { return 4; }
assertEq(f(), 4);
}
// Annex B still works.
assertEq(f(), 4);
function test() {
{
assertEq(f(), 2);
function f() { return 1; }
assertEq(f(), 2);
function f() { return 2; }
assertEq(f(), 2);
}
// Annex B still works.
assertEq(f(), 2);
}
test();
var log = '';
try {
// Strict mode still cannot redeclare.
eval(`"use strict";
{
function f() { }
function f() { }
}`);
} catch (e) {
assertEq(e instanceof SyntaxError, true);
log += 'e';
}
try {
// Redeclaring an explicitly 'let'-declared binding doesn't work.
eval(`{
let x = 42;
function x() {}
}`);
} catch (e) {
assertEq(e instanceof SyntaxError, true);
log += 'e';
}
try {
// Redeclaring an explicitly 'const'-declared binding doesn't work.
eval(`{
const x = 42;
function x() {}
}`);
} catch (e) {
assertEq(e instanceof SyntaxError, true);
log += 'e';
}
assertEq(log, 'eee');
if ('reportCompare' in this)
reportCompare(true, true);

View File

@ -265,6 +265,20 @@ class StaticBlockScope : public NestedStaticScope
setSlotValue(i, PrivateValue(def));
}
// XXXshu Used only for phasing in block-scope function early
// XXXshu errors.
// XXXshu
// XXXshu Back out when major version >= 50. See [1].
// XXXshu
// XXXshu [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1235590#c10
void updateDefinitionParseNode(unsigned i,
frontend::Definition* oldDef,
frontend::Definition* newDef)
{
MOZ_ASSERT(definitionParseNode(i) == oldDef);
setSlotValue(i, PrivateValue(newDef));
}
frontend::Definition* definitionParseNode(unsigned i) {
Value v = slotValue(i);
return reinterpret_cast<frontend::Definition*>(v.toPrivate());

View File

@ -29,11 +29,11 @@ namespace js {
*
* https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
*/
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 341;
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 342;
static const uint32_t XDR_BYTECODE_VERSION =
uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
static_assert(JSErr_Limit == 437,
static_assert(JSErr_Limit == 438,
"GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or "
"removed MSG_DEFs from js.msg, you should increment "
"XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "

View File

@ -507,14 +507,14 @@
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 10,
"description": "Use of SpiderMonkey's deprecated language extensions in web content: ForEach=0, DestructuringForIn=1 (obsolete), LegacyGenerator=2, ExpressionClosure=3, LetBlock=4 (obsolete), LetExpression=5 (obsolete), NoSuchMethod=6 (obsolete), FlagsArgument=7, RegExpSourceProp=8 (obsolete), RestoredRegExpStatics=9 (obsolete)"
"description": "Use of SpiderMonkey's deprecated language extensions in web content: ForEach=0, DestructuringForIn=1 (obsolete), LegacyGenerator=2, ExpressionClosure=3, LetBlock=4 (obsolete), LetExpression=5 (obsolete), NoSuchMethod=6 (obsolete), FlagsArgument=7, RegExpSourceProp=8 (obsolete), RestoredRegExpStatics=9 (obsolete), BlockScopeFunRedecl=10"
},
"JS_DEPRECATED_LANGUAGE_EXTENSIONS_IN_ADDONS": {
"alert_emails": ["jdemooij@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 10,
"description": "Use of SpiderMonkey's deprecated language extensions in add-ons: ForEach=0, DestructuringForIn=1 (obsolete), LegacyGenerator=2, ExpressionClosure=3, LetBlock=4 (obsolete), LetExpression=5 (obsolete), NoSuchMethod=6 (obsolete), FlagsArgument=7, RegExpSourceProp=8 (obsolete), RestoredRegExpStatics=9 (obsolete)"
"description": "Use of SpiderMonkey's deprecated language extensions in add-ons: ForEach=0, DestructuringForIn=1 (obsolete), LegacyGenerator=2, ExpressionClosure=3, LetBlock=4 (obsolete), LetExpression=5 (obsolete), NoSuchMethod=6 (obsolete), FlagsArgument=7, RegExpSourceProp=8 (obsolete), RestoredRegExpStatics=9 (obsolete), BlockScopeFunRedecl=10"
},
"XUL_CACHE_DISABLED": {
"expires_in_version": "default",