// Only allow non-global compiles for eval.
ASSERT(info->is_eval() || info->is_global());
-
- if (!ParserApi::Parse(info)) return Handle<SharedFunctionInfo>::null();
+ ParsingFlags flags = kNoParsingFlags;
+ if (info->pre_parse_data() != NULL ||
+ String::cast(script->source())->length() > FLAG_min_preparse_length) {
+ flags = kAllowLazy;
+ }
+ if (!ParserApi::Parse(info, flags)) {
+ return Handle<SharedFunctionInfo>::null();
+ }
// Measure how long it takes to do the compilation; only take the
// rest of the function into account to avoid overlap with the
int line_offset,
int column_offset,
v8::Extension* extension,
- ScriptDataImpl* input_pre_data,
+ ScriptDataImpl* pre_data,
Handle<Object> script_data,
NativesFlag natives) {
Isolate* isolate = source->GetIsolate();
// for small sources, odds are that there aren't many functions
// that would be compiled lazily anyway, so we skip the preparse step
// in that case too.
- ScriptDataImpl* pre_data = input_pre_data;
int flags = kNoParsingFlags;
if ((natives == NATIVES_CODE) || FLAG_allow_natives_syntax) {
flags |= kAllowNativesSyntax;
}
if (natives != NATIVES_CODE && FLAG_harmony_scoping) {
- flags |= kHarmonyScoping;
- }
- if (pre_data == NULL
- && source_length >= FLAG_min_preparse_length) {
- if (source->IsExternalTwoByteString()) {
- ExternalTwoByteStringUC16CharacterStream stream(
- Handle<ExternalTwoByteString>::cast(source), 0, source->length());
- pre_data = ParserApi::PartialPreParse(&stream, extension, flags);
- } else {
- GenericStringUC16CharacterStream stream(source, 0, source->length());
- pre_data = ParserApi::PartialPreParse(&stream, extension, flags);
- }
+ flags |= EXTENDED_MODE;
}
// Create a script object describing the script to be compiled.
if (extension == NULL && !result.is_null()) {
compilation_cache->PutScript(source, result);
}
-
- // Get rid of the pre-parsing data (if necessary).
- if (input_pre_data == NULL && pre_data != NULL) {
- delete pre_data;
- }
}
if (result.is_null()) isolate->ReportPendingMessages();
isolate->counters()->total_compile_size()->Increment(compiled_size);
// Generate the AST for the lazily compiled function.
- if (ParserApi::Parse(info)) {
+ if (ParserApi::Parse(info, kNoParsingFlags)) {
// Measure how long it takes to do the lazy compilation; only take the
// rest of the function into account to avoid overlap with the lazy
// parsing statistics.
// Parse and allocate variables.
CompilationInfo target_info(target);
- if (!ParserApi::Parse(&target_info) ||
+ if (!ParserApi::Parse(&target_info, kNoParsingFlags) ||
!Scope::Analyze(&target_info)) {
if (target_info.isolate()->has_pending_exception()) {
// Parse or scope error, never optimize this function.
// Build AST.
CompilationInfo info(script);
info.MarkAsGlobal();
- if (ParserApi::Parse(&info)) {
+ // Parse and don't allow skipping lazy functions.
+ if (ParserApi::Parse(&info, kNoParsingFlags)) {
// Compile the code.
LiveEditFunctionTracker tracker(info.isolate(), info.function());
if (Compiler::MakeCodeForLiveEdit(&info)) {
// Implementation of Parser
Parser::Parser(Handle<Script> script,
- bool allow_natives_syntax,
+ int parser_flags,
v8::Extension* extension,
ScriptDataImpl* pre_data)
: isolate_(script->GetIsolate()),
symbol_cache_(pre_data ? pre_data->symbol_count() : 0),
script_(script),
scanner_(isolate_->unicode_cache()),
+ reusable_preparser_(NULL),
top_scope_(NULL),
current_function_state_(NULL),
target_stack_(NULL),
extension_(extension),
pre_data_(pre_data),
fni_(NULL),
- allow_natives_syntax_(allow_natives_syntax),
+ allow_natives_syntax_((parser_flags & kAllowNativesSyntax) != 0),
+ allow_lazy_((parser_flags & kAllowLazy) != 0),
stack_overflow_(false),
parenthesized_function_(false) {
- scanner().SetHarmonyScoping(FLAG_harmony_scoping);
AstNode::ResetIds();
+ if ((parser_flags & kLanguageModeMask) == EXTENDED_MODE) {
+ scanner().SetHarmonyScoping(true);
+ }
}
if (pre_data_ != NULL) pre_data_->Initialize();
// Compute the parsing mode.
- mode_ = FLAG_lazy ? PARSE_LAZILY : PARSE_EAGERLY;
+ mode_ = (FLAG_lazy && allow_lazy_) ? PARSE_LAZILY : PARSE_EAGERLY;
if (allow_natives_syntax_ || extension_ != NULL) mode_ = PARSE_EAGERLY;
Handle<String> no_name = isolate()->factory()->empty_symbol();
}
+class SingletonLogger : public ParserRecorder {
+ public:
+ SingletonLogger() : has_error_(false), start_(-1), end_(-1) { }
+ ~SingletonLogger() { }
+
+ void Reset() { has_error_ = false; }
+
+ virtual void LogFunction(int start,
+ int end,
+ int literals,
+ int properties,
+ LanguageMode mode) {
+ ASSERT(!has_error_);
+ start_ = start;
+ end_ = end;
+ literals_ = literals;
+ properties_ = properties;
+ mode_ = mode;
+ };
+
+ // Logs a symbol creation of a literal or identifier.
+ virtual void LogAsciiSymbol(int start, Vector<const char> literal) { }
+ virtual void LogUC16Symbol(int start, Vector<const uc16> literal) { }
+
+ // Logs an error message and marks the log as containing an error.
+ // Further logging will be ignored, and ExtractData will return a vector
+ // representing the error only.
+ virtual void LogMessage(int start,
+ int end,
+ const char* message,
+ const char* argument_opt) {
+ has_error_ = true;
+ start_ = start;
+ end_ = end;
+ message_ = message;
+ argument_opt_ = argument_opt;
+ }
+
+ virtual int function_position() { return 0; }
+
+ virtual int symbol_position() { return 0; }
+
+ virtual int symbol_ids() { return -1; }
+
+ virtual Vector<unsigned> ExtractData() {
+ UNREACHABLE();
+ return Vector<unsigned>();
+ }
+
+ virtual void PauseRecording() { }
+
+ virtual void ResumeRecording() { }
+
+ bool has_error() { return has_error_; }
+
+ int start() { return start_; }
+ int end() { return end_; }
+ int literals() {
+ ASSERT(!has_error_);
+ return literals_;
+ }
+ int properties() {
+ ASSERT(!has_error_);
+ return properties_;
+ }
+ LanguageMode language_mode() {
+ ASSERT(!has_error_);
+ return mode_;
+ }
+ const char* message() {
+ ASSERT(has_error_);
+ return message_;
+ }
+ const char* argument_opt() {
+ ASSERT(has_error_);
+ return argument_opt_;
+ }
+
+ private:
+ bool has_error_;
+ int start_;
+ int end_;
+ // For function entries.
+ int literals_;
+ int properties_;
+ LanguageMode mode_;
+ // For error messages.
+ const char* message_;
+ const char* argument_opt_;
+};
+
+
FunctionLiteral* Parser::ParseFunctionLiteral(Handle<String> function_name,
bool name_is_strict_reserved,
int function_token_position,
? NewScope(top_scope_->DeclarationScope(), FUNCTION_SCOPE)
: NewScope(top_scope_, FUNCTION_SCOPE);
ZoneList<Statement*>* body = NULL;
- int materialized_literal_count;
- int expected_property_count;
+ int materialized_literal_count = -1;
+ int expected_property_count = -1;
int handler_count = 0;
bool only_simple_this_property_assignments;
Handle<FixedArray> this_property_assignments;
fvar = top_scope_->DeclareFunctionVar(function_name, fvar_mode);
}
- // Determine if the function will be lazily compiled. The mode can only
- // be PARSE_LAZILY if the --lazy flag is true. We will not lazily
- // compile if we do not have preparser data for the function.
+ // Determine whether the function will be lazily compiled.
+ // The heuristics are:
+ // - It must not have been prohibited by the caller to Parse (some callers
+ // need a full AST).
+ // - The outer scope must be trivial (only global variables in scope).
+ // - The function mustn't be a function expression with an open parenthesis
+ // before; we consider that a hint that the function will be called
+ // immediately, and it would be a waste of time to make it lazily
+ // compiled.
+ // These are all things we can know at this point, without looking at the
+ // function itself.
bool is_lazily_compiled = (mode() == PARSE_LAZILY &&
top_scope_->outer_scope()->is_global_scope() &&
top_scope_->HasTrivialOuterContext() &&
- !parenthesized_function_ &&
- pre_data() != NULL);
+ !parenthesized_function_);
parenthesized_function_ = false; // The bit was set for this function only.
if (is_lazily_compiled) {
int function_block_pos = scanner().location().beg_pos;
- FunctionEntry entry = pre_data()->GetFunctionEntry(function_block_pos);
- if (!entry.is_valid()) {
- // There is no preparser data for the function, we will not lazily
- // compile after all.
- is_lazily_compiled = false;
+ FunctionEntry entry;
+ if (pre_data_ != NULL) {
+ // If we have pre_data_, we use it to skip parsing the function body.
+ // the preparser data contains the information we need to construct the
+ // lazy function.
+ entry = pre_data()->GetFunctionEntry(function_block_pos);
+ if (entry.is_valid()) {
+ if (entry.end_pos() <= function_block_pos) {
+ // End position greater than end of stream is safe, and hard
+ // to check.
+ ReportInvalidPreparseData(function_name, CHECK_OK);
+ }
+ scanner().SeekForward(entry.end_pos() - 1);
+
+ scope->set_end_position(entry.end_pos());
+ Expect(Token::RBRACE, CHECK_OK);
+ isolate()->counters()->total_preparse_skipped()->Increment(
+ scope->end_position() - function_block_pos);
+ materialized_literal_count = entry.literal_count();
+ expected_property_count = entry.property_count();
+ top_scope_->SetLanguageMode(entry.language_mode());
+ only_simple_this_property_assignments = false;
+ this_property_assignments = isolate()->factory()->empty_fixed_array();
+ } else {
+ is_lazily_compiled = false;
+ }
} else {
- scope->set_end_position(entry.end_pos());
- if (scope->end_position() <= function_block_pos) {
- // End position greater than end of stream is safe, and hard to check.
- ReportInvalidPreparseData(function_name, CHECK_OK);
+ // With no preparser data, we partially parse the function, without
+ // building an AST. This gathers the data needed to build a lazy
+ // function.
+ SingletonLogger logger;
+ preparser::PreParser::PreParseResult result =
+ LazyParseFunctionLiteral(&logger);
+ if (result == preparser::PreParser::kPreParseStackOverflow) {
+ // Propagate stack overflow.
+ stack_overflow_ = true;
+ *ok = false;
+ return NULL;
+ }
+ if (logger.has_error()) {
+ const char* arg = logger.argument_opt();
+ Vector<const char*> args;
+ if (arg != NULL) {
+ args = Vector<const char*>(&arg, 1);
+ }
+ ReportMessageAt(Scanner::Location(logger.start(), logger.end()),
+ logger.message(), args);
+ *ok = false;
+ return NULL;
}
+ scope->set_end_position(logger.end());
+ Expect(Token::RBRACE, CHECK_OK);
isolate()->counters()->total_preparse_skipped()->Increment(
scope->end_position() - function_block_pos);
- // Seek to position just before terminal '}'.
- scanner().SeekForward(scope->end_position() - 1);
- materialized_literal_count = entry.literal_count();
- expected_property_count = entry.property_count();
- top_scope_->SetLanguageMode(entry.language_mode());
+ materialized_literal_count = logger.literals();
+ expected_property_count = logger.properties();
+ top_scope_->SetLanguageMode(logger.language_mode());
only_simple_this_property_assignments = false;
this_property_assignments = isolate()->factory()->empty_fixed_array();
- Expect(Token::RBRACE, CHECK_OK);
}
}
}
+preparser::PreParser::PreParseResult Parser::LazyParseFunctionLiteral(
+ SingletonLogger* logger) {
+ HistogramTimerScope preparse_scope(isolate()->counters()->pre_parse());
+ ASSERT_EQ(Token::LBRACE, scanner().current_token());
+
+ if (reusable_preparser_ == NULL) {
+ intptr_t stack_limit = isolate()->stack_guard()->real_climit();
+ bool do_allow_lazy = true;
+ reusable_preparser_ = new preparser::PreParser(&scanner_,
+ NULL,
+ stack_limit,
+ do_allow_lazy,
+ allow_natives_syntax_);
+ }
+ preparser::PreParser::PreParseResult result =
+ reusable_preparser_->PreParseLazyFunction(top_scope_->language_mode(),
+ logger);
+ return result;
+}
+
+
Expression* Parser::ParseV8Intrinsic(bool* ok) {
// CallRuntime ::
// '%' Identifier Arguments
scanner.SetHarmonyScoping(FLAG_harmony_scoping);
scanner.Initialize(source);
intptr_t stack_limit = isolate->stack_guard()->real_climit();
- if (!preparser::PreParser::PreParseProgram(&scanner,
- recorder,
- flags,
- stack_limit)) {
+ preparser::PreParser::PreParseResult result =
+ preparser::PreParser::PreParseProgram(&scanner,
+ recorder,
+ flags,
+ stack_limit);
+ if (result == preparser::PreParser::kPreParseStackOverflow) {
isolate->StackOverflow();
return NULL;
}
}
-bool ParserApi::Parse(CompilationInfo* info) {
+bool ParserApi::Parse(CompilationInfo* info, int parsing_flags) {
ASSERT(info->function() == NULL);
FunctionLiteral* result = NULL;
Handle<Script> script = info->script();
+ ASSERT((parsing_flags & kLanguageModeMask) == CLASSIC_MODE);
+ if (!info->is_native() && FLAG_harmony_scoping) {
+ // Harmony scoping is requested.
+ parsing_flags |= EXTENDED_MODE;
+ }
+ if (FLAG_allow_natives_syntax || info->is_native()) {
+ // We requre %identifier(..) syntax.
+ parsing_flags |= kAllowNativesSyntax;
+ }
if (info->is_lazy()) {
ASSERT(!info->is_eval());
- bool allow_natives_syntax =
- FLAG_allow_natives_syntax ||
- info->is_native();
- Parser parser(script, allow_natives_syntax, NULL, NULL);
+ Parser parser(script, parsing_flags, NULL, NULL);
result = parser.ParseLazy(info);
} else {
- // Whether we allow %identifier(..) syntax.
- bool allow_natives_syntax =
- info->is_native() || FLAG_allow_natives_syntax;
ScriptDataImpl* pre_data = info->pre_parse_data();
- Parser parser(script,
- allow_natives_syntax,
- info->extension(),
- pre_data);
+ Parser parser(script, parsing_flags, info->extension(), pre_data);
if (pre_data != NULL && pre_data->has_error()) {
Scanner::Location loc = pre_data->MessageLocation();
const char* message = pre_data->BuildMessage();
kSize
};
- explicit FunctionEntry(Vector<unsigned> backing) : backing_(backing) { }
- FunctionEntry() { }
+ explicit FunctionEntry(Vector<unsigned> backing)
+ : backing_(backing) { }
+
+ FunctionEntry() : backing_() { }
int start_pos() { return backing_[kStartPositionIndex]; }
int end_pos() { return backing_[kEndPositionIndex]; }
private:
Vector<unsigned> backing_;
+ bool owns_data_;
};
// Parses the source code represented by the compilation info and sets its
// function literal. Returns false (and deallocates any allocated AST
// nodes) if parsing failed.
- static bool Parse(CompilationInfo* info);
+ static bool Parse(CompilationInfo* info, int flags);
// Generic preparser generating full preparse data.
static ScriptDataImpl* PreParse(UC16CharacterStream* source,
// ----------------------------------------------------------------------------
// JAVASCRIPT PARSING
+// Forward declaration.
+class SingletonLogger;
+
class Parser {
public:
Parser(Handle<Script> script,
- bool allow_natives_syntax,
+ int parsing_flags, // Combination of ParsingFlags
v8::Extension* extension,
ScriptDataImpl* pre_data);
- virtual ~Parser() { }
+ virtual ~Parser() {
+ if (reusable_preparser_ != NULL) {
+ delete reusable_preparser_;
+ }
+ }
// Returns NULL if parsing failed.
FunctionLiteral* ParseProgram(CompilationInfo* info);
Handle<String> type,
Vector< Handle<Object> > arguments);
+ preparser::PreParser::PreParseResult LazyParseFunctionLiteral(
+ SingletonLogger* logger);
+
Isolate* isolate_;
ZoneList<Handle<String> > symbol_cache_;
Handle<Script> script_;
Scanner scanner_;
-
+ preparser::PreParser* reusable_preparser_;
Scope* top_scope_;
FunctionState* current_function_state_;
Target* target_stack_; // for break, continue statements
Mode mode_;
bool allow_natives_syntax_;
+ bool allow_lazy_;
bool stack_overflow_;
// If true, the next (and immediately following) function literal is
// preceded by a parenthesis.
namespace preparser {
+PreParser::PreParseResult PreParser::PreParseLazyFunction(
+ i::LanguageMode mode, i::ParserRecorder* log) {
+ log_ = log;
+ // Lazy functions always have trivial outer scopes (no with/catch scopes).
+ Scope top_scope(&scope_, kTopLevelScope);
+ set_language_mode(mode);
+ Scope function_scope(&scope_, kFunctionScope);
+ ASSERT_EQ(i::Token::LBRACE, scanner_->current_token());
+ bool ok = true;
+ int start_position = scanner_->peek_location().beg_pos;
+ ParseLazyFunctionLiteralBody(&ok);
+ if (stack_overflow_) return kPreParseStackOverflow;
+ if (!ok) {
+ ReportUnexpectedToken(scanner_->current_token());
+ } else {
+ ASSERT_EQ(i::Token::RBRACE, scanner_->peek());
+ if (!is_classic_mode()) {
+ int end_pos = scanner_->location().end_pos;
+ CheckOctalLiteral(start_position, end_pos, &ok);
+ if (ok) {
+ CheckDelayedStrictModeViolation(start_position, end_pos, &ok);
+ }
+ }
+ }
+ return kPreParseSuccess;
+}
+
+
// Preparsing checks a JavaScript program and emits preparse-data that helps
// a later parsing to be faster.
// See preparser-data.h for the data.
}
Expect(i::Token::RPAREN, CHECK_OK);
- Expect(i::Token::LBRACE, CHECK_OK);
- int function_block_pos = scanner_->location().beg_pos;
-
// Determine if the function will be lazily compiled.
// Currently only happens to top-level functions.
// Optimistically assume that all top-level functions are lazily compiled.
!parenthesized_function_);
parenthesized_function_ = false;
+ Expect(i::Token::LBRACE, CHECK_OK);
if (is_lazily_compiled) {
- log_->PauseRecording();
- ParseSourceElements(i::Token::RBRACE, ok);
- log_->ResumeRecording();
- if (!*ok) Expression::Default();
-
- Expect(i::Token::RBRACE, CHECK_OK);
-
- // Position right after terminal '}'.
- int end_pos = scanner_->location().end_pos;
- log_->LogFunction(function_block_pos, end_pos,
- function_scope.materialized_literal_count(),
- function_scope.expected_properties(),
- language_mode());
+ ParseLazyFunctionLiteralBody(CHECK_OK);
} else {
- ParseSourceElements(i::Token::RBRACE, CHECK_OK);
- Expect(i::Token::RBRACE, CHECK_OK);
+ ParseSourceElements(i::Token::RBRACE, ok);
}
+ Expect(i::Token::RBRACE, CHECK_OK);
if (!is_classic_mode()) {
int end_position = scanner_->location().end_pos;
}
+void PreParser::ParseLazyFunctionLiteralBody(bool* ok) {
+ int body_start = scanner_->location().beg_pos;
+ log_->PauseRecording();
+ ParseSourceElements(i::Token::RBRACE, ok);
+ log_->ResumeRecording();
+ if (!*ok) return;
+
+ // Position right after terminal '}'.
+ ASSERT_EQ(i::Token::RBRACE, scanner_->peek());
+ int body_end = scanner_->peek_location().end_pos;
+ log_->LogFunction(body_start, body_end,
+ scope_->materialized_literal_count(),
+ scope_->expected_properties(),
+ language_mode());
+}
+
+
PreParser::Expression PreParser::ParseV8Intrinsic(bool* ok) {
// CallRuntime ::
// '%' Identifier Arguments
kPreParseSuccess
};
+
+ PreParser(i::Scanner* scanner,
+ i::ParserRecorder* log,
+ uintptr_t stack_limit,
+ bool allow_lazy,
+ bool allow_natives_syntax)
+ : scanner_(scanner),
+ log_(log),
+ scope_(NULL),
+ stack_limit_(stack_limit),
+ strict_mode_violation_location_(i::Scanner::Location::invalid()),
+ strict_mode_violation_type_(NULL),
+ stack_overflow_(false),
+ allow_lazy_(allow_lazy),
+ allow_natives_syntax_(allow_natives_syntax),
+ parenthesized_function_(false),
+ harmony_scoping_(scanner->HarmonyScoping()) { }
+
~PreParser() {}
// Pre-parse the program from the character stream; returns true on
allow_lazy, allow_natives_syntax).PreParse();
}
+ // Parses a single function literal, from the opening parentheses before
+ // parameters to the closing brace after the body.
+ // Returns a FunctionEntry describing the body of the funciton in enough
+ // detail that it can be lazily compiled.
+ // The scanner is expected to have matched the "function" keyword and
+ // parameters, and have consumed the initial '{'.
+ // At return, unless an error occured, the scanner is positioned before the
+ // the final '}'.
+ PreParseResult PreParseLazyFunction(i::LanguageMode mode,
+ i::ParserRecorder* log);
+
private:
// Used to detect duplicates in object literals. Each of the values
// kGetterProperty, kSetterProperty and kValueProperty represents
i::LanguageMode language_mode_;
};
- // Private constructor only used in PreParseProgram.
- PreParser(i::Scanner* scanner,
- i::ParserRecorder* log,
- uintptr_t stack_limit,
- bool allow_lazy,
- bool allow_natives_syntax)
- : scanner_(scanner),
- log_(log),
- scope_(NULL),
- stack_limit_(stack_limit),
- strict_mode_violation_location_(i::Scanner::Location::invalid()),
- strict_mode_violation_type_(NULL),
- stack_overflow_(false),
- allow_lazy_(allow_lazy),
- allow_natives_syntax_(allow_natives_syntax),
- parenthesized_function_(false),
- harmony_scoping_(scanner->HarmonyScoping()) { }
-
// Preparse the program. Only called in PreParseProgram after creating
// the instance.
PreParseResult PreParse() {
Arguments ParseArguments(bool* ok);
Expression ParseFunctionLiteral(bool* ok);
+ void ParseLazyFunctionLiteralBody(bool* ok);
Identifier ParseIdentifier(bool* ok);
Identifier ParseIdentifierName(bool* ok);
namespace internal {
-// General collection of bit-flags that can be passed to scanners and
+// General collection of (multi-)bit-flags that can be passed to scanners and
// parsers to signify their (initial) mode of operation.
enum ParsingFlags {
kNoParsingFlags = 0,
- kAllowLazy = 1,
- kAllowNativesSyntax = 2,
- kHarmonyScoping = 4
+ // Embed LanguageMode values in parsing flags, i.e., equivalent to:
+ // CLASSIC_MODE = 0,
+ // STRICT_MODE,
+ // EXTENDED_MODE,
+ kLanguageModeMask = 0x03,
+ kAllowLazy = 4,
+ kAllowNativesSyntax = 8
};
+STATIC_ASSERT((kLanguageModeMask & CLASSIC_MODE) == CLASSIC_MODE);
+STATIC_ASSERT((kLanguageModeMask & STRICT_MODE) == STRICT_MODE);
+STATIC_ASSERT((kLanguageModeMask & EXTENDED_MODE) == EXTENDED_MODE);
+
// Returns the value (0 .. 15) of a hexadecimal character c.
// If c is not a legal hexadecimal character, returns a value < 0.
i::Handle<i::String> source(
FACTORY->NewStringFromAscii(i::CStrVector(program.start())));
i::Handle<i::Script> script = FACTORY->NewScript(source);
- i::Parser parser(script, false, NULL, NULL);
+ i::Parser parser(script, i::kAllowLazy | i::EXTENDED_MODE, NULL, NULL);
i::CompilationInfo info(script);
info.MarkAsGlobal();
info.SetLanguageMode(source_data[i].language_mode);
// Test that the illegal continue is thrown at parse time.
try {
- function Crash() { continue;if (Crash) {
- } }
+ eval("function Crash() { assertUnreachable(); continue;if (Crash) { } }");
Crash();
- assertTrue(false);
+ assertUnreachable();
} catch (e) {
assertTrue(e instanceof SyntaxError);
assertTrue(/continue/.test(e.message));