[vm/ffi] Throw on attaching NativeFinalizer to deeply immutable object

This CL

* adds a `kind` to `CheckWritableInstr`,
* starts using `CheckWritableInstr` in JIT mode, and
* adds support for `CheckWritableInstr` ia32.

TEST=tests/ffi/vmspecific_native_finalizer_deeply_immutable_test.dart

Closes: https://github.com/dart-lang/sdk/issues/55067
Change-Id: I0b397daba12cfc8b885401169889f7cd7c040166
Cq-Include-Trybots: dart-internal/g3.dart-internal.try:g3-cbuild-try
Cq-Include-Trybots: 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-aot-win-debug-arm64-try,vm-aot-win-debug-x64c-try,vm-aot-win-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-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,vm-win-debug-arm64-try,vm-win-debug-x64-try,vm-win-debug-x64c-try,vm-win-release-ia32-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/359223
Auto-Submit: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
This commit is contained in:
Daco Harkes 2024-03-28 11:24:36 +00:00 committed by Commit Queue
parent 9426e47d73
commit eb50c2683c
16 changed files with 111 additions and 20 deletions

View file

@ -26,6 +26,7 @@
#include "vm/longjump.h"
#include "vm/object_store.h"
#include "vm/parser.h"
#include "vm/pointer_tagging.h"
#include "vm/raw_object.h"
#include "vm/resolver.h"
#include "vm/service_isolate.h"
@ -3196,6 +3197,8 @@ void WriteErrorSlowPath::PushArgumentsForRuntimeCall(
FlowGraphCompiler* compiler) {
LocationSummary* locs = instruction()->locs();
__ PushRegister(locs->in(CheckWritableInstr::kReceiver).reg());
__ PushImmediate(
compiler::target::ToRawSmi(instruction()->AsCheckWritable()->kind()));
}
void WriteErrorSlowPath::EmitSharedStubCall(FlowGraphCompiler* compiler,

View file

@ -305,7 +305,7 @@ class WriteErrorSlowPath : public ThrowErrorSlowPathCode {
virtual void PushArgumentsForRuntimeCall(FlowGraphCompiler* compiler);
virtual intptr_t GetNumberOfArgumentsForRuntimeCall() {
return 1; // receiver
return 2; // receiver, kind
}
};

View file

@ -6814,6 +6814,11 @@ intptr_t CheckArrayBoundInstr::LengthOffsetFor(intptr_t class_id) {
}
Definition* CheckWritableInstr::Canonicalize(FlowGraph* flow_graph) {
if (kind_ == Kind::kDeeplyImmutableAttachNativeFinalizer) {
return this;
}
ASSERT(kind_ == Kind::kWriteUnmodifiableTypedData);
intptr_t cid = value()->Type()->ToCid();
if ((cid != kIllegalCid) && (cid != kDynamicCid) &&
!IsUnmodifiableTypedDataViewClassId(cid)) {

View file

@ -10873,11 +10873,17 @@ class GenericCheckBoundInstr : public CheckBoundBaseInstr {
class CheckWritableInstr : public TemplateDefinition<1, Throws, Pure> {
public:
CheckWritableInstr(Value* array,
enum Kind {
kWriteUnmodifiableTypedData = 0,
kDeeplyImmutableAttachNativeFinalizer = 1,
};
CheckWritableInstr(Value* receiver,
intptr_t deopt_id,
const InstructionSource& source)
: TemplateDefinition(source, deopt_id) {
SetInputAt(kReceiver, array);
const InstructionSource& source,
Kind kind = Kind::kWriteUnmodifiableTypedData)
: TemplateDefinition(source, deopt_id), kind_(kind) {
SetInputAt(kReceiver, receiver);
}
virtual bool AttributesEqual(const Instruction& other) const { return true; }
@ -10891,13 +10897,24 @@ class CheckWritableInstr : public TemplateDefinition<1, Throws, Pure> {
virtual Value* RedefinedValue() const;
virtual bool ComputeCanDeoptimize() const { return false; }
virtual bool ComputeCanDeoptimizeAfterCall() const {
return !CompilerState::Current().is_aot();
}
Kind kind() const { return kind_; }
// Give a name to the location/input indices.
enum {
kReceiver = 0,
};
DECLARE_EMPTY_SERIALIZATION(CheckWritableInstr, TemplateDefinition)
#define FIELD_LIST(F) F(const Kind, kind_)
DECLARE_INSTRUCTION_SERIALIZABLE_FIELDS(CheckWritableInstr,
TemplateDefinition,
FIELD_LIST)
#undef FIELD_LIST
private:
DISALLOW_COPY_AND_ASSIGN(CheckWritableInstr);

View file

@ -5536,22 +5536,25 @@ void CheckArrayBoundInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
LocationSummary* CheckWritableInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 1;
const intptr_t kNumTemps = 0;
const intptr_t kNumTemps = 1;
LocationSummary* locs = new (zone) LocationSummary(
zone, kNumInputs, kNumTemps,
UseSharedSlowPathStub(opt) ? LocationSummary::kCallOnSharedSlowPath
: LocationSummary::kCallOnSlowPath);
locs->set_in(kReceiver, Location::RequiresRegister());
locs->set_temp(0, Location::RequiresRegister());
return locs;
}
void CheckWritableInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
WriteErrorSlowPath* slow_path = new WriteErrorSlowPath(this);
const Register temp = locs()->temp(0).reg();
compiler->AddSlowPathCode(slow_path);
__ movl(TMP, compiler::FieldAddress(locs()->in(0).reg(),
compiler::target::Object::tags_offset()));
__ testl(TMP, compiler::Immediate(
1 << compiler::target::UntaggedObject::kImmutableBit));
__ movl(temp,
compiler::FieldAddress(locs()->in(0).reg(),
compiler::target::Object::tags_offset()));
__ testl(temp, compiler::Immediate(
1 << compiler::target::UntaggedObject::kImmutableBit));
__ j(NOT_ZERO, slow_path->entry_label());
}

View file

@ -1075,6 +1075,14 @@ Fragment BaseFlowGraphBuilder::CheckNullOptimized(
return Fragment(check_null);
}
Fragment BaseFlowGraphBuilder::CheckNotDeeplyImmutable(
CheckWritableInstr::Kind kind) {
Value* value = Pop();
auto* check_writable = new (Z)
CheckWritableInstr(value, GetNextDeoptId(), InstructionSource(), kind);
return Fragment(check_writable);
}
void BaseFlowGraphBuilder::RecordUncheckedEntryPoint(
GraphEntryInstr* graph_entry,
FunctionEntryInstr* unchecked_entry) {

View file

@ -440,6 +440,8 @@ class BaseFlowGraphBuilder {
position);
}
Fragment CheckNotDeeplyImmutable(CheckWritableInstr::Kind kind);
// Records extra unchecked entry point 'unchecked_entry' in 'graph_entry'.
void RecordUncheckedEntryPoint(GraphEntryInstr* graph_entry,
FunctionEntryInstr* unchecked_entry);

View file

@ -1057,6 +1057,7 @@ bool FlowGraphBuilder::IsRecognizedMethodForFlowGraph(
case MethodRecognizer::kFinalizerBase_setIsolateFinalizers:
case MethodRecognizer::kFinalizerEntry_allocate:
case MethodRecognizer::kFinalizerEntry_getExternalSize:
case MethodRecognizer::kCheckNotDeeplyImmutable:
case MethodRecognizer::kObjectEquals:
case MethodRecognizer::kStringBaseCodeUnitAt:
case MethodRecognizer::kStringBaseLength:
@ -1834,6 +1835,13 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfRecognizedMethod(
body += LoadNativeField(Slot::FinalizerEntry_external_size());
body += Box(kUnboxedInt64);
break;
case MethodRecognizer::kCheckNotDeeplyImmutable:
ASSERT_EQUAL(function.NumParameters(), 1);
body += LoadLocal(parsed_function_->RawParameterVariable(0));
body += CheckNotDeeplyImmutable(
CheckWritableInstr::kDeeplyImmutableAttachNativeFinalizer);
body += NullConstant();
break;
#define IL_BODY(method, slot) \
case MethodRecognizer::k##method: \
ASSERT_EQUAL(function.NumParameters(), 1); \

View file

@ -334,6 +334,7 @@ namespace dart {
V(_Double, get:hashCode, Double_hashCode, 0x75d244b3) \
V(::, _memCopy, MemCopy, 0x2740bc36) \
V(::, debugger, Debugger, 0xf0b98af4) \
V(::, _checkNotDeeplyImmutable, CheckNotDeeplyImmutable, 0x5646c2e4) \
// List of intrinsics:
// (class-name, function-name, intrinsification method, fingerprint).

View file

@ -503,7 +503,7 @@ void StubCodeCompiler::GenerateRangeError(bool with_fpu_regs) {
void StubCodeCompiler::GenerateWriteError(bool with_fpu_regs) {
auto perform_runtime_call = [&]() {
__ CallRuntime(kWriteErrorRuntimeEntry, /*argument_count=*/1);
__ CallRuntime(kWriteErrorRuntimeEntry, /*argument_count=*/2);
__ Breakpoint();
};

View file

@ -724,7 +724,7 @@ void StubCodeCompiler::GenerateRangeError(bool with_fpu_regs) {
void StubCodeCompiler::GenerateWriteError(bool with_fpu_regs) {
auto perform_runtime_call = [&]() {
__ CallRuntime(kWriteErrorRuntimeEntry, /*argument_count=*/1);
__ CallRuntime(kWriteErrorRuntimeEntry, /*argument_count=*/2);
__ Breakpoint();
};

View file

@ -591,7 +591,7 @@ void StubCodeCompiler::GenerateRangeError(bool with_fpu_regs) {
void StubCodeCompiler::GenerateWriteError(bool with_fpu_regs) {
auto perform_runtime_call = [&]() {
__ CallRuntime(kWriteErrorRuntimeEntry, /*argument_count=*/1);
__ CallRuntime(kWriteErrorRuntimeEntry, /*argument_count=*/2);
__ Breakpoint();
};

View file

@ -684,7 +684,7 @@ void StubCodeCompiler::GenerateRangeError(bool with_fpu_regs) {
void StubCodeCompiler::GenerateWriteError(bool with_fpu_regs) {
auto perform_runtime_call = [&]() {
__ CallRuntime(kWriteErrorRuntimeEntry, /*argument_count=*/1);
__ CallRuntime(kWriteErrorRuntimeEntry, /*argument_count=*/2);
__ Breakpoint();
};

View file

@ -163,12 +163,23 @@ DEFINE_RUNTIME_ENTRY(RangeErrorUnboxedInt64, 0) {
Exceptions::ThrowByType(Exceptions::kRange, args);
}
DEFINE_RUNTIME_ENTRY(WriteError, 1) {
DEFINE_RUNTIME_ENTRY(WriteError, 2) {
const Instance& receiver = Instance::CheckedHandle(zone, arguments.ArgAt(0));
const Smi& kind = Smi::CheckedHandle(zone, arguments.ArgAt(1));
auto& message = String::Handle(zone);
switch (kind.Value()) {
case 0: // CheckWritableInstr::Kind::kWriteUnmodifiableTypedData:
message = String::NewFormatted("Cannot modify an unmodifiable list: %s",
receiver.ToCString());
break;
case 1: // CheckWritableInstr::Kind::kDeeplyImmutableAttachNativeFinalizer:
message = String::NewFormatted(
"Cannot attach NativeFinalizer to deeply immutable object: %s",
receiver.ToCString());
break;
}
const Array& args = Array::Handle(Array::New(1));
args.SetAt(
0, String::Handle(String::NewFormatted(
"Cannot modify an unmodifiable list: %s", receiver.ToCString())));
args.SetAt(0, message);
Exceptions::ThrowByType(Exceptions::kUnsupported, args);
}

View file

@ -43,6 +43,7 @@ final class _NativeFinalizer extends FinalizerBase implements NativeFinalizer {
Object? detach,
int? externalSize,
}) {
_checkNotDeeplyImmutable(value);
externalSize ??= 0;
RangeError.checkNotNegative(externalSize, 'externalSize');
if (value == null) {
@ -98,7 +99,7 @@ final class _NativeFinalizer extends FinalizerBase implements NativeFinalizer {
}
@pragma("vm:entry-point", "call")
static _handleNativeFinalizerMessage(_NativeFinalizer finalizer) {
static void _handleNativeFinalizerMessage(_NativeFinalizer finalizer) {
finalizer._removeEntries();
}
}
@ -142,3 +143,6 @@ void _attachAsTypedListFinalizer(
isLeaf: true,
)
external Pointer<_AsTypedListFinalizerData> _allocateData();
@pragma("vm:recognized", "other")
external void _checkNotDeeplyImmutable(Object value);

View file

@ -0,0 +1,29 @@
// 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.
//
// SharedObjects=ffi_test_functions
import 'dart:ffi';
import 'package:expect/expect.dart';
import 'dylib_utils.dart';
import 'ffi_test_helpers.dart';
void main() {
testAttachDeeplyImmutableThrows();
}
DynamicLibrary ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
@pragma('vm:deeply-immutable')
final class MyFinalizable implements Finalizable {}
void testAttachDeeplyImmutableThrows() {
final myFinalizable = MyFinalizable();
Expect.throwsUnsupportedError(
() => setTokenFinalizer.attach(myFinalizable, nullptr.cast()),
);
}