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:
parent
65eef38349
commit
454c1faeef
10
src/api.cc
10
src/api.cc
@ -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);
|
||||
}
|
||||
|
@ -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 ||
|
||||
|
@ -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)
|
||||
|
@ -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 });
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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_;
|
||||
|
@ -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) {
|
||||
|
111
src/messages.cc
111
src/messages.cc
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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) \
|
||||
|
@ -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";
|
||||
|
@ -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.
|
||||
|
@ -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 ];
|
||||
|
@ -12,4 +12,4 @@ function getter() {
|
||||
|
||||
a.__proto__ = Error("");
|
||||
a.__defineGetter__('message', getter);
|
||||
a.message;
|
||||
assertThrows(()=>a.message, RangeError);
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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/'");
|
||||
|
Loading…
Reference in New Issue
Block a user