Skip to content

Commit 5c01f25

Browse files
yurydelendikkripken
authored andcommitted
Escape name section ids in binary format reading/writing to be WebAssembly spec compatible. (#1646)
1 parent e4d014f commit 5c01f25

8 files changed

+116
-2
lines changed

src/wasm-binary.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,7 @@ class WasmBinaryWriter {
721721

722722
// helpers
723723
void writeInlineString(const char* name);
724+
void writeEscapedName(const char* name);
724725
void writeInlineBuffer(const char* data, size_t size);
725726

726727
struct Buffer {

src/wasm/wasm-binary.cpp

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,13 +452,13 @@ void WasmBinaryWriter::writeNames() {
452452
for (auto& import : wasm->imports) {
453453
if (import->kind == ExternalKind::Function) {
454454
o << U32LEB(emitted);
455-
writeInlineString(import->name.str);
455+
writeEscapedName(import->name.str);
456456
emitted++;
457457
}
458458
}
459459
for (auto& curr : wasm->functions) {
460460
o << U32LEB(emitted);
461-
writeInlineString(curr->name.str);
461+
writeEscapedName(curr->name.str);
462462
emitted++;
463463
}
464464
assert(emitted == mappedFunctions.size());
@@ -565,6 +565,35 @@ void WasmBinaryWriter::writeInlineString(const char* name) {
565565
}
566566
}
567567

568+
static bool isHexDigit(char ch) {
569+
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
570+
}
571+
572+
static int decodeHexNibble(char ch) {
573+
return ch <= '9' ? ch & 15 : (ch & 15) + 9;
574+
}
575+
576+
void WasmBinaryWriter::writeEscapedName(const char* name) {
577+
if (!strpbrk(name, "\\")) {
578+
writeInlineString(name);
579+
return;
580+
}
581+
// decode escaped by escapeName (see below) function names
582+
std::string unescaped;
583+
int32_t size = strlen(name);
584+
for (int32_t i = 0; i < size;) {
585+
char ch = name[i++];
586+
// support only `\xx` escapes; ignore invalid or unsupported escapes
587+
if (ch != '\\' || i + 1 >= size || !isHexDigit(name[i]) || !isHexDigit(name[i + 1])) {
588+
unescaped.push_back(ch);
589+
continue;
590+
}
591+
unescaped.push_back(char((decodeHexNibble(name[i]) << 4) | decodeHexNibble(name[i + 1])));
592+
i += 2;
593+
}
594+
writeInlineString(unescaped.c_str());
595+
}
596+
568597
void WasmBinaryWriter::writeInlineBuffer(const char* data, size_t size) {
569598
o << U32LEB(size);
570599
for (size_t i = 0; i < size; i++) {
@@ -1515,6 +1544,42 @@ void WasmBinaryBuilder::readTableElements() {
15151544
}
15161545
}
15171546

1547+
static bool isIdChar(char ch) {
1548+
return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
1549+
ch == '!' || ch == '#' || ch == '$' || ch == '%' || ch == '&' || ch == '\'' || ch == '*' ||
1550+
ch == '+' || ch == '-' || ch == '.' || ch == '/' || ch == ':' || ch == '<' || ch == '=' ||
1551+
ch == '>' || ch == '?' || ch == '@' || ch == '^' || ch == '_' || ch == '`' || ch == '|' ||
1552+
ch == '~';
1553+
}
1554+
1555+
static char formatNibble(int nibble) {
1556+
return nibble < 10 ? '0' + nibble : 'a' - 10 + nibble;
1557+
}
1558+
1559+
static void escapeName(Name &name) {
1560+
bool allIdChars = true;
1561+
for (const char *p = name.str; allIdChars && *p; p++) {
1562+
allIdChars = isIdChar(*p);
1563+
}
1564+
if (allIdChars) {
1565+
return;
1566+
}
1567+
// encode name, if at least one non-idchar (per WebAssembly spec) was found
1568+
std::string escaped;
1569+
for (const char *p = name.str; *p; p++) {
1570+
char ch = *p;
1571+
if (isIdChar(ch)) {
1572+
escaped.push_back(ch);
1573+
continue;
1574+
}
1575+
// replace non-idchar with `\xx` escape
1576+
escaped.push_back('\\');
1577+
escaped.push_back(formatNibble(ch >> 4));
1578+
escaped.push_back(formatNibble(ch & 15));
1579+
}
1580+
name = escaped;
1581+
}
1582+
15181583
void WasmBinaryBuilder::readNames(size_t payloadLen) {
15191584
if (debug) std::cerr << "== readNames" << std::endl;
15201585
auto sectionPos = pos;
@@ -1533,6 +1598,7 @@ void WasmBinaryBuilder::readNames(size_t payloadLen) {
15331598
for (size_t i = 0; i < num; i++) {
15341599
auto index = getU32LEB();
15351600
auto rawName = getInlineString();
1601+
escapeName(rawName);
15361602
auto name = rawName;
15371603
// De-duplicate names by appending .1, .2, etc.
15381604
for (int i = 1; !usedNames.insert(name).second; ++i) {

test/complexBinaryNames.wasm

73 Bytes
Binary file not shown.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
(module
2+
(type $0 (func))
3+
(export "$zoo (.bar)" (func $1))
4+
(func $foo\20\28.bar\29 (; 0 ;) (type $0)
5+
(nop)
6+
)
7+
(func $1 (; 1 ;) (type $0)
8+
(call $foo\20\28.bar\29)
9+
)
10+
)
11+

test/complexTextNames.wast

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
(module
2+
(func $foo\20\28.bar\29)
3+
(func "$zoo (.bar)" (call $foo\20\28.bar\29))
4+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
(module
2+
(type $0 (func))
3+
(export "$zoo (.bar)" (func $1))
4+
(func $foo\20\28.bar\29 (; 0 ;) (type $0)
5+
(nop)
6+
)
7+
(func $1 (; 1 ;) (type $0)
8+
(call $foo\20\28.bar\29)
9+
)
10+
)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
(module
2+
(type $0 (func))
3+
(export "$zoo (.bar)" (func $1))
4+
(func $foo\20\28.bar\29 (; 0 ;) (type $0)
5+
(nop)
6+
)
7+
(func $1 (; 1 ;) (type $0)
8+
(call $foo\20\28.bar\29)
9+
)
10+
)
11+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
(module
2+
(type $0 (func))
3+
(export "$zoo (.bar)" (func $1))
4+
(func $0 (; 0 ;) (type $0)
5+
(nop)
6+
)
7+
(func $1 (; 1 ;) (type $0)
8+
(call $0)
9+
)
10+
)
11+

0 commit comments

Comments
 (0)