diff --git a/sdk/lib/_internal/wasm/lib/boxed_double.dart b/sdk/lib/_internal/wasm/lib/boxed_double.dart index 143130ef293..2ef0d1f4289 100644 --- a/sdk/lib/_internal/wasm/lib/boxed_double.dart +++ b/sdk/lib/_internal/wasm/lib/boxed_double.dart @@ -353,7 +353,7 @@ final class BoxedDouble implements double { return "Infinity"; } - String result = JSStringImpl( + String result = JSStringImpl.fromRefUnchecked( JS( 'Function.prototype.call.bind(Number.prototype.toString)', WasmF64.fromDouble(value), @@ -400,7 +400,7 @@ final class BoxedDouble implements double { return result; } - String _toStringAsFixed(int fractionDigits) => JSStringImpl( + String _toStringAsFixed(int fractionDigits) => JSStringImpl.fromRefUnchecked( JS( "(d, digits) => d.toFixed(digits)", value, @@ -434,15 +434,16 @@ final class BoxedDouble implements double { return result; } - String _toStringAsExponential(int? fractionDigits) => JSStringImpl( - fractionDigits == null - ? JS("d => d.toExponential()", value) - : JS( - "(d, f) => d.toExponential(f)", - value, - fractionDigits.toDouble(), - ), - ); + String _toStringAsExponential(int? fractionDigits) => + JSStringImpl.fromRefUnchecked( + fractionDigits == null + ? JS("d => d.toExponential()", value) + : JS( + "(d, f) => d.toExponential(f)", + value, + fractionDigits.toDouble(), + ), + ); String toStringAsPrecision(int precision) { // See ECMAScript-262, 15.7.4.7 for details. @@ -463,13 +464,14 @@ final class BoxedDouble implements double { return result; } - String _toStringAsPrecision(int fractionDigits) => JSStringImpl( - JS( - "(d, precision) => d.toPrecision(precision)", - value, - fractionDigits.toDouble(), - ), - ); + String _toStringAsPrecision(int fractionDigits) => + JSStringImpl.fromRefUnchecked( + JS( + "(d, precision) => d.toPrecision(precision)", + value, + fractionDigits.toDouble(), + ), + ); // Order is: NaN > Infinity > ... > 0.0 > -0.0 > ... > -Infinity. int compareTo(num other) { diff --git a/sdk/lib/_internal/wasm/lib/boxed_int_to_string.dart b/sdk/lib/_internal/wasm/lib/boxed_int_to_string.dart index ec9033352d1..b377b0cb764 100644 --- a/sdk/lib/_internal/wasm/lib/boxed_int_to_string.dart +++ b/sdk/lib/_internal/wasm/lib/boxed_int_to_string.dart @@ -35,5 +35,5 @@ String _jsBigIntToString(int i, int radix) { WasmI64.fromInt(i), WasmI32.fromInt(radix), ); - return JSStringImpl(result); + return JSStringImpl.fromRefUnchecked(result); } diff --git a/sdk/lib/_internal/wasm/lib/date_patch_patch.dart b/sdk/lib/_internal/wasm/lib/date_patch_patch.dart index ebf58ee3156..f4f6050d911 100644 --- a/sdk/lib/_internal/wasm/lib/date_patch_patch.dart +++ b/sdk/lib/_internal/wasm/lib/date_patch_patch.dart @@ -16,7 +16,7 @@ class DateTime { @patch static String _timeZoneNameForClampedSeconds(int secondsSinceEpoch) => - JSStringImpl( + JSStringImpl.fromRefUnchecked( JS(r"""secondsSinceEpoch => { const date = new Date(secondsSinceEpoch * 1000); const match = /\((.*)\)/.exec(date.toString()); diff --git a/sdk/lib/_internal/wasm/lib/internal_patch.dart b/sdk/lib/_internal/wasm/lib/internal_patch.dart index 1841d662c70..2d2ac40e60f 100644 --- a/sdk/lib/_internal/wasm/lib/internal_patch.dart +++ b/sdk/lib/_internal/wasm/lib/internal_patch.dart @@ -155,12 +155,14 @@ void _invokeMain(WasmExternRef jsArrayRef) { } } -String jsonEncode(String object) => JSStringImpl( - JS( - "s => JSON.stringify(s)", - jsStringFromDartString(object).toExternRef, - ), -); +String jsonEncode(String object) => + // Use checked boxing as `JSON.stringify` can be patched by users. + JSStringImpl.fromRef( + JS( + "s => JSON.stringify(s)", + jsStringFromDartString(object).toExternRef, + ), + ); /// Whether to check bounds in [IndexErrorUtils.checkIndex], /// which are used in list and typed data implementations. diff --git a/sdk/lib/_internal/wasm/lib/js_array.dart b/sdk/lib/_internal/wasm/lib/js_array.dart index 043efb11ebf..1c3e8e6dd0c 100644 --- a/sdk/lib/_internal/wasm/lib/js_array.dart +++ b/sdk/lib/_internal/wasm/lib/js_array.dart @@ -14,13 +14,27 @@ extension JSArrayImplUncheckedOperations on JSArrayImpl { class JSArrayImpl implements List { final WasmExternRef? _ref; - JSArrayImpl(this._ref); + static bool _checkRefType(WasmExternRef? ref) => + js.JS('o => o instanceof Array', ref); + + JSArrayImpl.fromRefUnchecked(this._ref) { + assert(_checkRefType(_ref)); + } + + factory JSArrayImpl.fromRef(WasmExternRef? ref) { + if (!_checkRefType(ref)) { + throw minify + ? ArgumentError() + : ArgumentError("JS reference is not an array"); + } + return JSArrayImpl.fromRefUnchecked(ref); + } factory JSArrayImpl.fromLength(int length) => - JSArrayImpl(js.newArrayFromLengthRaw(length)); + JSArrayImpl.fromRefUnchecked(js.newArrayFromLengthRaw(length)); static JSArrayImpl? box(WasmExternRef? ref) => - js.isDartNull(ref) ? null : JSArrayImpl(ref); + js.isDartNull(ref) ? null : JSArrayImpl.fromRefUnchecked(ref); WasmExternRef? get toExternRef => _ref; @@ -165,7 +179,7 @@ class JSArrayImpl implements List { toExternRef, separator.toExternRef, ); - return JSStringImpl(result); + return JSStringImpl.fromRefUnchecked(result); } @override @@ -257,7 +271,7 @@ class JSArrayImpl implements List { @override List sublist(int start, [int? end]) { end = RangeErrorUtils.checkValidRange(start, end, length); - return JSArrayImpl( + return JSArrayImpl.fromRefUnchecked( js.JS( '(a, s, e) => a.slice(s, e)', toExternRef, @@ -538,7 +552,7 @@ class JSArrayImpl implements List { @override List operator +(List other) { if (other is JSArrayImpl) { - return JSArrayImpl( + return JSArrayImpl.fromRefUnchecked( js.JS( '(a, t) => a.concat(t)', toExternRef, diff --git a/sdk/lib/_internal/wasm/lib/js_helper.dart b/sdk/lib/_internal/wasm/lib/js_helper.dart index fb1ccd125c6..43b3f6a4fc5 100644 --- a/sdk/lib/_internal/wasm/lib/js_helper.dart +++ b/sdk/lib/_internal/wasm/lib/js_helper.dart @@ -259,10 +259,10 @@ WasmExternRef? callMethodVarArgsRaw( ) => JS("(o, m, a) => o[m].apply(o, a)", o, method, args); String typeof(WasmExternRef? object) => - JSStringImpl(JS("o => typeof o", object)); + JSStringImpl.fromRefUnchecked(JS("o => typeof o", object)); String stringify(WasmExternRef? object) => - JSStringImpl(JS("o => String(o)", object)); + JSStringImpl.fromRefUnchecked(JS("o => String(o)", object)); void promiseThen( WasmExternRef? promise, @@ -491,20 +491,30 @@ Object? dartifyRaw(WasmExternRef? ref, [int? refType]) { ExternRefType.null_ || ExternRefType.undefined => null, ExternRefType.boolean => toDartBool(ref), ExternRefType.number => toDartNumber(ref), - ExternRefType.string => JSStringImpl.box(ref), + ExternRefType.string => JSStringImpl.fromRefUnchecked(ref), ExternRefType.array => toDartList(ref), - ExternRefType.int8Array => js_types.JSInt8ArrayImpl.fromJSArray(ref), - ExternRefType.uint8Array => js_types.JSUint8ArrayImpl.fromJSArray(ref), + ExternRefType.int8Array => js_types.JSInt8ArrayImpl.fromRefUnchecked(ref), + ExternRefType.uint8Array => js_types.JSUint8ArrayImpl.fromRefUnchecked(ref), ExternRefType.uint8ClampedArray => - js_types.JSUint8ClampedArrayImpl.fromJSArray(ref), - ExternRefType.int16Array => js_types.JSInt16ArrayImpl.fromJSArray(ref), - ExternRefType.uint16Array => js_types.JSUint16ArrayImpl.fromJSArray(ref), - ExternRefType.int32Array => js_types.JSInt32ArrayImpl.fromJSArray(ref), - ExternRefType.uint32Array => js_types.JSUint32ArrayImpl.fromJSArray(ref), - ExternRefType.float32Array => js_types.JSFloat32ArrayImpl.fromJSArray(ref), - ExternRefType.float64Array => js_types.JSFloat64ArrayImpl.fromJSArray(ref), - ExternRefType.arrayBuffer => js_types.JSArrayBufferImpl.fromRef(ref), - ExternRefType.dataView => js_types.JSDataViewImpl.fromRef(ref), + js_types.JSUint8ClampedArrayImpl.fromRefUnchecked(ref), + ExternRefType.int16Array => js_types.JSInt16ArrayImpl.fromRefUnchecked(ref), + ExternRefType.uint16Array => js_types.JSUint16ArrayImpl.fromRefUnchecked( + ref, + ), + ExternRefType.int32Array => js_types.JSInt32ArrayImpl.fromRefUnchecked(ref), + ExternRefType.uint32Array => js_types.JSUint32ArrayImpl.fromRefUnchecked( + ref, + ), + ExternRefType.float32Array => js_types.JSFloat32ArrayImpl.fromRefUnchecked( + ref, + ), + ExternRefType.float64Array => js_types.JSFloat64ArrayImpl.fromRefUnchecked( + ref, + ), + ExternRefType.arrayBuffer => js_types.JSArrayBufferImpl.fromRefUnchecked( + ref, + ), + ExternRefType.dataView => js_types.JSDataViewImpl.fromRefUnchecked(ref), ExternRefType.unknown => isJSWrappedDartFunction(ref) ? unwrapJSWrappedDartFunction(ref) diff --git a/sdk/lib/_internal/wasm/lib/js_interop_patch.dart b/sdk/lib/_internal/wasm/lib/js_interop_patch.dart index d67ea2d6602..56f31ac108d 100644 --- a/sdk/lib/_internal/wasm/lib/js_interop_patch.dart +++ b/sdk/lib/_internal/wasm/lib/js_interop_patch.dart @@ -279,7 +279,7 @@ extension ByteDataToJSDataView on ByteData { @patch extension JSInt8ArrayToInt8List on JSInt8Array { @patch - Int8List get toDart => js_types.JSInt8ArrayImpl.fromJSArray(toExternRef); + Int8List get toDart => js_types.JSInt8ArrayImpl.fromRef(toExternRef); } @patch @@ -302,7 +302,7 @@ extension Int8ListToJSInt8Array on Int8List { @patch extension JSUint8ArrayToUint8List on JSUint8Array { @patch - Uint8List get toDart => js_types.JSUint8ArrayImpl.fromJSArray(toExternRef); + Uint8List get toDart => js_types.JSUint8ArrayImpl.fromRef(toExternRef); } @patch @@ -326,7 +326,7 @@ extension Uint8ListToJSUint8Array on Uint8List { extension JSUint8ClampedArrayToUint8ClampedList on JSUint8ClampedArray { @patch Uint8ClampedList get toDart => - js_types.JSUint8ClampedArrayImpl.fromJSArray(toExternRef); + js_types.JSUint8ClampedArrayImpl.fromRef(toExternRef); } @patch @@ -349,7 +349,7 @@ extension Uint8ClampedListToJSUint8ClampedArray on Uint8ClampedList { @patch extension JSInt16ArrayToInt16List on JSInt16Array { @patch - Int16List get toDart => js_types.JSInt16ArrayImpl.fromJSArray(toExternRef); + Int16List get toDart => js_types.JSInt16ArrayImpl.fromRef(toExternRef); } @patch @@ -372,7 +372,7 @@ extension Int16ListToJSInt16Array on Int16List { @patch extension JSUint16ArrayToInt16List on JSUint16Array { @patch - Uint16List get toDart => js_types.JSUint16ArrayImpl.fromJSArray(toExternRef); + Uint16List get toDart => js_types.JSUint16ArrayImpl.fromRef(toExternRef); } @patch @@ -395,7 +395,7 @@ extension Uint16ListToJSInt16Array on Uint16List { @patch extension JSInt32ArrayToInt32List on JSInt32Array { @patch - Int32List get toDart => js_types.JSInt32ArrayImpl.fromJSArray(toExternRef); + Int32List get toDart => js_types.JSInt32ArrayImpl.fromRef(toExternRef); } @patch @@ -418,7 +418,7 @@ extension Int32ListToJSInt32Array on Int32List { @patch extension JSUint32ArrayToUint32List on JSUint32Array { @patch - Uint32List get toDart => js_types.JSUint32ArrayImpl.fromJSArray(toExternRef); + Uint32List get toDart => js_types.JSUint32ArrayImpl.fromRef(toExternRef); } @patch @@ -441,8 +441,7 @@ extension Uint32ListToJSUint32Array on Uint32List { @patch extension JSFloat32ArrayToFloat32List on JSFloat32Array { @patch - Float32List get toDart => - js_types.JSFloat32ArrayImpl.fromJSArray(toExternRef); + Float32List get toDart => js_types.JSFloat32ArrayImpl.fromRef(toExternRef); } @patch @@ -465,8 +464,7 @@ extension Float32ListToJSFloat32Array on Float32List { @patch extension JSFloat64ArrayToFloat64List on JSFloat64Array { @patch - Float64List get toDart => - js_types.JSFloat64ArrayImpl.fromJSArray(toExternRef); + Float64List get toDart => js_types.JSFloat64ArrayImpl.fromRef(toExternRef); } @patch @@ -489,7 +487,7 @@ extension Float64ListToJSFloat64Array on Float64List { @patch extension JSArrayToList on JSArray { @patch - List get toDart => js_types.JSArrayImpl(toExternRef); + List get toDart => js_types.JSArrayImpl.fromRef(toExternRef); } @patch @@ -554,7 +552,7 @@ extension BoolToJSBoolean on bool { @patch extension JSStringToString on JSString { @patch - String get toDart => JSStringImpl(toExternRef); + String get toDart => JSStringImpl.fromRef(toExternRef); } @patch diff --git a/sdk/lib/_internal/wasm/lib/js_string.dart b/sdk/lib/_internal/wasm/lib/js_string.dart index 331db61cd94..3f4e05d7bed 100644 --- a/sdk/lib/_internal/wasm/lib/js_string.dart +++ b/sdk/lib/_internal/wasm/lib/js_string.dart @@ -34,11 +34,24 @@ extension StringUncheckedOperations on String { final class JSStringImpl implements String, StringUncheckedOperationsBase { final WasmExternRef? _ref; - JSStringImpl(this._ref); + static bool _checkRefType(WasmExternRef? ref) => jsStringTest(ref).toBool(); + + JSStringImpl.fromRefUnchecked(this._ref) { + assert(_checkRefType(_ref)); + } + + factory JSStringImpl.fromRef(WasmExternRef? ref) { + if (!_checkRefType(ref)) { + throw minify + ? ArgumentError() + : ArgumentError("JS reference is not a string"); + } + return JSStringImpl.fromRefUnchecked(ref); + } @pragma("wasm:prefer-inline") static String? box(WasmExternRef? ref) => - js.isDartNull(ref) ? null : JSStringImpl(ref); + js.isDartNull(ref) ? null : JSStringImpl.fromRefUnchecked(ref); @override @pragma("wasm:prefer-inline") @@ -71,7 +84,7 @@ final class JSStringImpl implements String, StringUncheckedOperationsBase { static String _interpolate2(Object? value1, Object? value2) { final String string1 = value1 is String ? value1 : value1.toString(); final String string2 = value2 is String ? value2 : value2.toString(); - return JSStringImpl( + return JSStringImpl.fromRefUnchecked( _jsStringConcatImport( unsafeCast(string1).toExternRef, unsafeCast(string2).toExternRef, @@ -84,7 +97,7 @@ final class JSStringImpl implements String, StringUncheckedOperationsBase { final String string1 = value1 is String ? value1 : value1.toString(); final String string2 = value2 is String ? value2 : value2.toString(); final String string3 = value3 is String ? value3 : value3.toString(); - return JSStringImpl( + return JSStringImpl.fromRefUnchecked( _jsStringConcatImport( _jsStringConcatImport( unsafeCast(string1).toExternRef, @@ -106,7 +119,7 @@ final class JSStringImpl implements String, StringUncheckedOperationsBase { final String string2 = value2 is String ? value2 : value2.toString(); final String string3 = value3 is String ? value3 : value3.toString(); final String string4 = value4 is String ? value4 : value4.toString(); - return JSStringImpl( + return JSStringImpl.fromRefUnchecked( _jsStringConcatImport( _jsStringConcatImport( unsafeCast(string1).toExternRef, @@ -148,7 +161,7 @@ final class JSStringImpl implements String, StringUncheckedOperationsBase { int start, int end, ) { - return JSStringImpl( + return JSStringImpl.fromRefUnchecked( jsStringFromCharCodeArray(source, start.toWasmI32(), end.toWasmI32()), ); } @@ -214,7 +227,7 @@ final class JSStringImpl implements String, StringUncheckedOperationsBase { @override @pragma('dyn-module:callable') String operator +(String other) { - return JSStringImpl( + return JSStringImpl.fromRefUnchecked( _jsStringConcatImport( toExternRef, unsafeCast(other).toExternRef, @@ -464,7 +477,7 @@ final class JSStringImpl implements String, StringUncheckedOperationsBase { @override @pragma('wasm:prefer-inline') String _substringUnchecked(int start, int end) => - JSStringImpl(_jsSubstring(toExternRef, start, end)); + JSStringImpl.fromRefUnchecked(_jsSubstring(toExternRef, start, end)); @override String toLowerCase() { @@ -472,7 +485,7 @@ final class JSStringImpl implements String, StringUncheckedOperationsBase { final lowerCaseRef = _jsStringToLowerCase(thisRef); return _jsIdentical(thisRef, lowerCaseRef) ? this - : JSStringImpl(lowerCaseRef); + : JSStringImpl.fromRefUnchecked(lowerCaseRef); } @override @@ -481,7 +494,7 @@ final class JSStringImpl implements String, StringUncheckedOperationsBase { final upperCaseRef = _jsStringToUpperCase(thisRef); return _jsIdentical(thisRef, upperCaseRef) ? this - : JSStringImpl(upperCaseRef); + : JSStringImpl.fromRefUnchecked(upperCaseRef); } // Characters with Whitespace property (Unicode 6.3). @@ -585,7 +598,7 @@ final class JSStringImpl implements String, StringUncheckedOperationsBase { // Start by doing JS trim. Then check if it leaves a NEL at either end of // the string. - final result = JSStringImpl(_jsStringTrim(toExternRef)); + final result = JSStringImpl.fromRefUnchecked(_jsStringTrim(toExternRef)); final resultLength = result.length; if (resultLength == 0) return result; @@ -623,7 +636,9 @@ final class JSStringImpl implements String, StringUncheckedOperationsBase { // Start by doing JS trim. Then check if it leaves a NEL at the beginning // of the string. int startIndex = 0; - final result = JSStringImpl(_jsStringTrimLeft(toExternRef)); + final result = JSStringImpl.fromRefUnchecked( + _jsStringTrimLeft(toExternRef), + ); final resultLength = result.length; if (resultLength == 0) return result; @@ -649,7 +664,9 @@ final class JSStringImpl implements String, StringUncheckedOperationsBase { // Start by doing JS trim. Then check if it leaves a NEL at the end of the // string. - final result = JSStringImpl(_jsStringTrimRight(toExternRef)); + final result = JSStringImpl.fromRefUnchecked( + _jsStringTrimRight(toExternRef), + ); final resultLength = result.length; if (resultLength == 0) return result; @@ -675,7 +692,7 @@ final class JSStringImpl implements String, StringUncheckedOperationsBase { 'The implementation cannot handle very large operands (was: $times).', ); } - return JSStringImpl(_jsStringRepeat(toExternRef, times)); + return JSStringImpl.fromRefUnchecked(_jsStringRepeat(toExternRef, times)); } @override @@ -831,7 +848,7 @@ JSStringImpl _jsStringReplaceAll( WasmExternRef? string, WasmExternRef? pattern, WasmExternRef? replacement, -) => JSStringImpl( +) => JSStringImpl.fromRefUnchecked( js.JS( '(o, p, r) => o.replaceAll(p, () => r)', string, @@ -846,7 +863,7 @@ String _jsStringReplace( WasmExternRef? string, WasmExternRef? pattern, WasmExternRef? replacement, -) => JSStringImpl( +) => JSStringImpl.fromRefUnchecked( js.JS( '(o, p, r) => o.replace(p, () => r)', string, @@ -1008,3 +1025,6 @@ external WasmI32 jsStringIntoCharCodeArray( WasmArray? array, WasmI32 start, ); + +@pragma("wasm:import", "wasm:js-string.test") +external WasmI32 jsStringTest(WasmExternRef? s); diff --git a/sdk/lib/_internal/wasm/lib/js_string_convert.dart b/sdk/lib/_internal/wasm/lib/js_string_convert.dart index 8ab8ef79c1f..142c1ce986a 100644 --- a/sdk/lib/_internal/wasm/lib/js_string_convert.dart +++ b/sdk/lib/_internal/wasm/lib/js_string_convert.dart @@ -36,7 +36,7 @@ JSStringImpl? _useTextDecoder( // back on unintercepted decoder. The fallback will either succeed in // decoding, or report the problem better than `TextDecoder`. try { - return JSStringImpl( + return JSStringImpl.fromRefUnchecked( js.JS( '(decoder, codeUnits) => decoder.decode(codeUnits)', decoder, diff --git a/sdk/lib/_internal/wasm/lib/js_typed_array.dart b/sdk/lib/_internal/wasm/lib/js_typed_array.dart index 2ed8429e6a3..9430d7ffae3 100644 --- a/sdk/lib/_internal/wasm/lib/js_typed_array.dart +++ b/sdk/lib/_internal/wasm/lib/js_typed_array.dart @@ -11,9 +11,21 @@ final class JSArrayBufferImpl implements ByteBuffer { final bool _immutable; - JSArrayBufferImpl.fromRef(this._ref) : _immutable = false; + static bool _checkRefType(WasmExternRef? ref) => + js.JS('o => o instanceof ArrayBuffer', ref); - JSArrayBufferImpl.fromRefImmutable(this._ref) : _immutable = true; + JSArrayBufferImpl.fromRefUnchecked(this._ref) : _immutable = false { + assert(_checkRefType(_ref)); + } + + JSArrayBufferImpl.fromRefImmutableUnchecked(this._ref) : _immutable = true; + + factory JSArrayBufferImpl.fromRef(WasmExternRef? ref) { + if (!_checkRefType(ref)) { + return _throwConversionFailureError("ByteBuffer"); + } + return JSArrayBufferImpl.fromRefUnchecked(ref); + } @pragma("wasm:prefer-inline") WasmExternRef? get toExternRef => _ref; @@ -168,7 +180,7 @@ abstract class JSArrayBase implements TypedData { @override JSArrayBufferImpl get buffer => - JSArrayBufferImpl.fromRef(_dataViewBuffer(_ref)); + JSArrayBufferImpl.fromRefUnchecked(_dataViewBuffer(_ref)); @override @pragma("wasm:prefer-inline") @@ -238,21 +250,35 @@ final class JSDataViewImpl implements ByteData { final bool _immutable; + static bool _checkRefType(WasmExternRef? ref) => + js.JS('o => o instanceof DataView', ref); + + JSDataViewImpl._(this._ref, this.lengthInBytes, this._immutable) { + assert(_checkRefType(_ref)); + } + JSDataViewImpl(this.lengthInBytes) : _ref = _newDataView(lengthInBytes), _immutable = false; - JSDataViewImpl.fromRef(this._ref) + JSDataViewImpl.fromRefUnchecked(this._ref) : lengthInBytes = _dataViewByteLength(_ref), _immutable = false; + factory JSDataViewImpl.fromRef(WasmExternRef? ref) { + if (!_checkRefType(ref)) { + return _throwConversionFailureError("ByteData"); + } + return JSDataViewImpl.fromRefUnchecked(ref); + } + JSDataViewImpl.immutable(this._ref, this.lengthInBytes) : _immutable = true; factory JSDataViewImpl.view( JSArrayBufferImpl buffer, int offsetInBytes, int? length, - ) => JSDataViewImpl.fromRef( + ) => JSDataViewImpl.fromRefUnchecked( _newDataViewFromArrayBuffer(buffer.toExternRef, offsetInBytes, length), ); @@ -261,8 +287,10 @@ final class JSDataViewImpl implements ByteData { @override JSArrayBufferImpl get buffer => _immutable - ? JSArrayBufferImpl.fromRefImmutable(_dataViewBuffer(toExternRef)) - : JSArrayBufferImpl.fromRef(_dataViewBuffer(toExternRef)); + ? JSArrayBufferImpl.fromRefImmutableUnchecked( + _dataViewBuffer(toExternRef), + ) + : JSArrayBufferImpl.fromRefUnchecked(_dataViewBuffer(toExternRef)); @override @pragma("wasm:prefer-inline") @@ -732,7 +760,7 @@ mixin _UnmodifiableIntListMixin { WasmExternRef? get toExternRef; JSArrayBufferImpl get buffer => - JSArrayBufferImpl.fromRefImmutable(_dataViewBuffer(toExternRef)); + JSArrayBufferImpl.fromRefImmutableUnchecked(_dataViewBuffer(toExternRef)); void operator []=(int index, int value) { throw UnsupportedError("Cannot modify an unmodifiable list"); @@ -756,8 +784,20 @@ final class JSUint8ArrayImpl extends JSIntegerArrayBase factory JSUint8ArrayImpl(int length) => JSUint8ArrayImpl._(_newDataView(length)); - factory JSUint8ArrayImpl.fromJSArray(WasmExternRef? jsArrayRef) => - JSUint8ArrayImpl._(_dataViewFromJSArray(jsArrayRef)); + static bool _checkRefType(WasmExternRef? ref) => + js.JS('o => o instanceof Uint8Array', ref); + + factory JSUint8ArrayImpl.fromRefUnchecked(WasmExternRef? ref) { + _checkRefType(ref); + return JSUint8ArrayImpl._(_dataViewFromJSArray(ref)); + } + + factory JSUint8ArrayImpl.fromRef(WasmExternRef? ref) { + if (!_checkRefType(ref)) { + return _throwConversionFailureError("Uint8List"); + } + return JSUint8ArrayImpl.fromRefUnchecked(ref); + } factory JSUint8ArrayImpl.view( JSArrayBufferImpl buffer, @@ -848,8 +888,20 @@ final class JSInt8ArrayImpl extends JSIntegerArrayBase factory JSInt8ArrayImpl(int length) => JSInt8ArrayImpl._(_newDataView(length)); - factory JSInt8ArrayImpl.fromJSArray(WasmExternRef? jsArrayRef) => - JSInt8ArrayImpl._(_dataViewFromJSArray(jsArrayRef)); + static bool _checkRefType(WasmExternRef? ref) => + js.JS('o => o instanceof Int8Array', ref); + + factory JSInt8ArrayImpl.fromRefUnchecked(WasmExternRef? ref) { + assert(_checkRefType(ref)); + return JSInt8ArrayImpl._(_dataViewFromJSArray(ref)); + } + + factory JSInt8ArrayImpl.fromRef(WasmExternRef? ref) { + if (!_checkRefType(ref)) { + return _throwConversionFailureError("Int8List"); + } + return JSInt8ArrayImpl.fromRefUnchecked(ref); + } factory JSInt8ArrayImpl.view( JSArrayBufferImpl buffer, @@ -940,8 +992,20 @@ final class JSUint8ClampedArrayImpl extends JSIntegerArrayBase factory JSUint8ClampedArrayImpl(int length) => JSUint8ClampedArrayImpl._(_newDataView(length)); - factory JSUint8ClampedArrayImpl.fromJSArray(WasmExternRef? jsArrayRef) => - JSUint8ClampedArrayImpl._(_dataViewFromJSArray(jsArrayRef)); + static bool _checkRefType(WasmExternRef? ref) => + js.JS('o => o instanceof Uint8ClampedArray', ref); + + factory JSUint8ClampedArrayImpl.fromRefUnchecked(WasmExternRef? ref) { + assert(_checkRefType(ref)); + return JSUint8ClampedArrayImpl._(_dataViewFromJSArray(ref)); + } + + factory JSUint8ClampedArrayImpl.fromRef(WasmExternRef? ref) { + if (!_checkRefType(ref)) { + return _throwConversionFailureError("Uint8ClampedList"); + } + return JSUint8ClampedArrayImpl.fromRefUnchecked(ref); + } factory JSUint8ClampedArrayImpl.view( JSArrayBufferImpl buffer, @@ -1020,8 +1084,20 @@ final class JSUint16ArrayImpl extends JSIntegerArrayBase factory JSUint16ArrayImpl(int length) => JSUint16ArrayImpl._(_newDataView(length * 2)); - factory JSUint16ArrayImpl.fromJSArray(WasmExternRef? jsArrayRef) => - JSUint16ArrayImpl._(_dataViewFromJSArray(jsArrayRef)); + static bool _checkRefType(WasmExternRef? ref) => + js.JS('o => o instanceof Uint16Array', ref); + + factory JSUint16ArrayImpl.fromRefUnchecked(WasmExternRef? ref) { + assert(_checkRefType(ref)); + return JSUint16ArrayImpl._(_dataViewFromJSArray(ref)); + } + + factory JSUint16ArrayImpl.fromRef(WasmExternRef? ref) { + if (!_checkRefType(ref)) { + return _throwConversionFailureError("Uint16List"); + } + return JSUint16ArrayImpl.fromRefUnchecked(ref); + } factory JSUint16ArrayImpl.view( JSArrayBufferImpl buffer, @@ -1120,8 +1196,20 @@ final class JSInt16ArrayImpl extends JSIntegerArrayBase factory JSInt16ArrayImpl(int length) => JSInt16ArrayImpl._(_newDataView(length * 2)); - factory JSInt16ArrayImpl.fromJSArray(WasmExternRef? jsArrayRef) => - JSInt16ArrayImpl._(_dataViewFromJSArray(jsArrayRef)); + static bool _checkRefType(WasmExternRef? ref) => + js.JS('o => o instanceof Int16Array', ref); + + factory JSInt16ArrayImpl.fromRefUnchecked(WasmExternRef? ref) { + assert(_checkRefType(ref)); + return JSInt16ArrayImpl._(_dataViewFromJSArray(ref)); + } + + factory JSInt16ArrayImpl.fromRef(WasmExternRef? ref) { + if (!_checkRefType(ref)) { + return _throwConversionFailureError("Int16List"); + } + return JSInt16ArrayImpl.fromRefUnchecked(ref); + } factory JSInt16ArrayImpl.view( JSArrayBufferImpl buffer, @@ -1220,8 +1308,20 @@ final class JSUint32ArrayImpl extends JSIntegerArrayBase factory JSUint32ArrayImpl(int length) => JSUint32ArrayImpl._(_newDataView(length * 4)); - factory JSUint32ArrayImpl.fromJSArray(WasmExternRef? jsArrayRef) => - JSUint32ArrayImpl._(_dataViewFromJSArray(jsArrayRef)); + static bool _checkRefType(WasmExternRef? ref) => + js.JS('o => o instanceof Uint32Array', ref); + + factory JSUint32ArrayImpl.fromRefUnchecked(WasmExternRef? ref) { + assert(_checkRefType(ref)); + return JSUint32ArrayImpl._(_dataViewFromJSArray(ref)); + } + + factory JSUint32ArrayImpl.fromRef(WasmExternRef? ref) { + if (!_checkRefType(ref)) { + return _throwConversionFailureError("Uint32List"); + } + return JSUint32ArrayImpl.fromRefUnchecked(ref); + } factory JSUint32ArrayImpl.view( JSArrayBufferImpl buffer, @@ -1320,8 +1420,20 @@ final class JSInt32ArrayImpl extends JSIntegerArrayBase factory JSInt32ArrayImpl(int length) => JSInt32ArrayImpl._(_newDataView(length * 4)); - factory JSInt32ArrayImpl.fromJSArray(WasmExternRef? jsArrayRef) => - JSInt32ArrayImpl._(_dataViewFromJSArray(jsArrayRef)); + static bool _checkRefType(WasmExternRef? ref) => + js.JS('o => o instanceof Int32Array', ref); + + factory JSInt32ArrayImpl.fromRefUnchecked(WasmExternRef? ref) { + assert(_checkRefType(ref)); + return JSInt32ArrayImpl._(_dataViewFromJSArray(ref)); + } + + factory JSInt32ArrayImpl.fromRef(WasmExternRef? ref) { + if (!_checkRefType(ref)) { + return _throwConversionFailureError("Int32List"); + } + return JSInt32ArrayImpl.fromRefUnchecked(ref); + } factory JSInt32ArrayImpl.view( JSArrayBufferImpl buffer, @@ -2031,7 +2143,7 @@ mixin _UnmodifiableDoubleListMixin { WasmExternRef? get toExternRef; JSArrayBufferImpl get buffer => - JSArrayBufferImpl.fromRefImmutable(_dataViewBuffer(toExternRef)); + JSArrayBufferImpl.fromRefImmutableUnchecked(_dataViewBuffer(toExternRef)); void operator []=(int index, double value) { throw UnsupportedError("Cannot modify an unmodifiable list"); @@ -2055,8 +2167,20 @@ final class JSFloat32ArrayImpl extends JSFloatArrayBase factory JSFloat32ArrayImpl(int length) => JSFloat32ArrayImpl._(_newDataView(length * 4)); - factory JSFloat32ArrayImpl.fromJSArray(WasmExternRef? jsArrayRef) => - JSFloat32ArrayImpl._(_dataViewFromJSArray(jsArrayRef)); + static bool _checkRefType(WasmExternRef? ref) => + js.JS('o => o instanceof Float32Array', ref); + + factory JSFloat32ArrayImpl.fromRefUnchecked(WasmExternRef? ref) { + assert(_checkRefType(ref)); + return JSFloat32ArrayImpl._(_dataViewFromJSArray(ref)); + } + + factory JSFloat32ArrayImpl.fromRef(WasmExternRef? ref) { + if (!_checkRefType(ref)) { + return _throwConversionFailureError("Float32List"); + } + return JSFloat32ArrayImpl.fromRefUnchecked(ref); + } factory JSFloat32ArrayImpl.view( JSArrayBufferImpl buffer, @@ -2155,8 +2279,20 @@ final class JSFloat64ArrayImpl extends JSFloatArrayBase factory JSFloat64ArrayImpl(int length) => JSFloat64ArrayImpl._(_newDataView(length * 8)); - factory JSFloat64ArrayImpl.fromJSArray(WasmExternRef? jsArrayRef) => - JSFloat64ArrayImpl._(_dataViewFromJSArray(jsArrayRef)); + static bool _checkRefType(WasmExternRef? ref) => + js.JS('o => o instanceof Float64Array', ref); + + factory JSFloat64ArrayImpl.fromRefUnchecked(WasmExternRef? ref) { + assert(_checkRefType(ref)); + return JSFloat64ArrayImpl._(_dataViewFromJSArray(ref)); + } + + factory JSFloat64ArrayImpl.fromRef(WasmExternRef? ref) { + if (!_checkRefType(ref)) { + return _throwConversionFailureError("Float64List"); + } + return JSFloat64ArrayImpl.fromRefUnchecked(ref); + } factory JSFloat64ArrayImpl.view( JSArrayBufferImpl buffer, @@ -2707,3 +2843,7 @@ void _setFloat64( WasmF64.fromDouble(value.toDouble()), WasmI32.fromBool(littleEndian), ); + +Never _throwConversionFailureError(String expectedType) => minify + ? throw ArgumentError() + : throw ArgumentError("JS reference cannot be converted to $expectedType"); diff --git a/sdk/lib/_internal/wasm/lib/regexp_helper.dart b/sdk/lib/_internal/wasm/lib/regexp_helper.dart index 2552f17a1c5..831a82d82ec 100644 --- a/sdk/lib/_internal/wasm/lib/regexp_helper.dart +++ b/sdk/lib/_internal/wasm/lib/regexp_helper.dart @@ -13,7 +13,7 @@ String quoteStringForRegExp(String string) => // This method is optimized to test before replacement, which should be // much faster. This might be worth measuring in real world use cases // though. - JSStringImpl( + JSStringImpl.fromRefUnchecked( JS(r"""s => { if (/[[\]{}()*+?.\\^$|]/.test(s)) { s = s.replace(/[[\]{}()*+?.\\^$|]/g, '\\$&'); @@ -132,7 +132,7 @@ class JSSyntaxRegExp implements RegExp { if (isJSRegExp(result)) return JSValue(result!) as JSNativeRegExp; // The returned value is the stringified JavaScript exception. Turn it into // a Dart exception. - String errorMessage = JSStringImpl(result!); + String errorMessage = JSStringImpl.fromRefUnchecked(result!); throw FormatException('Illegal RegExp pattern ($errorMessage)', source); } diff --git a/sdk/lib/_internal/wasm/lib/stack_trace_patch.dart b/sdk/lib/_internal/wasm/lib/stack_trace_patch.dart index 49eb601d1cd..320c3c540f3 100644 --- a/sdk/lib/_internal/wasm/lib/stack_trace_patch.dart +++ b/sdk/lib/_internal/wasm/lib/stack_trace_patch.dart @@ -17,7 +17,7 @@ class StackTrace { // `getCurrentStackTrace` and `StackTrace.current`. On Chrome, the first // line is not a frame but a line with just "Error", which we also remove. return _StringStackTrace( - JSStringImpl( + JSStringImpl.fromRefUnchecked( JS(r"""() => { let stackString = new Error().stack.toString(); let frames = stackString.split('\n'); diff --git a/sdk/lib/_internal/wasm/lib/string_patch.dart b/sdk/lib/_internal/wasm/lib/string_patch.dart index 9365d3fc207..672821828c1 100644 --- a/sdk/lib/_internal/wasm/lib/string_patch.dart +++ b/sdk/lib/_internal/wasm/lib/string_patch.dart @@ -73,7 +73,7 @@ class String { for (int i = 0; i < count; ++i) { dst.write(i, src.readUnsigned(start + i)); } - return JSStringImpl( + return JSStringImpl.fromRefUnchecked( jsStringFromCharCodeArray(dst, 0.toWasmI32(), count.toWasmI32()), ); } @@ -96,7 +96,7 @@ class String { for (int i = 0; i < count; ++i) { dst.write(i, charCodes[start + i]); } - return JSStringImpl( + return JSStringImpl.fromRefUnchecked( jsStringFromCharCodeArray(dst, 0.toWasmI32(), count.toWasmI32()), ); } @@ -118,7 +118,7 @@ class String { end += offset; final data = charCodes.data; - return JSStringImpl( + return JSStringImpl.fromRefUnchecked( jsStringFromCharCodeArray(data, start.toWasmI32(), end.toWasmI32()), ); } @@ -146,7 +146,7 @@ class String { } dst.write(i, charCode); } - return JSStringImpl( + return JSStringImpl.fromRefUnchecked( jsStringFromCharCodeArray(dst, 0.toWasmI32(), count.toWasmI32()), ); } @@ -196,7 +196,7 @@ class String { } } - return JSStringImpl( + return JSStringImpl.fromRefUnchecked( jsStringFromCharCodeArray(list, const WasmI32(0), WasmI32.fromInt(index)), ); } diff --git a/sdk/lib/_internal/wasm/lib/uri_patch.dart b/sdk/lib/_internal/wasm/lib/uri_patch.dart index dcdd60f7663..9172f6ca5d1 100644 --- a/sdk/lib/_internal/wasm/lib/uri_patch.dart +++ b/sdk/lib/_internal/wasm/lib/uri_patch.dart @@ -8,7 +8,7 @@ part of "core_patch.dart"; class Uri { @patch static Uri get base { - final currentUri = JSStringImpl( + final currentUri = JSStringImpl.fromRefUnchecked( JS("""() => { // On browsers return `globalThis.location.href` if (globalThis.location != null) { diff --git a/sdk/lib/_internal/wasm_js_compatibility/lib/boxed_int_to_string.dart b/sdk/lib/_internal/wasm_js_compatibility/lib/boxed_int_to_string.dart index bbebec6c089..2e01bf288fe 100644 --- a/sdk/lib/_internal/wasm_js_compatibility/lib/boxed_int_to_string.dart +++ b/sdk/lib/_internal/wasm_js_compatibility/lib/boxed_int_to_string.dart @@ -38,5 +38,5 @@ String _jsBigIntToString(int i, int radix) { WasmI64.fromInt(i), WasmI32.fromInt(radix), ); - return JSStringImpl(result); + return JSStringImpl.fromRefUnchecked(result); } diff --git a/sdk/lib/_internal/wasm_js_compatibility/lib/string_patch.dart b/sdk/lib/_internal/wasm_js_compatibility/lib/string_patch.dart index 754e0f6011c..ee3dca27708 100644 --- a/sdk/lib/_internal/wasm_js_compatibility/lib/string_patch.dart +++ b/sdk/lib/_internal/wasm_js_compatibility/lib/string_patch.dart @@ -73,17 +73,22 @@ class String { @patch factory String.fromCharCode(int charCode) => _fromCharCode(charCode); - static String _fromOneByteCharCode(int charCode) => JSStringImpl( - js.JS('c => String.fromCharCode(c)', charCode.toDouble()), - ); + static String _fromOneByteCharCode(int charCode) => + JSStringImpl.fromRefUnchecked( + js.JS( + 'c => String.fromCharCode(c)', + charCode.toDouble(), + ), + ); - static String _fromTwoByteCharCode(int low, int high) => JSStringImpl( - js.JS( - '(l, h) => String.fromCharCode(h, l)', - low.toDouble(), - high.toDouble(), - ), - ); + static String _fromTwoByteCharCode(int low, int high) => + JSStringImpl.fromRefUnchecked( + js.JS( + '(l, h) => String.fromCharCode(h, l)', + low.toDouble(), + high.toDouble(), + ), + ); static String _fromCharCode(int charCode) { if (0 <= charCode) { @@ -105,7 +110,7 @@ class String { int index, int end, ) { - return JSStringImpl( + return JSStringImpl.fromRefUnchecked( js.JS( '(c, i, e) => String.fromCharCode.apply(null, new Uint32Array(c.buffer, c.byteOffset + i, e))', charCodes.toExternRef, diff --git a/tests/web/wasm/js_interop_type_tests_test.dart b/tests/web/wasm/js_interop_type_tests_test.dart new file mode 100644 index 00000000000..2d4cf25fb42 --- /dev/null +++ b/tests/web/wasm/js_interop_type_tests_test.dart @@ -0,0 +1,157 @@ +// Copyright (c) 2025, 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. + +// dart2wasmOptions=--extra-compiler-option=--enable-experimental-wasm-interop + +import 'dart:_js_helper'; +import 'dart:_js_types'; +import 'dart:_string'; +import 'dart:_wasm'; +import 'dart:js_interop' hide JS; +import 'dart:typed_data'; + +import 'package:expect/expect.dart'; + +void main() { + Expect.throws( + () => + JSInt8ArrayImpl.fromRef(JS('() => new Uint8Array(10)')), + ); + Expect.throws( + () => + JSUint8ArrayImpl.fromRef(JS('() => new Int8Array(10)')), + ); + Expect.throws( + () => JSUint8ClampedArrayImpl.fromRef( + JS('() => new Uint8Array(10)'), + ), + ); + Expect.throws( + () => JSInt16ArrayImpl.fromRef( + JS('() => new Uint16Array(10)'), + ), + ); + Expect.throws( + () => JSUint16ArrayImpl.fromRef( + JS('() => new Int16Array(10)'), + ), + ); + Expect.throws( + () => JSInt32ArrayImpl.fromRef( + JS('() => new Uint32Array(10)'), + ), + ); + Expect.throws( + () => JSUint32ArrayImpl.fromRef( + JS('() => new Int32Array(10)'), + ), + ); + Expect.throws( + () => JSFloat32ArrayImpl.fromRef( + JS('() => new Float64Array(10)'), + ), + ); + Expect.throws( + () => JSFloat64ArrayImpl.fromRef( + JS('() => new Float32Array(10)'), + ), + ); + Expect.throws( + () => JSArrayBufferImpl.fromRef( + JS('() => new DataView(new ArrayBuffer(10))'), + ), + ); + Expect.throws( + () => + JSDataViewImpl.fromRef(JS('() => new ArrayBuffer(10)')), + ); + Expect.throws( + () => JSStringImpl.fromRef(JS('() => new ArrayBuffer(10)')), + ); + Expect.throws( + () => JSArrayImpl.fromRef(JS('() => "hi"')), + ); + + final JSInt8Array jsInt8ArrayBox = + JSValue(JS('() => new Uint8Array(10)')) as JSInt8Array; + Expect.throws(() { + final Int8List dartValue = jsInt8ArrayBox.toDart; + }); + + final JSUint8Array jsUint8ArrayBox = + JSValue(JS('() => new Int8Array(10)')) as JSUint8Array; + Expect.throws(() { + final Uint8List dartValue = jsUint8ArrayBox.toDart; + }); + + final JSUint8ClampedArray jsUint8ClampedArrayBox = + JSValue(JS('() => new Uint8Array(10)')) + as JSUint8ClampedArray; + Expect.throws(() { + final Uint8ClampedList dartValue = jsUint8ClampedArrayBox.toDart; + }); + + final JSInt16Array jsInt16ArrayBox = + JSValue(JS('() => new Uint16Array(10)')) as JSInt16Array; + Expect.throws(() { + final Int16List dartValue = jsInt16ArrayBox.toDart; + }); + + final JSUint16Array jsUint16ArrayBox = + JSValue(JS('() => new Int16Array(10)')) as JSUint16Array; + Expect.throws(() { + final Uint16List dartValue = jsUint16ArrayBox.toDart; + }); + + final JSInt32Array jsInt32ArrayBox = + JSValue(JS('() => new Uint32Array(10)')) as JSInt32Array; + Expect.throws(() { + final Int32List dartValue = jsInt32ArrayBox.toDart; + }); + + final JSUint32Array jsUint32ArrayBox = + JSValue(JS('() => new Int32Array(10)')) as JSUint32Array; + Expect.throws(() { + final Uint32List dartValue = jsUint32ArrayBox.toDart; + }); + + final JSFloat32Array jsFloat32ArrayBox = + JSValue(JS('() => new Float64Array(10)')) + as JSFloat32Array; + Expect.throws(() { + final Float32List dartValue = jsFloat32ArrayBox.toDart; + }); + + final JSFloat64Array jsFloat64ArrayBox = + JSValue(JS('() => new Float32Array(10)')) + as JSFloat64Array; + Expect.throws(() { + final Float64List dartValue = jsFloat64ArrayBox.toDart; + }); + + final JSArrayBuffer jsArrayBufferBox = + JSValue(JS('() => new DataView(new ArrayBuffer(10))')) + as JSArrayBuffer; + Expect.throws(() { + final ByteBuffer dartValue = jsArrayBufferBox.toDart; + }); + + final JSDataView jsDataViewBox = + JSValue(JS('() => new ArrayBuffer(10)')) as JSDataView; + Expect.throws(() { + final ByteData dartValue = jsDataViewBox.toDart; + }); + + final JSString jsStringBox = + JSValue(JS('() => new ArrayBuffer(10)')) as JSString; + Expect.throws(() { + final String dartValue = jsStringBox.toDart; + }); + + final JSArray jsArrayBox = + JSValue(JS('() => "hi"')) as JSArray; + Expect.throws(() { + final List dartValue = jsArrayBox.toDart; + }); +}