зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1688616 part 2 - Check for large ArrayBuffer{View}s in WebIDL bindings. r=edgar,lth
The dom::TypedArray type currently represents its length as uint32_t. Changing that to size_t/uint64_t would cause problems (truncation) for many 'Length()' callers. Instead of requiring a length check for each of those call sites, for now check for and reject large array buffers and views in the generated bindings. Code and tests are based on the implementation of the [AllowShared] attribute. Additional tests for the new JSAPIs will be added as part of bug 1674777. Differential Revision: https://phabricator.services.mozilla.com/D102912
This commit is contained in:
Родитель
6b0004f4f5
Коммит
8d290159a6
|
@ -5680,6 +5680,12 @@ def getJSToNativeConversionInfo(
|
|||
"%s" % (firstCap(sourceDescription), exceptionCode)
|
||||
)
|
||||
|
||||
def onFailureIsLarge():
|
||||
return CGGeneric(
|
||||
'cx.ThrowErrorMessage<MSG_TYPEDARRAY_IS_LARGE>("%s");\n'
|
||||
"%s" % (firstCap(sourceDescription), exceptionCode)
|
||||
)
|
||||
|
||||
def onFailureNotCallable(failureCode):
|
||||
return CGGeneric(
|
||||
failureCode
|
||||
|
@ -6899,21 +6905,37 @@ def getJSToNativeConversionInfo(
|
|||
objRef=objRef,
|
||||
badType=onFailureBadType(failureCode, type.name).define(),
|
||||
)
|
||||
if not isAllowShared and type.isBufferSource():
|
||||
if type.isBufferSource():
|
||||
if type.isArrayBuffer():
|
||||
isSharedMethod = "JS::IsSharedArrayBufferObject"
|
||||
isLargeMethod = "JS::IsLargeArrayBufferMaybeShared"
|
||||
else:
|
||||
assert type.isArrayBufferView() or type.isTypedArray()
|
||||
isSharedMethod = "JS::IsArrayBufferViewShared"
|
||||
isLargeMethod = "JS::IsLargeArrayBufferView"
|
||||
if not isAllowShared:
|
||||
template += fill(
|
||||
"""
|
||||
if (${isSharedMethod}(${objRef}.Obj())) {
|
||||
$*{badType}
|
||||
}
|
||||
""",
|
||||
isSharedMethod=isSharedMethod,
|
||||
objRef=objRef,
|
||||
badType=onFailureIsShared().define(),
|
||||
)
|
||||
# For now reject large (> 2 GB) ArrayBuffers and ArrayBufferViews.
|
||||
# Supporting this will require changing dom::TypedArray and
|
||||
# consumers.
|
||||
template += fill(
|
||||
"""
|
||||
if (${isSharedMethod}(${objRef}.Obj())) {
|
||||
if (${isLargeMethod}(${objRef}.Obj())) {
|
||||
$*{badType}
|
||||
}
|
||||
""",
|
||||
isSharedMethod=isSharedMethod,
|
||||
isLargeMethod=isLargeMethod,
|
||||
objRef=objRef,
|
||||
badType=onFailureIsShared().define(),
|
||||
badType=onFailureIsLarge().define(),
|
||||
)
|
||||
template = wrapObjectTemplate(
|
||||
template, type, "${declName}.SetNull();\n", failureCode
|
||||
|
|
|
@ -72,6 +72,7 @@ MSG_DEF(MSG_IS_NOT_PROMISE, 1, true, JSEXN_TYPEERR, "{0}Argument is not a Promis
|
|||
MSG_DEF(MSG_SW_INSTALL_ERROR, 3, true, JSEXN_TYPEERR, "{0}ServiceWorker script at {1} for scope {2} encountered an error during installation.")
|
||||
MSG_DEF(MSG_SW_SCRIPT_THREW, 3, true, JSEXN_TYPEERR, "{0}ServiceWorker script at {1} for scope {2} threw an exception during script evaluation.")
|
||||
MSG_DEF(MSG_TYPEDARRAY_IS_SHARED, 2, true, JSEXN_TYPEERR, "{0}{1} can't be a SharedArrayBuffer or an ArrayBufferView backed by a SharedArrayBuffer")
|
||||
MSG_DEF(MSG_TYPEDARRAY_IS_LARGE, 2, true, JSEXN_TYPEERR, "{0}{1} can't be an ArrayBuffer or an ArrayBufferView larger than 2 GB")
|
||||
MSG_DEF(MSG_CACHE_ADD_FAILED_RESPONSE, 4, true, JSEXN_TYPEERR, "{0}Cache got {1} response with bad status {2} while trying to add request {3}")
|
||||
MSG_DEF(MSG_SW_UPDATE_BAD_REGISTRATION, 3, true, JSEXN_TYPEERR, "{0}Failed to update the ServiceWorker for scope {1} because the registration has been {2} since the update was scheduled.")
|
||||
MSG_DEF(MSG_INVALID_DURATION_ERROR, 2, true, JSEXN_TYPEERR, "{0}Invalid duration '{1}'.")
|
||||
|
|
|
@ -132,7 +132,11 @@ struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage,
|
|||
inline void ComputeState() const {
|
||||
MOZ_ASSERT(inited());
|
||||
MOZ_ASSERT(!mComputed);
|
||||
GetLengthAndDataAndSharedness(mImplObj, &mLength, &mShared, &mData);
|
||||
uint32_t length;
|
||||
GetLengthAndDataAndSharedness(mImplObj, &length, &mShared, &mData);
|
||||
MOZ_RELEASE_ASSERT(length <= INT32_MAX,
|
||||
"Bindings must have checked ArrayBuffer{View} length");
|
||||
mLength = length;
|
||||
mComputed = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ support-files =
|
|||
file_proxies_via_xray.html
|
||||
forOf_iframe.html
|
||||
!/js/xpconnect/tests/mochitest/file_empty.html
|
||||
prefs =
|
||||
javascript.options.large_arraybuffers=true
|
||||
|
||||
[test_async_stacks.html]
|
||||
[test_ByteString.html]
|
||||
|
@ -88,3 +90,5 @@ skip-if = debug == false
|
|||
skip-if = debug == false
|
||||
[test_attributes_on_types.html]
|
||||
skip-if = debug == false
|
||||
[test_large_arraybuffers.html]
|
||||
skip-if = (debug == false || bits == 32) # Large ArrayBuffers are only supported on 64-bit platforms.
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1688616
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for large ArrayBuffers and views</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1688616">Mozilla Bug 1688616</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
<script type="application/javascript">
|
||||
/* global TestFunctions */
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]}, go);
|
||||
|
||||
function checkThrowsTooLarge(f) {
|
||||
let ex;
|
||||
try{
|
||||
f();
|
||||
ok(false, "Should have thrown!");
|
||||
} catch (e) {
|
||||
ex = e;
|
||||
}
|
||||
ok(ex.toString().includes("larger than 2 GB"), "Got exception: " + ex);
|
||||
}
|
||||
|
||||
function go() {
|
||||
let test = new TestFunctions();
|
||||
|
||||
const gb = 1 * 1024 * 1024 * 1024;
|
||||
let ab = new ArrayBuffer(5 * gb);
|
||||
checkThrowsTooLarge(() => test.testNotAllowShared(ab));
|
||||
checkThrowsTooLarge(() => test.testAllowShared(ab));
|
||||
checkThrowsTooLarge(() => test.testDictWithAllowShared({arrayBuffer: ab}));
|
||||
checkThrowsTooLarge(() => test.testUnionOfBuffferSource(ab));
|
||||
checkThrowsTooLarge(() => { test.arrayBuffer = ab; });
|
||||
checkThrowsTooLarge(() => { test.allowSharedArrayBuffer = ab; });
|
||||
checkThrowsTooLarge(() => { test.sequenceOfArrayBuffer = [ab]; });
|
||||
checkThrowsTooLarge(() => { test.sequenceOfAllowSharedArrayBuffer = [ab]; });
|
||||
|
||||
let ta = new Int8Array(ab, 0, 3 * gb);
|
||||
checkThrowsTooLarge(() => test.testNotAllowShared(ta));
|
||||
checkThrowsTooLarge(() => test.testAllowShared(ta));
|
||||
checkThrowsTooLarge(() => test.testDictWithAllowShared({arrayBufferView: ta}));
|
||||
checkThrowsTooLarge(() => test.testUnionOfBuffferSource(ta));
|
||||
checkThrowsTooLarge(() => { test.arrayBufferView = ta; });
|
||||
checkThrowsTooLarge(() => { test.allowSharedArrayBufferView = ta; });
|
||||
checkThrowsTooLarge(() => { test.sequenceOfArrayBufferView = [ta]; });
|
||||
checkThrowsTooLarge(() => { test.sequenceOfAllowSharedArrayBufferView = [ta]; });
|
||||
|
||||
let dv = new DataView(ab);
|
||||
checkThrowsTooLarge(() => test.testNotAllowShared(dv));
|
||||
checkThrowsTooLarge(() => test.testAllowShared(dv));
|
||||
checkThrowsTooLarge(() => test.testDictWithAllowShared({arrayBufferView: dv}));
|
||||
checkThrowsTooLarge(() => test.testUnionOfBuffferSource(dv));
|
||||
checkThrowsTooLarge(() => { test.arrayBufferView = dv; });
|
||||
checkThrowsTooLarge(() => { test.allowSharedArrayBufferView = dv; });
|
||||
checkThrowsTooLarge(() => { test.sequenceOfArrayBufferView = [dv]; });
|
||||
checkThrowsTooLarge(() => { test.sequenceOfAllowSharedArrayBufferView = [dv]; });
|
||||
|
||||
if (this.SharedArrayBuffer) {
|
||||
let sab = new SharedArrayBuffer(5 * gb);
|
||||
checkThrowsTooLarge(() => test.testAllowShared(sab));
|
||||
checkThrowsTooLarge(() => test.testDictWithAllowShared({allowSharedArrayBuffer: sab}));
|
||||
checkThrowsTooLarge(() => test.testUnionOfAllowSharedBuffferSource(sab));
|
||||
checkThrowsTooLarge(() => { test.allowSharedArrayBuffer = sab; });
|
||||
checkThrowsTooLarge(() => { test.sequenceOfAllowSharedArrayBuffer = [sab]; });
|
||||
|
||||
let sta = new Int8Array(sab);
|
||||
checkThrowsTooLarge(() => test.testAllowShared(sta));
|
||||
checkThrowsTooLarge(() => test.testDictWithAllowShared({allowSharedArrayBufferView: sta}));
|
||||
checkThrowsTooLarge(() => test.testUnionOfAllowSharedBuffferSource(sta));
|
||||
checkThrowsTooLarge(() => { test.allowSharedArrayBufferView = sta; });
|
||||
checkThrowsTooLarge(() => { test.sequenceOfAllowSharedArrayBufferView = [sta]; });
|
||||
}
|
||||
|
||||
// Small views on large buffers are fine.
|
||||
let ta2 = new Int8Array(ab, 4 * gb);
|
||||
is(ta2.byteLength, 1 * gb, "Small view on large ArrayBuffer");
|
||||
test.testNotAllowShared(ta2);
|
||||
test.arrayBufferView = ta2;
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -89,6 +89,14 @@ extern JS_PUBLIC_API void GetArrayBufferMaybeSharedLengthAndData(
|
|||
extern JS_PUBLIC_API uint8_t* GetArrayBufferMaybeSharedData(
|
||||
JSObject* obj, bool* isSharedMemory, const AutoRequireNoGC&);
|
||||
|
||||
/**
|
||||
* Returns whether the passed array buffer is 'large': its byteLength >= 2 GB.
|
||||
* See also SetLargeArrayBuffersEnabled.
|
||||
*
|
||||
* |obj| must pass a JS::IsArrayBufferObjectMaybeShared test.
|
||||
*/
|
||||
extern JS_FRIEND_API bool IsLargeArrayBufferMaybeShared(JSObject* obj);
|
||||
|
||||
} // namespace JS
|
||||
|
||||
#endif /* js_ArrayBufferMaybeShared_h */
|
||||
|
|
|
@ -419,4 +419,16 @@ JS_FRIEND_API JSObject* JS_NewDataView(JSContext* cx,
|
|||
JS::Handle<JSObject*> buffer,
|
||||
size_t byteOffset, size_t byteLength);
|
||||
|
||||
namespace JS {
|
||||
|
||||
/*
|
||||
* Returns whether the passed array buffer view is 'large': its byteLength >= 2
|
||||
* GB. See also SetLargeArrayBuffersEnabled.
|
||||
*
|
||||
* |obj| must pass a JS_IsArrayBufferViewObject test.
|
||||
*/
|
||||
JS_FRIEND_API bool IsLargeArrayBufferView(JSObject* obj);
|
||||
|
||||
} // namespace JS
|
||||
|
||||
#endif // js_experimental_TypedData_h
|
||||
|
|
|
@ -184,6 +184,8 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared {
|
|||
|
||||
static bool supportLargeBuffers;
|
||||
|
||||
static constexpr size_t MaxByteLengthForSmallBuffer = INT32_MAX;
|
||||
|
||||
// The length of an ArrayBuffer or SharedArrayBuffer can be at most
|
||||
// INT32_MAX. Allow a larger limit on 64-bit platforms if the experimental
|
||||
// large-buffers flag is used.
|
||||
|
@ -193,7 +195,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared {
|
|||
return size_t(8) * 1024 * 1024 * 1024; // 8 GB.
|
||||
}
|
||||
#endif
|
||||
return INT32_MAX;
|
||||
return MaxByteLengthForSmallBuffer;
|
||||
}
|
||||
|
||||
/** The largest number of bytes that can be stored inline. */
|
||||
|
|
|
@ -58,3 +58,19 @@ JS_PUBLIC_API uint8_t* JS::GetArrayBufferMaybeSharedData(
|
|||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::IsLargeArrayBufferMaybeShared(JSObject* obj) {
|
||||
#ifdef JS_64BIT
|
||||
obj = UnwrapArrayBufferMaybeShared(obj);
|
||||
MOZ_ASSERT(obj);
|
||||
size_t len = obj->is<ArrayBufferObject>()
|
||||
? obj->as<ArrayBufferObject>().byteLength().get()
|
||||
: obj->as<SharedArrayBufferObject>().byteLength().get();
|
||||
return len > ArrayBufferObject::MaxByteLengthForSmallBuffer;
|
||||
#else
|
||||
// Large ArrayBuffers are not supported on 32-bit.
|
||||
MOZ_ASSERT(ArrayBufferObject::maxBufferByteLength() ==
|
||||
ArrayBufferObject::MaxByteLengthForSmallBuffer);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -296,3 +296,18 @@ JS_PUBLIC_API bool JS::IsArrayBufferViewShared(JSObject* obj) {
|
|||
}
|
||||
return view->isSharedMemory();
|
||||
}
|
||||
|
||||
JS_FRIEND_API bool JS::IsLargeArrayBufferView(JSObject* obj) {
|
||||
#ifdef JS_64BIT
|
||||
obj = &obj->unwrapAs<ArrayBufferViewObject>();
|
||||
BufferSize len = obj->is<DataViewObject>()
|
||||
? obj->as<DataViewObject>().byteLength()
|
||||
: obj->as<TypedArrayObject>().byteLength();
|
||||
return len.get() > ArrayBufferObject::MaxByteLengthForSmallBuffer;
|
||||
#else
|
||||
// Large ArrayBuffers are not supported on 32-bit.
|
||||
MOZ_ASSERT(ArrayBufferObject::maxBufferByteLength() ==
|
||||
ArrayBufferObject::MaxByteLengthForSmallBuffer);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче