| // Copyright (c) 2024, 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. |
| |
| // This imports 'codes/cfe_codes.dart' instead of 'api_prototype/codes.dart' to |
| // avoid cyclic dependency between `package:vm/modular` and `package:front_end`. |
| import 'package:front_end/src/codes/cfe_codes.dart' |
| show |
| codeFfiDeeplyImmutableClassesMustBeFinalOrSealed, |
| codeFfiDeeplyImmutableFieldsModifiers, |
| codeFfiDeeplyImmutableFieldsMustBeDeeplyImmutable, |
| codeFfiDeeplyImmutableSubtypesMustBeDeeplyImmutable, |
| codeFfiDeeplyImmutableSupertypeMustBeDeeplyImmutable; |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/core_types.dart'; |
| import 'package:kernel/library_index.dart' show LibraryIndex; |
| import 'package:kernel/target/targets.dart' show DiagnosticReporter; |
| |
| void validateLibraries( |
| Component component, |
| List<Library> libraries, |
| CoreTypes coreTypes, |
| DiagnosticReporter diagnosticReporter, |
| ) { |
| final LibraryIndex index = LibraryIndex(component, const [ |
| 'dart:ffi', |
| 'dart:nativewrappers', |
| ]); |
| final validator = DeeplyImmutableValidator( |
| index, |
| coreTypes, |
| diagnosticReporter, |
| ); |
| for (final library in libraries) { |
| validator.visitLibrary(library); |
| } |
| } |
| |
| /// Implements the `vm:deeply-immutable` semantics. |
| class DeeplyImmutableValidator { |
| static const vmDeeplyImmutable = "vm:deeply-immutable"; |
| |
| final CoreTypes coreTypes; |
| final DiagnosticReporter diagnosticReporter; |
| final Class pragmaClass; |
| final Field pragmaName; |
| // Can be null if nativewrappers library is not available. |
| final Class? nativeFieldWrapperClass1Class; |
| // Can be null if ffi library is not available. |
| final Class? structClass; |
| final Class? unionClass; |
| |
| DeeplyImmutableValidator( |
| LibraryIndex index, |
| this.coreTypes, |
| this.diagnosticReporter, |
| ) : pragmaClass = coreTypes.pragmaClass, |
| pragmaName = coreTypes.pragmaName, |
| nativeFieldWrapperClass1Class = index.tryGetClass( |
| 'dart:nativewrappers', |
| 'NativeFieldWrapperClass1', |
| ), |
| structClass = index.tryGetClass('dart:ffi', 'Struct'), |
| unionClass = index.tryGetClass('dart:ffi', 'Union'); |
| |
| void visitLibrary(Library library) { |
| for (final cls in library.classes) { |
| visitClass(cls); |
| } |
| } |
| |
| void visitClass(Class node) { |
| _validateDeeplyImmutable(node); |
| } |
| |
| bool _isOrExtendsNativeFieldWrapper1Class(Class? node) { |
| while (node != null && node != nativeFieldWrapperClass1Class) { |
| node = node.superclass; |
| } |
| return node != null; |
| } |
| |
| void _validateDeeplyImmutable(Class node) { |
| if (!_isDeeplyImmutableClass(node)) { |
| // If class is not marked deeply immutable, check that none of the super |
| // types is marked deeply immutable. |
| final classes = [ |
| if (node.superclass != null) node.superclass!, |
| for (final superType in node.implementedTypes) superType.classNode, |
| if (node.mixedInClass != null) node.mixedInClass!, |
| ]; |
| for (final superClass in classes) { |
| if (_isDeeplyImmutableClass(superClass)) { |
| diagnosticReporter.report( |
| codeFfiDeeplyImmutableSubtypesMustBeDeeplyImmutable, |
| node.fileOffset, |
| node.name.length, |
| node.location!.file, |
| ); |
| } |
| } |
| return; |
| } |
| |
| final superClass = node.superclass; |
| if (superClass != null && |
| superClass != coreTypes.objectClass && |
| node != structClass && |
| node != unionClass && |
| !_isOrExtendsNativeFieldWrapper1Class(superClass)) { |
| if (!_isDeeplyImmutableClass(superClass)) { |
| diagnosticReporter.report( |
| codeFfiDeeplyImmutableSupertypeMustBeDeeplyImmutable, |
| node.fileOffset, |
| node.name.length, |
| node.location!.file, |
| ); |
| } |
| } |
| |
| // Don't allow implementing, extending or mixing in deeply immutable classes |
| // in other libraries. Adding a `vm:deeply-immutable` pragma to a class that |
| // might be implemented, extended or mixed in would break subtypes that are |
| // not marked deeply immutable. (We could consider relaxing this and |
| // allowing breaking subtypes upon adding the pragma.) |
| // Exception being ffi Struct and Union classes which are extended, but |
| // have custom treatment. |
| if (node != structClass && |
| node != unionClass && |
| superClass != structClass && |
| superClass != unionClass) { |
| if (!(node.isFinal || node.isSealed)) { |
| diagnosticReporter.report( |
| codeFfiDeeplyImmutableClassesMustBeFinalOrSealed, |
| node.fileOffset, |
| node.name.length, |
| node.location!.file, |
| ); |
| } |
| } |
| |
| // All instance fields should be non-late final and deeply immutable. |
| for (final field in node.fields) { |
| if (field.isStatic) { |
| // Static fields are not part of instances. |
| continue; |
| } |
| if (!_isDeeplyImmutableDartType(field.type)) { |
| diagnosticReporter.report( |
| codeFfiDeeplyImmutableFieldsMustBeDeeplyImmutable, |
| field.fileOffset, |
| field.name.text.length, |
| field.location!.file, |
| ); |
| } |
| if (!field.isFinal || field.isLate) { |
| diagnosticReporter.report( |
| codeFfiDeeplyImmutableFieldsModifiers, |
| field.fileOffset, |
| field.name.text.length, |
| field.location!.file, |
| ); |
| } |
| } |
| } |
| |
| bool _isDeeplyImmutableDartType(DartType dartType) { |
| if (dartType is NullType) { |
| return true; |
| } |
| if (dartType is InterfaceType) { |
| final classNode = dartType.classNode; |
| return _isDeeplyImmutableClass(classNode); |
| } |
| if (dartType is TypeParameterType) { |
| return _isDeeplyImmutableDartType(dartType.bound); |
| } |
| return false; |
| } |
| |
| bool _isDeeplyImmutableClass(Class node) { |
| for (final annotation in node.annotations) { |
| if (annotation is ConstantExpression) { |
| final constant = annotation.constant; |
| if (constant is InstanceConstant && |
| constant.classNode == pragmaClass && |
| constant.fieldValues[pragmaName.fieldReference] == |
| StringConstant(vmDeeplyImmutable)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| } |