blob: ab650584b6890325583d4573b0beb39e279ab4d6 [file] [log] [blame]
// 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:expect/expect.dart';
import 'package:js/js.dart';
const dart2js = const bool.fromEnvironment('dart.library._dart2js_only');
const ddc = const bool.fromEnvironment('dart.library._ddc_only');
dynamic obj;
@JS()
external dynamic eval(String code);
@JS()
@pragma('dart2js:never-inline')
external String returnsNull();
@JS()
@pragma('dart2js:prefer-inline')
external String returnsNullInlined();
@JS()
@pragma('dart2js:never-inline')
external String returnsUndefined();
@JS()
@pragma('dart2js:prefer-inline')
external String returnsUndefinedInlined();
@JS()
@pragma('dart2js:never-inline')
external int get getsNull;
@JS()
@pragma('dart2js:prefer-inline')
external int get getsNullInlined;
@JS()
@pragma('dart2js:never-inline')
external int get undefinedGetter;
@JS()
@pragma('dart2js:prefer-inline')
external int get undefinedGetterInlined;
@JS()
@pragma('dart2js:never-inline')
external double Function() get getterFunctionReturnsNull;
@JS()
@pragma('dart2js:prefer-inline')
external double Function() get getterFunctionReturnsNullInlined;
@JS()
@pragma('dart2js:never-inline')
external double Function() get getterFunctionReturnsUndefined;
@JS()
@pragma('dart2js:prefer-inline')
external double Function() get getterFunctionReturnsUndefinedInlined;
@JS()
@pragma('dart2js:never-inline')
external bool nullField;
@JS()
@pragma('dart2js:prefer-inline')
external bool nullFieldInlined;
@JS()
@pragma('dart2js:never-inline')
external bool undefinedField;
@JS()
@pragma('dart2js:prefer-inline')
external bool undefinedFieldInlined;
@JS()
@pragma('dart2js:never-inline')
external int? nullableFunction();
@JS()
@pragma('dart2js:prefer-inline')
external int? nullableFunctionInlined();
@JS()
@pragma('dart2js:never-inline')
external int? get nullableGetter;
@JS()
@pragma('dart2js:prefer-inline')
external int? get nullableGetterInlined;
@JS()
@pragma('dart2js:never-inline')
external int? nullableField;
@JS()
@pragma('dart2js:prefer-inline')
external int? nullableFieldInlined;
@JS()
@anonymous
class SomeClass {
@pragma('dart2js:never-inline')
external String returnsNull();
@pragma('dart2js:prefer-inline')
external String returnsNullInlined();
@pragma('dart2js:never-inline')
external String returnsUndefined();
@pragma('dart2js:prefer-inline')
external String returnsUndefinedInlined();
@pragma('dart2js:never-inline')
external int get getsNull;
@pragma('dart2js:prefer-inline')
external int get getsNullInlined;
@pragma('dart2js:never-inline')
external int get undefinedGetter;
@pragma('dart2js:prefer-inline')
external int get undefinedGetterInlined;
@pragma('dart2js:never-inline')
external double Function() get getterFunctionReturnsNull;
@pragma('dart2js:prefer-inline')
external double Function() get getterFunctionReturnsNullInlined;
@pragma('dart2js:never-inline')
external double Function() get getterFunctionReturnsUndefined;
@pragma('dart2js:prefer-inline')
external double Function() get getterFunctionReturnsUndefinedInlined;
@pragma('dart2js:never-inline')
external bool nullField;
@pragma('dart2js:prefer-inline')
external bool nullFieldInlined;
@pragma('dart2js:never-inline')
external bool undefinedField;
@pragma('dart2js:prefer-inline')
external bool undefinedFieldInlined;
@pragma('dart2js:never-inline')
external int? nullableFunction();
@pragma('dart2js:prefer-inline')
external int? nullableFunctionInlined();
@pragma('dart2js:never-inline')
external int? get nullableGetter;
@pragma('dart2js:prefer-inline')
external int? get nullableGetterInlined;
@pragma('dart2js:never-inline')
external int? nullableField;
@pragma('dart2js:prefer-inline')
external int? nullableFieldInlined;
}
@JS()
@anonymous
class AnotherClass {
@pragma('dart2js:never-inline')
external static String staticReturnsNull();
@pragma('dart2js:prefer-inline')
external static String staticReturnsNullInlined();
@pragma('dart2js:never-inline')
external static String staticReturnsUndefined();
@pragma('dart2js:prefer-inline')
external static String staticReturnsUndefinedInlined();
@pragma('dart2js:never-inline')
external static int get staticGetsNull;
@pragma('dart2js:prefer-inline')
external static int get staticGetsNullInlined;
@pragma('dart2js:never-inline')
external static int get staticUndefinedGetter;
@pragma('dart2js:prefer-inline')
external static int get staticUndefinedGetterInlined;
@pragma('dart2js:never-inline')
external static double Function() get staticGetterFunctionReturnsNull;
@pragma('dart2js:prefer-inline')
external static double Function() get staticGetterFunctionReturnsNullInlined;
@pragma('dart2js:never-inline')
external static double Function() get staticGetterFunctionReturnsUndefined;
@pragma('dart2js:prefer-inline')
external static double Function()
get staticGetterFunctionReturnsUndefinedInlined;
@pragma('dart2js:never-inline')
external static bool staticNullField;
@pragma('dart2js:prefer-inline')
external static bool staticNullFieldInlined;
@pragma('dart2js:never-inline')
external static bool staticUndefinedField;
@pragma('dart2js:prefer-inline')
external static bool staticUndefinedFieldInlined;
@pragma('dart2js:never-inline')
external static int? staticNullableFunction();
@pragma('dart2js:prefer-inline')
external static int? staticNullableFunctionInlined();
@pragma('dart2js:never-inline')
external static int? get staticNullableGetter;
@pragma('dart2js:prefer-inline')
external static int? get staticNullableGetterInlined;
@pragma('dart2js:never-inline')
external static int? staticNullableField;
@pragma('dart2js:prefer-inline')
external static int? staticNullableFieldInlined;
}
@JS()
class NamedClass {
@pragma('dart2js:never-inline')
external String returnsNull();
@pragma('dart2js:prefer-inline')
external String returnsNullInlined();
@pragma('dart2js:never-inline')
external String returnsUndefined();
@pragma('dart2js:prefer-inline')
external String returnsUndefinedInlined();
@pragma('dart2js:never-inline')
external static String staticReturnsNull();
@pragma('dart2js:prefer-inline')
external static String staticReturnsNullInlined();
@pragma('dart2js:never-inline')
external static String staticReturnsUndefined();
@pragma('dart2js:prefer-inline')
external static String staticReturnsUndefinedInlined();
@pragma('dart2js:never-inline')
external int get getsNull;
@pragma('dart2js:prefer-inline')
external int get getsNullInlined;
@pragma('dart2js:never-inline')
external int get undefinedGetter;
@pragma('dart2js:prefer-inline')
external int get undefinedGetterInlined;
@pragma('dart2js:never-inline')
external static int get staticGetsNull;
@pragma('dart2js:prefer-inline')
external static int get staticGetsNullInlined;
@pragma('dart2js:never-inline')
external static int get staticUndefinedGetter;
@pragma('dart2js:prefer-inline')
external static int get staticUndefinedGetterInlined;
@pragma('dart2js:never-inline')
external double Function() get getterFunctionReturnsNull;
@pragma('dart2js:prefer-inline')
external double Function() get getterFunctionReturnsNullInlined;
@pragma('dart2js:never-inline')
external double Function() get getterFunctionReturnsUndefined;
@pragma('dart2js:prefer-inline')
external double Function() get getterFunctionReturnsUndefinedInlined;
@pragma('dart2js:never-inline')
external static double Function() get staticGetterFunctionReturnsNull;
@pragma('dart2js:prefer-inline')
external static double Function() get staticGetterFunctionReturnsNullInlined;
@pragma('dart2js:never-inline')
external static double Function() get staticGetterFunctionReturnsUndefined;
@pragma('dart2js:prefer-inline')
external static double Function()
get staticGetterFunctionReturnsUndefinedInlined;
@pragma('dart2js:never-inline')
external bool nullField;
@pragma('dart2js:prefer-inline')
external bool nullFieldInlined;
@pragma('dart2js:never-inline')
external bool undefinedField;
@pragma('dart2js:prefer-inline')
external bool undefinedFieldInlined;
@pragma('dart2js:never-inline')
external int? nullableFunction();
@pragma('dart2js:prefer-inline')
external int? nullableFunctionInlined();
@pragma('dart2js:never-inline')
external int? get nullableGetter;
@pragma('dart2js:prefer-inline')
external int? get nullableGetterInlined;
@pragma('dart2js:never-inline')
external int? nullableField;
@pragma('dart2js:prefer-inline')
external int? nullableFieldInlined;
@pragma('dart2js:never-inline')
external static bool staticNullField;
@pragma('dart2js:prefer-inline')
external static bool staticNullFieldInlined;
@pragma('dart2js:never-inline')
external static bool staticUndefinedField;
@pragma('dart2js:prefer-inline')
external static bool staticUndefinedFieldInlined;
@pragma('dart2js:never-inline')
external static int? staticNullableFunction();
@pragma('dart2js:prefer-inline')
external static int? staticNullableFunctionInlined();
@pragma('dart2js:never-inline')
external static int? get staticNullableGetter;
@pragma('dart2js:prefer-inline')
external static int? get staticNullableGetterInlined;
@pragma('dart2js:never-inline')
external static int? staticNullableField;
@pragma('dart2js:prefer-inline')
external static int? staticNullableFieldInlined;
external factory NamedClass.createNamedClass();
}
@JS()
external SomeClass createSomeClass();
void topLevelMemberTests({required bool checksEnabled}) {
// Provide at least one nullable value to [Expect.isNull] so that dart2js
// doesn't discard the branch.
Expect.isNull(null);
eval(r'self.returnsNull = function() { return null; }');
Expect.throwsTypeErrorWhen(checksEnabled, () => returnsNull());
eval(r'self.returnsUndefined = function() { return void 0; }');
Expect.throwsTypeErrorWhen(checksEnabled, () => returnsUndefined());
var functionTearoff = returnsNull;
Expect.throwsTypeErrorWhen(checksEnabled, () => functionTearoff());
functionTearoff = returnsUndefined;
Expect.throwsTypeErrorWhen(checksEnabled, () => functionTearoff());
eval(r'self.getsNull = null;');
Expect.throwsTypeErrorWhen(checksEnabled, () => getsNull);
Expect.throwsTypeErrorWhen(checksEnabled, () => undefinedGetter);
// At the time this test was written, getters that return function types don't
// get the same wrappers as function tearoffs so there isn't an opportunity to
// check the return values so they can still leak null or undefined through
// from the JavaScript side.
eval(r'self.getterFunctionReturnsNull = function() { return null; };');
Expect.isNull(getterFunctionReturnsNull());
var getterFunction = getterFunctionReturnsNull;
Expect.isNull(getterFunction());
eval(r'self.getterFunctionReturnsUndefined = function() { '
'return void 0; };');
Expect.isNull(getterFunctionReturnsUndefined());
getterFunction = getterFunctionReturnsUndefined;
Expect.isNull(getterFunction());
eval(r'self.nullField = null;');
Expect.throwsTypeErrorWhen(checksEnabled, () => nullField);
Expect.throwsTypeErrorWhen(checksEnabled, () => undefinedField);
eval(r'self.nullableFunction = function() { return null; };');
Expect.isNull(nullableFunction());
eval(r'self.nullableGetter = null;');
Expect.isNull(nullableGetter);
eval(r'self.nullableField = null;');
Expect.isNull(nullableField);
}
void inlinedTopLevelMemberTests({required bool checksEnabled}) {
// Provide at least one nullable value to [Expect.isNull] so that dart2js
// doesn't discard the branch.
Expect.isNull(null);
eval(r'self.returnsNullInlined = function() { return null; }');
Expect.throwsTypeErrorWhen(checksEnabled, () => returnsNullInlined());
eval(r'self.returnsUndefinedInlined = function() { return void 0; }');
Expect.throwsTypeErrorWhen(checksEnabled, () => returnsUndefinedInlined());
var functionTearoff = returnsNullInlined;
Expect.throwsTypeErrorWhen(checksEnabled, () => functionTearoff());
functionTearoff = returnsUndefinedInlined;
Expect.throwsTypeErrorWhen(checksEnabled, () => functionTearoff());
eval(r'self.getsNullInlined = null;');
Expect.throwsTypeErrorWhen(checksEnabled, () => getsNullInlined);
Expect.throwsTypeErrorWhen(checksEnabled, () => undefinedGetterInlined);
// At the time this test was written, getters that return function types don't
// get the same wrappers as function tearoffs so there isn't an opportunity to
// check the return values so they can still leak null or undefined through
// from the JavaScript side.
eval(r'self.getterFunctionReturnsNullInlined = function() { return null; };');
Expect.isNull(getterFunctionReturnsNullInlined());
var getterFunction = getterFunctionReturnsNullInlined;
Expect.isNull(getterFunction());
eval(r'self.getterFunctionReturnsUndefinedInlined = function() { '
'return void 0; };');
Expect.isNull(getterFunctionReturnsUndefinedInlined());
getterFunction = getterFunctionReturnsUndefinedInlined;
Expect.isNull(getterFunction());
eval(r'self.nullFieldInlined = null;');
Expect.throwsTypeErrorWhen(checksEnabled, () => nullFieldInlined);
Expect.throwsTypeErrorWhen(checksEnabled, () => undefinedFieldInlined);
eval(r'self.nullableFunctionInlined = function() { return null; };');
Expect.isNull(nullableFunctionInlined());
eval(r'self.nullableGetterInlined = null;');
Expect.isNull(nullableGetterInlined);
eval(r'self.nullableFieldInlined = null;');
Expect.isNull(nullableFieldInlined);
}
void anonymousClassSetup() {
eval(r'''self.createSomeClass = function() {
return {
"returnsNull": function() { return null; },
"returnsNullInlined": function() { return null; },
"returnsUndefined": function() { return void 0; },
"returnsUndefinedInlined": function() { return void 0; },
"getsNull" : null,
"getsNullInlined" : null,
"jsGetsNull": { get: function() { return null; } },
"jsGetsNullInlined": { get: function() { return null; } },
"jsGetsUndefined": { get: function() { return void 0; } },
"jsGetsUndefinedInlined": { get: function() { return void 0; } },
"getterFunctionReturnsNull": function() { return null; },
"getterFunctionReturnsNullInlined": function() { return null; },
"getterFunctionReturnsUndefined": function() { return void 0; },
"getterFunctionReturnsUndefinedInlined": function() { return void 0; },
"nullField" : null,
"nullFieldInlined" : null,
"nullableFunction": function() { return null; },
"nullableFunctionInlined": function() { return null; },
"nullableGetter": null,
"nullableGetterInlined": null,
"nullableField": null,
"nullableFieldInlined": null,
};
};
''');
eval(r'''self.AnotherClass = class AnotherClass {
static staticReturnsNull() { return null; }
static staticReturnsNullInlined() { return null; }
static staticReturnsUndefined() { return void 0; }
static staticReturnsUndefinedInlined() { return void 0; }
static get staticGetsNull() { return null; }
static get staticGetsNullInlined() { return null; }
static get staticGetterFunctionReturnsNull() {
return function() { return null; };
}
static get staticGetterFunctionReturnsNullInlined() {
return function() { return null; };
}
static get staticGetterFunctionReturnsUndefined() {
return function() { return void 0; };
}
static get staticGetterFunctionReturnsUndefinedInlined() {
return function() { return void 0; };
}
static staticNullableFunction() { return null; }
static staticNullableFunctionInlined() { return null; }
static get staticNullableGetter() { return null; }
static get staticNullableGetterInlined() { return null; }
static staticNullableField = null;
static staticNullableFieldInlined = null;
};''');
}
void anonymousClassTests({required bool checksEnabled}) {
// Provide at least one nullable value to [Expect.isNull] so that dart2js
// doesn't discard the branch.
Expect.isNull(null);
var x = createSomeClass();
Expect.throwsTypeErrorWhen(checksEnabled, () => x.returnsNull());
Expect.throwsTypeErrorWhen(checksEnabled, () => x.returnsUndefined());
// In dart2js, a tearoff of an interop method is indistinguishable from a
// getter returning the underlying function.
var functionTearoff = x.returnsNull;
Expect.throwsTypeErrorWhen(ddc && checksEnabled, () => functionTearoff());
functionTearoff = x.returnsUndefined;
Expect.throwsTypeErrorWhen(ddc && checksEnabled, () => functionTearoff());
Expect.throwsTypeErrorWhen(checksEnabled, () => x.getsNull);
Expect.throwsTypeErrorWhen(checksEnabled, () => x.undefinedGetter);
// Immediate invocations of instance getters are seen as function calls so
// the results get checked.
Expect.throwsTypeErrorWhen(
checksEnabled, () => x.getterFunctionReturnsNull());
Expect.throwsTypeErrorWhen(
checksEnabled, () => x.getterFunctionReturnsUndefined());
// At the time this test was written, getters that return function types don't
// get the same wrappers as function tearoffs so there isn't an opportunity to
// check the return values so they can still leak null or undefined through
// from the JavaScript side.
var getterFunction = x.getterFunctionReturnsNull;
Expect.isNull(getterFunction());
getterFunction = x.getterFunctionReturnsUndefined;
Expect.isNull(getterFunction());
Expect.throwsTypeErrorWhen(checksEnabled, () => x.nullField);
Expect.throwsTypeErrorWhen(checksEnabled, () => x.undefinedField);
Expect.isNull(x.nullableFunction());
Expect.isNull(x.nullableGetter);
Expect.isNull(x.nullableField);
Expect.throwsTypeErrorWhen(
checksEnabled, () => AnotherClass.staticReturnsNull());
Expect.throwsTypeErrorWhen(
checksEnabled, () => AnotherClass.staticReturnsUndefined());
functionTearoff = AnotherClass.staticReturnsNull;
Expect.throwsTypeErrorWhen(checksEnabled, () => functionTearoff());
functionTearoff = AnotherClass.staticReturnsUndefined;
Expect.throwsTypeErrorWhen(checksEnabled, () => functionTearoff());
Expect.throwsTypeErrorWhen(checksEnabled, () => AnotherClass.staticGetsNull);
Expect.throwsTypeErrorWhen(
checksEnabled, () => AnotherClass.staticUndefinedGetter);
// At the time this test was written, getters that return function types don't
// get the same wrappers as function tearoffs so there isn't an opportunity to
// check the return values so they can still leak null or undefined through
// from the JavaScript side.
Expect.isNull(AnotherClass.staticGetterFunctionReturnsNull());
Expect.isNull(AnotherClass.staticGetterFunctionReturnsUndefined());
getterFunction = AnotherClass.staticGetterFunctionReturnsNull;
Expect.isNull(getterFunction());
getterFunction = AnotherClass.staticGetterFunctionReturnsUndefined;
Expect.isNull(getterFunction());
Expect.throwsTypeErrorWhen(checksEnabled, () => AnotherClass.staticNullField);
Expect.throwsTypeErrorWhen(
checksEnabled, () => AnotherClass.staticUndefinedField);
Expect.isNull(AnotherClass.staticNullableFunction());
Expect.isNull(AnotherClass.staticNullableGetter);
Expect.isNull(AnotherClass.staticNullableField);
}
void inlinedAnonymousClassTests({required bool checksEnabled}) {
// Provide at least one nullable value to [Expect.isNull] so that dart2js
// doesn't discard the branch.
Expect.isNull(null);
var x = createSomeClass();
Expect.throwsTypeErrorWhen(checksEnabled, () => x.returnsNullInlined());
Expect.throwsTypeErrorWhen(checksEnabled, () => x.returnsUndefinedInlined());
// In dart2js, a tearoff of an interop method is indistinguishable from a
// getter returning the underlying function.
var functionTearoff = x.returnsNullInlined;
Expect.throwsTypeErrorWhen(ddc && checksEnabled, () => functionTearoff());
functionTearoff = x.returnsUndefinedInlined;
Expect.throwsTypeErrorWhen(ddc && checksEnabled, () => functionTearoff());
Expect.throwsTypeErrorWhen(checksEnabled, () => x.getsNullInlined);
Expect.throwsTypeErrorWhen(checksEnabled, () => x.undefinedGetterInlined);
// Immediate invocations of instance getters are seen as function calls so
// the results get checked.
Expect.throwsTypeErrorWhen(
checksEnabled, () => x.getterFunctionReturnsNullInlined());
Expect.throwsTypeErrorWhen(
checksEnabled, () => x.getterFunctionReturnsUndefinedInlined());
// At the time this test was written, getters that return function types don't
// get the same wrappers as function tearoffs so there isn't an opportunity to
// check the return values so they can still leak null or undefined through
// from the JavaScript side.
var getterFunction = x.getterFunctionReturnsNullInlined;
Expect.isNull(getterFunction());
getterFunction = x.getterFunctionReturnsUndefinedInlined;
Expect.isNull(getterFunction());
Expect.throwsTypeErrorWhen(checksEnabled, () => x.nullFieldInlined);
Expect.throwsTypeErrorWhen(checksEnabled, () => x.undefinedFieldInlined);
Expect.isNull(x.nullableFunctionInlined());
Expect.isNull(x.nullableGetterInlined);
Expect.isNull(x.nullableFieldInlined);
Expect.throwsTypeErrorWhen(
checksEnabled, () => AnotherClass.staticReturnsNullInlined());
Expect.throwsTypeErrorWhen(
checksEnabled, () => AnotherClass.staticReturnsUndefinedInlined());
functionTearoff = AnotherClass.staticReturnsNullInlined;
Expect.throwsTypeErrorWhen(checksEnabled, () => functionTearoff());
functionTearoff = AnotherClass.staticReturnsUndefinedInlined;
Expect.throwsTypeErrorWhen(checksEnabled, () => functionTearoff());
Expect.throwsTypeErrorWhen(
checksEnabled, () => AnotherClass.staticGetsNullInlined);
Expect.throwsTypeErrorWhen(
checksEnabled, () => AnotherClass.staticUndefinedGetterInlined);
// At the time this test was written, getters that return function types don't
// get the same wrappers as function tearoffs so there isn't an opportunity to
// check the return values so they can still leak null or undefined through
// from the JavaScript side.
Expect.isNull(AnotherClass.staticGetterFunctionReturnsNullInlined());
Expect.isNull(AnotherClass.staticGetterFunctionReturnsUndefinedInlined());
getterFunction = AnotherClass.staticGetterFunctionReturnsNullInlined;
Expect.isNull(getterFunction());
getterFunction = AnotherClass.staticGetterFunctionReturnsUndefinedInlined;
Expect.isNull(getterFunction());
Expect.throwsTypeErrorWhen(
checksEnabled, () => AnotherClass.staticNullFieldInlined);
Expect.throwsTypeErrorWhen(
checksEnabled, () => AnotherClass.staticUndefinedFieldInlined);
Expect.isNull(AnotherClass.staticNullableFunctionInlined());
Expect.isNull(AnotherClass.staticNullableGetterInlined);
Expect.isNull(AnotherClass.staticNullableFieldInlined);
}
// DDC does not perform checks for dynamic invocations.
void dynamicAnonymousClassTests({required bool checksEnabled}) {
// Provide at least one nullable value to [Expect.isNull] so that dart2js
// doesn't discard the branch.
Expect.isNull(null);
obj = createSomeClass();
Expect.throwsTypeErrorWhen(dart2js && checksEnabled, () => obj.returnsNull());
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.returnsUndefined());
// In dart2js, a tearoff of an interop method is indistinguishable from a
// getter returning the underlying function.
var functionTearoff = obj.returnsNull;
Expect.throwsTypeErrorWhen(false, () => functionTearoff());
functionTearoff = obj.returnsUndefined;
Expect.throwsTypeErrorWhen(false, () => functionTearoff());
Expect.throwsTypeErrorWhen(dart2js && checksEnabled, () => obj.getsNull);
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.undefinedGetter);
// Immediate invocations of instance getters are seen as function calls so
// the results get checked.
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.getterFunctionReturnsNull());
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.getterFunctionReturnsUndefined());
// At the time this test was written, getters that return function types don't
// get the same wrappers as function tearoffs so there isn't an opportunity to
// check the return values so they can still leak null or undefined through
// from the JavaScript side.
var getterFunction = obj.getterFunctionReturnsNull;
Expect.isNull(getterFunction());
getterFunction = obj.getterFunctionReturnsUndefined;
Expect.isNull(getterFunction());
Expect.throwsTypeErrorWhen(dart2js && checksEnabled, () => obj.nullField);
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.undefinedField);
Expect.isNull(obj.nullableFunction());
Expect.isNull(obj.nullableGetter);
Expect.isNull(obj.nullableField);
}
// DDC does not perform checks for dynamic invocations.
void dynamicInlinedAnonymousClassTests({required bool checksEnabled}) {
// Provide at least one nullable value to [Expect.isNull] so that dart2js
// doesn't discard the branch.
Expect.isNull(null);
obj = createSomeClass();
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.returnsNullInlined());
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.returnsUndefinedInlined());
// In dart2js, a tearoff of an interop method is indistinguishable from a
// getter returning the underlying function.
var functionTearoff = obj.returnsNullInlined;
Expect.throwsTypeErrorWhen(false, () => functionTearoff());
functionTearoff = obj.returnsUndefinedInlined;
Expect.throwsTypeErrorWhen(false, () => functionTearoff());
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.getsNullInlined);
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.undefinedGetterInlined);
// Immediate invocations of instance getters are seen as function calls so
// the results get checked.
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.getterFunctionReturnsNullInlined());
Expect.throwsTypeErrorWhen(dart2js && checksEnabled,
() => obj.getterFunctionReturnsUndefinedInlined());
// At the time this test was written, getters that return function types don't
// get the same wrappers as function tearoffs so there isn't an opportunity to
// check the return values so they can still leak null or undefined through
// from the JavaScript side.
var getterFunction = obj.getterFunctionReturnsNullInlined;
Expect.isNull(getterFunction());
getterFunction = obj.getterFunctionReturnsUndefinedInlined;
Expect.isNull(getterFunction());
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.nullFieldInlined);
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.undefinedFieldInlined);
Expect.isNull(obj.nullableFunctionInlined());
Expect.isNull(obj.nullableGetterInlined);
Expect.isNull(obj.nullableFieldInlined);
}
void namedClassSetup() {
eval(r'''self.NamedClass = class NamedClass {
returnsNull() { return null; }
returnsNullInlined() { return null; }
returnsUndefined() { return void 0; }
returnsUndefinedInlined() { return void 0; }
static staticReturnsNull() { return null; }
static staticReturnsNullInlined() { return null; }
static staticReturnsUndefined() { return void 0; }
static staticReturnsUndefinedInlined() { return void 0; }
get getsNull() { return null; }
get getsNullInlined() { return null; }
static get staticGetsNull() { return null; }
static get staticGetsNullInlined() { return null; }
get getterFunctionReturnsNull() {
return function() { return null; };
}
get getterFunctionReturnsNullInlined() {
return function() { return null; };
}
get getterFunctionReturnsUndefined() {
return function() { return void 0; };
}
get getterFunctionReturnsUndefinedInlined() {
return function() { return void 0; };
}
static get staticGetterFunctionReturnsNull() {
return function() { return null; };
}
static get staticGetterFunctionReturnsNullInlined() {
return function() { return null; };
}
static get staticGetterFunctionReturnsUndefined() {
return function() { return void 0; };
}
static get staticGetterFunctionReturnsUndefinedInlined() {
return function() { return void 0; };
}
nullableFunction() { return null; }
nullableFunctionInlined() { return null; }
get nullableGetter() { return null; }
get nullableGetterInlined() { return null; }
nullableField = null;
nullableFieldInlined = null;
static staticNullableFunction() { return null; }
static staticNullableFunctionInlined() { return null; }
static get staticNullableGetter() { return null; }
static get staticNullableGetterInlined() { return null; }
static staticNullableField = null;
static staticNullableFieldInlined = null;
static createNamedClass() { return new NamedClass(); }
};''');
}
void namedClassTests({required bool checksEnabled}) {
// Provide at least one nullable value to [Expect.isNull] so that dart2js
// doesn't discard the branch.
Expect.isNull(null);
var y = NamedClass.createNamedClass();
Expect.throwsTypeErrorWhen(checksEnabled, () => y.returnsNull());
Expect.throwsTypeErrorWhen(checksEnabled, () => y.returnsUndefined());
// In dart2js, a tearoff of an interop method is indistinguishable from a
// getter returning the underlying function.
var functionTearoff = y.returnsNull;
Expect.throwsTypeErrorWhen(ddc && checksEnabled, () => functionTearoff());
functionTearoff = y.returnsUndefined;
Expect.throwsTypeErrorWhen(ddc && checksEnabled, () => functionTearoff());
Expect.throwsTypeErrorWhen(
checksEnabled, () => NamedClass.staticReturnsNull());
Expect.throwsTypeErrorWhen(
checksEnabled, () => NamedClass.staticReturnsUndefined());
functionTearoff = NamedClass.staticReturnsNull;
Expect.throwsTypeErrorWhen(checksEnabled, () => functionTearoff());
functionTearoff = NamedClass.staticReturnsUndefined;
Expect.throwsTypeErrorWhen(checksEnabled, () => functionTearoff());
Expect.throwsTypeErrorWhen(checksEnabled, () => y.getsNull);
Expect.throwsTypeErrorWhen(checksEnabled, () => y.undefinedGetter);
// Immediate invocations of instance getters are seen as function calls so
// the results get checked.
Expect.throwsTypeErrorWhen(
checksEnabled, () => y.getterFunctionReturnsNull());
Expect.throwsTypeErrorWhen(
checksEnabled, () => y.getterFunctionReturnsUndefined());
Expect.throwsTypeErrorWhen(checksEnabled, () => NamedClass.staticGetsNull);
Expect.throwsTypeErrorWhen(
checksEnabled, () => NamedClass.staticUndefinedGetter);
// At the time this test was written, getters that return function types don't
// get the same wrappers as function tearoffs so there isn't an opportunity to
// check the return values so they can still leak null or undefined through
// from the JavaScript side.
var getterFunction = y.getterFunctionReturnsNull;
Expect.isNull(getterFunction());
getterFunction = y.getterFunctionReturnsNull;
Expect.isNull(getterFunction());
Expect.isNull(NamedClass.staticGetterFunctionReturnsNull());
Expect.isNull(NamedClass.staticGetterFunctionReturnsUndefined());
getterFunction = NamedClass.staticGetterFunctionReturnsNull;
Expect.isNull(getterFunction());
getterFunction = NamedClass.staticGetterFunctionReturnsUndefined;
Expect.isNull(getterFunction());
Expect.throwsTypeErrorWhen(checksEnabled, () => y.nullField);
Expect.throwsTypeErrorWhen(checksEnabled, () => y.undefinedField);
Expect.throwsTypeErrorWhen(checksEnabled, () => NamedClass.staticNullField);
Expect.throwsTypeErrorWhen(
checksEnabled, () => NamedClass.staticUndefinedField);
Expect.isNull(y.nullableFunction());
Expect.isNull(y.nullableGetter);
Expect.isNull(y.nullableField);
Expect.isNull(NamedClass.staticNullableFunction());
Expect.isNull(NamedClass.staticNullableGetter);
Expect.isNull(NamedClass.staticNullableField);
}
void inlinedNamedClassTests({required bool checksEnabled}) {
// Provide at least one nullable value to [Expect.isNull] so that dart2js
// doesn't discard the branch.
Expect.isNull(null);
var y = NamedClass.createNamedClass();
Expect.throwsTypeErrorWhen(checksEnabled, () => y.returnsNullInlined());
Expect.throwsTypeErrorWhen(checksEnabled, () => y.returnsUndefinedInlined());
// In dart2js, a tearoff of an interop method is indistinguishable from a
// getter returning the underlying function.
var functionTearoff = y.returnsNullInlined;
Expect.throwsTypeErrorWhen(ddc && checksEnabled, () => functionTearoff());
functionTearoff = y.returnsUndefinedInlined;
Expect.throwsTypeErrorWhen(ddc && checksEnabled, () => functionTearoff());
Expect.throwsTypeErrorWhen(
checksEnabled, () => NamedClass.staticReturnsNullInlined());
Expect.throwsTypeErrorWhen(
checksEnabled, () => NamedClass.staticReturnsUndefinedInlined());
functionTearoff = NamedClass.staticReturnsNullInlined;
Expect.throwsTypeErrorWhen(checksEnabled, () => functionTearoff());
functionTearoff = NamedClass.staticReturnsUndefinedInlined;
Expect.throwsTypeErrorWhen(checksEnabled, () => functionTearoff());
Expect.throwsTypeErrorWhen(checksEnabled, () => y.getsNullInlined);
Expect.throwsTypeErrorWhen(checksEnabled, () => y.undefinedGetterInlined);
// Immediate invocations of instance getters are seen as function calls so
// the results get checked.
Expect.throwsTypeErrorWhen(
checksEnabled, () => y.getterFunctionReturnsNullInlined());
Expect.throwsTypeErrorWhen(
checksEnabled, () => y.getterFunctionReturnsUndefinedInlined());
Expect.throwsTypeErrorWhen(
checksEnabled, () => NamedClass.staticGetsNullInlined);
Expect.throwsTypeErrorWhen(
checksEnabled, () => NamedClass.staticUndefinedGetterInlined);
// At the time this test was written, getters that return function types don't
// get the same wrappers as function tearoffs so there isn't an opportunity to
// check the return values so they can still leak null or undefined through
// from the JavaScript side.
var getterFunction = y.getterFunctionReturnsNullInlined;
Expect.isNull(getterFunction());
getterFunction = y.getterFunctionReturnsNullInlined;
Expect.isNull(getterFunction());
Expect.isNull(NamedClass.staticGetterFunctionReturnsNullInlined());
Expect.isNull(NamedClass.staticGetterFunctionReturnsUndefinedInlined());
getterFunction = NamedClass.staticGetterFunctionReturnsNullInlined;
Expect.isNull(getterFunction());
getterFunction = NamedClass.staticGetterFunctionReturnsUndefinedInlined;
Expect.isNull(getterFunction());
Expect.throwsTypeErrorWhen(checksEnabled, () => y.nullFieldInlined);
Expect.throwsTypeErrorWhen(checksEnabled, () => y.undefinedFieldInlined);
Expect.throwsTypeErrorWhen(
checksEnabled, () => NamedClass.staticNullFieldInlined);
Expect.throwsTypeErrorWhen(
checksEnabled, () => NamedClass.staticUndefinedFieldInlined);
Expect.isNull(y.nullableFunctionInlined());
Expect.isNull(y.nullableGetterInlined);
Expect.isNull(y.nullableFieldInlined);
Expect.isNull(NamedClass.staticNullableFunctionInlined());
Expect.isNull(NamedClass.staticNullableGetterInlined);
Expect.isNull(NamedClass.staticNullableFieldInlined);
}
// DDC does not perform checks for dynamic invocations.
void dynamicNamedClassTests({required bool checksEnabled}) {
// Provide at least one nullable value to [Expect.isNull] so that dart2js
// doesn't discard the branch.
Expect.isNull(null);
obj = NamedClass.createNamedClass();
Expect.throwsTypeErrorWhen(dart2js && checksEnabled, () => obj.returnsNull());
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.returnsUndefined());
// In dart2js, a tearoff of an interop method is indistinguishable from a
// getter returning the underlying function.
var functionTearoff = obj.returnsNull;
Expect.throwsTypeErrorWhen(false, () => functionTearoff());
functionTearoff = obj.returnsUndefined;
Expect.throwsTypeErrorWhen(false, () => functionTearoff());
Expect.throwsTypeErrorWhen(dart2js && checksEnabled, () => obj.getsNull);
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.undefinedGetter);
// Immediate invocations of instance getters are seen as function calls so
// the results get checked.
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.getterFunctionReturnsNull());
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.getterFunctionReturnsUndefined());
// At the time this test was written, getters that return function types don't
// get the same wrappers as function tearoffs so there isn't an opportunity to
// check the return values so they can still leak null or undefined through
// from the JavaScript side.
var getterFunction = obj.getterFunctionReturnsNull;
Expect.isNull(getterFunction());
getterFunction = obj.getterFunctionReturnsNull;
Expect.isNull(getterFunction());
Expect.throwsTypeErrorWhen(dart2js && checksEnabled, () => obj.nullField);
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.undefinedField);
Expect.isNull(obj.nullableFunction());
Expect.isNull(obj.nullableGetter);
Expect.isNull(obj.nullableField);
}
// DDC does not perform checks for dynamic invocations.
void dynamicInlinedNamedClassTests({required bool checksEnabled}) {
// Provide at least one nullable value to [Expect.isNull] so that dart2js
// doesn't discard the branch.
Expect.isNull(null);
obj = NamedClass.createNamedClass();
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.returnsNullInlined());
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.returnsUndefinedInlined());
// In dart2js, a tearoff of an interop method is indistinguishable from a
// getter returning the underlying function.
var functionTearoff = obj.returnsNullInlined;
Expect.throwsTypeErrorWhen(false, () => functionTearoff());
functionTearoff = obj.returnsUndefinedInlined;
Expect.throwsTypeErrorWhen(false, () => functionTearoff());
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.getsNullInlined);
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.undefinedGetterInlined);
// Immediate invocations of instance getters are seen as function calls so
// the results get checked.
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.getterFunctionReturnsNullInlined());
Expect.throwsTypeErrorWhen(dart2js && checksEnabled,
() => obj.getterFunctionReturnsUndefinedInlined());
// At the time this test was written, getters that return function types don't
// get the same wrappers as function tearoffs so there isn't an opportunity to
// check the return values so they can still leak null or undefined through
// from the JavaScript side.
var getterFunction = obj.getterFunctionReturnsNullInlined;
Expect.isNull(getterFunction());
getterFunction = obj.getterFunctionReturnsNullInlined;
Expect.isNull(getterFunction());
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.nullFieldInlined);
Expect.throwsTypeErrorWhen(
dart2js && checksEnabled, () => obj.undefinedFieldInlined);
Expect.isNull(obj.nullableFunctionInlined());
Expect.isNull(obj.nullableGetterInlined);
Expect.isNull(obj.nullableFieldInlined);
}
@JS()
class CollisionA {
@pragma('dart2js:prefer-inline')
external String foo([int? x]);
@pragma('dart2js:never-inline')
external String bar([int? x]);
external factory CollisionA.create();
}
@JS()
class CollisionB {
@pragma('dart2js:prefer-inline')
external String? foo();
@pragma('dart2js:never-inline')
external String? bar();
external factory CollisionB.create();
}
void collisionClassSetup() {
eval(r'''
self.CollisionA = class CollisionA {
foo(x) { return null; }
bar(x) { return null; }
static create() { return new CollisionA(); }
};
self.CollisionB = class CollisionB {
foo() { return null; }
bar() { return null; }
static create() { return new CollisionB(); }
};''');
}
void collisionTests({required bool checksEnabled}) {
// Provide at least one nullable value to [Expect.isNull] so that dart2js
// doesn't discard the branch.
Expect.isNull(null);
var a = CollisionA.create();
var b = CollisionB.create();
// In dart2js, the receiver type is erased to `LegacyJavaScriptObject`, so we
// cannot identify which class's method is being invoked and cannot emit a
// null check. DDC is able to identify the member being invoked.
Expect.throwsTypeErrorWhen(checksEnabled && ddc, () => a.foo());
Expect.throwsTypeErrorWhen(checksEnabled && ddc, () => a.bar());
Expect.isNull(b.foo());
Expect.isNull(b.bar());
// Because `foo$1` and `bar$1` only exist for [CollisionA] and not
// [CollisionB], dart2js still emits these checks in the callee.
Expect.throwsTypeErrorWhen(checksEnabled, () => a.foo(42));
Expect.throwsTypeErrorWhen(checksEnabled, () => a.bar(42));
}
void runTests({required bool checksEnabled}) {
topLevelMemberTests(checksEnabled: checksEnabled);
inlinedTopLevelMemberTests(checksEnabled: checksEnabled);
anonymousClassSetup();
anonymousClassTests(checksEnabled: checksEnabled);
inlinedAnonymousClassTests(checksEnabled: checksEnabled);
dynamicAnonymousClassTests(checksEnabled: checksEnabled);
dynamicInlinedAnonymousClassTests(checksEnabled: checksEnabled);
namedClassSetup();
namedClassTests(checksEnabled: checksEnabled);
inlinedNamedClassTests(checksEnabled: checksEnabled);
dynamicNamedClassTests(checksEnabled: checksEnabled);
dynamicInlinedNamedClassTests(checksEnabled: checksEnabled);
collisionClassSetup();
collisionTests(checksEnabled: checksEnabled);
}