Files
sdk/tests/lib/isolate/deeply_immutable_test.dart
T
Daco Harkes 8de00e2137 [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>
2024-03-07 18:33:58 +00:00

230 lines
5.9 KiB
Dart

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