blob: c8d4f49a0e9a21d1d8c2160f6154e5b98aaa5a6e [file] [log] [blame]
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/script/v8c/v8c_global_environment.h"
#include <algorithm>
#include <utility>
#include "base/debug/trace_event.h"
#include "base/lazy_instance.h"
#include "base/stringprintf.h"
#include "cobalt/base/polymorphic_downcast.h"
#include "cobalt/script/v8c/embedded_resources.h"
#include "cobalt/script/v8c/entry_scope.h"
#include "cobalt/script/v8c/v8c_script_value_factory.h"
#include "cobalt/script/v8c/v8c_source_code.h"
#include "cobalt/script/v8c/v8c_user_object_holder.h"
#include "cobalt/script/v8c/v8c_value_handle.h"
#include "nb/memory_scope.h"
namespace cobalt {
namespace script {
namespace v8c {
namespace {
std::string ExceptionToString(const v8::TryCatch& try_catch) {
v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
v8::String::Utf8Value exception(try_catch.Exception());
v8::Local<v8::Message> message(try_catch.Message());
std::string string;
if (message.IsEmpty()) {
string.append(base::StringPrintf("%s\n", *exception));
} else {
v8::String::Utf8Value filename(message->GetScriptOrigin().ResourceName());
int linenum = message->GetLineNumber();
int colnum = message->GetStartColumn();
string.append(base::StringPrintf("%s:%i:%i %s\n", *filename, linenum,
colnum, *exception));
v8::String::Utf8Value sourceline(message->GetSourceLine());
string.append(base::StringPrintf("%s\n", *sourceline));
}
return string;
}
std::string ToStringOrNull(v8::Local<v8::Value> value) {
if (value.IsEmpty() || !value->IsString()) {
return "";
}
return *v8::String::Utf8Value(value.As<v8::String>());
}
} // namespace
V8cGlobalEnvironment::V8cGlobalEnvironment(v8::Isolate* isolate)
: isolate_(isolate),
destruction_helper_(isolate),
wrapper_factory_(new WrapperFactory(isolate)),
script_value_factory_(new V8cScriptValueFactory(isolate)) {
TRACK_MEMORY_SCOPE("Javascript");
TRACE_EVENT0("cobalt::script",
"V8cGlobalEnvironment::V8cGlobalEnvironment()");
wrapper_factory_.reset(new WrapperFactory(isolate));
isolate_->SetData(kIsolateDataIndex, this);
DCHECK(isolate_->GetData(kIsolateDataIndex) == this);
isolate_->SetAllowCodeGenerationFromStringsCallback(
AllowCodeGenerationFromStringsCallback);
isolate_->SetAllowWasmCodeGenerationCallback(
[](v8::Local<v8::Context> context, v8::Local<v8::String> source) {
return false;
});
isolate_->AddMessageListenerWithErrorLevel(
MessageHandler,
v8::Isolate::kMessageError | v8::Isolate::kMessageWarning |
v8::Isolate::kMessageInfo | v8::Isolate::kMessageDebug |
v8::Isolate::kMessageLog);
}
V8cGlobalEnvironment::~V8cGlobalEnvironment() {
TRACE_EVENT0("cobalt::script",
"V8cGlobalEnvironment::~V8cGlobalEnvironment()");
DCHECK(thread_checker_.CalledOnValidThread());
}
void V8cGlobalEnvironment::CreateGlobalObject() {
TRACE_EVENT0("cobalt::script", "V8cGlobalEnvironment::CreateGlobalObject()");
TRACK_MEMORY_SCOPE("Javascript");
DCHECK(thread_checker_.CalledOnValidThread());
// Intentionally not an |EntryScope|, since the context doesn't exist yet.
v8::Isolate::Scope isolate_scope(isolate_);
v8::HandleScope handle_scope(isolate_);
v8::Local<v8::Context> context = v8::Context::New(isolate_);
context_.Reset(isolate_, context);
v8::Context::Scope context_scope(context);
EvaluateAutomatics();
}
bool V8cGlobalEnvironment::EvaluateScript(
const scoped_refptr<SourceCode>& source_code,
std::string* out_result_utf8) {
TRACK_MEMORY_SCOPE("Javascript");
TRACE_EVENT0("cobalt::script", "V8cGlobalEnvironment::EvaluateScript()");
DCHECK(thread_checker_.CalledOnValidThread());
EntryScope entry_scope(isolate_);
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
v8::TryCatch try_catch(isolate_);
v8::Local<v8::Value> result;
if (!EvaluateScriptInternal(source_code).ToLocal(&result)) {
if (!try_catch.HasCaught()) {
LOG(WARNING) << "Script evaluation failed with no JavaScript exception.";
return false;
}
// The MessageHandler appears to never get called under a |v8::TryCatch|
// block, even if we re-throw it. We work around this by manually passing
// it to the MessageHandler.
MessageHandler(try_catch.Message(), try_catch.Exception());
if (out_result_utf8) {
*out_result_utf8 = ExceptionToString(try_catch);
}
return false;
}
if (out_result_utf8) {
V8cExceptionState exception_state(isolate_);
FromJSValue(isolate_, result, kNoConversionFlags, &exception_state,
out_result_utf8);
}
return true;
}
bool V8cGlobalEnvironment::EvaluateScript(
const scoped_refptr<SourceCode>& source_code,
const scoped_refptr<Wrappable>& owning_object,
base::optional<ValueHandleHolder::Reference>* out_value_handle) {
TRACK_MEMORY_SCOPE("Javascript");
TRACE_EVENT0("cobalt::script", "V8cGlobalEnvironment::EvaluateScript()");
DCHECK(thread_checker_.CalledOnValidThread());
EntryScope entry_scope(isolate_);
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
v8::TryCatch try_catch(isolate_);
v8::Local<v8::Value> result;
if (!EvaluateScriptInternal(source_code).ToLocal(&result)) {
if (!try_catch.HasCaught()) {
LOG(WARNING) << "Script evaluation failed with no JavaScript exception.";
}
// The MessageHandler appears to never get called under a |v8::TryCatch|
// block, even if we re-throw it. We work around this by manually passing
// it to the MessageHandler.
MessageHandler(try_catch.Message(), try_catch.Exception());
return false;
}
if (out_value_handle) {
V8cValueHandleHolder v8c_value_handle_holder(isolate_, result);
out_value_handle->emplace(owning_object.get(), v8c_value_handle_holder);
}
return true;
}
std::vector<StackFrame> V8cGlobalEnvironment::GetStackTrace(int max_frames) {
TRACE_EVENT0("cobalt::script", "V8cGlobalEnvironment::GetStackTrace()");
DCHECK(thread_checker_.CalledOnValidThread());
// cobalt::script treats |max_frames| being set to 0 as "the entire stack",
// while V8 interprets the frame count being set to 0 as "give me 0 frames",
// so we have to translate between the two.
const int kV8CallMaxFrameAmount = 4096;
int v8_max_frames = (max_frames == 0) ? kV8CallMaxFrameAmount : max_frames;
v8::HandleScope handle_scope(isolate_);
v8::Local<v8::StackTrace> stack_trace =
v8::StackTrace::CurrentStackTrace(isolate_, v8_max_frames);
std::vector<StackFrame> result;
for (int i = 0; i < stack_trace->GetFrameCount(); i++) {
v8::Local<v8::StackFrame> stack_frame = stack_trace->GetFrame(i);
result.emplace_back(
stack_frame->GetLineNumber(), stack_frame->GetColumn(),
*v8::String::Utf8Value(isolate_, stack_frame->GetFunctionName()),
*v8::String::Utf8Value(isolate_, stack_frame->GetScriptName()));
}
return result;
}
void V8cGlobalEnvironment::AddRoot(Traceable* traceable) {
V8cEngine::GetFromIsolate(isolate_)->heap_tracer()->AddRoot(traceable);
}
void V8cGlobalEnvironment::RemoveRoot(Traceable* traceable) {
V8cEngine::GetFromIsolate(isolate_)->heap_tracer()->RemoveRoot(traceable);
}
void V8cGlobalEnvironment::PreventGarbageCollection(
const scoped_refptr<Wrappable>& wrappable) {
DCHECK(thread_checker_.CalledOnValidThread());
v8::HandleScope handle_scope(isolate_);
v8::Local<v8::Object> wrapper = wrapper_factory_->GetWrapper(wrappable);
WrapperPrivate::GetFromWrapperObject(wrapper)->IncrementRefCount();
}
void V8cGlobalEnvironment::AllowGarbageCollection(
const scoped_refptr<Wrappable>& wrappable) {
TRACK_MEMORY_SCOPE("Javascript");
DCHECK(thread_checker_.CalledOnValidThread());
v8::HandleScope handle_scope(isolate_);
v8::Local<v8::Object> wrapper = wrapper_factory_->GetWrapper(wrappable);
WrapperPrivate::GetFromWrapperObject(wrapper)->DecrementRefCount();
}
void V8cGlobalEnvironment::DisableEval(const std::string& message) {
DCHECK(thread_checker_.CalledOnValidThread());
EntryScope entry_scope(isolate_);
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
context->AllowCodeGenerationFromStrings(false);
context->SetErrorMessageForCodeGenerationFromStrings(
v8::String::NewFromUtf8(isolate_, message.c_str(),
v8::NewStringType::kNormal)
.ToLocalChecked());
}
void V8cGlobalEnvironment::EnableEval() {
DCHECK(thread_checker_.CalledOnValidThread());
EntryScope entry_scope(isolate_);
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
context->AllowCodeGenerationFromStrings(true);
}
void V8cGlobalEnvironment::DisableJit() {
DCHECK(thread_checker_.CalledOnValidThread());
LOG(INFO) << "V8 version " << V8_MAJOR_VERSION << '.' << V8_MINOR_VERSION
<< "can only be run with JIT enabled, ignoring |DisableJit| call.";
}
void V8cGlobalEnvironment::SetReportEvalCallback(
const base::Closure& report_eval) {
DCHECK(thread_checker_.CalledOnValidThread());
report_eval_ = report_eval;
}
void V8cGlobalEnvironment::SetReportErrorCallback(
const ReportErrorCallback& report_error_callback) {
DCHECK(thread_checker_.CalledOnValidThread());
report_error_callback_ = report_error_callback;
}
void V8cGlobalEnvironment::Bind(const std::string& identifier,
const scoped_refptr<Wrappable>& impl) {
TRACE_EVENT0("cobalt::script", "V8cGlobalEnvironment::Bind()");
TRACK_MEMORY_SCOPE("Javascript");
DCHECK(impl);
EntryScope entry_scope(isolate_);
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
v8::Local<v8::Object> wrapper = wrapper_factory_->GetWrapper(impl);
v8::Local<v8::Object> global_object = context->Global();
v8::Maybe<bool> set_result = global_object->Set(
context,
v8::String::NewFromUtf8(isolate_, identifier.c_str(),
v8::NewStringType::kInternalized)
.ToLocalChecked(),
wrapper);
DCHECK(set_result.FromJust());
}
ScriptValueFactory* V8cGlobalEnvironment::script_value_factory() {
DCHECK(script_value_factory_);
return script_value_factory_.get();
}
V8cGlobalEnvironment::DestructionHelper::~DestructionHelper() {
class ForceWeakVisitor : public v8::PersistentHandleVisitor {
public:
explicit ForceWeakVisitor(v8::Isolate* isolate) : isolate_(isolate) {}
void VisitPersistentHandle(v8::Persistent<v8::Value>* value,
uint16_t class_id) override {
if (class_id == WrapperPrivate::kClassId) {
v8::Local<v8::Value> v = value->Get(isolate_);
DCHECK(v->IsObject());
WrapperPrivate* wrapper_private =
WrapperPrivate::GetFromWrapperObject(v.As<v8::Object>());
wrapper_private->ForceWeakForShutDown();
}
}
private:
v8::Isolate* isolate_;
};
TRACE_EVENT0("cobalt::script",
"V8cGlobalEnvironment::DestructionHelper::~DestructionHelper()");
{
TRACE_EVENT0("cobalt::script",
"V8cGlobalEnvironment::DestructionHelper::~DestructionHelper::"
"ForceWeakVisitor");
v8::HandleScope handle_scope(isolate_);
ForceWeakVisitor force_weak_visitor(isolate_);
isolate_->VisitHandlesWithClassIds(&force_weak_visitor);
}
isolate_->SetEmbedderHeapTracer(nullptr);
isolate_->SetData(kIsolateDataIndex, nullptr);
isolate_->LowMemoryNotification();
}
// static
bool V8cGlobalEnvironment::AllowCodeGenerationFromStringsCallback(
v8::Local<v8::Context> context, v8::Local<v8::String> source) {
V8cGlobalEnvironment* global_environment =
V8cGlobalEnvironment::GetFromIsolate(context->GetIsolate());
DCHECK(global_environment);
if (!global_environment->report_eval_.is_null()) {
global_environment->report_eval_.Run();
}
// This callback should only be called while code generation from strings is
// not allowed from within V8, so this should always be false. Note that
// WebAssembly code generation will fall back to this callback if a
// WebAssembly callback has not been explicitly set, however we *have* set
// one.
DCHECK_EQ(context->IsCodeGenerationFromStringsAllowed(), false);
return context->IsCodeGenerationFromStringsAllowed();
}
// static
void V8cGlobalEnvironment::MessageHandler(v8::Local<v8::Message> message,
v8::Local<v8::Value> data) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
V8cGlobalEnvironment* global_environment =
V8cGlobalEnvironment::GetFromIsolate(isolate);
if (isolate->GetEnteredContext().IsEmpty()) {
return;
}
if (message->ErrorLevel() != v8::Isolate::kMessageError) {
return;
}
v8::Local<v8::Context> context = isolate->GetEnteredContext();
ErrorReport error_report;
error_report.message = *v8::String::Utf8Value(message->Get());
error_report.filename = ToStringOrNull(message->GetScriptResourceName());
int line_number = 0;
int column_number = 0;
if (message->GetLineNumber(context).To(&line_number) &&
message->GetStartColumn(context).To(&column_number)) {
column_number++;
}
error_report.line_number = line_number;
error_report.column_number = column_number;
error_report.is_muted = message->IsSharedCrossOrigin();
error_report.error.reset(new V8cValueHandleHolder(isolate, data));
if (!global_environment->report_error_callback_.is_null()) {
global_environment->report_error_callback_.Run(error_report);
}
}
v8::MaybeLocal<v8::Value> V8cGlobalEnvironment::EvaluateScriptInternal(
const scoped_refptr<SourceCode>& source_code) {
TRACK_MEMORY_SCOPE("Javascript");
DCHECK(thread_checker_.CalledOnValidThread());
// Note that we expect an |EntryScope| and |v8::TryCatch| to have been set
// up by our caller.
V8cSourceCode* v8c_source_code =
base::polymorphic_downcast<V8cSourceCode*>(source_code.get());
const base::SourceLocation& source_location = v8c_source_code->location();
v8::Local<v8::String> resource_name;
if (!v8::String::NewFromUtf8(isolate_, source_location.file_path.c_str(),
v8::NewStringType::kNormal)
.ToLocal(&resource_name)) {
// Technically possible, but whoa man should this never happen.
LOG(WARNING) << "Failed to convert source location file path \""
<< source_location.file_path << "\" to a V8 UTF-8 string.";
return {};
}
// Note that |v8::ScriptOrigin| offsets are 0-based, whereas
// |SourceLocation| line/column numbers are 1-based, so subtract 1 to
// translate between the two.
v8::ScriptOrigin script_origin(
/*resource_name=*/resource_name,
/*resource_line_offset=*/
v8::Integer::New(isolate_, source_location.line_number - 1),
/*resource_column_offset=*/
v8::Integer::New(isolate_, source_location.column_number - 1),
/*resource_is_shared_cross_origin=*/
v8::Boolean::New(isolate_, !v8c_source_code->is_muted()));
v8::Local<v8::String> source;
if (!v8::String::NewFromUtf8(isolate_, v8c_source_code->source_utf8().c_str(),
v8::NewStringType::kNormal)
.ToLocal(&source)) {
LOG(WARNING) << "Failed to convert source code to V8 UTF-8 string.";
return {};
}
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
v8::Local<v8::Script> script;
{
TRACE_EVENT0("cobalt::script", "v8::Script::Compile()");
if (!v8::Script::Compile(context, source, &script_origin)
.ToLocal(&script)) {
LOG(WARNING) << "Failed to compile script.";
return {};
}
}
v8::Local<v8::Value> result;
{
TRACE_EVENT0("cobalt::script", "v8::Script::Run()");
if (!script->Run(context).ToLocal(&result)) {
LOG(WARNING) << "Failed to run script.";
return {};
}
}
return result;
}
void V8cGlobalEnvironment::EvaluateEmbeddedScript(const unsigned char* data,
size_t size,
const char* filename) {
TRACE_EVENT1("cobalt::script", "V8cGlobalEnvironment::EvaluateEmbeddedScript",
"filename", filename);
TRACK_MEMORY_SCOPE("Javascript");
std::string source(reinterpret_cast<const char*>(data), size);
scoped_refptr<SourceCode> source_code =
new V8cSourceCode(source, base::SourceLocation(filename, 1, 1));
std::string result;
bool success = EvaluateScript(source_code, &result);
if (!success) {
DLOG(FATAL) << result;
}
}
void V8cGlobalEnvironment::EvaluateAutomatics() {
TRACE_EVENT0("cobalt::script", "V8cGlobalEnvironment::EvaluateAutomatics()");
EvaluateEmbeddedScript(
V8cEmbeddedResources::byte_length_queuing_strategy_js,
sizeof(V8cEmbeddedResources::byte_length_queuing_strategy_js),
"byte_length_queuing_strategy.js");
EvaluateEmbeddedScript(
V8cEmbeddedResources::count_queuing_strategy_js,
sizeof(V8cEmbeddedResources::count_queuing_strategy_js),
"count_queuing_strategy.js");
EvaluateEmbeddedScript(V8cEmbeddedResources::readable_stream_js,
sizeof(V8cEmbeddedResources::readable_stream_js),
"readable_stream.js");
EvaluateEmbeddedScript(V8cEmbeddedResources::fetch_js,
sizeof(V8cEmbeddedResources::fetch_js), "fetch.js");
}
bool V8cGlobalEnvironment::HasInterfaceData(int key) const {
DCHECK_GE(key, 0);
if (key >= cached_interface_data_.size()) {
return false;
}
return !cached_interface_data_[key].IsEmpty();
}
v8::Local<v8::FunctionTemplate> V8cGlobalEnvironment::GetInterfaceData(
int key) const {
DCHECK(HasInterfaceData(key));
return cached_interface_data_[key].Get(isolate_);
}
void V8cGlobalEnvironment::AddInterfaceData(
int key, v8::Local<v8::FunctionTemplate> function_template) {
DCHECK(!HasInterfaceData(key));
if (key >= cached_interface_data_.size()) {
cached_interface_data_.resize(key + 1);
}
DCHECK(!HasInterfaceData(key));
cached_interface_data_[key].Set(isolate_, function_template);
}
} // namespace v8c
} // namespace script
} // namespace cobalt