diff --git a/bazel/external/wee8.BUILD b/bazel/external/wee8.BUILD index 2eb6631ed2..4794b9e8f6 100644 --- a/bazel/external/wee8.BUILD +++ b/bazel/external/wee8.BUILD @@ -4,12 +4,9 @@ load(":genrule_cmd.bzl", "genrule_cmd") cc_library( name = "wee8", - srcs = select({ - "@envoy//bazel:windows_x86_64": ["WINDOWS_IS_NOT_SUPPORTED_YET"], - "//conditions:default": [ - "libwee8.a", - ], - }), + srcs = [ + "libwee8.a", + ], hdrs = [ "wee8/third_party/wasm-api/wasm.hh", ], diff --git a/bazel/external/wee8.genrule_cmd b/bazel/external/wee8.genrule_cmd index 24ce12fa8e..00eefe223a 100644 --- a/bazel/external/wee8.genrule_cmd +++ b/bazel/external/wee8.genrule_cmd @@ -33,6 +33,10 @@ export NM=$${NM:-nm} if [[ $${ENVOY_ASAN-} == "1" ]]; then WEE8_BUILD_ARGS+=" is_asan=true" WEE8_BUILD_ARGS+=" is_lsan=true" + WEE8_BUILD_ARGS+=" is_ubsan=true" +fi +if [[ $${ENVOY_UBSAN_VPTR-} == "1" ]]; then + WEE8_BUILD_ARGS+=" is_ubsan_vptr=true" fi if [[ $${ENVOY_MSAN-} == "1" ]]; then WEE8_BUILD_ARGS+=" is_msan=true" diff --git a/bazel/external/wee8.patch b/bazel/external/wee8.patch index 3b16ca7d7c..97f0e14b3b 100644 --- a/bazel/external/wee8.patch +++ b/bazel/external/wee8.patch @@ -1,7 +1,7 @@ # 1. Fix linking with unbundled toolchain on macOS. # 2. Increase VSZ limit to 4TiB (allows us to start up to 370 VMs). ---- a/wee8/build/toolchain/gcc_toolchain.gni -+++ b/wee8/build/toolchain/gcc_toolchain.gni +--- wee8/build/toolchain/gcc_toolchain.gni ++++ wee8/build/toolchain/gcc_toolchain.gni @@ -355,6 +355,8 @@ template("gcc_toolchain") { # AIX does not support either -D (deterministic output) or response # files. @@ -20,8 +20,8 @@ # the "--start-group .. --end-group" feature isn't available on the aix ld. start_group_flag = "-Wl,--start-group" end_group_flag = "-Wl,--end-group " ---- a/wee8/src/wasm/wasm-memory.cc -+++ b/wee8/src/wasm/wasm-memory.cc +--- wee8/src/wasm/wasm-memory.cc ++++ wee8/src/wasm/wasm-memory.cc @@ -142,7 +142,7 @@ void* TryAllocateBackingStore(WasmMemoryTracker* memory_tracker, Heap* heap, // address space limits needs to be smaller. constexpr size_t kAddressSpaceLimit = 0x8000000000L; // 512 GiB diff --git a/bazel/genrule_repository.bzl b/bazel/genrule_repository.bzl index 46b097279c..0689c39c88 100644 --- a/bazel/genrule_repository.bzl +++ b/bazel/genrule_repository.bzl @@ -9,7 +9,7 @@ def _genrule_repository(ctx): for ii, patch in enumerate(ctx.attr.patches): patch_input = "patch-input-%d.patch" % (ii,) ctx.symlink(patch, patch_input) - patch_result = ctx.execute(["patch", "-p1", "--input", patch_input]) + patch_result = ctx.execute(["patch", "-p0", "--input", patch_input]) if patch_result.return_code != 0: fail("Failed to apply patch %r: %s" % (patch, patch_result.stderr)) diff --git a/bazel/setup_clang.sh b/bazel/setup_clang.sh index 3e8d66f2af..2539c2c1e8 100755 --- a/bazel/setup_clang.sh +++ b/bazel/setup_clang.sh @@ -20,6 +20,7 @@ build:clang --action_env=CXX=clang++ build:clang --action_env=LD_LIBRARY_PATH=$(llvm-config --libdir) build:clang --test_env=LD_LIBRARY_PATH=$(llvm-config --libdir) +build:clang-asan --action_env=ENVOY_UBSAN_VPTR=1 build:clang-asan --copt=-fsanitize=vptr,function build:clang-asan --linkopt=-fsanitize=vptr,function build:clang-asan --linkopt=-L${RT_LIBRARY_PATH} diff --git a/source/extensions/common/wasm/v8/BUILD b/source/extensions/common/wasm/v8/BUILD index 8131e80c1d..6287ab08c5 100644 --- a/source/extensions/common/wasm/v8/BUILD +++ b/source/extensions/common/wasm/v8/BUILD @@ -17,8 +17,6 @@ envoy_cc_library( "wee8", ], deps = [ - "//include/envoy/server:wasm_interface", - "//include/envoy/thread_local:thread_local_interface", "//source/common/common:assert_lib", "//source/extensions/common/wasm:wasm_vm_interface", "//source/extensions/common/wasm:well_known_names", diff --git a/source/extensions/common/wasm/v8/v8.cc b/source/extensions/common/wasm/v8/v8.cc index 13541d9916..44e4a46f19 100644 --- a/source/extensions/common/wasm/v8/v8.cc +++ b/source/extensions/common/wasm/v8/v8.cc @@ -24,29 +24,29 @@ wasm::Engine* engine() { } struct FuncData { - FuncData(std::string name) : name(name) {} + FuncData(std::string name) : name_(std::move(name)) {} - std::string name; - wasm::own callback; - void* raw_func; + std::string name_; + wasm::own callback_; + void* raw_func_; }; -typedef std::unique_ptr FuncDataPtr; +using FuncDataPtr = std::unique_ptr; class V8 : public WasmVm { public: V8() = default; // Extensions::Common::Wasm::WasmVm - absl::string_view runtime() override { return WasmRuntimeNames::get().v8; } + absl::string_view runtime() override { return WasmRuntimeNames::get().V8; } bool load(const std::string& code, bool allow_precompiled) override; absl::string_view getCustomSection(absl::string_view name) override; void link(absl::string_view debug_name) override; - // v8 is currently not clonable. + // V8 is currently not cloneable. bool cloneable() override { return false; } - std::unique_ptr clone() override { return nullptr; } + WasmVmPtr clone() override { return nullptr; } uint64_t getMemorySize() override; absl::optional getMemory(uint64_t pointer, uint64_t size) override; @@ -54,16 +54,16 @@ class V8 : public WasmVm { bool getWord(uint64_t pointer, Word* word) override; bool setWord(uint64_t pointer, Word word) override; -#define _REGISTER_HOST_FUNCTION(_T) \ - void registerCallback(absl::string_view module_name, absl::string_view function_name, _T, \ - typename ConvertFunctionTypeWordToUint32<_T>::type f) override { \ +#define _REGISTER_HOST_FUNCTION(T) \ + void registerCallback(absl::string_view module_name, absl::string_view function_name, T, \ + typename ConvertFunctionTypeWordToUint32::type f) override { \ registerHostFunctionImpl(module_name, function_name, f); \ }; FOR_ALL_WASM_VM_IMPORTS(_REGISTER_HOST_FUNCTION) #undef _REGISTER_HOST_FUNCTION -#define _GET_MODULE_FUNCTION(_T) \ - void getFunction(absl::string_view function_name, _T* f) override { \ +#define _GET_MODULE_FUNCTION(T) \ + void getFunction(absl::string_view function_name, T* f) override { \ getModuleFunctionImpl(function_name, f); \ }; FOR_ALL_WASM_VM_EXPORTS(_GET_MODULE_FUNCTION) @@ -194,8 +194,12 @@ static uint32_t parseVarint(const byte_t*& pos, const byte_t* end) { // Template magic. -template struct ConvertWordType { using type = T; }; -template <> struct ConvertWordType { using type = uint32_t; }; +template struct ConvertWordType { + using type = T; // NOLINT(readability-identifier-naming) +}; +template <> struct ConvertWordType { + using type = uint32_t; // NOLINT(readability-identifier-naming) +}; template wasm::Val makeVal(T t) { return wasm::Val::make(t); } template <> wasm::Val makeVal(Word t) { return wasm::Val::make(static_cast(t.u64_)); } @@ -235,7 +239,7 @@ template constexpr T convertValTypesToArgsTuple(const U // V8 implementation. bool V8::load(const std::string& code, bool /* allow_precompiled */) { - ENVOY_LOG(trace, "[wasm] load()"); + ENVOY_LOG(trace, "load()"); store_ = wasm::Store::make(engine()); RELEASE_ASSERT(store_ != nullptr, ""); @@ -247,7 +251,7 @@ bool V8::load(const std::string& code, bool /* allow_precompiled */) { } absl::string_view V8::getCustomSection(absl::string_view name) { - ENVOY_LOG(trace, "[wasm] getCustomSection(\"{}\")", name); + ENVOY_LOG(trace, "getCustomSection(\"{}\")", name); ASSERT(source_.get() != nullptr); const byte_t* end = source_.get() + source_.size(); @@ -270,8 +274,8 @@ absl::string_view V8::getCustomSection(absl::string_view name) { pos += len; rest -= (pos - start); if (len == name.size() && ::memcmp(pos - len, name.data(), len) == 0) { - ENVOY_LOG(trace, "[wasm] getCustomSection(\"{}\") found, size: {}", name, rest); - return absl::string_view(pos, rest); + ENVOY_LOG(trace, "getCustomSection(\"{}\") found, size: {}", name, rest); + return {pos, rest}; } } pos += rest; @@ -280,7 +284,7 @@ absl::string_view V8::getCustomSection(absl::string_view name) { } void V8::link(absl::string_view debug_name) { - ENVOY_LOG(trace, "[wasm] link(\"{}\")", debug_name); + ENVOY_LOG(trace, "link(\"{}\")", debug_name); ASSERT(module_ != nullptr); const auto import_types = module_.get()->imports(); @@ -294,7 +298,7 @@ void V8::link(absl::string_view debug_name) { switch (import_type->kind()) { case wasm::EXTERN_FUNC: { - ENVOY_LOG(trace, "[wasm] link(), export host func: {}.{} ({} -> {})", module, name, + ENVOY_LOG(trace, "link(), export host func: {}.{} ({} -> {})", module, name, printValTypes(import_type->func()->params()), printValTypes(import_type->func()->results())); @@ -303,7 +307,7 @@ void V8::link(absl::string_view debug_name) { throw WasmVmException( fmt::format("Failed to load WASM module due to a missing import: {}.{}", module, name)); } - auto func = it->second.get()->callback.get(); + auto func = it->second.get()->callback_.get(); if (!equalValTypes(import_type->func()->params(), func->type()->params()) || !equalValTypes(import_type->func()->results(), func->type()->results())) { throw WasmVmException(fmt::format( @@ -317,7 +321,8 @@ void V8::link(absl::string_view debug_name) { } break; case wasm::EXTERN_GLOBAL: { - ENVOY_LOG(trace, "[wasm] link(), export host global: {}.{} ({})", module, name, + // TODO(PiotrSikora): add support when/if needed. + ENVOY_LOG(trace, "link(), export host global: {}.{} ({})", module, name, printValKind(import_type->global()->content()->kind())); throw WasmVmException( @@ -325,7 +330,7 @@ void V8::link(absl::string_view debug_name) { } break; case wasm::EXTERN_MEMORY: { - ENVOY_LOG(trace, "[wasm] link(), export host memory: {}.{} (min: {} max: {})", module, name, + ENVOY_LOG(trace, "link(), export host memory: {}.{} (min: {} max: {})", module, name, import_type->memory()->limits().min, import_type->memory()->limits().max); ASSERT(memory_ == nullptr); @@ -335,7 +340,7 @@ void V8::link(absl::string_view debug_name) { } break; case wasm::EXTERN_TABLE: { - ENVOY_LOG(trace, "[wasm] link(), export host table: {}.{} (min: {} max: {})", module, name, + ENVOY_LOG(trace, "link(), export host table: {}.{} (min: {} max: {})", module, name, import_type->table()->limits().min, import_type->table()->limits().max); ASSERT(table_ == nullptr); @@ -366,22 +371,22 @@ void V8::link(absl::string_view debug_name) { switch (export_type->kind()) { case wasm::EXTERN_FUNC: { - ENVOY_LOG(trace, "[wasm] link(), import module func: {} ({} -> {})", name, + ENVOY_LOG(trace, "link(), import module func: {} ({} -> {})", name, printValTypes(export_type->func()->params()), printValTypes(export_type->func()->results())); ASSERT(export_item->func() != nullptr); - module_functions_.emplace(name, export_item->func()->copy()); + module_functions_.insert_or_assign(name, export_item->func()->copy()); } break; case wasm::EXTERN_GLOBAL: { // TODO(PiotrSikora): add support when/if needed. - ENVOY_LOG(trace, "[wasm] link(), import module global: {} ({}) --- IGNORED", name, + ENVOY_LOG(trace, "link(), import module global: {} ({}) --- IGNORED", name, printValKind(export_type->global()->content()->kind())); } break; case wasm::EXTERN_MEMORY: { - ENVOY_LOG(trace, "[wasm] link(), import module memory: {} (min: {} max: {})", name, + ENVOY_LOG(trace, "link(), import module memory: {} (min: {} max: {})", name, export_type->memory()->limits().min, export_type->memory()->limits().max); ASSERT(export_item->memory() != nullptr); @@ -391,7 +396,7 @@ void V8::link(absl::string_view debug_name) { case wasm::EXTERN_TABLE: { // TODO(PiotrSikora): add support when/if needed. - ENVOY_LOG(trace, "[wasm] link(), import module table: {} (min: {} max: {}) --- IGNORED", name, + ENVOY_LOG(trace, "link(), import module table: {} (min: {} max: {}) --- IGNORED", name, export_type->table()->limits().min, export_type->table()->limits().max); } break; } @@ -399,12 +404,12 @@ void V8::link(absl::string_view debug_name) { } uint64_t V8::getMemorySize() { - ENVOY_LOG(trace, "[wasm] getMemorySize()"); + ENVOY_LOG(trace, "getMemorySize()"); return memory_->data_size(); } absl::optional V8::getMemory(uint64_t pointer, uint64_t size) { - ENVOY_LOG(trace, "[wasm] getMemory({}, {})", pointer, size); + ENVOY_LOG(trace, "getMemory({}, {})", pointer, size); ASSERT(memory_ != nullptr); if (pointer + size > memory_->data_size()) { return absl::nullopt; @@ -413,7 +418,7 @@ absl::optional V8::getMemory(uint64_t pointer, uint64_t size) } bool V8::setMemory(uint64_t pointer, uint64_t size, const void* data) { - ENVOY_LOG(trace, "[wasm] setMemory({}, {})", pointer, size); + ENVOY_LOG(trace, "setMemory({}, {})", pointer, size); ASSERT(memory_ != nullptr); if (pointer + size > memory_->data_size()) { return false; @@ -423,8 +428,8 @@ bool V8::setMemory(uint64_t pointer, uint64_t size, const void* data) { } bool V8::getWord(uint64_t pointer, Word* word) { - ENVOY_LOG(trace, "[wasm] getWord({})", pointer); - auto size = sizeof(uint32_t); + ENVOY_LOG(trace, "getWord({})", pointer); + constexpr auto size = sizeof(uint32_t); if (pointer + size > memory_->data_size()) { return false; } @@ -435,8 +440,8 @@ bool V8::getWord(uint64_t pointer, Word* word) { } bool V8::setWord(uint64_t pointer, Word word) { - ENVOY_LOG(trace, "[wasm] setWord({}, {})", pointer, word.u64_); - auto size = sizeof(uint32_t); + ENVOY_LOG(trace, "setWord({}, {})", pointer, word.u64_); + constexpr auto size = sizeof(uint32_t); if (pointer + size > memory_->data_size()) { return false; } @@ -448,7 +453,7 @@ bool V8::setWord(uint64_t pointer, Word word) { template void V8::registerHostFunctionImpl(absl::string_view module_name, absl::string_view function_name, void (*function)(void*, Args...)) { - ENVOY_LOG(trace, "[wasm] registerHostFunction(\"{}.{}\")", module_name, function_name); + ENVOY_LOG(trace, "registerHostFunction(\"{}.{}\")", module_name, function_name); auto data = std::make_unique(absl::StrCat(module_name, ".", function_name)); auto type = wasm::FuncType::make(convertArgsTupleToValTypes>(), convertArgsTupleToValTypes>()); @@ -456,25 +461,25 @@ void V8::registerHostFunctionImpl(absl::string_view module_name, absl::string_vi store_.get(), type.get(), [](void* data, const wasm::Val params[], wasm::Val[]) -> wasm::own { auto func_data = reinterpret_cast(data); - ENVOY_LOG(trace, "[wasm] [vm->host] {}({})", func_data->name, + ENVOY_LOG(trace, "[vm->host] {}({})", func_data->name_, printValues(params, std::tuple_size>::value)); auto args_tuple = convertValTypesToArgsTuple>(params); auto args = std::tuple_cat(std::make_tuple(current_context_), args_tuple); - auto function = reinterpret_cast(func_data->raw_func); + auto function = reinterpret_cast(func_data->raw_func_); absl::apply(function, args); - ENVOY_LOG(trace, "[wasm] [vm<-host] {} return: void", func_data->name); + ENVOY_LOG(trace, "[vm<-host] {} return: void", func_data->name_); return nullptr; }, data.get()); - data.get()->callback = std::move(func); - data.get()->raw_func = reinterpret_cast(function); - host_functions_.emplace(absl::StrCat(module_name, ".", function_name), std::move(data)); + data->callback_ = std::move(func); + data->raw_func_ = reinterpret_cast(function); + host_functions_.insert_or_assign(absl::StrCat(module_name, ".", function_name), std::move(data)); } template void V8::registerHostFunctionImpl(absl::string_view module_name, absl::string_view function_name, R (*function)(void*, Args...)) { - ENVOY_LOG(trace, "[wasm] registerHostFunction(\"{}.{}\")", module_name, function_name); + ENVOY_LOG(trace, "registerHostFunction(\"{}.{}\")", module_name, function_name); auto data = std::make_unique(absl::StrCat(module_name, ".", function_name)); auto type = wasm::FuncType::make(convertArgsTupleToValTypes>(), convertArgsTupleToValTypes>()); @@ -482,26 +487,26 @@ void V8::registerHostFunctionImpl(absl::string_view module_name, absl::string_vi store_.get(), type.get(), [](void* data, const wasm::Val params[], wasm::Val results[]) -> wasm::own { auto func_data = reinterpret_cast(data); - ENVOY_LOG(trace, "[wasm] [vm->host] {}({})", func_data->name, + ENVOY_LOG(trace, "[vm->host] {}({})", func_data->name_, printValues(params, sizeof...(Args))); auto args_tuple = convertValTypesToArgsTuple>(params); auto args = std::tuple_cat(std::make_tuple(current_context_), args_tuple); - auto function = reinterpret_cast(func_data->raw_func); + auto function = reinterpret_cast(func_data->raw_func_); R rvalue = absl::apply(function, args); results[0] = makeVal(rvalue); - ENVOY_LOG(trace, "[wasm] [vm<-host] {} return: {}", func_data->name, rvalue); + ENVOY_LOG(trace, "[vm<-host] {} return: {}", func_data->name_, rvalue); return nullptr; }, data.get()); - data.get()->callback = std::move(func); - data.get()->raw_func = reinterpret_cast(function); - host_functions_.emplace(absl::StrCat(module_name, ".", function_name), std::move(data)); + data->callback_ = std::move(func); + data->raw_func_ = reinterpret_cast(function); + host_functions_.insert_or_assign(absl::StrCat(module_name, ".", function_name), std::move(data)); } template void V8::getModuleFunctionImpl(absl::string_view function_name, std::function* function) { - ENVOY_LOG(trace, "[wasm] getModuleFunction(\"{}\")", function_name); + ENVOY_LOG(trace, "getModuleFunction(\"{}\")", function_name); auto it = module_functions_.find(function_name); if (it == module_functions_.end()) { *function = nullptr; @@ -514,23 +519,22 @@ void V8::getModuleFunctionImpl(absl::string_view function_name, } *function = [func, function_name](Context* context, Args... args) -> void { wasm::Val params[] = {makeVal(args)...}; - ENVOY_LOG(trace, "[wasm] [host->vm] {}({})", function_name, - printValues(params, sizeof...(Args))); - SaveRestoreContext _saved_context(context); + ENVOY_LOG(trace, "[host->vm] {}({})", function_name, printValues(params, sizeof...(Args))); + SaveRestoreContext saved_context(context); auto trap = func->call(params, nullptr); if (trap) { throw WasmException( fmt::format("Function: {} failed: {}", function_name, absl::string_view(trap->message().get(), trap->message().size()))); } - ENVOY_LOG(trace, "[wasm] [host<-vm] {} return: void", function_name); + ENVOY_LOG(trace, "[host<-vm] {} return: void", function_name); }; } template void V8::getModuleFunctionImpl(absl::string_view function_name, std::function* function) { - ENVOY_LOG(trace, "[wasm] getModuleFunction(\"{}\")", function_name); + ENVOY_LOG(trace, "getModuleFunction(\"{}\")", function_name); auto it = module_functions_.find(function_name); if (it == module_functions_.end()) { *function = nullptr; @@ -543,10 +547,9 @@ void V8::getModuleFunctionImpl(absl::string_view function_name, } *function = [func, function_name](Context* context, Args... args) -> R { wasm::Val params[] = {makeVal(args)...}; - ENVOY_LOG(trace, "[wasm] [host->vm] {}({})", function_name, - printValues(params, sizeof...(Args))); - SaveRestoreContext _saved_context(context); wasm::Val results[1]; + ENVOY_LOG(trace, "[host->vm] {}({})", function_name, printValues(params, sizeof...(Args))); + SaveRestoreContext saved_context(context); auto trap = func->call(params, results); if (trap) { throw WasmException( @@ -554,7 +557,7 @@ void V8::getModuleFunctionImpl(absl::string_view function_name, absl::string_view(trap->message().get(), trap->message().size()))); } R rvalue = results[0].get::type>(); - ENVOY_LOG(trace, "[wasm] [host<-vm] {} return: {}", function_name, rvalue); + ENVOY_LOG(trace, "[host<-vm] {} return: {}", function_name, rvalue); return rvalue; }; } diff --git a/source/extensions/common/wasm/v8/v8.h b/source/extensions/common/wasm/v8/v8.h index b26d52646d..3650f190a7 100644 --- a/source/extensions/common/wasm/v8/v8.h +++ b/source/extensions/common/wasm/v8/v8.h @@ -10,7 +10,7 @@ namespace Common { namespace Wasm { namespace V8 { -std::unique_ptr createVm(); +WasmVmPtr createVm(); } // namespace V8 } // namespace Wasm diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index 7fefec710a..a90a236b87 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -73,25 +73,6 @@ std::string base64Sha256(absl::string_view data) { inline Word wasmResultToWord(WasmResult r) { return Word(static_cast(r)); } -inline uint32_t convertWordToUint32(Word w) { return static_cast(w.u64_); } - -// Convert a function of the form Word(Word...) to one of the form uint32_t(uint32_t...). -template struct ConvertFunctionWordToUint32 { - static void convertFunctionWordToUint32() {} -}; -template R> -struct ConvertFunctionWordToUint32 { - static auto convertFunctionWordToUint32(typename ConvertWordTypeToUint32::type... args) { - return convertWordToUint32(F(std::forward(args)...)); - } -}; -template void> -struct ConvertFunctionWordToUint32 { - static void convertFunctionWordToUint32(typename ConvertWordTypeToUint32::type... args) { - F(std::forward(args)...); - } -}; - class SharedData { public: WasmResult get(absl::string_view vm_id, const absl::string_view key, diff --git a/source/extensions/common/wasm/wasm_vm.cc b/source/extensions/common/wasm/wasm_vm.cc index 953e0b4fa6..1bc300e11c 100644 --- a/source/extensions/common/wasm/wasm_vm.cc +++ b/source/extensions/common/wasm/wasm_vm.cc @@ -27,7 +27,7 @@ WasmVmPtr createWasmVm(absl::string_view runtime) { return Null::createVm(); } else #ifdef ENVOY_WASM_V8 - if (runtime == WasmRuntimeNames::get().v8) { + if (runtime == WasmRuntimeNames::get().V8) { return V8::createVm(); } else #endif diff --git a/source/extensions/common/wasm/wasm_vm.h b/source/extensions/common/wasm/wasm_vm.h index 7aee9a542e..5c12156bde 100644 --- a/source/extensions/common/wasm/wasm_vm.h +++ b/source/extensions/common/wasm/wasm_vm.h @@ -40,6 +40,30 @@ template struct ConvertFunctionTypeWordToUint32::type...); }; +template inline auto convertWordToUint32(T t) { return t; } +template <> inline auto convertWordToUint32(Word t) { return static_cast(t.u64_); } + +// Convert a function of the form Word(Word...) to one of the form uint32_t(uint32_t...). +template struct ConvertFunctionWordToUint32 { + static void convertFunctionWordToUint32() {} +}; +template R> +struct ConvertFunctionWordToUint32 { + static typename ConvertWordTypeToUint32::type + convertFunctionWordToUint32(typename ConvertWordTypeToUint32::type... args) { + return convertWordToUint32(F(std::forward(args)...)); + } +}; +template void> +struct ConvertFunctionWordToUint32 { + static void convertFunctionWordToUint32(typename ConvertWordTypeToUint32::type... args) { + F(std::forward(args)...); + } +}; + +#define CONVERT_FUNCTION_WORD_TO_UINT32(_f) \ + &ConvertFunctionWordToUint32::convertFunctionWordToUint32 + // These are templates and its helper for constructing signatures of functions calling into and out // of WASM VMs. // - WasmFuncTypeHelper is a helper for WasmFuncType and shouldn't be used anywhere else than diff --git a/source/extensions/common/wasm/well_known_names.h b/source/extensions/common/wasm/well_known_names.h index 283908935e..5fb8602bf8 100644 --- a/source/extensions/common/wasm/well_known_names.h +++ b/source/extensions/common/wasm/well_known_names.h @@ -15,13 +15,13 @@ namespace Wasm { */ class WasmRuntimeValues { public: - // V8 (https://v8.dev) WASM VM. - const std::string v8 = "envoy.wasm.runtime.v8"; // WAVM (https://github.com/WAVM/WAVM) Wasm VM. const std::string Wavm = "envoy.wasm.runtime.wavm"; // Null sandbox: modules must be compiled into envoy and registered name is given in the // DataSource.inline_string. const std::string Null = "envoy.wasm.runtime.null"; + // V8-based (https://v8.dev) WebAssembly runtime. + const std::string V8 = "envoy.wasm.runtime.v8"; // Filter state name const std::string FilterState = "envoy.wasm"; diff --git a/test/extensions/common/wasm/BUILD b/test/extensions/common/wasm/BUILD index 490402dc17..bdc4969b14 100644 --- a/test/extensions/common/wasm/BUILD +++ b/test/extensions/common/wasm/BUILD @@ -3,7 +3,6 @@ licenses(["notice"]) # Apache 2 load( "//bazel:envoy_build_system.bzl", "envoy_cc_test", - "envoy_cc_test_library", "envoy_package", ) @@ -12,8 +11,12 @@ envoy_package() envoy_cc_test( name = "wasm_vm_test", srcs = ["wasm_vm_test.cc"], + data = [ + "//test/extensions/common/wasm/test_data:modules", + ], deps = [ "//source/extensions/common/wasm:wasm_vm_lib", + "//test/test_common:environment_lib", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/common/wasm/test_data/BUILD b/test/extensions/common/wasm/test_data/BUILD new file mode 100644 index 0000000000..ef4f373862 --- /dev/null +++ b/test/extensions/common/wasm/test_data/BUILD @@ -0,0 +1,13 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) + +envoy_package() + +filegroup( + name = "modules", + srcs = glob(["*.wasm"]), +) diff --git a/test/extensions/common/wasm/test_data/test_rust.rs b/test/extensions/common/wasm/test_data/test_rust.rs new file mode 100644 index 0000000000..304161e506 --- /dev/null +++ b/test/extensions/common/wasm/test_data/test_rust.rs @@ -0,0 +1,33 @@ +// Build using: +// $ rustc -C lto -C opt-level=3 -C panic=abort -C link-arg=-S -C link-arg=-zstack-size=32768 --crate-type cdylib --target wasm32-unknown-unknown test_rust.rs + +// Import functions exported from the host environment. +extern "C" { + fn pong(value: u32); + fn random() -> u32; +} + +#[no_mangle] +extern "C" fn ping(value: u32) { + unsafe { pong(value) } +} + +#[no_mangle] +extern "C" fn lucky(number: u32) -> bool { + unsafe { number == random() } +} + +#[no_mangle] +extern "C" fn sum(a: u32, b: u32, c: u32) -> u32 { + a + b + c +} + +#[no_mangle] +extern "C" fn div(a: u32, b: u32) -> u32 { + a / b +} + +#[no_mangle] +extern "C" fn abort() { + panic!("abort") +} diff --git a/test/extensions/common/wasm/test_data/test_rust.wasm b/test/extensions/common/wasm/test_data/test_rust.wasm new file mode 100644 index 0000000000..e7b1ce902a Binary files /dev/null and b/test/extensions/common/wasm/test_data/test_rust.wasm differ diff --git a/test/extensions/common/wasm/wasm_vm_test.cc b/test/extensions/common/wasm/wasm_vm_test.cc index 81df913cc9..a3920b69ab 100644 --- a/test/extensions/common/wasm/wasm_vm_test.cc +++ b/test/extensions/common/wasm/wasm_vm_test.cc @@ -3,11 +3,15 @@ #include "extensions/common/wasm/null/null_vm_plugin.h" #include "extensions/common/wasm/wasm_vm.h" +#include "test/test_common/environment.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::HasSubstr; +using testing::Return; + namespace Envoy { namespace Extensions { namespace Common { @@ -39,18 +43,18 @@ std::unique_ptr PluginFactory::create() const { return result; } -TEST(WasmVmTest, NoRuntime) { +TEST(BadVmTest, NoRuntime) { EXPECT_THROW_WITH_MESSAGE(createWasmVm(""), WasmVmException, "Failed to create WASM VM with unspecified runtime."); } -TEST(WasmVmTest, BadRuntime) { +TEST(BadVmTest, BadRuntime) { EXPECT_THROW_WITH_MESSAGE(createWasmVm("envoy.wasm.runtime.invalid"), WasmVmException, "Failed to create WASM VM using envoy.wasm.runtime.invalid runtime. " "Envoy was compiled without support for it."); } -TEST(WasmVmTest, NullVmStartup) { +TEST(NullVmTest, NullVmStartup) { auto wasm_vm = createWasmVm("envoy.wasm.runtime.null"); EXPECT_TRUE(wasm_vm != nullptr); EXPECT_TRUE(wasm_vm->runtime() == "envoy.wasm.runtime.null"); @@ -60,7 +64,7 @@ TEST(WasmVmTest, NullVmStartup) { EXPECT_TRUE(wasm_vm->getCustomSection("user").empty()); } -TEST(WasmVmTest, NullVmMemory) { +TEST(NullVmTest, NullVmMemory) { auto wasm_vm = createWasmVm("envoy.wasm.runtime.null"); EXPECT_EQ(wasm_vm->getMemorySize(), std::numeric_limits::max()); std::string d = "data"; @@ -89,6 +93,184 @@ TEST(WasmVmTest, NullVmMemory) { EXPECT_FALSE(wasm_vm->getWord(0 /* nullptr */, &w2)); } +class MockHostFunctions { +public: + MOCK_CONST_METHOD1(pong, void(uint32_t)); + MOCK_CONST_METHOD0(random, uint32_t()); +}; + +MockHostFunctions* g_host_functions; + +void pong(void*, Word value) { g_host_functions->pong(convertWordToUint32(value)); } + +Word random(void*) { return Word(g_host_functions->random()); } + +// pong() with wrong number of arguments. +void bad_pong1(void*) { return; } + +// pong() with wrong return type. +Word bad_pong2(void*, Word) { return 2; } + +// pong() with wrong argument type. +double bad_pong3(void*, double) { return 3; } + +class WasmVmTest : public testing::Test { +public: + void SetUp() override { g_host_functions = new MockHostFunctions(); } + void TearDown() override { delete g_host_functions; } +}; + +TEST_F(WasmVmTest, V8BadCode) { + auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8"); + ASSERT_TRUE(wasm_vm != nullptr); + + EXPECT_FALSE(wasm_vm->load("bad code", false)); +} + +TEST_F(WasmVmTest, V8Code) { + auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8"); + ASSERT_TRUE(wasm_vm != nullptr); + + EXPECT_TRUE(wasm_vm->runtime() == "envoy.wasm.runtime.v8"); + EXPECT_FALSE(wasm_vm->cloneable()); + EXPECT_TRUE(wasm_vm->clone() == nullptr); + + auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_rust.wasm")); + EXPECT_TRUE(wasm_vm->load(code, false)); + + EXPECT_THAT(wasm_vm->getCustomSection("producers"), HasSubstr("rustc")); + EXPECT_TRUE(wasm_vm->getCustomSection("emscripten_metadata").empty()); +} + +TEST_F(WasmVmTest, V8BadHostFunctions) { + auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8"); + ASSERT_TRUE(wasm_vm != nullptr); + + auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_rust.wasm")); + EXPECT_TRUE(wasm_vm->load(code, false)); + + wasm_vm->registerCallback("env", "random", &random, CONVERT_FUNCTION_WORD_TO_UINT32(random)); + EXPECT_THROW_WITH_MESSAGE(wasm_vm->link("test"), WasmVmException, + "Failed to load WASM module due to a missing import: env.pong"); + + wasm_vm->registerCallback("env", "pong", &bad_pong1, CONVERT_FUNCTION_WORD_TO_UINT32(bad_pong1)); + EXPECT_THROW_WITH_MESSAGE(wasm_vm->link("test"), WasmVmException, + "Failed to load WASM module due to an import type mismatch: env.pong, " + "want: i32 -> void, but host exports: void -> void"); + + wasm_vm->registerCallback("env", "pong", &bad_pong2, CONVERT_FUNCTION_WORD_TO_UINT32(bad_pong2)); + EXPECT_THROW_WITH_MESSAGE(wasm_vm->link("test"), WasmVmException, + "Failed to load WASM module due to an import type mismatch: env.pong, " + "want: i32 -> void, but host exports: i32 -> i32"); + + wasm_vm->registerCallback("env", "pong", &bad_pong3, CONVERT_FUNCTION_WORD_TO_UINT32(bad_pong3)); + EXPECT_THROW_WITH_MESSAGE(wasm_vm->link("test"), WasmVmException, + "Failed to load WASM module due to an import type mismatch: env.pong, " + "want: i32 -> void, but host exports: f64 -> f64"); +} + +TEST_F(WasmVmTest, V8BadModuleFunctions) { + auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8"); + ASSERT_TRUE(wasm_vm != nullptr); + + auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_rust.wasm")); + EXPECT_TRUE(wasm_vm->load(code, false)); + + wasm_vm->registerCallback("env", "pong", &pong, CONVERT_FUNCTION_WORD_TO_UINT32(pong)); + wasm_vm->registerCallback("env", "random", &random, CONVERT_FUNCTION_WORD_TO_UINT32(random)); + wasm_vm->link("test"); + + WasmCallVoid<1> ping; + WasmCallWord<3> sum; + + wasm_vm->getFunction("nonexistent", &ping); + EXPECT_TRUE(ping == nullptr); + + wasm_vm->getFunction("nonexistent", &sum); + EXPECT_TRUE(sum == nullptr); + + EXPECT_THROW_WITH_MESSAGE(wasm_vm->getFunction("ping", &sum), WasmVmException, + "Bad function signature for: ping"); + + EXPECT_THROW_WITH_MESSAGE(wasm_vm->getFunction("sum", &ping), WasmVmException, + "Bad function signature for: sum"); +} + +TEST_F(WasmVmTest, V8FunctionCalls) { + auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8"); + ASSERT_TRUE(wasm_vm != nullptr); + + auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_rust.wasm")); + EXPECT_TRUE(wasm_vm->load(code, false)); + + wasm_vm->registerCallback("env", "pong", &pong, CONVERT_FUNCTION_WORD_TO_UINT32(pong)); + wasm_vm->registerCallback("env", "random", &random, CONVERT_FUNCTION_WORD_TO_UINT32(random)); + wasm_vm->link("test"); + + WasmCallVoid<1> ping; + wasm_vm->getFunction("ping", &ping); + EXPECT_CALL(*g_host_functions, pong(42)); + ping(nullptr /* no context */, 42); + + WasmCallWord<1> lucky; + wasm_vm->getFunction("lucky", &lucky); + EXPECT_CALL(*g_host_functions, random()).WillRepeatedly(Return(42)); + EXPECT_EQ(0, lucky(nullptr /* no context */, 1).u64_); + EXPECT_EQ(1, lucky(nullptr /* no context */, 42).u64_); + + WasmCallWord<3> sum; + wasm_vm->getFunction("sum", &sum); + EXPECT_EQ(42, sum(nullptr /* no context */, 13, 14, 15).u64_); + + WasmCallWord<2> div; + wasm_vm->getFunction("div", &div); + EXPECT_THROW_WITH_MESSAGE(div(nullptr /* no context */, 42, 0), WasmException, + "Function: div failed: Uncaught RuntimeError: unreachable"); + + WasmCallVoid<0> abort; + wasm_vm->getFunction("abort", &abort); + EXPECT_THROW_WITH_MESSAGE(abort(nullptr /* no context */), WasmException, + "Function: abort failed: Uncaught RuntimeError: unreachable"); +} + +TEST_F(WasmVmTest, V8Memory) { + auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8"); + ASSERT_TRUE(wasm_vm != nullptr); + + auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_rust.wasm")); + EXPECT_TRUE(wasm_vm->load(code, false)); + + wasm_vm->registerCallback("env", "pong", &pong, CONVERT_FUNCTION_WORD_TO_UINT32(pong)); + wasm_vm->registerCallback("env", "random", &random, CONVERT_FUNCTION_WORD_TO_UINT32(random)); + wasm_vm->link("test"); + + EXPECT_EQ(wasm_vm->getMemorySize(), 65536 /* stack size requested at the build-time */); + + const uint64_t test_addr = 128; + + std::string set = "test"; + EXPECT_TRUE(wasm_vm->setMemory(test_addr, set.size(), set.data())); + auto got = wasm_vm->getMemory(test_addr, set.size()).value(); + EXPECT_EQ(sizeof("test") - 1, got.size()); + EXPECT_STREQ("test", got.data()); + + EXPECT_FALSE(wasm_vm->setMemory(1024 * 1024 /* out of bound */, 1 /* size */, nullptr)); + EXPECT_FALSE(wasm_vm->getMemory(1024 * 1024 /* out of bound */, 1 /* size */).has_value()); + + Word word(0); + EXPECT_TRUE(wasm_vm->setWord(test_addr, std::numeric_limits::max())); + EXPECT_TRUE(wasm_vm->getWord(test_addr, &word)); + EXPECT_EQ(std::numeric_limits::max(), word.u64_); + + EXPECT_FALSE(wasm_vm->setWord(1024 * 1024 /* out of bound */, 1)); + EXPECT_FALSE(wasm_vm->getWord(1024 * 1024 /* out of bound */, &word)); +} + } // namespace } // namespace Wasm } // namespace Common