blob: 6b6c96fd57d4f4bf35535cc98fa6f11aa33ab4d2 [file]
/*
* 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);
}