blob: e4a8c02cc2dd72c1a6733a190c2484435ed2b309 [file] [log] [blame]
// 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;
}
}