Skip to content

Commit 8de00e2

Browse files
dcharkesCommit Queue
authored and
Commit Queue
committed
[vm] Introduce pragma vm:deeply-immutable
This CL introduces a way to mark all instances of a class as deeply immutable. In order to statically verify that all instances of a deeply immutable class are immutable, a deeply immutable classes must have the following properties: 1. All instance fields must 1. have a deeply immutable type, 2. be final, and 3. be non-late. 2. The class must be `final` or `sealed`. This ensures no non-deeply-immutable subtypes are added by external code. 3. All subtypes must be deeply immutable. This ensures 1.1 can be trusted. 4. The super type must be deeply immutable (except for Object). Note that instances of some classes in the VM are deeply immutable while their class cannot be marked immutable. * SendPort, Capability, RegExp, and StackTrace are not `final` and can be implemented by external code. * UnmodifiableTypedDataViews do not have a public type. (It was recently deprecated.) See runtime/docs/deeply_immutable.md for more details. Use case: This enables attaching a `Dart_FinalizableHandle` to a deeply immutable object and the deeply immutable object with other isolates in the same isolate group. (Note that `NativeFinalizer`s live in an isolate, and not an isolate group. So this should currently _not_ be used with `NativeFinalizer`s. See #55062 for making a `NativeFinalizer.shared(` that would live in an isolate group instead of in an isolate.) Implementation details: Before this CL, the `ImmutableBit` in the object header was only ever set to true for predefined class ids (and for const objects). After this CL, the bit can also be set to true for non const instances of user-defined classes. The object allocation and initialization code has been changed to deal with this new case. The immutability of a class is saved in the class state bits. On object allocation and initialization the immutability bit is read from the class for non-predefined class ids. TEST=runtime/tests/vm/dart/isolates/fast_object_copy2_test.dart TEST=runtime/vm/isolate_reload_test.cc TEST=tests/lib/isolate/deeply_immutable_* Bug: #55120 Bug: #54885 Change-Id: Ib97fe589cb4f81673cb928c93e3093838d82132d Cq-Include-Trybots: luci.dart.try:vm-aot-android-release-arm64c-try,vm-aot-android-release-arm_x64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-debug-x64c-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try,vm-aot-obfuscate-linux-release-x64-try,vm-aot-optimization-level-linux-release-x64-try,vm-appjit-linux-debug-x64-try,vm-asan-linux-release-x64-try,vm-checked-mac-release-arm64-try,vm-eager-optimization-linux-release-ia32-try,vm-eager-optimization-linux-release-x64-try,vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64c-try,vm-ffi-qemu-linux-release-arm-try,vm-ffi-qemu-linux-release-riscv64-try,vm-fuchsia-release-x64-try,vm-kernel-linux-debug-x64-try,vm-kernel-precomp-linux-release-x64-try,vm-linux-debug-ia32-try,vm-linux-debug-x64-try,vm-linux-debug-x64c-try,vm-mac-debug-arm64-try,vm-mac-debug-x64-try,vm-msan-linux-release-x64-try,vm-reload-linux-debug-x64-try,vm-reload-rollback-linux-debug-x64-try,vm-ubsan-linux-release-x64-try Cq-Include-Trybots: dart-internal/g3.dart-internal.try:g3-cbuild-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/354902 Commit-Queue: Daco Harkes <[email protected]> Reviewed-by: Martin Kustermann <[email protected]>
1 parent 094202b commit 8de00e2

37 files changed

+1092
-17
lines changed

pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5994,6 +5994,73 @@ const MessageCode messageFfiCreateOfStructOrUnion = const MessageCode(
59945994
r"""Subclasses of 'Struct' and 'Union' are backed by native memory, and can't be instantiated by a generative constructor. Try allocating it via allocation, or load from a 'Pointer'.""",
59955995
);
59965996

5997+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
5998+
const Code<Null> codeFfiDeeplyImmutableClassesMustBeFinalOrSealed =
5999+
messageFfiDeeplyImmutableClassesMustBeFinalOrSealed;
6000+
6001+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
6002+
const MessageCode messageFfiDeeplyImmutableClassesMustBeFinalOrSealed =
6003+
const MessageCode(
6004+
"FfiDeeplyImmutableClassesMustBeFinalOrSealed",
6005+
problemMessage: r"""Deeply immutable classes must be final or sealed.""",
6006+
correctionMessage: r"""Try marking this class as final or sealed.""",
6007+
);
6008+
6009+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
6010+
const Code<Null> codeFfiDeeplyImmutableFieldsModifiers =
6011+
messageFfiDeeplyImmutableFieldsModifiers;
6012+
6013+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
6014+
const MessageCode messageFfiDeeplyImmutableFieldsModifiers = const MessageCode(
6015+
"FfiDeeplyImmutableFieldsModifiers",
6016+
problemMessage:
6017+
r"""Deeply immutable classes must only have final non-late instance fields.""",
6018+
correctionMessage:
6019+
r"""Add the 'final' modifier to this field, and remove 'late' modifier from this field.""",
6020+
);
6021+
6022+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
6023+
const Code<Null> codeFfiDeeplyImmutableFieldsMustBeDeeplyImmutable =
6024+
messageFfiDeeplyImmutableFieldsMustBeDeeplyImmutable;
6025+
6026+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
6027+
const MessageCode messageFfiDeeplyImmutableFieldsMustBeDeeplyImmutable =
6028+
const MessageCode(
6029+
"FfiDeeplyImmutableFieldsMustBeDeeplyImmutable",
6030+
problemMessage:
6031+
r"""Deeply immutable classes must only have deeply immutable instance fields. Deeply immutable types include 'int', 'double', 'bool', 'String', 'Pointer', 'Float32x4', 'Float64x2', 'Int32x4', and classes annotated with `@pragma('vm:deeply-immutable')`.""",
6032+
correctionMessage:
6033+
r"""Try changing the type of this field to a deeply immutable type or mark the type of this field as deeply immutable.""",
6034+
);
6035+
6036+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
6037+
const Code<Null> codeFfiDeeplyImmutableSubtypesMustBeDeeplyImmutable =
6038+
messageFfiDeeplyImmutableSubtypesMustBeDeeplyImmutable;
6039+
6040+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
6041+
const MessageCode messageFfiDeeplyImmutableSubtypesMustBeDeeplyImmutable =
6042+
const MessageCode(
6043+
"FfiDeeplyImmutableSubtypesMustBeDeeplyImmutable",
6044+
problemMessage:
6045+
r"""Subtypes of deeply immutable classes must be deeply immutable.""",
6046+
correctionMessage:
6047+
r"""Try marking this class deeply immutable by adding `@pragma('vm:deeply-immutable')`.""",
6048+
);
6049+
6050+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
6051+
const Code<Null> codeFfiDeeplyImmutableSupertypeMustBeDeeplyImmutable =
6052+
messageFfiDeeplyImmutableSupertypeMustBeDeeplyImmutable;
6053+
6054+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
6055+
const MessageCode messageFfiDeeplyImmutableSupertypeMustBeDeeplyImmutable =
6056+
const MessageCode(
6057+
"FfiDeeplyImmutableSupertypeMustBeDeeplyImmutable",
6058+
problemMessage:
6059+
r"""The super type of deeply immutable classes must be deeply immutable.""",
6060+
correctionMessage:
6061+
r"""Try marking the super class deeply immutable by adding `@pragma('vm:deeply-immutable')`.""",
6062+
);
6063+
59976064
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
59986065
const Code<Null> codeFfiDefaultAssetDuplicate = messageFfiDefaultAssetDuplicate;
59996066

pkg/front_end/lib/src/api_unstable/vm.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ export '../fasta/codes/fasta_codes.dart'
4848
messageFfiAbiSpecificIntegerMappingInvalid,
4949
messageFfiAddressOfMustBeNative,
5050
messageFfiCreateOfStructOrUnion,
51+
messageFfiDeeplyImmutableClassesMustBeFinalOrSealed,
52+
messageFfiDeeplyImmutableFieldsModifiers,
53+
messageFfiDeeplyImmutableFieldsMustBeDeeplyImmutable,
54+
messageFfiDeeplyImmutableSubtypesMustBeDeeplyImmutable,
55+
messageFfiDeeplyImmutableSupertypeMustBeDeeplyImmutable,
5156
messageFfiDefaultAssetDuplicate,
5257
messageFfiExceptionalReturnNull,
5358
messageFfiExpectedConstant,

pkg/front_end/messages.status

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,11 @@ FfiAbiSpecificIntegerMappingInvalid/analyzerCode: Fail
382382
FfiCompoundImplementsFinalizable/analyzerCode: Fail
383383
FfiCreateOfStructOrUnion/analyzerCode: Fail
384384
FfiDartTypeMismatch/analyzerCode: Fail
385+
FfiDeeplyImmutableClassesMustBeFinalOrSealed/analyzerCode: Fail
386+
FfiDeeplyImmutableFieldsModifiers/analyzerCode: Fail
387+
FfiDeeplyImmutableFieldsMustBeDeeplyImmutable/analyzerCode: Fail
388+
FfiDeeplyImmutableSubtypesMustBeDeeplyImmutable/analyzerCode: Fail
389+
FfiDeeplyImmutableSupertypeMustBeDeeplyImmutable/analyzerCode: Fail
385390
FfiEmptyStruct/analyzerCode: Fail
386391
FfiExceptionalReturnNull/analyzerCode: Fail
387392
FfiExpectedConstant/analyzerCode: Fail

pkg/front_end/messages.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5165,6 +5165,36 @@ FfiDartTypeMismatch:
51655165
problemMessage: "Expected '#type' to be a subtype of '#type2'."
51665166
external: test/ffi_test.dart
51675167

5168+
FfiDeeplyImmutableClassesMustBeFinalOrSealed:
5169+
# Used by dart:ffi
5170+
problemMessage: 'Deeply immutable classes must be final or sealed.'
5171+
correctionMessage: 'Try marking this class as final or sealed.'
5172+
external: test/ffi_test.dart
5173+
5174+
FfiDeeplyImmutableFieldsMustBeDeeplyImmutable:
5175+
# Used by dart:ffi
5176+
problemMessage: "Deeply immutable classes must only have deeply immutable instance fields. Deeply immutable types include 'int', 'double', 'bool', 'String', 'Pointer', 'Float32x4', 'Float64x2', 'Int32x4', and classes annotated with `@pragma('vm:deeply-immutable')`."
5177+
correctionMessage: 'Try changing the type of this field to a deeply immutable type or mark the type of this field as deeply immutable.'
5178+
external: test/ffi_test.dart
5179+
5180+
FfiDeeplyImmutableFieldsModifiers:
5181+
# Used by dart:ffi
5182+
problemMessage: 'Deeply immutable classes must only have final non-late instance fields.'
5183+
correctionMessage: "Add the 'final' modifier to this field, and remove 'late' modifier from this field."
5184+
external: test/ffi_test.dart
5185+
5186+
FfiDeeplyImmutableSubtypesMustBeDeeplyImmutable:
5187+
# Used by dart:ffi
5188+
problemMessage: 'Subtypes of deeply immutable classes must be deeply immutable.'
5189+
correctionMessage: "Try marking this class deeply immutable by adding `@pragma('vm:deeply-immutable')`."
5190+
external: test/ffi_test.dart
5191+
5192+
FfiDeeplyImmutableSupertypeMustBeDeeplyImmutable:
5193+
# Used by dart:ffi
5194+
problemMessage: 'The super type of deeply immutable classes must be deeply immutable.'
5195+
correctionMessage: "Try marking the super class deeply immutable by adding `@pragma('vm:deeply-immutable')`."
5196+
external: test/ffi_test.dart
5197+
51685198
FfiDefaultAssetDuplicate:
51695199
# Used by dart:ffi
51705200
problemMessage: "There may be at most one @DefaultAsset annotation on a library."

pkg/front_end/test/spell_checking_list_messages.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ dart:js_interop
4545
dart:js_interop_unsafe
4646
dart_runner
4747
dartbug.com
48+
deeply
4849
defaultasset
4950
dname
5051
e.g
@@ -55,6 +56,8 @@ extensiontype
5556
f
5657
ffi
5758
finality
59+
float32x
60+
float64x
5861
flutter_runner
5962
function.tojs
6063
futureor
@@ -63,6 +66,7 @@ guarded
6366
guides
6467
h
6568
https
69+
int32x
6670
interact
6771
interop
6872
intervening
@@ -79,6 +83,7 @@ loadlibrary
7983
macro
8084
member(s)
8185
migrate
86+
modifier
8287
mocking
8388
n
8489
name.#name
@@ -106,6 +111,7 @@ patterns
106111
placing
107112
pointer`s
108113
pragma
114+
pragma('vm:deeply
109115
preexisting
110116
pubspec.yaml
111117
r

pkg/vm/lib/modular/target/vm.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:kernel/target/changed_structure_notifier.dart';
1111
import 'package:kernel/target/targets.dart';
1212

1313
import '../transformations/call_site_annotator.dart' as callSiteAnnotator;
14+
import '../transformations/deeply_immutable.dart' as deeply_immutable;
1415
import '../transformations/lowering.dart' as lowering
1516
show transformLibraries, transformProcedure;
1617
import '../transformations/mixin_full_resolution.dart' as transformMixins
@@ -151,6 +152,13 @@ class VmTarget extends Target {
151152
ReferenceFromIndex? referenceFromIndex,
152153
{void Function(String msg)? logger,
153154
ChangedStructureNotifier? changedStructureNotifier}) {
155+
deeply_immutable.validateLibraries(
156+
libraries,
157+
coreTypes,
158+
diagnosticReporter,
159+
);
160+
logger?.call("Validated deeply immutable");
161+
154162
transformMixins.transformLibraries(
155163
this, coreTypes, hierarchy, libraries, referenceFromIndex);
156164
logger?.call("Transformed mixin applications");
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:front_end/src/fasta/codes/fasta_codes.dart'
6+
show
7+
messageFfiDeeplyImmutableClassesMustBeFinalOrSealed,
8+
messageFfiDeeplyImmutableFieldsModifiers,
9+
messageFfiDeeplyImmutableFieldsMustBeDeeplyImmutable,
10+
messageFfiDeeplyImmutableSubtypesMustBeDeeplyImmutable,
11+
messageFfiDeeplyImmutableSupertypeMustBeDeeplyImmutable;
12+
import 'package:kernel/ast.dart';
13+
import 'package:kernel/core_types.dart';
14+
import 'package:kernel/target/targets.dart' show DiagnosticReporter;
15+
16+
void validateLibraries(
17+
List<Library> libraries,
18+
CoreTypes coreTypes,
19+
DiagnosticReporter diagnosticReporter,
20+
) {
21+
final validator = DeeplyImmutableValidator(
22+
coreTypes,
23+
diagnosticReporter,
24+
);
25+
for (final library in libraries) {
26+
validator.visitLibrary(library);
27+
}
28+
}
29+
30+
/// Implements the `vm:deeply-immutable` semantics.
31+
class DeeplyImmutableValidator {
32+
static const vmDeeplyImmutable = "vm:deeply-immutable";
33+
34+
final CoreTypes coreTypes;
35+
final DiagnosticReporter diagnosticReporter;
36+
final Class pragmaClass;
37+
final Field pragmaName;
38+
39+
DeeplyImmutableValidator(
40+
this.coreTypes,
41+
this.diagnosticReporter,
42+
) : pragmaClass = coreTypes.pragmaClass,
43+
pragmaName = coreTypes.pragmaName;
44+
45+
void visitLibrary(Library library) {
46+
for (final cls in library.classes) {
47+
visitClass(cls);
48+
}
49+
}
50+
51+
void visitClass(Class node) {
52+
_validateDeeplyImmutable(node);
53+
}
54+
55+
void _validateDeeplyImmutable(Class node) {
56+
if (!_isDeeplyImmutableClass(node)) {
57+
// If class is not marked deeply immutable, check that none of the super
58+
// types is marked deeply immutable.
59+
final classes = [
60+
if (node.superclass != null) node.superclass!,
61+
for (final superType in node.implementedTypes) superType.classNode,
62+
if (node.mixedInClass != null) node.mixedInClass!,
63+
];
64+
for (final superClass in classes) {
65+
if (_isDeeplyImmutableClass(superClass)) {
66+
diagnosticReporter.report(
67+
messageFfiDeeplyImmutableSubtypesMustBeDeeplyImmutable,
68+
node.fileOffset,
69+
node.name.length,
70+
node.location!.file,
71+
);
72+
}
73+
}
74+
return;
75+
}
76+
77+
final superClass = node.superclass;
78+
if (superClass != null && superClass != coreTypes.objectClass) {
79+
if (!_isDeeplyImmutableClass(superClass)) {
80+
diagnosticReporter.report(
81+
messageFfiDeeplyImmutableSupertypeMustBeDeeplyImmutable,
82+
node.fileOffset,
83+
node.name.length,
84+
node.location!.file,
85+
);
86+
}
87+
}
88+
89+
// Don't allow implementing, extending or mixing in deeply immutable classes
90+
// in other libraries. Adding a `vm:deeply-immutable` pragma to a class that
91+
// might be implemented, extended or mixed in would break subtypes that are
92+
// not marked deeply immutable. (We could consider relaxing this and
93+
// allowing breaking subtypes upon adding the pragma.)
94+
if (!(node.isFinal || node.isSealed)) {
95+
diagnosticReporter.report(
96+
messageFfiDeeplyImmutableClassesMustBeFinalOrSealed,
97+
node.fileOffset,
98+
node.name.length,
99+
node.location!.file,
100+
);
101+
}
102+
103+
// All instance fields should be non-late final and deeply immutable.
104+
for (final field in node.fields) {
105+
if (field.isStatic) {
106+
// Static fields are not part of instances.
107+
continue;
108+
}
109+
if (!_isDeeplyImmutableDartType(field.type)) {
110+
diagnosticReporter.report(
111+
messageFfiDeeplyImmutableFieldsMustBeDeeplyImmutable,
112+
field.fileOffset,
113+
field.name.text.length,
114+
field.location!.file,
115+
);
116+
}
117+
if (!field.isFinal || field.isLate) {
118+
diagnosticReporter.report(
119+
messageFfiDeeplyImmutableFieldsModifiers,
120+
field.fileOffset,
121+
field.name.text.length,
122+
field.location!.file,
123+
);
124+
}
125+
}
126+
}
127+
128+
bool _isDeeplyImmutableDartType(DartType dartType) {
129+
if (dartType is NullType) {
130+
return true;
131+
}
132+
if (dartType is InterfaceType) {
133+
final classNode = dartType.classNode;
134+
return _isDeeplyImmutableClass(classNode);
135+
}
136+
if (dartType is TypeParameterType) {
137+
return _isDeeplyImmutableDartType(dartType.bound);
138+
}
139+
return false;
140+
}
141+
142+
bool _isDeeplyImmutableClass(Class node) {
143+
for (final annotation in node.annotations) {
144+
if (annotation is ConstantExpression) {
145+
final constant = annotation.constant;
146+
if (constant is InstanceConstant &&
147+
constant.classNode == pragmaClass &&
148+
constant.fieldValues[pragmaName.fieldReference] ==
149+
StringConstant(vmDeeplyImmutable)) {
150+
return true;
151+
}
152+
}
153+
}
154+
return false;
155+
}
156+
}

0 commit comments

Comments
 (0)