Bug 1556604 - [Structured Clone] Implement clone of Error objects r=evilpie

At this point I am convinced that not changing the SavedFrame code is the best way forward.
- We need to maintain the SavedFrame code for backwards compat.
- We already use the ChildCounter anyway for errors and cause.
- DOM Exception ended up landing before this without stack cloning support.

Differential Revision: https://phabricator.services.mozilla.com/D145184
This commit is contained in:
Tom Schuster 2022-06-16 22:01:14 +00:00
Родитель fea107f9ad
Коммит 51fadcc121
16 изменённых файлов: 699 добавлений и 253 удалений

Просмотреть файл

@ -217,13 +217,15 @@ enum TransferableOwnership {
class CloneDataPolicy {
bool allowIntraClusterClonableSharedObjects_;
bool allowSharedMemoryObjects_;
bool allowErrorStackFrames_;
public:
// The default is to deny all policy-controlled aspects.
CloneDataPolicy()
: allowIntraClusterClonableSharedObjects_(false),
allowSharedMemoryObjects_(false) {}
allowSharedMemoryObjects_(false),
allowErrorStackFrames_(false) {}
// SharedArrayBuffers and WASM modules can only be cloned intra-process
// because the shared memory areas are allocated in process-private memory or
@ -234,16 +236,20 @@ class CloneDataPolicy {
void allowIntraClusterClonableSharedObjects() {
allowIntraClusterClonableSharedObjects_ = true;
}
bool areIntraClusterClonableSharedObjectsAllowed() const {
return allowIntraClusterClonableSharedObjects_;
}
void allowSharedMemoryObjects() { allowSharedMemoryObjects_ = true; }
bool areSharedMemoryObjectsAllowed() const {
return allowSharedMemoryObjects_;
}
// The Error stack property is saved as SavedFrames, which
// have an associated principal. This principal can't be cloned
// in certain cases.
void allowErrorStackFrames() { allowErrorStackFrames_ = true; }
bool areErrorStackFramesAllowed() const { return allowErrorStackFrames_; }
};
} /* namespace JS */

Просмотреть файл

@ -4741,6 +4741,30 @@ bool js::testingFunc_serialize(JSContext* cx, unsigned argc, Value* vp) {
}
clonebuf.emplace(*scope, nullptr, nullptr);
}
if (!JS_GetProperty(cx, opts, "ErrorStackFrames", &v)) {
return false;
}
if (!v.isUndefined()) {
JSString* str = JS::ToString(cx, v);
if (!str) {
return false;
}
JSLinearString* poli = str->ensureLinear(cx);
if (!poli) {
return false;
}
if (StringEqualsLiteral(poli, "allow")) {
policy.allowErrorStackFrames();
} else if (StringEqualsLiteral(poli, "deny")) {
// default
} else {
JS_ReportErrorASCII(cx, "Invalid policy value for 'ErrorStackFrames'");
return false;
}
}
}
if (!clonebuf) {
@ -4771,6 +4795,7 @@ static bool Deserialize(JSContext* cx, unsigned argc, Value* vp) {
&args[0].toObject().as<CloneBufferObject>());
JS::CloneDataPolicy policy;
JS::StructuredCloneScope scope =
obj->isSynthetic() ? JS::StructuredCloneScope::DifferentProcess
: JS::StructuredCloneScope::SameProcess;
@ -4830,6 +4855,30 @@ static bool Deserialize(JSContext* cx, unsigned argc, Value* vp) {
scope = *maybeScope;
}
if (!JS_GetProperty(cx, opts, "ErrorStackFrames", &v)) {
return false;
}
if (!v.isUndefined()) {
JSString* str = JS::ToString(cx, v);
if (!str) {
return false;
}
JSLinearString* poli = str->ensureLinear(cx);
if (!poli) {
return false;
}
if (StringEqualsLiteral(poli, "allow")) {
policy.allowErrorStackFrames();
} else if (StringEqualsLiteral(poli, "deny")) {
// default
} else {
JS_ReportErrorASCII(cx, "Invalid policy value for 'ErrorStackFrames'");
return false;
}
}
}
// Clone buffer was already consumed?

Просмотреть файл

