// 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); }