Skip to content

Commit

Permalink
Bug 1762658 - wasm: Add CacheableName to support names with null term…
Browse files Browse the repository at this point in the history
…inator before end. r=yury

A WebAssembly 'name' is a length terminated sequence of UTF-8 characters.
This implies that '\0' can show up in a string before the end, and we
cannot treat them like c-strings. A new CacheableName struct is added
to support this. CacheableChars is still used in unrelated areas and
left as-is.

The test importer is extended to handle escaping unusual unicode
sequences better, and names.wast is now imported.

Differential Revision: https://phabricator.services.mozilla.com/D147365
  • Loading branch information
eqrion committed Jun 9, 2022
1 parent 4e26b09 commit 793d9e2
Show file tree
Hide file tree
Showing 13 changed files with 2,486 additions and 112 deletions.
29 changes: 21 additions & 8 deletions js/src/fuzz-tests/testWasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ static bool assignImportKind(const Import& import, HandleObject obj,
JS::Handle<JS::IdVector> lastExportIds,
size_t* currentExportId, size_t exportsLength,
HandleValue defaultValue) {
RootedId fieldName(gCx);
if (!import.field.toPropertyKey(gCx, &fieldName)) {
return false;
}
bool assigned = false;
while (*currentExportId < exportsLength) {
RootedValue propVal(gCx);
Expand All @@ -93,7 +97,7 @@ static bool assignImportKind(const Import& import, HandleObject obj,
(*currentExportId)++;

if (propVal.isObject() && propVal.toObject().is<T>()) {
if (!JS_SetProperty(gCx, obj, import.field.get(), propVal)) {
if (!JS_SetPropertyById(gCx, obj, fieldName, propVal)) {
return false;
}

Expand All @@ -102,7 +106,7 @@ static bool assignImportKind(const Import& import, HandleObject obj,
}
}
if (!assigned) {
if (!JS_SetProperty(gCx, obj, import.field.get(), defaultValue)) {
if (!JS_SetPropertyById(gCx, obj, fieldName, defaultValue)) {
return false;
}
}
Expand Down Expand Up @@ -306,36 +310,45 @@ static int testWasmFuzz(const uint8_t* buf, size_t size) {
size_t currentTagExportId = 0;

for (const Import& import : importVec) {
RootedId moduleName(gCx);
if (!import.module.toPropertyKey(gCx, &moduleName)) {
return false;
}
RootedId fieldName(gCx);
if (!import.field.toPropertyKey(gCx, &fieldName)) {
return false;
}

// First try to get the namespace object, create one if this is the
// first time.
RootedValue v(gCx);
if (!JS_GetProperty(gCx, importObj, import.module.get(), &v) ||
if (!JS_GetPropertyById(gCx, importObj, moduleName, &v) ||
!v.isObject()) {
// Insert empty object at importObj[import.module.get()]
// Insert empty object at importObj[moduleName]
RootedObject plainObj(gCx, JS_NewPlainObject(gCx));

if (!plainObj) {
return 0;
}

RootedValue plainVal(gCx, ObjectValue(*plainObj));
if (!JS_SetProperty(gCx, importObj, import.module.get(), plainVal)) {
if (!JS_SetPropertyById(gCx, importObj, moduleName, plainVal)) {
return 0;
}

// Get the object we just inserted, store in v, ensure it is an
// object (no proxies or other magic at work).
if (!JS_GetProperty(gCx, importObj, import.module.get(), &v) ||
if (!JS_GetPropertyById(gCx, importObj, moduleName, &v) ||
!v.isObject()) {
return 0;
}
}

RootedObject obj(gCx, &v.toObject());
bool found = false;
if (JS_HasProperty(gCx, obj, import.field.get(), &found) && !found) {
if (JS_HasPropertyById(gCx, obj, fieldName, &found) && !found) {
// Insert i-th export object that fits the type requirement
// at `v[import.field.get()]`.
// at `v[fieldName]`.

switch (import.kind) {
case DefinitionKind::Function:
Expand Down
2 changes: 0 additions & 2 deletions js/src/jit-test/etc/wasm/generate-spectests/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ excluded_tests = [
"exports.wast",
# false-positive windows-specific failure
"memory_trap.wast",
# false-positive error on duplicate export name (names.wast:19)
"names.wast",
# false-positive error on invalid UTF-8 custom section name (utf8-custom-section-id.wast:6)
"utf8-custom-section-id.wast",
# invalid table maximum length for web embeddings
Expand Down
17 changes: 14 additions & 3 deletions js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,20 @@ fn convert_directive(
}

fn escape_template_string(text: &str) -> String {
text.replace("$", "$$")
.replace("\\", "\\\\")
.replace("`", "\\`")
let mut escaped = String::new();
for c in text.chars() {
match c {
'$' => escaped.push_str("$$"),
'\\' => escaped.push_str("\\\\"),
'`' => escaped.push_str("\\`"),
c if c.is_ascii_control() && c != '\n' && c != '\t' => {
escaped.push_str(&format!("\\x{:02x}", c as u32))
}
c if !c.is_ascii() => escaped.push_str(&c.escape_unicode().to_string()),
c => escaped.push(c),
}
}
escaped
}

fn span_to_offset(span: wast::Span, text: &str) -> Result<usize> {
Expand Down
20 changes: 20 additions & 0 deletions js/src/jit-test/tests/wasm/name.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// An export name may contain a null terminator
let exports = wasmEvalText(`(module
(func)
(export "\\00first" (func 0))
(export "\\00second" (func 0))
)`).exports;
assertEq(exports["\0first"] instanceof Function, true);
assertEq(exports["\0second"] instanceof Function, true);

// An import name may contain a null terminator
let imports = {
"\0module": {
"\0field": 10,
}
};
let {global} = wasmEvalText(`(module
(import "\\00module" "\\00field" (global i32))
(export "global" (global 0))
)`, imports).exports;
assertEq(global.value, 10);
Loading

0 comments on commit 793d9e2

Please sign in to comment.