@ -0,0 +1,144 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
load(libdir + "asserts.js");
function roundtrip(error) {
let opts = {ErrorStackFrames: "allow"};
return deserialize(serialize(error, [], opts), opts);
}
// Basic
{
let error = new Error("hello world");
let cloned = roundtrip(error);
assertDeepEq(cloned, error);
assertEq(cloned.name, "Error");
assertEq(cloned.message, "hello world");
assertEq(cloned.stack, error.stack);
}
let constructors = [Error, EvalError, RangeError, ReferenceError,
SyntaxError, TypeError, URIError];
for (let constructor of constructors) {
// With message
let error = new constructor("hello");
let cloned = roundtrip(error);
assertDeepEq(cloned, error);
assertEq(cloned.hasOwnProperty('message'), true);
assertEq(cloned instanceof constructor, true);
// Without message
error = new constructor();
cloned = roundtrip(error);
assertDeepEq(cloned, error);
assertEq(cloned.hasOwnProperty('message'), false);
assertEq(cloned instanceof constructor, true);
// Custom name
error = new constructor("hello");
error.name = "MyError";
cloned = roundtrip(error);
assertEq(cloned.name, "Error");
assertEq(cloned.message, "hello");
assertEq(cloned.stack, error.stack);
if (constructor !== Error) {
assertEq(cloned instanceof constructor, false);
}
// |cause| property
error = new constructor("hello", { cause: new Error("foobar") });
cloned = roundtrip(error);
assertDeepEq(cloned, error);
assertEq(cloned.hasOwnProperty('message'), true);
assertEq(cloned instanceof constructor, true);
assertEq(cloned.stack, error.stack);
assertEq(cloned.stack === undefined, false);
// Subclassing
error = new (class MyError extends constructor {});
cloned = roundtrip(error);
assertEq(cloned.name, constructor.name);
assertEq(cloned.hasOwnProperty('message'), false);
assertEq(cloned.stack, error.stack);
assertEq(cloned instanceof Error, true);
// Cross-compartment
error = evalcx(`new ${constructor.name}("hello")`);
cloned = roundtrip(error);
assertEq(cloned.name, constructor.name);
assertEq(cloned.message, "hello");
assertEq(cloned.stack, error.stack);
assertEq(cloned instanceof constructor, true);
}
// Non-string message
{
let error = new Error("hello world");
error.message = 123;
let cloned = roundtrip(error);
assertEq(cloned.message, "123");
assertEq(cloned.hasOwnProperty('message'), true);
error = new Error();
Object.defineProperty(error, 'message', { get: () => {} });
cloned = roundtrip(error);
assertEq(cloned.message, "");
assertEq(cloned.hasOwnProperty('message'), false);
}
// AggregateError
{
// With message
let error = new AggregateError([{a: 1}, {b: 2}], "hello");
let cloned = roundtrip(error);
assertDeepEq(cloned, error);
assertEq(cloned.hasOwnProperty('message'), true);
assertEq(cloned instanceof AggregateError, true);
// Without message
error = new AggregateError([{a: 1}, {b: 2}]);
cloned = roundtrip(error);
assertDeepEq(cloned, error);
assertEq(cloned.hasOwnProperty('message'), false);
assertEq(cloned instanceof AggregateError, true);
// Custom name breaks this!
error = new AggregateError([{a: 1}, {b: 2}]);
error.name = "MyError";
cloned = roundtrip(error);
assertEq(cloned.name, "Error");
assertEq(cloned.message, "");
assertEq(cloned.stack, error.stack);
assertEq(cloned instanceof AggregateError, false);
assertEq(cloned.errors, undefined);
assertEq(cloned.hasOwnProperty('errors'), false);
}
{
let error = new Error();
// When serializing without stack-frames, deserialization is empty.
let cloned = deserialize(serialize(error, [], {ErrorStackFrames: "deny"}),
{ErrorStackFrames: "allow"});
assertEq(cloned.name, "Error");
assertEq(cloned.stack, "");
// Defaults to disallow.
cloned = deserialize(serialize(error));
assertEq(cloned.name, "Error");
assertEq(cloned.stack, "");
// Unexpected stack frames during deserialization throw.
assertErrorMessage(() => {
deserialize(serialize(error, [], {ErrorStackFrames: "allow"}),
{ErrorStackFrames: "deny"});
}, InternalError, "bad serialized structured data (disallowed 'stack' field encountered for Error object)");
// Sanity check
cloned = roundtrip(error);
assertEq(cloned.stack.length > 0, true);
}

Просмотреть файл

@ -13,7 +13,6 @@ function check(v) {
}
// Unsupported object types.
check(new Error("oops"));
check(this);
check(Math);
check(function () {});

Просмотреть файл

@ -126,6 +126,17 @@ class ErrorObject : public NativeObject {
return mozilla::Some(value);
}
void setStackSlot(const Value& stack) {
MOZ_ASSERT(stack.isObjectOrNull());
setReservedSlot(STACK_SLOT, stack);
}
void setCauseSlot(const Value& cause) {
MOZ_ASSERT(!cause.isMagic());
MOZ_ASSERT(getCause().isSome());
setReservedSlot(CAUSE_SLOT, cause);
}
// Getter and setter for the Error.prototype.stack accessor.
static bool getStack(JSContext* cx, unsigned argc, Value* vp);
static bool getStack_impl(JSContext* cx, const CallArgs& args);

Просмотреть файл

