Codegen: Disallow duplicate declarations of const properties

Spec 13.3.1.1 (Static Semantics: Early Errors) says:

    It is a Syntax Error if the BoundNames of BindingList contains any
    duplicate entries.

Only let/const are supposed to be treated in this way, so we ensure that
one of them has been marked read-only (since we don't support "let"
yet).

There's still no runtime check on assigning to a constant-declared variable.

[ChangeLog][QtQml] "const" variable declarations now throw a SyntaxError if
multiple attempts to declare the same variable name are found. Note that
"const" is still not fully spec-compliant (i.e. reassignment at runtime is
not disallowed).

Task-number: QTBUG-58493
Change-Id: I31fd5f2bf3e79d48734e8ecb714c4e7f47e31d2a
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
This commit is contained in:
Robin Burchell 2017-01-28 14:48:23 +01:00
parent b63393c7aa
commit 5f807a6276
3 changed files with 53 additions and 5 deletions

View File

@ -220,7 +220,15 @@ bool Codegen::ScanFunctions::visit(VariableDeclaration *ast)
_cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Missing initializer in const declaration"));
return false;
}
_env->enter(ast->name.toString(), ast->expression ? Environment::VariableDefinition : Environment::VariableDeclaration);
QString name = ast->name.toString();
const Environment::Member *m = 0;
if (_env->memberInfo(name, &m)) {
if (m->isConstant || ast->readOnly) {
_cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Identifier %1 has already been declared").arg(name));
return false;
}
}
_env->enter(ast->name.toString(), ast->expression ? Environment::VariableDefinition : Environment::VariableDeclaration, ast->readOnly);
return true;
}
@ -391,7 +399,7 @@ void Codegen::ScanFunctions::enterFunction(Node *ast, const QString &name, Forma
_env->hasNestedFunctions = true;
// The identifier of a function expression cannot be referenced from the enclosing environment.
if (expr)
_env->enter(name, Environment::FunctionDefinition, expr);
_env->enter(name, Environment::FunctionDefinition, false /* readonly */, expr);
if (name == QLatin1String("arguments"))
_env->usesArgumentsObject = Environment::ArgumentsObjectNotUsed;
wasStrict = _env->isStrict;
@ -2044,7 +2052,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast,
function->column = loc.startColumn;
if (function->usesArgumentsObject)
_env->enter(QStringLiteral("arguments"), Environment::VariableDeclaration);
_env->enter(QStringLiteral("arguments"), Environment::VariableDeclaration, false /* readonly */);
// variables in global code are properties of the global context object, not locals as with other functions.
if (_env->compilationMode == FunctionCode || _env->compilationMode == QmlBinding) {
@ -2062,7 +2070,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast,
function->LOCAL(inheritedLocal);
unsigned tempIndex = entryBlock->newTemp();
Environment::Member member = { Environment::UndefinedMember,
static_cast<int>(tempIndex), 0 };
static_cast<int>(tempIndex), 0, false /* readonly */ };
_env->members.insert(inheritedLocal, member);
}
}

View File

@ -145,6 +145,7 @@ protected:
MemberType type;
int index;
AST::FunctionExpression *function;
bool isConstant : 1;
};
typedef QMap<QString, Member> MemberMap;
@ -191,6 +192,18 @@ protected:
return (*it).index;
}
bool memberInfo(const QString &name, const Member **m) const
{
Q_ASSERT(m);
MemberMap::const_iterator it = members.find(name);
if (it == members.end()) {
*m = 0;
return false;
}
*m = &(*it);
return true;
}
bool lookupMember(const QString &name, Environment **scope, int *index, int *distance)
{
Environment *it = this;
@ -206,7 +219,7 @@ protected:
return false;
}
void enter(const QString &name, MemberType type, AST::FunctionExpression *function = 0)
void enter(const QString &name, MemberType type, bool isConstant, AST::FunctionExpression *function = 0)
{
if (! name.isEmpty()) {
if (type != FunctionDefinition) {
@ -220,8 +233,10 @@ protected:
m.index = -1;
m.type = type;
m.function = function;
m.isConstant = isConstant;
members.insert(name, m);
} else {
Q_ASSERT(isConstant == (*it).isConstant);
if ((*it).type <= type) {
(*it).type = type;
(*it).function = function;

View File

@ -8208,6 +8208,12 @@ void tst_qqmlecmascript::constkw_data()
"v + i\n"
<< false
<< QVariant(25);
QTest::newRow("const-multiple-scopes-same-var")
<< "const v = 3\n"
"function f() { const v = 1; return v; }\n"
"v + f()\n"
<< false
<< QVariant(4);
// error cases
QTest::newRow("const-no-initializer")
@ -8218,6 +8224,25 @@ void tst_qqmlecmascript::constkw_data()
<< "const v = 1, i\n"
<< true
<< QVariant("SyntaxError: Missing initializer in const declaration");
QTest::newRow("const-no-duplicate")
<< "const v = 1, v = 2\n"
<< true
<< QVariant("SyntaxError: Identifier v has already been declared");
QTest::newRow("const-no-duplicate-2")
<< "const v = 1\n"
"const v = 2\n"
<< true
<< QVariant("SyntaxError: Identifier v has already been declared");
QTest::newRow("const-no-duplicate-var")
<< "const v = 1\n"
"var v = 1\n"
<< true
<< QVariant("SyntaxError: Identifier v has already been declared");
QTest::newRow("var-no-duplicate-const")
<< "var v = 1\n"
"const v = 1\n"
<< true
<< QVariant("SyntaxError: Identifier v has already been declared");
}
void tst_qqmlecmascript::constkw()