Log Message
lexical scoping is broken with respect to "break" and "continue" https://bugs.webkit.org/show_bug.cgi?id=147063
Reviewed by Filip Pizlo. Bug #142944 which introduced "let" and lexical scoping didn't properly hook into the bytecode generator's machinery for calculating scope depth deltas for "break" and "continue". This resulted in the bytecode generator popping an incorrect number of scopes when lexical scopes were involved. This patch fixes this problem and generalizes this machinery a bit. This patch also renames old functions in a sensible way that is more coherent in a world with lexical scoping. * bytecompiler/BytecodeGenerator.cpp: (JSC::BytecodeGenerator::BytecodeGenerator): (JSC::BytecodeGenerator::newLabelScope): (JSC::BytecodeGenerator::emitProfileType): (JSC::BytecodeGenerator::pushLexicalScope): (JSC::BytecodeGenerator::popLexicalScope): (JSC::BytecodeGenerator::prepareLexicalScopeForNextForLoopIteration): (JSC::BytecodeGenerator::resolveType): (JSC::BytecodeGenerator::emitResolveScope): (JSC::BytecodeGenerator::emitGetFromScope): (JSC::BytecodeGenerator::emitPutToScope): (JSC::BytecodeGenerator::emitPushWithScope): (JSC::BytecodeGenerator::emitGetParentScope): (JSC::BytecodeGenerator::emitPopScope): (JSC::BytecodeGenerator::emitPopWithOrCatchScope): (JSC::BytecodeGenerator::emitPopScopes): (JSC::BytecodeGenerator::calculateTargetScopeDepthForExceptionHandler): (JSC::BytecodeGenerator::localScopeDepth): (JSC::BytecodeGenerator::labelScopeDepth): (JSC::BytecodeGenerator::emitThrowReferenceError): (JSC::BytecodeGenerator::emitPushFunctionNameScope): (JSC::BytecodeGenerator::pushScopedControlFlowContext): (JSC::BytecodeGenerator::popScopedControlFlowContext): (JSC::BytecodeGenerator::emitPushCatchScope): (JSC::BytecodeGenerator::currentScopeDepth): Deleted. * bytecompiler/BytecodeGenerator.h: (JSC::BytecodeGenerator::hasFinaliser): (JSC::BytecodeGenerator::scopeDepth): Deleted. * bytecompiler/NodesCodegen.cpp: (JSC::ContinueNode::trivialTarget): (JSC::BreakNode::trivialTarget): (JSC::ReturnNode::emitBytecode): (JSC::WithNode::emitBytecode): (JSC::TryNode::emitBytecode): * tests/stress/lexical-scoping-break-continue.js: Added. (assert): (.):
Modified Paths
- trunk/Source/_javascript_Core/ChangeLog
- trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp
- trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.h
- trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp
Added Paths
Diff
Modified: trunk/Source/_javascript_Core/ChangeLog (186995 => 186996)
--- trunk/Source/_javascript_Core/ChangeLog 2015-07-18 19:48:59 UTC (rev 186995)
+++ trunk/Source/_javascript_Core/ChangeLog 2015-07-18 20:12:14 UTC (rev 186996)
@@ -1,3 +1,58 @@
+2015-07-18 Saam barati <[email protected]>
+
+ lexical scoping is broken with respect to "break" and "continue"
+ https://bugs.webkit.org/show_bug.cgi?id=147063
+
+ Reviewed by Filip Pizlo.
+
+ Bug #142944 which introduced "let" and lexical scoping
+ didn't properly hook into the bytecode generator's machinery
+ for calculating scope depth deltas for "break" and "continue". This
+ resulted in the bytecode generator popping an incorrect number
+ of scopes when lexical scopes were involved.
+
+ This patch fixes this problem and generalizes this machinery a bit.
+ This patch also renames old functions in a sensible way that is more
+ coherent in a world with lexical scoping.
+
+ * bytecompiler/BytecodeGenerator.cpp:
+ (JSC::BytecodeGenerator::BytecodeGenerator):
+ (JSC::BytecodeGenerator::newLabelScope):
+ (JSC::BytecodeGenerator::emitProfileType):
+ (JSC::BytecodeGenerator::pushLexicalScope):
+ (JSC::BytecodeGenerator::popLexicalScope):
+ (JSC::BytecodeGenerator::prepareLexicalScopeForNextForLoopIteration):
+ (JSC::BytecodeGenerator::resolveType):
+ (JSC::BytecodeGenerator::emitResolveScope):
+ (JSC::BytecodeGenerator::emitGetFromScope):
+ (JSC::BytecodeGenerator::emitPutToScope):
+ (JSC::BytecodeGenerator::emitPushWithScope):
+ (JSC::BytecodeGenerator::emitGetParentScope):
+ (JSC::BytecodeGenerator::emitPopScope):
+ (JSC::BytecodeGenerator::emitPopWithOrCatchScope):
+ (JSC::BytecodeGenerator::emitPopScopes):
+ (JSC::BytecodeGenerator::calculateTargetScopeDepthForExceptionHandler):
+ (JSC::BytecodeGenerator::localScopeDepth):
+ (JSC::BytecodeGenerator::labelScopeDepth):
+ (JSC::BytecodeGenerator::emitThrowReferenceError):
+ (JSC::BytecodeGenerator::emitPushFunctionNameScope):
+ (JSC::BytecodeGenerator::pushScopedControlFlowContext):
+ (JSC::BytecodeGenerator::popScopedControlFlowContext):
+ (JSC::BytecodeGenerator::emitPushCatchScope):
+ (JSC::BytecodeGenerator::currentScopeDepth): Deleted.
+ * bytecompiler/BytecodeGenerator.h:
+ (JSC::BytecodeGenerator::hasFinaliser):
+ (JSC::BytecodeGenerator::scopeDepth): Deleted.
+ * bytecompiler/NodesCodegen.cpp:
+ (JSC::ContinueNode::trivialTarget):
+ (JSC::BreakNode::trivialTarget):
+ (JSC::ReturnNode::emitBytecode):
+ (JSC::WithNode::emitBytecode):
+ (JSC::TryNode::emitBytecode):
+ * tests/stress/lexical-scoping-break-continue.js: Added.
+ (assert):
+ (.):
+
2015-07-17 Filip Pizlo <[email protected]>
DFG should have some obvious mitigations against watching structures that are unprofitable to watch
Modified: trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp (186995 => 186996)
--- trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp 2015-07-18 19:48:59 UTC (rev 186995)
+++ trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp 2015-07-18 20:12:14 UTC (rev 186996)
@@ -522,6 +522,8 @@
instructions().append(0);
}
+ if (m_lexicalEnvironmentRegister)
+ pushScopedControlFlowContext();
m_symbolTableStack.append(SymbolTableStackEntry{ Strong<SymbolTable>(*m_vm, m_symbolTable), m_lexicalEnvironmentRegister, false, symbolTableConstantIndex });
m_TDZStack.append(std::make_pair(*parentScopeTDZVariables, false));
}
@@ -627,7 +629,7 @@
m_labelScopes.removeLast();
// Allocate new label scope.
- LabelScope scope(type, name, scopeDepth(), newLabel(), type == LabelScope::Loop ? newLabel() : PassRefPtr<Label>()); // Only loops have continue targets.
+ LabelScope scope(type, name, labelScopeDepth(), newLabel(), type == LabelScope::Loop ? newLabel() : PassRefPtr<Label>()); // Only loops have continue targets.
m_labelScopes.append(scope);
return LabelScopePtr(m_labelScopes, m_labelScopes.size() - 1);
}
@@ -1210,7 +1212,7 @@
// The format of this instruction is: op_profile_type regToProfile, TypeLocation*, flag, identifier?, resolveType?
emitOpcode(op_profile_type);
instructions().append(registerToProfile->index());
- instructions().append(currentScopeDepth());
+ instructions().append(localScopeDepth());
instructions().append(flag);
instructions().append(identifier ? addConstant(*identifier) : 0);
instructions().append(resolveType());
@@ -1318,6 +1320,8 @@
instructions().append(addConstantValue(jsTDZValue())->index());
emitMove(scopeRegister(), newScope);
+
+ pushScopedControlFlowContext();
}
m_symbolTableStack.append(SymbolTableStackEntry{ symbolTable, newScope, false, symbolTableConstantIndex });
@@ -1365,8 +1369,8 @@
if (hasCapturedVariables) {
RELEASE_ASSERT(stackEntry.m_scope);
- RefPtr<RegisterID> parentScope = emitGetParentScope(newTemporary(), scopeRegister());
- emitMove(scopeRegister(), parentScope.get());
+ emitPopScope(scopeRegister(), stackEntry.m_scope);
+ popScopedControlFlowContext();
stackEntry.m_scope->deref();
}
@@ -1423,7 +1427,7 @@
// as the previous scope because the loop body is compiled under
// the assumption that the scope's register index is constant even
// though the value in that register will change on each loop iteration.
- RefPtr<RegisterID> parentScope = emitGetParentScope(newTemporary(), scopeRegister());
+ RefPtr<RegisterID> parentScope = emitGetParentScope(newTemporary(), loopScope);
emitMove(scopeRegister(), parentScope.get());
emitOpcode(op_create_lexical_environment);
@@ -1572,8 +1576,11 @@
// will start with this ResolveType and compute the least upper bound including intercepting scopes.
ResolveType BytecodeGenerator::resolveType()
{
- if (m_localScopeDepth)
- return Dynamic;
+ for (unsigned i = m_symbolTableStack.size(); i--; ) {
+ if (m_symbolTableStack[i].m_isWithOrCatch)
+ return Dynamic;
+ }
+
if (m_symbolTable && m_symbolTable->usesNonStrictEval())
return GlobalPropertyWithVarInjectionChecks;
return GlobalProperty;
@@ -1632,7 +1639,7 @@
instructions().append(scopeRegister()->index());
instructions().append(addConstant(variable.ident()));
instructions().append(resolveType());
- instructions().append(currentScopeDepth());
+ instructions().append(localScopeDepth());
instructions().append(0);
return dst;
}
@@ -1666,7 +1673,7 @@
instructions().append(scope->index());
instructions().append(addConstant(variable.ident()));
instructions().append(ResolveModeAndType(resolveMode, variable.offset().isScope() ? LocalClosureVar : resolveType()).operand());
- instructions().append(currentScopeDepth());
+ instructions().append(localScopeDepth());
instructions().append(variable.offset().isScope() ? variable.offset().scopeOffset().offset() : 0);
instructions().append(profile);
return dst;
@@ -1706,7 +1713,7 @@
} else {
ASSERT(resolveType() != LocalClosureVar);
instructions().append(ResolveModeAndType(resolveMode, resolveType()).operand());
- instructions().append(currentScopeDepth());
+ instructions().append(localScopeDepth());
}
instructions().append(!!offset ? offset.offset() : 0);
return value;
@@ -2474,10 +2481,7 @@
RegisterID* BytecodeGenerator::emitPushWithScope(RegisterID* dst, RegisterID* scope)
{
- ControlFlowContext context;
- context.isFinallyBlock = false;
- m_scopeContextStack.append(context);
- m_localScopeDepth++;
+ pushScopedControlFlowContext();
RegisterID* result = emitUnaryOp(op_push_with_scope, dst, scope);
m_symbolTableStack.append(SymbolTableStackEntry{ Strong<SymbolTable>(), nullptr, true, 0 });
@@ -2492,16 +2496,16 @@
return dst;
}
-void BytecodeGenerator::emitPopScope(RegisterID* srcDst)
+void BytecodeGenerator::emitPopScope(RegisterID* dst, RegisterID* scope)
{
- ASSERT(m_scopeContextStack.size());
- ASSERT(!m_scopeContextStack.last().isFinallyBlock);
+ RefPtr<RegisterID> parentScope = emitGetParentScope(newTemporary(), scope);
+ emitMove(dst, parentScope.get());
+}
- RefPtr<RegisterID> parentScope = emitGetParentScope(newTemporary(), srcDst);
- emitMove(srcDst, parentScope.get());
-
- m_scopeContextStack.removeLast();
- m_localScopeDepth--;
+void BytecodeGenerator::emitPopWithOrCatchScope(RegisterID* srcDst)
+{
+ emitPopScope(srcDst, srcDst);
+ popScopedControlFlowContext();
SymbolTableStackEntry stackEntry = m_symbolTableStack.takeLast();
RELEASE_ASSERT(stackEntry.m_isWithOrCatch);
}
@@ -2815,9 +2819,9 @@
void BytecodeGenerator::emitPopScopes(RegisterID* scope, int targetScopeDepth)
{
- ASSERT(scopeDepth() - targetScopeDepth >= 0);
+ ASSERT(labelScopeDepth() - targetScopeDepth >= 0);
- size_t scopeDelta = scopeDepth() - targetScopeDepth;
+ size_t scopeDelta = labelScopeDepth() - targetScopeDepth;
ASSERT(scopeDelta <= m_scopeContextStack.size());
if (!scopeDelta)
return;
@@ -2876,16 +2880,10 @@
int BytecodeGenerator::calculateTargetScopeDepthForExceptionHandler() const
{
- int depth = m_localScopeDepth;
+ int depth = localScopeDepth();
- for (unsigned i = m_symbolTableStack.size(); i--; ) {
- RegisterID* scope = m_symbolTableStack[i].m_scope;
- if (scope)
- depth++;
- }
-
// Currently, we're maintaing compatibility with how things are done and letting the exception handling
- // code take into consideration the base activation of the function. There is no reason we shouldn't
+ // code take into consideration the base activation of the function. There is no reason we shouldn't
// be able to calculate the exact depth here and let the exception handler not worry if there is a base
// activation or not.
if (m_lexicalEnvironmentRegister)
@@ -2895,18 +2893,16 @@
return depth;
}
-int BytecodeGenerator::currentScopeDepth() const
+int BytecodeGenerator::localScopeDepth() const
{
- // This is the current number of JSScope descendents that would be allocated
- // in this function/program if this code were running.
- int depth = 0;
- for (unsigned i = m_symbolTableStack.size(); i--; ) {
- if (m_symbolTableStack[i].m_scope || m_symbolTableStack[i].m_isWithOrCatch)
- depth++;
- }
- return depth;
+ return m_localScopeDepth;
}
+int BytecodeGenerator::labelScopeDepth() const
+{
+ return localScopeDepth() + m_finallyDepth;
+}
+
void BytecodeGenerator::emitThrowReferenceError(const String& message)
{
emitOpcode(op_throw_static_error);
@@ -2930,13 +2926,26 @@
instructions().append(JSNameScope::FunctionNameScope);
}
-void BytecodeGenerator::emitPushCatchScope(RegisterID* dst, const Identifier& property, RegisterID* value, unsigned attributes)
+void BytecodeGenerator::pushScopedControlFlowContext()
{
ControlFlowContext context;
context.isFinallyBlock = false;
m_scopeContextStack.append(context);
m_localScopeDepth++;
+}
+void BytecodeGenerator::popScopedControlFlowContext()
+{
+ ASSERT(m_scopeContextStack.size());
+ ASSERT(!m_scopeContextStack.last().isFinallyBlock);
+ m_scopeContextStack.removeLast();
+ m_localScopeDepth--;
+}
+
+void BytecodeGenerator::emitPushCatchScope(RegisterID* dst, const Identifier& property, RegisterID* value, unsigned attributes)
+{
+ pushScopedControlFlowContext();
+
emitOpcode(op_push_name_scope);
instructions().append(dst->index());
instructions().append(value->index());
Modified: trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.h (186995 => 186996)
--- trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.h 2015-07-18 19:48:59 UTC (rev 186995)
+++ trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.h 2015-07-18 20:12:14 UTC (rev 186996)
@@ -585,12 +585,12 @@
void emitGetScope();
RegisterID* emitPushWithScope(RegisterID* dst, RegisterID* scope);
- void emitPopScope(RegisterID* srcDst);
+ void emitPopScope(RegisterID* dst, RegisterID* scope);
+ void emitPopWithOrCatchScope(RegisterID* srcDst);
RegisterID* emitGetParentScope(RegisterID* dst, RegisterID* scope);
void emitDebugHook(DebugHookID, unsigned line, unsigned charOffset, unsigned lineStart);
- int scopeDepth() { return m_localScopeDepth + m_finallyDepth; }
bool hasFinaliser() { return m_finallyDepth != 0; }
void pushFinallyContext(StatementNode* finallyBlock);
@@ -624,6 +624,7 @@
void pushLexicalScope(VariableEnvironmentNode*, bool canOptimizeTDZChecks, RegisterID** constantSymbolTableResult = nullptr);
void popLexicalScope(VariableEnvironmentNode*);
void prepareLexicalScopeForNextForLoopIteration(VariableEnvironmentNode*, RegisterID* loopSymbolTable);
+ int labelScopeDepth() const;
private:
void reclaimFreeRegisters();
@@ -761,7 +762,9 @@
const CodeType m_codeType;
int calculateTargetScopeDepthForExceptionHandler() const;
- int currentScopeDepth() const;
+ int localScopeDepth() const;
+ void pushScopedControlFlowContext();
+ void popScopedControlFlowContext();
Vector<ControlFlowContext, 0, UnsafeVectorOverflow> m_scopeContextStack;
Vector<SwitchInfo> m_switchContextStack;
Modified: trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp (186995 => 186996)
--- trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp 2015-07-18 19:48:59 UTC (rev 186995)
+++ trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp 2015-07-18 20:12:14 UTC (rev 186996)
@@ -2627,7 +2627,7 @@
LabelScopePtr scope = generator.continueTarget(m_ident);
ASSERT(scope);
- if (generator.scopeDepth() != scope->scopeDepth())
+ if (generator.labelScopeDepth() != scope->scopeDepth())
return 0;
return scope->continueTarget();
@@ -2656,7 +2656,7 @@
LabelScopePtr scope = generator.breakTarget(m_ident);
ASSERT(scope);
- if (generator.scopeDepth() != scope->scopeDepth())
+ if (generator.labelScopeDepth() != scope->scopeDepth())
return 0;
return scope->breakTarget();
@@ -2690,7 +2690,7 @@
generator.emitProfileType(returnRegister.get(), ProfileTypeBytecodeFunctionReturnStatement, nullptr);
generator.emitTypeProfilerExpressionInfo(divotStart(), divotEnd());
}
- if (generator.scopeDepth()) {
+ if (generator.labelScopeDepth()) {
returnRegister = generator.emitMove(generator.newTemporary(), returnRegister.get());
generator.emitPopScopes(generator.scopeRegister(), 0);
}
@@ -2714,7 +2714,7 @@
generator.emitExpressionInfo(m_divot, m_divot - m_expressionLength, m_divot);
generator.emitPushWithScope(generator.scopeRegister(), scope.get());
generator.emitNode(dst, m_statement);
- generator.emitPopScope(generator.scopeRegister());
+ generator.emitPopWithOrCatchScope(generator.scopeRegister());
}
// ------------------------------ CaseClauseNode --------------------------------
@@ -2967,7 +2967,7 @@
generator.emitPushCatchScope(generator.scopeRegister(), m_thrownValueIdent, thrownValueRegister.get(), DontDelete);
generator.emitProfileControlFlow(m_tryBlock->endOffset() + 1);
generator.emitNode(dst, m_catchBlock);
- generator.emitPopScope(generator.scopeRegister());
+ generator.emitPopWithOrCatchScope(generator.scopeRegister());
generator.emitLabel(catchEndLabel.get());
}
Added: trunk/Source/_javascript_Core/tests/stress/lexical-scoping-break-continue.js (0 => 186996)
--- trunk/Source/_javascript_Core/tests/stress/lexical-scoping-break-continue.js (rev 0)
+++ trunk/Source/_javascript_Core/tests/stress/lexical-scoping-break-continue.js 2015-07-18 20:12:14 UTC (rev 186996)
@@ -0,0 +1,216 @@
+function assert(b) {
+ if (!b)
+ throw new Error("bad assertion");
+}
+noInline(assert);
+
+;(function() {
+ function test1() {
+ let x = 20;
+ function foo() {
+ label: {
+ let y = 21;
+ let capY = function () { return y; }
+ assert(x === 20);
+ break label;
+ }
+ assert(x === 20);
+ }
+ foo();
+ }
+
+ function test2() {
+ let x = 20;
+ function capX() { return x; }
+ function foo() {
+ label1: {
+ label2: {
+ let y = 21;
+ let capY = function () { return y; }
+ break label2;
+ }
+ assert(x === 20);
+ }
+ assert(x === 20);
+
+ label1: {
+ label2: {
+ let y = 21;
+ let capY = function () { return y; }
+ assert(x === 20);
+ assert(y === 21);
+ break label1;
+ }
+ }
+ assert(x === 20);
+
+ label1: {
+ let y = 21;
+ let capY = function () { return y; }
+ label2: {
+ let y = 21;
+ let capY = function () { return y; }
+ assert(x === 20);
+ assert(y === 21);
+ break label1;
+ }
+ }
+ assert(x === 20);
+ }
+ foo()
+ }
+
+ function test3() {
+ let x = 20;
+ function capX() { return x; }
+ function foo() {
+ loop1: for (var i = 0; i++ < 1000; ) {
+ //assert(x === 20);
+ loop2: for (var j = 0; j++ < 1000; ) {
+ let y = 21;
+ let capY = function() { return y; }
+ assert(x === 20);
+ assert(y === 21);
+ continue loop1;
+ //break loop1;
+ }
+ }
+ assert(x === 20);
+ }
+ foo()
+ }
+
+ function test4() {
+ let x = 20;
+ function capX() { return x; }
+ function foo() {
+ loop1: for (var i = 0; i++ < 1000; ) {
+ loop2: for (var j = 0; j++ < 1000; ) {
+ let y = 21;
+ let capY = function() { return y; }
+ assert(x === 20);
+ assert(y === 21);
+ break loop1;
+ }
+ }
+ assert(x === 20);
+ }
+ foo()
+ }
+
+ function test5() {
+ let x = 20;
+ function capX() { return x; }
+ function foo() {
+ loop1: for (var i = 0; i++ < 1000; ) {
+ let y = 21;
+ let capY = function() { return y; }
+ loop2: for (var j = 0; j++ < 1000; ) {
+ let y = 21;
+ let capY = function() { return y; }
+ assert(x === 20);
+ assert(y === 21);
+ break loop1;
+ }
+ }
+ assert(x === 20);
+ }
+ foo()
+ }
+
+ function test6() {
+ let x = 20;
+ function capX() { return x; }
+ function foo() {
+ loop1: for (var i = 0; i++ < 1000; ) {
+ assert(x === 20);
+ let y = 21;
+ let capY = function() { return y; }
+ loop2: for (var j = 0; j++ < 1000; ) {
+ let y = 21;
+ let capY = function() { return y; }
+ assert(x === 20);
+ assert(y === 21);
+ try {
+ throw new Error();
+ } catch(e) {
+ } finally {
+ assert(x === 20);
+ continue loop1;
+ }
+ }
+ }
+ assert(x === 20);
+ }
+ foo()
+ }
+
+ function test7() {
+ let x = 20;
+ function capX() { return x; }
+ function foo() {
+ loop1: for (var i = 0; i++ < 1000; ) {
+ assert(x === 20);
+ let y = 21;
+ let capY = function() { return y; }
+ loop2: for (var j = 0; j++ < 1000; ) {
+ let y = 21;
+ let capY = function() { return y; }
+ assert(x === 20);
+ assert(y === 21);
+ try {
+ throw new Error();
+ } catch(e) {
+ continue loop1;
+ } finally {
+ let x = 40;
+ let capX = function() { return x; }
+ assert(x === 40);
+ }
+ }
+ }
+ assert(x === 20);
+ }
+ foo()
+ }
+
+ function test8() {
+ let x = 20;
+ function capX() { return x; }
+ function foo() {
+ loop1: for (var i = 0; i++ < 1000; ) {
+ assert(x === 20);
+ let y = 21;
+ let capY = function() { return y; }
+ loop2: for (var j = 0; j++ < 1000; ) {
+ let y = 21;
+ let capY = function() { return y; }
+ assert(x === 20);
+ assert(y === 21);
+ try {
+ throw new Error();
+ } catch(e) {
+ break loop1;
+ } finally {
+ let x = 40;
+ let capX = function() { return x; }
+ assert(x === 40);
+ }
+ }
+ }
+ assert(x === 20);
+ }
+ foo()
+ }
+
+ for (var i = 0; i < 1000; i++) {
+ test1();
+ test2();
+ test3();
+ test4();
+ test5();
+ test6();
+ test7();
+ test8();
+ }
+})();
_______________________________________________ webkit-changes mailing list [email protected] https://lists.webkit.org/mailman/listinfo/webkit-changes
