Skip to content

Commit 496345e

Browse files
[vm/ffi] Update exception behavior of FFI callbacks and fix callbacks returning void
Issues dartbug.com/36856 and dartbug.com/37301 Change-Id: I96595b0781a3a6ddf73bba5595ed5c1aa08be257 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/106352 Reviewed-by: Martin Kustermann <[email protected]> Reviewed-by: Daco Harkes <[email protected]>
1 parent 8d70508 commit 496345e

16 files changed

Lines changed: 270 additions & 102 deletions

File tree

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ export '../fasta/fasta_codes.dart'
4343
templateFfiTypeMismatch,
4444
templateFfiTypeUnsized,
4545
templateFfiFieldInitializer,
46-
templateIllegalRecursiveType;
46+
templateIllegalRecursiveType,
47+
templateFfiDartTypeMismatch;
4748

4849
export '../fasta/hybrid_file_system.dart' show HybridFileSystem;
4950

pkg/front_end/lib/src/fasta/fasta_codes_generated.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3641,6 +3641,34 @@ const MessageCode messageFastaUsageShort =
36413641
-o <file> Generate the output into <file>.
36423642
-h Display this message (add -v for information about all options).""");
36433643

3644+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
3645+
const Template<Message Function(DartType _type, DartType _type2)>
3646+
templateFfiDartTypeMismatch =
3647+
const Template<Message Function(DartType _type, DartType _type2)>(
3648+
messageTemplate: r"""Expected '#type' to be a subtype of '#type2'.""",
3649+
withArguments: _withArgumentsFfiDartTypeMismatch);
3650+
3651+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
3652+
const Code<Message Function(DartType _type, DartType _type2)>
3653+
codeFfiDartTypeMismatch =
3654+
const Code<Message Function(DartType _type, DartType _type2)>(
3655+
"FfiDartTypeMismatch",
3656+
templateFfiDartTypeMismatch,
3657+
);
3658+
3659+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
3660+
Message _withArgumentsFfiDartTypeMismatch(DartType _type, DartType _type2) {
3661+
TypeLabeler labeler = new TypeLabeler();
3662+
List<Object> typeParts = labeler.labelType(_type);
3663+
List<Object> type2Parts = labeler.labelType(_type2);
3664+
String type = typeParts.join();
3665+
String type2 = type2Parts.join();
3666+
return new Message(codeFfiDartTypeMismatch,
3667+
message: """Expected '${type}' to be a subtype of '${type2}'.""" +
3668+
labeler.originMessages,
3669+
arguments: {'type': _type, 'type2': _type2});
3670+
}
3671+
36443672
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
36453673
const Template<
36463674
Message Function(String name)> templateFfiFieldAnnotation = const Template<

pkg/front_end/messages.status

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ FfiStructAnnotation/analyzerCode: Fail
253253
FfiTypeInvalid/analyzerCode: Fail
254254
FfiTypeMismatch/analyzerCode: Fail
255255
FfiTypeUnsized/analyzerCode: Fail
256+
FfiDartTypeMismatch/analyzerCode: Fail
256257
FieldInitializedOutsideDeclaringClass/part_wrapped_script1: Fail
257258
FieldInitializedOutsideDeclaringClass/script1: Fail
258259
FieldInitializerOutsideConstructor/part_wrapped_script1: Fail

pkg/front_end/messages.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3477,6 +3477,11 @@ FfiStructAnnotation:
34773477
template: "Class '#name' is a dart:ffi Pointer but has no struct annotation. Only struct Pointers can have fields."
34783478
external: test/ffi_test.dart
34793479

3480+
FfiDartTypeMismatch:
3481+
# Used by dart:ffi
3482+
template: "Expected '#type' to be a subtype of '#type2'."
3483+
external: test/ffi_test.dart
3484+
34803485
SpreadTypeMismatch:
34813486
template: "Unexpected type '#type' of a spread. Expected 'dynamic' or an Iterable."
34823487
script:

pkg/vm/lib/transformations/ffi_use_sites.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:front_end/src/api_unstable/vm.dart'
88
show
99
templateFfiTypeInvalid,
1010
templateFfiTypeMismatch,
11+
templateFfiDartTypeMismatch,
1112
templateFfiTypeUnsized,
1213
templateFfiNotStatic;
1314

@@ -120,6 +121,20 @@ class _FfiUseSiteTransformer extends FfiTransformer {
120121
_ensureIsStatic(func);
121122
_ensureNativeTypeValid(nativeType, node);
122123
_ensureNativeTypeToDartType(nativeType, dartType, node);
124+
125+
// Check `exceptionalReturn`'s type.
126+
final FunctionType funcType = dartType;
127+
final Expression exceptionalReturn = node.arguments.positional[1];
128+
final DartType returnType = exceptionalReturn.getStaticType(env);
129+
130+
if (!env.isSubtypeOf(returnType, funcType.returnType)) {
131+
diagnosticReporter.report(
132+
templateFfiDartTypeMismatch.withArguments(
133+
returnType, funcType.returnType),
134+
exceptionalReturn.fileOffset,
135+
1,
136+
exceptionalReturn.location.file);
137+
}
123138
}
124139
} catch (_FfiStaticTypeError) {}
125140

runtime/bin/ffi_test/ffi_test_functions.cc

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,8 +632,8 @@ DART_EXPORT int TestStore(int64_t* (*fn)(int64_t* a)) {
632632
return 0;
633633
}
634634

635-
DART_EXPORT int TestReturnNull(int32_t fn()) {
636-
CHECK_EQ(fn(), 0);
635+
DART_EXPORT int TestReturnNull(int32_t (*fn)()) {
636+
CHECK_EQ(fn(), 42);
637637
return 0;
638638
}
639639

@@ -662,6 +662,26 @@ DART_EXPORT int TestGC(void (*do_gc)()) {
662662
return 0;
663663
}
664664

665+
DART_EXPORT int TestReturnVoid(int (*return_void)()) {
666+
CHECK_EQ(return_void(), 0);
667+
return 0;
668+
}
669+
670+
DART_EXPORT int TestThrowExceptionDouble(double (*fn)()) {
671+
CHECK_EQ(fn(), 42.0);
672+
return 0;
673+
}
674+
675+
DART_EXPORT int TestThrowExceptionPointer(void* (*fn)()) {
676+
CHECK_EQ(fn(), reinterpret_cast<void*>(42));
677+
return 0;
678+
}
679+
680+
DART_EXPORT int TestThrowException(int (*fn)()) {
681+
CHECK_EQ(fn(), 42);
682+
return 0;
683+
}
684+
665685
struct CallbackTestData {
666686
int success;
667687
void (*callback)();

runtime/lib/ffi.cc

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "platform/globals.h"
99
#include "vm/bootstrap_natives.h"
1010
#include "vm/class_finalizer.h"
11+
#include "vm/class_id.h"
1112
#include "vm/compiler/assembler/assembler.h"
1213
#include "vm/compiler/ffi.h"
1314
#include "vm/compiler/jit/compiler.h"
@@ -527,11 +528,11 @@ DEFINE_NATIVE_ENTRY(Ffi_asFunction, 1, 1) {
527528
}
528529

529530
// Generates assembly to trampoline from native code into Dart.
531+
#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_PRECOMPILER)
530532
static uword CompileNativeCallback(const Function& c_signature,
531-
const Function& dart_target) {
532-
#if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER)
533-
UNREACHABLE();
534-
#elif defined(TARGET_ARCH_DBC)
533+
const Function& dart_target,
534+
const Instance& exceptional_return) {
535+
#if defined(TARGET_ARCH_DBC)
535536
// https://github.com/dart-lang/sdk/issues/35774
536537
// FFI is supported, but callbacks are not.
537538
Exceptions::ThrowUnsupportedError(
@@ -564,6 +565,33 @@ static uword CompileNativeCallback(const Function& c_signature,
564565
function.SetFfiCallbackId(callback_id);
565566
function.SetFfiCallbackTarget(dart_target);
566567

568+
// We require that the exceptional return value for functions returning 'Void'
569+
// must be 'null', since native code should not look at the result.
570+
if (compiler::ffi::NativeTypeIsVoid(
571+
AbstractType::Handle(c_signature.result_type())) &&
572+
!exceptional_return.IsNull()) {
573+
Exceptions::ThrowUnsupportedError(
574+
"Only 'null' may be used as the exceptional return value for a "
575+
"callback returning void.");
576+
}
577+
578+
// We need to load the exceptional return value as a constant in the generated
579+
// function. This means we need to ensure that it's in old space and has no
580+
// (transitively) mutable fields. This is done by checking (asserting) that
581+
// it's a built-in FFI class, whose fields are all immutable, or a
582+
// user-defined Pointer class, which has no fields.
583+
//
584+
// TODO(36730): We'll need to extend this when we support passing/returning
585+
// structs by value.
586+
ASSERT(exceptional_return.IsNull() || exceptional_return.IsNumber() ||
587+
exceptional_return.IsPointer());
588+
if (!exceptional_return.IsSmi() && exceptional_return.IsNew()) {
589+
function.SetFfiCallbackExceptionalReturn(
590+
Instance::Handle(exceptional_return.CopyShallowToOldSpace(thread)));
591+
} else {
592+
function.SetFfiCallbackExceptionalReturn(exceptional_return);
593+
}
594+
567595
// We compile the callback immediately because we need to return a pointer to
568596
// the entry-point. Native calls do not use patching like Dart calls, so we
569597
// cannot compile it lazily.
@@ -578,23 +606,36 @@ static uword CompileNativeCallback(const Function& c_signature,
578606
thread->SetFfiCallbackCode(callback_id, code);
579607

580608
return code.EntryPoint();
581-
#endif
609+
#endif // defined(TARGET_ARCH_DBC)
582610
}
611+
#endif // !defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_PRECOMPILER)
583612

584-
DEFINE_NATIVE_ENTRY(Ffi_fromFunction, 1, 1) {
613+
DEFINE_NATIVE_ENTRY(Ffi_fromFunction, 1, 2) {
614+
#if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER)
615+
UNREACHABLE();
616+
#else
585617
GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0));
586618
GET_NON_NULL_NATIVE_ARGUMENT(Closure, closure, arguments->NativeArgAt(0));
619+
GET_NON_NULL_NATIVE_ARGUMENT(Instance, exceptional_return,
620+
arguments->NativeArgAt(1));
621+
622+
if (!type_arg.IsInstantiated() || !type_arg.IsFunctionType()) {
623+
// TODO(35902): Remove this when dynamic invocations of fromFunction are
624+
// prohibited.
625+
Exceptions::ThrowUnsupportedError(
626+
"Type argument to fromFunction must an instantiated function type.");
627+
}
587628

588629
const Function& native_signature =
589-
Function::Handle(((Type&)type_arg).signature());
630+
Function::Handle(Type::Cast(type_arg).signature());
590631
Function& func = Function::Handle(closure.function());
591632
TypeArguments& type_args = TypeArguments::Handle(zone);
592633
type_args = TypeArguments::New(1);
593634
type_args.SetTypeAt(Pointer::kNativeTypeArgPos, type_arg);
594635
type_args = type_args.Canonicalize();
595636

596-
Class& native_function_class = Class::Handle(
597-
Isolate::Current()->class_table()->At(kFfiNativeFunctionCid));
637+
Class& native_function_class =
638+
Class::Handle(isolate->class_table()->At(kFfiNativeFunctionCid));
598639
native_function_class.EnsureIsFinalized(Thread::Current());
599640

600641
Type& native_function_type = Type::Handle(
@@ -613,12 +654,30 @@ DEFINE_NATIVE_ENTRY(Ffi_fromFunction, 1, 1) {
613654
func = func.parent_function();
614655
ASSERT(func.is_static());
615656

616-
const uword address = CompileNativeCallback(native_signature, func);
657+
const AbstractType& return_type =
658+
AbstractType::Handle(native_signature.result_type());
659+
if (compiler::ffi::NativeTypeIsVoid(return_type)) {
660+
if (!exceptional_return.IsNull()) {
661+
const String& error = String::Handle(
662+
String::NewFormatted("Exceptional return argument to 'fromFunction' "
663+
"must be null for functions returning void."));
664+
Exceptions::ThrowArgumentError(error);
665+
}
666+
} else if (!compiler::ffi::NativeTypeIsPointer(return_type) &&
667+
exceptional_return.IsNull()) {
668+
const String& error = String::Handle(String::NewFormatted(
669+
"Exceptional return argument to 'fromFunction' must not be null."));
670+
Exceptions::ThrowArgumentError(error);
671+
}
672+
673+
const uword address =
674+
CompileNativeCallback(native_signature, func, exceptional_return);
617675

618676
const Pointer& result = Pointer::Handle(Pointer::New(
619677
native_function_type, Integer::Handle(zone, Integer::New(address))));
620678

621679
return result.raw();
680+
#endif
622681
}
623682

624683
#if defined(TARGET_ARCH_DBC)

runtime/lib/ffi_patch.dart

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ int sizeOf<T extends NativeType>() native "Ffi_sizeOf";
1515

1616
@patch
1717
Pointer<NativeFunction<T>> fromFunction<T extends Function>(
18-
@DartRepresentationOf("T") Function f) native "Ffi_fromFunction";
18+
@DartRepresentationOf("T") Function f,
19+
Object exceptionalReturn) native "Ffi_fromFunction";
1920

2021
@patch
2122
@pragma("vm:entry-point")
@@ -53,21 +54,3 @@ class Pointer<T extends NativeType> {
5354
@patch
5455
void free() native "Ffi_free";
5556
}
56-
57-
// This method gets called when an exception bubbles up to the native -> Dart
58-
// boundary from an FFI native callback. Since native code does not have any
59-
// concept of exceptions, the exception cannot be propagated any further.
60-
// Instead, print a warning with the exception and return 0/0.0 from the
61-
// callback.
62-
//
63-
// TODO(36856): Iron out the story behind exceptions.
64-
@pragma("vm:entry-point")
65-
void _handleExposedException(dynamic exception, dynamic stackTrace) {
66-
print(
67-
"==================== UNHANDLED EXCEPTION FROM FFI CALLBACK ====================");
68-
print(
69-
""" ** Native callbacks should not throw exceptions because they cannot be
70-
propagated into native code. **""");
71-
print("EXCEPTION: $exception");
72-
print(stackTrace);
73-
}

runtime/vm/bootstrap_natives.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ namespace dart {
384384
V(Ffi_cast, 1) \
385385
V(Ffi_sizeOf, 0) \
386386
V(Ffi_asFunction, 1) \
387-
V(Ffi_fromFunction, 1) \
387+
V(Ffi_fromFunction, 2) \
388388
V(Ffi_dl_open, 1) \
389389
V(Ffi_dl_lookup, 2) \
390390
V(Ffi_dl_getHandle, 1) \

runtime/vm/compiler/frontend/kernel_to_il.cc

Lines changed: 15 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2454,22 +2454,6 @@ Fragment FlowGraphBuilder::FfiUnboxedExtend(Representation representation,
24542454
return Fragment(extend);
24552455
}
24562456

2457-
Fragment FlowGraphBuilder::FfiExceptionalReturnValue(
2458-
const AbstractType& result_type,
2459-
Representation representation) {
2460-
ASSERT(optimizing_);
2461-
Object& result = Object::ZoneHandle(Z, Object::null());
2462-
if (representation == kUnboxedFloat || representation == kUnboxedDouble) {
2463-
result = Double::New(0.0, Heap::kOld);
2464-
} else {
2465-
result = Integer::New(0, Heap::kOld);
2466-
}
2467-
Fragment code;
2468-
code += Constant(result);
2469-
code += UnboxTruncate(representation);
2470-
return code;
2471-
}
2472-
24732457
#if !defined(TARGET_ARCH_DBC)
24742458
Fragment FlowGraphBuilder::NativeReturn(Representation result) {
24752459
auto* instr = new (Z)
@@ -2568,6 +2552,15 @@ Fragment FlowGraphBuilder::FfiConvertArgumentToNative(
25682552
const AbstractType& ffi_type,
25692553
const Representation native_representation) {
25702554
Fragment body;
2555+
2556+
// Return 0 for void.
2557+
if (compiler::ffi::NativeTypeIsVoid(ffi_type)) {
2558+
body += Drop();
2559+
body += IntConstant(0);
2560+
body += UnboxTruncate(kUnboxedFfiIntPtr);
2561+
return body;
2562+
}
2563+
25712564
// Check for 'null'. Only ffi.Pointers are allowed to be null.
25722565
if (!compiler::ffi::NativeTypeIsPointer(ffi_type)) {
25732566
body += LoadLocal(MakeTemporary());
@@ -2724,24 +2717,12 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfFfiCallback(const Function& function) {
27242717
++catch_depth_;
27252718
Fragment catch_body =
27262719
CatchBlockEntry(Array::empty_array(), try_handler_index,
2727-
/*needs_stacktrace=*/true, /*is_synthesized=*/true);
2728-
2729-
catch_body += LoadLocal(CurrentException());
2730-
catch_body += PushArgument();
2731-
catch_body += LoadLocal(CurrentStackTrace());
2732-
catch_body += PushArgument();
2733-
2734-
// Find '_handleExposedException(e, st)' from ffi_patch.dart and call it.
2735-
const Library& ffi_lib =
2736-
Library::Handle(Z, Library::LookupLibrary(thread_, Symbols::DartFfi()));
2737-
const Function& handler = Function::ZoneHandle(
2738-
Z, ffi_lib.LookupFunctionAllowPrivate(Symbols::HandleExposedException()));
2739-
ASSERT(!handler.IsNull());
2740-
catch_body += StaticCall(TokenPosition::kNoSource, handler, /*num_args=*/2,
2741-
/*arg_names=*/Array::empty_array(), ICData::kStatic);
2742-
catch_body += Drop();
2743-
2744-
catch_body += FfiExceptionalReturnValue(ffi_type, result_rep);
2720+
/*needs_stacktrace=*/false, /*is_synthesized=*/true);
2721+
2722+
// Return the "exceptional return" value given in 'fromFunction'.
2723+
catch_body += Constant(
2724+
Instance::ZoneHandle(Z, function.FfiCallbackExceptionalReturn()));
2725+
catch_body += FfiConvertArgumentToNative(function, ffi_type, result_rep);
27452726
catch_body += NativeReturn(result_rep);
27462727
--catch_depth_;
27472728

0 commit comments

Comments
 (0)