diff --git a/src/objects/value-serializer.cc b/src/objects/value-serializer.cc index f08abc8bf9..469daf8b79 100644 --- a/src/objects/value-serializer.cc +++ b/src/objects/value-serializer.cc @@ -22,6 +22,7 @@ #include "src/objects/objects-inl.h" #include "src/objects/oddball-inl.h" #include "src/objects/ordered-hash-table-inl.h" +#include "src/objects/property-descriptor.h" #include "src/objects/smi.h" #include "src/objects/transitions-inl.h" #include "src/snapshot/code-serializer.h" @@ -161,6 +162,9 @@ enum class SerializationTag : uint8_t { // A transferred WebAssembly.Memory object. maximumPages:int32_t, then by // SharedArrayBuffer tag and its data. kWasmMemoryTransfer = 'm', + // A list of (subtag: ErrorTag, [subtag dependent data]). See ErrorTag for + // details. + kError = 'e', }; namespace { @@ -184,6 +188,28 @@ enum class WasmEncodingTag : uint8_t { kRawBytes = 'y', }; +// Sub-tags only meaningful for error serialization. +enum class ErrorTag : uint8_t { + // The error is a EvalError. No accompanying data. + kEvalErrorPrototype = 'E', + // The error is a RangeError. No accompanying data. + kRangeErrorPrototype = 'R', + // The error is a ReferenceError. No accompanying data. + kReferenceErrorPrototype = 'F', + // The error is a SyntaxError. No accompanying data. + kSyntaxErrorPrototype = 'S', + // The error is a TypeError. No accompanying data. + kTypeErrorPrototype = 'T', + // The error is a URIError. No accompanying data. + kUriErrorPrototype = 'U', + // Followed by message: string. + kMessage = 'm', + // Followed by stack: string. + kStack = 's', + // The end of this error information. + kEnd = '.', +}; + } // namespace ValueSerializer::ValueSerializer(Isolate* isolate, @@ -520,6 +546,8 @@ Maybe ValueSerializer::WriteJSReceiver(Handle receiver) { case JS_TYPED_ARRAY_TYPE: case JS_DATA_VIEW_TYPE: return WriteJSArrayBufferView(JSArrayBufferView::cast(*receiver)); + case JS_ERROR_TYPE: + return WriteJSError(Handle::cast(receiver)); case WASM_MODULE_TYPE: { auto enabled_features = wasm::WasmFeaturesFromIsolate(isolate_); if (!FLAG_wasm_disable_structured_cloning || enabled_features.threads) { @@ -876,6 +904,60 @@ Maybe ValueSerializer::WriteJSArrayBufferView(JSArrayBufferView view) { return ThrowIfOutOfMemory(); } +Maybe ValueSerializer::WriteJSError(Handle error) { + Handle stack; + PropertyDescriptor message_desc; + Maybe message_found = JSReceiver::GetOwnPropertyDescriptor( + isolate_, error, isolate_->factory()->message_string(), &message_desc); + MAYBE_RETURN(message_found, Nothing()); + + WriteTag(SerializationTag::kError); + + Handle prototype; + if (!JSObject::GetPrototype(isolate_, error).ToHandle(&prototype)) { + return Nothing(); + } + + if (*prototype == isolate_->eval_error_function()->prototype()) { + WriteVarint(static_cast(ErrorTag::kEvalErrorPrototype)); + } else if (*prototype == isolate_->range_error_function()->prototype()) { + WriteVarint(static_cast(ErrorTag::kRangeErrorPrototype)); + } else if (*prototype == isolate_->reference_error_function()->prototype()) { + WriteVarint(static_cast(ErrorTag::kReferenceErrorPrototype)); + } else if (*prototype == isolate_->syntax_error_function()->prototype()) { + WriteVarint(static_cast(ErrorTag::kSyntaxErrorPrototype)); + } else if (*prototype == isolate_->type_error_function()->prototype()) { + WriteVarint(static_cast(ErrorTag::kTypeErrorPrototype)); + } else if (*prototype == isolate_->uri_error_function()->prototype()) { + WriteVarint(static_cast(ErrorTag::kUriErrorPrototype)); + } else { + // The default prototype in the deserialization side is Error.prototype, so + // we don't have to do anything here. + } + + if (message_found.FromJust() && + PropertyDescriptor::IsDataDescriptor(&message_desc)) { + Handle message; + if (!Object::ToString(isolate_, message_desc.value()).ToHandle(&message)) { + return Nothing(); + } + WriteVarint(static_cast(ErrorTag::kMessage)); + WriteString(message); + } + + if (!Object::GetProperty(isolate_, error, isolate_->factory()->stack_string()) + .ToHandle(&stack)) { + return Nothing(); + } + if (stack->IsString()) { + WriteVarint(static_cast(ErrorTag::kStack)); + WriteString(Handle::cast(stack)); + } + + WriteVarint(static_cast(ErrorTag::kEnd)); + return ThrowIfOutOfMemory(); +} + Maybe ValueSerializer::WriteWasmModule(Handle object) { if (delegate_ != nullptr) { // TODO(titzer): introduce a Utils::ToLocal for WasmModuleObject. @@ -1258,6 +1340,8 @@ MaybeHandle ValueDeserializer::ReadObjectInternal() { const bool is_shared = true; return ReadJSArrayBuffer(is_shared); } + case SerializationTag::kError: + return ReadJSError(); case SerializationTag::kWasmModule: return ReadWasmModule(); case SerializationTag::kWasmModuleTransfer: @@ -1773,6 +1857,78 @@ MaybeHandle ValueDeserializer::ReadJSArrayBufferView( return typed_array; } +MaybeHandle ValueDeserializer::ReadJSError() { + Handle message = isolate_->factory()->undefined_value(); + Handle stack = isolate_->factory()->undefined_value(); + Handle no_caller; + auto constructor = isolate_->error_function(); + bool done = false; + + while (!done) { + uint8_t tag; + if (!ReadVarint().To(&tag)) { + return MaybeHandle(); + } + switch (static_cast(tag)) { + case ErrorTag::kEvalErrorPrototype: + constructor = isolate_->eval_error_function(); + break; + case ErrorTag::kRangeErrorPrototype: + constructor = isolate_->range_error_function(); + break; + case ErrorTag::kReferenceErrorPrototype: + constructor = isolate_->reference_error_function(); + break; + case ErrorTag::kSyntaxErrorPrototype: + constructor = isolate_->syntax_error_function(); + break; + case ErrorTag::kTypeErrorPrototype: + constructor = isolate_->type_error_function(); + break; + case ErrorTag::kUriErrorPrototype: + constructor = isolate_->uri_error_function(); + break; + case ErrorTag::kMessage: { + Handle message_string; + if (!ReadString().ToHandle(&message_string)) { + return MaybeHandle(); + } + message = message_string; + break; + } + case ErrorTag::kStack: { + Handle stack_string; + if (!ReadString().ToHandle(&stack_string)) { + return MaybeHandle(); + } + stack = stack_string; + break; + } + case ErrorTag::kEnd: + done = true; + break; + default: + return MaybeHandle(); + } + } + + Handle error; + if (!ErrorUtils::Construct(isolate_, constructor, constructor, message, + SKIP_NONE, no_caller, + ErrorUtils::StackTraceCollection::kNone) + .ToHandle(&error)) { + return MaybeHandle(); + } + + if (Object::SetProperty( + isolate_, error, isolate_->factory()->stack_trace_symbol(), stack, + StoreOrigin::kMaybeKeyed, Just(ShouldThrow::kThrowOnError)) + .is_null()) { + return MaybeHandle(); + } + return error; +} + MaybeHandle ValueDeserializer::ReadWasmModuleTransfer() { auto enabled_features = wasm::WasmFeaturesFromIsolate(isolate_); if ((FLAG_wasm_disable_structured_cloning && !enabled_features.threads) || diff --git a/src/objects/value-serializer.h b/src/objects/value-serializer.h index 536b84d6bb..9e381d7e76 100644 --- a/src/objects/value-serializer.h +++ b/src/objects/value-serializer.h @@ -128,6 +128,7 @@ class ValueSerializer { Maybe WriteJSArrayBuffer(Handle array_buffer) V8_WARN_UNUSED_RESULT; Maybe WriteJSArrayBufferView(JSArrayBufferView array_buffer); + Maybe WriteJSError(Handle error) V8_WARN_UNUSED_RESULT; Maybe WriteWasmModule(Handle object) V8_WARN_UNUSED_RESULT; Maybe WriteWasmMemory(Handle object) @@ -276,6 +277,7 @@ class ValueDeserializer { V8_WARN_UNUSED_RESULT; MaybeHandle ReadJSArrayBufferView( Handle buffer) V8_WARN_UNUSED_RESULT; + MaybeHandle ReadJSError() V8_WARN_UNUSED_RESULT; MaybeHandle ReadWasmModule() V8_WARN_UNUSED_RESULT; MaybeHandle ReadWasmModuleTransfer() V8_WARN_UNUSED_RESULT; MaybeHandle ReadWasmMemory() V8_WARN_UNUSED_RESULT; diff --git a/test/unittests/objects/value-serializer-unittest.cc b/test/unittests/objects/value-serializer-unittest.cc index 38aae33809..a3a6fb22a7 100644 --- a/test/unittests/objects/value-serializer-unittest.cc +++ b/test/unittests/objects/value-serializer-unittest.cc @@ -2885,5 +2885,68 @@ TEST_F(ValueSerializerTestWithLimitedMemory, FailIfNoMemoryInWriteHostObject) { EXPECT_TRUE(EvaluateScriptForInput("gotA")->IsFalse()); } +// We only have basic tests and tests for .stack here, because we have more +// comprehensive tests as web platform tests. +TEST_F(ValueSerializerTest, RoundTripError) { + Local value = RoundTripTest("Error('hello')"); + ASSERT_TRUE(value->IsObject()); + Local error = value.As(); + + Local name; + Local message; + + { + Context::Scope scope(deserialization_context()); + EXPECT_EQ(error->GetPrototype(), Exception::Error(String::Empty(isolate())) + .As() + ->GetPrototype()); + } + ASSERT_TRUE(error->Get(deserialization_context(), StringFromUtf8("name")) + .ToLocal(&name)); + ASSERT_TRUE(name->IsString()); + EXPECT_EQ(Utf8Value(name), "Error"); + + ASSERT_TRUE(error->Get(deserialization_context(), StringFromUtf8("message")) + .ToLocal(&message)); + ASSERT_TRUE(message->IsString()); + EXPECT_EQ(Utf8Value(message), "hello"); +} + +TEST_F(ValueSerializerTest, DefaultErrorStack) { + Local value = + RoundTripTest("function hkalkcow() { return Error(); } hkalkcow();"); + ASSERT_TRUE(value->IsObject()); + Local error = value.As(); + + Local stack; + ASSERT_TRUE(error->Get(deserialization_context(), StringFromUtf8("stack")) + .ToLocal(&stack)); + ASSERT_TRUE(stack->IsString()); + EXPECT_NE(Utf8Value(stack).find("hkalkcow"), std::string::npos); +} + +TEST_F(ValueSerializerTest, ModifiedErrorStack) { + Local value = RoundTripTest("let e = Error(); e.stack = 'hello'; e"); + ASSERT_TRUE(value->IsObject()); + Local error = value.As(); + + Local stack; + ASSERT_TRUE(error->Get(deserialization_context(), StringFromUtf8("stack")) + .ToLocal(&stack)); + ASSERT_TRUE(stack->IsString()); + EXPECT_EQ(Utf8Value(stack), "hello"); +} + +TEST_F(ValueSerializerTest, NonStringErrorStack) { + Local value = RoundTripTest("let e = Error(); e.stack = 17; e"); + ASSERT_TRUE(value->IsObject()); + Local error = value.As(); + + Local stack; + ASSERT_TRUE(error->Get(deserialization_context(), StringFromUtf8("stack")) + .ToLocal(&stack)); + EXPECT_TRUE(stack->IsUndefined()); +} + } // namespace } // namespace v8