| /* | |
| * Copyright 2026 WebAssembly Community Group participants | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include <string> | |
| #include <vector> | |
| #include "support/js-embedded-module.h" | |
| #include "gtest/gtest.h" | |
| using namespace wasm; | |
| namespace { | |
| std::vector<char> makeBytes(std::initializer_list<uint8_t> list) { | |
| std::vector<char> result; | |
| result.reserve(list.size()); | |
| for (uint8_t b : list) { | |
| result.push_back(static_cast<char>(b)); | |
| } | |
| return result; | |
| } | |
| } // namespace | |
| TEST(JsEmbeddedModuleTest, HexArrayLiteral) { | |
| std::string js = R"( | |
| let bytes = new Uint8Array([ | |
| 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00 | |
| ]); | |
| )"; | |
| auto modules = findEmbeddedModules(js); | |
| ASSERT_EQ(modules.size(), 1u); | |
| EXPECT_EQ(modules[0].bytes, | |
| makeBytes({0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00})); | |
| EXPECT_EQ(js[modules[0].start], '['); | |
| EXPECT_EQ(js[modules[0].end - 1], ']'); | |
| } | |
| TEST(JsEmbeddedModuleTest, VariousNumericFormatsAndTrailingComma) { | |
| std::string js = R"( | |
| var raw = [ | |
| 0, 97, 115, 109, | |
| 0o1, 0b0000_0000, +1, -1, -128, 0Xff, | |
| ]; | |
| )"; | |
| auto modules = findEmbeddedModules(js); | |
| ASSERT_EQ(modules.size(), 1u); | |
| EXPECT_EQ( | |
| modules[0].bytes, | |
| makeBytes({0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x01, 0xff, 0x80, 0xff})); | |
| } | |
| TEST(JsEmbeddedModuleTest, InnerCommentsAndWhitespace) { | |
| std::string js = R"( | |
| const buf = [ | |
| // Wasm magic | |
| 0x00, 0x61, /* 'a' */ 0x73, 0x6d, | |
| /* version 1 */ | |
| 0x01, 0x00, 0x00, 0x00 // end of header | |
| /* trailing comment */ | |
| ]; | |
| )"; | |
| auto modules = findEmbeddedModules(js); | |
| ASSERT_EQ(modules.size(), 1u); | |
| EXPECT_EQ(modules[0].bytes, | |
| makeBytes({0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00})); | |
| } | |
| TEST(JsEmbeddedModuleTest, IgnoresStringsAndComments) { | |
| std::string js = R"( | |
| // [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x01] | |
| /* | |
| [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x02] | |
| */ | |
| const s1 = "[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x03]"; | |
| const s2 = 'escaped \' [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x04]'; | |
| const s3 = `multiline template \` | |
| [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x05] | |
| `; | |
| const actual = [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x06]; | |
| )"; | |
| auto modules = findEmbeddedModules(js); | |
| ASSERT_EQ(modules.size(), 1u); | |
| EXPECT_EQ(modules[0].bytes, | |
| makeBytes({0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x06})); | |
| } | |
| TEST(JsEmbeddedModuleTest, EarlyRejectionAndNestedArrays) { | |
| std::string js = R"( | |
| let empty = []; | |
| let tooShort = [0x00, 0x61, 0x73]; | |
| let nonWasm = [1, 2, 3, 4, 5, 6, 7, 8]; | |
| let wrongMagic = [0x00, 0x61, 0x73, 0x6e, 1, 0, 0, 1]; | |
| let outOfRangePos = [0x00, 0x61, 0x73, 0x6d, 256, 0, 0, 2]; | |
| let outOfRangeNeg = [0x00, 0x61, 0x73, 0x6d, -129, 0, 0, 3]; | |
| let nonConstant = [0x00, 0x61, 0x73, 0x6d, foo(), 0, 0, 4]; | |
| let nested = [[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x05]]; | |
| )"; | |
| auto modules = findEmbeddedModules(js); | |
| ASSERT_EQ(modules.size(), 1u); | |
| EXPECT_EQ(modules[0].bytes, | |
| makeBytes({0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x05})); | |
| EXPECT_EQ(js.substr(modules[0].start, modules[0].end - modules[0].start), | |
| "[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x05]"); | |
| } | |
| TEST(JsEmbeddedModuleTest, MultiModuleReverseOrderSplicing) { | |
| std::string js = R"( | |
| const m0 = new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a]); | |
| const m1 = new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0b]); | |
| const m2 = new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0c]); | |
| )"; | |
| auto modules = findEmbeddedModules(js); | |
| ASSERT_EQ(modules.size(), 3u); | |
| EXPECT_EQ(static_cast<uint8_t>(modules[0].bytes.back()), 0x0au); | |
| EXPECT_EQ(static_cast<uint8_t>(modules[1].bytes.back()), 0x0bu); | |
| EXPECT_EQ(static_cast<uint8_t>(modules[2].bytes.back()), 0x0cu); | |
| // Replace from last to first without rescanning, verifying earlier offsets do | |
| // not drift. | |
| auto reducedBytes = | |
| makeBytes({0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00}); | |
| std::string formatted = formatByteArray(reducedBytes); | |
| for (size_t i = modules.size(); i > 0; --i) { | |
| const auto& mod = modules[i - 1]; | |
| js.replace(mod.start, mod.end - mod.start, formatted); | |
| } | |
| auto rescanned = findEmbeddedModules(js); | |
| ASSERT_EQ(rescanned.size(), 3u); | |
| for (const auto& mod : rescanned) { | |
| EXPECT_EQ(mod.bytes, reducedBytes); | |
| } | |
| } | |
| TEST(JsEmbeddedModuleTest, FormatByteArray) { | |
| EXPECT_EQ(formatByteArray({}), "[]"); | |
| auto bytes24 = makeBytes({0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, | |
| 0x01, 0x04, 0x01, 0x60, 0x00, 0x00, 0x03, 0x02, | |
| 0x01, 0x00, 0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b}); | |
| std::string expected24 = | |
| "[\n" | |
| " 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, 0x60, " | |
| "0x00, 0x00, 0x03, 0x02,\n" | |
| " 0x01, 0x00, 0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b\n" | |
| "]"; | |
| EXPECT_EQ(formatByteArray(bytes24), expected24); | |
| // Round-trip formatted array back through findEmbeddedModules. | |
| auto parsed = findEmbeddedModules(expected24); | |
| ASSERT_EQ(parsed.size(), 1u); | |
| EXPECT_EQ(parsed[0].bytes, bytes24); | |
| } |