| // 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 |