diff --git a/bazel/external/proxy_wasm_cpp_host.BUILD b/bazel/external/proxy_wasm_cpp_host.BUILD new file mode 100644 index 0000000000000..a6ff90cdec0c9 --- /dev/null +++ b/bazel/external/proxy_wasm_cpp_host.BUILD @@ -0,0 +1,54 @@ +licenses(["notice"]) # Apache 2 + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "include", + hdrs = [ + "include/proxy-wasm/compat.h", + "include/proxy-wasm/context.h", + "include/proxy-wasm/exports.h", + "include/proxy-wasm/null.h", + "include/proxy-wasm/null_plugin.h", + "include/proxy-wasm/null_vm.h", + "include/proxy-wasm/null_vm_plugin.h", + "include/proxy-wasm/v8.h", + "include/proxy-wasm/wasm.h", + "include/proxy-wasm/wasm_api_impl.h", + "include/proxy-wasm/wasm_vm.h", + "include/proxy-wasm/word.h", + ], + copts = ["-std=c++14"], + deps = [ + "@proxy_wasm_cpp_sdk//:common_lib", + ], +) + +cc_library( + name = "lib", + srcs = [ + "src/base64.cc", + "src/base64.h", + "src/context.cc", + "src/exports.cc", + "src/foreign.cc", + "src/null/null.cc", + "src/null/null_plugin.cc", + "src/null/null_vm.cc", + "src/v8/v8.cc", + "src/wasm.cc", + ], + copts = ["-std=c++14"], + deps = [ + ":include", + "//external:abseil_flat_hash_map", + "//external:abseil_optional", + "//external:abseil_strings", + "//external:protobuf", + "//external:wee8", + "//external:zlib", + "@boringssl//:ssl", + "@proxy_wasm_cpp_sdk//:api_lib", + "@proxy_wasm_cpp_sdk//:common_lib", + ], +) diff --git a/bazel/external/proxy_wasm_cpp_sdk.BUILD b/bazel/external/proxy_wasm_cpp_sdk.BUILD new file mode 100644 index 0000000000000..259e7ea358431 --- /dev/null +++ b/bazel/external/proxy_wasm_cpp_sdk.BUILD @@ -0,0 +1,16 @@ +licenses(["notice"]) # Apache 2 + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "api_lib", + hdrs = ["proxy_wasm_api.h"], +) + +cc_library( + name = "common_lib", + hdrs = [ + "proxy_wasm_common.h", + "proxy_wasm_enums.h", + ], +) diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index f77167aeae289..dc94ba11dde67 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -146,6 +146,8 @@ def envoy_dependencies(skip_targets = []): _io_opentracing_cpp() _net_zlib() _upb() + _proxy_wasm_cpp_sdk() + _proxy_wasm_cpp_host() _repository_impl("com_googlesource_code_re2") _com_google_cel_cpp() _repository_impl("bazel_toolchains") @@ -741,6 +743,18 @@ def _com_github_gperftools_gperftools(): actual = "@envoy//bazel/foreign_cc:gperftools", ) +def _proxy_wasm_cpp_sdk(): + _repository_impl( + name = "proxy_wasm_cpp_sdk", + build_file = "@envoy//bazel/external:proxy_wasm_cpp_sdk.BUILD", + ) + +def _proxy_wasm_cpp_host(): + _repository_impl( + name = "proxy_wasm_cpp_host", + build_file = "@envoy//bazel/external:proxy_wasm_cpp_host.BUILD", + ) + def _kafka_deps(): # This archive contains Kafka client source code. # We are using request/response message format files to generate parser code. diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 20ae46e7c4651..4f878f5053b15 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -319,4 +319,14 @@ REPOSITORY_LOCATIONS = dict( strip_prefix = "kafka-python-2.0.0", urls = ["https://github.com/dpkp/kafka-python/archive/2.0.0.tar.gz"], ), + proxy_wasm_cpp_sdk = dict( + sha256 = "14f66f67e8f37ec81d28d7f5307be4407d206ac5f0daaf6d22fa5536797bcac1", + strip_prefix = "proxy-wasm-cpp-sdk-31f1fc5b7e09f231fa532d2d296e479a113c3e10", + urls = ["https://github.com/proxy-wasm/proxy-wasm-cpp-sdk/archive/31f1fc5b7e09f231fa532d2d296e479a113c3e10.tar.gz"], + ), + proxy_wasm_cpp_host = dict( + sha256 = "4aaf28e2c3326f9f465ffc460162ee195ee3ceaba93241c58453ae57373191fe", + strip_prefix = "proxy-wasm-cpp-host-a9c0c47d8062aa36d955b8604b1b96ee9078253f", + urls = ["https://github.com/proxy-wasm/proxy-wasm-cpp-host/archive/a9c0c47d8062aa36d955b8604b1b96ee9078253f.tar.gz"], + ), ) diff --git a/include/abi/wasm/proxy_wasm_exports.h b/include/abi/wasm/proxy_wasm_exports.h index f2536a8c51a4e..748a1a0ef4352 100644 --- a/include/abi/wasm/proxy_wasm_exports.h +++ b/include/abi/wasm/proxy_wasm_exports.h @@ -28,7 +28,7 @@ * Called when the VM starts by the first plugin to use the VM. * @param root_context_id is an identifier for one or more related plugins. * @param vm_configuration_size is the size of any configuration available via - * proxy_get_configuration during the lifetime of this call. + * proxy_get_buffer_bytes() during the lifetime of this call. * @return non-zero on success and zero on failure (e.g. bad configuration). */ enum class WasmOnVmStartResult : uint32_t { @@ -44,7 +44,7 @@ extern "C" WasmOnVmStartResult proxy_on_vm_start(uint32_t root_context_id, * configuration. * @param root_context_id is a unique identifier for the configuration verification context. * @param configuration_size is the size of any configuration available via - * proxy_get_configuration(). + * proxy_get_buffer_bytes(). * @return non-zero on success and zero on failure (i.e. bad configuration). */ enum class WasmOnValidateConfigurationResult : uint32_t { @@ -57,7 +57,7 @@ proxy_validate_configuration(uint32_t root_context_id, uint32_t configuration_si * Called when a plugin loads or when plugin configuration changes dynamically. * @param root_context_id is an identifier for one or more related plugins. * @param plugin_configuration_size is the size of any configuration available via - * proxy_get_configuration(). + * proxy_get_buffer_bytes(). * @return non-zero on success and zero on failure (e.g. bad configuration). */ enum class WasmOnConfigureResult : uint32_t { @@ -73,7 +73,7 @@ extern "C" WasmOnConfigureResult proxy_on_configure(uint32_t root_context_id, * Called when a request, stream or other ephemeral context is created. * @param context_id is an identifier of the ephemeral context. * @param configuration_size is the size of any configuration available via - * proxy_get_configuration(). + * proxy_get_buffer_bytes(). */ extern "C" void proxy_on_context_create(uint32_t context_id, uint32_t root_context_id); diff --git a/include/abi/wasm/proxy_wasm_imports.h b/include/abi/wasm/proxy_wasm_imports.h index 6341420035029..189b764790221 100644 --- a/include/abi/wasm/proxy_wasm_imports.h +++ b/include/abi/wasm/proxy_wasm_imports.h @@ -15,23 +15,25 @@ // Configuration and Status /** - * Called from the VM to get any configuration. Valid only when in proxy_on_start() (where it will - * return a VM configuration), proxy_on_configure() (where it will return a plugin configuration) or - * in proxy_validate_configuration() (where it will return a VM configuration before - * proxy_on_start() has been called and a plugin configuration after). + * Called from the VM to get bytes from a buffer (e.g. a vm or plugin configuration). * @param start is the offset of the first byte to retrieve. * @param length is the number of the bytes to retrieve. If start + length exceeds the number of * bytes available then configuration_size will be set to the number of bytes returned. - * @param configuration_ptr a pointer to a location which will be filled with either nullptr (if no - * configuration is available) or a pointer to a allocated block containing the configuration + * @param ptr a pointer to a location which will be filled with either nullptr (if no + * data is available) or a pointer to a allocated block containing the configuration * bytes. - * @param configuration_size a pointer to a location containing the size (or zero) of any returned - * configuration byte block. - * @return a WasmResult: OK, InvalidMemoryAccess. Note: if OK is returned *configuration_ptr may + * @param size a pointer to a location containing the size (or zero) of any returned + * byte block. + * @return a WasmResult: OK, InvalidArgument, InvalidMemoryAccess. Note: if OK is returned *ptr may * be nullptr. */ -extern "C" WasmResult proxy_get_configuration((uint32_t start, uint32_t length, - const char** configuration_ptr, size_t* configuration_size); +enum class WasmBufferType : int32_t { + VmConfiguration = 0, + PluginConfiguration = 1, + MAX = 2, +}; +extern "C" WasmResult proxy_get_buffer_bytes(WasmBufferType type, uint32_t start, uint32_t length, + const char** ptr, size_t* size); // Logging // diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index 5440906a78072..ea05c48e0a62b 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -837,6 +837,7 @@ class StatNameSet { using StringStatNameMap = absl::flat_hash_map; StringStatNameMap builtin_stat_names_; }; +using StatNameSetSharedPtr = std::shared_ptr; } // namespace Stats } // namespace Envoy diff --git a/source/extensions/common/wasm/BUILD b/source/extensions/common/wasm/BUILD index 9333c679421f6..2a0b7a245bfe6 100644 --- a/source/extensions/common/wasm/BUILD +++ b/source/extensions/common/wasm/BUILD @@ -17,29 +17,49 @@ envoy_cc_library( ) envoy_cc_library( - name = "wasm_vm_interface", - hdrs = ["wasm_vm.h"], + name = "wasm_vm_lib", + srcs = ["wasm_vm.cc"], deps = [ - ":well_known_names", - "//source/common/common:minimal_logger_lib", + ":wasm_hdr", + "//source/common/common:assert_lib", + "//source/common/stats:stats_lib", + "@proxy_wasm_cpp_host//:lib", + "@proxy_wasm_cpp_sdk//:common_lib", ], ) +# NB: Used to break the circular dependency between wasm_lib and null_plugin_lib. envoy_cc_library( - name = "wasm_vm_base", - hdrs = ["wasm_vm_base.h"], + name = "wasm_hdr", + hdrs = [ + "context.h", + "wasm.h", + "wasm_vm.h", + ], deps = [ + ":well_known_names", + "//include/envoy/upstream:cluster_manager_interface", + "//source/common/config:datasource_lib", "//source/common/stats:stats_lib", + "@envoy_api//envoy/extensions/wasm/v3:pkg_cc_proto", + "@proxy_wasm_cpp_host//:include", + "@proxy_wasm_cpp_sdk//:common_lib", ], ) envoy_cc_library( - name = "wasm_vm_lib", - srcs = ["wasm_vm.cc"], + name = "wasm_lib", + srcs = [ + "context.cc", + "wasm.cc", + ], deps = [ - ":wasm_vm_interface", - "//source/common/common:assert_lib", - "//source/extensions/common/wasm/null:null_lib", - "//source/extensions/common/wasm/v8:v8_lib", + ":wasm_hdr", + ":wasm_vm_lib", + "//external:abseil_base", + "//external:abseil_node_hash_map", + "//source/common/common:base64_lib", + "//source/common/common:enum_to_int", + "@envoy_api//envoy/extensions/wasm/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc new file mode 100644 index 0000000000000..fca82de3c01de --- /dev/null +++ b/source/extensions/common/wasm/context.cc @@ -0,0 +1,215 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include "envoy/common/exception.h" +#include "envoy/extensions/wasm/v3/wasm.pb.validate.h" +#include "envoy/local_info/local_info.h" +#include "envoy/thread_local/thread_local.h" + +#include "common/common/assert.h" +#include "common/common/empty_string.h" +#include "common/common/enum_to_int.h" +#include "common/common/logger.h" + +#include "extensions/common/wasm/wasm.h" +#include "extensions/common/wasm/well_known_names.h" + +#include "absl/base/casts.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/node_hash_map.h" +#include "absl/strings/str_cat.h" +#include "absl/synchronization/mutex.h" +#include "openssl/bytestring.h" +#include "openssl/hmac.h" +#include "openssl/sha.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +bool Buffer::copyTo(WasmBase* wasm, size_t start, size_t length, uint64_t ptr_ptr, + uint64_t size_ptr) const { + if (start + length > data_.size()) { + return false; + } + absl::string_view s = data_.substr(start, length); + return wasm->copyToPointerSize(s, ptr_ptr, size_ptr); +} + +Wasm* Context::wasm() const { return static_cast(wasm_); } + +void Context::error(absl::string_view message) { throw WasmException(std::string(message)); } + +void Context::initializeRoot(WasmBase* wasm, PluginBaseSharedPtr plugin) { + ContextBase::initializeRoot(wasm, plugin); + root_local_info_ = &std::static_pointer_cast(plugin)->local_info_; +} + +uint64_t Context::getCurrentTimeNanoseconds() { + return std::chrono::duration_cast( + wasm()->time_source_.systemTime().time_since_epoch()) + .count(); +} + +WasmResult Context::log(uint64_t level, absl::string_view message) { + switch (static_cast(level)) { + case proxy_wasm::LogLevel::trace: + ENVOY_LOG(trace, "wasm log{}: {}", log_prefix(), message); + break; + case proxy_wasm::LogLevel::debug: + ENVOY_LOG(debug, "wasm log{}: {}", log_prefix(), message); + break; + case proxy_wasm::LogLevel::info: + ENVOY_LOG(info, "wasm log{}: {}", log_prefix(), message); + break; + case proxy_wasm::LogLevel::warn: + ENVOY_LOG(warn, "wasm log{}: {}", log_prefix(), message); + break; + case proxy_wasm::LogLevel::error: + ENVOY_LOG(error, "wasm log{}: {}", log_prefix(), message); + break; + case proxy_wasm::LogLevel::critical: + ENVOY_LOG(critical, "wasm log{}: {}", log_prefix(), message); + break; + default: + return WasmResult::BadArgument; + } + return WasmResult::Ok; +} + +WasmResult Context::getProperty(absl::string_view path, std::string* result) { + if (path.size() == 15 && !memcmp(reinterpret_cast(&*path.begin()), + reinterpret_cast("plugin_root_id\0"), 15)) { + *result = std::string(root_id()); + return WasmResult::Ok; + } + return WasmResult::Unimplemented; +} + +WasmResult Context::defineMetric(MetricType type, absl::string_view name, uint32_t* metric_id_ptr) { + auto stat_name = wasm()->stat_name_set()->add(name); + switch (type) { + case MetricType::Counter: { + auto id = wasm_->nextCounterMetricId(); + auto c = &wasm()->scope_->counterFromStatName(stat_name); + wasm()->counters_.emplace(id, c); + *metric_id_ptr = id; + return WasmResult::Ok; + } + case MetricType::Gauge: { + auto id = wasm()->nextGaugeMetricId(); + auto g = &wasm()->scope_->gaugeFromStatName(stat_name, Stats::Gauge::ImportMode::Accumulate); + wasm()->gauges_.emplace(id, g); + *metric_id_ptr = id; + return WasmResult::Ok; + } + case MetricType::Histogram: { + auto id = wasm()->nextHistogramMetricId(); + auto h = &wasm()->scope_->histogramFromStatName(stat_name, Stats::Histogram::Unit::Unspecified); + wasm()->histograms_.emplace(id, h); + *metric_id_ptr = id; + return WasmResult::Ok; + } + default: + return WasmResult::BadArgument; + } +} + +WasmResult Context::incrementMetric(uint32_t metric_id, int64_t offset) { + auto type = static_cast(metric_id & Wasm::kMetricTypeMask); + if (type == MetricType::Counter) { + auto it = wasm()->counters_.find(metric_id); + if (it != wasm()->counters_.end()) { + if (offset > 0) { + it->second->add(offset); + return WasmResult::Ok; + } else { + return WasmResult::BadArgument; + } + return WasmResult::NotFound; + } + } else if (type == MetricType::Gauge) { + auto it = wasm()->gauges_.find(metric_id); + if (it != wasm()->gauges_.end()) { + if (offset > 0) { + it->second->add(offset); + return WasmResult::Ok; + } else { + it->second->sub(-offset); + return WasmResult::Ok; + } + } + return WasmResult::NotFound; + } + return WasmResult::BadArgument; +} + +WasmResult Context::recordMetric(uint32_t metric_id, uint64_t value) { + auto type = static_cast(metric_id & Wasm::kMetricTypeMask); + if (type == MetricType::Counter) { + auto it = wasm()->counters_.find(metric_id); + if (it != wasm()->counters_.end()) { + it->second->add(value); + return WasmResult::Ok; + } + } else if (type == MetricType::Gauge) { + auto it = wasm()->gauges_.find(metric_id); + if (it != wasm()->gauges_.end()) { + it->second->set(value); + return WasmResult::Ok; + } + } else if (type == MetricType::Histogram) { + auto it = wasm()->histograms_.find(metric_id); + if (it != wasm()->histograms_.end()) { + it->second->recordValue(value); + return WasmResult::Ok; + } + } + return WasmResult::NotFound; +} + +WasmResult Context::getMetric(uint32_t metric_id, uint64_t* result_uint64_ptr) { + auto type = static_cast(metric_id & Wasm::kMetricTypeMask); + if (type == MetricType::Counter) { + auto it = wasm()->counters_.find(metric_id); + if (it != wasm()->counters_.end()) { + *result_uint64_ptr = it->second->value(); + return WasmResult::Ok; + } + return WasmResult::NotFound; + } else if (type == MetricType::Gauge) { + auto it = wasm()->gauges_.find(metric_id); + if (it != wasm()->gauges_.end()) { + *result_uint64_ptr = it->second->value(); + return WasmResult::Ok; + } + return WasmResult::NotFound; + } + return WasmResult::BadArgument; +} + +const BufferInterface* Context::getBuffer(WasmBufferType type) { + switch (type) { + case WasmBufferType::VmConfiguration: + return buffer_.set(wasm()->vm_configuration()); + case WasmBufferType::PluginConfiguration: + if (plugin_) + return buffer_.set(plugin_->plugin_configuration_); + break; + default: + break; + } + return nullptr; +} + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/common/wasm/context.h b/source/extensions/common/wasm/context.h new file mode 100644 index 0000000000000..ab0e8ea40a0ec --- /dev/null +++ b/source/extensions/common/wasm/context.h @@ -0,0 +1,130 @@ +#pragma once + +#include +#include +#include + +#include "envoy/access_log/access_log.h" +#include "envoy/buffer/buffer.h" +#include "envoy/http/filter.h" +#include "envoy/upstream/cluster_manager.h" + +#include "common/common/assert.h" +#include "common/common/logger.h" + +#include "include/proxy-wasm/context.h" +#include "include/proxy-wasm/wasm.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +class Wasm; + +#include "proxy_wasm_common.h" + +using proxy_wasm::BufferInterface; +using proxy_wasm::MetricType; +using proxy_wasm::Pairs; +using proxy_wasm::PairsWithStringValues; +using proxy_wasm::PluginBase; +using proxy_wasm::WasmBase; +using proxy_wasm::WasmBufferType; +using proxy_wasm::WasmHandle; +using proxy_wasm::WasmResult; +using proxy_wasm::WasmVm; + +using PluginBaseSharedPtr = std::shared_ptr; + +class Buffer : public proxy_wasm::BufferInterface { +public: + Buffer() {} + + // Add additional wrapped types here. + Buffer* set(absl::string_view data) { + data_ = data; + return this; + } + + size_t size() const override { return data_.size(); } + bool copyTo(WasmBase* wasm, size_t start, size_t length, uint64_t ptr_ptr, + uint64_t size_ptr) const override; + +private: + absl::string_view data_; +}; + +// Plugin contains the information for a filter/service. +struct Plugin : public PluginBase { + Plugin(absl::string_view name, absl::string_view root_id, absl::string_view vm_id, + absl::string_view plugin_configuration, + const envoy::config::core::v3::TrafficDirection& direction, + const LocalInfo::LocalInfo& local_info, + const envoy::config::core::v3::Metadata* listener_metadata) + : PluginBase(name, root_id, vm_id, plugin_configuration), direction_(direction), + local_info_(local_info), listener_metadata_(listener_metadata) {} + + // Information specific to HTTP Filters. + // TODO: consider using a variant record or filter-type specific sub-objects via unique_ptr. + const envoy::config::core::v3::TrafficDirection direction_; + const LocalInfo::LocalInfo& local_info_; + const envoy::config::core::v3::Metadata* listener_metadata_; +}; +using PluginSharedPtr = std::shared_ptr; + +// A context which will be the target of callbacks for a particular session +// e.g. a handler of a stream. +class Context : public proxy_wasm::ContextBase, + Logger::Loggable, + public std::enable_shared_from_this { +public: + Context(); // Testing. + Context(WasmBase* wasm) : ContextBase(wasm) {} // Vm Context. + Context(WasmBase* wasm, PluginSharedPtr plugin) : ContextBase(wasm, plugin) {} // Root Context. + Context(WasmBase* wasm, uint32_t root_context_id, PluginSharedPtr plugin) + : ContextBase(wasm, root_context_id, plugin) {} // Stream context. + virtual ~Context() {} + + Wasm* wasm() const; + + // proxy_wasm::ContextBase + void error(absl::string_view message) override; + + /** + * General Callbacks. + */ + // proxy_wasm::ContextBase + WasmResult log(uint64_t level, absl::string_view message) override; + uint64_t getCurrentTimeNanoseconds() override; + + // Buffer + // proxy_wasm::ContextBase + const BufferInterface* getBuffer(WasmBufferType type) override; + + // Properties + // proxy_wasm::ContextBase + WasmResult getProperty(absl::string_view path, std::string* result) override; + + // Metrics + // proxy_wasm::ContextBase + WasmResult defineMetric(MetricType type, absl::string_view name, + uint32_t* metric_id_ptr) override; + WasmResult incrementMetric(uint32_t metric_id, int64_t offset) override; + WasmResult recordMetric(uint32_t metric_id, uint64_t value) override; + WasmResult getMetric(uint32_t metric_id, uint64_t* value_ptr) override; + +protected: + void initializeRoot(WasmBase* wasm, PluginBaseSharedPtr plugin) override; + + const LocalInfo::LocalInfo* root_local_info_{nullptr}; // set only for root_context. + + // Temporary state used for and valid only during calls into the VM. + Buffer buffer_; +}; +using ContextSharedPtr = std::shared_ptr; + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/common/wasm/null/BUILD b/source/extensions/common/wasm/null/BUILD deleted file mode 100644 index 2bae8acd9f4ff..0000000000000 --- a/source/extensions/common/wasm/null/BUILD +++ /dev/null @@ -1,49 +0,0 @@ -licenses(["notice"]) # Apache 2 - -load( - "//bazel:envoy_build_system.bzl", - "envoy_cc_library", - "envoy_package", -) - -envoy_package() - -envoy_cc_library( - name = "null_vm_plugin_interface", - hdrs = ["null_vm_plugin.h"], - deps = [ - "//include/envoy/config:typed_config_interface", - "//source/extensions/common/wasm:wasm_vm_interface", - "//source/extensions/common/wasm:well_known_names", - ], -) - -envoy_cc_library( - name = "null_vm_lib", - srcs = ["null_vm.cc"], - hdrs = ["null_vm.h"], - deps = [ - ":null_vm_plugin_interface", - "//external:abseil_node_hash_map", - "//include/envoy/registry", - "//source/common/common:assert_lib", - "//source/extensions/common/wasm:wasm_vm_base", - "//source/extensions/common/wasm:wasm_vm_interface", - "//source/extensions/common/wasm:well_known_names", - ], -) - -envoy_cc_library( - name = "null_lib", - srcs = ["null.cc"], - hdrs = ["null.h"], - deps = [ - ":null_vm_lib", - ":null_vm_plugin_interface", - "//external:abseil_node_hash_map", - "//include/envoy/registry", - "//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/null/null.cc b/source/extensions/common/wasm/null/null.cc deleted file mode 100644 index af2ba77d1dc56..0000000000000 --- a/source/extensions/common/wasm/null/null.cc +++ /dev/null @@ -1,27 +0,0 @@ -#include "extensions/common/wasm/null/null.h" - -#include -#include -#include - -#include "envoy/registry/registry.h" - -#include "common/common/assert.h" - -#include "extensions/common/wasm/null/null_vm.h" -#include "extensions/common/wasm/null/null_vm_plugin.h" -#include "extensions/common/wasm/well_known_names.h" - -namespace Envoy { -namespace Extensions { -namespace Common { -namespace Wasm { -namespace Null { - -WasmVmPtr createVm(const Stats::ScopeSharedPtr& scope) { return std::make_unique(scope); } - -} // namespace Null -} // namespace Wasm -} // namespace Common -} // namespace Extensions -} // namespace Envoy diff --git a/source/extensions/common/wasm/null/null.h b/source/extensions/common/wasm/null/null.h deleted file mode 100644 index 285b13373fbc8..0000000000000 --- a/source/extensions/common/wasm/null/null.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include - -#include "extensions/common/wasm/null/null_vm_plugin.h" -#include "extensions/common/wasm/wasm_vm.h" - -namespace Envoy { -namespace Extensions { -namespace Common { -namespace Wasm { -namespace Null { - -WasmVmPtr createVm(const Stats::ScopeSharedPtr& scope); - -} // namespace Null -} // namespace Wasm -} // namespace Common -} // namespace Extensions -} // namespace Envoy diff --git a/source/extensions/common/wasm/null/null_vm.cc b/source/extensions/common/wasm/null/null_vm.cc deleted file mode 100644 index abd4418fc76b9..0000000000000 --- a/source/extensions/common/wasm/null/null_vm.cc +++ /dev/null @@ -1,94 +0,0 @@ -#include "extensions/common/wasm/null/null_vm.h" - -#include -#include -#include - -#include "envoy/registry/registry.h" - -#include "common/common/assert.h" - -#include "extensions/common/wasm/null/null_vm_plugin.h" -#include "extensions/common/wasm/well_known_names.h" - -namespace Envoy { -namespace Extensions { -namespace Common { -namespace Wasm { -namespace Null { - -WasmVmPtr NullVm::clone() { - auto cloned_null_vm = std::make_unique(*this); - cloned_null_vm->load(plugin_name_, false /* unused */); - return cloned_null_vm; -} - -// "Load" the plugin by obtaining a pointer to it from the factory. -bool NullVm::load(const std::string& name, bool /* allow_precompiled */) { - auto factory = Registry::FactoryRegistry::getFactory(name); - if (!factory) { - return false; - } - plugin_name_ = name; - plugin_ = factory->create(); - return true; -} - -void NullVm::link(absl::string_view /* name */) {} - -uint64_t NullVm::getMemorySize() { return std::numeric_limits::max(); } - -// NulVm pointers are just native pointers. -absl::optional NullVm::getMemory(uint64_t pointer, uint64_t size) { - if (pointer == 0 && size != 0) { - return absl::nullopt; - } - return absl::string_view(reinterpret_cast(pointer), static_cast(size)); -} - -bool NullVm::setMemory(uint64_t pointer, uint64_t size, const void* data) { - if ((pointer == 0 || data == nullptr)) { - if (size != 0) { - return false; - } else { - return true; - } - } - auto p = reinterpret_cast(pointer); - memcpy(p, data, size); - return true; -} - -bool NullVm::setWord(uint64_t pointer, Word data) { - if (pointer == 0) { - return false; - } - auto p = reinterpret_cast(pointer); - memcpy(p, &data.u64_, sizeof(data.u64_)); - return true; -} - -bool NullVm::getWord(uint64_t pointer, Word* data) { - if (pointer == 0) { - return false; - } - auto p = reinterpret_cast(pointer); - memcpy(&data->u64_, p, sizeof(data->u64_)); - return true; -} - -absl::string_view NullVm::getCustomSection(absl::string_view /* name */) { - // Return nothing: there is no WASM file. - return {}; -} - -absl::string_view NullVm::getPrecompiledSectionName() { - // Return nothing: there is no WASM file. - return {}; -} - -} // namespace Null -} // namespace Wasm -} // namespace Common -} // namespace Extensions -} // namespace Envoy diff --git a/source/extensions/common/wasm/null/null_vm.h b/source/extensions/common/wasm/null/null_vm.h deleted file mode 100644 index e0cf345c51b6e..0000000000000 --- a/source/extensions/common/wasm/null/null_vm.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "envoy/registry/registry.h" - -#include "common/common/assert.h" - -#include "extensions/common/wasm/null/null_vm_plugin.h" -#include "extensions/common/wasm/wasm_vm_base.h" -#include "extensions/common/wasm/well_known_names.h" - -namespace Envoy { -namespace Extensions { -namespace Common { -namespace Wasm { -namespace Null { - -// The NullVm wraps a C++ WASM plugin which has been compiled with the WASM API -// and linked directly into the Envoy process. This is useful for development -// in that it permits the debugger to set breakpoints in both Envoy and the plugin. -struct NullVm : public WasmVmBase { - NullVm(const Stats::ScopeSharedPtr& scope) : WasmVmBase(scope, WasmRuntimeNames::get().Null) {} - NullVm(const NullVm& other) - : WasmVmBase(other.scope_, WasmRuntimeNames::get().Null), plugin_name_(other.plugin_name_) {} - - // WasmVm - absl::string_view runtime() override { return WasmRuntimeNames::get().Null; } - Cloneable cloneable() override { return Cloneable::InstantiatedModule; }; - WasmVmPtr clone() override; - bool load(const std::string& code, bool allow_precompiled) override; - void link(absl::string_view debug_name) override; - uint64_t getMemorySize() override; - absl::optional getMemory(uint64_t pointer, uint64_t size) override; - bool setMemory(uint64_t pointer, uint64_t size, const void* data) override; - bool setWord(uint64_t pointer, Word data) override; - bool getWord(uint64_t pointer, Word* data) override; - absl::string_view getCustomSection(absl::string_view name) override; - absl::string_view getPrecompiledSectionName() override; - -#define _FORWARD_GET_FUNCTION(_T) \ - void getFunction(absl::string_view function_name, _T* f) override { \ - plugin_->getFunction(function_name, f); \ - } - FOR_ALL_WASM_VM_EXPORTS(_FORWARD_GET_FUNCTION) -#undef _FORWARD_GET_FUNCTION - - // These are not needed for NullVm which invokes the handlers directly. -#define _REGISTER_CALLBACK(_T) \ - void registerCallback(absl::string_view, absl::string_view, _T, \ - typename ConvertFunctionTypeWordToUint32<_T>::type) override{}; - FOR_ALL_WASM_VM_IMPORTS(_REGISTER_CALLBACK) -#undef _REGISTER_CALLBACK - - std::string plugin_name_; - std::unique_ptr plugin_; -}; - -} // namespace Null -} // namespace Wasm -} // namespace Common -} // namespace Extensions -} // namespace Envoy diff --git a/source/extensions/common/wasm/null/null_vm_plugin.h b/source/extensions/common/wasm/null/null_vm_plugin.h deleted file mode 100644 index eef7fcebeac5e..0000000000000 --- a/source/extensions/common/wasm/null/null_vm_plugin.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include "envoy/config/typed_config.h" - -#include "extensions/common/wasm/wasm_vm.h" - -namespace Envoy { -namespace Extensions { -namespace Common { -namespace Wasm { -namespace Null { - -// A wrapper for the natively compiled NullVm plugin which implements the WASM ABI. -class NullVmPlugin { -public: - NullVmPlugin() = default; - virtual ~NullVmPlugin() = default; - - // NB: These are defined rather than declared PURE because gmock uses __LINE__ internally for - // uniqueness, making it impossible to use FOR_ALL_WASM_VM_EXPORTS with MOCK_METHOD. -#define _DEFINE_GET_FUNCTION(_T) \ - virtual void getFunction(absl::string_view, _T* f) { *f = nullptr; } - FOR_ALL_WASM_VM_EXPORTS(_DEFINE_GET_FUNCTION) -#undef _DEFIN_GET_FUNCTIONE -}; - -/** - * Pseudo-WASM plugins using the NullVM should implement this factory and register via - * Registry::registerFactory or the convenience class RegisterFactory. - */ -class NullVmPluginFactory : public Config::UntypedFactory { -public: - virtual ~NullVmPluginFactory() = default; - - std::string category() const override { return "envoy.wasm.null_vms"; } - - /** - * Create an instance of the plugin. - */ - virtual std::unique_ptr create() const PURE; -}; - -} // namespace Null -} // namespace Wasm -} // namespace Common -} // namespace Extensions -} // namespace Envoy diff --git a/source/extensions/common/wasm/v8/BUILD b/source/extensions/common/wasm/v8/BUILD deleted file mode 100644 index 04d0954d1b2cd..0000000000000 --- a/source/extensions/common/wasm/v8/BUILD +++ /dev/null @@ -1,24 +0,0 @@ -licenses(["notice"]) # Apache 2 - -load( - "//bazel:envoy_build_system.bzl", - "envoy_cc_library", - "envoy_package", -) - -envoy_package() - -envoy_cc_library( - name = "v8_lib", - srcs = ["v8.cc"], - hdrs = ["v8.h"], - external_deps = [ - "wee8", - ], - deps = [ - "//source/common/common:assert_lib", - "//source/extensions/common/wasm:wasm_vm_base", - "//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 deleted file mode 100644 index 5bd2c1f57b6f0..0000000000000 --- a/source/extensions/common/wasm/v8/v8.cc +++ /dev/null @@ -1,678 +0,0 @@ -#include "extensions/common/wasm/v8/v8.h" - -#include -#include -#include - -#include "common/common/assert.h" - -#include "extensions/common/wasm/wasm_vm_base.h" -#include "extensions/common/wasm/well_known_names.h" - -#include "absl/container/flat_hash_map.h" -#include "absl/strings/match.h" -#include "v8-version.h" -#include "wasm-api/wasm.hh" - -namespace Envoy { -namespace Extensions { -namespace Common { -namespace Wasm { -namespace V8 { - -wasm::Engine* engine() { - static const auto engine = wasm::Engine::make(); - return engine.get(); -} - -struct FuncData { - FuncData(std::string name) : name_(std::move(name)) {} - - std::string name_; - wasm::own callback_; - void* raw_func_; -}; - -using FuncDataPtr = std::unique_ptr; - -class V8 : public WasmVmBase { -public: - V8(const Stats::ScopeSharedPtr& scope) : WasmVmBase(scope, WasmRuntimeNames::get().V8) {} - - // Extensions::Common::Wasm::WasmVm - 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; - absl::string_view getPrecompiledSectionName() override; - void link(absl::string_view debug_name) override; - - Cloneable cloneable() override { return Cloneable::CompiledBytecode; } - WasmVmPtr clone() override; - - uint64_t getMemorySize() override; - absl::optional getMemory(uint64_t pointer, uint64_t size) override; - bool setMemory(uint64_t pointer, uint64_t size, const void* data) override; - 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::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 { \ - getModuleFunctionImpl(function_name, f); \ - }; - FOR_ALL_WASM_VM_EXPORTS(_GET_MODULE_FUNCTION) -#undef _GET_MODULE_FUNCTION - -private: - wasm::vec getStrippedSource(); - - template - void registerHostFunctionImpl(absl::string_view module_name, absl::string_view function_name, - void (*function)(void*, Args...)); - - template - void registerHostFunctionImpl(absl::string_view module_name, absl::string_view function_name, - R (*function)(void*, Args...)); - - template - void getModuleFunctionImpl(absl::string_view function_name, - std::function* function); - - template - void getModuleFunctionImpl(absl::string_view function_name, - std::function* function); - - wasm::vec source_ = wasm::vec::invalid(); - wasm::own store_; - wasm::own module_; - wasm::own> shared_module_; - wasm::own instance_; - wasm::own memory_; - wasm::own table_; - - absl::flat_hash_map host_functions_; - absl::flat_hash_map> module_functions_; -}; - -// Helper functions. - -static std::string printValue(const wasm::Val& value) { - switch (value.kind()) { - case wasm::I32: - return std::to_string(value.get()); - case wasm::I64: - return std::to_string(value.get()); - case wasm::F32: - return std::to_string(value.get()); - case wasm::F64: - return std::to_string(value.get()); - default: - return "unknown"; - } -} - -static std::string printValues(const wasm::Val values[], size_t size) { - if (size == 0) { - return ""; - } - - std::string s; - for (size_t i = 0; i < size; i++) { - if (i) { - s.append(", "); - } - s.append(printValue(values[i])); - } - return s; -} - -static const char* printValKind(wasm::ValKind kind) { - switch (kind) { - case wasm::I32: - return "i32"; - case wasm::I64: - return "i64"; - case wasm::F32: - return "f32"; - case wasm::F64: - return "f64"; - case wasm::ANYREF: - return "anyref"; - case wasm::FUNCREF: - return "funcref"; - default: - return "unknown"; - } -} - -static std::string printValTypes(const wasm::ownvec& types) { - if (types.size() == 0) { - return "void"; - } - - std::string s; - s.reserve(types.size() * 8 /* max size + " " */ - 1); - for (size_t i = 0; i < types.size(); i++) { - if (i) { - s.append(" "); - } - s.append(printValKind(types[i]->kind())); - } - return s; -} - -static bool equalValTypes(const wasm::ownvec& left, - const wasm::ownvec& right) { - if (left.size() != right.size()) { - return false; - } - for (size_t i = 0; i < left.size(); i++) { - if (left[i]->kind() != right[i]->kind()) { - return false; - } - } - return true; -} - -static uint32_t parseVarint(const byte_t*& pos, const byte_t* end) { - uint32_t n = 0; - uint32_t shift = 0; - byte_t b; - do { - if (pos + 1 > end) { - throw WasmVmException("Failed to parse corrupted WASM module"); - } - b = *pos++; - n += (b & 0x7f) << shift; - shift += 7; - } while ((b & 0x80) != 0); - return n; -} - -// Template magic. - -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_)); } - -template constexpr auto convertArgToValKind(); -template <> constexpr auto convertArgToValKind() { return wasm::I32; }; -template <> constexpr auto convertArgToValKind() { return wasm::I32; }; -template <> constexpr auto convertArgToValKind() { return wasm::I32; }; -template <> constexpr auto convertArgToValKind() { return wasm::I64; }; -template <> constexpr auto convertArgToValKind() { return wasm::I64; }; -template <> constexpr auto convertArgToValKind() { return wasm::F32; }; -template <> constexpr auto convertArgToValKind() { return wasm::F64; }; - -template -constexpr auto convertArgsTupleToValTypesImpl(absl::index_sequence) { - return wasm::ownvec::make( - wasm::ValType::make(convertArgToValKind::type>())...); -} - -template constexpr auto convertArgsTupleToValTypes() { - return convertArgsTupleToValTypesImpl(absl::make_index_sequence::value>()); -} - -template -constexpr T convertValTypesToArgsTupleImpl(const U& arr, absl::index_sequence) { - return std::make_tuple( - (arr[I] - .template get< - typename ConvertWordType::type>::type>())...); -} - -template constexpr T convertValTypesToArgsTuple(const U& arr) { - return convertValTypesToArgsTupleImpl(arr, - absl::make_index_sequence::value>()); -} - -// V8 implementation. - -bool V8::load(const std::string& code, bool allow_precompiled) { - ENVOY_LOG(trace, "load()"); - store_ = wasm::Store::make(engine()); - - // Wasm file header is 8 bytes (magic number + version). - static const uint8_t magic_number[4] = {0x00, 0x61, 0x73, 0x6d}; - if (code.size() < 8 || ::memcmp(code.data(), magic_number, 4) != 0) { - return false; - } - - source_ = wasm::vec::make_uninitialized(code.size()); - ::memcpy(source_.get(), code.data(), code.size()); - - if (allow_precompiled) { - const auto section_name = getPrecompiledSectionName(); - if (!section_name.empty()) { - const auto precompiled = getCustomSection(section_name); - if (!precompiled.empty()) { - auto vec = wasm::vec::make_uninitialized(precompiled.size()); - ::memcpy(vec.get(), precompiled.data(), precompiled.size()); - - // TODO(PiotrSikora): fuzz loading of precompiled Wasm modules. - // See: https://github.com/envoyproxy/envoy/issues/9731 - module_ = wasm::Module::deserialize(store_.get(), vec); - if (!module_) { - // Precompiled module that cannot be loaded is considered a hard error, - // so don't fallback to compiling the bytecode. - return false; - } - } - } - } - - if (!module_) { - // TODO(PiotrSikora): fuzz loading of Wasm modules. - // See: https://github.com/envoyproxy/envoy/issues/9731 - const auto stripped_source = getStrippedSource(); - module_ = wasm::Module::make(store_.get(), stripped_source ? stripped_source : source_); - } - - if (module_) { - shared_module_ = module_->share(); - } - - return module_ != nullptr; -} - -WasmVmPtr V8::clone() { - ENVOY_LOG(trace, "clone()"); - ASSERT(shared_module_ != nullptr); - - auto clone = std::make_unique(scope_); - clone->store_ = wasm::Store::make(engine()); - - clone->module_ = wasm::Module::obtain(clone->store_.get(), shared_module_.get()); - - return clone; -} - -// Get Wasm module without Custom Sections to save some memory in workers. -wasm::vec V8::getStrippedSource() { - ENVOY_LOG(trace, "getStrippedSource()"); - ASSERT(source_.get() != nullptr); - - std::vector stripped; - - const byte_t* pos = source_.get() + 8 /* Wasm header */; - const byte_t* end = source_.get() + source_.size(); - while (pos < end) { - const auto section_start = pos; - if (pos + 1 > end) { - return wasm::vec::invalid(); - } - const auto section_type = *pos++; - const auto section_len = parseVarint(pos, end); - if (section_len == static_cast(-1) || pos + section_len > end) { - return wasm::vec::invalid(); - } - pos += section_len; - if (section_type == 0 /* custom section */) { - if (stripped.empty()) { - const byte_t* start = source_.get(); - stripped.insert(stripped.end(), start, section_start); - } - } else if (!stripped.empty()) { - stripped.insert(stripped.end(), section_start, pos /* section end */); - } - } - - // No custom sections found, use the original source. - if (stripped.empty()) { - return wasm::vec::invalid(); - } - - // Return stripped source, without custom sections. - return wasm::vec::make(stripped.size(), stripped.data()); -} - -absl::string_view V8::getCustomSection(absl::string_view name) { - ENVOY_LOG(trace, "getCustomSection(\"{}\")", name); - ASSERT(source_.get() != nullptr); - - const byte_t* pos = source_.get() + 8 /* Wasm header */; - const byte_t* end = source_.get() + source_.size(); - while (pos < end) { - if (pos + 1 > end) { - throw WasmVmException("Failed to parse corrupted WASM module"); - } - const auto section_type = *pos++; - const auto section_len = parseVarint(pos, end); - if (section_len == static_cast(-1) || pos + section_len > end) { - throw WasmVmException("Failed to parse corrupted WASM module"); - } - if (section_type == 0 /* custom section */) { - const auto section_data_start = pos; - const auto section_name_len = parseVarint(pos, end); - if (section_name_len == static_cast(-1) || pos + section_name_len > end) { - throw WasmVmException("Failed to parse corrupted WASM module"); - } - if (section_name_len == name.size() && ::memcmp(pos, name.data(), section_name_len) == 0) { - pos += section_name_len; - ENVOY_LOG(trace, "getCustomSection(\"{}\") found, size: {}", name, - section_data_start + section_len - pos); - return {pos, static_cast(section_data_start + section_len - pos)}; - } - pos = section_data_start + section_len; - } else { - pos += section_len; - } - } - return ""; -} - -#if defined(__linux__) && defined(__x86_64__) -#define WEE8_WASM_PRECOMPILE_PLATFORM "linux_x86_64" -#endif - -absl::string_view V8::getPrecompiledSectionName() { -#ifndef WEE8_WASM_PRECOMPILE_PLATFORM - return ""; -#else - static const auto name = - absl::StrCat("precompiled_v8_v", V8_MAJOR_VERSION, ".", V8_MINOR_VERSION, ".", - V8_BUILD_NUMBER, ".", V8_PATCH_LEVEL, "_", WEE8_WASM_PRECOMPILE_PLATFORM); - return name; -#endif -} - -void V8::link(absl::string_view debug_name) { - ENVOY_LOG(trace, "link(\"{}\")", debug_name); - ASSERT(module_ != nullptr); - - const auto import_types = module_.get()->imports(); - std::vector imports; - - for (size_t i = 0; i < import_types.size(); i++) { - absl::string_view module(import_types[i]->module().get(), import_types[i]->module().size()); - absl::string_view name(import_types[i]->name().get(), import_types[i]->name().size()); - auto import_type = import_types[i]->type(); - - switch (import_type->kind()) { - - case wasm::EXTERN_FUNC: { - ENVOY_LOG(trace, "link(), export host func: {}.{} ({} -> {})", module, name, - printValTypes(import_type->func()->params()), - printValTypes(import_type->func()->results())); - - auto it = host_functions_.find(absl::StrCat(module, ".", name)); - if (it == host_functions_.end()) { - throw WasmVmException( - fmt::format("Failed to load WASM module due to a missing import: {}.{}", module, name)); - } - 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( - "Failed to load WASM module due to an import type mismatch: {}.{}, " - "want: {} -> {}, but host exports: {} -> {}", - module, name, printValTypes(import_type->func()->params()), - printValTypes(import_type->func()->results()), printValTypes(func->type()->params()), - printValTypes(func->type()->results()))); - } - imports.push_back(func); - } break; - - case wasm::EXTERN_GLOBAL: { - // TODO(PiotrSikora): add support when/if needed. - ENVOY_LOG(trace, "link(), export host global: {}.{} ({})", module, name, - printValKind(import_type->global()->content()->kind())); - - throw WasmVmException( - fmt::format("Failed to load WASM module due to a missing import: {}.{}", module, name)); - } break; - - case wasm::EXTERN_MEMORY: { - ENVOY_LOG(trace, "link(), export host memory: {}.{} (min: {} max: {})", module, name, - import_type->memory()->limits().min, import_type->memory()->limits().max); - - ASSERT(memory_ == nullptr); - auto type = wasm::MemoryType::make(import_type->memory()->limits()); - memory_ = wasm::Memory::make(store_.get(), type.get()); - imports.push_back(memory_.get()); - } break; - - case wasm::EXTERN_TABLE: { - ENVOY_LOG(trace, "link(), export host table: {}.{} (min: {} max: {})", module, name, - import_type->table()->limits().min, import_type->table()->limits().max); - - ASSERT(table_ == nullptr); - auto type = - wasm::TableType::make(wasm::ValType::make(import_type->table()->element()->kind()), - import_type->table()->limits()); - table_ = wasm::Table::make(store_.get(), type.get()); - imports.push_back(table_.get()); - } break; - } - } - - ASSERT(import_types.size() == imports.size()); - - instance_ = wasm::Instance::make(store_.get(), module_.get(), imports.data()); - - const auto export_types = module_.get()->exports(); - const auto exports = instance_.get()->exports(); - ASSERT(export_types.size() == exports.size()); - - for (size_t i = 0; i < export_types.size(); i++) { - absl::string_view name(export_types[i]->name().get(), export_types[i]->name().size()); - auto export_type = export_types[i]->type(); - auto export_item = exports[i].get(); - ASSERT(export_type->kind() == export_item->kind()); - - switch (export_type->kind()) { - - case wasm::EXTERN_FUNC: { - 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_.insert_or_assign(name, export_item->func()->copy()); - } break; - - case wasm::EXTERN_GLOBAL: { - // TODO(PiotrSikora): add support when/if needed. - ENVOY_LOG(trace, "link(), import module global: {} ({}) --- IGNORED", name, - printValKind(export_type->global()->content()->kind())); - } break; - - case wasm::EXTERN_MEMORY: { - 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); - ASSERT(memory_ == nullptr); - memory_ = exports[i]->memory()->copy(); - } break; - - case wasm::EXTERN_TABLE: { - // TODO(PiotrSikora): add support when/if needed. - ENVOY_LOG(trace, "link(), import module table: {} (min: {} max: {}) --- IGNORED", name, - export_type->table()->limits().min, export_type->table()->limits().max); - } break; - } - } -} - -uint64_t V8::getMemorySize() { - ENVOY_LOG(trace, "getMemorySize()"); - return memory_->data_size(); -} - -absl::optional V8::getMemory(uint64_t pointer, uint64_t size) { - ENVOY_LOG(trace, "getMemory({}, {})", pointer, size); - ASSERT(memory_ != nullptr); - if (pointer + size > memory_->data_size()) { - return absl::nullopt; - } - return absl::string_view(memory_->data() + pointer, size); -} - -bool V8::setMemory(uint64_t pointer, uint64_t size, const void* data) { - ENVOY_LOG(trace, "setMemory({}, {})", pointer, size); - ASSERT(memory_ != nullptr); - if (pointer + size > memory_->data_size()) { - return false; - } - ::memcpy(memory_->data() + pointer, data, size); - return true; -} - -bool V8::getWord(uint64_t pointer, Word* word) { - ENVOY_LOG(trace, "getWord({})", pointer); - constexpr auto size = sizeof(uint32_t); - if (pointer + size > memory_->data_size()) { - return false; - } - uint32_t word32; - ::memcpy(&word32, memory_->data() + pointer, size); - word->u64_ = word32; - return true; -} - -bool V8::setWord(uint64_t pointer, Word word) { - ENVOY_LOG(trace, "setWord({}, {})", pointer, word.u64_); - constexpr auto size = sizeof(uint32_t); - if (pointer + size > memory_->data_size()) { - return false; - } - uint32_t word32 = word.u32(); - ::memcpy(memory_->data() + pointer, &word32, size); - return true; -} - -template -void V8::registerHostFunctionImpl(absl::string_view module_name, absl::string_view function_name, - void (*function)(void*, Args...)) { - 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>()); - auto func = wasm::Func::make( - store_.get(), type.get(), - [](void* data, const wasm::Val params[], wasm::Val[]) -> wasm::own { - auto func_data = reinterpret_cast(data); - 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_); - absl::apply(function, args); - ENVOY_LOG(trace, "[vm<-host] {} return: void", func_data->name_); - return nullptr; - }, - data.get()); - 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, "registerHostFunction(\"{}.{}\")", module_name, function_name); - auto data = std::make_unique(absl::StrCat(module_name, ".", function_name)); - auto type = wasm::FuncType::make(convertArgsTupleToValTypes>(), - convertArgsTupleToValTypes>()); - auto func = wasm::Func::make( - store_.get(), type.get(), - [](void* data, const wasm::Val params[], wasm::Val results[]) -> wasm::own { - auto func_data = reinterpret_cast(data); - 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_); - R rvalue = absl::apply(function, args); - results[0] = makeVal(rvalue); - ENVOY_LOG(trace, "[vm<-host] {} return: {}", func_data->name_, rvalue); - return nullptr; - }, - data.get()); - 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, "getModuleFunction(\"{}\")", function_name); - auto it = module_functions_.find(function_name); - if (it == module_functions_.end()) { - *function = nullptr; - return; - } - const wasm::Func* func = it->second.get(); - if (!equalValTypes(func->type()->params(), convertArgsTupleToValTypes>()) || - !equalValTypes(func->type()->results(), convertArgsTupleToValTypes>())) { - throw WasmVmException(fmt::format("Bad function signature for: {}", function_name)); - } - *function = [func, function_name](Context* context, Args... args) -> void { - wasm::Val params[] = {makeVal(args)...}; - 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, "[host<-vm] {} return: void", function_name); - }; -} - -template -void V8::getModuleFunctionImpl(absl::string_view function_name, - std::function* function) { - ENVOY_LOG(trace, "getModuleFunction(\"{}\")", function_name); - auto it = module_functions_.find(function_name); - if (it == module_functions_.end()) { - *function = nullptr; - return; - } - const wasm::Func* func = it->second.get(); - if (!equalValTypes(func->type()->params(), convertArgsTupleToValTypes>()) || - !equalValTypes(func->type()->results(), convertArgsTupleToValTypes>())) { - throw WasmVmException(fmt::format("Bad function signature for: {}", function_name)); - } - *function = [func, function_name](Context* context, Args... args) -> R { - wasm::Val params[] = {makeVal(args)...}; - 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( - fmt::format("Function: {} failed: {}", function_name, - absl::string_view(trap->message().get(), trap->message().size()))); - } - R rvalue = results[0].get::type>(); - ENVOY_LOG(trace, "[host<-vm] {} return: {}", function_name, rvalue); - return rvalue; - }; -} - -WasmVmPtr createVm(const Stats::ScopeSharedPtr& scope) { return std::make_unique(scope); } - -} // namespace V8 -} // namespace Wasm -} // namespace Common -} // namespace Extensions -} // namespace Envoy diff --git a/source/extensions/common/wasm/v8/v8.h b/source/extensions/common/wasm/v8/v8.h deleted file mode 100644 index a7288f0004a59..0000000000000 --- a/source/extensions/common/wasm/v8/v8.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include - -#include "extensions/common/wasm/wasm_vm.h" - -namespace Envoy { -namespace Extensions { -namespace Common { -namespace Wasm { -namespace V8 { - -WasmVmPtr createVm(const Stats::ScopeSharedPtr& scope); - -} // namespace V8 -} // namespace Wasm -} // namespace Common -} // namespace Extensions -} // namespace Envoy diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc new file mode 100644 index 0000000000000..ded72e71f41c3 --- /dev/null +++ b/source/extensions/common/wasm/wasm.cc @@ -0,0 +1,181 @@ +#include "extensions/common/wasm/wasm.h" + +#include + +#include +#include +#include +#include +#include + +#include "envoy/common/exception.h" +#include "envoy/extensions/wasm/v3/wasm.pb.validate.h" +#include "envoy/grpc/status.h" +#include "envoy/http/codes.h" +#include "envoy/local_info/local_info.h" +#include "envoy/thread_local/thread_local.h" + +#include "common/common/assert.h" +#include "common/common/base64.h" +#include "common/common/empty_string.h" +#include "common/common/enum_to_int.h" +#include "common/common/logger.h" + +#include "extensions/common/wasm/wasm_vm.h" +#include "extensions/common/wasm/well_known_names.h" + +#include "absl/base/casts.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/node_hash_map.h" +#include "absl/strings/str_cat.h" +#include "absl/synchronization/mutex.h" +#include "openssl/bytestring.h" +#include "openssl/hmac.h" +#include "openssl/sha.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +namespace { + +const std::string INLINE_STRING = ""; + +std::atomic active_wasm_; + +// Downcast WasmBase to the actual Wasm. +inline Wasm* getWasm(WasmHandleSharedPtr& base_wasm_handle) { + return static_cast(base_wasm_handle->wasm().get()); +} + +} // namespace + +Wasm::Wasm(absl::string_view runtime, absl::string_view vm_id, absl::string_view vm_configuration, + absl::string_view vm_key, Stats::ScopeSharedPtr scope, + Upstream::ClusterManager& cluster_manager, Event::Dispatcher& dispatcher) + : WasmBase(createWasmVm(runtime, scope), vm_id, vm_configuration, vm_key), scope_(scope), + cluster_manager_(cluster_manager), dispatcher_(dispatcher), + time_source_(dispatcher.timeSource()), + wasm_stats_(WasmStats{ + ALL_WASM_STATS(POOL_COUNTER_PREFIX(*scope_, absl::StrCat("wasm.", runtime, ".")), + POOL_GAUGE_PREFIX(*scope_, absl::StrCat("wasm.", runtime, ".")))}), + stat_name_set_(scope_->symbolTable().makeSet("Wasm").release()) { + active_wasm_++; + wasm_stats_.active_.set(active_wasm_); + wasm_stats_.created_.inc(); + ENVOY_LOG(debug, "Base Wasm created {} now active", active_wasm_); +} + +Wasm::Wasm(WasmHandleSharedPtr& base_wasm_handle, Event::Dispatcher& dispatcher) + : WasmBase(base_wasm_handle, + [&base_wasm_handle]() { + return createWasmVm( + getEnvoyWasmIntegration(*base_wasm_handle->wasm()->wasm_vm()).runtime(), + getWasm(base_wasm_handle)->scope_); + }), + scope_(getWasm(base_wasm_handle)->scope_), + cluster_manager_(getWasm(base_wasm_handle)->clusterManager()), dispatcher_(dispatcher), + time_source_(dispatcher.timeSource()), wasm_stats_(getWasm(base_wasm_handle)->wasm_stats_), + stat_name_set_(getWasm(base_wasm_handle)->stat_name_set()) { + active_wasm_++; + wasm_stats_.active_.set(active_wasm_); + wasm_stats_.created_.inc(); + ENVOY_LOG(debug, "Thread-Local Wasm created {} now active", active_wasm_); +} + +Wasm::~Wasm() { + active_wasm_--; + wasm_stats_.active_.set(active_wasm_); + ENVOY_LOG(debug, "~Wasm {} remaining active", active_wasm_); +} + +static void createWasmInternal(const VmConfig& vm_config, PluginSharedPtr plugin, + Stats::ScopeSharedPtr scope, + Upstream::ClusterManager& cluster_manager, + Init::Manager& init_manager, Event::Dispatcher& dispatcher, + Runtime::RandomGenerator& random, Api::Api& api, + std::unique_ptr root_context_for_testing, + Config::DataSource::RemoteAsyncDataProviderPtr& remote_data_provider, + CreateWasmCallback&& cb) { + std::string source, code; + if (vm_config.code().has_remote()) { + source = vm_config.code().remote().http_uri().uri(); + } else if (vm_config.code().has_local()) { + code = Config::DataSource::read(vm_config.code().local(), true, api); + source = Config::DataSource::getPath(vm_config.code().local()) + .value_or(code.empty() ? EMPTY_STRING : INLINE_STRING); + } + + auto callback = [vm_config, scope, &cluster_manager, &dispatcher, plugin, cb, source, + context_ptr = root_context_for_testing ? root_context_for_testing.release() + : nullptr](const std::string& code) { + std::unique_ptr context(context_ptr); + if (code.empty()) { + throw WasmException(fmt::format("Failed to load WASM code from {}", source)); + } + std::string configuration; + if (!vm_config.configuration().SerializeToString(&configuration)) { + throw WasmException(fmt::format("Failed to serialize vm configuration")); + } + auto vm_key = proxy_wasm::makeVmKey(vm_config.vm_id(), configuration, code); + cb(proxy_wasm::createWasm( + vm_key, code, plugin, + [&vm_config, &configuration, &scope, &cluster_manager, + &dispatcher](absl::string_view vm_key) { + return std::make_shared( + std::make_shared(vm_config.runtime(), vm_config.vm_id(), configuration, vm_key, + scope, cluster_manager, dispatcher)); + }, + vm_config.allow_precompiled(), std::move(context))); + }; + + if (vm_config.code().has_remote()) { + remote_data_provider = std::make_unique( + cluster_manager, init_manager, vm_config.code().remote(), dispatcher, random, true, + std::move(callback)); + } else if (vm_config.code().has_local()) { + callback(code); + } else { + callback(EMPTY_STRING); + } +} + +void createWasm(const VmConfig& vm_config, PluginSharedPtr plugin, Stats::ScopeSharedPtr scope, + Upstream::ClusterManager& cluster_manager, Init::Manager& init_manager, + Event::Dispatcher& dispatcher, Runtime::RandomGenerator& random, Api::Api& api, + Config::DataSource::RemoteAsyncDataProviderPtr& remote_data_provider, + CreateWasmCallback&& cb) { + createWasmInternal(vm_config, plugin, scope, cluster_manager, init_manager, dispatcher, random, + api, nullptr /* root_context_for_testing */, remote_data_provider, + std::move(cb)); +} + +void createWasmForTesting(const VmConfig& vm_config, PluginSharedPtr plugin, + Stats::ScopeSharedPtr scope, Upstream::ClusterManager& cluster_manager, + Init::Manager& init_manager, Event::Dispatcher& dispatcher, + Runtime::RandomGenerator& random, Api::Api& api, + std::unique_ptr root_context_for_testing, + Config::DataSource::RemoteAsyncDataProviderPtr& remote_data_provider, + CreateWasmCallback&& cb) { + createWasmInternal(vm_config, plugin, scope, cluster_manager, init_manager, dispatcher, random, + api, std::move(root_context_for_testing), remote_data_provider, std::move(cb)); +} + +WasmHandleSharedPtr getOrCreateThreadLocalWasm(WasmHandleSharedPtr base_wasm, + PluginSharedPtr plugin, + Event::Dispatcher& dispatcher) { + auto wasm_handle = proxy_wasm::getOrCreateThreadLocalWasm( + base_wasm, plugin, [&dispatcher](WasmHandleSharedPtr base_wasm) -> WasmHandleSharedPtr { + return std::make_shared(std::make_shared(base_wasm, dispatcher)); + }); + if (!wasm_handle) { + throw WasmException("Failed to configure WASM code"); + } + return wasm_handle; +} + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/common/wasm/wasm.h b/source/extensions/common/wasm/wasm.h new file mode 100644 index 0000000000000..53fff38ac11b8 --- /dev/null +++ b/source/extensions/common/wasm/wasm.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include + +#include "envoy/common/exception.h" +#include "envoy/extensions/wasm/v3/wasm.pb.validate.h" +#include "envoy/stats/scope.h" +#include "envoy/stats/stats.h" +#include "envoy/thread_local/thread_local.h" +#include "envoy/upstream/cluster_manager.h" + +#include "common/common/assert.h" +#include "common/common/logger.h" +#include "common/config/datasource.h" +#include "common/stats/symbol_table_impl.h" + +#include "extensions/common/wasm/context.h" +#include "extensions/common/wasm/wasm_vm.h" +#include "extensions/common/wasm/well_known_names.h" + +#include "include/proxy-wasm/exports.h" +#include "include/proxy-wasm/wasm.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +#define ALL_WASM_STATS(COUNTER, GAUGE) \ + COUNTER(created) \ + GAUGE(active, NeverImport) + +struct WasmStats { + ALL_WASM_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT) +}; + +using VmConfig = envoy::extensions::wasm::v3::VmConfig; + +// Wasm execution instance. Manages the Envoy side of the Wasm interface. +class Wasm : public WasmBase, Logger::Loggable { +public: + Wasm(absl::string_view runtime, absl::string_view vm_id, absl::string_view vm_configuration, + absl::string_view vm_key, Stats::ScopeSharedPtr scope, + Upstream::ClusterManager& cluster_manager, Event::Dispatcher& dispatcher); + Wasm(std::shared_ptr& other, Event::Dispatcher& dispatcher); + virtual ~Wasm(); + + Stats::StatNameSetSharedPtr stat_name_set() const { return stat_name_set_; } + Upstream::ClusterManager& clusterManager() const { return cluster_manager_; } + Event::Dispatcher& dispatcher() { return dispatcher_; } + + void error(absl::string_view message) override { throw WasmException(std::string(message)); } + +private: + friend class Context; + + Stats::ScopeSharedPtr scope_; + Upstream::ClusterManager& cluster_manager_; + Event::Dispatcher& dispatcher_; + TimeSource& time_source_; + + // Host Stats/Metrics + WasmStats wasm_stats_; + + // Plugin Stats/Metrics + Stats::StatNameSetSharedPtr stat_name_set_; + absl::flat_hash_map counters_; + absl::flat_hash_map gauges_; + absl::flat_hash_map histograms_; +}; +using WasmSharedPtr = std::shared_ptr; + +using WasmHandleSharedPtr = std::shared_ptr; +using CreateWasmCallback = std::function; + +// Create a high level Wasm VM with Envoy API support. Note: 'id' may be empty if this VM will not +// be shared by APIs (e.g. HTTP Filter + AccessLog). +void createWasm(const VmConfig& vm_config, PluginSharedPtr plugin_config, + Stats::ScopeSharedPtr scope, Upstream::ClusterManager& cluster_manager, + Init::Manager& init_manager, Event::Dispatcher& dispatcher, + Runtime::RandomGenerator& random, Api::Api& api, + Config::DataSource::RemoteAsyncDataProviderPtr& remote_data_provider, + CreateWasmCallback&& cb); + +void createWasmForTesting(const VmConfig& vm_config, PluginSharedPtr plugin, + Stats::ScopeSharedPtr scope, Upstream::ClusterManager& cluster_manager, + Init::Manager& init_manager, Event::Dispatcher& dispatcher, + Runtime::RandomGenerator& random, Api::Api& api, + std::unique_ptr root_context_for_testing, + Config::DataSource::RemoteAsyncDataProviderPtr& remote_data_provider, + CreateWasmCallback&& cb); + +WasmHandleSharedPtr getOrCreateThreadLocalWasm(WasmHandleSharedPtr base_wasm, + PluginSharedPtr plugin, + Event::Dispatcher& dispatcher); + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/common/wasm/wasm_vm.cc b/source/extensions/common/wasm/wasm_vm.cc index 9299eceba2d10..35c3cf053e9e5 100644 --- a/source/extensions/common/wasm/wasm_vm.cc +++ b/source/extensions/common/wasm/wasm_vm.cc @@ -2,27 +2,33 @@ #include -#include "extensions/common/wasm/null/null.h" -#include "extensions/common/wasm/v8/v8.h" #include "extensions/common/wasm/well_known_names.h" +#include "include/proxy-wasm/null.h" +#include "include/proxy-wasm/v8.h" + namespace Envoy { namespace Extensions { namespace Common { namespace Wasm { -thread_local Envoy::Extensions::Common::Wasm::Context* current_context_ = nullptr; -thread_local uint32_t effective_context_id_ = 0; +void EnvoyWasmVmIntegration::error(absl::string_view message) { + throw WasmException(std::string(message)); +} WasmVmPtr createWasmVm(absl::string_view runtime, const Stats::ScopeSharedPtr& scope) { if (runtime.empty()) { - throw WasmVmException("Failed to create WASM VM with unspecified runtime."); + throw WasmException("Failed to create WASM VM with unspecified runtime."); } else if (runtime == WasmRuntimeNames::get().Null) { - return Null::createVm(scope); + auto wasm = proxy_wasm::createNullVm(); + wasm->integration().reset(new EnvoyWasmVmIntegration(scope, runtime, "null")); + return wasm; } else if (runtime == WasmRuntimeNames::get().V8) { - return V8::createVm(scope); + auto wasm = proxy_wasm::createV8Vm(); + wasm->integration().reset(new EnvoyWasmVmIntegration(scope, runtime, "v8")); + return wasm; } else { - throw WasmVmException(fmt::format( + throw WasmException(fmt::format( "Failed to create WASM VM using {} runtime. Envoy was compiled without support for it.", runtime)); } diff --git a/source/extensions/common/wasm/wasm_vm.h b/source/extensions/common/wasm/wasm_vm.h index 3506eaaa0966b..83426a42b3e19 100644 --- a/source/extensions/common/wasm/wasm_vm.h +++ b/source/extensions/common/wasm/wasm_vm.h @@ -4,266 +4,74 @@ #include "envoy/common/exception.h" #include "envoy/stats/scope.h" +#include "envoy/stats/stats.h" +#include "envoy/stats/stats_macros.h" #include "common/common/logger.h" -#include "absl/types/optional.h" +#include "absl/strings/str_cat.h" +#include "include/proxy-wasm/wasm_vm.h" +#include "include/proxy-wasm/word.h" namespace Envoy { namespace Extensions { namespace Common { namespace Wasm { -class Context; - -// Represents a WASM-native word-sized datum. On 32-bit VMs, the high bits are always zero. -// The WASM/VM API treats all bits as significant. -struct Word { - Word(uint64_t w) : u64_(w) {} // Implicit conversion into Word. - uint32_t u32() const { return static_cast(u64_); } - uint64_t u64_; -}; - -inline std::ostream& operator<<(std::ostream& os, const Word& w) { return os << w.u64_; } - -// Convert Word type for use by 32-bit VMs. -template struct ConvertWordTypeToUint32 { - using type = T; // NOLINT(readability-identifier-naming) -}; -template <> struct ConvertWordTypeToUint32 { - using type = uint32_t; // NOLINT(readability-identifier-naming) -}; - -// Convert Word-based function types for 32-bit VMs. -template struct ConvertFunctionTypeWordToUint32 {}; -template struct ConvertFunctionTypeWordToUint32 { - using type = typename ConvertWordTypeToUint32::type (*)( - typename ConvertWordTypeToUint32::type...); +using Word = proxy_wasm::Word; +using WasmVm = proxy_wasm::WasmVm; +using Cloneable = proxy_wasm::Cloneable; +using ContextBase = proxy_wasm::ContextBase; + +/** + * Wasm host stats. + */ +#define ALL_VM_STATS(COUNTER, GAUGE) \ + COUNTER(created) \ + COUNTER(cloned) \ + GAUGE(active, NeverImport) + +struct VmStats { + ALL_VM_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT) }; -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)...)); +// Wasm VM data providing stats. +class EnvoyWasmVmIntegration : public proxy_wasm::WasmVmIntegration, + Logger::Loggable { +public: + EnvoyWasmVmIntegration(const Stats::ScopeSharedPtr& scope, absl::string_view runtime, + absl::string_view short_runtime) + : scope_(scope), runtime_(std::string(runtime)), short_runtime_(std::string(short_runtime)), + runtime_prefix_(absl::StrCat("wasm_vm.", short_runtime, ".")), + stats_(VmStats{ALL_VM_STATS(POOL_COUNTER_PREFIX(*scope_, runtime_prefix_), + POOL_GAUGE_PREFIX(*scope_, runtime_prefix_))}) { + stats_.created_.inc(); + stats_.active_.inc(); + ENVOY_LOG(debug, "WasmVm created {} now active", runtime_, stats_.active_.value()); } -}; -template void> -struct ConvertFunctionWordToUint32 { - static void convertFunctionWordToUint32(typename ConvertWordTypeToUint32::type... args) { - F(std::forward(args)...); + virtual ~EnvoyWasmVmIntegration() { + stats_.active_.dec(); + ENVOY_LOG(debug, "~WasmVm {} {} remaining active", runtime_, stats_.active_.value()); } -}; - -#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 -// WasmFuncType definition. -// - WasmFuncType takes 4 template parameter which are number of argument, return type, context type -// and param type respectively, resolve to a function type. -// For example `WasmFuncType<3, void, Context*, Word>` resolves to `void(Context*, Word, Word, -// Word)` -template -struct WasmFuncTypeHelper {}; - -template -struct WasmFuncTypeHelper { - // NOLINTNEXTLINE(readability-identifier-naming) - using type = typename WasmFuncTypeHelper::type; -}; - -template -struct WasmFuncTypeHelper<0, ReturnType, ContextType, ParamType, ReturnType(ContextType, Args...)> { - using type = ReturnType(ContextType, Args...); // NOLINT(readability-identifier-naming) -}; - -template -using WasmFuncType = typename WasmFuncTypeHelper::type; - -// Calls into the WASM VM. -// 1st arg is always a pointer to Context (Context*). -template using WasmCallVoid = std::function>; -template using WasmCallWord = std::function>; - -#define FOR_ALL_WASM_VM_EXPORTS(_f) \ - _f(WasmCallVoid<0>) _f(WasmCallVoid<1>) _f(WasmCallVoid<2>) _f(WasmCallVoid<3>) \ - _f(WasmCallVoid<5>) _f(WasmCallWord<1>) _f(WasmCallWord<2>) _f(WasmCallWord<3>) - -// Calls out of the WASM VM. -// 1st arg is always a pointer to raw_context (void*). -template using WasmCallbackVoid = WasmFuncType*; -template using WasmCallbackWord = WasmFuncType*; - -// Using the standard g++/clang mangling algorithm: -// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling-builtin -// Extended with W = Word -// Z = void, j = uint32_t, l = int64_t, m = uint64_t -using WasmCallback_WWl = Word (*)(void*, Word, int64_t); -using WasmCallback_WWlWW = Word (*)(void*, Word, int64_t, Word, Word); -using WasmCallback_WWm = Word (*)(void*, Word, uint64_t); -using WasmCallback_dd = double (*)(void*, double); - -#define FOR_ALL_WASM_VM_IMPORTS(_f) \ - _f(WasmCallbackVoid<0>) _f(WasmCallbackVoid<1>) _f(WasmCallbackVoid<2>) _f(WasmCallbackVoid<3>) \ - _f(WasmCallbackVoid<4>) _f(WasmCallbackWord<0>) _f(WasmCallbackWord<1>) \ - _f(WasmCallbackWord<2>) _f(WasmCallbackWord<3>) _f(WasmCallbackWord<4>) \ - _f(WasmCallbackWord<5>) _f(WasmCallbackWord<6>) _f(WasmCallbackWord<7>) \ - _f(WasmCallbackWord<8>) _f(WasmCallbackWord<9>) _f(WasmCallbackWord<10>) \ - _f(WasmCallback_WWl) _f(WasmCallback_WWlWW) _f(WasmCallback_WWm) \ - _f(WasmCallback_dd) - -enum class Cloneable { - NotCloneable, // VMs can not be cloned and should be created from scratch. - CompiledBytecode, // VMs can be cloned with compiled bytecode. - InstantiatedModule // VMs can be cloned from an instantiated module. -}; - -// Wasm VM instance. Provides the low level WASM interface. -class WasmVm : public Logger::Loggable { -public: - using WasmVmPtr = std::unique_ptr; - - virtual ~WasmVm() = default; - /** - * Return the runtime identifier. - * @return one of WasmRuntimeValues from well_known_names.h (e.g. "envoy.wasm.runtime.null"). - */ - virtual absl::string_view runtime() PURE; - - /** - * Whether or not the VM implementation supports cloning. Cloning is VM system dependent. - * When a VM is configured a single VM is instantiated to check that the .wasm file is valid and - * to do VM system specific initialization. In the case of WAVM this is potentially ahead-of-time - * compilation. Then, if cloning is supported, we clone that VM for each worker, potentially - * copying and sharing the initialized data structures for efficiency. Otherwise we create an new - * VM from scratch for each worker. - * @return one of enum Cloneable with the VMs cloneability. - */ - virtual Cloneable cloneable() PURE; - - /** - * Make a worker/thread-specific copy if supported by the underlying VM system (see cloneable() - * above). If not supported, the caller will need to create a new VM from scratch. If supported, - * the clone may share compiled code and other read-only data with the source VM. - * @return a clone of 'this' (e.g. for a different worker/thread). - */ - virtual WasmVmPtr clone() PURE; - - /** - * Load the WASM code from a file. Return true on success. Once the module is loaded it can be - * queried, e.g. to see which version of emscripten support is required. After loading, the - * appropriate ABI callbacks can be registered and then the module can be link()ed (see below). - * @param code the WASM binary code (or registered NullVm plugin name). - * @param allow_precompiled if true, allows supporting VMs (e.g. WAVM) to load the binary - * machine code from a user-defined section of the WASM file. Because that code is not verified by - * the envoy process it is up to the user to ensure that the code is both safe and is built for - * the linked in version of WAVM. - * @return whether or not the load was successful. - */ - virtual bool load(const std::string& code, bool allow_precompiled) PURE; - - /** - * Link the WASM code to the host-provided functions, e.g. the ABI. Prior to linking, the module - * should be loaded and the ABI callbacks registered (see above). Linking should be done once - * after load(). - * @param debug_name user-provided name for use in log and error messages. - */ - virtual void link(absl::string_view debug_name) PURE; - - /** - * Get size of the currently allocated memory in the VM. - * @return the size of memory in bytes. - */ - virtual uint64_t getMemorySize() PURE; - - /** - * Convert a block of memory in the VM to a string_view. - * @param pointer the offset into VM memory of the requested VM memory block. - * @param size the size of the requested VM memory block. - * @return if std::nullopt then the pointer/size pair were invalid, otherwise returns - * a host string_view pointing to the pointer/size pair in VM memory. - */ - virtual absl::optional getMemory(uint64_t pointer, uint64_t size) PURE; - - /** - * Set a block of memory in the VM, returns true on success, false if the pointer/size is invalid. - * @param pointer the offset into VM memory describing the start of a region of VM memory. - * @param size the size of the region of VM memory. - * @return whether or not the pointer/size pair was a valid VM memory block. - */ - virtual bool setMemory(uint64_t pointer, uint64_t size, const void* data) PURE; - - /** - * Get a VM native Word (e.g. sizeof(void*) or sizeof(size_t)) from VM memory, returns true on - * success, false if the pointer is invalid. WASM-32 VMs have 32-bit native words and WASM-64 VMs - * (not yet supported) will have 64-bit words as does the Null VM (compiled into 64-bit Envoy). - * This function can be used to chase pointers in VM memory. - * @param pointer the offset into VM memory describing the start of VM native word size block. - * @param data a pointer to a Word whose contents will be filled from the VM native word at - * 'pointer'. - * @return whether or not the pointer was to a valid VM memory block of VM native word size. - */ - virtual bool getWord(uint64_t pointer, Word* data) PURE; - - /** - * Set a Word in the VM, returns true on success, false if the pointer is invalid. - * See getWord above for details. This function can be used (for example) to set indirect pointer - * return values (e.g. proxy_getHeaderHapValue(... const char** value_ptr, size_t* value_size). - * @param pointer the offset into VM memory describing the start of VM native word size block. - * @param data a Word whose contents will be written in VM native word size at 'pointer'. - * @return whether or not the pointer was to a valid VM memory block of VM native word size. - */ - virtual bool setWord(uint64_t pointer, Word data) PURE; - - /** - * Get the contents of the custom section with the given name or "" if it does not exist. - * @param name the name of the custom section to get. - * @return the contents of the custom section (if any). The result will be empty if there - * is no such section. - */ - virtual absl::string_view getCustomSection(absl::string_view name) PURE; - - /** - * Get the name of the custom section that contains precompiled module. - * @return the name of the custom section that contains precompiled module. - */ - virtual absl::string_view getPrecompiledSectionName() PURE; + proxy_wasm::WasmVmIntegration* clone() override { + return new EnvoyWasmVmIntegration(scope_, runtime_, short_runtime_); + } + // void log(proxy_wasm::LogLevel level, absl::string_view message) override; + void error(absl::string_view message) override; - /** - * Get typed function exported by the WASM module. - */ -#define _GET_FUNCTION(_T) virtual void getFunction(absl::string_view function_name, _T* f) PURE; - FOR_ALL_WASM_VM_EXPORTS(_GET_FUNCTION) -#undef _GET_FUNCTION + const std::string& runtime() const { return runtime_; } - /** - * Register typed callbacks exported by the host environment. - */ -#define _REGISTER_CALLBACK(_T) \ - virtual void registerCallback(absl::string_view moduleName, absl::string_view function_name, \ - _T f, typename ConvertFunctionTypeWordToUint32<_T>::type) PURE; - FOR_ALL_WASM_VM_IMPORTS(_REGISTER_CALLBACK) -#undef _REGISTER_CALLBACK -}; -using WasmVmPtr = std::unique_ptr; +protected: + const Stats::ScopeSharedPtr scope_; + const std::string runtime_; + const std::string short_runtime_; + const std::string runtime_prefix_; + VmStats stats_; +}; // namespace Wasm -// Exceptions for issues with the WasmVm. -class WasmVmException : public EnvoyException { -public: - using EnvoyException::EnvoyException; -}; +inline const EnvoyWasmVmIntegration& getEnvoyWasmIntegration(proxy_wasm::WasmVm& wasm_vm) { + return *static_cast(wasm_vm.integration().get()); +} // Exceptions for issues with the WebAssembly code. class WasmException : public EnvoyException { @@ -271,34 +79,7 @@ class WasmException : public EnvoyException { using EnvoyException::EnvoyException; }; -// Thread local state set during a call into a WASM VM so that calls coming out of the -// VM can be attributed correctly to calling Filter. We use thread_local instead of ThreadLocal -// because this state is live only during the calls and does not need to be initialized consistently -// over all workers as with ThreadLocal data. -extern thread_local Envoy::Extensions::Common::Wasm::Context* current_context_; - -// Requested effective context set by code within the VM to request that the calls coming out of the -// VM be attributed to another filter, for example if a control plane gRPC comes back to the -// RootContext which effects some set of waiting filters. -extern thread_local uint32_t effective_context_id_; - -// Helper to save and restore thread local VM call context information to support reentrant calls. -// NB: this happens for example when a call from the VM invokes a handler which needs to _malloc -// memory in the VM. -struct SaveRestoreContext { - explicit SaveRestoreContext(Context* context) { - saved_context = current_context_; - saved_effective_context_id_ = effective_context_id_; - current_context_ = context; - effective_context_id_ = 0; // No effective context id. - } - ~SaveRestoreContext() { - current_context_ = saved_context; - effective_context_id_ = saved_effective_context_id_; - } - Context* saved_context; - uint32_t saved_effective_context_id_; -}; +using WasmVmPtr = std::unique_ptr; // Create a new low-level WASM VM using runtime of the given type (e.g. "envoy.wasm.runtime.wavm"). WasmVmPtr createWasmVm(absl::string_view runtime, const Stats::ScopeSharedPtr& scope); diff --git a/source/extensions/common/wasm/wasm_vm_base.h b/source/extensions/common/wasm/wasm_vm_base.h deleted file mode 100644 index a709534cba521..0000000000000 --- a/source/extensions/common/wasm/wasm_vm_base.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include "envoy/stats/scope.h" -#include "envoy/stats/stats.h" -#include "envoy/stats/stats_macros.h" - -#include "extensions/common/wasm/wasm_vm.h" - -#include "absl/strings/str_cat.h" - -namespace Envoy { -namespace Extensions { -namespace Common { -namespace Wasm { - -/** - * Wasm host stats. - */ -#define ALL_VM_STATS(COUNTER, GAUGE) \ - COUNTER(created) \ - COUNTER(cloned) \ - GAUGE(active, NeverImport) - -struct VmStats { - ALL_VM_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT) -}; - -// Wasm VM base instance. Provides common behavior (e.g. Stats). -class WasmVmBase : public WasmVm { -public: - WasmVmBase(const Stats::ScopeSharedPtr& scope, absl::string_view runtime) - : scope_(scope), runtime_prefix_(absl::StrCat("wasm_vm.", runtime, ".")), - runtime_(std::string(runtime)), - stats_(VmStats{ALL_VM_STATS(POOL_COUNTER_PREFIX(*scope_, runtime_prefix_), - POOL_GAUGE_PREFIX(*scope_, runtime_prefix_))}) { - stats_.created_.inc(); - stats_.active_.inc(); - ENVOY_LOG(debug, "WasmVm created {} now active", runtime_, stats_.active_.value()); - } - virtual ~WasmVmBase() { - stats_.active_.dec(); - ENVOY_LOG(debug, "~WasmVm {} {} remaining active", runtime_, stats_.active_.value()); - } - -protected: - const Stats::ScopeSharedPtr scope_; - const std::string runtime_prefix_; - const std::string runtime_; // The runtime e.g. "v8". - VmStats stats_; -}; - -} // namespace Wasm -} // namespace Common -} // namespace Extensions -} // namespace Envoy diff --git a/test/extensions/common/wasm/BUILD b/test/extensions/common/wasm/BUILD index bdc4969b141a2..d39546165cc32 100644 --- a/test/extensions/common/wasm/BUILD +++ b/test/extensions/common/wasm/BUILD @@ -20,3 +20,22 @@ envoy_cc_test( "//test/test_common:utility_lib", ], ) + +envoy_cc_test( + name = "wasm_test", + srcs = ["wasm_test.cc"], + data = [ + "//test/extensions/common/wasm/test_data:modules", + ], + external_deps = ["abseil_optional"], + deps = [ + "//source/common/event:dispatcher_lib", + "//source/common/stats:isolated_store_lib", + "//source/common/stats:stats_lib", + "//source/extensions/common/wasm:wasm_lib", + "//test/extensions/common/wasm/test_data:test_cpp_plugin", + "//test/mocks/server:server_mocks", + "//test/test_common:environment_lib", + "//test/test_common:simulated_time_system_lib", + ], +) diff --git a/test/extensions/common/wasm/test_data/BUILD b/test/extensions/common/wasm/test_data/BUILD index ef4f37386280f..40c8946c25154 100644 --- a/test/extensions/common/wasm/test_data/BUILD +++ b/test/extensions/common/wasm/test_data/BUILD @@ -2,6 +2,7 @@ licenses(["notice"]) # Apache 2 load( "//bazel:envoy_build_system.bzl", + "envoy_cc_library", "envoy_package", ) @@ -11,3 +12,20 @@ filegroup( name = "modules", srcs = glob(["*.wasm"]), ) + +envoy_cc_library( + name = "test_cpp_plugin", + srcs = [ + "test_cpp.cc", + "test_cpp_null_plugin.cc", + ], + copts = ["-DNULL_PLUGIN=1"], + deps = [ + "//external:abseil_node_hash_map", + "//source/common/common:assert_lib", + "//source/common/common:c_smart_ptr_lib", + "//source/extensions/common/wasm:wasm_hdr", + "//source/extensions/common/wasm:wasm_lib", + "//source/extensions/common/wasm:well_known_names", + ], +) diff --git a/test/extensions/common/wasm/test_data/Makefile b/test/extensions/common/wasm/test_data/Makefile new file mode 100644 index 0000000000000..6a560db28cd4e --- /dev/null +++ b/test/extensions/common/wasm/test_data/Makefile @@ -0,0 +1,6 @@ +# PROXY_WASM_CPP_SDK must be set in the environment +NO_CONTEXT=1 + +all: test_cpp.wasm bad_signature_cpp.wasm + +include $(PROXY_WASM_CPP_SDK)/Makefile.base diff --git a/test/extensions/common/wasm/test_data/Makefile.docker_cpp_builder b/test/extensions/common/wasm/test_data/Makefile.docker_cpp_builder new file mode 100644 index 0000000000000..f88f58029e6ff --- /dev/null +++ b/test/extensions/common/wasm/test_data/Makefile.docker_cpp_builder @@ -0,0 +1,6 @@ +DOCKER_SDK=/external_sdk + +all: test_cpp.wasm + chown ${uid}.${gid} *.wasm + +include ${DOCKER_SDK}/Makefile.base diff --git a/test/extensions/common/wasm/test_data/bad_signature_cpp.cc b/test/extensions/common/wasm/test_data/bad_signature_cpp.cc new file mode 100644 index 0000000000000..de745e2f4497e --- /dev/null +++ b/test/extensions/common/wasm/test_data/bad_signature_cpp.cc @@ -0,0 +1,13 @@ +// NOLINT(namespace-envoy) +#include + +#define EMSCRIPTEN_KEEPALIVE __attribute__((used)) __attribute__((visibility("default"))) + +extern "C" uint32_t proxy_log(uint32_t level, const char* logMessage, size_t messageSize); + +extern "C" EMSCRIPTEN_KEEPALIVE uint32_t proxy_on_configure(uint32_t, int bad, char* configuration, + int size) { + std::string message = "bad signature"; + proxy_log(4 /* error */, message.c_str(), message.size()); + return 1; +} diff --git a/test/extensions/common/wasm/test_data/bad_signature_cpp.wasm b/test/extensions/common/wasm/test_data/bad_signature_cpp.wasm new file mode 100644 index 0000000000000..dd54be38ceee0 Binary files /dev/null and b/test/extensions/common/wasm/test_data/bad_signature_cpp.wasm differ diff --git a/test/extensions/common/wasm/test_data/docker_build_cpp_wasm.sh b/test/extensions/common/wasm/test_data/docker_build_cpp_wasm.sh new file mode 100755 index 0000000000000..7e4f35ae5ec9e --- /dev/null +++ b/test/extensions/common/wasm/test_data/docker_build_cpp_wasm.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker run -e uid="$(id -u)" -e gid="$(id -g)" -v $PWD:/work -v $(realpath ../../../../../api/wasm/cpp):/external_sdk -w /work wasmsdk:v2 bash ./docker_cpp_builder.sh diff --git a/test/extensions/common/wasm/test_data/docker_cpp_builder.sh b/test/extensions/common/wasm/test_data/docker_cpp_builder.sh new file mode 100755 index 0000000000000..95047e8473395 --- /dev/null +++ b/test/extensions/common/wasm/test_data/docker_cpp_builder.sh @@ -0,0 +1,4 @@ +#!/bin/bash +source /root/emsdk/emsdk_env.sh +export PATH=/usr/local/bin:$PATH +make -j -f Makefile.docker_cpp_builder diff --git a/test/extensions/common/wasm/test_data/test_cpp.cc b/test/extensions/common/wasm/test_data/test_cpp.cc new file mode 100644 index 0000000000000..6c33af205f9ac --- /dev/null +++ b/test/extensions/common/wasm/test_data/test_cpp.cc @@ -0,0 +1,136 @@ +// NOLINT(namespace-envoy) +#include +#include +#include + +#ifndef NULL_PLUGIN +#include "proxy_wasm_intrinsics.h" +#else +#include "include/proxy-wasm/null_plugin.h" +#endif + +START_WASM_PLUGIN(CommonWasmTestCpp) + +static int* badptr = nullptr; +static float gNan = std::nan("1"); +static float gInfinity = INFINITY; + +#ifndef CHECK_RESULT +#define CHECK_RESULT(_c) \ + do { \ + if ((_c) != WasmResult::Ok) { \ + proxy_log(LogLevel::critical, #_c, sizeof(#_c) - 1); \ + abort(); \ + } \ + } while (0) +#endif + +WASM_EXPORT(uint32_t, proxy_on_vm_start, (uint32_t context_id, uint32_t configuration_size)) { + const char* configuration_ptr = nullptr; + size_t size; + proxy_get_buffer_bytes(WasmBufferType::VmConfiguration, 0, configuration_size, &configuration_ptr, + &size); + std::string configuration(configuration_ptr, size); + if (configuration == "logging") { + std::string trace_message = "test trace logging"; + proxy_log(LogLevel::trace, trace_message.c_str(), trace_message.size()); + std::string debug_message = "test debug logging"; + proxy_log(LogLevel::debug, debug_message.c_str(), debug_message.size()); + std::string warn_message = "test warn logging"; + proxy_log(LogLevel::warn, warn_message.c_str(), warn_message.size()); + std::string error_message = "test error logging"; + proxy_log(LogLevel::error, error_message.c_str(), error_message.size()); + } else if (configuration == "segv") { + std::string message = "before badptr"; + proxy_log(LogLevel::error, message.c_str(), message.size()); + ::free(const_cast(reinterpret_cast(configuration_ptr))); + *badptr = 1; + message = "after badptr"; + proxy_log(LogLevel::error, message.c_str(), message.size()); + } else if (configuration == "divbyzero") { + std::string message = "before div by zero"; + proxy_log(LogLevel::error, message.c_str(), message.size()); + int zero = context_id / 1000; + ::free(const_cast(reinterpret_cast(configuration_ptr))); + message = "divide by zero: " + std::to_string(100 / zero); + proxy_log(LogLevel::error, message.c_str(), message.size()); + } else if (configuration == "globals") { + std::string message = "NaN " + std::to_string(gNan); + proxy_log(LogLevel::warn, message.c_str(), message.size()); + message = "inf " + std::to_string(gInfinity); + proxy_log(LogLevel::warn, message.c_str(), message.size()); + message = "inf " + std::to_string(1.0 / 0.0); + proxy_log(LogLevel::warn, message.c_str(), message.size()); + message = std::string("inf ") + (std::isinf(gInfinity) ? "inf" : "nan"); + proxy_log(LogLevel::warn, message.c_str(), message.size()); + } else if (configuration == "stats") { + uint32_t c, g, h; + + std::string name = "test_counter"; + CHECK_RESULT(proxy_define_metric(MetricType::Counter, name.data(), name.size(), &c)); + name = "test_gauge"; + CHECK_RESULT(proxy_define_metric(MetricType::Gauge, name.data(), name.size(), &g)); + name = "test_historam"; + CHECK_RESULT(proxy_define_metric(MetricType::Histogram, name.data(), name.size(), &h)); + + CHECK_RESULT(proxy_increment_metric(c, 1)); + CHECK_RESULT(proxy_record_metric(g, 2)); + CHECK_RESULT(proxy_record_metric(h, 3)); + + uint64_t value; + std::string message; + CHECK_RESULT(proxy_get_metric(c, &value)); + message = std::string("get counter = ") + std::to_string(value); + proxy_log(LogLevel::trace, message.c_str(), message.size()); + CHECK_RESULT(proxy_increment_metric(c, 1)); + CHECK_RESULT(proxy_get_metric(c, &value)); + message = std::string("get counter = ") + std::to_string(value); + proxy_log(LogLevel::debug, message.c_str(), message.size()); + CHECK_RESULT(proxy_record_metric(c, 3)); + CHECK_RESULT(proxy_get_metric(c, &value)); + message = std::string("get counter = ") + std::to_string(value); + proxy_log(LogLevel::info, message.c_str(), message.size()); + CHECK_RESULT(proxy_get_metric(g, &value)); + message = std::string("get gauge = ") + std::to_string(value); + proxy_log(LogLevel::warn, message.c_str(), message.size()); + // Get on histograms is not supported. + if (proxy_get_metric(h, &value) != WasmResult::Ok) { + message = std::string("get histogram = Unsupported"); + proxy_log(LogLevel::error, message.c_str(), message.size()); + } + } else { + std::string message = "on_vm_start " + configuration; + proxy_log(LogLevel::info, message.c_str(), message.size()); + } + ::free(const_cast(reinterpret_cast(configuration_ptr))); + return 1; +} + +WASM_EXPORT(uint32_t, proxy_on_configure, (uint32_t, uint32_t configuration_size)) { + const char* configuration_ptr = nullptr; + size_t size; + proxy_get_buffer_bytes(WasmBufferType::PluginConfiguration, 0, configuration_size, + &configuration_ptr, &size); + std::string configuration(configuration_ptr, size); + if (configuration == "done") { + proxy_done(); + } else { + std::string message = "on_configuration " + configuration; + proxy_log(LogLevel::info, message.c_str(), message.size()); + } + ::free(const_cast(reinterpret_cast(configuration_ptr))); + return 1; +} + +WASM_EXPORT(uint32_t, proxy_on_done, (uint32_t)) { + std::string message = "on_done logging"; + proxy_log(LogLevel::info, message.c_str(), message.size()); + return 0; +} + +WASM_EXPORT(void, proxy_on_delete, (uint32_t)) { + std::string message = "on_delete logging"; + proxy_log(LogLevel::info, message.c_str(), message.size()); +} + +END_WASM_PLUGIN diff --git a/test/extensions/common/wasm/test_data/test_cpp.wasm b/test/extensions/common/wasm/test_data/test_cpp.wasm new file mode 100644 index 0000000000000..c0d750ff69326 Binary files /dev/null and b/test/extensions/common/wasm/test_data/test_cpp.wasm differ diff --git a/test/extensions/common/wasm/test_data/test_cpp_null_plugin.cc b/test/extensions/common/wasm/test_data/test_cpp_null_plugin.cc new file mode 100644 index 0000000000000..d8665f7b28c02 --- /dev/null +++ b/test/extensions/common/wasm/test_data/test_cpp_null_plugin.cc @@ -0,0 +1,15 @@ +// NOLINT(namespace-envoy) +#include "include/proxy-wasm/null_plugin.h" + +namespace proxy_wasm { +namespace null_plugin { +namespace CommonWasmTestCpp { +NullPluginRegistry* context_registry_; +} // namespace CommonWasmTestCpp + +RegisterNullVmPluginFactory register_common_wasm_test_cpp_plugin("CommonWasmTestCpp", []() { + return std::make_unique(CommonWasmTestCpp::context_registry_); +}); + +} // namespace null_plugin +} // namespace proxy_wasm diff --git a/test/extensions/common/wasm/test_data/test_rust.wasm b/test/extensions/common/wasm/test_data/test_rust.wasm index 6af3c924447fe..fdb7828c247f7 100755 Binary files a/test/extensions/common/wasm/test_data/test_rust.wasm and b/test/extensions/common/wasm/test_data/test_rust.wasm differ diff --git a/test/extensions/common/wasm/wasm_test.cc b/test/extensions/common/wasm/wasm_test.cc new file mode 100644 index 0000000000000..34fa4d595bc69 --- /dev/null +++ b/test/extensions/common/wasm/wasm_test.cc @@ -0,0 +1,325 @@ +#include + +#include "common/event/dispatcher_impl.h" +#include "common/stats/isolated_store_impl.h" + +#include "extensions/common/wasm/wasm.h" + +#include "test/mocks/server/mocks.h" +#include "test/mocks/upstream/mocks.h" +#include "test/test_common/environment.h" +#include "test/test_common/utility.h" + +#include "absl/types/optional.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::Eq; + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +class TestContext : public Extensions::Common::Wasm::Context { +public: + TestContext(Extensions::Common::Wasm::Wasm* wasm) : Extensions::Common::Wasm::Context(wasm) {} + TestContext(Extensions::Common::Wasm::Wasm* wasm, + Extensions::Common::Wasm::PluginSharedPtr plugin) + : Extensions::Common::Wasm::Context(wasm, plugin) {} + TestContext(Extensions::Common::Wasm::Wasm* wasm, uint32_t root_context_id, + Extensions::Common::Wasm::PluginSharedPtr plugin) + : Extensions::Common::Wasm::Context(wasm, root_context_id, plugin) {} + ~TestContext() override {} + proxy_wasm::WasmResult log(uint64_t level, absl::string_view message) override { + std::cerr << std::string(message) << "\n"; + log_(static_cast(level), message); + return proxy_wasm::WasmResult::Ok; + } + MOCK_METHOD2(log_, void(spdlog::level::level_enum level, absl::string_view message)); +}; + +class WasmCommonTest : public testing::TestWithParam {}; + +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmCommonTest, testing::Values("v8", "null")); + +TEST_P(WasmCommonTest, Logging) { + Stats::IsolatedStoreImpl stats_store; + Api::ApiPtr api = Api::createApiForTest(stats_store); + Upstream::MockClusterManager cluster_manager; + Event::DispatcherPtr dispatcher(api->allocateDispatcher()); + auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); + NiceMock local_info; + auto name = ""; + auto root_id = ""; + auto vm_id = ""; + auto vm_configuration = "logging"; + auto plugin_configuration = "configure-test"; + std::string code; + if (GetParam() == "v8") { + code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + absl::StrCat("{{ test_rundir }}/test/extensions/common/wasm/test_data/test_cpp.wasm"))); + } else { + // The name of the Null VM plugin. + code = "CommonWasmTestCpp"; + } + EXPECT_FALSE(code.empty()); + auto plugin = std::make_shared( + name, root_id, vm_id, plugin_configuration, + envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); + auto vm_key = proxy_wasm::makeVmKey(vm_id, vm_configuration, code); + auto wasm = std::make_shared( + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, scope, + cluster_manager, *dispatcher); + EXPECT_NE(wasm, nullptr); + EXPECT_THROW_WITH_MESSAGE(wasm->error("foo"), Extensions::Common::Wasm::WasmException, "foo"); + auto wasm_weak = std::weak_ptr(wasm); + auto wasm_handle = std::make_shared(std::move(wasm)); + auto context = std::make_unique(wasm_weak.lock().get()); + auto vm_context = std::make_unique(wasm_weak.lock().get(), plugin); + + EXPECT_CALL(*context, log_(spdlog::level::trace, Eq("test trace logging"))); + EXPECT_CALL(*context, log_(spdlog::level::debug, Eq("test debug logging"))); + EXPECT_CALL(*context, log_(spdlog::level::warn, Eq("test warn logging"))); + EXPECT_CALL(*context, log_(spdlog::level::err, Eq("test error logging"))); + EXPECT_CALL(*context, log_(spdlog::level::info, Eq("on_configuration configure-test"))); + EXPECT_CALL(*context, log_(spdlog::level::info, Eq("on_done logging"))); + EXPECT_CALL(*context, log_(spdlog::level::info, Eq("on_delete logging"))); + + EXPECT_TRUE(wasm_weak.lock()->initialize(code, false)); + auto thread_local_wasm = std::make_shared(wasm_handle, *dispatcher); + thread_local_wasm.reset(); + wasm_weak.lock()->setContext(context.get()); + auto root_context = context.get(); + wasm_weak.lock()->startForTesting(std::move(context), plugin); + wasm_weak.lock()->configure(root_context, plugin); + + wasm_handle.reset(); + dispatcher->run(Event::Dispatcher::RunType::NonBlock); + // This will fault on nullptr if wasm has been deleted. + plugin->plugin_configuration_ = "done"; + wasm_weak.lock()->configure(root_context, plugin); + dispatcher->run(Event::Dispatcher::RunType::NonBlock); + dispatcher->clearDeferredDeleteList(); +} + +TEST_P(WasmCommonTest, BadSignature) { + if (GetParam() != "v8") { + return; + } + Stats::IsolatedStoreImpl stats_store; + Api::ApiPtr api = Api::createApiForTest(stats_store); + Upstream::MockClusterManager cluster_manager; + Event::DispatcherPtr dispatcher(api->allocateDispatcher()); + auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); + NiceMock local_info; + auto name = ""; + auto root_id = ""; + auto vm_id = ""; + auto vm_configuration = ""; + auto plugin_configuration = ""; + const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/common/wasm/test_data/bad_signature_cpp.wasm")); + EXPECT_FALSE(code.empty()); + auto plugin = std::make_shared( + name, root_id, vm_id, plugin_configuration, + envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); + auto vm_key = proxy_wasm::makeVmKey(vm_id, vm_configuration, code); + auto wasm = std::make_unique( + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, scope, + cluster_manager, *dispatcher); + EXPECT_NE(wasm, nullptr); + EXPECT_THROW_WITH_MESSAGE(wasm->initialize(code, false), Extensions::Common::Wasm::WasmException, + "Bad function signature for: proxy_on_configure"); +} + +TEST_P(WasmCommonTest, Segv) { + if (GetParam() != "v8") { + return; + } + Stats::IsolatedStoreImpl stats_store; + Api::ApiPtr api = Api::createApiForTest(stats_store); + Upstream::MockClusterManager cluster_manager; + Event::DispatcherPtr dispatcher(api->allocateDispatcher()); + auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); + NiceMock local_info; + auto name = ""; + auto root_id = ""; + auto vm_id = ""; + auto vm_configuration = "segv"; + auto plugin_configuration = ""; + const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_cpp.wasm")); + EXPECT_FALSE(code.empty()); + auto plugin = std::make_shared( + name, root_id, vm_id, plugin_configuration, + envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); + auto vm_key = proxy_wasm::makeVmKey(vm_id, vm_configuration, code); + auto wasm = std::make_unique( + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, scope, + cluster_manager, *dispatcher); + auto context = std::make_unique(wasm.get()); + EXPECT_CALL(*context, log_(spdlog::level::err, Eq("before badptr"))); + EXPECT_TRUE(wasm->initialize(code, false)); + + EXPECT_THROW_WITH_MESSAGE( + wasm->startForTesting(std::move(context), plugin), Extensions::Common::Wasm::WasmException, + "Function: proxy_on_vm_start failed: Uncaught RuntimeError: unreachable"); +} + +TEST_P(WasmCommonTest, DivByZero) { + if (GetParam() != "v8") { + return; + } + Stats::IsolatedStoreImpl stats_store; + Api::ApiPtr api = Api::createApiForTest(stats_store); + Upstream::MockClusterManager cluster_manager; + Event::DispatcherPtr dispatcher(api->allocateDispatcher()); + auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); + NiceMock local_info; + auto name = ""; + auto root_id = ""; + auto vm_id = ""; + auto vm_configuration = "divbyzero"; + auto plugin_configuration = ""; + const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_cpp.wasm")); + EXPECT_FALSE(code.empty()); + auto plugin = std::make_shared( + name, root_id, vm_id, plugin_configuration, + envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); + auto vm_key = proxy_wasm::makeVmKey(vm_id, vm_configuration, code); + auto wasm = std::make_unique( + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, scope, + cluster_manager, *dispatcher); + EXPECT_NE(wasm, nullptr); + auto context = std::make_unique(wasm.get()); + EXPECT_CALL(*context, log_(spdlog::level::err, Eq("before div by zero"))); + EXPECT_TRUE(wasm->initialize(code, false)); + + EXPECT_THROW_WITH_MESSAGE( + wasm->startForTesting(std::move(context), plugin), Extensions::Common::Wasm::WasmException, + "Function: proxy_on_vm_start failed: Uncaught RuntimeError: divide by zero"); +} + +TEST_P(WasmCommonTest, EmscriptenVersion) { + if (GetParam() != "v8") { + return; + } + Stats::IsolatedStoreImpl stats_store; + Api::ApiPtr api = Api::createApiForTest(stats_store); + Upstream::MockClusterManager cluster_manager; + Event::DispatcherPtr dispatcher(api->allocateDispatcher()); + auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); + NiceMock local_info; + auto name = ""; + auto root_id = ""; + auto vm_id = ""; + auto vm_configuration = ""; + auto plugin_configuration = ""; + const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_cpp.wasm")); + EXPECT_FALSE(code.empty()); + auto plugin = std::make_shared( + name, root_id, vm_id, plugin_configuration, + envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); + auto vm_key = proxy_wasm::makeVmKey(vm_id, vm_configuration, code); + auto wasm = std::make_unique( + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, scope, + cluster_manager, *dispatcher); + EXPECT_NE(wasm, nullptr); + auto context = std::make_unique(wasm.get()); + EXPECT_TRUE(wasm->initialize(code, false)); + + uint32_t major = 9, minor = 9, abi_major = 9, abi_minor = 9; + EXPECT_TRUE(wasm->getEmscriptenVersion(&major, &minor, &abi_major, &abi_minor)); + EXPECT_EQ(major, 0); + EXPECT_LE(minor, 3); + // Up to (at least) emsdk 1.39.6. + EXPECT_EQ(abi_major, 0); + EXPECT_LE(abi_minor, 20); +} + +TEST_P(WasmCommonTest, IntrinsicGlobals) { + Stats::IsolatedStoreImpl stats_store; + Api::ApiPtr api = Api::createApiForTest(stats_store); + Upstream::MockClusterManager cluster_manager; + Event::DispatcherPtr dispatcher(api->allocateDispatcher()); + auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); + NiceMock local_info; + auto name = ""; + auto root_id = ""; + auto vm_id = ""; + auto vm_configuration = "globals"; + auto plugin_configuration = ""; + std::string code; + if (GetParam() == "v8") { + code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + absl::StrCat("{{ test_rundir }}/test/extensions/common/wasm/test_data/test_cpp.wasm"))); + } else { + // The name of the Null VM plugin. + code = "CommonWasmTestCpp"; + } + EXPECT_FALSE(code.empty()); + auto plugin = std::make_shared( + name, root_id, vm_id, plugin_configuration, + envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); + auto vm_key = proxy_wasm::makeVmKey(vm_id, vm_configuration, code); + auto wasm = std::make_unique( + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, scope, + cluster_manager, *dispatcher); + EXPECT_NE(wasm, nullptr); + auto context = std::make_unique(wasm.get()); + EXPECT_CALL(*context, log_(spdlog::level::warn, Eq("NaN nan"))); + EXPECT_CALL(*context, log_(spdlog::level::warn, Eq("inf inf"))).Times(3); + EXPECT_TRUE(wasm->initialize(code, false)); + wasm->startForTesting(std::move(context), plugin); +} + +TEST_P(WasmCommonTest, Stats) { + Stats::IsolatedStoreImpl stats_store; + Api::ApiPtr api = Api::createApiForTest(stats_store); + Upstream::MockClusterManager cluster_manager; + Event::DispatcherPtr dispatcher(api->allocateDispatcher()); + auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); + NiceMock local_info; + auto name = ""; + auto root_id = ""; + auto vm_id = ""; + auto vm_configuration = "stats"; + auto plugin_configuration = ""; + std::string code; + if (GetParam() == "v8") { + code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + absl::StrCat("{{ test_rundir }}/test/extensions/common/wasm/test_data/test_cpp.wasm"))); + } else { + // The name of the Null VM plugin. + code = "CommonWasmTestCpp"; + } + EXPECT_FALSE(code.empty()); + auto plugin = std::make_shared( + name, root_id, vm_id, plugin_configuration, + envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); + auto vm_key = proxy_wasm::makeVmKey(vm_id, vm_configuration, code); + auto wasm = std::make_unique( + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, scope, + cluster_manager, *dispatcher); + EXPECT_NE(wasm, nullptr); + auto context = std::make_unique(wasm.get()); + + EXPECT_CALL(*context, log_(spdlog::level::trace, Eq("get counter = 1"))); + EXPECT_CALL(*context, log_(spdlog::level::debug, Eq("get counter = 2"))); + // recordMetric on a Counter is the same as increment. + EXPECT_CALL(*context, log_(spdlog::level::info, Eq("get counter = 5"))); + EXPECT_CALL(*context, log_(spdlog::level::warn, Eq("get gauge = 2"))); + // Get is not supported on histograms. + EXPECT_CALL(*context, log_(spdlog::level::err, Eq("get histogram = Unsupported"))); + + EXPECT_TRUE(wasm->initialize(code, false)); + wasm->startForTesting(std::move(context), plugin); +} + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/common/wasm/wasm_vm_test.cc b/test/extensions/common/wasm/wasm_vm_test.cc index c206994ef6e1d..7630fe158b4b8 100644 --- a/test/extensions/common/wasm/wasm_vm_test.cc +++ b/test/extensions/common/wasm/wasm_vm_test.cc @@ -2,7 +2,6 @@ #include "common/stats/isolated_store_impl.h" -#include "extensions/common/wasm/null/null_vm_plugin.h" #include "extensions/common/wasm/wasm_vm.h" #include "test/test_common/environment.h" @@ -10,7 +9,10 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "include/proxy-wasm/null_vm_plugin.h" +using proxy_wasm::WasmCallVoid; +using proxy_wasm::WasmCallWord; using testing::HasSubstr; using testing::Return; @@ -20,7 +22,7 @@ namespace Common { namespace Wasm { namespace { -class TestNullVmPlugin : public Null::NullVmPlugin { +class TestNullVmPlugin : public proxy_wasm::NullVmPlugin { public: TestNullVmPlugin() = default; ~TestNullVmPlugin() override = default; @@ -28,22 +30,13 @@ class TestNullVmPlugin : public Null::NullVmPlugin { MOCK_METHOD(void, start, ()); }; -class PluginFactory : public Null::NullVmPluginFactory { -public: - PluginFactory() = default; - - std::string name() const override { return "test_null_vm_plugin"; } - std::unique_ptr create() const override; -}; - TestNullVmPlugin* test_null_vm_plugin_ = nullptr; -Envoy::Registry::RegisterFactory register_; -std::unique_ptr PluginFactory::create() const { - auto result = std::make_unique(); - test_null_vm_plugin_ = result.get(); - return result; -} +proxy_wasm::RegisterNullVmPluginFactory register_test_null_vm_plugin("test_null_vm_plugin", []() { + auto plugin = std::make_unique(); + test_null_vm_plugin_ = plugin.get(); + return plugin; +}); class BaseVmTest : public testing::Test { public: @@ -55,12 +48,12 @@ class BaseVmTest : public testing::Test { }; TEST_F(BaseVmTest, NoRuntime) { - EXPECT_THROW_WITH_MESSAGE(createWasmVm("", scope_), WasmVmException, + EXPECT_THROW_WITH_MESSAGE(createWasmVm("", scope_), WasmException, "Failed to create WASM VM with unspecified runtime."); } TEST_F(BaseVmTest, BadRuntime) { - EXPECT_THROW_WITH_MESSAGE(createWasmVm("envoy.wasm.runtime.invalid", scope_), WasmVmException, + EXPECT_THROW_WITH_MESSAGE(createWasmVm("envoy.wasm.runtime.invalid", scope_), WasmException, "Failed to create WASM VM using envoy.wasm.runtime.invalid runtime. " "Envoy was compiled without support for it."); } @@ -68,7 +61,7 @@ TEST_F(BaseVmTest, BadRuntime) { TEST_F(BaseVmTest, NullVmStartup) { auto wasm_vm = createWasmVm("envoy.wasm.runtime.null", scope_); EXPECT_TRUE(wasm_vm != nullptr); - EXPECT_TRUE(wasm_vm->runtime() == "envoy.wasm.runtime.null"); + EXPECT_TRUE(wasm_vm->runtime() == "null"); EXPECT_TRUE(wasm_vm->cloneable() == Cloneable::InstantiatedModule); auto wasm_vm_clone = wasm_vm->clone(); EXPECT_TRUE(wasm_vm_clone != nullptr); @@ -149,7 +142,7 @@ TEST_P(WasmVmTest, V8BadCode) { TEST_P(WasmVmTest, V8Code) { auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8", scope_); ASSERT_TRUE(wasm_vm != nullptr); - EXPECT_TRUE(wasm_vm->runtime() == "envoy.wasm.runtime.v8"); + EXPECT_TRUE(wasm_vm->runtime() == "v8"); auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_rust.wasm")); @@ -175,21 +168,21 @@ TEST_P(WasmVmTest, V8BadHostFunctions) { EXPECT_TRUE(wasm_vm->load(code, GetParam())); wasm_vm->registerCallback("env", "random", &random, CONVERT_FUNCTION_WORD_TO_UINT32(random)); - EXPECT_THROW_WITH_MESSAGE(wasm_vm->link("test"), WasmVmException, + EXPECT_THROW_WITH_MESSAGE(wasm_vm->link("test"), WasmException, "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, + EXPECT_THROW_WITH_MESSAGE(wasm_vm->link("test"), WasmException, "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, + EXPECT_THROW_WITH_MESSAGE(wasm_vm->link("test"), WasmException, "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, + EXPECT_THROW_WITH_MESSAGE(wasm_vm->link("test"), WasmException, "Failed to load WASM module due to an import type mismatch: env.pong, " "want: i32 -> void, but host exports: f64 -> f64"); } @@ -215,10 +208,10 @@ TEST_P(WasmVmTest, V8BadModuleFunctions) { wasm_vm->getFunction("nonexistent", &sum); EXPECT_TRUE(sum == nullptr); - EXPECT_THROW_WITH_MESSAGE(wasm_vm->getFunction("ping", &sum), WasmVmException, + EXPECT_THROW_WITH_MESSAGE(wasm_vm->getFunction("ping", &sum), WasmException, "Bad function signature for: ping"); - EXPECT_THROW_WITH_MESSAGE(wasm_vm->getFunction("sum", &ping), WasmVmException, + EXPECT_THROW_WITH_MESSAGE(wasm_vm->getFunction("sum", &ping), WasmException, "Bad function signature for: sum"); } diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 24ba7e79890ca..283ab22381f76 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -533,6 +533,7 @@ dynamodb emplace emplaced emscripten +emsdk enablement encodings endian