[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:
Daco Harkes 2024-03-07 18:33:58 +00:00 committed by Commit Queue
parent 094202bb91
commit 8de00e2137
37 changed files with 1092 additions and 17 deletions

View file

@ -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;

View file

@ -48,6 +48,11 @@ export '../fasta/codes/fasta_codes.dart'
messageFfiAbiSpecificIntegerMappingInvalid,
messageFfiAddressOfMustBeNative,
messageFfiCreateOfStructOrUnion,
messageFfiDeeplyImmutableClassesMustBeFinalOrSealed,
messageFfiDeeplyImmutableFieldsModifiers,
messageFfiDeeplyImmutableFieldsMustBeDeeplyImmutable,
messageFfiDeeplyImmutableSubtypesMustBeDeeplyImmutable,
messageFfiDeeplyImmutableSupertypeMustBeDeeplyImmutable,
messageFfiDefaultAssetDuplicate,
messageFfiExceptionalReturnNull,
messageFfiExpectedConstant,

View file

@ -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

View file

@ -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."

View file

@ -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

View file

@ -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");

View 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;
}
}

View 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).

View file

@ -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

View file

@ -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,
});
}

View file

@ -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;
}

View file

@ -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) {

View file

@ -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() {

View file

@ -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 {

View file

@ -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);
}

View file

@ -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,

View file

@ -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();
}

View file

@ -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());

View file

@ -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();

View file

@ -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.

View file

@ -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> {};

View file

@ -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);

View file

@ -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")

View file

@ -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")

View file

@ -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

View file

@ -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() {

View file

@ -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() {

View file

@ -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")

View file

@ -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 {

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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.

View 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!';
}

View 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!';
}

View 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.

View file

@ -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