blob: ad59183f6c52e007e188a449220f8e6d08709189 [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 "support/js-embedded-module.h"
#include <cctype>
#include <cstdint>
namespace wasm {
namespace {
// Advances `pos` past a `//` single-line comment (leaving `pos` at the newline
// character or end of `js`). Assumes `js.substr(pos, 2) == "//"`.
void skipSingleLineComment(std::string_view js, size_t& pos) {
pos += 2;
while (pos < js.size() && js[pos] != '\n') {
++pos;
}
}
// Advances `pos` past a `/* ... */` multi-line comment (or to the end of `js`
// if the comment is unterminated). Assumes `js.substr(pos, 2) == "/*"`.
void skipMultiLineComment(std::string_view js, size_t& pos) {
pos += 2;
while (pos + 1 < js.size() && !(js[pos] == '*' && js[pos + 1] == '/')) {
++pos;
}
if (pos + 1 < js.size()) {
pos += 2;
} else {
pos = js.size();
}
}
// Advances `pos` past any contiguous ASCII whitespace, single-line comments,
// and multi-line comments.
void skipWhitespaceAndComments(std::string_view js, size_t& pos) {
while (pos < js.size()) {
if (std::isspace(static_cast<unsigned char>(js[pos]))) {
++pos;
} else if (pos + 1 < js.size() && js[pos] == '/' && js[pos + 1] == '/') {
skipSingleLineComment(js, pos);
} else if (pos + 1 < js.size() && js[pos] == '/' && js[pos + 1] == '*') {
skipMultiLineComment(js, pos);
} else {
break;
}
}
}
// Parses a single integer literal representing an 8-bit byte at `pos` (in
// decimal, `0x` hex, `0o` octal, or `0b` binary, with optional `+`/`-` sign and
// `_` numeric separators). Accepts unsigned values in [0, 255] and signed
// negative values in [-128, -1]. On success, advances `pos` past the literal,
// writes the byte to `outByte`, and returns true.
bool parseByteLiteral(std::string_view js, size_t& pos, char& outByte) {
if (pos >= js.size()) {
return false;
}
bool negative = false;
if (js[pos] == '-' || js[pos] == '+') {
negative = (js[pos] == '-');
++pos;
if (pos >= js.size()) {
return false;
}
}
int base = 10;
if (js[pos] == '0' && pos + 1 < js.size()) {
char next = js[pos + 1];
if (next == 'x' || next == 'X') {
base = 16;
pos += 2;
} else if (next == 'o' || next == 'O') {
base = 8;
pos += 2;
} else if (next == 'b' || next == 'B') {
base = 2;
pos += 2;
}
}
uint32_t value = 0;
bool hasDigits = false;
while (pos < js.size()) {
char c = js[pos];
if (c == '_') {
++pos;
continue;
}
int digit = -1;
if (c >= '0' && c <= '9') {
digit = c - '0';
} else if (c >= 'a' && c <= 'f') {
digit = 10 + (c - 'a');
} else if (c >= 'A' && c <= 'F') {
digit = 10 + (c - 'A');
} else {
break;
}
if (digit >= base) {
return false;
}
hasDigits = true;
value = value * base + digit;
if ((!negative && value > 255) || (negative && value > 128)) {
return false;
}
++pos;
}
if (!hasDigits) {
return false;
}
// Ensure the literal is not immediately followed by an identifier character.
if (pos < js.size() &&
(std::isalnum(static_cast<unsigned char>(js[pos])) || js[pos] == '_')) {
return false;
}
if (negative) {
outByte = static_cast<char>(static_cast<uint8_t>(-static_cast<int>(value)));
} else {
outByte = static_cast<char>(static_cast<uint8_t>(value));
}
return true;
}
// Attempts to parse a JavaScript array literal starting at index `start`
// (`js[start] == '['`) as an embedded WebAssembly module. Rejects early if the
// first 4 elements do not match the `\0asm` magic bytes (`0x00, 0x61, 0x73,
// 0x6d`) or if any element is not a valid byte literal.
bool tryParseWasmByteArray(std::string_view js,
size_t start,
EmbeddedModule& outModule) {
static constexpr uint8_t WasmMagic[4] = {0x00, 0x61, 0x73, 0x6d};
size_t pos = start + 1;
std::vector<char> bytes;
while (true) {
skipWhitespaceAndComments(js, pos);
if (pos >= js.size()) {
return false;
}
if (js[pos] == ']') {
if (bytes.size() < 4) {
return false;
}
outModule = EmbeddedModule{start, pos + 1, std::move(bytes)};
return true;
}
char byteVal = 0;
if (!parseByteLiteral(js, pos, byteVal)) {
return false;
}
// Early rejection if the first 4 bytes do not match \0asm.
if (bytes.size() < 4 &&
static_cast<uint8_t>(byteVal) != WasmMagic[bytes.size()]) {
return false;
}
bytes.push_back(byteVal);
skipWhitespaceAndComments(js, pos);
if (pos >= js.size()) {
return false;
}
if (js[pos] == ',') {
++pos;
} else if (js[pos] != ']') {
return false;
}
}
}
} // namespace
std::vector<EmbeddedModule> findEmbeddedModules(std::string_view js) {
std::vector<EmbeddedModule> modules;
size_t pos = 0;
while (pos < js.size()) {
char c = js[pos];
// Skip single-line and multi-line comments.
if (c == '/' && pos + 1 < js.size()) {
if (js[pos + 1] == '/') {
skipSingleLineComment(js, pos);
continue;
}
if (js[pos + 1] == '*') {
skipMultiLineComment(js, pos);
continue;
}
}
// Skip string and template literals.
if (c == '\'' || c == '"' || c == '`') {
char quote = c;
++pos;
while (pos < js.size()) {
if (js[pos] == '\\') {
pos += 2;
continue;
}
if (js[pos] == quote) {
++pos;
break;
}
if (quote != '`' && (js[pos] == '\n' || js[pos] == '\r')) {
// Single- and double-quoted JS strings cannot span unescaped lines.
++pos;
break;
}
++pos;
}
continue;
}
if (c == '[') {
EmbeddedModule mod;
if (tryParseWasmByteArray(js, pos, mod)) {
pos = mod.end;
modules.push_back(std::move(mod));
continue;
}
}
++pos;
}
return modules;
}
std::string formatByteArray(const std::vector<char>& bytes) {
if (bytes.empty()) {
return "[]";
}
static constexpr char HexDigits[] = "0123456789abcdef";
std::string out = "[\n";
// Each byte takes ~6 chars ("0x00, "), plus indentation and brackets.
out.reserve(bytes.size() * 6 + (bytes.size() / 16 + 1) * 4 + 4);
for (size_t i = 0; i < bytes.size(); ++i) {
if (i % 16 == 0) {
out += " ";
}
uint8_t b = static_cast<uint8_t>(bytes[i]);
out += "0x";
out += HexDigits[b >> 4];
out += HexDigits[b & 0x0f];
if (i + 1 < bytes.size()) {
if ((i + 1) % 16 == 0) {
out += ",\n";
} else {
out += ", ";
}
} else {
out += "\n]";
}
}
return out;
}
} // namespace wasm