420857 - Syntax error for missing brace should mention the line number of the opening brace.
This commit is contained in:
parent
d3e6886052
commit
713888bd05
|
@ -66,29 +66,32 @@ using UsedNamePtr = UsedNameTracker::UsedNameMap::Ptr;
|
|||
|
||||
// Read a token. Report an error and return null() if that token doesn't match
|
||||
// to the condition. Do not use MUST_MATCH_TOKEN_INTERNAL directly.
|
||||
#define MUST_MATCH_TOKEN_INTERNAL(cond, modifier, errorNumber) \
|
||||
#define MUST_MATCH_TOKEN_INTERNAL(cond, modifier, errorReport) \
|
||||
JS_BEGIN_MACRO \
|
||||
TokenKind token; \
|
||||
if (!tokenStream.getToken(&token, modifier)) \
|
||||
return null(); \
|
||||
if (!(cond)) { \
|
||||
error(errorNumber); \
|
||||
errorReport; \
|
||||
return null(); \
|
||||
} \
|
||||
JS_END_MACRO
|
||||
|
||||
#define MUST_MATCH_TOKEN_MOD(tt, modifier, errorNumber) \
|
||||
MUST_MATCH_TOKEN_INTERNAL(token == tt, modifier, errorNumber)
|
||||
MUST_MATCH_TOKEN_INTERNAL(token == tt, modifier, error(errorNumber))
|
||||
|
||||
#define MUST_MATCH_TOKEN(tt, errorNumber) \
|
||||
MUST_MATCH_TOKEN_MOD(tt, TokenStream::None, errorNumber)
|
||||
|
||||
#define MUST_MATCH_TOKEN_FUNC_MOD(func, modifier, errorNumber) \
|
||||
MUST_MATCH_TOKEN_INTERNAL((func)(token), modifier, errorNumber)
|
||||
MUST_MATCH_TOKEN_INTERNAL((func)(token), modifier, error(errorNumber))
|
||||
|
||||
#define MUST_MATCH_TOKEN_FUNC(func, errorNumber) \
|
||||
MUST_MATCH_TOKEN_FUNC_MOD(func, TokenStream::None, errorNumber)
|
||||
|
||||
#define MUST_MATCH_TOKEN_MOD_WITH_REPORT(tt, modifier, errorReport) \
|
||||
MUST_MATCH_TOKEN_INTERNAL(token == tt, modifier, errorReport)
|
||||
|
||||
template <class T, class U>
|
||||
static inline void
|
||||
PropagateTransitiveParseFlags(const T* inner, U* outer)
|
||||
|
@ -1012,6 +1015,35 @@ Parser<ParseHandler>::hasValidSimpleStrictParameterNames()
|
|||
return true;
|
||||
}
|
||||
|
||||
template <typename ParseHandler>
|
||||
void
|
||||
Parser<ParseHandler>::reportMissingClosing(unsigned errorNumber, unsigned noteNumber,
|
||||
uint32_t openedPos)
|
||||
{
|
||||
auto notes = MakeUnique<JSErrorNotes>();
|
||||
if (!notes)
|
||||
return;
|
||||
|
||||
uint32_t line, column;
|
||||
tokenStream.srcCoords.lineNumAndColumnIndex(openedPos, &line, &column);
|
||||
|
||||
const size_t MaxWidth = sizeof("4294967295");
|
||||
char columnNumber[MaxWidth];
|
||||
SprintfLiteral(columnNumber, "%" PRIu32, column);
|
||||
char lineNumber[MaxWidth];
|
||||
SprintfLiteral(lineNumber, "%" PRIu32, line);
|
||||
|
||||
if (!notes->addNoteASCII(pc->sc()->context,
|
||||
getFilename(), line, column,
|
||||
GetErrorMessage, nullptr,
|
||||
noteNumber, lineNumber, columnNumber))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
errorWithNotes(Move(notes), errorNumber);
|
||||
}
|
||||
|
||||
template <typename ParseHandler>
|
||||
void
|
||||
Parser<ParseHandler>::reportRedeclaration(HandlePropertyName name, DeclarationKind prevKind,
|
||||
|
@ -1039,11 +1071,11 @@ Parser<ParseHandler>::reportRedeclaration(HandlePropertyName name, DeclarationKi
|
|||
char lineNumber[MaxWidth];
|
||||
SprintfLiteral(lineNumber, "%" PRIu32, line);
|
||||
|
||||
if (!notes->addNoteLatin1(pc->sc()->context,
|
||||
getFilename(), line, column,
|
||||
GetErrorMessage, nullptr,
|
||||
JSMSG_REDECLARED_PREV,
|
||||
lineNumber, columnNumber))
|
||||
if (!notes->addNoteASCII(pc->sc()->context,
|
||||
getFilename(), line, column,
|
||||
GetErrorMessage, nullptr,
|
||||
JSMSG_REDECLARED_PREV,
|
||||
lineNumber, columnNumber))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -3613,6 +3645,7 @@ Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling,
|
|||
TokenKind tt;
|
||||
if (!tokenStream.getToken(&tt, TokenStream::Operand))
|
||||
return false;
|
||||
uint32_t openedPos = 0;
|
||||
if (tt != TOK_LC) {
|
||||
if ((funbox->isStarGenerator() && !funbox->isAsync()) || kind == Method ||
|
||||
kind == GetterNoExpressionClosure || kind == SetterNoExpressionClosure ||
|
||||
|
@ -3634,6 +3667,8 @@ Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling,
|
|||
tokenStream.ungetToken();
|
||||
bodyType = ExpressionBody;
|
||||
funbox->setIsExprBody();
|
||||
} else {
|
||||
openedPos = pos().begin;
|
||||
}
|
||||
|
||||
// Arrow function parameters inherit yieldHandling from the enclosing
|
||||
|
@ -3671,13 +3706,9 @@ Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling,
|
|||
}
|
||||
|
||||
if (bodyType == StatementListBody) {
|
||||
bool matched;
|
||||
if (!tokenStream.matchToken(&matched, TOK_RC, TokenStream::Operand))
|
||||
return false;
|
||||
if (!matched) {
|
||||
error(JSMSG_CURLY_AFTER_BODY);
|
||||
return false;
|
||||
}
|
||||
MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand,
|
||||
reportMissingClosing(JSMSG_CURLY_AFTER_BODY,
|
||||
JSMSG_CURLY_OPENED, openedPos));
|
||||
funbox->setEnd(pos().end);
|
||||
} else {
|
||||
#if !JS_HAS_EXPR_CLOSURES
|
||||
|
@ -4449,6 +4480,7 @@ typename ParseHandler::Node
|
|||
Parser<ParseHandler>::blockStatement(YieldHandling yieldHandling, unsigned errorNumber)
|
||||
{
|
||||
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC));
|
||||
uint32_t openedPos = pos().begin;
|
||||
|
||||
ParseContext::Statement stmt(pc, StatementKind::Block);
|
||||
ParseContext::Scope scope(this);
|
||||
|
@ -4459,7 +4491,9 @@ Parser<ParseHandler>::blockStatement(YieldHandling yieldHandling, unsigned error
|
|||
if (!list)
|
||||
return null();
|
||||
|
||||
MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, errorNumber);
|
||||
MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand,
|
||||
reportMissingClosing(errorNumber, JSMSG_CURLY_OPENED,
|
||||
openedPos));
|
||||
|
||||
return finishLexicalScope(scope, list);
|
||||
}
|
||||
|
@ -6711,6 +6745,8 @@ Parser<ParseHandler>::tryStatement(YieldHandling yieldHandling)
|
|||
{
|
||||
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_TRY);
|
||||
|
||||
uint32_t openedPos = pos().begin;
|
||||
|
||||
ParseContext::Statement stmt(pc, StatementKind::Try);
|
||||
ParseContext::Scope scope(this);
|
||||
if (!scope.init(pc))
|
||||
|
@ -6724,7 +6760,9 @@ Parser<ParseHandler>::tryStatement(YieldHandling yieldHandling)
|
|||
if (!innerBlock)
|
||||
return null();
|
||||
|
||||
MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_TRY);
|
||||
MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand,
|
||||
reportMissingClosing(JSMSG_CURLY_AFTER_TRY,
|
||||
JSMSG_CURLY_OPENED, openedPos));
|
||||
}
|
||||
|
||||
bool hasUnconditionalCatch = false;
|
||||
|
@ -6840,6 +6878,8 @@ Parser<ParseHandler>::tryStatement(YieldHandling yieldHandling)
|
|||
if (tt == TOK_FINALLY) {
|
||||
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_FINALLY);
|
||||
|
||||
uint32_t openedPos = pos().begin;
|
||||
|
||||
ParseContext::Statement stmt(pc, StatementKind::Finally);
|
||||
ParseContext::Scope scope(this);
|
||||
if (!scope.init(pc))
|
||||
|
@ -6853,7 +6893,9 @@ Parser<ParseHandler>::tryStatement(YieldHandling yieldHandling)
|
|||
if (!finallyBlock)
|
||||
return null();
|
||||
|
||||
MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_FINALLY);
|
||||
MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand,
|
||||
reportMissingClosing(JSMSG_CURLY_AFTER_FINALLY,
|
||||
JSMSG_CURLY_OPENED, openedPos));
|
||||
} else {
|
||||
tokenStream.ungetToken();
|
||||
}
|
||||
|
@ -6870,6 +6912,8 @@ typename ParseHandler::Node
|
|||
Parser<ParseHandler>::catchBlockStatement(YieldHandling yieldHandling,
|
||||
ParseContext::Scope& catchParamScope)
|
||||
{
|
||||
uint32_t openedPos = pos().begin;
|
||||
|
||||
ParseContext::Statement stmt(pc, StatementKind::Block);
|
||||
|
||||
// ES 13.15.7 CatchClauseEvaluation
|
||||
|
@ -6889,7 +6933,9 @@ Parser<ParseHandler>::catchBlockStatement(YieldHandling yieldHandling,
|
|||
if (!list)
|
||||
return null();
|
||||
|
||||
MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_CATCH);
|
||||
MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand,
|
||||
reportMissingClosing(JSMSG_CURLY_AFTER_CATCH,
|
||||
JSMSG_CURLY_OPENED, openedPos));
|
||||
|
||||
// The catch parameter names are not bound in the body scope, so remove
|
||||
// them before generating bindings.
|
||||
|
@ -9243,7 +9289,9 @@ Parser<ParseHandler>::arrayInitializer(YieldHandling yieldHandling, PossibleErro
|
|||
}
|
||||
}
|
||||
|
||||
MUST_MATCH_TOKEN_MOD(TOK_RB, modifier, JSMSG_BRACKET_AFTER_LIST);
|
||||
MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RB, modifier,
|
||||
reportMissingClosing(JSMSG_BRACKET_AFTER_LIST,
|
||||
JSMSG_BRACKET_OPENED, begin));
|
||||
}
|
||||
handler.setEndPosition(literal, pos().end);
|
||||
return literal;
|
||||
|
@ -9478,6 +9526,8 @@ Parser<ParseHandler>::objectLiteral(YieldHandling yieldHandling, PossibleError*
|
|||
{
|
||||
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC));
|
||||
|
||||
uint32_t openedPos = pos().begin;
|
||||
|
||||
Node literal = handler.newObjectLiteral(pos().begin);
|
||||
if (!literal)
|
||||
return null();
|
||||
|
@ -9640,7 +9690,7 @@ Parser<ParseHandler>::objectLiteral(YieldHandling yieldHandling, PossibleError*
|
|||
if (tt == TOK_RC)
|
||||
break;
|
||||
if (tt != TOK_COMMA) {
|
||||
error(JSMSG_CURLY_AFTER_LIST);
|
||||
reportMissingClosing(JSMSG_CURLY_AFTER_LIST, JSMSG_CURLY_OPENED, openedPos);
|
||||
return null();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1459,6 +1459,8 @@ class Parser final : public ParserBase, private JS::AutoGCRooter
|
|||
|
||||
bool hasValidSimpleStrictParameterNames();
|
||||
|
||||
void reportMissingClosing(unsigned errorNumber, unsigned noteNumber, uint32_t openedPos);
|
||||
|
||||
void reportRedeclaration(HandlePropertyName name, DeclarationKind prevKind, TokenPos pos,
|
||||
uint32_t prevPos);
|
||||
bool notePositionalFormalParameter(Node fn, HandlePropertyName name, uint32_t beginPos,
|
||||
|
|
90
js/src/jit-test/tests/parser/missing-closing-brace.js
Normal file
90
js/src/jit-test/tests/parser/missing-closing-brace.js
Normal file
|
@ -0,0 +1,90 @@
|
|||
function test(source, [lineNumber, columnNumber], openType = "{", closeType = "}") {
|
||||
let caught = false;
|
||||
try {
|
||||
Reflect.parse(source, { source: "foo.js" });
|
||||
} catch (e) {
|
||||
assertEq(e.message.includes("missing " + closeType + " "), true);
|
||||
let notes = getErrorNotes(e);
|
||||
assertEq(notes.length, 1);
|
||||
let note = notes[0];
|
||||
assertEq(note.message, openType + " opened at line " + lineNumber + ", column " + columnNumber);
|
||||
assertEq(note.fileName, "foo.js");
|
||||
assertEq(note.lineNumber, lineNumber);
|
||||
assertEq(note.columnNumber, columnNumber);
|
||||
caught = true;
|
||||
}
|
||||
assertEq(caught, true);
|
||||
}
|
||||
|
||||
// Function
|
||||
|
||||
test(`
|
||||
function test1() {
|
||||
}
|
||||
function test2() {
|
||||
if (true) {
|
||||
//}
|
||||
}
|
||||
function test3() {
|
||||
}
|
||||
`, [4, 17]);
|
||||
|
||||
// Block statement.
|
||||
test(`
|
||||
{
|
||||
if (true) {
|
||||
}
|
||||
`, [2, 0]);
|
||||
test(`
|
||||
if (true) {
|
||||
if (true) {
|
||||
}
|
||||
`, [2, 10]);
|
||||
test(`
|
||||
for (;;) {
|
||||
if (true) {
|
||||
}
|
||||
`, [2, 9]);
|
||||
test(`
|
||||
while (true) {
|
||||
if (true) {
|
||||
}
|
||||
`, [2, 13]);
|
||||
test(`
|
||||
do {
|
||||
do {
|
||||
} while(true);
|
||||
`, [2, 3]);
|
||||
|
||||
// try-catch-finally.
|
||||
test(`
|
||||
try {
|
||||
if (true) {
|
||||
}
|
||||
`, [2, 4]);
|
||||
test(`
|
||||
try {
|
||||
} catch (e) {
|
||||
if (true) {
|
||||
}
|
||||
`, [3, 12]);
|
||||
test(`
|
||||
try {
|
||||
} finally {
|
||||
if (true) {
|
||||
}
|
||||
`, [3, 10]);
|
||||
|
||||
// Object literal.
|
||||
test(`
|
||||
var x = {
|
||||
foo: {
|
||||
};
|
||||
`, [2, 8]);
|
||||
|
||||
// Array literal.
|
||||
test(`
|
||||
var x = [
|
||||
[
|
||||
];
|
||||
`, [2, 8], "[", "]");
|
|
@ -218,6 +218,7 @@ MSG_DEF(JSMSG_BAD_SUPERCALL, 0, JSEXN_SYNTAXERR, "super() is only vali
|
|||
MSG_DEF(JSMSG_BRACKET_AFTER_ARRAY_COMPREHENSION, 0, JSEXN_SYNTAXERR, "missing ] after array comprehension")
|
||||
MSG_DEF(JSMSG_BRACKET_AFTER_LIST, 0, JSEXN_SYNTAXERR, "missing ] after element list")
|
||||
MSG_DEF(JSMSG_BRACKET_IN_INDEX, 0, JSEXN_SYNTAXERR, "missing ] in index expression")
|
||||
MSG_DEF(JSMSG_BRACKET_OPENED, 2, JSEXN_NOTE, "[ opened at line {0}, column {1}")
|
||||
MSG_DEF(JSMSG_CATCH_AFTER_GENERAL, 0, JSEXN_SYNTAXERR, "catch after unconditional catch")
|
||||
MSG_DEF(JSMSG_CATCH_IDENTIFIER, 0, JSEXN_SYNTAXERR, "missing identifier in catch")
|
||||
MSG_DEF(JSMSG_CATCH_OR_FINALLY, 0, JSEXN_SYNTAXERR, "missing catch or finally after try")
|
||||
|
@ -228,6 +229,7 @@ MSG_DEF(JSMSG_COLON_IN_COND, 0, JSEXN_SYNTAXERR, "missing : in conditi
|
|||
MSG_DEF(JSMSG_COMP_PROP_UNTERM_EXPR, 0, JSEXN_SYNTAXERR, "missing ] in computed property name")
|
||||
MSG_DEF(JSMSG_CONTRARY_NONDIRECTIVE, 1, JSEXN_SYNTAXERR, "'{0}' statement won't be enforced as a directive because it isn't in directive prologue position")
|
||||
MSG_DEF(JSMSG_CURLY_AFTER_BODY, 0, JSEXN_SYNTAXERR, "missing } after function body")
|
||||
MSG_DEF(JSMSG_CURLY_OPENED, 2, JSEXN_NOTE, "{ opened at line {0}, column {1}")
|
||||
MSG_DEF(JSMSG_CURLY_AFTER_CATCH, 0, JSEXN_SYNTAXERR, "missing } after catch block")
|
||||
MSG_DEF(JSMSG_CURLY_AFTER_FINALLY, 0, JSEXN_SYNTAXERR, "missing } after finally block")
|
||||
MSG_DEF(JSMSG_CURLY_AFTER_LIST, 0, JSEXN_SYNTAXERR, "missing } after property list")
|
||||
|
|
|
@ -1013,7 +1013,20 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint)
|
|||
if (!out.append(")"))
|
||||
return nullptr;
|
||||
}
|
||||
} else if (fun->isInterpreted() && !fun->isSelfHostedBuiltin()) {
|
||||
} else if (fun->isInterpreted() &&
|
||||
(!fun->isSelfHostedBuiltin() ||
|
||||
fun->infallibleIsDefaultClassConstructor(cx)))
|
||||
{
|
||||
// Default class constructors should always haveSource except;
|
||||
//
|
||||
// 1. Source has been discarded for the whole compartment.
|
||||
//
|
||||
// 2. The source is marked as "lazy", i.e., retrieved on demand, and
|
||||
// the embedding has not provided a hook to retrieve sources.
|
||||
MOZ_ASSERT_IF(fun->infallibleIsDefaultClassConstructor(cx),
|
||||
!cx->runtime()->sourceHook ||
|
||||
!script->scriptSource()->sourceRetrievable() ||
|
||||
fun->compartment()->behaviors().discardSource());
|
||||
if (!AppendPrelude() ||
|
||||
!out.append("() {\n ") ||
|
||||
!out.append("[sourceless code]") ||
|
||||
|
@ -1022,10 +1035,6 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint)
|
|||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
// Default class constructors should always haveSource unless source
|
||||
// has been discarded for the whole compartment.
|
||||
MOZ_ASSERT(!fun->infallibleIsDefaultClassConstructor(cx) ||
|
||||
fun->compartment()->behaviors().discardSource());
|
||||
|
||||
if (!AppendPrelude() ||
|
||||
!out.append("() {\n "))
|
||||
|
|
Loading…
Reference in New Issue
Block a user