Skip to content

Commit 3850dc4

Browse files
authored
fix(async-rewriter): ensure finally blocks always run MONGOSH-1076 (#1167)
Ensure that `finally` blocks are run even if the `try` block did not result in an exception.
1 parent b785555 commit 3850dc4

File tree

3 files changed

+89
-4
lines changed

3 files changed

+89
-4
lines changed

packages/async-rewriter2/README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,21 @@ try {
107107
into
108108

109109
```js
110-
let _isCatchable;
110+
let _isCatchable = true;
111111

112112
try {
113113
foo1();
114114
} catch (err) {
115115
_isCatchable = !err || !err[Symbol.for('@@mongosh.uncatchable')];
116116

117-
if (_isCatchable) bar1(err); else throw err;
117+
if (_isCatchable) {
118+
try {
119+
bar1(err);
120+
} catch (innerErr) {
121+
_isCatchable = !innerErr || !innerErr[Symbol.for('@@mongosh.uncatchable')];
122+
throw innerErr;
123+
}
124+
} else throw err;
118125
} finally {
119126
if (_isCatchable) baz();
120127
}

packages/async-rewriter2/src/async-writer-babel.spec.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,33 @@ describe('AsyncWriter', () => {
925925
expect(result).to.equal('finally');
926926
});
927927

928+
it('allows returning from finally when exception was thrown in catch block', () => {
929+
const result = runTranspiledCode(`
930+
(() => {
931+
try {
932+
throw new Error('first error');
933+
} catch (err) {
934+
throw new Error('second error');
935+
} finally {
936+
return 'finally';
937+
}
938+
})();`);
939+
expect(result).to.equal('finally');
940+
});
941+
942+
it('allows returning from finally when no exception is thrown', () => {
943+
const result = runTranspiledCode(`
944+
(() => {
945+
try {
946+
} catch (err) {
947+
return 'catch';
948+
} finally {
949+
return 'finally';
950+
}
951+
})();`);
952+
expect(result).to.equal('finally');
953+
});
954+
928955
it('allows finally without catch', () => {
929956
const result = runTranspiledCode(`
930957
(() => {
@@ -937,6 +964,18 @@ describe('AsyncWriter', () => {
937964
expect(result).to.equal('finally');
938965
});
939966

967+
it('allows finally without catch with return from try block', () => {
968+
const result = runTranspiledCode(`
969+
(() => {
970+
try {
971+
return 'try';
972+
} finally {
973+
return 'finally';
974+
}
975+
})();`);
976+
expect(result).to.equal('finally');
977+
});
978+
940979
it('allows throwing primitives', () => {
941980
const result = runTranspiledCode(`
942981
(() => {
@@ -1020,5 +1059,23 @@ describe('AsyncWriter', () => {
10201059
expect(err.message).to.equal('uncatchable!');
10211060
}
10221061
});
1062+
1063+
it('does not catch uncatchable exceptions thrown from catch block', () => {
1064+
try {
1065+
runTranspiledCode(`
1066+
(() => {
1067+
try {
1068+
throw new Error('regular error');
1069+
} catch {
1070+
throwUncatchable();
1071+
} finally {
1072+
return;
1073+
}
1074+
})();`);
1075+
expect.fail('missed exception');
1076+
} catch (err) {
1077+
expect(err.message).to.equal('uncatchable!');
1078+
}
1079+
});
10231080
});
10241081
});

packages/async-rewriter2/src/stages/uncatchable-exceptions.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,10 @@ export default ({ types: t }: { types: typeof BabelTypes }): babel.PluginObj<{}>
7575
// (i.e. whether the finalizer is allowed to run) outside of the
7676
// try/catch/finally block.
7777
const isCatchable = path.scope.generateUidIdentifier('_isCatchable');
78+
const exceptionFromCatchIdentifier = path.scope.generateUidIdentifier('_innerExc');
7879
path.replaceWithMultiple([
79-
t.variableDeclaration('let', [t.variableDeclarator(isCatchable)]),
80+
// let isCatchable = true /* for the case in which no exception is thrown */
81+
t.variableDeclaration('let', [t.variableDeclarator(isCatchable, t.booleanLiteral(true))]),
8082
Object.assign(
8183
t.tryStatement(
8284
block,
@@ -89,7 +91,26 @@ export default ({ types: t }: { types: typeof BabelTypes }): babel.PluginObj<{}>
8991
isCatchable,
9092
notUncatchableCheck({ ERR_IDENTIFIER: catchParam }))),
9193
// if (isCatchable) { ... } else throw err;
92-
t.ifStatement(isCatchable, handler.body, t.throwStatement(catchParam))
94+
t.ifStatement(
95+
isCatchable,
96+
// try/catch around the catch body so we know whether finally should run here
97+
Object.assign(t.tryStatement(
98+
handler.body,
99+
// catch (err) {
100+
t.catchClause(
101+
exceptionFromCatchIdentifier,
102+
t.blockStatement([
103+
// isCatchable = !err[isUncatchableSymbol]
104+
t.expressionStatement(
105+
t.assignmentExpression('=',
106+
isCatchable,
107+
notUncatchableCheck({ ERR_IDENTIFIER: exceptionFromCatchIdentifier }))),
108+
// throw err;
109+
t.throwStatement(exceptionFromCatchIdentifier)
110+
])
111+
)
112+
), { [isGeneratedTryCatch]: true }),
113+
t.throwStatement(catchParam))
93114
]),
94115
),
95116
t.blockStatement([

0 commit comments

Comments
 (0)