|
| 1 | +// Copyright (c) Microsoft Corporation. All rights reserved. |
| 2 | +// Licensed under the Apache 2.0 License. |
| 3 | +#pragma once |
| 4 | + |
| 5 | +#include "ccf/js/modules/module_loader_interface.h" |
| 6 | +#include "ccf/service/tables/modules.h" |
| 7 | +#include "ccf/tx.h" |
| 8 | +#include "ccf/version.h" |
| 9 | + |
| 10 | +#include <string> |
| 11 | + |
| 12 | +namespace ccf::js::modules |
| 13 | +{ |
| 14 | + class KvBytecodeModuleLoader : public ModuleLoaderInterface |
| 15 | + { |
| 16 | + protected: |
| 17 | + ccf::ModulesQuickJsBytecode::ReadOnlyHandle* modules_bytecode_handle; |
| 18 | + |
| 19 | + bool version_ok; |
| 20 | + |
| 21 | + public: |
| 22 | + KvBytecodeModuleLoader( |
| 23 | + ccf::ModulesQuickJsBytecode::ReadOnlyHandle* mbh, |
| 24 | + ccf::ModulesQuickJsVersion::ReadOnlyHandle* modules_version_handle) : |
| 25 | + modules_bytecode_handle(mbh) |
| 26 | + { |
| 27 | + const auto version_in_kv = modules_version_handle->get(); |
| 28 | + const auto version_in_binary = std::string(ccf::quickjs_version); |
| 29 | + if (version_in_kv != version_in_binary) |
| 30 | + { |
| 31 | + CCF_APP_INFO( |
| 32 | + "Ignoring bytecode table, which was written for QuickJS {} (this " |
| 33 | + "node is running QuickJS {})", |
| 34 | + version_in_kv, |
| 35 | + version_in_binary); |
| 36 | + version_ok = false; |
| 37 | + } |
| 38 | + else |
| 39 | + { |
| 40 | + version_ok = true; |
| 41 | + } |
| 42 | + } |
| 43 | + |
| 44 | + virtual std::optional<js::core::JSWrappedValue> get_module( |
| 45 | + std::string_view module_name, js::core::Context& ctx) override |
| 46 | + { |
| 47 | + if (!version_ok) |
| 48 | + { |
| 49 | + return std::nullopt; |
| 50 | + } |
| 51 | + |
| 52 | + std::string module_name_kv(module_name); |
| 53 | + if (module_name_kv[0] != '/') |
| 54 | + { |
| 55 | + module_name_kv.insert(0, "/"); |
| 56 | + } |
| 57 | + |
| 58 | + CCF_APP_TRACE("Looking for module '{}' bytecode in KV", module_name_kv); |
| 59 | + |
| 60 | + auto module_bytecode = modules_bytecode_handle->get(module_name_kv); |
| 61 | + if (!module_bytecode.has_value()) |
| 62 | + { |
| 63 | + CCF_APP_TRACE("Module '{}' not found", module_name_kv); |
| 64 | + return std::nullopt; |
| 65 | + } |
| 66 | + |
| 67 | + auto module_val = ctx.read_object( |
| 68 | + module_bytecode->data(), module_bytecode->size(), JS_READ_OBJ_BYTECODE); |
| 69 | + |
| 70 | + const bool failed_deser = module_val.is_exception(); |
| 71 | + const bool failed_resolve = |
| 72 | + !failed_deser && (JS_ResolveModule(ctx, module_val.val) < 0); |
| 73 | + |
| 74 | + if (failed_deser || failed_resolve) |
| 75 | + { |
| 76 | + auto [reason, trace] = ctx.error_message(); |
| 77 | + |
| 78 | + auto& rt = ctx.runtime(); |
| 79 | + if (rt.log_exception_details) |
| 80 | + { |
| 81 | + CCF_APP_FAIL("{}: {}", reason, trace.value_or("<no trace>")); |
| 82 | + } |
| 83 | + |
| 84 | + if (failed_deser) |
| 85 | + { |
| 86 | + throw std::runtime_error(fmt::format( |
| 87 | + "Failed to deserialize bytecode for module '{}': {}", |
| 88 | + module_name, |
| 89 | + reason)); |
| 90 | + } |
| 91 | + else |
| 92 | + { |
| 93 | + throw std::runtime_error(fmt::format( |
| 94 | + "Failed to resolve dependencies for module '{}': {}", |
| 95 | + module_name, |
| 96 | + reason)); |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + CCF_APP_TRACE( |
| 101 | + "Module '{}' bytecode found in KV (table: {})", |
| 102 | + module_name_kv, |
| 103 | + modules_bytecode_handle->get_name_of_map()); |
| 104 | + return module_val; |
| 105 | + } |
| 106 | + }; |
| 107 | +} |
0 commit comments