f59a23356b
Stack trace generation requires access to the receiver; and while the receiver is already on the stack, we cannot determine its position during stack trace generation (it's stored in argv[0], and argc is only stored in a callee-saved register). This patch grants access to the receiver by pushing argc onto builtin exit frames as an extra argument. Compared to simply pushing the receiver, this requires an additional dereference during stack trace generation, but one fewer during builtin calls. BUG=v8:4815 Review-Url: https://codereview.chromium.org/2106883003 Cr-Commit-Position: refs/heads/master@{#37500}
453 lines
16 KiB
C++
453 lines
16 KiB
C++
// Copyright 2011 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "src/messages.h"
|
|
|
|
#include "src/api.h"
|
|
#include "src/execution.h"
|
|
#include "src/isolate-inl.h"
|
|
#include "src/keys.h"
|
|
#include "src/string-builder.h"
|
|
#include "src/wasm/wasm-module.h"
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
|
|
MessageLocation::MessageLocation(Handle<Script> script, int start_pos,
|
|
int end_pos)
|
|
: script_(script), start_pos_(start_pos), end_pos_(end_pos) {}
|
|
MessageLocation::MessageLocation(Handle<Script> script, int start_pos,
|
|
int end_pos, Handle<JSFunction> function)
|
|
: script_(script),
|
|
start_pos_(start_pos),
|
|
end_pos_(end_pos),
|
|
function_(function) {}
|
|
MessageLocation::MessageLocation() : start_pos_(-1), end_pos_(-1) {}
|
|
|
|
// If no message listeners have been registered this one is called
|
|
// by default.
|
|
void MessageHandler::DefaultMessageReport(Isolate* isolate,
|
|
const MessageLocation* loc,
|
|
Handle<Object> message_obj) {
|
|
base::SmartArrayPointer<char> str = GetLocalizedMessage(isolate, message_obj);
|
|
if (loc == NULL) {
|
|
PrintF("%s\n", str.get());
|
|
} else {
|
|
HandleScope scope(isolate);
|
|
Handle<Object> data(loc->script()->name(), isolate);
|
|
base::SmartArrayPointer<char> data_str;
|
|
if (data->IsString())
|
|
data_str = Handle<String>::cast(data)->ToCString(DISALLOW_NULLS);
|
|
PrintF("%s:%i: %s\n", data_str.get() ? data_str.get() : "<unknown>",
|
|
loc->start_pos(), str.get());
|
|
}
|
|
}
|
|
|
|
|
|
Handle<JSMessageObject> MessageHandler::MakeMessageObject(
|
|
Isolate* isolate, MessageTemplate::Template message,
|
|
MessageLocation* location, Handle<Object> argument,
|
|
Handle<JSArray> stack_frames) {
|
|
Factory* factory = isolate->factory();
|
|
|
|
int start = -1;
|
|
int end = -1;
|
|
Handle<Object> script_handle = factory->undefined_value();
|
|
if (location != NULL) {
|
|
start = location->start_pos();
|
|
end = location->end_pos();
|
|
script_handle = Script::GetWrapper(location->script());
|
|
} else {
|
|
script_handle = Script::GetWrapper(isolate->factory()->empty_script());
|
|
}
|
|
|
|
Handle<Object> stack_frames_handle = stack_frames.is_null()
|
|
? Handle<Object>::cast(factory->undefined_value())
|
|
: Handle<Object>::cast(stack_frames);
|
|
|
|
Handle<JSMessageObject> message_obj = factory->NewJSMessageObject(
|
|
message, argument, start, end, script_handle, stack_frames_handle);
|
|
|
|
return message_obj;
|
|
}
|
|
|
|
|
|
void MessageHandler::ReportMessage(Isolate* isolate, MessageLocation* loc,
|
|
Handle<JSMessageObject> message) {
|
|
// We are calling into embedder's code which can throw exceptions.
|
|
// Thus we need to save current exception state, reset it to the clean one
|
|
// and ignore scheduled exceptions callbacks can throw.
|
|
|
|
// We pass the exception object into the message handler callback though.
|
|
Object* exception_object = isolate->heap()->undefined_value();
|
|
if (isolate->has_pending_exception()) {
|
|
exception_object = isolate->pending_exception();
|
|
}
|
|
Handle<Object> exception(exception_object, isolate);
|
|
|
|
Isolate::ExceptionScope exception_scope(isolate);
|
|
isolate->clear_pending_exception();
|
|
isolate->set_external_caught_exception(false);
|
|
|
|
// Turn the exception on the message into a string if it is an object.
|
|
if (message->argument()->IsJSObject()) {
|
|
HandleScope scope(isolate);
|
|
Handle<Object> argument(message->argument(), isolate);
|
|
|
|
MaybeHandle<Object> maybe_stringified;
|
|
Handle<Object> stringified;
|
|
// Make sure we don't leak uncaught internally generated Error objects.
|
|
if (argument->IsJSError()) {
|
|
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");
|
|
}
|
|
message->set_argument(*stringified);
|
|
}
|
|
|
|
v8::Local<v8::Message> api_message_obj = v8::Utils::MessageToLocal(message);
|
|
v8::Local<v8::Value> api_exception_obj = v8::Utils::ToLocal(exception);
|
|
|
|
v8::NeanderArray global_listeners(isolate->factory()->message_listeners());
|
|
int global_length = global_listeners.length();
|
|
if (global_length == 0) {
|
|
DefaultMessageReport(isolate, loc, message);
|
|
if (isolate->has_scheduled_exception()) {
|
|
isolate->clear_scheduled_exception();
|
|
}
|
|
} else {
|
|
for (int i = 0; i < global_length; i++) {
|
|
HandleScope scope(isolate);
|
|
if (global_listeners.get(i)->IsUndefined(isolate)) continue;
|
|
v8::NeanderObject listener(JSObject::cast(global_listeners.get(i)));
|
|
Handle<Foreign> callback_obj(Foreign::cast(listener.get(0)));
|
|
v8::MessageCallback callback =
|
|
FUNCTION_CAST<v8::MessageCallback>(callback_obj->foreign_address());
|
|
Handle<Object> callback_data(listener.get(1), isolate);
|
|
{
|
|
// Do not allow exceptions to propagate.
|
|
v8::TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate));
|
|
callback(api_message_obj, callback_data->IsUndefined(isolate)
|
|
? api_exception_obj
|
|
: v8::Utils::ToLocal(callback_data));
|
|
}
|
|
if (isolate->has_scheduled_exception()) {
|
|
isolate->clear_scheduled_exception();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Handle<String> MessageHandler::GetMessage(Isolate* isolate,
|
|
Handle<Object> data) {
|
|
Handle<JSMessageObject> message = Handle<JSMessageObject>::cast(data);
|
|
Handle<Object> arg = Handle<Object>(message->argument(), isolate);
|
|
return MessageTemplate::FormatMessage(isolate, message->type(), arg);
|
|
}
|
|
|
|
|
|
base::SmartArrayPointer<char> MessageHandler::GetLocalizedMessage(
|
|
Isolate* isolate, Handle<Object> data) {
|
|
HandleScope scope(isolate);
|
|
return GetMessage(isolate, data)->ToCString(DISALLOW_NULLS);
|
|
}
|
|
|
|
|
|
CallSite::CallSite(Isolate* isolate, Handle<JSObject> call_site_obj)
|
|
: isolate_(isolate) {
|
|
Handle<Object> maybe_function = JSObject::GetDataProperty(
|
|
call_site_obj, isolate->factory()->call_site_function_symbol());
|
|
if (maybe_function->IsJSFunction()) {
|
|
// javascript
|
|
fun_ = Handle<JSFunction>::cast(maybe_function);
|
|
receiver_ = JSObject::GetDataProperty(
|
|
call_site_obj, isolate->factory()->call_site_receiver_symbol());
|
|
} else {
|
|
Handle<Object> maybe_wasm_func_index = JSObject::GetDataProperty(
|
|
call_site_obj, isolate->factory()->call_site_wasm_func_index_symbol());
|
|
if (!maybe_wasm_func_index->IsSmi()) {
|
|
// invalid: neither javascript nor wasm
|
|
return;
|
|
}
|
|
// wasm
|
|
wasm_obj_ = Handle<JSObject>::cast(JSObject::GetDataProperty(
|
|
call_site_obj, isolate->factory()->call_site_wasm_obj_symbol()));
|
|
wasm_func_index_ = Smi::cast(*maybe_wasm_func_index)->value();
|
|
DCHECK(static_cast<int>(wasm_func_index_) >= 0);
|
|
}
|
|
|
|
CHECK(JSObject::GetDataProperty(
|
|
call_site_obj, isolate->factory()->call_site_position_symbol())
|
|
->ToInt32(&pos_));
|
|
}
|
|
|
|
|
|
Handle<Object> CallSite::GetFileName() {
|
|
if (!IsJavaScript()) return isolate_->factory()->null_value();
|
|
Object* script = fun_->shared()->script();
|
|
if (!script->IsScript()) return isolate_->factory()->null_value();
|
|
return Handle<Object>(Script::cast(script)->name(), isolate_);
|
|
}
|
|
|
|
|
|
Handle<Object> CallSite::GetFunctionName() {
|
|
if (IsWasm()) {
|
|
return wasm::GetWasmFunctionNameOrNull(isolate_, wasm_obj_,
|
|
wasm_func_index_);
|
|
}
|
|
Handle<String> result = JSFunction::GetName(fun_);
|
|
if (result->length() != 0) return result;
|
|
|
|
Handle<Object> script(fun_->shared()->script(), isolate_);
|
|
if (script->IsScript() &&
|
|
Handle<Script>::cast(script)->compilation_type() ==
|
|
Script::COMPILATION_TYPE_EVAL) {
|
|
return isolate_->factory()->eval_string();
|
|
}
|
|
return isolate_->factory()->null_value();
|
|
}
|
|
|
|
Handle<Object> CallSite::GetScriptNameOrSourceUrl() {
|
|
if (!IsJavaScript()) return isolate_->factory()->null_value();
|
|
Object* script_obj = fun_->shared()->script();
|
|
if (!script_obj->IsScript()) return isolate_->factory()->null_value();
|
|
Handle<Script> script(Script::cast(script_obj), isolate_);
|
|
Object* source_url = script->source_url();
|
|
if (source_url->IsString()) return Handle<Object>(source_url, isolate_);
|
|
return Handle<Object>(script->name(), isolate_);
|
|
}
|
|
|
|
bool CheckMethodName(Isolate* isolate, Handle<JSObject> obj, Handle<Name> name,
|
|
Handle<JSFunction> fun,
|
|
LookupIterator::Configuration config) {
|
|
LookupIterator iter =
|
|
LookupIterator::PropertyOrElement(isolate, obj, name, config);
|
|
if (iter.state() == LookupIterator::DATA) {
|
|
return iter.GetDataValue().is_identical_to(fun);
|
|
} else if (iter.state() == LookupIterator::ACCESSOR) {
|
|
Handle<Object> accessors = iter.GetAccessors();
|
|
if (accessors->IsAccessorPair()) {
|
|
Handle<AccessorPair> pair = Handle<AccessorPair>::cast(accessors);
|
|
return pair->getter() == *fun || pair->setter() == *fun;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
Handle<Object> CallSite::GetMethodName() {
|
|
if (!IsJavaScript() || receiver_->IsNull(isolate_) ||
|
|
receiver_->IsUndefined(isolate_)) {
|
|
return isolate_->factory()->null_value();
|
|
}
|
|
Handle<JSReceiver> receiver =
|
|
Object::ToObject(isolate_, receiver_).ToHandleChecked();
|
|
if (!receiver->IsJSObject()) {
|
|
return isolate_->factory()->null_value();
|
|
}
|
|
|
|
Handle<JSObject> obj = Handle<JSObject>::cast(receiver);
|
|
Handle<Object> function_name(fun_->shared()->name(), isolate_);
|
|
if (function_name->IsName()) {
|
|
Handle<Name> name = Handle<Name>::cast(function_name);
|
|
// ES2015 gives getters and setters name prefixes which must
|
|
// be stripped to find the property name.
|
|
Handle<String> name_string = Handle<String>::cast(name);
|
|
if (name_string->IsUtf8EqualTo(CStrVector("get "), true) ||
|
|
name_string->IsUtf8EqualTo(CStrVector("set "), true)) {
|
|
name = isolate_->factory()->NewProperSubString(name_string, 4,
|
|
name_string->length());
|
|
}
|
|
if (CheckMethodName(isolate_, obj, name, fun_,
|
|
LookupIterator::PROTOTYPE_CHAIN_SKIP_INTERCEPTOR)) {
|
|
return name;
|
|
}
|
|
}
|
|
|
|
HandleScope outer_scope(isolate_);
|
|
Handle<Object> result;
|
|
for (PrototypeIterator iter(isolate_, obj, kStartAtReceiver); !iter.IsAtEnd();
|
|
iter.Advance()) {
|
|
Handle<Object> current = PrototypeIterator::GetCurrent(iter);
|
|
if (!current->IsJSObject()) break;
|
|
Handle<JSObject> current_obj = Handle<JSObject>::cast(current);
|
|
if (current_obj->IsAccessCheckNeeded()) break;
|
|
Handle<FixedArray> keys =
|
|
KeyAccumulator::GetOwnEnumPropertyKeys(isolate_, current_obj);
|
|
for (int i = 0; i < keys->length(); i++) {
|
|
HandleScope inner_scope(isolate_);
|
|
if (!keys->get(i)->IsName()) continue;
|
|
Handle<Name> name_key(Name::cast(keys->get(i)), isolate_);
|
|
if (!CheckMethodName(isolate_, current_obj, name_key, fun_,
|
|
LookupIterator::OWN_SKIP_INTERCEPTOR))
|
|
continue;
|
|
// Return null in case of duplicates to avoid confusion.
|
|
if (!result.is_null()) return isolate_->factory()->null_value();
|
|
result = inner_scope.CloseAndEscape(name_key);
|
|
}
|
|
}
|
|
|
|
if (!result.is_null()) return outer_scope.CloseAndEscape(result);
|
|
return isolate_->factory()->null_value();
|
|
}
|
|
|
|
|
|
int CallSite::GetLineNumber() {
|
|
if (pos_ >= 0 && IsJavaScript()) {
|
|
Handle<Object> script_obj(fun_->shared()->script(), isolate_);
|
|
if (script_obj->IsScript()) {
|
|
Handle<Script> script = Handle<Script>::cast(script_obj);
|
|
return Script::GetLineNumber(script, pos_) + 1;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
int CallSite::GetColumnNumber() {
|
|
if (pos_ >= 0 && IsJavaScript()) {
|
|
Handle<Object> script_obj(fun_->shared()->script(), isolate_);
|
|
if (script_obj->IsScript()) {
|
|
Handle<Script> script = Handle<Script>::cast(script_obj);
|
|
return Script::GetColumnNumber(script, pos_) + 1;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
bool CallSite::IsNative() {
|
|
if (!IsJavaScript()) return false;
|
|
Handle<Object> script(fun_->shared()->script(), isolate_);
|
|
return script->IsScript() &&
|
|
Handle<Script>::cast(script)->type() == Script::TYPE_NATIVE;
|
|
}
|
|
|
|
|
|
bool CallSite::IsToplevel() {
|
|
if (IsWasm()) return false;
|
|
return receiver_->IsJSGlobalProxy() || receiver_->IsNull(isolate_) ||
|
|
receiver_->IsUndefined(isolate_);
|
|
}
|
|
|
|
|
|
bool CallSite::IsEval() {
|
|
if (!IsJavaScript()) return false;
|
|
Handle<Object> script(fun_->shared()->script(), isolate_);
|
|
return script->IsScript() &&
|
|
Handle<Script>::cast(script)->compilation_type() ==
|
|
Script::COMPILATION_TYPE_EVAL;
|
|
}
|
|
|
|
|
|
bool CallSite::IsConstructor() {
|
|
// Builtin exit frames mark constructors by passing a special symbol as the
|
|
// receiver.
|
|
Object* ctor_symbol = isolate_->heap()->call_site_constructor_symbol();
|
|
if (*receiver_ == ctor_symbol) return true;
|
|
if (!IsJavaScript() || !receiver_->IsJSObject()) return false;
|
|
Handle<Object> constructor =
|
|
JSReceiver::GetDataProperty(Handle<JSObject>::cast(receiver_),
|
|
isolate_->factory()->constructor_string());
|
|
return constructor.is_identical_to(fun_);
|
|
}
|
|
|
|
|
|
Handle<String> MessageTemplate::FormatMessage(Isolate* isolate,
|
|
int template_index,
|
|
Handle<Object> arg) {
|
|
Factory* factory = isolate->factory();
|
|
Handle<String> result_string;
|
|
if (arg->IsString()) {
|
|
result_string = Handle<String>::cast(arg);
|
|
} else {
|
|
Handle<JSFunction> fun = isolate->no_side_effects_to_string_fun();
|
|
|
|
MaybeHandle<Object> maybe_result =
|
|
Execution::TryCall(isolate, fun, factory->undefined_value(), 1, &arg);
|
|
Handle<Object> result;
|
|
if (!maybe_result.ToHandle(&result) || !result->IsString()) {
|
|
return factory->InternalizeOneByteString(STATIC_CHAR_VECTOR("<error>"));
|
|
}
|
|
result_string = Handle<String>::cast(result);
|
|
}
|
|
MaybeHandle<String> maybe_result_string = MessageTemplate::FormatMessage(
|
|
template_index, result_string, factory->empty_string(),
|
|
factory->empty_string());
|
|
if (!maybe_result_string.ToHandle(&result_string)) {
|
|
return factory->InternalizeOneByteString(STATIC_CHAR_VECTOR("<error>"));
|
|
}
|
|
// A string that has been obtained from JS code in this way is
|
|
// likely to be a complicated ConsString of some sort. We flatten it
|
|
// here to improve the efficiency of converting it to a C string and
|
|
// other operations that are likely to take place (see GetLocalizedMessage
|
|
// for example).
|
|
return String::Flatten(result_string);
|
|
}
|
|
|
|
|
|
const char* MessageTemplate::TemplateString(int template_index) {
|
|
switch (template_index) {
|
|
#define CASE(NAME, STRING) \
|
|
case k##NAME: \
|
|
return STRING;
|
|
MESSAGE_TEMPLATES(CASE)
|
|
#undef CASE
|
|
case kLastMessage:
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
MaybeHandle<String> MessageTemplate::FormatMessage(int template_index,
|
|
Handle<String> arg0,
|
|
Handle<String> arg1,
|
|
Handle<String> arg2) {
|
|
Isolate* isolate = arg0->GetIsolate();
|
|
const char* template_string = TemplateString(template_index);
|
|
if (template_string == NULL) {
|
|
isolate->ThrowIllegalOperation();
|
|
return MaybeHandle<String>();
|
|
}
|
|
|
|
IncrementalStringBuilder builder(isolate);
|
|
|
|
unsigned int i = 0;
|
|
Handle<String> args[] = {arg0, arg1, arg2};
|
|
for (const char* c = template_string; *c != '\0'; c++) {
|
|
if (*c == '%') {
|
|
// %% results in verbatim %.
|
|
if (*(c + 1) == '%') {
|
|
c++;
|
|
builder.AppendCharacter('%');
|
|
} else {
|
|
DCHECK(i < arraysize(args));
|
|
Handle<String> arg = args[i++];
|
|
builder.AppendString(arg);
|
|
}
|
|
} else {
|
|
builder.AppendCharacter(*c);
|
|
}
|
|
}
|
|
|
|
return builder.Finish();
|
|
}
|
|
|
|
|
|
} // namespace internal
|
|
} // namespace v8
|