fd2e9b9f1a
Instead of implicitly creating closures for FFI
asFunction/lookupFunction APIs in the VM, now they are explicitly expressed in the kernel AST. That makes it possible to analyze
them in TFA.
FFI calls from Dart code into native are now performed in the following way:
```
block {
Pointer #ffiTarget0 = target;
@pragma('vm:ffi:call-closure', _FfiCall<Int32 Function(Int32)>(isLeaf: false))
#ffiClosure0(int arg1) {
_nativeEffect(arg1);
return _ffiCall<int>(#ffiTarget0);
}
} =>#ffiClosure0;
```
_ffiCall method is recognized by the VM and its call is replaced
directly with FFI calling sequence. _ffiCall uses closure
parameters implicitly. No extra trampolines are generated for FFI calls.
TEST=existing
Fixes https://github.com/dart-lang/sdk/issues/54172
Issue https://github.com/dart-lang/sdk/issues/39692
CoreLibraryReviewExempt: Implementation change only.
Change-Id: I92b3ff7391470686151ad0807e2cdbbf1a69d256
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/339662
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
480 lines
15 KiB
Dart
480 lines
15 KiB
Dart
// Copyright (c) 2019, 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.
|
|
//
|
|
// Dart test program for testing dart:ffi function pointers.
|
|
//
|
|
// VMOptions=
|
|
// VMOptions=--deterministic --optimization-counter-threshold=10
|
|
// VMOptions=--use-slow-path
|
|
// VMOptions=--use-slow-path --stacktrace-every=100
|
|
// VMOptions=--write-protect-code --no-dual-map-code
|
|
// VMOptions=--write-protect-code --no-dual-map-code --use-slow-path
|
|
// VMOptions=--write-protect-code --no-dual-map-code --stacktrace-every=100
|
|
// SharedObjects=ffi_test_functions
|
|
|
|
import 'dart:ffi';
|
|
|
|
import "package:ffi/ffi.dart";
|
|
import "package:expect/expect.dart";
|
|
|
|
import 'dylib_utils.dart';
|
|
|
|
void main() {
|
|
for (int i = 0; i < 100; ++i) {
|
|
testNativeFunctionFromCast();
|
|
testNativeFunctionFromLookup();
|
|
test64bitInterpretations();
|
|
testExtension();
|
|
testTruncation();
|
|
testNativeFunctionDoubles();
|
|
testNativeFunctionFloats();
|
|
testNativeFunctionManyArguments1();
|
|
testNativeFunctionManyArguments2();
|
|
testNativeFunctionManyArguments3();
|
|
testNativeFunctionManyArguments4();
|
|
testNativeFunctionManyArguments5();
|
|
testNativeFunctionPointer();
|
|
testNullPointers();
|
|
testFloatRounding();
|
|
testVoidReturn();
|
|
testNoArgs();
|
|
testNativeFunctionNullableInt();
|
|
}
|
|
}
|
|
|
|
final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
|
|
|
|
typedef NativeBinaryOp = Int32 Function(Int32, Int32);
|
|
typedef UnaryOp = int Function(int);
|
|
typedef BinaryOp = int Function(int, int);
|
|
typedef GenericBinaryOp<T> = int Function(int, T);
|
|
|
|
void testNativeFunctionFromCast() {
|
|
Pointer<IntPtr> p1 = calloc();
|
|
Pointer<NativeFunction<NativeBinaryOp>> p2 = p1.cast();
|
|
p2.asFunction<BinaryOp>();
|
|
p2.asFunction<GenericBinaryOp<int>>();
|
|
calloc.free(p1);
|
|
}
|
|
|
|
typedef NativeQuadOpSigned = Int64 Function(Int8, Int16, Int32, Int64);
|
|
typedef QuadOp = int Function(int, int, int, int);
|
|
typedef NativeQuadOpUnsigned = Uint64 Function(Uint8, Uint16, Uint32, Uint64);
|
|
|
|
BinaryOp sumPlus42 =
|
|
ffiTestFunctions.lookupFunction<NativeBinaryOp, BinaryOp>("SumPlus42");
|
|
|
|
QuadOp intComputation = ffiTestFunctions
|
|
.lookupFunction<NativeQuadOpSigned, QuadOp>("IntComputation");
|
|
|
|
void testNativeFunctionFromLookup() {
|
|
Expect.equals(49, sumPlus42(3, 4));
|
|
Expect.equals(49, (sumPlus42 as dynamic)(3, 4));
|
|
Expect.throwsNoSuchMethodError(() => (sumPlus42 as dynamic)());
|
|
Expect.throwsTypeError(() => (sumPlus42 as dynamic)(3, 4.0));
|
|
|
|
Expect.equals(625, intComputation(125, 250, 500, 1000));
|
|
Expect.equals(625, (intComputation as dynamic)(125, 250, 500, 1000));
|
|
|
|
Expect.equals(
|
|
0x7FFFFFFFFFFFFFFF, intComputation(0, 0, 0, 0x7FFFFFFFFFFFFFFF));
|
|
Expect.equals(
|
|
-0x8000000000000000, intComputation(0, 0, 0, -0x8000000000000000));
|
|
|
|
Expect.equals(0x7FFFFFFFFFFFFFFF,
|
|
(intComputation as dynamic)(0, 0, 0, 0x7FFFFFFFFFFFFFFF));
|
|
Expect.equals(-0x8000000000000000,
|
|
(intComputation as dynamic)(0, 0, 0, -0x8000000000000000));
|
|
}
|
|
|
|
typedef NativeReturnMaxUint8 = Uint8 Function();
|
|
int Function() returnMaxUint8 = ffiTestFunctions
|
|
.lookup("ReturnMaxUint8")
|
|
.cast<NativeFunction<NativeReturnMaxUint8>>()
|
|
.asFunction();
|
|
int Function() returnMaxUint8v2 = ffiTestFunctions
|
|
.lookup("ReturnMaxUint8v2")
|
|
.cast<NativeFunction<NativeReturnMaxUint8>>()
|
|
.asFunction();
|
|
|
|
typedef NativeReturnMaxUint16 = Uint16 Function();
|
|
int Function() returnMaxUint16 = ffiTestFunctions
|
|
.lookup("ReturnMaxUint16")
|
|
.cast<NativeFunction<NativeReturnMaxUint16>>()
|
|
.asFunction();
|
|
int Function() returnMaxUint16v2 = ffiTestFunctions
|
|
.lookup("ReturnMaxUint16v2")
|
|
.cast<NativeFunction<NativeReturnMaxUint16>>()
|
|
.asFunction();
|
|
|
|
typedef NativeReturnMaxUint32 = Uint32 Function();
|
|
int Function() returnMaxUint32 = ffiTestFunctions
|
|
.lookup("ReturnMaxUint32")
|
|
.cast<NativeFunction<NativeReturnMaxUint32>>()
|
|
.asFunction();
|
|
int Function() returnMaxUint32v2 = ffiTestFunctions
|
|
.lookup("ReturnMaxUint32v2")
|
|
.cast<NativeFunction<NativeReturnMaxUint32>>()
|
|
.asFunction();
|
|
|
|
typedef NativeReturnMinInt8 = Int8 Function();
|
|
int Function() returnMinInt8 = ffiTestFunctions
|
|
.lookup("ReturnMinInt8")
|
|
.cast<NativeFunction<NativeReturnMinInt8>>()
|
|
.asFunction();
|
|
int Function() returnMinInt8v2 = ffiTestFunctions
|
|
.lookup("ReturnMinInt8v2")
|
|
.cast<NativeFunction<NativeReturnMinInt8>>()
|
|
.asFunction();
|
|
|
|
typedef NativeReturnMinInt16 = Int16 Function();
|
|
int Function() returnMinInt16 = ffiTestFunctions
|
|
.lookup("ReturnMinInt16")
|
|
.cast<NativeFunction<NativeReturnMinInt16>>()
|
|
.asFunction();
|
|
int Function() returnMinInt16v2 = ffiTestFunctions
|
|
.lookup("ReturnMinInt16v2")
|
|
.cast<NativeFunction<NativeReturnMinInt16>>()
|
|
.asFunction();
|
|
|
|
typedef NativeReturnMinInt32 = Int32 Function();
|
|
int Function() returnMinInt32 = ffiTestFunctions
|
|
.lookup("ReturnMinInt32")
|
|
.cast<NativeFunction<NativeReturnMinInt32>>()
|
|
.asFunction();
|
|
int Function() returnMinInt32v2 = ffiTestFunctions
|
|
.lookup("ReturnMinInt32v2")
|
|
.cast<NativeFunction<NativeReturnMinInt32>>()
|
|
.asFunction();
|
|
|
|
typedef NativeTakeMaxUint8 = IntPtr Function(Uint8);
|
|
int Function(int) takeMaxUint8 = ffiTestFunctions
|
|
.lookup("TakeMaxUint8")
|
|
.cast<NativeFunction<NativeTakeMaxUint8>>()
|
|
.asFunction();
|
|
|
|
typedef NativeTakeMaxUint16 = IntPtr Function(Uint16);
|
|
int Function(int) takeMaxUint16 = ffiTestFunctions
|
|
.lookup("TakeMaxUint16")
|
|
.cast<NativeFunction<NativeTakeMaxUint16>>()
|
|
.asFunction();
|
|
|
|
typedef NativeTakeMaxUint32 = IntPtr Function(Uint32);
|
|
int Function(int) takeMaxUint32 = ffiTestFunctions
|
|
.lookup("TakeMaxUint32")
|
|
.cast<NativeFunction<NativeTakeMaxUint32>>()
|
|
.asFunction();
|
|
|
|
typedef NativeTakeMinInt8 = IntPtr Function(Int8);
|
|
int Function(int) takeMinInt8 = ffiTestFunctions
|
|
.lookup("TakeMinInt8")
|
|
.cast<NativeFunction<NativeTakeMinInt8>>()
|
|
.asFunction();
|
|
|
|
typedef NativeTakeMinInt16 = IntPtr Function(Int16);
|
|
int Function(int) takeMinInt16 = ffiTestFunctions
|
|
.lookup("TakeMinInt16")
|
|
.cast<NativeFunction<NativeTakeMinInt16>>()
|
|
.asFunction();
|
|
|
|
typedef NativeTakeMinInt32 = IntPtr Function(Int32);
|
|
int Function(int) takeMinInt32 = ffiTestFunctions
|
|
.lookup("TakeMinInt32")
|
|
.cast<NativeFunction<NativeTakeMinInt32>>()
|
|
.asFunction();
|
|
|
|
typedef NativeTakeMaxUint8x10 = IntPtr Function(
|
|
Uint8, Uint8, Uint8, Uint8, Uint8, Uint8, Uint8, Uint8, Uint8, Uint8);
|
|
int Function(int, int, int, int, int, int, int, int, int, int) takeMaxUint8x10 =
|
|
ffiTestFunctions
|
|
.lookup("TakeMaxUint8x10")
|
|
.cast<NativeFunction<NativeTakeMaxUint8x10>>()
|
|
.asFunction();
|
|
|
|
void testExtension() {
|
|
// Sign extension on the way back to Dart.
|
|
Expect.equals(0xff, returnMaxUint8());
|
|
Expect.equals(0xffff, returnMaxUint16());
|
|
Expect.equals(0xffffffff, returnMaxUint32());
|
|
Expect.equals(-0x80, returnMinInt8());
|
|
Expect.equals(-0x8000, returnMinInt16());
|
|
Expect.equals(-0x80000000, returnMinInt32());
|
|
// Truncation in C, and sign extension back to Dart.
|
|
Expect.equals(0xff, returnMaxUint8v2());
|
|
Expect.equals(0xffff, returnMaxUint16v2());
|
|
Expect.equals(0xffffffff, returnMaxUint32v2());
|
|
Expect.equals(-0x80, returnMinInt8v2());
|
|
Expect.equals(-0x8000, returnMinInt16v2());
|
|
Expect.equals(-0x80000000, returnMinInt32v2());
|
|
|
|
// Upper bits propper, should work without truncation.
|
|
Expect.equals(1, takeMaxUint8(0xff));
|
|
Expect.equals(1, takeMaxUint16(0xffff));
|
|
Expect.equals(1, takeMaxUint32(0xffffffff));
|
|
Expect.equals(1, takeMinInt8(-0x80));
|
|
Expect.equals(1, takeMinInt16(-0x8000));
|
|
Expect.equals(1, takeMinInt32(-0x80000000));
|
|
Expect.equals(
|
|
1,
|
|
takeMaxUint8x10(
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff));
|
|
// Upper bits garbage, needs to truncate.
|
|
Expect.equals(1, takeMaxUint8(0xabcff));
|
|
Expect.equals(1, takeMaxUint16(0xabcffff));
|
|
Expect.equals(1, takeMaxUint32(0xabcffffffff));
|
|
Expect.equals(1, takeMinInt8(0x8abc80));
|
|
Expect.equals(1, takeMinInt16(0x8abc8000));
|
|
Expect.equals(1, takeMinInt32(0x8abc80000000));
|
|
Expect.equals(
|
|
1,
|
|
takeMaxUint8x10(0xabcff, 0xabcff, 0xabcff, 0xabcff, 0xabcff, 0xabcff,
|
|
0xabcff, 0xabcff, 0xabcff, 0xabcff));
|
|
}
|
|
|
|
QuadOp uintComputation = ffiTestFunctions
|
|
.lookupFunction<NativeQuadOpUnsigned, QuadOp>("UintComputation");
|
|
|
|
void test64bitInterpretations() {
|
|
// 2 ^ 63 - 1
|
|
Expect.equals(
|
|
0x7FFFFFFFFFFFFFFF, uintComputation(0, 0, 0, 0x7FFFFFFFFFFFFFFF));
|
|
// -2 ^ 63 interpreted as 2 ^ 63
|
|
Expect.equals(
|
|
-0x8000000000000000, uintComputation(0, 0, 0, -0x8000000000000000));
|
|
// -1 interpreted as 2 ^ 64 - 1
|
|
Expect.equals(-1, uintComputation(0, 0, 0, -1));
|
|
}
|
|
|
|
typedef NativeSenaryOp = Int64 Function(
|
|
Int8, Int16, Int32, Uint8, Uint16, Uint32);
|
|
typedef SenaryOp = int Function(int, int, int, int, int, int);
|
|
|
|
SenaryOp sumSmallNumbers = ffiTestFunctions
|
|
.lookupFunction<NativeSenaryOp, SenaryOp>("SumSmallNumbers");
|
|
|
|
void testTruncation() {
|
|
sumSmallNumbers(128, 0, 0, 0, 0, 0);
|
|
sumSmallNumbers(-129, 0, 0, 0, 0, 0);
|
|
sumSmallNumbers(0, 0, 0, 256, 0, 0);
|
|
sumSmallNumbers(0, 0, 0, -1, 0, 0);
|
|
|
|
sumSmallNumbers(0, 0x8000, 0, 0, 0, 0);
|
|
sumSmallNumbers(0, 0xFFFFFFFFFFFF7FFF, 0, 0, 0, 0);
|
|
sumSmallNumbers(0, 0, 0, 0, 0x10000, 0);
|
|
sumSmallNumbers(0, 0, 0, 0, -1, 0);
|
|
|
|
Expect.equals(0xFFFFFFFF80000000, sumSmallNumbers(0, 0, 0x80000000, 0, 0, 0));
|
|
Expect.equals(
|
|
0x000000007FFFFFFF, sumSmallNumbers(0, 0, 0xFFFFFFFF7FFFFFFF, 0, 0, 0));
|
|
Expect.equals(0, sumSmallNumbers(0, 0, 0, 0, 0, 0x100000000));
|
|
Expect.equals(0xFFFFFFFF, sumSmallNumbers(0, 0, 0, 0, 0, -1));
|
|
}
|
|
|
|
typedef NativeDoubleUnaryOp = Double Function(Double);
|
|
typedef DoubleUnaryOp = double Function(double);
|
|
|
|
DoubleUnaryOp times1_337Double = ffiTestFunctions
|
|
.lookupFunction<NativeDoubleUnaryOp, DoubleUnaryOp>("Times1_337Double");
|
|
|
|
void testNativeFunctionDoubles() {
|
|
Expect.approxEquals(2.0 * 1.337, times1_337Double(2.0));
|
|
}
|
|
|
|
typedef NativeFloatUnaryOp = Float Function(Float);
|
|
|
|
DoubleUnaryOp times1_337Float = ffiTestFunctions
|
|
.lookupFunction<NativeFloatUnaryOp, DoubleUnaryOp>("Times1_337Float");
|
|
|
|
void testNativeFunctionFloats() {
|
|
Expect.approxEquals(1337.0, times1_337Float(1000.0));
|
|
}
|
|
|
|
typedef NativeDecenaryOp = IntPtr Function(IntPtr, IntPtr, IntPtr, IntPtr,
|
|
IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr);
|
|
typedef NativeDecenaryOp2 = Int16 Function(
|
|
Int8, Int16, Int8, Int16, Int8, Int16, Int8, Int16, Int8, Int16);
|
|
typedef DecenaryOp = int Function(
|
|
int, int, int, int, int, int, int, int, int, int);
|
|
|
|
DecenaryOp sumManyInts = ffiTestFunctions
|
|
.lookupFunction<NativeDecenaryOp, DecenaryOp>("SumManyInts");
|
|
|
|
void testNativeFunctionManyArguments1() {
|
|
Expect.equals(55, sumManyInts(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
|
|
}
|
|
|
|
DecenaryOp sumManySmallInts = ffiTestFunctions
|
|
.lookupFunction<NativeDecenaryOp2, DecenaryOp>("SumManySmallInts");
|
|
|
|
void testNativeFunctionManyArguments5() {
|
|
Expect.equals(55, sumManySmallInts(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
|
|
}
|
|
|
|
typedef NativeUndenaryOp = IntPtr Function(IntPtr, IntPtr, IntPtr, IntPtr,
|
|
IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr);
|
|
typedef UndenaryOp = int Function(
|
|
int, int, int, int, int, int, int, int, int, int, int);
|
|
|
|
UndenaryOp sumManyIntsOdd = ffiTestFunctions
|
|
.lookupFunction<NativeUndenaryOp, UndenaryOp>("SumManyIntsOdd");
|
|
|
|
void testNativeFunctionManyArguments4() {
|
|
Expect.equals(66, sumManyIntsOdd(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11));
|
|
}
|
|
|
|
typedef NativeDoubleDecenaryOp = Double Function(Double, Double, Double, Double,
|
|
Double, Double, Double, Double, Double, Double);
|
|
typedef DoubleDecenaryOp = double Function(double, double, double, double,
|
|
double, double, double, double, double, double);
|
|
|
|
DoubleDecenaryOp sumManyDoubles = ffiTestFunctions
|
|
.lookupFunction<NativeDoubleDecenaryOp, DoubleDecenaryOp>("SumManyDoubles");
|
|
|
|
void testNativeFunctionManyArguments2() {
|
|
Expect.approxEquals(
|
|
55.0, sumManyDoubles(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0));
|
|
}
|
|
|
|
typedef NativeVigesimalOp = Double Function(
|
|
IntPtr,
|
|
Float,
|
|
IntPtr,
|
|
Double,
|
|
IntPtr,
|
|
Float,
|
|
IntPtr,
|
|
Double,
|
|
IntPtr,
|
|
Float,
|
|
IntPtr,
|
|
Double,
|
|
IntPtr,
|
|
Float,
|
|
IntPtr,
|
|
Double,
|
|
IntPtr,
|
|
Float,
|
|
IntPtr,
|
|
Double);
|
|
typedef VigesimalOp = double Function(
|
|
int,
|
|
double,
|
|
int,
|
|
double,
|
|
int,
|
|
double,
|
|
int,
|
|
double,
|
|
int,
|
|
double,
|
|
int,
|
|
double,
|
|
int,
|
|
double,
|
|
int,
|
|
double,
|
|
int,
|
|
double,
|
|
int,
|
|
double);
|
|
|
|
VigesimalOp sumManyNumbers = ffiTestFunctions
|
|
.lookupFunction<NativeVigesimalOp, VigesimalOp>("SumManyNumbers");
|
|
|
|
void testNativeFunctionManyArguments3() {
|
|
Expect.approxEquals(
|
|
210.0,
|
|
sumManyNumbers(1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9, 10.0, 11, 12.0, 13,
|
|
14.0, 15, 16.0, 17, 18.0, 19, 20.0));
|
|
}
|
|
|
|
typedef Int64PointerUnOp = Pointer<Int64> Function(Pointer<Int64>);
|
|
|
|
Int64PointerUnOp assign1337Index1 = ffiTestFunctions
|
|
.lookupFunction<Int64PointerUnOp, Int64PointerUnOp>("Assign1337Index1");
|
|
|
|
void testNativeFunctionPointer() {
|
|
Pointer<Int64> p2 = calloc(2);
|
|
p2.value = 42;
|
|
p2[1] = 1000;
|
|
Pointer<Int64> result = assign1337Index1(p2);
|
|
Expect.equals(1337, result.value);
|
|
Expect.equals(1337, p2[1]);
|
|
Expect.equals(p2.elementAt(1).address, result.address);
|
|
calloc.free(p2);
|
|
}
|
|
|
|
Int64PointerUnOp nullableInt64ElemAt1 = ffiTestFunctions
|
|
.lookupFunction<Int64PointerUnOp, Int64PointerUnOp>("NullableInt64ElemAt1");
|
|
|
|
void testNullPointers() {
|
|
Pointer<Int64> result = nullableInt64ElemAt1(nullptr);
|
|
Expect.equals(result, nullptr);
|
|
|
|
Pointer<Int64> p2 = calloc(2);
|
|
result = nullableInt64ElemAt1(p2);
|
|
Expect.notEquals(result, nullptr);
|
|
calloc.free(p2);
|
|
}
|
|
|
|
typedef NativeFloatPointerToBool = Uint8 Function(Pointer<Float>);
|
|
typedef FloatPointerToBool = int Function(Pointer<Float>);
|
|
|
|
FloatPointerToBool isRoughly1337 = ffiTestFunctions.lookupFunction<
|
|
NativeFloatPointerToBool, FloatPointerToBool>("IsRoughly1337");
|
|
|
|
void testFloatRounding() {
|
|
Pointer<Float> p2 = calloc();
|
|
p2.value = 1337.0;
|
|
|
|
int result = isRoughly1337(p2);
|
|
Expect.equals(1, result);
|
|
|
|
calloc.free(p2);
|
|
}
|
|
|
|
typedef NativeFloatToVoid = Void Function(Float);
|
|
typedef DoubleToVoid = void Function(double);
|
|
|
|
DoubleToVoid devNullFloat = ffiTestFunctions
|
|
.lookupFunction<NativeFloatToVoid, DoubleToVoid>("DevNullFloat");
|
|
|
|
void testVoidReturn() {
|
|
devNullFloat(1337.0);
|
|
|
|
dynamic loseSignature = devNullFloat;
|
|
dynamic result = loseSignature(1337.0);
|
|
Expect.isNull(result);
|
|
}
|
|
|
|
typedef NativeVoidToFloat = Float Function();
|
|
typedef VoidToDouble = double Function();
|
|
|
|
VoidToDouble inventFloatValue = ffiTestFunctions
|
|
.lookupFunction<NativeVoidToFloat, VoidToDouble>("InventFloatValue");
|
|
|
|
void testNoArgs() {
|
|
double result = inventFloatValue();
|
|
Expect.approxEquals(1337.0, result);
|
|
}
|
|
|
|
// Returns a possibly ofuscated 'arg2' identifier.
|
|
String get arg2ObfuscatedName {
|
|
final str = (arg2: 0).toString();
|
|
return str.substring('('.length, str.length - ': 0)'.length);
|
|
}
|
|
|
|
void testNativeFunctionNullableInt() {
|
|
final sumPlus42 = ffiTestFunctions.lookupFunction<
|
|
Int32 Function(Int32, Int32), int Function(int, int?)>("SumPlus42");
|
|
|
|
try {
|
|
sumPlus42(3, null);
|
|
} catch (e) {
|
|
// TODO(http://dartbug.com/47098): Save param names to dwarf.
|
|
Expect.isTrue(e.toString().contains(arg2ObfuscatedName) ||
|
|
e.toString().contains('<optimized out>'));
|
|
}
|
|
}
|