Only call return() for an abrupt completion in user code (#51297)

This commit is contained in:
Ron Buckton
2022-10-28 18:36:40 -04:00
committed by GitHub
parent a7a9d158e8
commit 5cfb3a2fe3
13 changed files with 528 additions and 266 deletions

View File

@@ -643,12 +643,26 @@ namespace ts {
return node;
}
function convertForOfStatementHead(node: ForOfStatement, boundValue: Expression) {
const binding = createForOfBindingStatement(factory, node.initializer, boundValue);
function convertForOfStatementHead(node: ForOfStatement, boundValue: Expression, nonUserCode: Identifier) {
const value = factory.createTempVariable(hoistVariableDeclaration);
const iteratorValueExpression = factory.createAssignment(value, boundValue);
const iteratorValueStatement = factory.createExpressionStatement(iteratorValueExpression);
setSourceMapRange(iteratorValueStatement, node.expression);
const exitNonUserCodeExpression = factory.createAssignment(nonUserCode, factory.createFalse());
const exitNonUserCodeStatement = factory.createExpressionStatement(exitNonUserCodeExpression);
setSourceMapRange(exitNonUserCodeStatement, node.expression);
const enterNonUserCodeExpression = factory.createAssignment(nonUserCode, factory.createTrue());
const enterNonUserCodeStatement = factory.createExpressionStatement(enterNonUserCodeExpression);
setSourceMapRange(exitNonUserCodeStatement, node.expression);
const statements: Statement[] = [];
const binding = createForOfBindingStatement(factory, node.initializer, value);
statements.push(visitNode(binding, visitor, isStatement));
let bodyLocation: TextRange | undefined;
let statementsLocation: TextRange | undefined;
const statements: Statement[] = [visitNode(binding, visitor, isStatement)];
const statement = visitIterationBody(node.statement, visitor, context);
if (isBlock(statement)) {
addRange(statements, statement.statements);
@@ -659,7 +673,7 @@ namespace ts {
statements.push(statement);
}
return setEmitFlags(
const body = setEmitFlags(
setTextRange(
factory.createBlock(
setTextRange(factory.createNodeArray(statements), statementsLocation),
@@ -669,6 +683,18 @@ namespace ts {
),
EmitFlags.NoSourceMap | EmitFlags.NoTokenSourceMaps
);
return factory.createBlock([
iteratorValueStatement,
exitNonUserCodeStatement,
factory.createTryStatement(
body,
/*catchClause*/ undefined,
factory.createBlock([
enterNonUserCodeStatement
])
)
]);
}
function createDownlevelAwait(expression: Expression) {
@@ -681,6 +707,8 @@ namespace ts {
const expression = visitNode(node.expression, visitor, isExpression);
const iterator = isIdentifier(expression) ? factory.getGeneratedNameForNode(expression) : factory.createTempVariable(/*recordTempVariable*/ undefined);
const result = isIdentifier(expression) ? factory.getGeneratedNameForNode(iterator) : factory.createTempVariable(/*recordTempVariable*/ undefined);
const nonUserCode = factory.createTempVariable(/*recordTempVariable*/ undefined);
const done = factory.createTempVariable(hoistVariableDeclaration);
const errorRecord = factory.createUniqueName("e");
const catchVariable = factory.getGeneratedNameForNode(errorRecord);
const returnMethod = factory.createTempVariable(/*recordTempVariable*/ undefined);
@@ -704,6 +732,7 @@ namespace ts {
/*initializer*/ setEmitFlags(
setTextRange(
factory.createVariableDeclarationList([
factory.createVariableDeclaration(nonUserCode, /*exclamationToken*/ undefined, /*type*/ undefined, factory.createTrue()),
setTextRange(factory.createVariableDeclaration(iterator, /*exclamationToken*/ undefined, /*type*/ undefined, initializer), node.expression),
factory.createVariableDeclaration(result)
]),
@@ -711,12 +740,13 @@ namespace ts {
),
EmitFlags.NoHoisting
),
/*condition*/ factory.createComma(
/*condition*/ factory.inlineExpressions([
factory.createAssignment(result, createDownlevelAwait(callNext)),
factory.createLogicalNot(getDone)
),
factory.createAssignment(done, getDone),
factory.createLogicalNot(done)
]),
/*incrementor*/ undefined,
/*statement*/ convertForOfStatementHead(node, getValue)
/*statement*/ convertForOfStatementHead(node, getValue, nonUserCode)
),
/*location*/ node
),
@@ -754,8 +784,8 @@ namespace ts {
factory.createIfStatement(
factory.createLogicalAnd(
factory.createLogicalAnd(
result,
factory.createLogicalNot(getDone)
factory.createLogicalNot(nonUserCode),
factory.createLogicalNot(done),
),
factory.createAssignment(
returnMethod,

View File

@@ -102,4 +102,31 @@ describe("unittests:: evaluation:: forAwaitOfEvaluation", () => {
assert.instanceOf(result.output[1], Promise);
assert.instanceOf(result.output[2], Promise);
});
it("don't call return when non-user code throws (es2015)", async () => {
const result = evaluator.evaluateTypeScript(`
let returnCalled = false;
async function f() {
let i = 0;
const iterator = {
[Symbol.asyncIterator](): AsyncIterableIterator<any> { return this; },
async next() {
i++;
if (i < 2) return { value: undefined, done: false };
throw new Error();
},
async return() {
returnCalled = true;
}
};
for await (const item of iterator) {
}
}
export async function main() {
try { await f(); } catch { }
return returnCalled;
}
`, { target: ts.ScriptTarget.ES2015 });
assert.isFalse(await result.main());
});
});