blob: 7f25e0f7503bcdd99ad19058935daef2a7355d43 [file] [log] [blame] [edit]
// Copyright 2019 The Fuchsia 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 "runtime.h"
#include <lib/syslog/cpp/log_level.h>
#include <lib/syslog/cpp/macros.h>
#include <string.h>
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <vector>
#include "src/developer/shell/josh/lib/fdio.h"
#include "src/developer/shell/josh/lib/fidl.h"
#include "src/developer/shell/josh/lib/fxlog.h"
#include "src/developer/shell/josh/lib/zx.h"
#include "third_party/quickjs/quickjs-libc.h"
#include "third_party/quickjs/quickjs.h"
#include "third_party/rapidjson/include/rapidjson/document.h"
namespace fs = std::filesystem;
// This is present and needs to be called in the most recent version of quickjs.
// It's only here so that we can run against both the old and new version until
// we integrate the new version.
__attribute__((weak)) void js_std_init_handlers(JSRuntime* rt) {}
namespace shell {
Runtime::Runtime() : has_error_(false) {
rt_ = JS_NewRuntime();
js_std_init_handlers(rt_);
is_valid_ = (rt_ == nullptr);
// The correct loader for ES6 modules. Not sure why this has to be done by hand.
JS_SetModuleLoaderFunc(rt_, nullptr, js_module_loader, nullptr);
}
Runtime::~Runtime() {
if (is_valid_) {
js_std_free_handlers(rt_);
JS_FreeRuntime(rt_);
}
}
void Runtime::HandlePromiseRejection(JSContext* ctx, JSValueConst promise, JSValueConst reason) {
// Currently there is no additional handling of promise rejections
has_error_ = true;
Value rv(ctx, reason);
std::cerr << "Unhandled promise rejection: " << rv;
}
Context::Context(const Runtime* rt) : ctx_(JS_NewContext(rt->Get())) {
is_valid_ = (ctx_ == nullptr);
#if __has_feature(address_sanitizer)
// ASan tends to exceed the max stack size of 256K.
JS_SetMaxStackSize(rt->Get(), 1024 * 1024);
#endif // __has_feature(address_sanitizer)
}
Context::~Context() {
if (is_valid_) {
JS_FreeContext(ctx_);
}
}
bool Context::ExportScript(const std::string& lib, const std::string& js_path) {
std::string path;
int flags = JS_EVAL_TYPE_MODULE;
if (!js_path.empty()) {
path = js_path;
} else {
path = lib;
flags |= JS_EVAL_FLAG_COMPILE_ONLY;
}
std::string init_str = "import * as " + lib + " from '" + path +
"';\n"
"globalThis." +
lib + " = " + lib + ";\n";
JSValue init_compile = JS_Eval(ctx_, init_str.c_str(), init_str.length(), "<input>", flags);
if (JS_IsException(init_compile)) {
js_std_dump_error(ctx_);
return false;
}
if (js_path.empty()) {
js_module_set_import_meta(ctx_, init_compile, 1, 1);
JSValue init_run = JS_EvalFunction(ctx_, init_compile);
if (JS_IsException(init_run)) {
js_std_dump_error(ctx_);
return false;
}
}
return true;
}
bool Context::Export(const std::string& lib, const std::string& js_path) {
if (js_path.empty()) {
return ExportScript(lib);
}
std::string path = js_path;
if (path[path.length() - 1] != '/') {
path.append("/");
}
path.append(lib);
path.append(".js");
return ExportScript(lib, path);
}
bool Context::InitStd() {
// System modules
js_init_module_std(ctx_, "std");
if (!Export("std")) {
return false;
}
js_init_module_os(ctx_, "os");
if (!Export("os")) {
return false;
}
return true;
}
extern "C" const uint8_t qjsc_fidl[];
extern "C" const uint32_t qjsc_fidl_size;
extern "C" const uint8_t qjsc_fdio[];
extern "C" const uint32_t qjsc_fdio_size;
extern "C" const uint8_t qjsc_zx[];
extern "C" const uint32_t qjsc_zx_size;
extern "C" const uint8_t qjsc_fxlog[];
extern "C" const uint32_t qjsc_fxlog_size;
bool Context::InitBuiltins(const std::string& fidl_path, const std::string& boot_js_path) {
if (fxlog::FxLogModuleInit(ctx_, "fxlog_internal") == nullptr) {
return false;
}
js_std_eval_binary(ctx_, qjsc_fxlog, qjsc_fxlog_size, 0);
if (fdio::FdioModuleInit(ctx_, "fdio") == nullptr) {
return false;
}
if (!Export("fdio")) {
return false;
}
if (fidl::FidlModuleInit(ctx_, "fidl_internal", fidl_path) == nullptr) {
return false;
}
js_std_eval_binary(ctx_, qjsc_fidl, qjsc_fidl_size, 0);
if (zx::ZxModuleInit(ctx_, "zx_internal") == nullptr) {
return false;
}
js_std_eval_binary(ctx_, qjsc_zx, qjsc_zx_size, 0);
if (!boot_js_path.empty()) {
constexpr std::array modules{
"pp",
"util",
"ns",
};
for (auto& module : modules) {
if (!Export(std::string(module), boot_js_path)) {
return false;
}
}
}
return true;
}
bool Context::InitStartups(const std::string& startup_js_path) {
if (!fs::is_directory(startup_js_path)) {
return false;
}
fs::path sequence_file = fs::path(startup_js_path) / DEFAULT_SEQUENCE_JSON_FILENAME;
if (!fs::is_regular_file(sequence_file)) {
// No sequence.json means nothing to start, which is acceptable
return true;
}
std::ifstream f_seq(sequence_file.string());
std::stringstream buffer;
buffer << f_seq.rdbuf();
f_seq.close();
rapidjson::Document seq;
seq.Parse(buffer.str());
if (!seq.IsObject()) {
FX_LOGS(ERROR) << "Failed to parse sequence file " << sequence_file;
return false;
}
if (seq.HasMember("startup")) {
const rapidjson::Value& seq_startup = seq["startup"];
if (!seq_startup.IsArray()) {
FX_LOGS(ERROR) << "The 'startup' field needs to be an array";
return false;
}
// Start loading scripts inside startup_js_path based on what is specified
// in "startup array"
for (rapidjson::SizeType i = 0; i < seq_startup.Size(); i++) {
if (!seq_startup[i].IsString()) {
FX_LOGS(ERROR) << "Wrong type at entry " << i;
return false;
}
fs::path module_path(startup_js_path);
module_path.append(seq_startup[i].GetString());
if (!fs::is_regular_file(module_path)) {
FX_LOGS(ERROR) << "The module script " << module_path << " does not exist";
return false;
}
std::string module_name = module_path.filename().replace_extension("");
// If the starting chars are digists, strip them because js modules cannot
// start with digits
if (!(isupper(module_name[0]) || islower(module_name[0]) || module_name[0] == '_')) {
FX_LOGS(ERROR) << "Module name " << module_name
<< " is invalid, can only start with characters or '_'" << module_path;
return false;
}
// Will not continue if any boot script failed to load.
if (!ExportScript(module_name, module_path.string())) {
FX_LOGS(ERROR) << "Error occurred while exporting module " << module_path;
return false;
}
}
}
return true;
}
std::ostream& operator<<(std::ostream& os, const Value& value) {
os << value.ToString();
return os;
}
std::string Value::RawValueToString() const {
std::string result;
const char* str = JS_ToCString(ctx_, val_);
if (str) {
result += str;
JS_FreeCString(ctx_, str);
} else {
result += "[unknown object]";
}
return result;
}
std::string Value::ToString() const {
std::string&& result = RawValueToString();
if (JS_IsError(ctx_, val_)) {
JSValue prop_string = JS_GetPropertyStr(ctx_, val_, "stack");
if (!JS_IsUndefined(prop_string)) {
Value ps(ctx_, prop_string);
result += ps.RawValueToString();
}
JS_FreeValue(ctx_, prop_string);
}
return result;
}
} // namespace shell