mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 19:21:30 +00:00
[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 https://github.com/dart-lang/sdk/issues/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: https://github.com/dart-lang/sdk/issues/55120 Bug: https://github.com/dart-lang/sdk/issues/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 <dacoharkes@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
parent
094202bb91
commit
8de00e2137
|
@ -5994,6 +5994,73 @@ const MessageCode messageFfiCreateOfStructOrUnion = const MessageCode(
|
|||
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'.""",
|
||||
);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Null> codeFfiDeeplyImmutableClassesMustBeFinalOrSealed =
|
||||
messageFfiDeeplyImmutableClassesMustBeFinalOrSealed;
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const MessageCode messageFfiDeeplyImmutableClassesMustBeFinalOrSealed =
|
||||
const MessageCode(
|
||||
"FfiDeeplyImmutableClassesMustBeFinalOrSealed",
|
||||
problemMessage: r"""Deeply immutable classes must be final or sealed.""",
|
||||
correctionMessage: r"""Try marking this class as final or sealed.""",
|
||||
);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Null> codeFfiDeeplyImmutableFieldsModifiers =
|
||||
messageFfiDeeplyImmutableFieldsModifiers;
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const MessageCode messageFfiDeeplyImmutableFieldsModifiers = const MessageCode(
|
||||
"FfiDeeplyImmutableFieldsModifiers",
|
||||
problemMessage:
|
||||
r"""Deeply immutable classes must only have final non-late instance fields.""",
|
||||
correctionMessage:
|
||||
r"""Add the 'final' modifier to this field, and remove 'late' modifier from this field.""",
|
||||
);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Null> codeFfiDeeplyImmutableFieldsMustBeDeeplyImmutable =
|
||||
messageFfiDeeplyImmutableFieldsMustBeDeeplyImmutable;
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const MessageCode messageFfiDeeplyImmutableFieldsMustBeDeeplyImmutable =
|
||||
const MessageCode(
|
||||
"FfiDeeplyImmutableFieldsMustBeDeeplyImmutable",
|
||||
problemMessage:
|
||||
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')`.""",
|
||||
correctionMessage:
|
||||
r"""Try changing the type of this field to a deeply immutable type or mark the type of this field as deeply immutable.""",
|
||||
);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Null> codeFfiDeeplyImmutableSubtypesMustBeDeeplyImmutable =
|
||||
messageFfiDeeplyImmutableSubtypesMustBeDeeplyImmutable;
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const MessageCode messageFfiDeeplyImmutableSubtypesMustBeDeeplyImmutable =
|
||||
const MessageCode(
|
||||
"FfiDeeplyImmutableSubtypesMustBeDeeplyImmutable",
|
||||
problemMessage:
|
||||
r"""Subtypes of deeply immutable classes must be deeply immutable.""",
|
||||
correctionMessage:
|
||||
r"""Try marking this class deeply immutable by adding `@pragma('vm:deeply-immutable')`.""",
|
||||
);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Null> codeFfiDeeplyImmutableSupertypeMustBeDeeplyImmutable =
|
||||
messageFfiDeeplyImmutableSupertypeMustBeDeeplyImmutable;
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const MessageCode messageFfiDeeplyImmutableSupertypeMustBeDeeplyImmutable =
|
||||
const MessageCode(
|
||||
"FfiDeeplyImmutableSupertypeMustBeDeeplyImmutable",
|
||||
problemMessage:
|
||||
r"""The super type of deeply immutable classes must be deeply immutable.""",
|
||||
correctionMessage:
|
||||
r"""Try marking the super class deeply immutable by adding `@pragma('vm:deeply-immutable')`.""",
|
||||
);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Null> codeFfiDefaultAssetDuplicate = messageFfiDefaultAssetDuplicate;
|
||||
|
||||
|
|
|
@ -48,6 +48,11 @@ export '../fasta/codes/fasta_codes.dart'
|
|||
messageFfiAbiSpecificIntegerMappingInvalid,
|
||||
messageFfiAddressOfMustBeNative,
|
||||
messageFfiCreateOfStructOrUnion,
|
||||
messageFfiDeeplyImmutableClassesMustBeFinalOrSealed,
|
||||
messageFfiDeeplyImmutableFieldsModifiers,
|
||||
messageFfiDeeplyImmutableFieldsMustBeDeeplyImmutable,
|
||||
messageFfiDeeplyImmutableSubtypesMustBeDeeplyImmutable,
|
||||
messageFfiDeeplyImmutableSupertypeMustBeDeeplyImmutable,
|
||||
messageFfiDefaultAssetDuplicate,
|
||||
messageFfiExceptionalReturnNull,
|
||||
messageFfiExpectedConstant,
|
||||
|
|
|
@ -382,6 +382,11 @@ FfiAbiSpecificIntegerMappingInvalid/analyzerCode: Fail
|
|||
FfiCompoundImplementsFinalizable/analyzerCode: Fail
|
||||
FfiCreateOfStructOrUnion/analyzerCode: Fail
|
||||
FfiDartTypeMismatch/analyzerCode: Fail
|
||||
FfiDeeplyImmutableClassesMustBeFinalOrSealed/analyzerCode: Fail
|
||||
FfiDeeplyImmutableFieldsModifiers/analyzerCode: Fail
|
||||
FfiDeeplyImmutableFieldsMustBeDeeplyImmutable/analyzerCode: Fail
|
||||
FfiDeeplyImmutableSubtypesMustBeDeeplyImmutable/analyzerCode: Fail
|
||||
FfiDeeplyImmutableSupertypeMustBeDeeplyImmutable/analyzerCode: Fail
|
||||
FfiEmptyStruct/analyzerCode: Fail
|
||||
FfiExceptionalReturnNull/analyzerCode: Fail
|
||||
FfiExpectedConstant/analyzerCode: Fail
|
||||
|
|
|
@ -5165,6 +5165,36 @@ FfiDartTypeMismatch:
|
|||
problemMessage: "Expected '#type' to be a subtype of '#type2'."
|
||||
external: test/ffi_test.dart
|
||||
|
||||
FfiDeeplyImmutableClassesMustBeFinalOrSealed:
|
||||
# Used by dart:ffi
|
||||
problemMessage: 'Deeply immutable classes must be final or sealed.'
|
||||
correctionMessage: 'Try marking this class as final or sealed.'
|
||||
external: test/ffi_test.dart
|
||||
|
||||
FfiDeeplyImmutableFieldsMustBeDeeplyImmutable:
|
||||
# Used by dart:ffi
|
||||
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')`."
|
||||
correctionMessage: 'Try changing the type of this field to a deeply immutable type or mark the type of this field as deeply immutable.'
|
||||
external: test/ffi_test.dart
|
||||
|
||||
FfiDeeplyImmutableFieldsModifiers:
|
||||
# Used by dart:ffi
|
||||
problemMessage: 'Deeply immutable classes must only have final non-late instance fields.'
|
||||
correctionMessage: "Add the 'final' modifier to this field, and remove 'late' modifier from this field."
|
||||
external: test/ffi_test.dart
|
||||
|
||||
FfiDeeplyImmutableSubtypesMustBeDeeplyImmutable:
|
||||
# Used by dart:ffi
|
||||
problemMessage: 'Subtypes of deeply immutable classes must be deeply immutable.'
|
||||
correctionMessage: "Try marking this class deeply immutable by adding `@pragma('vm:deeply-immutable')`."
|
||||
external: test/ffi_test.dart
|
||||
|
||||
FfiDeeplyImmutableSupertypeMustBeDeeplyImmutable:
|
||||
# Used by dart:ffi
|
||||
problemMessage: 'The super type of deeply immutable classes must be deeply immutable.'
|
||||
correctionMessage: "Try marking the super class deeply immutable by adding `@pragma('vm:deeply-immutable')`."
|
||||
external: test/ffi_test.dart
|
||||
|
||||
FfiDefaultAssetDuplicate:
|
||||
# Used by dart:ffi
|
||||
problemMessage: "There may be at most one @DefaultAsset annotation on a library."
|
||||
|
|
|
@ -45,6 +45,7 @@ dart:js_interop
|
|||
dart:js_interop_unsafe
|
||||
dart_runner
|
||||
dartbug.com
|
||||
deeply
|
||||
defaultasset
|
||||
dname
|
||||
e.g
|
||||
|
@ -55,6 +56,8 @@ extensiontype
|
|||
f
|
||||
ffi
|
||||
finality
|
||||
float32x
|
||||
float64x
|
||||
flutter_runner
|
||||
function.tojs
|
||||
futureor
|
||||
|
@ -63,6 +66,7 @@ guarded
|
|||
guides
|
||||
h
|
||||
https
|
||||
int32x
|
||||
interact
|
||||
interop
|
||||
intervening
|
||||
|
@ -79,6 +83,7 @@ loadlibrary
|
|||
macro
|
||||
member(s)
|
||||
migrate
|
||||
modifier
|
||||
mocking
|
||||
n
|
||||
name.#name
|
||||
|
@ -106,6 +111,7 @@ patterns
|
|||
placing
|
||||
pointer`s
|
||||
pragma
|
||||
pragma('vm:deeply
|
||||
preexisting
|
||||
pubspec.yaml
|
||||
r
|
||||
|
|
|
@ -11,6 +11,7 @@ import 'package:kernel/target/changed_structure_notifier.dart';
|
|||
import 'package:kernel/target/targets.dart';
|
||||
|
||||
import '../transformations/call_site_annotator.dart' as callSiteAnnotator;
|
||||
import '../transformations/deeply_immutable.dart' as deeply_immutable;
|
||||
import '../transformations/lowering.dart' as lowering
|
||||
show transformLibraries, transformProcedure;
|
||||
import '../transformations/mixin_full_resolution.dart' as transformMixins
|
||||
|
@ -151,6 +152,13 @@ class VmTarget extends Target {
|
|||
ReferenceFromIndex? referenceFromIndex,
|
||||
{void Function(String msg)? logger,
|
||||
ChangedStructureNotifier? changedStructureNotifier}) {
|
||||
deeply_immutable.validateLibraries(
|
||||
libraries,
|
||||
coreTypes,
|
||||
diagnosticReporter,
|
||||
);
|
||||
logger?.call("Validated deeply immutable");
|
||||
|
||||
transformMixins.transformLibraries(
|
||||
this, coreTypes, hierarchy, libraries, referenceFromIndex);
|
||||
logger?.call("Transformed mixin applications");
|
||||
|
|
156
pkg/vm/lib/modular/transformations/deeply_immutable.dart
Normal file
156
pkg/vm/lib/modular/transformations/deeply_immutable.dart
Normal file
|
@ -0,0 +1,156 @@
|
|||
// 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.
|
||||
|
||||
import 'package:front_end/src/fasta/codes/fasta_codes.dart'
|
||||
show
|
||||
messageFfiDeeplyImmutableClassesMustBeFinalOrSealed,
|
||||
messageFfiDeeplyImmutableFieldsModifiers,
|
||||
messageFfiDeeplyImmutableFieldsMustBeDeeplyImmutable,
|
||||
messageFfiDeeplyImmutableSubtypesMustBeDeeplyImmutable,
|
||||
messageFfiDeeplyImmutableSupertypeMustBeDeeplyImmutable;
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:kernel/core_types.dart';
|
||||
import 'package:kernel/target/targets.dart' show DiagnosticReporter;
|
||||
|
||||
void validateLibraries(
|
||||
List<Library> libraries,
|
||||
CoreTypes coreTypes,
|
||||
DiagnosticReporter diagnosticReporter,
|
||||
) {
|
||||
final validator = DeeplyImmutableValidator(
|
||||
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;
|
||||
|
||||
DeeplyImmutableValidator(
|
||||
this.coreTypes,
|
||||
this.diagnosticReporter,
|
||||
) : pragmaClass = coreTypes.pragmaClass,
|
||||
pragmaName = coreTypes.pragmaName;
|
||||
|
||||
void visitLibrary(Library library) {
|
||||
for (final cls in library.classes) {
|
||||
visitClass(cls);
|
||||
}
|
||||
}
|
||||
|
||||
void visitClass(Class node) {
|
||||
_validateDeeplyImmutable(node);
|
||||
}
|
||||
|
||||
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(
|
||||
messageFfiDeeplyImmutableSubtypesMustBeDeeplyImmutable,
|
||||
node.fileOffset,
|
||||
node.name.length,
|
||||
node.location!.file,
|
||||
);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final superClass = node.superclass;
|
||||
if (superClass != null && superClass != coreTypes.objectClass) {
|
||||
if (!_isDeeplyImmutableClass(superClass)) {
|
||||
diagnosticReporter.report(
|
||||
messageFfiDeeplyImmutableSupertypeMustBeDeeplyImmutable,
|
||||
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.)
|
||||
if (!(node.isFinal || node.isSealed)) {
|
||||
diagnosticReporter.report(
|
||||
messageFfiDeeplyImmutableClassesMustBeFinalOrSealed,
|
||||
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(
|
||||
messageFfiDeeplyImmutableFieldsMustBeDeeplyImmutable,
|
||||
field.fileOffset,
|
||||
field.name.text.length,
|
||||
field.location!.file,
|
||||
);
|
||||
}
|
||||
if (!field.isFinal || field.isLate) {
|
||||
diagnosticReporter.report(
|
||||
messageFfiDeeplyImmutableFieldsModifiers,
|
||||
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;
|
||||
}
|
||||
}
|
79
runtime/docs/deeply_immutable.md
Normal file
79
runtime/docs/deeply_immutable.md
Normal file
|
@ -0,0 +1,79 @@
|
|||
# Deeply immutable instances and types
|
||||
|
||||
The Dart VM has a concept of deeply immutable instances.
|
||||
|
||||
Deeply immutable instances can be shared across isolates within the same group.
|
||||
|
||||
## Deeply immutable types
|
||||
|
||||
A deeply immutable type is a type for which all instances that have this type are deeply immutable.
|
||||
|
||||
This is useful for static checks on classes annotated `@pragma('vm:deeply-immutable')`.
|
||||
All the instance fields of such classes must have a deeply immutable type.
|
||||
|
||||
A list of immutable types:
|
||||
|
||||
* `bool`
|
||||
* `double`
|
||||
* `int`
|
||||
* `Null`
|
||||
* `String`
|
||||
* `Float32x4`
|
||||
* `Float64x2`
|
||||
* `Int32x4`
|
||||
* `Pointer`
|
||||
* classes annotated with `@pragma('vm:deeply-immutable')`
|
||||
* type parameters bound by a deeply immutable type
|
||||
|
||||
## Deeply immutable instances without a deeply immutable type
|
||||
|
||||
In addition to instances from deeply immutable types,
|
||||
instances can also be deeply immutable while their type is not deeply immutable:
|
||||
|
||||
* `SendPort` (implemented externally `package:isolate`, so cannot be `final` https://github.com/dart-lang/sdk/issues/54885#issuecomment-1967329435)
|
||||
* `Capability` (has `SendPort` as subtype so cannot be `final`)
|
||||
* `RegExp` (can be implemented externally, not `final`)
|
||||
* `StackTrace` (can be implemented externally, not `final`)
|
||||
* `Type` (can be implemented externally, not `final`)
|
||||
* const object (the class can be deeply immutable)
|
||||
|
||||
This means users cannot mark classes with fields typed with these types as `@pragma('vm:deeply-immutable')`.
|
||||
|
||||
## Shallowly immutable instances
|
||||
|
||||
The VM also has shallow immutability.
|
||||
|
||||
* unmodifiable typed data views (the backing view might not be immutable)
|
||||
* closures (the context might not be empty)
|
||||
|
||||
## Implementation details
|
||||
|
||||
### Deeply and shallowly immutable instances
|
||||
|
||||
The `UntaggedObject::ImmutableBit` tracks whether an instance is deeply or shallowly immutable at runtime.
|
||||
For shallow immutable objects, the VM needs to know the layout and what to check when to check for to check deep immutability at runtime.
|
||||
|
||||
### Deeply immutable types
|
||||
|
||||
The `Class::is_deeply_immutable` tracks whether all instances of a class are deeply immutable.
|
||||
|
||||
This bit can be set in two ways:
|
||||
|
||||
1. For recognized classes, in the VM initialization.
|
||||
2. For classes with a Dart source, with the `vm:deeply-immutable` pragma.
|
||||
|
||||
The `vm:deeply-immutable` pragma is added to classes of which their _type_ is deeply immutable.
|
||||
|
||||
This puts the following restrictions on these classes:
|
||||
|
||||
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).
|
||||
|
||||
These restructions are enforced by [DeeplyImmutableValidator](../../pkg/vm/lib/transformations/ffi/deeply_immutable.dart).
|
|
@ -19,6 +19,7 @@ These pragmas are part of the VM's API and are safe for use in external code.
|
|||
| `weak-tearoff-reference` | [Declaring a static weak reference intrinsic method.](compiler/pragmas_recognized_by_compiler.md#declaring-a-static-weak-reference-intrinsic-method) |
|
||||
| `vm:isolate-unsendable` | Marks a class, instances of which won't be allowed to be passed through ports or sent between isolates. |
|
||||
| `vm:awaiter-link` | [Specifying variable to follow for awaiter stack unwinding](awaiter_stack_traces.md) |
|
||||
| `vm:deeply-immutable` | [Specifying a class and all its subtypes are deeply immutable](deeply_immutable.md) |
|
||||
|
||||
## Unsafe pragmas for general use
|
||||
|
||||
|
|
|
@ -101,6 +101,30 @@ final sharableObjects = [
|
|||
Float64x2(1.0, 2.0),
|
||||
StackTrace.current,
|
||||
Pointer<Int8>.fromAddress(0xdeadbeef),
|
||||
DeeplyImmutable(
|
||||
someString: 'someString',
|
||||
someNullableString: 'someString',
|
||||
someInt: 3,
|
||||
someDouble: 3.3,
|
||||
someBool: false,
|
||||
someNull: null,
|
||||
someInt32x4: Int32x4(0, 1, 2, 3),
|
||||
someFloat32x4: Float32x4(0.0, 1.1, 2.2, 3.3),
|
||||
someFloat64x2: Float64x2(4.4, 5.5),
|
||||
someDeeplyImmutable: DeeplyImmutable(
|
||||
someString: 'someString',
|
||||
someInt: 3,
|
||||
someDouble: 3.3,
|
||||
someBool: false,
|
||||
someNull: null,
|
||||
someInt32x4: Int32x4(0, 1, 2, 3),
|
||||
someFloat32x4: Float32x4(0.0, 1.1, 2.2, 3.3),
|
||||
someFloat64x2: Float64x2(4.4, 5.5),
|
||||
someDeeplyImmutable: null,
|
||||
somePointer: Pointer.fromAddress(0x8badf00d),
|
||||
),
|
||||
somePointer: Pointer.fromAddress(0xdeadbeef),
|
||||
),
|
||||
];
|
||||
|
||||
final copyableClosures = <dynamic>[
|
||||
|
@ -225,3 +249,32 @@ void msanUnpoison(Pointer<Uint8> pointer, int size) {
|
|||
pointer.cast(), size);
|
||||
}
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class DeeplyImmutable {
|
||||
final String someString;
|
||||
final String? someNullableString;
|
||||
final int someInt;
|
||||
final double someDouble;
|
||||
final bool someBool;
|
||||
final Null someNull;
|
||||
final Int32x4 someInt32x4;
|
||||
final Float32x4 someFloat32x4;
|
||||
final Float64x2 someFloat64x2;
|
||||
final DeeplyImmutable? someDeeplyImmutable;
|
||||
final Pointer somePointer;
|
||||
|
||||
DeeplyImmutable({
|
||||
required this.someString,
|
||||
this.someNullableString,
|
||||
required this.someInt,
|
||||
required this.someDouble,
|
||||
required this.someBool,
|
||||
required this.someNull,
|
||||
required this.someInt32x4,
|
||||
required this.someFloat32x4,
|
||||
required this.someFloat64x2,
|
||||
this.someDeeplyImmutable,
|
||||
required this.somePointer,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -879,6 +879,7 @@ void Deserializer::InitializeHeader(ObjectPtr raw,
|
|||
tags = UntaggedObject::NotMarkedBit::update(true, tags);
|
||||
tags = UntaggedObject::OldAndNotRememberedBit::update(true, tags);
|
||||
tags = UntaggedObject::NewBit::update(false, tags);
|
||||
// TODO(https://dartbug.com/55136): Initialize the ImmutableBit.
|
||||
raw->untag()->tags_ = tags;
|
||||
}
|
||||
|
||||
|
|
|
@ -309,7 +309,7 @@ inline bool IsInternalOnlyClassId(intptr_t index) {
|
|||
return index <= kLastInternalOnlyCid;
|
||||
}
|
||||
|
||||
// Make sure this function is updated when new Error types are added.
|
||||
// Make sure this function is updated when new Error types are added.
|
||||
static const ClassId kFirstErrorCid = kErrorCid;
|
||||
static const ClassId kLastErrorCid = kUnwindErrorCid;
|
||||
COMPILE_ASSERT(kFirstErrorCid == kErrorCid &&
|
||||
|
@ -468,15 +468,41 @@ inline bool IsUnmodifiableTypedDataViewClassId(intptr_t index) {
|
|||
kTypedDataCidRemainderUnmodifiable);
|
||||
}
|
||||
|
||||
inline bool ShouldHaveImmutabilityBitSet(intptr_t index) {
|
||||
return IsUnmodifiableTypedDataViewClassId(index) || IsStringClassId(index) ||
|
||||
index == kMintCid || index == kNeverCid || index == kSentinelCid ||
|
||||
index == kStackTraceCid || index == kDoubleCid ||
|
||||
index == kFloat32x4Cid || index == kFloat64x2Cid ||
|
||||
index == kInt32x4Cid || index == kSendPortCid ||
|
||||
index == kCapabilityCid || index == kRegExpCid || index == kBoolCid ||
|
||||
index == kNullCid || index == kPointerCid || index == kTypeCid ||
|
||||
index == kRecordTypeCid || index == kFunctionTypeCid;
|
||||
// For predefined cids only. Refer to Class::is_deeply_immutable for
|
||||
// instances of non-predefined classes.
|
||||
//
|
||||
// Having the `@pragma('vm:deeply-immutable')`, which means statically proven
|
||||
// deeply immutable, implies true for this function. The other way around is not
|
||||
// guaranteed, predefined classes can be marked deeply immutable in the VM while
|
||||
// not having their subtypes or super type being deeply immutable.
|
||||
//
|
||||
// Keep consistent with runtime/docs/deeply_immutable.md.
|
||||
inline bool IsDeeplyImmutableCid(intptr_t predefined_cid) {
|
||||
ASSERT(predefined_cid < kNumPredefinedCids);
|
||||
return IsStringClassId(predefined_cid) || predefined_cid == kNumberCid ||
|
||||
predefined_cid == kIntegerCid || predefined_cid == kSmiCid ||
|
||||
predefined_cid == kMintCid || predefined_cid == kNeverCid ||
|
||||
predefined_cid == kSentinelCid || predefined_cid == kStackTraceCid ||
|
||||
predefined_cid == kDoubleCid || predefined_cid == kFloat32x4Cid ||
|
||||
predefined_cid == kFloat64x2Cid || predefined_cid == kInt32x4Cid ||
|
||||
predefined_cid == kSendPortCid || predefined_cid == kCapabilityCid ||
|
||||
predefined_cid == kRegExpCid || predefined_cid == kBoolCid ||
|
||||
predefined_cid == kNullCid || predefined_cid == kPointerCid ||
|
||||
predefined_cid == kTypeCid || predefined_cid == kRecordTypeCid ||
|
||||
predefined_cid == kFunctionTypeCid;
|
||||
}
|
||||
|
||||
inline bool IsShallowlyImmutableCid(intptr_t predefined_cid) {
|
||||
ASSERT(predefined_cid < kNumPredefinedCids);
|
||||
// TODO(https://dartbug.com/55136): Mark kClosureCid as shallowly imutable.
|
||||
return IsUnmodifiableTypedDataViewClassId(predefined_cid);
|
||||
}
|
||||
|
||||
// See documentation on ImmutableBit in raw_object.h
|
||||
inline bool ShouldHaveImmutabilityBitSetCid(intptr_t predefined_cid) {
|
||||
ASSERT(predefined_cid < kNumPredefinedCids);
|
||||
return IsDeeplyImmutableCid(predefined_cid) ||
|
||||
IsShallowlyImmutableCid(predefined_cid);
|
||||
}
|
||||
|
||||
inline bool IsFfiTypeClassId(intptr_t index) {
|
||||
|
|
|
@ -363,7 +363,7 @@ uword MakeTagWordForNewSpaceObject(classid_t cid, uword instance_size) {
|
|||
dart::UntaggedObject::AlwaysSetBit::encode(true) |
|
||||
dart::UntaggedObject::NotMarkedBit::encode(true) |
|
||||
dart::UntaggedObject::ImmutableBit::encode(
|
||||
ShouldHaveImmutabilityBitSet(cid));
|
||||
dart::Object::ShouldHaveImmutabilityBitSet(cid));
|
||||
}
|
||||
|
||||
word Object::tags_offset() {
|
||||
|
|
|
@ -4190,6 +4190,210 @@ TEST_CASE(IsolateReload_ShapeChange_Const_RemoveSlot) {
|
|||
"Library:'file:///test-lib' Class: A");
|
||||
}
|
||||
|
||||
TEST_CASE(IsolateReload_DeeplyImmutableChange) {
|
||||
const char* kScript = R"(
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class A {
|
||||
final int x;
|
||||
A(this.x);
|
||||
}
|
||||
String main () {
|
||||
A(123);
|
||||
return 'okay';
|
||||
}
|
||||
)";
|
||||
|
||||
Dart_Handle lib = TestCase::LoadTestScript(kScript, nullptr);
|
||||
EXPECT_VALID(lib);
|
||||
EXPECT_STREQ("okay", SimpleInvokeStr(lib, "main"));
|
||||
|
||||
const char* kReloadScript = R"(
|
||||
final class A {
|
||||
final int x;
|
||||
A(this.x);
|
||||
}
|
||||
String main () {
|
||||
A(123);
|
||||
return 'okay';
|
||||
}
|
||||
)";
|
||||
|
||||
lib = TestCase::ReloadTestScript(kReloadScript);
|
||||
EXPECT_ERROR(lib,
|
||||
"Classes cannot change their @pragma('vm:deeply-immutable'): "
|
||||
"Library:'file:///test-lib' Class: A");
|
||||
}
|
||||
|
||||
TEST_CASE(IsolateReload_DeeplyImmutableChange_2) {
|
||||
const char* kScript = R"(
|
||||
final class A {
|
||||
final int x;
|
||||
A(this.x);
|
||||
}
|
||||
String main () {
|
||||
A(123);
|
||||
return 'okay';
|
||||
}
|
||||
)";
|
||||
|
||||
Dart_Handle lib = TestCase::LoadTestScript(kScript, nullptr);
|
||||
EXPECT_VALID(lib);
|
||||
EXPECT_STREQ("okay", SimpleInvokeStr(lib, "main"));
|
||||
|
||||
const char* kReloadScript = R"(
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class A {
|
||||
final int x;
|
||||
A(this.x);
|
||||
}
|
||||
String main () {
|
||||
A(123);
|
||||
return 'okay';
|
||||
}
|
||||
)";
|
||||
|
||||
lib = TestCase::ReloadTestScript(kReloadScript);
|
||||
EXPECT_ERROR(lib,
|
||||
"Classes cannot change their @pragma('vm:deeply-immutable'): "
|
||||
"Library:'file:///test-lib' Class: A");
|
||||
}
|
||||
|
||||
TEST_CASE(IsolateReload_DeeplyImmutableChange_MultiLib) {
|
||||
// clang-format off
|
||||
Dart_SourceFile sourcefiles[] = {
|
||||
{
|
||||
"file:///test-app.dart",
|
||||
R"(
|
||||
import 'test-lib.dart';
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class A {
|
||||
final B b;
|
||||
A(this.b);
|
||||
}
|
||||
int main () {
|
||||
A(B(123));
|
||||
return 42;
|
||||
}
|
||||
)",
|
||||
},
|
||||
{
|
||||
"file:///test-lib.dart",
|
||||
R"(
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class B {
|
||||
final int x;
|
||||
B(this.x);
|
||||
}
|
||||
)"
|
||||
}};
|
||||
// clang-format on
|
||||
|
||||
Dart_Handle lib = TestCase::LoadTestScriptWithDFE(
|
||||
sizeof(sourcefiles) / sizeof(Dart_SourceFile), sourcefiles,
|
||||
nullptr /* resolver */, true /* finalize */, true /* incrementally */);
|
||||
EXPECT_VALID(lib);
|
||||
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, nullptr);
|
||||
int64_t value = 0;
|
||||
result = Dart_IntegerToInt64(result, &value);
|
||||
EXPECT_VALID(result);
|
||||
EXPECT_EQ(42, value);
|
||||
|
||||
// clang-format off
|
||||
Dart_SourceFile updated_sourcefiles[] = {
|
||||
{
|
||||
"file:///test-lib.dart",
|
||||
R"(
|
||||
final class B {
|
||||
final int x;
|
||||
B(this.x);
|
||||
}
|
||||
)"
|
||||
}};
|
||||
// clang-format on
|
||||
|
||||
{
|
||||
const uint8_t* kernel_buffer = nullptr;
|
||||
intptr_t kernel_buffer_size = 0;
|
||||
char* error = TestCase::CompileTestScriptWithDFE(
|
||||
"file:///test-app.dart",
|
||||
sizeof(updated_sourcefiles) / sizeof(Dart_SourceFile),
|
||||
updated_sourcefiles, &kernel_buffer, &kernel_buffer_size,
|
||||
true /* incrementally */);
|
||||
// This is rejected by class A being recompiled and the validator failing.
|
||||
EXPECT(error != nullptr);
|
||||
EXPECT_NULLPTR(kernel_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(IsolateReload_DeeplyImmutableChange_TypeBound) {
|
||||
// clang-format off
|
||||
Dart_SourceFile sourcefiles[] = {
|
||||
{
|
||||
"file:///test-app.dart",
|
||||
R"(
|
||||
import 'test-lib.dart';
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class A<T extends B> {
|
||||
final T b;
|
||||
A(this.b);
|
||||
}
|
||||
int main () {
|
||||
A(B(123));
|
||||
return 42;
|
||||
}
|
||||
)",
|
||||
},
|
||||
{
|
||||
"file:///test-lib.dart",
|
||||
R"(
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class B {
|
||||
final int x;
|
||||
B(this.x);
|
||||
}
|
||||
)"
|
||||
}};
|
||||
// clang-format on
|
||||
|
||||
Dart_Handle lib = TestCase::LoadTestScriptWithDFE(
|
||||
sizeof(sourcefiles) / sizeof(Dart_SourceFile), sourcefiles,
|
||||
nullptr /* resolver */, true /* finalize */, true /* incrementally */);
|
||||
EXPECT_VALID(lib);
|
||||
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, nullptr);
|
||||
int64_t value = 0;
|
||||
result = Dart_IntegerToInt64(result, &value);
|
||||
EXPECT_VALID(result);
|
||||
EXPECT_EQ(42, value);
|
||||
|
||||
// clang-format off
|
||||
Dart_SourceFile updated_sourcefiles[] = {
|
||||
{
|
||||
"file:///test-lib.dart",
|
||||
R"(
|
||||
final class B {
|
||||
final int x;
|
||||
B(this.x);
|
||||
}
|
||||
)"
|
||||
}};
|
||||
// clang-format on
|
||||
|
||||
{
|
||||
const uint8_t* kernel_buffer = nullptr;
|
||||
intptr_t kernel_buffer_size = 0;
|
||||
char* error = TestCase::CompileTestScriptWithDFE(
|
||||
"file:///test-app.dart",
|
||||
sizeof(updated_sourcefiles) / sizeof(Dart_SourceFile),
|
||||
updated_sourcefiles, &kernel_buffer, &kernel_buffer_size,
|
||||
true /* incrementally */);
|
||||
// This is rejected by class A being recompiled and the validator failing.
|
||||
EXPECT(error != nullptr);
|
||||
EXPECT_NULLPTR(kernel_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(IsolateReload_ConstToNonConstClass) {
|
||||
const char* kScript = R"(
|
||||
class A {
|
||||
|
|
|
@ -1355,6 +1355,13 @@ void KernelLoader::LoadClass(const Library& library,
|
|||
if (IsolateUnsendablePragma::decode(pragma_bits)) {
|
||||
out_class->set_is_isolate_unsendable_due_to_pragma(true);
|
||||
}
|
||||
if (DeeplyImmutablePragma::decode(pragma_bits)) {
|
||||
out_class->set_is_deeply_immutable(true);
|
||||
// Ensure that the pragma implies deeply immutability for VM recognized
|
||||
// classes.
|
||||
ASSERT(out_class->id() >= kNumPredefinedCids ||
|
||||
IsDeeplyImmutableCid(out_class->id()));
|
||||
}
|
||||
if (HasPragma::decode(pragma_bits)) {
|
||||
out_class->set_has_pragma(true);
|
||||
}
|
||||
|
@ -1754,6 +1761,10 @@ void KernelLoader::ReadVMAnnotations(const Library& library,
|
|||
"vm:isolate-unsendable")) {
|
||||
*pragma_bits = IsolateUnsendablePragma::update(true, *pragma_bits);
|
||||
}
|
||||
if (constant_reader.IsStringConstant(name_index,
|
||||
"vm:deeply-immutable")) {
|
||||
*pragma_bits = DeeplyImmutablePragma::update(true, *pragma_bits);
|
||||
}
|
||||
if (constant_reader.IsStringConstant(name_index, "vm:ffi:native")) {
|
||||
*pragma_bits = FfiNativePragma::update(true, *pragma_bits);
|
||||
}
|
||||
|
|
|
@ -220,8 +220,10 @@ class KernelLoader : public ValueObject {
|
|||
BitField<uint32_t, bool, ExternalNamePragma::kNextBit, 1>;
|
||||
using IsolateUnsendablePragma =
|
||||
BitField<uint32_t, bool, InvisibleFunctionPragma::kNextBit, 1>;
|
||||
using FfiNativePragma =
|
||||
using DeeplyImmutablePragma =
|
||||
BitField<uint32_t, bool, IsolateUnsendablePragma::kNextBit, 1>;
|
||||
using FfiNativePragma =
|
||||
BitField<uint32_t, bool, DeeplyImmutablePragma::kNextBit, 1>;
|
||||
|
||||
void FinishTopLevelClassLoading(const Class& toplevel_class,
|
||||
const Library& library,
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "vm/bootstrap.h"
|
||||
#include "vm/canonical_tables.h"
|
||||
#include "vm/class_finalizer.h"
|
||||
#include "vm/class_id.h"
|
||||
#include "vm/closure_functions_cache.h"
|
||||
#include "vm/code_comments.h"
|
||||
#include "vm/code_descriptors.h"
|
||||
|
@ -2711,6 +2712,15 @@ StringPtr Object::DictionaryName() const {
|
|||
return String::null();
|
||||
}
|
||||
|
||||
bool Object::ShouldHaveImmutabilityBitSet(classid_t class_id) {
|
||||
if (class_id < kNumPredefinedCids) {
|
||||
return ShouldHaveImmutabilityBitSetCid(class_id);
|
||||
} else {
|
||||
return Class::IsDeeplyImmutable(
|
||||
IsolateGroup::Current()->class_table()->At(class_id));
|
||||
}
|
||||
}
|
||||
|
||||
void Object::InitializeObject(uword address,
|
||||
intptr_t class_id,
|
||||
intptr_t size,
|
||||
|
@ -2814,7 +2824,7 @@ void Object::InitializeObject(uword address,
|
|||
tags = UntaggedObject::OldAndNotRememberedBit::update(is_old, tags);
|
||||
tags = UntaggedObject::NewBit::update(!is_old, tags);
|
||||
tags = UntaggedObject::ImmutableBit::update(
|
||||
ShouldHaveImmutabilityBitSet(class_id), tags);
|
||||
Object::ShouldHaveImmutabilityBitSet(class_id), tags);
|
||||
#if defined(HASH_IN_OBJECT_HEADER)
|
||||
tags = UntaggedObject::HashTag::update(0, tags);
|
||||
#endif
|
||||
|
@ -3164,6 +3174,10 @@ ClassPtr Class::New(IsolateGroup* isolate_group, bool register_class) {
|
|||
// references, but do not recompute size.
|
||||
result.set_is_prefinalized();
|
||||
}
|
||||
if (FakeObject::kClassId < kNumPredefinedCids &&
|
||||
IsDeeplyImmutableCid(FakeObject::kClassId)) {
|
||||
result.set_is_deeply_immutable(true);
|
||||
}
|
||||
NOT_IN_PRECOMPILED(result.set_kernel_offset(0));
|
||||
result.InitEmptyFields();
|
||||
if (register_class) {
|
||||
|
@ -3218,6 +3232,11 @@ void Class::set_is_isolate_unsendable_due_to_pragma(bool value) const {
|
|||
IsIsolateUnsendableDueToPragmaBit::update(value, state_bits()));
|
||||
}
|
||||
|
||||
void Class::set_is_deeply_immutable(bool value) const {
|
||||
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
|
||||
set_state_bits(IsDeeplyImmutableBit::update(value, state_bits()));
|
||||
}
|
||||
|
||||
void Class::set_is_future_subtype(bool value) const {
|
||||
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
|
||||
set_state_bits(IsFutureSubtypeBit::update(value, state_bits()));
|
||||
|
@ -5322,6 +5341,8 @@ ClassPtr Class::NewStringClass(intptr_t class_id, IsolateGroup* isolate_group) {
|
|||
result.set_next_field_offset(host_next_field_offset,
|
||||
target_next_field_offset);
|
||||
result.set_is_prefinalized();
|
||||
ASSERT(IsDeeplyImmutableCid(class_id));
|
||||
result.set_is_deeply_immutable(true);
|
||||
isolate_group->class_table()->Register(result);
|
||||
return result.ptr();
|
||||
}
|
||||
|
|
|
@ -657,6 +657,8 @@ class Object {
|
|||
kNo,
|
||||
};
|
||||
|
||||
static bool ShouldHaveImmutabilityBitSet(classid_t class_id);
|
||||
|
||||
protected:
|
||||
friend ObjectPtr AllocateObject(intptr_t, intptr_t, intptr_t);
|
||||
|
||||
|
@ -2062,9 +2064,19 @@ class Class : public Object {
|
|||
// - super class / super interface classes are marked as unsendable.
|
||||
// - class has native fields.
|
||||
kIsIsolateUnsendableBit,
|
||||
// True if this class has `@pragma('vm:isolate-unsendable') annotation or
|
||||
// True if this class has `@pragma('vm:isolate-unsendable')` annotation or
|
||||
// base class or implemented interfaces has this bit.
|
||||
kIsIsolateUnsendableDueToPragmaBit,
|
||||
// Will be set to 1 for the following classes:
|
||||
//
|
||||
// 1. Deeply immutable class.
|
||||
// a. Statically guaranteed deeply immutable classes.
|
||||
// `@pragma('vm:deeply-immutable')`.
|
||||
// b. VM recognized deeply immutable classes.
|
||||
// `IsDeeplyImmutableCid(intptr_t predefined_cid)`.
|
||||
//
|
||||
// See also ImmutableBit in raw_object.h.
|
||||
kIsDeeplyImmutableBit,
|
||||
// This class is a subtype of Future.
|
||||
kIsFutureSubtypeBit,
|
||||
// This class has a non-abstract subtype which is a subtype of Future.
|
||||
|
@ -2104,6 +2116,8 @@ class Class : public Object {
|
|||
class IsIsolateUnsendableDueToPragmaBit
|
||||
: public BitField<uint32_t, bool, kIsIsolateUnsendableDueToPragmaBit, 1> {
|
||||
};
|
||||
class IsDeeplyImmutableBit
|
||||
: public BitField<uint32_t, bool, kIsDeeplyImmutableBit, 1> {};
|
||||
class IsFutureSubtypeBit
|
||||
: public BitField<uint32_t, bool, kIsFutureSubtypeBit, 1> {};
|
||||
class CanBeFutureBit : public BitField<uint32_t, bool, kCanBeFutureBit, 1> {};
|
||||
|
@ -2159,6 +2173,14 @@ class Class : public Object {
|
|||
return IsIsolateUnsendableDueToPragmaBit::decode(state_bits());
|
||||
}
|
||||
|
||||
void set_is_deeply_immutable(bool value) const;
|
||||
bool is_deeply_immutable() const {
|
||||
return IsDeeplyImmutableBit::decode(state_bits());
|
||||
}
|
||||
static bool IsDeeplyImmutable(ClassPtr clazz) {
|
||||
return IsDeeplyImmutableBit::decode(clazz->untag()->state_bits_);
|
||||
}
|
||||
|
||||
void set_is_future_subtype(bool value) const;
|
||||
bool is_future_subtype() const {
|
||||
ASSERT(is_type_finalized());
|
||||
|
|
|
@ -160,10 +160,13 @@ static bool CanShareObject(ObjectPtr obj, uword tags) {
|
|||
->untag()
|
||||
->IsImmutable();
|
||||
}
|
||||
|
||||
// All other objects that have immutability bit set are deeply immutable.
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO(https://dartbug.com/55136): Mark Closures as shallowly imutable.
|
||||
// And move this into the if above.
|
||||
if (cid == kClosureCid) {
|
||||
// We can share a closure iff it doesn't close over any state.
|
||||
return Closure::RawCast(obj)->untag()->context() == Object::null();
|
||||
|
|
|
@ -374,6 +374,19 @@ class EnsureFinalizedError : public ClassReasonForCancelling {
|
|||
StringPtr ToString() { return String::New(error_.ToErrorCString()); }
|
||||
};
|
||||
|
||||
class DeeplyImmutableChange : public ClassReasonForCancelling {
|
||||
public:
|
||||
DeeplyImmutableChange(Zone* zone, const Class& from, const Class& to)
|
||||
: ClassReasonForCancelling(zone, from, to) {}
|
||||
|
||||
private:
|
||||
StringPtr ToString() {
|
||||
return String::NewFormatted(
|
||||
"Classes cannot change their @pragma('vm:deeply-immutable'): %s",
|
||||
from_.ToCString());
|
||||
}
|
||||
};
|
||||
|
||||
class ConstToNonConstClass : public ClassReasonForCancelling {
|
||||
public:
|
||||
ConstToNonConstClass(Zone* zone, const Class& from, const Class& to)
|
||||
|
@ -512,6 +525,13 @@ void Class::CheckReload(const Class& replacement,
|
|||
TIR_Print("Finalized replacement class for %s\n", ToCString());
|
||||
}
|
||||
|
||||
if (is_deeply_immutable() != replacement.is_deeply_immutable()) {
|
||||
context->group_reload_context()->AddReasonForCancelling(
|
||||
new (context->zone())
|
||||
DeeplyImmutableChange(context->zone(), *this, replacement));
|
||||
return; // No reason to check other properties.
|
||||
}
|
||||
|
||||
if (is_finalized() && is_const() && (constants() != Array::null()) &&
|
||||
(Array::LengthOf(constants()) > 0)) {
|
||||
// Consts can't become non-consts.
|
||||
|
|
|
@ -255,9 +255,27 @@ class UntaggedObject {
|
|||
class OldAndNotRememberedBit
|
||||
: public BitField<uword, bool, kOldAndNotRememberedBit, 1> {};
|
||||
|
||||
// Will be set to 1 iff
|
||||
// - is unmodifiable typed data view (backing store may be mutable)
|
||||
// - is transitively immutable
|
||||
// Will be set to 1 for the following instances:
|
||||
//
|
||||
// 1. Deeply immutable instances.
|
||||
// `Class::is_deeply_immutable`.
|
||||
// a. Statically guaranteed deeply immutable instances.
|
||||
// `@pragma('vm:deeply-immutable')`.
|
||||
// b. VM recognized deeply immutable instances.
|
||||
// `IsDeeplyImmutableCid(intptr_t predefined_cid)`.
|
||||
// 2. Shallowly unmodifiable instances.
|
||||
// `IsShallowlyImmutableCid(intptr_t predefined_cid)`
|
||||
// a. Unmodifiable typed data view (backing store may be mutable).
|
||||
// b. Closures (the context may be modifiable).
|
||||
//
|
||||
// The bit is used in `CanShareObject` in object_graph_copy, where special
|
||||
// care is taken to look at the shallow immutable instances. Shallow immutable
|
||||
// instances always need special care in the VM because the VM needs to know
|
||||
// what their fields are.
|
||||
//
|
||||
// The bit is also used to make typed data stores efficient. 2.a.
|
||||
//
|
||||
// See also Class::kIsDeeplyImmutableBit.
|
||||
class ImmutableBit : public BitField<uword, bool, kImmutableBit, 1> {};
|
||||
|
||||
class ReservedBit : public BitField<uword, intptr_t, kReservedBit, 1> {};
|
||||
|
|
|
@ -84,6 +84,7 @@ part "uri_patch.dart";
|
|||
part "weak_property.dart";
|
||||
|
||||
@patch
|
||||
@pragma('vm:deeply-immutable')
|
||||
class num {
|
||||
num _addFromInteger(int other);
|
||||
num _subFromInteger(int other);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
part of "core_patch.dart";
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
@pragma("vm:entry-point")
|
||||
final class _Double implements double {
|
||||
@pragma("vm:recognized", "asm-intrinsic")
|
||||
|
|
|
@ -7,6 +7,7 @@ part of "core_patch.dart";
|
|||
// VM implementation of double.
|
||||
|
||||
@patch
|
||||
@pragma('vm:deeply-immutable')
|
||||
@pragma("vm:entry-point")
|
||||
class double {
|
||||
@pragma("vm:external-name", "Double_parse")
|
||||
|
|
|
@ -198,6 +198,7 @@ external dynamic _nativeIsolateLocalCallbackFunction<NS extends Function>(
|
|||
dynamic exceptionalReturn);
|
||||
|
||||
@patch
|
||||
@pragma('vm:deeply-immutable')
|
||||
@pragma("vm:entry-point")
|
||||
final class Pointer<T extends NativeType> implements SizedNativeType {
|
||||
@patch
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
part of "core_patch.dart";
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
abstract final class _IntegerImplementation implements int {
|
||||
@pragma("vm:recognized", "graph-intrinsic")
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
|
@ -552,6 +553,7 @@ abstract final class _IntegerImplementation implements int {
|
|||
}
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
@pragma("vm:entry-point")
|
||||
final class _Smi extends _IntegerImplementation {
|
||||
factory _Smi._uninstantiable() {
|
||||
|
@ -754,6 +756,7 @@ final class _Smi extends _IntegerImplementation {
|
|||
}
|
||||
|
||||
// Represents integers that cannot be represented by Smi but fit into 64bits.
|
||||
@pragma('vm:deeply-immutable')
|
||||
@pragma("vm:entry-point")
|
||||
final class _Mint extends _IntegerImplementation {
|
||||
factory _Mint._uninstantiable() {
|
||||
|
|
|
@ -10,6 +10,7 @@ const int _maxUtf16 = 0xffff;
|
|||
const int _maxUnicode = 0x10ffff;
|
||||
|
||||
@patch
|
||||
@pragma('vm:deeply-immutable')
|
||||
@pragma("vm:entry-point")
|
||||
class String {
|
||||
@patch
|
||||
|
@ -59,6 +60,7 @@ class String {
|
|||
* [_StringBase] contains common methods used by concrete String
|
||||
* implementations, e.g., _OneByteString.
|
||||
*/
|
||||
@pragma('vm:deeply-immutable')
|
||||
abstract final class _StringBase implements String {
|
||||
bool _isWhitespace(int codeUnit);
|
||||
|
||||
|
@ -997,6 +999,7 @@ int _clampedPositiveProduct(int a, int b) {
|
|||
return product;
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
@pragma("vm:entry-point")
|
||||
final class _OneByteString extends _StringBase {
|
||||
factory _OneByteString._uninstantiable() {
|
||||
|
@ -1335,6 +1338,7 @@ final class _OneByteString extends _StringBase {
|
|||
}
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
@pragma("vm:entry-point")
|
||||
final class _TwoByteString extends _StringBase {
|
||||
factory _TwoByteString._uninstantiable() {
|
||||
|
@ -1397,6 +1401,7 @@ final class _TwoByteString extends _StringBase {
|
|||
}
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
@pragma("vm:entry-point")
|
||||
final class _ExternalOneByteString extends _StringBase {
|
||||
factory _ExternalOneByteString._uninstantiable() {
|
||||
|
@ -1417,6 +1422,7 @@ final class _ExternalOneByteString extends _StringBase {
|
|||
}
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
@pragma("vm:entry-point")
|
||||
final class _ExternalTwoByteString extends _StringBase {
|
||||
factory _ExternalTwoByteString._uninstantiable() {
|
||||
|
|
|
@ -3776,6 +3776,7 @@ final class _ExternalFloat64x2Array extends _TypedList
|
|||
}
|
||||
|
||||
@patch
|
||||
@pragma('vm:deeply-immutable')
|
||||
class Float32x4 {
|
||||
@patch
|
||||
@pragma("vm:prefer-inline")
|
||||
|
@ -3824,6 +3825,7 @@ class Float32x4 {
|
|||
external factory Float32x4.fromFloat64x2(Float64x2 v);
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
@pragma("vm:entry-point")
|
||||
final class _Float32x4 implements Float32x4 {
|
||||
@pragma("vm:recognized", "graph-intrinsic")
|
||||
|
@ -3977,6 +3979,7 @@ final class _Float32x4 implements Float32x4 {
|
|||
}
|
||||
|
||||
@patch
|
||||
@pragma('vm:deeply-immutable')
|
||||
class Int32x4 {
|
||||
@patch
|
||||
@pragma("vm:prefer-inline")
|
||||
|
@ -4015,6 +4018,7 @@ class Int32x4 {
|
|||
external factory Int32x4.fromFloat32x4Bits(Float32x4 x);
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
@pragma("vm:entry-point")
|
||||
final class _Int32x4 implements Int32x4 {
|
||||
@pragma("vm:external-name", "Int32x4_or")
|
||||
|
@ -4155,6 +4159,7 @@ final class _Int32x4 implements Int32x4 {
|
|||
}
|
||||
|
||||
@patch
|
||||
@pragma('vm:deeply-immutable')
|
||||
class Float64x2 {
|
||||
@patch
|
||||
@pragma("vm:prefer-inline")
|
||||
|
@ -4194,6 +4199,7 @@ class Float64x2 {
|
|||
external factory Float64x2.fromFloat32x4(Float32x4 v);
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
@pragma("vm:entry-point")
|
||||
final class _Float64x2 implements Float64x2 {
|
||||
@pragma("vm:recognized", "graph-intrinsic")
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import "dart:_internal" show patch, checkNotNullable;
|
||||
|
||||
@patch
|
||||
@pragma('vm:deeply-immutable')
|
||||
@pragma("vm:entry-point")
|
||||
@pragma("wasm:entry-point")
|
||||
class bool {
|
||||
|
|
|
@ -8,6 +8,7 @@ import "dart:typed_data" show Int64List;
|
|||
|
||||
/// VM implementation of int.
|
||||
@patch
|
||||
@pragma('vm:deeply-immutable')
|
||||
@pragma("vm:entry-point")
|
||||
class int {
|
||||
@patch
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import "dart:_internal" show patch;
|
||||
|
||||
@patch
|
||||
@pragma('vm:deeply-immutable')
|
||||
@pragma("vm:entry-point")
|
||||
class Null {
|
||||
static const _HASH_CODE = 2011; // The year Dart was announced and a prime.
|
||||
|
|
|
@ -807,6 +807,7 @@ class EPointer extends Pointer {}
|
|||
// [analyzer] COMPILE_TIME_ERROR.NO_GENERATIVE_CONSTRUCTORS_IN_SUPERCLASS
|
||||
// ^
|
||||
// [cfe] The superclass, 'Pointer', has no unnamed constructor that takes no arguments.
|
||||
// [cfe] Subtypes of deeply immutable classes must be deeply immutable.
|
||||
|
||||
// Cannot implement native natives or Struct.
|
||||
|
||||
|
@ -889,6 +890,7 @@ class INativeFunction implements NativeFunction {}
|
|||
class IPointer implements Pointer {}
|
||||
// ^^^^^^^^
|
||||
// [cfe] The non-abstract class 'IPointer' is missing implementations for these members:
|
||||
// [cfe] Subtypes of deeply immutable classes must be deeply immutable.
|
||||
// [analyzer] COMPILE_TIME_ERROR.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER
|
||||
// ^^^^^^^
|
||||
// [cfe] The class 'Pointer' can't be implemented outside of its library because it's a final class.
|
||||
|
|
|
@ -13,6 +13,7 @@ class BadExtends extends Null {}
|
|||
// ^
|
||||
// [cfe] 'Null' is restricted and can't be extended or implemented.
|
||||
// [cfe] The superclass, 'Null', has no unnamed constructor that takes no arguments.
|
||||
// [cfe] Subtypes of deeply immutable classes must be deeply immutable.
|
||||
// ^
|
||||
// [cfe] 'Null' is restricted and can't be extended or implemented.
|
||||
|
||||
|
@ -21,6 +22,7 @@ class BadImplements implements Null {}
|
|||
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_DISALLOWED_TYPE
|
||||
// ^
|
||||
// [cfe] 'Null' is restricted and can't be extended or implemented.
|
||||
// [cfe] Subtypes of deeply immutable classes must be deeply immutable.
|
||||
// ^
|
||||
// [cfe] 'Null' is restricted and can't be extended or implemented.
|
||||
|
||||
|
@ -29,6 +31,7 @@ class BadMixin extends Object with Null {}
|
|||
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_DISALLOWED_TYPE
|
||||
// ^
|
||||
// [cfe] 'Null' is restricted and can't be extended or implemented.
|
||||
// [cfe] Subtypes of deeply immutable classes must be deeply immutable.
|
||||
// ^
|
||||
// [cfe] 'Null' is restricted and can't be extended or implemented.
|
||||
|
||||
|
@ -37,5 +40,6 @@ class BadMixin2 = Object with Null;
|
|||
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_DISALLOWED_TYPE
|
||||
// ^
|
||||
// [cfe] 'Null' is restricted and can't be extended or implemented.
|
||||
// [cfe] Subtypes of deeply immutable classes must be deeply immutable.
|
||||
// ^
|
||||
// [cfe] 'Null' is restricted and can't be extended or implemented.
|
||||
|
|
36
tests/lib/isolate/deeply_immutable_2_test.dart
Normal file
36
tests/lib/isolate/deeply_immutable_2_test.dart
Normal file
|
@ -0,0 +1,36 @@
|
|||
// 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.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:isolate';
|
||||
|
||||
class Base {
|
||||
var myMutableField;
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Foo extends Base {}
|
||||
// ^^^
|
||||
// [cfe] The super type of deeply immutable classes must be deeply immutable.
|
||||
|
||||
Future<T> sendReceive<T>(T o) async {
|
||||
final r = ReceivePort();
|
||||
final si = StreamIterator(r);
|
||||
|
||||
r.sendPort.send(o);
|
||||
await si.moveNext();
|
||||
final o2 = si.current;
|
||||
|
||||
si.cancel();
|
||||
return o2;
|
||||
}
|
||||
|
||||
main() async {
|
||||
final o = Foo();
|
||||
final o2 = await sendReceive(o);
|
||||
if (!identical(o, o2)) throw 'not identical';
|
||||
|
||||
throw 'we could share mutable objects - oh no!';
|
||||
}
|
38
tests/lib/isolate/deeply_immutable_no_ffi_test.dart
Normal file
38
tests/lib/isolate/deeply_immutable_no_ffi_test.dart
Normal file
|
@ -0,0 +1,38 @@
|
|||
// 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.
|
||||
|
||||
// These checks are not implemented in the analyzer. If we ever decide to
|
||||
// implement the static checks in the analyzer, move this test into the
|
||||
// static_checks subdir to prevent analyzer errors showing up in the IDE.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:isolate';
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Foo {
|
||||
dynamic myMutableField;
|
||||
// ^
|
||||
// [cfe] Deeply immutable classes must only have final non-late instance fields.
|
||||
// [cfe] 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')`.
|
||||
}
|
||||
|
||||
Future<T> sendReceive<T>(T o) async {
|
||||
final r = ReceivePort();
|
||||
final si = StreamIterator(r);
|
||||
|
||||
r.sendPort.send(o);
|
||||
await si.moveNext();
|
||||
final o2 = si.current;
|
||||
|
||||
si.cancel();
|
||||
return o2;
|
||||
}
|
||||
|
||||
main() async {
|
||||
final o = Foo();
|
||||
final o2 = await sendReceive(o);
|
||||
if (!identical(o, o2)) throw 'not identical';
|
||||
|
||||
throw 'we could share mutable objects - oh no!';
|
||||
}
|
229
tests/lib/isolate/deeply_immutable_test.dart
Normal file
229
tests/lib/isolate/deeply_immutable_test.dart
Normal file
|
@ -0,0 +1,229 @@
|
|||
// 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.
|
||||
|
||||
// These checks are not implemented in the analyzer. If we ever decide to
|
||||
// implement the static checks in the analyzer, move this test into the
|
||||
// static_checks subdir to prevent analyzer errors showing up in the IDE.
|
||||
|
||||
import 'dart:ffi';
|
||||
import 'dart:typed_data';
|
||||
|
||||
void main() {
|
||||
testInstantiateDeeplyImmutable();
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class EmptyClass {}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class1 {
|
||||
Class1(this.a);
|
||||
|
||||
int a;
|
||||
// ^
|
||||
// [cfe] Deeply immutable classes must only have final non-late instance fields.
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class2 {
|
||||
late final int a;
|
||||
// ^
|
||||
// [cfe] Deeply immutable classes must only have final non-late instance fields.
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class3 {
|
||||
Class3(this.a);
|
||||
|
||||
final int a;
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class4 {
|
||||
// Static fields are not part of the instance.
|
||||
static late final int a;
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class5 {
|
||||
// External fields are defined as setter/getter pairs.
|
||||
external int a;
|
||||
}
|
||||
|
||||
final class NotDeeplyImmutable {
|
||||
late int a;
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class6 {
|
||||
Class6(this.a);
|
||||
|
||||
final NotDeeplyImmutable a;
|
||||
// ^
|
||||
// [cfe] 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')`.
|
||||
}
|
||||
|
||||
void testInstantiateDeeplyImmutable() {
|
||||
Class7(
|
||||
someString: 'someString',
|
||||
someNullableString: 'someString',
|
||||
someInt: 3,
|
||||
someDouble: 3.3,
|
||||
someBool: false,
|
||||
someNull: null,
|
||||
someInt32x4: Int32x4(0, 1, 2, 3),
|
||||
someFloat32x4: Float32x4(0.0, 1.1, 2.2, 3.3),
|
||||
someFloat64x2: Float64x2(4.4, 5.5),
|
||||
someClass7: Class7(
|
||||
someString: 'someString',
|
||||
someInt: 3,
|
||||
someDouble: 3.3,
|
||||
someBool: false,
|
||||
someNull: null,
|
||||
someInt32x4: Int32x4(0, 1, 2, 3),
|
||||
someFloat32x4: Float32x4(0.0, 1.1, 2.2, 3.3),
|
||||
someFloat64x2: Float64x2(4.4, 5.5),
|
||||
someClass7: null,
|
||||
somePointer: Pointer.fromAddress(0x8badf00d),
|
||||
),
|
||||
somePointer: Pointer.fromAddress(0xdeadbeef),
|
||||
);
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class7 {
|
||||
final String someString;
|
||||
final String? someNullableString;
|
||||
final int someInt;
|
||||
final double someDouble;
|
||||
final bool someBool;
|
||||
final Null someNull;
|
||||
final Int32x4 someInt32x4;
|
||||
final Float32x4 someFloat32x4;
|
||||
final Float64x2 someFloat64x2;
|
||||
final Class7? someClass7;
|
||||
final Pointer somePointer;
|
||||
|
||||
// Note that UnmodifiableUint8ListView has been deprecated. Which means there
|
||||
// currently is no way to intentionally have a typed data as a field in a
|
||||
// class which is deeply immutable.
|
||||
// See: https://github.com/dart-lang/sdk/issues/53218.
|
||||
|
||||
// Note that RegExp, SendPort, and Capability can be implemented. So fields
|
||||
// are not allowed to be of these types either.
|
||||
|
||||
Class7({
|
||||
required this.someString,
|
||||
this.someNullableString,
|
||||
required this.someInt,
|
||||
required this.someDouble,
|
||||
required this.someBool,
|
||||
required this.someNull,
|
||||
required this.someInt32x4,
|
||||
required this.someFloat32x4,
|
||||
required this.someFloat64x2,
|
||||
required this.someClass7,
|
||||
required this.somePointer,
|
||||
});
|
||||
}
|
||||
|
||||
void testInstantiateImmutableHierarchy() {
|
||||
Class8(
|
||||
animal: Cat(
|
||||
numberOfLegs: 4,
|
||||
averageNumberOfMeowsPerDay: 42.0,
|
||||
),
|
||||
);
|
||||
Class8(
|
||||
animal: Dog(
|
||||
numberOfLegs: 4,
|
||||
averageNumberOfWoofsPerDay: 1337.0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Animal {
|
||||
final int numberOfLegs;
|
||||
|
||||
Animal({
|
||||
required this.numberOfLegs,
|
||||
});
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Cat extends Animal {
|
||||
final double averageNumberOfMeowsPerDay;
|
||||
|
||||
Cat({
|
||||
required super.numberOfLegs,
|
||||
required this.averageNumberOfMeowsPerDay,
|
||||
});
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Dog extends Animal {
|
||||
final double averageNumberOfWoofsPerDay;
|
||||
|
||||
Dog({
|
||||
required super.numberOfLegs,
|
||||
required this.averageNumberOfWoofsPerDay,
|
||||
});
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class8 {
|
||||
final Animal animal;
|
||||
|
||||
Class8({
|
||||
required this.animal,
|
||||
});
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
abstract final class DeeplyImmutableInterface {}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class9 implements DeeplyImmutableInterface {}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class10 implements DeeplyImmutableInterface {}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
sealed class Class11 {}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
class NotSealedOrFinalClass {}
|
||||
// ^^^^^^^^^^^^^^^^^^^^^
|
||||
// [cfe] Deeply immutable classes must be final or sealed.
|
||||
|
||||
final class Class12 extends DeeplyImmutableInterface {}
|
||||
// ^^^^^^^
|
||||
// [cfe] Subtypes of deeply immutable classes must be deeply immutable.
|
||||
|
||||
final class Class13 implements DeeplyImmutableInterface {
|
||||
// ^^^^^^^
|
||||
// [cfe] Subtypes of deeply immutable classes must be deeply immutable.
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class14<T extends DeeplyImmutableInterface> {
|
||||
final T deeplyImmutable;
|
||||
|
||||
Class14({required this.deeplyImmutable});
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class15<T extends NotDeeplyImmutable> {
|
||||
final T notDeeplyImmutable;
|
||||
// ^^^^^^^^^^^^^^^^^^
|
||||
// [cfe] 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')`.
|
||||
|
||||
Class15({required this.notDeeplyImmutable});
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
abstract mixin class Class17 {}
|
||||
// ^^^^^^^
|
||||
// [cfe] Deeply immutable classes must be final or sealed.
|
|
@ -228,36 +228,42 @@ abstract class CMFloat64x2List with Float64x2List {}
|
|||
abstract class CIInt32x4 implements Int32x4 {}
|
||||
// ^
|
||||
// [cfe] 'Int32x4' is restricted and can't be extended or implemented.
|
||||
// [cfe] Subtypes of deeply immutable classes must be deeply immutable.
|
||||
// ^^^^^^^
|
||||
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_DISALLOWED_TYPE
|
||||
|
||||
abstract class CMInt32x4 with Int32x4 {}
|
||||
// ^
|
||||
// [cfe] 'Int32x4' is restricted and can't be extended or implemented.
|
||||
// [cfe] Subtypes of deeply immutable classes must be deeply immutable.
|
||||
// ^^^^^^^
|
||||
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_DISALLOWED_TYPE
|
||||
|
||||
abstract class CIFloat32x4 implements Float32x4 {}
|
||||
// ^
|
||||
// [cfe] 'Float32x4' is restricted and can't be extended or implemented.
|
||||
// [cfe] Subtypes of deeply immutable classes must be deeply immutable.
|
||||
// ^^^^^^^^^
|
||||
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_DISALLOWED_TYPE
|
||||
|
||||
abstract class CMFloat32x4 with Float32x4 {}
|
||||
// ^
|
||||
// [cfe] 'Float32x4' is restricted and can't be extended or implemented.
|
||||
// [cfe] Subtypes of deeply immutable classes must be deeply immutable.
|
||||
// ^^^^^^^^^
|
||||
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_DISALLOWED_TYPE
|
||||
|
||||
abstract class CIFloat64x2 implements Float64x2 {}
|
||||
// ^
|
||||
// [cfe] 'Float64x2' is restricted and can't be extended or implemented.
|
||||
// [cfe] Subtypes of deeply immutable classes must be deeply immutable.
|
||||
// ^^^^^^^^^
|
||||
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_DISALLOWED_TYPE
|
||||
|
||||
abstract class CMFloat64x2 with Float64x2 {}
|
||||
// ^
|
||||
// [cfe] 'Float64x2' is restricted and can't be extended or implemented.
|
||||
// [cfe] Subtypes of deeply immutable classes must be deeply immutable.
|
||||
// ^^^^^^^^^
|
||||
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_DISALLOWED_TYPE
|
||||
|
||||
|
|
Loading…
Reference in a new issue