| // Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'package:expect/expect.dart'; | |
| import 'package:expect/variations.dart'; | |
| // Prevent inlining and Binaryen duplicate-function-elimination so `exception1` | |
| // and `exception2` remain distinct frames in stack traces when unminified. | |
| @pragma('vm:never-inline') | |
| @pragma('dart2js:noInline') | |
| @pragma('wasm:never-inline') | |
| void exception1(String e) { | |
| if (identical(e, '1')) return; | |
| throw e; | |
| } | |
| @pragma('vm:never-inline') | |
| @pragma('dart2js:noInline') | |
| @pragma('wasm:never-inline') | |
| void exception2(String e) { | |
| if (identical(e, '2')) return; | |
| throw e; | |
| } | |
| @pragma('vm:never-inline') | |
| @pragma('dart2js:noInline') | |
| @pragma('wasm:never-inline') | |
| void doSyncThrow1() { | |
| try { | |
| exception1('outer'); | |
| } on Object { | |
| try { | |
| exception2('inner'); | |
| } on Object { | |
| // ignore | |
| } | |
| rethrow; | |
| } | |
| } | |
| @pragma('vm:never-inline') | |
| @pragma('dart2js:noInline') | |
| @pragma('wasm:never-inline') | |
| void doSyncThrow2() { | |
| try { | |
| exception2('outer'); | |
| } on Object { | |
| try { | |
| exception1('inner'); | |
| } on Object { | |
| try { | |
| exception1('more inner'); | |
| } on Object { | |
| // ignore | |
| } | |
| } | |
| rethrow; | |
| } | |
| } | |
| @pragma('vm:never-inline') | |
| @pragma('dart2js:noInline') | |
| @pragma('wasm:never-inline') | |
| void doSyncThrow3() { | |
| try { | |
| exception1('outer'); | |
| } on Object { | |
| try { | |
| // don't throw | |
| } on Object { | |
| try { | |
| // also don't throw | |
| } on Object { | |
| // ignore | |
| } | |
| } | |
| rethrow; | |
| } | |
| } | |
| @pragma('vm:never-inline') | |
| @pragma('dart2js:noInline') | |
| @pragma('wasm:never-inline') | |
| void doSyncThrow4() { | |
| try { | |
| exception1('outer'); | |
| } on Object { | |
| try { | |
| exception2('inner'); | |
| } on bool {} | |
| rethrow; | |
| } | |
| } | |
| @pragma('vm:never-inline') | |
| @pragma('dart2js:noInline') | |
| @pragma('wasm:never-inline') | |
| void doSyncThrowFinally() { | |
| try { | |
| exception1('outer'); | |
| } finally { | |
| // empty finalizer | |
| } | |
| } | |
| @pragma('vm:never-inline') | |
| @pragma('dart2js:noInline') | |
| @pragma('wasm:never-inline') | |
| void doSyncThrowNestedFinally() { | |
| try { | |
| exception1('outer'); | |
| } finally { | |
| try { | |
| exception2('inner'); | |
| } on Object { | |
| // ignore | |
| } | |
| } | |
| } | |
| @pragma('vm:never-inline') | |
| @pragma('dart2js:noInline') | |
| @pragma('wasm:never-inline') | |
| List<String> testTryFinallyInLoop() { | |
| final log = <String>[]; | |
| for (int i = 0; i < 4; i++) { | |
| label: | |
| try { | |
| log.add('try:$i'); | |
| if (i == 0) { | |
| break label; | |
| } | |
| if (i == 1) { | |
| try { | |
| throw 'err'; | |
| } finally { | |
| log.add('inner-finally:$i'); | |
| } | |
| } | |
| } catch (e) { | |
| log.add('catch:$i:$e'); | |
| } finally { | |
| log.add('finally:$i'); | |
| } | |
| log.add('after:$i'); | |
| } | |
| return log; | |
| } | |
| void main() { | |
| try { | |
| doSyncThrow1(); | |
| Expect.fail('should throw'); | |
| } catch (e, s) { | |
| Expect.equals(e, 'outer'); | |
| if (symbolicUnminifiedStackTraces) { | |
| // Verify stack trace integrity of the rethrow when symbols are preserved | |
| Expect.isTrue('$s'.contains('exception1')); | |
| Expect.isTrue('$s'.contains('doSyncThrow1')); | |
| } | |
| } | |
| try { | |
| doSyncThrow2(); | |
| Expect.fail('should throw'); | |
| } catch (e, s) { | |
| Expect.equals(e, 'outer'); | |
| if (symbolicUnminifiedStackTraces) { | |
| Expect.isTrue('$s'.contains('exception2')); | |
| Expect.isTrue('$s'.contains('doSyncThrow2')); | |
| } | |
| } | |
| try { | |
| doSyncThrow3(); | |
| Expect.fail('should throw'); | |
| } catch (e, s) { | |
| Expect.equals(e, 'outer'); | |
| if (symbolicUnminifiedStackTraces) { | |
| Expect.isTrue('$s'.contains('exception1')); | |
| Expect.isTrue('$s'.contains('doSyncThrow3')); | |
| } | |
| } | |
| try { | |
| doSyncThrow4(); | |
| Expect.fail('should throw'); | |
| } catch (e, s) { | |
| Expect.equals(e, 'inner'); | |
| if (symbolicUnminifiedStackTraces) { | |
| Expect.isTrue('$s'.contains('exception2')); | |
| Expect.isTrue('$s'.contains('doSyncThrow4')); | |
| } | |
| } | |
| try { | |
| doSyncThrowFinally(); | |
| Expect.fail('should throw'); | |
| } catch (e, s) { | |
| Expect.equals(e, 'outer'); | |
| if (symbolicUnminifiedStackTraces) { | |
| Expect.isTrue('$s'.contains('exception1')); | |
| Expect.isTrue('$s'.contains('doSyncThrowFinally')); | |
| } | |
| } | |
| try { | |
| doSyncThrowNestedFinally(); | |
| Expect.fail('should throw'); | |
| } catch (e, s) { | |
| Expect.equals(e, 'outer'); | |
| if (symbolicUnminifiedStackTraces) { | |
| Expect.isTrue('$s'.contains('exception1')); | |
| Expect.isTrue('$s'.contains('doSyncThrowNestedFinally')); | |
| } | |
| } | |
| Expect.listEquals([ | |
| 'try:0', | |
| 'finally:0', | |
| 'after:0', | |
| 'try:1', | |
| 'inner-finally:1', | |
| 'catch:1:err', | |
| 'finally:1', | |
| 'after:1', | |
| 'try:2', | |
| 'finally:2', | |
| 'after:2', | |
| 'try:3', | |
| 'finally:3', | |
| 'after:3', | |
| ], testTryFinallyInLoop()); | |
| } |