1
0
mirror of https://github.com/dart-lang/sdk synced 2024-07-08 12:06:26 +00:00

[ddc] Enforce more null safety on js interop

- Add a runtime flag to enable checks on non-nullable APIs that
  return values from non-static JavaScript interop.
- Call a new helper method at the call site when these APIs are
  detected to perform the null check.
- Add test file for the cases we can detect and enforce.

NOTE: This does not make non-static JavaScript interop sound.
This only adds more checks to enforce soundness with respect
to nullability in some cases. There are still holes that will
never be closed due to the permissive nature of this form of
JavaScript interop.

Change-Id: I2f88d1543a683fdc84d764e2b0eaafeb0ca73107
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/358581
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Mayank Patke <fishythefish@google.com>
Commit-Queue: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
This commit is contained in:
Nicholas Shahan 2024-04-03 23:20:15 +00:00 committed by Commit Queue
parent 51d7b8477d
commit 2379cdc080
10 changed files with 428 additions and 19 deletions

View File

@ -4899,7 +4899,10 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
// Otherwise generate this as a normal typed property get.
var jsMemberName =
_emitMemberName(memberName, member: node.interfaceTarget);
return js_ast.PropertyAccess(jsReceiver, jsMemberName);
var instanceGet = js_ast.PropertyAccess(jsReceiver, jsMemberName);
return _isNullCheckableJsInterop(node.interfaceTarget)
? _wrapWithJsInteropNullCheck(instanceGet)
: instanceGet;
}
@override
@ -4943,7 +4946,8 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
}
var jsPropertyAccess = js_ast.PropertyAccess(jsReceiver, jsMemberName);
return isJsMember(member)
? runtimeCall('tearoffInterop(#)', [jsPropertyAccess])
? runtimeCall('tearoffInterop(#, #)',
[jsPropertyAccess, js.boolean(_isNullCheckableJsInterop(member))])
: jsPropertyAccess;
}
@ -5011,6 +5015,26 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
return false;
}
/// Returns [expression] wrapped in an optional null check.
///
/// The null check is enabled by setting a flag during the application
/// bootstrap via `jsInteropNonNullAsserts(true)` in the SDK runtime library.
js_ast.Expression _wrapWithJsInteropNullCheck(js_ast.Expression expression) =>
runtimeCall('jsInteropNullCheck(#)', [expression]);
/// Returns `true` when [member] is a JavaScript interop API that should be
/// checked to be not null when the runtime flag `--interop-null-assertions`
/// is enabled.
///
/// These APIs are defined using the non-static package:js interop library and
/// are typed to be non-nullable.
bool _isNullCheckableJsInterop(Member member) {
var type =
member is Procedure ? member.function.returnType : member.getterType;
return type.nullability == Nullability.nonNullable &&
isNonStaticJsInterop(member);
}
/// Return whether [member] returns a native object whose type needs to be
/// null-checked in sound null-safety.
///
@ -5097,7 +5121,10 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
return runtimeCall('global');
}
}
return _emitStaticGet(target);
var staticGet = _emitStaticGet(target);
return _isNullCheckableJsInterop(target)
? _wrapWithJsInteropNullCheck(staticGet)
: staticGet;
}
@override
@ -5138,15 +5165,21 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
@override
js_ast.Expression visitInstanceInvocation(InstanceInvocation node) {
return _emitMethodCall(
var invocation = _emitMethodCall(
node.receiver, node.interfaceTarget, node.arguments, node);
return _isNullCheckableJsInterop(node.interfaceTarget)
? _wrapWithJsInteropNullCheck(invocation)
: invocation;
}
@override
js_ast.Expression visitInstanceGetterInvocation(
InstanceGetterInvocation node) {
return _emitMethodCall(
var getterInvocation = _emitMethodCall(
node.receiver, node.interfaceTarget, node.arguments, node);
return _isNullCheckableJsInterop(node.interfaceTarget)
? _wrapWithJsInteropNullCheck(getterInvocation)
: getterInvocation;
}
@override
@ -6152,7 +6185,10 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
var fn = _emitStaticTarget(target);
var args = _emitArgumentList(node.arguments, target: target);
return js_ast.Call(fn, args);
var staticCall = js_ast.Call(fn, args);
return _isNullCheckableJsInterop(target)
? _wrapWithJsInteropNullCheck(staticCall)
: staticCall;
}
js_ast.Expression _emitJSObjectGetPrototypeOf(js_ast.Expression obj,
@ -7171,8 +7207,10 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
return _emitStaticTarget(node.target);
}
if (node.target.isExternal && !isSdk) {
return runtimeCall(
'tearoffInterop(#)', [_emitStaticTarget(node.target)]);
return runtimeCall('tearoffInterop(#, #)', [
_emitStaticTarget(node.target),
js.boolean(_isNullCheckableJsInterop(node.target))
]);
}
}
if (node is TypeLiteralConstant) {

View File

@ -153,6 +153,8 @@ String ddcHtml(
NnbdMode mode,
String genDir,
bool nonNullAsserts,
bool nativeNonNullAsserts,
bool jsInteropNonNullAsserts,
bool weakNullSafetyErrors) {
var testId = pathToJSIdentifier(testName);
var testIdAlias = pathToJSIdentifier(testNameAlias);
@ -242,6 +244,8 @@ requirejs(["$testName", "dart_sdk", "async_helper"],
sdk.dart.weakNullSafetyWarnings(!($weakNullSafetyErrors || $soundNullSafety));
sdk.dart.weakNullSafetyErrors($weakNullSafetyErrors);
sdk.dart.nonNullAsserts($nonNullAsserts);
sdk.dart.nativeNonNullAsserts($nativeNonNullAsserts);
sdk.dart.jsInteropNonNullAsserts($jsInteropNonNullAsserts);
dartMainRunner(function testMainWrapper() {
// Some callbacks are not scheduled with timers/microtasks, so they don't

View File

@ -677,10 +677,13 @@ class DevCompilerConfiguration extends CompilerConfiguration {
List<String> sharedOptions, Map<String, String> environment) {
var args = <String>[];
// Remove option for generating non-null assertions for non-nullable
// method parameters in weak mode. DDC treats this as a runtime flag for
// the bootstrapping code, instead of a compiler option.
// method parameters in weak mode, native APIs and JavaScript interop APIs.
// DDC treats all of these as runtime flags for the bootstrapping code,
// instead of a compiler option.
var options = sharedOptions.toList();
options.remove('--null-assertions');
options.remove('--native-null-assertions');
options.remove('--interop-null-assertions');
if (!_useSdk || !_soundNullSafety) {
// If we're testing a built SDK, DDC will find its own summary.
//
@ -767,6 +770,8 @@ class DevCompilerConfiguration extends CompilerConfiguration {
runFile = "$tempDir/$moduleName.d8.js";
var nonNullAsserts = arguments.contains('--null-assertions');
var nativeNonNullAsserts = arguments.contains('--native-null-assertions');
var jsInteropNonNullAsserts =
arguments.contains('--interop-null-assertions');
var weakNullSafetyErrors =
arguments.contains('--weak-null-safety-errors');
var weakNullSafetyWarnings = !(weakNullSafetyErrors || _soundNullSafety);
@ -806,6 +811,7 @@ class DevCompilerConfiguration extends CompilerConfiguration {
sdk.dart.weakNullSafetyErrors($weakNullSafetyErrors);
sdk.dart.nonNullAsserts($nonNullAsserts);
sdk.dart.nativeNonNullAsserts($nativeNonNullAsserts);
sdk.dart.jsInteropNonNullAsserts($jsInteropNonNullAsserts);
// Invoke main through the d8 preamble to ensure the code is running
// within the fake event loop.

View File

@ -969,6 +969,10 @@ class StandardTestSuite extends TestSuite {
Path(compilationTempDir).relativeTo(Repository.dir).toString();
var nullAssertions =
testFile.sharedOptions.contains('--null-assertions');
var nativeNonNullAsserts =
testFile.ddcOptions.contains('--native-null-assertions');
var jsInteropNonNullAsserts =
testFile.ddcOptions.contains('--interop-null-assertions');
var weakNullSafetyErrors =
testFile.ddcOptions.contains('--weak-null-safety-errors');
content = ddcHtml(
@ -979,6 +983,8 @@ class StandardTestSuite extends TestSuite {
configuration.nnbdMode,
ddcConfig.buildOptionsDir,
nullAssertions,
nativeNonNullAsserts,
jsInteropNonNullAsserts,
weakNullSafetyErrors);
} else {
throw UnsupportedError(

View File

@ -1167,6 +1167,21 @@ defineLazyFieldOld(to, name, desc) => JS('', '''(() => {
return ${defineProperty(to, name, desc)};
})()''');
/// Checks for null or undefined and returns [val].
///
/// Throws a [TypeError] when [val] is null or undefined and the option for
/// these checks has been enabled by [jsInteropNonNullAsserts].
///
/// Called from generated code when the compiler detects a non-static JavaScript
/// interop API access that is typed to be non-nullable.
Object? jsInteropNullCheck(Object? val) {
if (_jsInteropNonNullAsserts && val == null) {
throw TypeErrorImpl('Unexpected null value encountered from a '
'JavaScript Interop API typed as non-nullable.');
}
return val;
}
checkNativeNonNull(dynamic variable) {
if (_nativeNonNullAsserts && variable == null) {
// TODO(srujzs): Add link/patch for instructions to disable in internal

View File

@ -99,6 +99,16 @@ void nativeNonNullAsserts(bool enable) {
_nativeNonNullAsserts = enable;
}
@notNull
bool _jsInteropNonNullAsserts = false;
/// Enables null assertions on non-static JavaScript interop APIs to make sure
/// values returned are sound with respect to the nullability.
void jsInteropNonNullAsserts(bool enable) {
// This value is only read from `jsInteropNullCheck`.
_jsInteropNonNullAsserts = enable;
}
/// A JavaScript Symbol used to store the Rti signature object on a function.
///
/// Accessed by a call to `JS_GET_NAME(JsGetName.SIGNATURE_NAME)`.
@ -143,20 +153,30 @@ bool isDartFunction(Object? obj) {
Expando<Function> _assertInteropExpando = Expando<Function>();
@NoReifyGeneric()
F tearoffInterop<F extends Function?>(F f) {
F tearoffInterop<F extends Function?>(F f, bool checkReturnType) {
// Wrap a JS function with a closure that ensures all function arguments are
// native JS functions.
if (f is! LegacyJavaScriptObject || f == null) return f;
var ret = _assertInteropExpando[f];
if (ret == null) {
ret = JS(
'',
'function (...arguments) {'
' var args = arguments.map(#);'
' return #.apply(this, args);'
'}',
assertInterop,
f);
ret = checkReturnType
? JS(
'',
'function (...arguments) {'
' var args = arguments.map(#);'
' return #(#.apply(this, args));'
'}',
assertInterop,
jsInteropNullCheck,
f)
: JS(
'',
'function (...arguments) {'
' var args = arguments.map(#);'
' return #.apply(this, args);'
'}',
assertInterop,
f);
_assertInteropExpando[f] = ret;
}
// Suppress a cast back to F.

View File

@ -0,0 +1,18 @@
// 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.
/// When using non-static JavaScript interop via package:js, values flowing
/// through APIs defined to be non-nullable should not be checked for null when
/// `--interop-null-assertions` is not enabled.
///
/// Without that flag, null values can leak through APIs that are typed as
/// non-nullable, even when sound null safety is enabled.
import 'js_interop_non_null_asserts_utils.dart';
void main() {
topLevelMemberTests(checksEnabled: false);
anonymousClassTests(checksEnabled: false);
namedClassTests(checksEnabled: false);
}

View File

@ -0,0 +1,23 @@
// 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.
// dart2jsOptions=--interop-null-assertions
// ddcOptions=--interop-null-assertions
/// When using non-static JavaScript interop via package:js, values flowing
/// through APIs defined to be non-nullable should be checked for null when
/// `--interop-null-assertions` is enabled.
///
/// - In dart2js this is a compile time flag.
/// - In DDC this is a runtime flag so the option gets passed to the
/// bootstrapper script and set in the runtime before invoking the main
/// method.
import 'js_interop_non_null_asserts_utils.dart';
void main() {
topLevelMemberTests(checksEnabled: true);
anonymousClassTests(checksEnabled: true);
namedClassTests(checksEnabled: true);
}

View File

@ -0,0 +1,276 @@
// 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';
@JS()
external dynamic eval(String code);
@JS()
external String returnsNull();
@JS()
external String returnsUndefined();
@JS()
external int get getsNull;
@JS()
external int get undefinedGetter;
@JS()
external double Function() get getterFunctionReturnsNull;
@JS()
external double Function() get getterFunctionReturnsUndefined;
@JS()
external bool nullField;
@JS()
external bool undefinedField;
@JS()
@anonymous
class SomeClass {
external String returnsNull();
external String returnsUndefined();
external int get getsNull;
external int get undefinedGetter;
external double Function() get getterFunctionReturnsNull;
external double Function() get getterFunctionReturnsUndefined;
external bool nullField;
external bool undefinedField;
}
@JS()
@anonymous
class AnotherClass {
external static String staticReturnsNull();
external static String staticReturnsUndefined();
external static int get staticGetsNull;
external static int get staticUndefinedGetter;
external static double Function() get staticGetterFunctionReturnsNull;
external static double Function() get staticGetterFunctionReturnsUndefined;
external static bool staticNullField;
external static bool staticUndefinedField;
}
@JS()
class NamedClass {
external String returnsNull();
external String returnsUndefined();
external static String staticReturnsNull();
external static String staticReturnsUndefined();
external int get getsNull;
external int get undefinedGetter;
external static int get staticGetsNull;
external static int get staticUndefinedGetter;
external double Function() get getterFunctionReturnsNull;
external double Function() get getterFunctionReturnsUndefined;
external static double Function() get staticGetterFunctionReturnsNull;
external static double Function() get staticGetterFunctionReturnsUndefined;
external bool nullField;
external bool undefinedField;
external static bool staticNullField;
external static bool staticUndefinedField;
external factory NamedClass.createNamedClass();
}
@JS()
external SomeClass createSomeClass();
void topLevelMemberTests({required bool checksEnabled}) {
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);
}
void anonymousClassTests({required bool checksEnabled}) {
eval(r'''self.createSomeClass = function() {
return {
"returnsNull": function() { return null; },
"returnsUndefined": function() { return void 0; },
"getsNull" : null,
"jsGetsNull": { get: function() { return null; } },
"jsGetsUndefined": { get: function() { return void 0; } },
"getterFunctionReturnsNull": function() { return null; },
"getterFunctionReturnsUndefined": function() { return void 0; },
"nullField" : null,
};
};
''');
var x = createSomeClass();
Expect.throwsTypeErrorWhen(checksEnabled, () => x.returnsNull());
Expect.throwsTypeErrorWhen(checksEnabled, () => x.returnsUndefined());
var functionTearoff = x.returnsNull;
Expect.throwsTypeErrorWhen(checksEnabled, () => functionTearoff());
functionTearoff = x.returnsUndefined;
Expect.throwsTypeErrorWhen(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);
eval(r'''self.AnotherClass = class AnotherClass {
static staticReturnsNull() { return null; }
static staticReturnsUndefined() { return void 0; }
static get staticGetsNull() { return null; }
static get staticGetterFunctionReturnsNull() {
return function() { return null; };
}
static get staticGetterFunctionReturnsUndefined() {
return function() { return void 0; };
}
};''');
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);
}
void namedClassTests({required bool checksEnabled}) {
eval(r'''self.NamedClass = class NamedClass {
returnsNull() { return null; }
returnsUndefined() { return void 0; }
static staticReturnsNull() { return null; }
static staticReturnsUndefined() { return void 0; }
get getsNull() { return null; }
static get staticGetsNull() { return null; }
get getterFunctionReturnsNull() {
return function() { return null; };
}
get getterFunctionReturnsUndefined() {
return function() { return void 0; };
}
static get staticGetterFunctionReturnsNull() {
return function() { return null; };
}
static get staticGetterFunctionReturnsUndefined() {
return function() { return void 0; };
}
static createNamedClass() { return new NamedClass(); }
};''');
var y = NamedClass.createNamedClass();
Expect.throwsTypeErrorWhen(checksEnabled, () => y.returnsNull());
Expect.throwsTypeErrorWhen(checksEnabled, () => y.returnsUndefined());
var functionTearoff = y.returnsNull;
Expect.throwsTypeErrorWhen(checksEnabled, () => functionTearoff());
functionTearoff = y.returnsUndefined;
Expect.throwsTypeErrorWhen(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);
}

View File

@ -48,8 +48,11 @@ consistent_index_error_string_test: Slow, Pass # Issue 25940
deferred_custom_loader_test: SkipByDesign # Issue 25683
deferred_fail_and_retry_test: SkipByDesign # Uses eval to simulate failed loading.
internal/object_members_test: SkipByDesign # Uses eval for interop
js_interop_non_null_asserts_disabled_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js_interop_non_null_asserts_enabled_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
regress/issue/49129_test: SkipByDesign # Uses eval for interop
[ $compiler == dart2js && !$host_asserts ]
deferred/many_parts/many_parts_test: Slow, Pass # Large stress test by design
dummy_compiler_test: Slow, Pass # Issue 32439. self-hosting doesn't work with CFE yet.