Make Error.prototype.toString spec compliant; and fix various side-effect-free error printing methods

R=yangguo@chromium.org
LOG=n

Committed: https://crrev.com/5dffa35350d0f57402806e6bd87a914e1d5933e4
Cr-Commit-Position: refs/heads/master@{#32695}

Review URL: https://codereview.chromium.org/1507273002

Cr-Commit-Position: refs/heads/master@{#32720}
This commit is contained in:
verwaest 2015-12-09 09:00:47 -08:00 committed by Commit bot
parent 65eef38349
commit 454c1faeef
22 changed files with 129 additions and 383 deletions

View File

@ -2898,12 +2898,16 @@ Local<String> Value::ToString(Isolate* isolate) const {
MaybeLocal<String> Value::ToDetailString(Local<Context> context) const {
auto obj = Utils::OpenHandle(this);
i::Handle<i::Object> obj = Utils::OpenHandle(this);
if (obj->IsString()) return ToApiHandle<String>(obj);
PREPARE_FOR_EXECUTION(context, "ToDetailString", String);
Local<String> result;
has_pending_exception =
!ToLocal<String>(i::Execution::ToDetailString(isolate, obj), &result);
i::Handle<i::Object> args[] = {obj};
has_pending_exception = !ToLocal<String>(
i::Execution::TryCall(isolate, isolate->no_side_effects_to_string_fun(),
isolate->factory()->undefined_value(),
arraysize(args), args),
&result);
RETURN_ON_FAILED_EXECUTION(String);
RETURN_ESCAPED(result);
}

View File

@ -1154,7 +1154,13 @@ bool Scope::ResolveVariable(ParseInfo* info, VariableProxy* proxy,
// - Variables must not be allocated to the global scope.
CHECK_NOT_NULL(outer_scope());
// - Variables must be bound locally or unallocated.
CHECK_EQ(BOUND, binding_kind);
if (BOUND != binding_kind) {
// The following variable name may be minified. If so, disable
// minification in js2c.py for better output.
Handle<String> name = proxy->raw_name()->string();
V8_Fatal(__FILE__, __LINE__, "Unbound variable: '%s' in native script.",
name->ToCString().get());
}
VariableLocation location = var->location();
CHECK(location == VariableLocation::LOCAL ||
location == VariableLocation::CONTEXT ||

View File

@ -126,8 +126,8 @@ enum BindingFlags {
V(NATIVE_OBJECT_NOTIFIER_PERFORM_CHANGE, JSFunction, \
native_object_notifier_perform_change) \
V(NATIVE_OBJECT_OBSERVE_INDEX, JSFunction, native_object_observe) \
V(NO_SIDE_EFFECT_TO_STRING_FUN_INDEX, JSFunction, \
no_side_effect_to_string_fun) \
V(NO_SIDE_EFFECTS_TO_STRING_FUN_INDEX, JSFunction, \
no_side_effects_to_string_fun) \
V(OBJECT_VALUE_OF, JSFunction, object_value_of) \
V(OBJECT_TO_STRING, JSFunction, object_to_string) \
V(OBSERVERS_BEGIN_SPLICE_INDEX, JSFunction, observers_begin_perform_splice) \
@ -150,7 +150,6 @@ enum BindingFlags {
V(SET_HAS_METHOD_INDEX, JSFunction, set_has) \
V(STACK_OVERFLOW_BOILERPLATE_INDEX, JSObject, stack_overflow_boilerplate) \
V(SYNTAX_ERROR_FUNCTION_INDEX, JSFunction, syntax_error_function) \
V(TO_DETAIL_STRING_FUN_INDEX, JSFunction, to_detail_string_fun) \
V(TYPE_ERROR_FUNCTION_INDEX, JSFunction, type_error_function) \
V(URI_ERROR_FUNCTION_INDEX, JSFunction, uri_error_function) \
NATIVE_CONTEXT_JS_BUILTINS(V)

View File

@ -433,12 +433,6 @@ void StackGuard::InitThread(const ExecutionAccess& lock) {
} while (false)
MaybeHandle<Object> Execution::ToDetailString(
Isolate* isolate, Handle<Object> obj) {
RETURN_NATIVE_CALL(to_detail_string, { obj });
}
MaybeHandle<Object> Execution::NewDate(Isolate* isolate, double time) {
Handle<Object> time_obj = isolate->factory()->NewNumber(time);
RETURN_NATIVE_CALL(create_date, { time_obj });

View File

@ -52,10 +52,6 @@ class Execution final : public AllStatic {
Handle<Object> argv[],
MaybeHandle<Object>* exception_out = NULL);
// ECMA-262 9.8
MUST_USE_RESULT static MaybeHandle<Object> ToDetailString(
Isolate* isolate, Handle<Object> obj);
// ECMA-262 9.9
MUST_USE_RESULT static MaybeHandle<Object> ToObject(
Isolate* isolate, Handle<Object> obj);

View File

@ -1348,22 +1348,11 @@ bool Isolate::ComputeLocationFromStackTrace(MessageLocation* target,
}
// Use stack_trace_symbol as proxy for [[ErrorData]].
bool Isolate::IsErrorObject(Handle<Object> object) {
Handle<Name> symbol = factory()->stack_trace_symbol();
if (!object->IsJSObject()) return false;
Maybe<bool> has_stack_trace =
JSReceiver::HasOwnProperty(Handle<JSReceiver>::cast(object), symbol);
DCHECK(!has_stack_trace.IsNothing());
return has_stack_trace.FromJust();
}
Handle<JSMessageObject> Isolate::CreateMessage(Handle<Object> exception,
MessageLocation* location) {
Handle<JSArray> stack_trace_object;
if (capture_stack_trace_for_uncaught_exceptions_) {
if (IsErrorObject(exception)) {
if (Object::IsErrorObject(this, exception)) {
// We fetch the stack trace that corresponds to this error object.
// If the lookup fails, the exception is probably not a valid Error
// object. In that case, we fall through and capture the stack trace

View File

@ -947,10 +947,6 @@ class Isolate {
date_cache_ = date_cache;
}
ErrorToStringHelper* error_tostring_helper() {
return &error_tostring_helper_;
}
Map* get_initial_js_array_map(ElementsKind kind,
Strength strength = Strength::WEAK);
@ -1201,10 +1197,6 @@ class Isolate {
// the frame.
void RemoveMaterializedObjectsOnUnwind(StackFrame* frame);
// Traverse prototype chain to find out whether the object is derived from
// the Error object.
bool IsErrorObject(Handle<Object> obj);
base::Atomic32 id_;
EntryStackItem* entry_stack_;
int stack_trace_nesting_level_;
@ -1249,7 +1241,6 @@ class Isolate {
regexp_macro_assembler_canonicalize_;
RegExpStack* regexp_stack_;
DateCache* date_cache_;
ErrorToStringHelper error_tostring_helper_;
unibrow::Mapping<unibrow::Ecma262Canonicalize> interp_canonicalize_mapping_;
CallInterfaceDescriptorData* call_descriptor_data_;
base::RandomNumberGenerator* random_number_generator_;

View File

@ -23,6 +23,7 @@ var callSitePositionSymbol =
utils.ImportNow("call_site_position_symbol");
var callSiteStrictSymbol =
utils.ImportNow("call_site_strict_symbol");
var FLAG_harmony_tostring;
var Float32x4ToString;
var formattedStackTraceSymbol =
utils.ImportNow("formatted_stack_trace_symbol");
@ -41,6 +42,7 @@ var StringCharAt;
var StringIndexOf;
var StringSubstring;
var SymbolToString;
var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol");
var Uint16x8ToString;
var Uint32x4ToString;
var Uint8x16ToString;
@ -66,6 +68,10 @@ utils.Import(function(from) {
Uint8x16ToString = from.Uint8x16ToString;
});
utils.ImportFromExperimental(function(from) {
FLAG_harmony_tostring = from.FLAG_harmony_tostring;
});
// -------------------------------------------------------------------
var GlobalError;
@ -80,11 +86,35 @@ var GlobalEvalError;
function NoSideEffectsObjectToString() {
if (IS_UNDEFINED(this)) return "[object Undefined]";
if (IS_NULL(this)) return "[object Null]";
return "[object " + %_ClassOf(TO_OBJECT(this)) + "]";
var O = TO_OBJECT(this);
var builtinTag = %_ClassOf(O);
var tag;
if (FLAG_harmony_tostring) {
tag = %GetDataProperty(O, toStringTagSymbol);
if (!IS_STRING(tag)) {
tag = builtinTag;
}
} else {
tag = builtinTag;
}
return `[object ${tag}]`;
}
function IsErrorObject(obj) {
return HAS_PRIVATE(obj, stackTraceSymbol);
}
function NoSideEffectToString(obj) {
function NoSideEffectsErrorToString() {
var name = %GetDataProperty(this, "name");
var message = %GetDataProperty(this, "message");
name = IS_UNDEFINED(name) ? "Error" : NoSideEffectsToString(name);
message = IS_UNDEFINED(message) ? "" : NoSideEffectsToString(message);
if (name == "") return message;
if (message == "") return name;
return `${name}: ${message}`;
}
function NoSideEffectsToString(obj) {
if (IS_STRING(obj)) return obj;
if (IS_NUMBER(obj)) return %_NumberToString(obj);
if (IS_BOOLEAN(obj)) return obj ? 'true' : 'false';
@ -113,71 +143,28 @@ function NoSideEffectToString(obj) {
case 'bool8x16': return %_Call(Bool8x16ToString, obj);
}
}
if (IS_OBJECT(obj)
&& %GetDataProperty(obj, "toString") === ObjectToString) {
if (IS_SPEC_OBJECT(obj)) {
// When internally formatting error objects, use a side-effects-free version
// of Error.prototype.toString independent of the actually installed
// toString method.
if (IsErrorObject(obj) ||
%GetDataProperty(obj, "toString") === ErrorToString) {
return %_Call(NoSideEffectsErrorToString, obj);
}
if (%GetDataProperty(obj, "toString") === ObjectToString) {
var constructor = %GetDataProperty(obj, "constructor");
if (typeof constructor == "function") {
var constructorName = constructor.name;
if (IS_STRING(constructorName) && constructorName !== "") {
return "#<" + constructorName + ">";
if (IS_FUNCTION(constructor)) {
var constructor_name = %FunctionGetName(constructor);
if (constructor_name != "") return `#<${constructor_name}>`;
}
}
}
if (CanBeSafelyTreatedAsAnErrorObject(obj)) {
return %_Call(ErrorToString, obj);
}
return %_Call(NoSideEffectsObjectToString, obj);
}
// To determine whether we can safely stringify an object using ErrorToString
// without the risk of side-effects, we need to check whether the object is
// either an instance of a native error type (via '%_ClassOf'), or has Error
// in its prototype chain and hasn't overwritten 'toString' with something
// strange and unusual.
function CanBeSafelyTreatedAsAnErrorObject(obj) {
switch (%_ClassOf(obj)) {
case 'Error':
case 'EvalError':
case 'RangeError':
case 'ReferenceError':
case 'SyntaxError':
case 'TypeError':
case 'URIError':
return true;
}
var objToString = %GetDataProperty(obj, "toString");
return obj instanceof GlobalError && objToString === ErrorToString;
}
// When formatting internally created error messages, do not
// invoke overwritten error toString methods but explicitly use
// the error to string method. This is to avoid leaking error
// objects between script tags in a browser setting.
function ToStringCheckErrorObject(obj) {
if (CanBeSafelyTreatedAsAnErrorObject(obj)) {
return %_Call(ErrorToString, obj);
} else {
return TO_STRING(obj);
}
}
function ToDetailString(obj) {
if (obj != null && IS_OBJECT(obj) && obj.toString === ObjectToString) {
var constructor = obj.constructor;
if (typeof constructor == "function") {
var constructorName = constructor.name;
if (IS_STRING(constructorName) && constructorName !== "") {
return "#<" + constructorName + ">";
}
}
}
return ToStringCheckErrorObject(obj);
}
function MakeGenericError(constructor, type, arg0, arg1, arg2) {
var error = new constructor(FormatMessage(type, arg0, arg1, arg2));
@ -200,9 +187,9 @@ function MakeGenericError(constructor, type, arg0, arg1, arg2) {
// Helper functions; called from the runtime system.
function FormatMessage(type, arg0, arg1, arg2) {
var arg0 = NoSideEffectToString(arg0);
var arg1 = NoSideEffectToString(arg1);
var arg2 = NoSideEffectToString(arg2);
var arg0 = NoSideEffectsToString(arg0);
var arg1 = NoSideEffectsToString(arg1);
var arg2 = NoSideEffectsToString(arg2);
try {
return %FormatMessageString(type, arg0, arg1, arg2);
} catch (e) {
@ -849,20 +836,13 @@ function FormatStackTrace(obj, raw_stack) {
function GetTypeName(receiver, requireConstructor) {
if (IS_NULL_OR_UNDEFINED(receiver)) return null;
if (%_IsJSProxy(receiver)) {
return "Proxy";
};
var constructor = receiver.constructor;
if (!constructor) {
return requireConstructor ? null :
%_Call(NoSideEffectsObjectToString, receiver);
if (%_IsJSProxy(receiver)) return "Proxy";
var constructor = %GetDataProperty(TO_OBJECT(receiver), "constructor");
if (!IS_FUNCTION(constructor)) {
return requireConstructor ? null : %_Call(NoSideEffectsToString, receiver);
}
var constructorName = constructor.name;
if (!constructorName) {
return requireConstructor ? null :
%_Call(NoSideEffectsObjectToString, receiver);
}
return constructorName;
return %FunctionGetName(constructor);
}
@ -896,7 +876,7 @@ var StackTraceGetter = function() {
// If the receiver equals the holder, set the formatted stack trace that the
// getter returns.
var StackTraceSetter = function(v) {
if (HAS_PRIVATE(this, stackTraceSymbol)) {
if (IsErrorObject(this)) {
SET_PRIVATE(this, stackTraceSymbol, UNDEFINED);
SET_PRIVATE(this, formattedStackTraceSymbol, v);
}
@ -956,7 +936,15 @@ function ErrorToString() {
throw MakeTypeError(kCalledOnNonObject, "Error.prototype.toString");
}
return %ErrorToStringRT(this);
var name = this.name;
name = IS_UNDEFINED(name) ? "Error" : TO_STRING(name);
var message = this.message;
message = IS_UNDEFINED(message) ? "" : TO_STRING(message);
if (name == "") return message;
if (message == "") return name;
return `${name}: ${message}`
}
function MakeError(type, arg0, arg1, arg2) {
@ -1004,9 +992,8 @@ GlobalError.captureStackTrace = captureStackTrace;
"message_get_column_number", GetColumnNumber,
"message_get_line_number", GetLineNumber,
"message_get_source_line", GetSourceLine,
"no_side_effect_to_string_fun", NoSideEffectToString,
"no_side_effects_to_string_fun", NoSideEffectsToString,
"stack_overflow_boilerplate", StackOverflowBoilerplate,
"to_detail_string_fun", ToDetailString,
]);
utils.Export(function(to) {

View File

@ -82,11 +82,23 @@ void MessageHandler::ReportMessage(Isolate* isolate, MessageLocation* loc,
if (message->argument()->IsJSObject()) {
HandleScope scope(isolate);
Handle<Object> argument(message->argument(), isolate);
Handle<Object> args[] = {argument};
MaybeHandle<Object> maybe_stringified = Execution::TryCall(
isolate, isolate->to_detail_string_fun(),
isolate->factory()->undefined_value(), arraysize(args), args);
MaybeHandle<Object> maybe_stringified;
Handle<Object> stringified;
// Make sure we don't leak uncaught internally generated Error objects.
if (Object::IsErrorObject(isolate, argument)) {
Handle<Object> args[] = {argument};
maybe_stringified = Execution::TryCall(
isolate, isolate->no_side_effects_to_string_fun(),
isolate->factory()->undefined_value(), arraysize(args), args);
} else {
v8::TryCatch catcher(reinterpret_cast<v8::Isolate*>(isolate));
catcher.SetVerbose(false);
catcher.SetCaptureMessage(false);
maybe_stringified = Object::ToString(isolate, argument);
}
if (!maybe_stringified.ToHandle(&stringified)) {
stringified = isolate->factory()->NewStringFromAsciiChecked("exception");
}
@ -317,7 +329,7 @@ Handle<String> MessageTemplate::FormatMessage(Isolate* isolate,
if (arg->IsString()) {
result_string = Handle<String>::cast(arg);
} else {
Handle<JSFunction> fun = isolate->no_side_effect_to_string_fun();
Handle<JSFunction> fun = isolate->no_side_effects_to_string_fun();
MaybeHandle<Object> maybe_result =
Execution::TryCall(isolate, fun, factory->undefined_value(), 1, &arg);
@ -391,94 +403,5 @@ MaybeHandle<String> MessageTemplate::FormatMessage(int template_index,
}
MaybeHandle<String> ErrorToStringHelper::Stringify(Isolate* isolate,
Handle<JSObject> error) {
VisitedScope scope(this, error);
if (scope.has_visited()) return isolate->factory()->empty_string();
Handle<String> name;
Handle<String> message;
Handle<Name> internal_key = isolate->factory()->internal_error_symbol();
Handle<String> message_string =
isolate->factory()->NewStringFromStaticChars("message");
Handle<String> name_string = isolate->factory()->name_string();
LookupIterator internal_error_lookup(
error, internal_key, LookupIterator::PROTOTYPE_CHAIN_SKIP_INTERCEPTOR);
// Find out whether an internally created error object is on the prototype
// chain. If the name property is found on a holder prior to the internally
// created error object, use that name property. Otherwise just use the
// constructor name to avoid triggering possible side effects.
// Similar for the message property. If the message property shadows the
// internally created error object, use that message property. Otherwise
// use empty string as message.
LookupIterator name_lookup(error, name_string,
LookupIterator::PROTOTYPE_CHAIN_SKIP_INTERCEPTOR);
if (internal_error_lookup.IsFound() &&
!ShadowsInternalError(isolate, &name_lookup, &internal_error_lookup)) {
Handle<JSObject> holder = internal_error_lookup.GetHolder<JSObject>();
name = JSReceiver::GetConstructorName(holder);
} else {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, name,
GetStringifiedProperty(isolate, &name_lookup,
isolate->factory()->Error_string()),
String);
}
LookupIterator message_lookup(
error, message_string, LookupIterator::PROTOTYPE_CHAIN_SKIP_INTERCEPTOR);
if (internal_error_lookup.IsFound() &&
!ShadowsInternalError(isolate, &message_lookup, &internal_error_lookup)) {
message = isolate->factory()->empty_string();
} else {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, message,
GetStringifiedProperty(isolate, &message_lookup,
isolate->factory()->empty_string()),
String);
}
if (name->length() == 0) return message;
if (message->length() == 0) return name;
IncrementalStringBuilder builder(isolate);
builder.AppendString(name);
builder.AppendCString(": ");
builder.AppendString(message);
return builder.Finish();
}
bool ErrorToStringHelper::ShadowsInternalError(
Isolate* isolate, LookupIterator* property_lookup,
LookupIterator* internal_error_lookup) {
if (!property_lookup->IsFound()) return false;
Handle<JSObject> holder = property_lookup->GetHolder<JSObject>();
// It's fine if the property is defined on the error itself.
if (holder.is_identical_to(property_lookup->GetReceiver())) return true;
PrototypeIterator it(isolate, holder, PrototypeIterator::START_AT_RECEIVER);
while (true) {
if (it.IsAtEnd()) return false;
if (it.IsAtEnd(internal_error_lookup->GetHolder<JSObject>())) return true;
it.AdvanceIgnoringProxies();
}
}
MaybeHandle<String> ErrorToStringHelper::GetStringifiedProperty(
Isolate* isolate, LookupIterator* property_lookup,
Handle<String> default_value) {
if (!property_lookup->IsFound()) return default_value;
Handle<Object> obj;
ASSIGN_RETURN_ON_EXCEPTION(isolate, obj, Object::GetProperty(property_lookup),
String);
if (obj->IsUndefined()) return default_value;
if (!obj->IsString()) {
ASSIGN_RETURN_ON_EXCEPTION(isolate, obj, Object::ToString(isolate, obj),
String);
}
return Handle<String>::cast(obj);
}
} // namespace internal
} // namespace v8

View File

@ -486,44 +486,6 @@ class MessageHandler {
};
class ErrorToStringHelper {
public:
ErrorToStringHelper() : visited_(0) {}
MUST_USE_RESULT MaybeHandle<String> Stringify(Isolate* isolate,
Handle<JSObject> error);
private:
class VisitedScope {
public:
VisitedScope(ErrorToStringHelper* helper, Handle<JSObject> error)
: helper_(helper), has_visited_(false) {
for (const Handle<JSObject>& visited : helper->visited_) {
if (visited.is_identical_to(error)) {
has_visited_ = true;
break;
}
}
helper->visited_.Add(error);
}
~VisitedScope() { helper_->visited_.RemoveLast(); }
bool has_visited() { return has_visited_; }
private:
ErrorToStringHelper* helper_;
bool has_visited_;
};
static bool ShadowsInternalError(Isolate* isolate,
LookupIterator* property_lookup,
LookupIterator* internal_error_lookup);
static MUST_USE_RESULT MaybeHandle<String> GetStringifiedProperty(
Isolate* isolate, LookupIterator* property_lookup,
Handle<String> default_value);
List<Handle<JSObject> > visited_;
};
} // namespace internal
} // namespace v8

View File

@ -1162,6 +1162,18 @@ Maybe<bool> Object::SetPropertyWithDefinedSetter(Handle<Object> receiver,
}
// static
bool Object::IsErrorObject(Isolate* isolate, Handle<Object> object) {
if (!object->IsJSObject()) return false;
// Use stack_trace_symbol as proxy for [[ErrorData]].
Handle<Name> symbol = isolate->factory()->stack_trace_symbol();
Maybe<bool> has_stack_trace =
JSReceiver::HasOwnProperty(Handle<JSReceiver>::cast(object), symbol);
DCHECK(!has_stack_trace.IsNothing());
return has_stack_trace.FromJust();
}
// static
bool JSObject::AllCanRead(LookupIterator* it) {
// Skip current iteration, it's in state ACCESS_CHECK or INTERCEPTOR, both of

View File

@ -1171,6 +1171,9 @@ class Object {
MUST_USE_RESULT static MaybeHandle<Object> GetMethod(
Handle<JSReceiver> receiver, Handle<Name> name);
// Check whether |object| is an instance of Error or NativeError.
static bool IsErrorObject(Isolate* isolate, Handle<Object> object);
// ES6 section 12.5.6 The typeof Operator
static Handle<String> TypeOf(Isolate* isolate, Handle<Object> object);

View File

@ -292,18 +292,6 @@ RUNTIME_FUNCTION(Runtime_MessageGetScript) {
}
RUNTIME_FUNCTION(Runtime_ErrorToStringRT) {
HandleScope scope(isolate);
DCHECK(args.length() == 1);
CONVERT_ARG_HANDLE_CHECKED(JSObject, error, 0);
Handle<String> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result,
isolate->error_tostring_helper()->Stringify(isolate, error));
return *result;
}
RUNTIME_FUNCTION(Runtime_FormatMessageString) {
HandleScope scope(isolate);
DCHECK(args.length() == 4);

View File

@ -335,7 +335,6 @@ namespace internal {
F(CollectStackTrace, 2, 1) \
F(MessageGetStartPosition, 1, 1) \
F(MessageGetScript, 1, 1) \
F(ErrorToStringRT, 1, 1) \
F(FormatMessageString, 4, 1) \
F(CallSiteGetFileNameRT, 1, 1) \
F(CallSiteGetFunctionNameRT, 1, 1) \

View File

@ -5540,67 +5540,6 @@ TEST(TryCatchInTryFinally) {
}
static void check_reference_error_message(v8::Local<v8::Message> message,
v8::Local<v8::Value> data) {
const char* reference_error = "Uncaught ReferenceError: asdf is not defined";
Local<v8::Context> context = CcTest::isolate()->GetCurrentContext();
const char* reference_error2 = "Uncaught Error: asdf is not defined";
CHECK(message->Get()->Equals(context, v8_str(reference_error)).FromJust() ||
message->Get()->Equals(context, v8_str(reference_error2)).FromJust());
}
static void Fail(const v8::FunctionCallbackInfo<v8::Value>& args) {
ApiTestFuzzer::Fuzz();
CHECK(false);
}
// Test that overwritten methods are not invoked on uncaught exception
// formatting. However, they are invoked when performing normal error
// string conversions.
TEST(APIThrowMessageOverwrittenToString) {
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
isolate->AddMessageListener(check_reference_error_message);
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
templ->Set(v8_str("fail"), v8::FunctionTemplate::New(isolate, Fail));
LocalContext context(NULL, templ);
CompileRun("asdf;");
CompileRun(
"var limit = {};"
"limit.valueOf = fail;"
"Error.stackTraceLimit = limit;");
CompileRun("asdf");
CompileRun("Array.prototype.pop = fail;");
CompileRun("Object.prototype.hasOwnProperty = fail;");
CompileRun("Object.prototype.toString = function f() { return 'Yikes'; }");
CompileRun("Number.prototype.toString = function f() { return 'Yikes'; }");
CompileRun("String.prototype.toString = function f() { return 'Yikes'; }");
CompileRun(
"ReferenceError.prototype.toString ="
" function() { return 'Whoops' }");
CompileRun("asdf;");
CompileRun("ReferenceError.prototype.constructor.name = void 0;");
CompileRun("asdf;");
CompileRun("ReferenceError.prototype.constructor = void 0;");
CompileRun("asdf;");
CompileRun("ReferenceError.prototype.__proto__ = new Object();");
CompileRun("asdf;");
CompileRun("ReferenceError.prototype = new Object();");
CompileRun("asdf;");
v8::Local<Value> string = CompileRun("try { asdf; } catch(e) { e + ''; }");
CHECK(string->Equals(context.local(), v8_str("Whoops")).FromJust());
CompileRun(
"ReferenceError.prototype.constructor = new Object();"
"ReferenceError.prototype.constructor.name = 1;"
"Number.prototype.toString = function() { return 'Whoops'; };"
"ReferenceError.prototype.toString = Object.prototype.toString;");
CompileRun("asdf;");
isolate->RemoveMessageListeners(check_reference_error_message);
}
static void check_custom_error_tostring(v8::Local<v8::Message> message,
v8::Local<v8::Value> data) {
const char* uncaught_error = "Uncaught MyError toString";

View File

@ -69,9 +69,11 @@ try {
assertTrue(e.hasOwnProperty('stack'));
// Check that intercepting property access from toString is prevented for
// compiler errors. This is not specified, but allowing interception
// through a getter can leak error objects from different
// script tags in the same context in a browser setting.
// compiler errors. This is not specified, but allowing interception through a
// getter can leak error objects from different script tags in the same context
// in a browser setting. Use Realm.eval as a proxy for loading scripts. We
// ignore the exception thrown from it since that would not be catchable from
// user-land code.
var errors = [SyntaxError, ReferenceError, TypeError, RangeError, URIError];
var error_triggers = ["syntax error",
"var error = reference",
@ -79,39 +81,12 @@ var error_triggers = ["syntax error",
"String.fromCodePoint(0xFFFFFF)",
"decodeURI('%F')"];
for (var i in errors) {
var name = errors[i].name;
// Monkey-patch prototype.
var props = ["name", "message", "stack"];
for (var j in props) {
errors[i].prototype.__defineGetter__(props[j], fail);
for (var prop of ["name", "message", "stack"]) {
errors[i].prototype.__defineGetter__(prop, fail);
}
// String conversion should not invoke monkey-patched getters on prototype.
var error;
try {
eval(error_triggers[i]);
} catch (e) {
error = e;
}
assertTrue(error.toString().startsWith(name));
// Deleting message on the error (exposing the getter) is fine.
delete error.message;
assertEquals(name, error.toString());
// Custom properties shadowing the name are fine.
var myerror = { name: "myerror", message: "mymessage"};
myerror.__proto__ = error;
assertEquals("myerror: mymessage", myerror.toString());
// Custom getters in actual objects are welcome.
error.__defineGetter__("name", function() { return "mine"; });
assertEquals("mine", error.toString());
// Custom properties shadowing the name are fine.
var myerror2 = { message: "mymessage"};
myerror2.__proto__ = error;
assertEquals("mine: mymessage", myerror2.toString());
assertThrows(()=>Realm.eval(0, error_triggers[i]));
}
// Monkey-patching non-internal errors should still be observable.

View File

@ -41,7 +41,7 @@ e.message = e;
e.stack = "Does not occur in output";
e.arguments = "Does not occur in output";
e.type = "Does not occur in output";
assertEquals('', e.toString());
assertThrows(()=>e.toString(), RangeError);
e = new Error();
e.name = [ e ];

View File

@ -12,4 +12,4 @@ function getter() {
a.__proto__ = Error("");
a.__defineGetter__('message', getter);
a.message;
assertThrows(()=>a.message, RangeError);

View File

@ -6,5 +6,4 @@ var error;
try { reference_error(); } catch (e) { error = e; }
toString = error.toString;
error.__proto__ = [];
assertEquals("ReferenceError: reference_error is not defined",
toString.call(error));
assertEquals("Error: reference_error is not defined", toString.call(error));

View File

@ -213,27 +213,11 @@ function testErrorsDuringFormatting() {
Nasty.prototype.foo = function () { throw new RangeError(); };
var n = new Nasty();
n.__defineGetter__('constructor', function () { CONS_FAIL; });
var threw = false;
try {
n.foo();
} catch (e) {
threw = true;
assertTrue(e.stack.indexOf('<error: ReferenceError') != -1,
"ErrorsDuringFormatting didn't contain error: ReferenceError");
}
assertTrue(threw, "ErrorsDuringFormatting didn't throw");
threw = false;
assertThrows(()=>n.foo(), RangeError);
// Now we can't even format the message saying that we couldn't format
// the stack frame. Put that in your pipe and smoke it!
ReferenceError.prototype.toString = function () { NESTED_FAIL; };
try {
n.foo();
} catch (e) {
threw = true;
assertTrue(e.stack.indexOf('<error>') != -1,
"ErrorsDuringFormatting didn't contain <error>");
}
assertTrue(threw, "ErrorsDuringFormatting didnt' throw (2)");
assertThrows(()=>n.foo(), RangeError);
}

View File

@ -27,7 +27,6 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE
PASS var array = []; array[0] = array; array + '' is ''
PASS var error = new Error; error.name = error; error.message = error; error + '' is ''
PASS var regexp = /a/; regexp.source = regexp; regexp + '' is '/a/'
PASS successfullyParsed is true

View File

@ -28,8 +28,5 @@ description(
// Array (elements)
shouldBe("var array = []; array[0] = array; array + ''", "''");
// Error (name, message)
shouldBe("var error = new Error; error.name = error; error.message = error; error + ''", "''");
// RegExp (source)
shouldBe("var regexp = /a/; regexp.source = regexp; regexp + ''", "'/a/'");