| // 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:cfg/ir/global_context.dart'; | |
| import 'package:cfg/ir/instructions.dart'; | |
| import 'package:cfg/ir/source_position.dart'; | |
| import 'package:kernel/ast.dart' as ast; | |
| class ExceptionHandler._( | |
| final int index, | |
| final int outerIndex, | |
| final bool isSynthetic, | |
| final bool needsStackTrace, | |
| final List<ast.DartType> guardTypes, | |
| ) { | |
| late final int pcOffset; | |
| bool get hasCatchAll => guardTypes.any(_isCatchAll); | |
| static bool _isCatchAll(ast.DartType guardType) => switch (guardType) { | |
| ast.DynamicType() => true, | |
| ast.VoidType() => true, | |
| ast.InterfaceType() => | |
| guardType.classNode == GlobalContext.instance.coreTypes.objectClass, | |
| ast.FutureOrType() => _isCatchAll(guardType.typeArgument), | |
| ast.ExtensionType() => _isCatchAll(guardType.extensionTypeErasure), | |
| _ => false, | |
| }; | |
| } | |
| /// Metadata describing exception handlers. | |
| class ExceptionHandlers { | |
| /// Whether this code has a top-level async exception handler. | |
| final bool hasAsyncHandler; | |
| final Map<CatchBlock, ExceptionHandler> _handlers = {}; | |
| ExceptionHandlers({required this.hasAsyncHandler}); | |
| Iterable<ExceptionHandler> get handlers => _handlers.values; | |
| ExceptionHandler getHandler(CatchBlock catchBlock) => | |
| _handlers[catchBlock] ??= _createHandler(catchBlock); | |
| ExceptionHandler _createHandler(CatchBlock catchBlock) { | |
| final outerHandler = catchBlock.exceptionHandler; | |
| final outerIndex = outerHandler != null | |
| ? getHandler(outerHandler).index | |
| : -1; | |
| var needsStackTrace = false; | |
| for (final instr in catchBlock) { | |
| if (instr is! Parameter) break; | |
| if (instr.variable.isStackTraceVariable) { | |
| needsStackTrace = instr.hasUses; | |
| break; | |
| } | |
| } | |
| return ExceptionHandler._( | |
| _handlers.length, | |
| outerIndex, | |
| catchBlock.isSynthetic, | |
| needsStackTrace, | |
| catchBlock.guardTypes, | |
| ); | |
| } | |
| @override | |
| int get hashCode => | |
| _handlers.isEmpty ? hasAsyncHandler.hashCode : identityHashCode(this); | |
| @override | |
| bool operator ==(Object other) => | |
| identical(this, other) || | |
| (_handlers.isEmpty && | |
| other is ExceptionHandlers && | |
| other._handlers.isEmpty && | |
| this.hasAsyncHandler == other.hasAsyncHandler); | |
| } | |
| class CallSite( | |
| final int pcOffset, | |
| final int exceptionHandlerIndex, | |
| final SourcePosition sourcePosition, | |
| ); | |
| /// Metadata describing call sites (aka PC descriptors). | |
| class PcDescriptors { | |
| final List<CallSite> callSites = []; | |
| void add(CallSite cs) { | |
| assert(callSites.isEmpty || callSites.last.pcOffset < cs.pcOffset); | |
| callSites.add(cs); | |
| } | |
| @override | |
| int get hashCode => callSites.isEmpty ? 37 : identityHashCode(this); | |
| @override | |
| bool operator ==(Object other) => | |
| identical(this, other) || | |
| (callSites.isEmpty && other is PcDescriptors && other.callSites.isEmpty); | |
| } | |
| class ExceptionSite(final int pcOffset) { | |
| // TODO: add moves | |
| } | |
| /// Metadata describing how to move values from an exception site to the exception handler. | |
| class CatchEntryMoves { | |
| final List<ExceptionSite> exceptionSites = []; | |
| void add(ExceptionSite es) { | |
| exceptionSites.add(es); | |
| } | |
| @override | |
| int get hashCode => exceptionSites.isEmpty ? 31 : identityHashCode(this); | |
| @override | |
| bool operator ==(Object other) => | |
| identical(this, other) || | |
| (exceptionSites.isEmpty && | |
| other is CatchEntryMoves && | |
| other.exceptionSites.isEmpty); | |
| } | |
| class CodeSourcePosition( | |
| final int pcOffset, | |
| final SourcePosition sourcePosition, | |
| ); | |
| /// Metadata describing source positions. | |
| class CodeSourceMap { | |
| final List<CodeSourcePosition> sourcePositions = []; | |
| void add(CodeSourcePosition sp) { | |
| if (sourcePositions.isNotEmpty) { | |
| assert(sourcePositions.last.pcOffset < sp.pcOffset); | |
| if (sourcePositions.last.sourcePosition == sp.sourcePosition) { | |
| return; | |
| } | |
| } | |
| sourcePositions.add(sp); | |
| } | |
| @override | |
| int get hashCode => sourcePositions.isEmpty ? 23 : identityHashCode(this); | |
| @override | |
| bool operator ==(Object other) => | |
| identical(this, other) || | |
| (sourcePositions.isEmpty && | |
| other is CodeSourceMap && | |
| other.sourcePositions.isEmpty); | |
| } |