@ -32,6 +32,7 @@
#include "mozilla/CheckedInt.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Maybe.h"
#include "mozilla/RangedPtr.h"
#include "mozilla/ScopeExit.h"
@ -56,6 +57,7 @@
#include "js/SharedArrayBuffer.h" // JS::IsSharedArrayBufferObject
#include "js/Wrapper.h"
#include "vm/BigIntType.h"
#include "vm/ErrorObject.h"
#include "vm/JSContext.h"
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/RegExpObject.h"
@ -65,6 +67,8 @@
#include "vm/WrapperObject.h"
#include "wasm/WasmJS.h"
#include "vm/Compartment-inl.h"
#include "vm/ErrorObject-inl.h"
#include "vm/InlineCharBuffer-inl.h"
#include "vm/JSContext-inl.h"
#include "vm/JSObject-inl.h"
@ -78,6 +82,7 @@ using JS::RegExpFlags;
using JS::RootedValueVector;
using mozilla::AssertedCast;
using mozilla::BitwiseCast;
using mozilla::Maybe;
using mozilla::NativeEndian;
using mozilla::NumbersAreIdentical;
using mozilla::RangedPtr;
@ -135,6 +140,8 @@ enum StructuredDataType : uint32_t {
SCTAG_TYPED_ARRAY_OBJECT,
SCTAG_DATA_VIEW_OBJECT,
SCTAG_ERROR_OBJECT,
SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100,
SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int8,
SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8,
@ -436,6 +443,8 @@ struct JSStructuredCloneReader {
bool readHeader();
bool readTransferMap();
[[nodiscard]] bool readUint32(uint32_t* num);
template <typename CharT>
JSString* readStringImpl(uint32_t nchars, gc::InitialHeap heap);
JSString* readString(uint32_t data, gc::InitialHeap heap = gc::DefaultHeap);
@ -444,15 +453,35 @@ struct JSStructuredCloneReader {
[[nodiscard]] bool readTypedArray(uint32_t arrayType, uint64_t nelems,
MutableHandleValue vp, bool v1Read = false);
[[nodiscard]] bool readDataView(uint64_t byteLength, MutableHandleValue vp);
[[nodiscard]] bool readArrayBuffer(StructuredDataType type, uint32_t data,
MutableHandleValue vp);
[[nodiscard]] bool readSharedArrayBuffer(MutableHandleValue vp);
[[nodiscard]] bool readSharedWasmMemory(uint32_t nbytes,
MutableHandleValue vp);
[[nodiscard]] bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems,
MutableHandleValue vp);
JSObject* readSavedFrame(uint32_t principalsTag);
[[nodiscard]] bool readSharedArrayBuffer(MutableHandleValue vp);
[[nodiscard]] bool readSharedWasmMemory(uint32_t nbytes,
MutableHandleValue vp);
// A serialized SavedFrame contains primitive values in a header followed by
// an optional parent frame that is read recursively.
[[nodiscard]] JSObject* readSavedFrameHeader(uint32_t principalsTag);
[[nodiscard]] bool readSavedFrameFields(Handle<SavedFrame*> frameObj,
HandleValue parent, bool* state);
// A serialized Error contains primitive values in a header followed by
// 'cause', 'errors', and 'stack' fields that are read recursively.
[[nodiscard]] JSObject* readErrorHeader(uint32_t type);
[[nodiscard]] bool readErrorFields(Handle<ErrorObject*> errorObj,
HandleValue cause, bool* state);
[[nodiscard]] bool readMapField(Handle<MapObject*> mapObj, HandleValue key);
[[nodiscard]] bool readObjectField(HandleObject obj, HandleValue key);
[[nodiscard]] bool startRead(MutableHandleValue vp,
gc::InitialHeap strHeap = gc::DefaultHeap);
@ -575,6 +604,7 @@ struct JSStructuredCloneWriter {
bool traverseMap(HandleObject obj);
bool traverseSet(HandleObject obj);
bool traverseSavedFrame(HandleObject obj);
bool traverseError(HandleObject obj);
template <typename... Args>
bool reportDataCloneError(uint32_t errorId, Args&&... aArgs);
@ -609,6 +639,7 @@ struct JSStructuredCloneWriter {
// For Map: Key followed by value
// For Set: Key
// For SavedFrame: parent SavedFrame
// For Error: cause, errors, stack
RootedValueVector otherEntries;
// The "memory" list described in the HTML5 internal structured cloning
@ -1611,9 +1642,9 @@ bool JSStructuredCloneWriter::traverseObject(HandleObject obj, ESClass cls) {
// <Map tag>
// <key1 class tag>
// <value1 class tag>
// ...key1 data...
// ...key1 fields...
// <end-of-children marker for key1>
// ...value1 data...
// ...value1 fields...
// <end-of-children marker for value1>
// <end-of-children marker for Map>
//
@ -1778,6 +1809,163 @@ bool JSStructuredCloneWriter::traverseSavedFrame(HandleObject obj) {
return true;
}
// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
// 2.7.3 StructuredSerializeInternal ( value, forStorage [ , memory ] )
//
// Step 17. Otherwise, if value has an [[ErrorData]] internal slot and
// value is not a platform object, then:
//
// Note: This contains custom extensions for handling non-standard properties.
bool JSStructuredCloneWriter::traverseError(HandleObject obj) {
JSContext* cx = context();
// 1. Let name be ? Get(value, "name").
RootedValue name(cx);
if (!GetProperty(cx, obj, obj, cx->names().name, &name)) {
return false;
}
// 2. If name is not one of "Error", "EvalError", "RangeError",
// "ReferenceError", "SyntaxError", "TypeError", or "URIError",
// (not yet specified: or "AggregateError")
// then set name to "Error".
JSExnType type = JSEXN_ERR;
if (name.isString()) {
JSLinearString* linear = name.toString()->ensureLinear(cx);
if (!linear) {
return false;
}
if (EqualStrings(linear, cx->names().Error)) {
type = JSEXN_ERR;
} else if (EqualStrings(linear, cx->names().EvalError)) {
type = JSEXN_EVALERR;
} else if (EqualStrings(linear, cx->names().RangeError)) {
type = JSEXN_RANGEERR;
} else if (EqualStrings(linear, cx->names().ReferenceError)) {
type = JSEXN_REFERENCEERR;
} else if (EqualStrings(linear, cx->names().SyntaxError)) {
type = JSEXN_SYNTAXERR;
} else if (EqualStrings(linear, cx->names().TypeError)) {
type = JSEXN_TYPEERR;
} else if (EqualStrings(linear, cx->names().URIError)) {
type = JSEXN_URIERR;
} else if (EqualStrings(linear, cx->names().AggregateError)) {
type = JSEXN_AGGREGATEERR;
}
}
// 3. Let valueMessageDesc be ? value.[[GetOwnProperty]]("message").
RootedId messageId(cx, NameToId(cx->names().message));
Rooted<Maybe<PropertyDescriptor>> messageDesc(cx);
if (!GetOwnPropertyDescriptor(cx, obj, messageId, &messageDesc)) {
return false;
}
// 4. Let message be undefined if IsDataDescriptor(valueMessageDesc) is false,
// and ? ToString(valueMessageDesc.[[Value]]) otherwise.
RootedString message(cx);
if (messageDesc.isSome() && messageDesc->isDataDescriptor()) {
RootedValue messageVal(cx, messageDesc->value());
message = ToString<CanGC>(cx, messageVal);
if (!message) {
return false;
}
}
// 5. Set serialized to { [[Type]]: "Error", [[Name]]: name, [[Message]]:
// message }.
if (!objs.append(ObjectValue(*obj))) {
return false;
}
Rooted<ErrorObject*> unwrapped(cx, obj->maybeUnwrapAs<ErrorObject>());
MOZ_ASSERT(unwrapped);
// Non-standard: Serialize |stack|.
// The Error stack property is saved as SavedFrames, which
// have an associated principal. This principal can't be cloned
// in certain cases.
RootedValue stack(cx, NullValue());
if (cloneDataPolicy.areErrorStackFramesAllowed()) {
RootedObject stackObj(cx, unwrapped->stack());
if (stackObj && stackObj->canUnwrapAs<SavedFrame>()) {
stack.setObject(*stackObj);
if (!cx->compartment()->wrap(cx, &stack)) {
return false;
}
}
}
if (!otherEntries.append(stack)) {
return false;
}
// Serialize |errors|
if (type == JSEXN_AGGREGATEERR) {
RootedValue errors(cx);
if (!GetProperty(cx, obj, obj, cx->names().errors, &errors)) {
return false;
}
if (!otherEntries.append(errors)) {
return false;
}
} else {
if (!otherEntries.append(NullValue())) {
return false;
}
}
// Non-standard: Serialize |cause|. Because this property
// might be missing we also write "hasCause" later.
Rooted<Maybe<Value>> cause(cx, unwrapped->getCause());
if (!cx->compartment()->wrap(cx, &cause)) {
return false;
}
if (!otherEntries.append(cause.get().valueOr(NullValue()))) {
return false;
}
// |cause| + |errors| + |stack|, pushed in reverse order
if (!counts.append(3)) {
return false;
}
checkStack();
if (!out.writePair(SCTAG_ERROR_OBJECT, type)) {
return false;
}
RootedValue val(cx, message ? StringValue(message) : NullValue());
if (!writePrimitive(val)) {
return false;
}
// hasCause
val = BooleanValue(cause.isSome());
if (!writePrimitive(val)) {
return false;
}
// Non-standard: Also serialize fileName, lineNumber and columnNumber.
{
JSAutoRealm ar(cx, unwrapped);
val = StringValue(unwrapped->fileName(cx));
}
if (!cx->compartment()->wrap(cx, &val) || !writePrimitive(val)) {
return false;
}
val = Int32Value(unwrapped->lineNumber());
if (!writePrimitive(val)) {
return false;
}
val = Int32Value(unwrapped->columnNumber());
return writePrimitive(val);
}
bool JSStructuredCloneWriter::writePrimitive(HandleValue v) {
MOZ_ASSERT(v.isPrimitive());
context()->check(v);
@ -1884,6 +2072,8 @@ bool JSStructuredCloneWriter::startWrite(HandleValue v) {
return traverseSet(obj);
case ESClass::Map:
return traverseMap(obj);
case ESClass::Error:
return traverseError(obj);
case ESClass::BigInt: {
RootedValue unboxed(context());
if (!Unbox(context(), obj, &unboxed)) {
@ -1895,7 +2085,6 @@ bool JSStructuredCloneWriter::startWrite(HandleValue v) {
case ESClass::MapIterator:
case ESClass::SetIterator:
case ESClass::Arguments:
case ESClass::Error:
case ESClass::Function:
break;
@ -2136,6 +2325,10 @@ bool JSStructuredCloneWriter::write(HandleValue v) {
RootedValue val(context());
RootedId id(context());
RootedValue cause(context());
RootedValue errors(context());
RootedValue stack(context());
while (!counts.empty()) {
obj = &objs.back().toObject();
context()->check(obj);
@ -2165,6 +2358,21 @@ bool JSStructuredCloneWriter::write(HandleValue v) {
if (!startWrite(key)) {
return false;
}
} else if (cls == ESClass::Error) {
cause = otherEntries.popCopy();
checkStack();
counts.back()--;
errors = otherEntries.popCopy();
checkStack();
counts.back()--;
stack = otherEntries.popCopy();
checkStack();
if (!startWrite(cause) || !startWrite(errors) || !startWrite(stack)) {
return false;
}
} else {
id = objectEntries.popCopy();
key = IdToValue(id);
@ -2230,6 +2438,20 @@ JSString* JSStructuredCloneReader::readString(uint32_t data,
: readStringImpl<char16_t>(nchars, heap);
}
[[nodiscard]] bool JSStructuredCloneReader::readUint32(uint32_t* num) {
Rooted<Value> lineVal(context());
if (!startRead(&lineVal)) {
return false;
}
if (!lineVal.isInt32()) {
JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
JSMSG_SC_BAD_SERIALIZED_DATA, "integer required");
return false;
}
*num = uint32_t(lineVal.toInt32());
return true;
}
BigInt* JSStructuredCloneReader::readBigInt(uint32_t data) {
size_t length = data & BitMask(31);
bool isNegative = data & (1 << 31);
@ -2836,7 +3058,17 @@ bool JSStructuredCloneReader::startRead(MutableHandleValue vp,
}
case SCTAG_SAVED_FRAME_OBJECT: {
auto obj = readSavedFrame(data);
auto* obj = readSavedFrameHeader(data);
if (!obj || !objs.append(ObjectValue(*obj)) ||
!objState.append(std::make_pair(obj, false))) {
return false;
}
vp.setObject(*obj);
break;
}
case SCTAG_ERROR_OBJECT: {
auto* obj = readErrorHeader(data);
if (!obj || !objs.append(ObjectValue(*obj)) ||
!objState.append(std::make_pair(obj, false))) {
return false;
@ -3079,7 +3311,8 @@ bool JSStructuredCloneReader::readTransferMap() {
return true;
}
JSObject* JSStructuredCloneReader::readSavedFrame(uint32_t principalsTag) {
JSObject* JSStructuredCloneReader::readSavedFrameHeader(
uint32_t principalsTag) {
Rooted<SavedFrame*> savedFrame(context(), SavedFrame::create(context()));
if (!savedFrame) {
return nullptr;
@ -3150,16 +3383,14 @@ JSObject* JSStructuredCloneReader::readSavedFrame(uint32_t principalsTag) {
RootedValue lineVal(context());
uint32_t line;
if (!startRead(&lineVal) || !lineVal.isNumber() ||
!ToUint32(context(), lineVal, &line)) {
if (!readUint32(&line)) {
return nullptr;
}
savedFrame->initLine(line);
RootedValue columnVal(context());
uint32_t column;
if (!startRead(&columnVal) || !columnVal.isNumber() ||
!ToUint32(context(), columnVal, &column)) {
if (!readUint32(&column)) {
return nullptr;
}
savedFrame->initColumn(column);
@ -3210,6 +3441,216 @@ JSObject* JSStructuredCloneReader::readSavedFrame(uint32_t principalsTag) {
return savedFrame;
}
// SavedFrame object: there is one child value, the parent SavedFrame,
// which is either null or another SavedFrame object.
bool JSStructuredCloneReader::readSavedFrameFields(Handle<SavedFrame*> frameObj,
HandleValue parent,
bool* state) {
if (*state) {
JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
JSMSG_SC_BAD_SERIALIZED_DATA,
"multiple SavedFrame parents");
return false;
}
SavedFrame* parentFrame;
if (parent.isNull()) {
parentFrame = nullptr;
} else if (parent.isObject() && parent.toObject().is<SavedFrame>()) {
parentFrame = &parent.toObject().as<SavedFrame>();
} else {
JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
JSMSG_SC_BAD_SERIALIZED_DATA,
"invalid SavedFrame parent");
return false;
}
frameObj->initParent(parentFrame);
*state = true;
return true;
}
JSObject* JSStructuredCloneReader::readErrorHeader(uint32_t type) {
JSContext* cx = context();
switch (type) {
case JSEXN_ERR:
case JSEXN_EVALERR:
case JSEXN_RANGEERR:
case JSEXN_REFERENCEERR:
case JSEXN_SYNTAXERR:
case JSEXN_TYPEERR:
case JSEXN_URIERR:
case JSEXN_AGGREGATEERR:
break;
default:
JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
JSMSG_SC_BAD_SERIALIZED_DATA,
"invalid error type");
return nullptr;
}
RootedString message(cx);
{
RootedValue messageVal(cx);
if (!startRead(&messageVal)) {
return nullptr;
}
if (messageVal.isString()) {
message = messageVal.toString();
} else if (!messageVal.isNull()) {
JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
JSMSG_SC_BAD_SERIALIZED_DATA,
"invalid 'message' field for Error object");
return nullptr;
}
}
// We have to set |cause| to something if it exists, otherwise the shape
// would be wrong. The actual value will be overwritten later.
RootedValue val(cx);
if (!startRead(&val)) {
return nullptr;
}
bool hasCause = ToBoolean(val);
Rooted<Maybe<Value>> cause(cx, mozilla::Nothing());
if (hasCause) {
cause = mozilla::Some(BooleanValue(true));
}
if (!startRead(&val)) {
return nullptr;
}
if (!val.isString()) {
JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
JSMSG_SC_BAD_SERIALIZED_DATA,
"invalid 'fileName' field for Error object");
return nullptr;
}
RootedString fileName(cx, val.toString());
uint32_t lineNumber, columnNumber;
if (!readUint32(&lineNumber) || !readUint32(&columnNumber)) {
return nullptr;
}
// The |cause| and |stack| slots of the objects might be overwritten later.
// For AggregateErrors the |errors| property will be added.
RootedObject errorObj(
cx, ErrorObject::create(cx, static_cast<JSExnType>(type), nullptr,
fileName, 0, lineNumber, columnNumber, nullptr,
message, cause));
if (!errorObj) {
return nullptr;
}
return errorObj;
}
// Error objects have 3 fields, some or all of them null: cause,
// errors, and stack.
bool JSStructuredCloneReader::readErrorFields(Handle<ErrorObject*> errorObj,
HandleValue cause, bool* state) {
JSContext* cx = context();
if (*state) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_SC_BAD_SERIALIZED_DATA,
"unexpected child value seen for Error object");
return false;
}
RootedValue errors(cx);
RootedValue stack(cx);
if (!startRead(&errors) || !startRead(&stack)) {
return false;
}
bool hasCause = errorObj->getCause().isSome();
if (hasCause) {
errorObj->setCauseSlot(cause);
} else if (!cause.isNull()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_SC_BAD_SERIALIZED_DATA,
"invalid 'cause' field for Error object");
return false;
}
if (errorObj->type() == JSEXN_AGGREGATEERR) {
if (!DefineDataProperty(context(), errorObj, cx->names().errors, errors,
0)) {
return false;
}
} else if (!errors.isNull()) {
JS_ReportErrorNumberASCII(
cx, GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA,
"unexpected 'errors' field seen for non-AggregateError");
return false;
}
if (stack.isObject()) {
RootedObject stackObj(cx, &stack.toObject());
if (!stackObj->is<SavedFrame>()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_SC_BAD_SERIALIZED_DATA,
"invalid 'stack' field for Error object");
return false;
}
if (!cloneDataPolicy.areErrorStackFramesAllowed()) {
JS_ReportErrorNumberASCII(
cx, GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA,
"disallowed 'stack' field encountered for Error object");
return false;
}
errorObj->setStackSlot(stack);
} else if (!stack.isNull()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_SC_BAD_SERIALIZED_DATA,
"invalid 'stack' field for Error object");
return false;
}
*state = true;
return true;
}
// Read a value and treat as a key,value pair.
bool JSStructuredCloneReader::readMapField(Handle<MapObject*> mapObj,
HandleValue key) {
RootedValue val(context());
if (!startRead(&val)) {
return false;
}
return MapObject::set(context(), mapObj, key, val);
}
// Read a value and treat as a key,value pair. Interpret as a plain property
// value.
bool JSStructuredCloneReader::readObjectField(HandleObject obj,
HandleValue key) {
RootedValue val(context());
if (!startRead(&val)) {
return false;
}
if (!key.isString() && !key.isInt32()) {
JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
JSMSG_SC_BAD_SERIALIZED_DATA,
"property key expected");
return false;
}
RootedId id(context());
if (!PrimitiveValueToId<CanGC>(context(), key, &id)) {
return false;
}
if (!DefineDataProperty(context(), obj, id, val)) {
return false;
}
return true;
}
// Perform the whole recursive reading procedure.
bool JSStructuredCloneReader::read(MutableHandleValue vp, size_t nbytes) {
auto startTime = mozilla::TimeStamp::Now();
@ -3278,7 +3719,7 @@ bool JSStructuredCloneReader::read(MutableHandleValue vp, size_t nbytes) {
}
if (key.isNull() && !(obj->is<MapObject>() || obj->is<SetObject>() ||
obj->is<SavedFrame>())) {
obj->is<SavedFrame>() || obj->is<ErrorObject>())) {
// Backwards compatibility: Null formerly indicated the end of
// object properties.
@ -3289,73 +3730,39 @@ bool JSStructuredCloneReader::read(MutableHandleValue vp, size_t nbytes) {
continue;
}
// Set object: the values between obj header (from startRead()) and
// SCTAG_END_OF_KEYS are all interpreted as values to add to the set.
context()->check(key);
if (obj->is<SetObject>()) {
// Set object: the values between obj header (from startRead()) and
// SCTAG_END_OF_KEYS are all interpreted as values to add to the set.
if (!SetObject::add(context(), obj, key)) {
return false;
}
continue;
}
// SavedFrame object: there is one child value, the parent SavedFrame,
// which is either null or another SavedFrame object.
if (obj->is<SavedFrame>()) {
SavedFrame* parentFrame;
if (key.isNull()) {
parentFrame = nullptr;
} else if (key.isObject() && key.toObject().is<SavedFrame>()) {
parentFrame = &key.toObject().as<SavedFrame>();
} else {
JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
JSMSG_SC_BAD_SERIALIZED_DATA,
"invalid SavedFrame parent");
} else if (obj->is<MapObject>()) {
Rooted<MapObject*> mapObj(context(), &obj->as<MapObject>());
if (!readMapField(mapObj, key)) {
return false;
}
} else if (obj->is<SavedFrame>()) {
Rooted<SavedFrame*> frameObj(context(), &obj->as<SavedFrame>());
MOZ_ASSERT(objState[objStateIdx].first() == obj);
bool& state = objState[objStateIdx].second();
if (state == false) {
obj->as<SavedFrame>().initParent(parentFrame);
state = true;
} else {
JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
JSMSG_SC_BAD_SERIALIZED_DATA,
"multiple SavedFrame parents");
bool state = objState[objStateIdx].second();
if (!readSavedFrameFields(frameObj, key, &state)) {
return false;
}
continue;
}
// Everything else uses a series of key,value,key,value,... Value
// objects.
RootedValue val(context());
if (!startRead(&val)) {
return false;
}
if (obj->is<MapObject>()) {
// For a Map, store those <key,value> pairs in the contained map
// data structure.
if (!MapObject::set(context(), obj, key, val)) {
objState[objStateIdx].second() = state;
} else if (obj->is<ErrorObject>()) {
Rooted<ErrorObject*> errorObj(context(), &obj->as<ErrorObject>());
MOZ_ASSERT(objState[objStateIdx].first() == obj);
bool state = objState[objStateIdx].second();
if (!readErrorFields(errorObj, key, &state)) {
return false;
}
objState[objStateIdx].second() = state;
} else {
// For any other Object, interpret them as plain properties.
RootedId id(context());
if (!key.isString() && !key.isInt32()) {
JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
JSMSG_SC_BAD_SERIALIZED_DATA,
"property key expected");
return false;
}
if (!PrimitiveValueToId<CanGC>(context(), key, &id)) {
return false;
}
if (!DefineDataProperty(context(), obj, id, val)) {
// Everything else uses a series of key,value,key,value,... Value
// objects.
if (!readObjectField(obj, key)) {
return false;
}
}
@ -3369,8 +3776,8 @@ bool JSStructuredCloneReader::read(MutableHandleValue vp, size_t nbytes) {
#ifndef FUZZING
bool extraData;
if (tailStartPos.isSome()) {
// in.tell() is the end of the main data. If "tail" data was consumed, then
// check whether there's any data between the main data and the
// in.tell() is the end of the main data. If "tail" data was consumed,
// then check whether there's any data between the main data and the
// beginning of the tail, or after the last read point in the tail.
extraData = (in.tell() != *tailStartPos || !tailEndPos->done());
} else {

Просмотреть файл

@ -5,68 +5,10 @@
expected: OK
[structured-clone.any.html?81-100]
[SyntaxError: SyntaxError]
expected: FAIL
[Error: Error: abc]
expected: FAIL
[Error: Error]
expected: FAIL
[RangeError: RangeError: ghi]
expected: FAIL
[SyntaxError: SyntaxError: ghi]
expected: FAIL
[ReferenceError: ReferenceError]
expected: FAIL
[RangeError: RangeError]
expected: FAIL
[EvalError: EvalError]
expected: FAIL
[EvalError: EvalError: ghi]
expected: FAIL
[ReferenceError: ReferenceError: ghi]
expected: FAIL
expected: OK
[structured-clone.any.worker.html?81-100]
[SyntaxError: SyntaxError]
expected: FAIL
[Error: Error: abc]
expected: FAIL
[Error: Error]
expected: FAIL
[RangeError: RangeError: ghi]
expected: FAIL
[SyntaxError: SyntaxError: ghi]
expected: FAIL
[ReferenceError: ReferenceError]
expected: FAIL
[RangeError: RangeError]
expected: FAIL
[EvalError: EvalError]
expected: FAIL
[EvalError: EvalError: ghi]
expected: FAIL
[ReferenceError: ReferenceError: ghi]
expected: FAIL
expected: OK
[structured-clone.any.html?101-last]
expected:
@ -134,18 +76,6 @@
if (os == "mac") and debug: TIMEOUT
[TIMEOUT, PASS]
[TypeError: TypeError]
expected: FAIL
[TypeError: TypeError: ghi]
expected: FAIL
[URIError: URIError]
expected: FAIL
[URIError: URIError: ghi]
expected: FAIL
[structured-clone.any.worker.html?101-last]
expected:
@ -213,19 +143,6 @@
if (os == "mac") and debug: [PASS, TIMEOUT]
[TIMEOUT, PASS]
[TypeError: TypeError]
expected: FAIL
[TypeError: TypeError: ghi]
expected: FAIL
[URIError: URIError]
expected: FAIL
[URIError: URIError: ghi]
expected: FAIL
[structured-clone.any.html?1-20]
[structured-clone.any.worker.html?1-20]

Просмотреть файл

@ -4,6 +4,3 @@
[pushState must not be allowed to create cross-origin URLs (data:URI)]
expected: FAIL
[pushState must be able to use an error object as data]
expected: FAIL

Просмотреть файл

@ -4,6 +4,3 @@
[replaceState must not be allowed to create cross-origin URLs (data:URI)]
expected: FAIL
[replaceState must be able to use an error object as data]
expected: FAIL

Просмотреть файл

@ -1,8 +0,0 @@
[structured-cloning-error-extra.html]
expected: ERROR
[Throwing name getter fails serialization]
expected: FAIL
[Errors sent across realms should preserve their type]
expected: TIMEOUT

Просмотреть файл

@ -1,37 +0,0 @@
[structuredclone_0.html]
[ReferenceError objects can be cloned]
expected: FAIL
[Error.message: getter is ignored when cloning]
expected: FAIL
[EvalError objects can be cloned]
expected: FAIL
[URIError objects can be cloned]
expected: FAIL
[Cloning a modified Error]
expected: FAIL
[RangeError objects can be cloned]
expected: FAIL
[Empty Error objects can be cloned]
expected: FAIL
[TypeError objects can be cloned]
expected: FAIL
[Error objects can be cloned]
expected: FAIL
[Error.message: undefined property is stringified]
expected: FAIL
[SyntaxError objects can be cloned]
expected: FAIL
[URIError objects from other realms are treated as URIError]
expected: FAIL

Просмотреть файл

@ -1,4 +1,3 @@
[MediaStreamTrackGenerator-in-service-worker.https.html]
expected: TIMEOUT
[A service worker is able to initialize a MediaStreamTrackGenerator without crashing]
expected: TIMEOUT
expected: FAIL

Просмотреть файл

@ -1,4 +1,3 @@
[MediaStreamTrackGenerator-in-shared-worker.https.html]
expected: TIMEOUT
[A shared worker is able to initialize a MediaStreamTrackGenerator without crashing]
expected: TIMEOUT
expected: FAIL

Просмотреть файл

@ -1,10 +1,9 @@
[MediaStreamTrackGenerator-in-worker.https.html]
expected: ERROR
[A worker is able to initialize a MediaStreamTrackGenerator without crashing]
expected: TIMEOUT
expected: FAIL
[A worker is able to enable a MediaStreamTrackGenerator without crashing]
expected: NOTRUN
expected: FAIL
[A worker is able to disable a MediaStreamTrackGenerator without crashing]
expected: NOTRUN
expected: FAIL

Просмотреть файл

@ -1,33 +0,0 @@
[reason.html]
[a TypeError message should not be preserved if it is inherited]
expected: FAIL
[URIError should be preserved]
expected: FAIL
[a TypeError message should be converted to a string]
expected: FAIL
[TypeError should be preserved]
expected: FAIL
[RangeError should be preserved]
expected: FAIL
[other attributes of a TypeError should not be preserved]
expected: FAIL
[ReferenceError should be preserved]
expected: FAIL
[SyntaxError should be preserved]
expected: FAIL
[EvalError should be preserved]
expected: FAIL
[the type and message of a TypeError should be preserved]
expected: FAIL
[a TypeError message should not be preserved if it is a getter]
expected: FAIL

Просмотреть файл

@ -591,7 +591,7 @@ add_task(async function test_storage_local_data_migration_failure() {
// (because it can't be cloned and it is going to raise a DataCloneError), which
// will trigger a data migration failure that we expect to increment the related
// telemetry histogram.
jsonFile.data.set("fake_invalid_key", new Error());
jsonFile.data.set("fake_invalid_key", function() {});
async function background() {
await browser.storage.local.set({