Bug 1717914 - wasm: Introduce sourceMaxPages and clampedMaxPages for memories. r=lth

Wasm memory objects contain a maxPages field which is ambiguous. It
is usually the maximum specified in the module source, but may be
clamped on 32-bit systems. We use this maximum for linking and
type reflection which is not correct.

This commit splits the dual uses out into two separate variables.
  - sourceMaxPages: The original maximum pages from the module,
    may not be present. Used for linking and reflection.
  - clampedMaxPages: The computed maximum pages the memory may grow
    to. Always present, even if the memory does not specify a max.
    Used for limiting what size a memory may grow to, while still
    respecting both the source maximum and implementation limits.

The SMDOC for linear memory is updated with the invariants that
these variables hold.

Differential Revision: https://phabricator.services.mozilla.com/D118649
This commit is contained in:
Ryan Hunt 2021-07-16 20:58:11 +00:00
parent 60ef57ffc0
commit f162f75a6a
13 changed files with 294 additions and 2919 deletions

View File

@ -9,6 +9,7 @@ excluded_tests = [
# bulk-memory-operations/issues/133 (data.wast:161)
"data.wast",
# memory limits can be encoded with more bytes now
"binary.wast",
"binary-leb128.wast",
# testing that multiple tables fail (imports.wast:309)
"imports.wast",

View File

@ -0,0 +1,32 @@
const MemoryMaxValid = 65536;
// Linking should fail if the imported memory has a higher maximum than required,
// however if we internally clamp maximum values to an implementation limit
// and use that for linking we may erroneously accept some modules.
function testLinkFail(importMax, importedMax) {
assertErrorMessage(() => {
let importedMemory = new WebAssembly.Memory({
initial: 0,
maximum: importedMax,
});
wasmEvalText(`(module
(memory (import "" "") 0 ${importMax})
)`, {"": {"": importedMemory}});
}, WebAssembly.LinkError, /incompatible maximum/);
}
testLinkFail(0, 1);
testLinkFail(MemoryMaxValid - 1, MemoryMaxValid);
// The type reflection interface for WebAssembly.Memory should not report
// an internally clamped maximum.
if ('type' in WebAssembly.Memory.prototype) {
let memory = new WebAssembly.Memory({
initial: 0,
maximum: MemoryMaxValid,
});
let type = memory.type();
assertEq(type.maximum, MemoryMaxValid, 'reported memory maximum is not clamped');
}

File diff suppressed because it is too large Load Diff

View File

@ -1,885 +0,0 @@
/* Copyright 2021 Mozilla Foundation
*
* 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.
*/
// ./test/core/binary.wast
// ./test/core/binary.wast:1
let $0 = instantiate(`(module binary "\\00asm\\01\\00\\00\\00")`);
// ./test/core/binary.wast:2
let $1 = instantiate(`(module binary "\\00asm" "\\01\\00\\00\\00")`);
// ./test/core/binary.wast:3
let $2 = instantiate(`(module $$M1 binary "\\00asm\\01\\00\\00\\00")`);
register($2, `M1`);
// ./test/core/binary.wast:4
let $3 = instantiate(`(module $$M2 binary "\\00asm" "\\01\\00\\00\\00")`);
register($3, `M2`);
// ./test/core/binary.wast:6
assert_malformed(() => instantiate(`(module binary "")`), `unexpected end`);
// ./test/core/binary.wast:7
assert_malformed(() => instantiate(`(module binary "\\01")`), `unexpected end`);
// ./test/core/binary.wast:8
assert_malformed(
() => instantiate(`(module binary "\\00as")`),
`unexpected end`,
);
// ./test/core/binary.wast:9
assert_malformed(
() => instantiate(`(module binary "asm\\00")`),
`magic header not detected`,
);
// ./test/core/binary.wast:10
assert_malformed(
() => instantiate(`(module binary "msa\\00")`),
`magic header not detected`,
);
// ./test/core/binary.wast:11
assert_malformed(
() => instantiate(`(module binary "msa\\00\\01\\00\\00\\00")`),
`magic header not detected`,
);
// ./test/core/binary.wast:12
assert_malformed(
() => instantiate(`(module binary "msa\\00\\00\\00\\00\\01")`),
`magic header not detected`,
);
// ./test/core/binary.wast:13
assert_malformed(
() => instantiate(`(module binary "asm\\01\\00\\00\\00\\00")`),
`magic header not detected`,
);
// ./test/core/binary.wast:14
assert_malformed(
() => instantiate(`(module binary "wasm\\01\\00\\00\\00")`),
`magic header not detected`,
);
// ./test/core/binary.wast:15
assert_malformed(
() => instantiate(`(module binary "\\7fasm\\01\\00\\00\\00")`),
`magic header not detected`,
);
// ./test/core/binary.wast:16
assert_malformed(
() => instantiate(`(module binary "\\80asm\\01\\00\\00\\00")`),
`magic header not detected`,
);
// ./test/core/binary.wast:17
assert_malformed(
() => instantiate(`(module binary "\\82asm\\01\\00\\00\\00")`),
`magic header not detected`,
);
// ./test/core/binary.wast:18
assert_malformed(
() => instantiate(`(module binary "\\ffasm\\01\\00\\00\\00")`),
`magic header not detected`,
);
// ./test/core/binary.wast:21
assert_malformed(
() => instantiate(`(module binary "\\00\\00\\00\\01msa\\00")`),
`magic header not detected`,
);
// ./test/core/binary.wast:24
assert_malformed(
() => instantiate(`(module binary "a\\00ms\\00\\01\\00\\00")`),
`magic header not detected`,
);
// ./test/core/binary.wast:25
assert_malformed(
() => instantiate(`(module binary "sm\\00a\\00\\00\\01\\00")`),
`magic header not detected`,
);
// ./test/core/binary.wast:28
assert_malformed(
() => instantiate(`(module binary "\\00ASM\\01\\00\\00\\00")`),
`magic header not detected`,
);
// ./test/core/binary.wast:31
assert_malformed(
() => instantiate(`(module binary "\\00\\81\\a2\\94\\01\\00\\00\\00")`),
`magic header not detected`,
);
// ./test/core/binary.wast:34
assert_malformed(
() => instantiate(`(module binary "\\ef\\bb\\bf\\00asm\\01\\00\\00\\00")`),
`magic header not detected`,
);
// ./test/core/binary.wast:37
assert_malformed(
() => instantiate(`(module binary "\\00asm")`),
`unexpected end`,
);
// ./test/core/binary.wast:38
assert_malformed(
() => instantiate(`(module binary "\\00asm\\01")`),
`unexpected end`,
);
// ./test/core/binary.wast:39
assert_malformed(
() => instantiate(`(module binary "\\00asm\\01\\00\\00")`),
`unexpected end`,
);
// ./test/core/binary.wast:40
assert_malformed(
() => instantiate(`(module binary "\\00asm\\00\\00\\00\\00")`),
`unknown binary version`,
);
// ./test/core/binary.wast:41
assert_malformed(
() => instantiate(`(module binary "\\00asm\\0d\\00\\00\\00")`),
`unknown binary version`,
);
// ./test/core/binary.wast:42
assert_malformed(
() => instantiate(`(module binary "\\00asm\\0e\\00\\00\\00")`),
`unknown binary version`,
);
// ./test/core/binary.wast:43
assert_malformed(
() => instantiate(`(module binary "\\00asm\\00\\01\\00\\00")`),
`unknown binary version`,
);
// ./test/core/binary.wast:44
assert_malformed(
() => instantiate(`(module binary "\\00asm\\00\\00\\01\\00")`),
`unknown binary version`,
);
// ./test/core/binary.wast:45
assert_malformed(
() => instantiate(`(module binary "\\00asm\\00\\00\\00\\01")`),
`unknown binary version`,
);
// ./test/core/binary.wast:49
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\04\\04\\01\\70\\00\\00" ;; Table section
"\\0a\\09\\01" ;; Code section
;; function 0
"\\07\\00"
"\\41\\00" ;; i32.const 0
"\\11\\00" ;; call_indirect (type 0)
"\\01" ;; call_indirect reserved byte is not equal to zero!
"\\0b" ;; end
)`), `zero flag expected`);
// ./test/core/binary.wast:68
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\04\\04\\01\\70\\00\\00" ;; Table section
"\\0a\\0a\\01" ;; Code section
;; function 0
"\\07\\00"
"\\41\\00" ;; i32.const 0
"\\11\\00" ;; call_indirect (type 0)
"\\80\\00" ;; call_indirect reserved byte
"\\0b" ;; end
)`), `zero flag expected`);
// ./test/core/binary.wast:87
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\04\\04\\01\\70\\00\\00" ;; Table section
"\\0a\\0b\\01" ;; Code section
;; function 0
"\\08\\00"
"\\41\\00" ;; i32.const 0
"\\11\\00" ;; call_indirect (type 0)
"\\80\\80\\00" ;; call_indirect reserved byte
"\\0b" ;; end
)`), `zero flag expected`);
// ./test/core/binary.wast:105
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\04\\04\\01\\70\\00\\00" ;; Table section
"\\0a\\0c\\01" ;; Code section
;; function 0
"\\09\\00"
"\\41\\00" ;; i32.const 0
"\\11\\00" ;; call_indirect (type 0)
"\\80\\80\\80\\00" ;; call_indirect reserved byte
"\\0b" ;; end
)`), `zero flag expected`);
// ./test/core/binary.wast:123
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\04\\04\\01\\70\\00\\00" ;; Table section
"\\0a\\0d\\01" ;; Code section
;; function 0
"\\0a\\00"
"\\41\\00" ;; i32.const 0
"\\11\\00" ;; call_indirect (type 0)
"\\80\\80\\80\\80\\00" ;; call_indirect reserved byte
"\\0b" ;; end
)`), `zero flag expected`);
// ./test/core/binary.wast:142
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\05\\03\\01\\00\\00" ;; Memory section
"\\0a\\09\\01" ;; Code section
;; function 0
"\\07\\00"
"\\41\\00" ;; i32.const 0
"\\40" ;; memory.grow
"\\01" ;; memory.grow reserved byte is not equal to zero!
"\\1a" ;; drop
"\\0b" ;; end
)`), `zero flag expected`);
// ./test/core/binary.wast:162
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\05\\03\\01\\00\\00" ;; Memory section
"\\0a\\0a\\01" ;; Code section
;; function 0
"\\08\\00"
"\\41\\00" ;; i32.const 0
"\\40" ;; memory.grow
"\\80\\00" ;; memory.grow reserved byte
"\\1a" ;; drop
"\\0b" ;; end
)`), `zero flag expected`);
// ./test/core/binary.wast:182
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\05\\03\\01\\00\\00" ;; Memory section
"\\0a\\0b\\01" ;; Code section
;; function 0
"\\09\\00"
"\\41\\00" ;; i32.const 0
"\\40" ;; memory.grow
"\\80\\80\\00" ;; memory.grow reserved byte
"\\1a" ;; drop
"\\0b" ;; end
)`), `zero flag expected`);
// ./test/core/binary.wast:201
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\05\\03\\01\\00\\00" ;; Memory section
"\\0a\\0c\\01" ;; Code section
;; function 0
"\\0a\\00"
"\\41\\00" ;; i32.const 0
"\\40" ;; memory.grow
"\\80\\80\\80\\00" ;; memory.grow reserved byte
"\\1a" ;; drop
"\\0b" ;; end
)`), `zero flag expected`);
// ./test/core/binary.wast:220
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\05\\03\\01\\00\\00" ;; Memory section
"\\0a\\0d\\01" ;; Code section
;; function 0
"\\0b\\00"
"\\41\\00" ;; i32.const 0
"\\40" ;; memory.grow
"\\80\\80\\80\\80\\00" ;; memory.grow reserved byte
"\\1a" ;; drop
"\\0b" ;; end
)`), `zero flag expected`);
// ./test/core/binary.wast:240
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\05\\03\\01\\00\\00" ;; Memory section
"\\0a\\07\\01" ;; Code section
;; function 0
"\\05\\00"
"\\3f" ;; memory.size
"\\01" ;; memory.size reserved byte is not equal to zero!
"\\1a" ;; drop
"\\0b" ;; end
)`), `zero flag expected`);
// ./test/core/binary.wast:259
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\05\\03\\01\\00\\00" ;; Memory section
"\\0a\\08\\01" ;; Code section
;; function 0
"\\06\\00"
"\\3f" ;; memory.size
"\\80\\00" ;; memory.size reserved byte
"\\1a" ;; drop
"\\0b" ;; end
)`), `zero flag expected`);
// ./test/core/binary.wast:278
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\05\\03\\01\\00\\00" ;; Memory section
"\\0a\\09\\01" ;; Code section
;; function 0
"\\07\\00"
"\\3f" ;; memory.size
"\\80\\80\\00" ;; memory.size reserved byte
"\\1a" ;; drop
"\\0b" ;; end
)`), `zero flag expected`);
// ./test/core/binary.wast:296
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\05\\03\\01\\00\\00" ;; Memory section
"\\0a\\0a\\01" ;; Code section
;; function 0
"\\08\\00"
"\\3f" ;; memory.size
"\\80\\80\\80\\00" ;; memory.size reserved byte
"\\1a" ;; drop
"\\0b" ;; end
)`), `zero flag expected`);
// ./test/core/binary.wast:314
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\05\\03\\01\\00\\00" ;; Memory section
"\\0a\\0b\\01" ;; Code section
;; function 0
"\\09\\00"
"\\3f" ;; memory.size
"\\80\\80\\80\\80\\00" ;; memory.size reserved byte
"\\1a" ;; drop
"\\0b" ;; end
)`), `zero flag expected`);
// ./test/core/binary.wast:333
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\0a\\0c\\01" ;; Code section
;; function 0
"\\0a\\02"
"\\ff\\ff\\ff\\ff\\0f\\7f" ;; 0xFFFFFFFF i32
"\\02\\7e" ;; 0x00000002 i64
"\\0b" ;; end
)`), `too many locals`);
// ./test/core/binary.wast:350
let $4 = instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\0a\\0a\\01" ;; Code section
;; function 0
"\\08\\03"
"\\00\\7f" ;; 0 i32
"\\00\\7e" ;; 0 i64
"\\02\\7d" ;; 2 f32
"\\0b" ;; end
)`);
// ./test/core/binary.wast:365
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\03\\02\\00\\00" ;; Function section with 2 functions
)`), `function and code section have inconsistent lengths`);
// ./test/core/binary.wast:375
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\0a\\04\\01\\02\\00\\0b" ;; Code section with 1 empty function
)`), `function and code section have inconsistent lengths`);
// ./test/core/binary.wast:384
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\03\\02\\00\\00" ;; Function section with 2 functions
"\\0a\\04\\01\\02\\00\\0b" ;; Code section with 1 empty function
)`), `function and code section have inconsistent lengths`);
// ./test/core/binary.wast:395
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section with 1 function
"\\0a\\07\\02\\02\\00\\0b\\02\\00\\0b" ;; Code section with 2 empty functions
)`), `function and code section have inconsistent lengths`);
// ./test/core/binary.wast:406
let $5 = instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\03\\01\\00" ;; Function section with 0 functions
)`);
// ./test/core/binary.wast:412
let $6 = instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\0a\\01\\00" ;; Code section with 0 functions
)`);
// ./test/core/binary.wast:418
let $7 = instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\01\\00" ;; type count can be zero
)`);
// ./test/core/binary.wast:424
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\07\\02" ;; type section with inconsistent count (2 declared, 1 given)
"\\60\\00\\00" ;; 1st type
;; "\\60\\00\\00" ;; 2nd type (missed)
)`), `unexpected end of section or function`);
// ./test/core/binary.wast:435
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\07\\01" ;; type section with inconsistent count (1 declared, 2 given)
"\\60\\00\\00" ;; 1st type
"\\60\\00\\00" ;; 2nd type (redundant)
)`), `section size mismatch`);
// ./test/core/binary.wast:446
let $8 = instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\05\\01" ;; type section
"\\60\\01\\7f\\00" ;; type 0
"\\02\\01\\00" ;; import count can be zero
)`);
// ./test/core/binary.wast:454
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\05\\01" ;; type section
"\\60\\01\\7f\\00" ;; type 0
"\\02\\16\\02" ;; import section with inconsistent count (2 declared, 1 given)
;; 1st import
"\\08" ;; string length
"\\73\\70\\65\\63\\74\\65\\73\\74" ;; spectest
"\\09" ;; string length
"\\70\\72\\69\\6e\\74\\5f\\69\\33\\32" ;; print_i32
"\\00\\00" ;; import kind, import signature index
;; 2nd import
;; (missed)
)`), `unexpected end of section or function`);
// ./test/core/binary.wast:473
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\09\\02" ;; type section
"\\60\\01\\7f\\00" ;; type 0
"\\60\\01\\7d\\00" ;; type 1
"\\02\\2b\\01" ;; import section with inconsistent count (1 declared, 2 given)
;; 1st import
"\\08" ;; string length
"\\73\\70\\65\\63\\74\\65\\73\\74" ;; spectest
"\\09" ;; string length
"\\70\\72\\69\\6e\\74\\5f\\69\\33\\32" ;; print_i32
"\\00\\00" ;; import kind, import signature index
;; 2nd import
;; (redundant)
"\\08" ;; string length
"\\73\\70\\65\\63\\74\\65\\73\\74" ;; spectest
"\\09" ;; string length
"\\70\\72\\69\\6e\\74\\5f\\66\\33\\32" ;; print_f32
"\\00\\01" ;; import kind, import signature index
)`), `section size mismatch`);
// ./test/core/binary.wast:498
let $9 = instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\04\\01\\00" ;; table count can be zero
)`);
// ./test/core/binary.wast:504
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\04\\01\\01" ;; table section with inconsistent count (1 declared, 0 given)
;; "\\70\\01\\00\\00" ;; table entity
)`), `unexpected end of section or function`);
// ./test/core/binary.wast:514
let $10 = instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\05\\01\\00" ;; memory count can be zero
)`);
// ./test/core/binary.wast:520
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\05\\01\\01" ;; memory section with inconsistent count (1 declared, 0 given)
;; "\\00\\00" ;; memory 0 (missed)
)`), `unexpected end of section or function`);
// ./test/core/binary.wast:530
let $11 = instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\06\\01\\00" ;; global count can be zero
)`);
// ./test/core/binary.wast:536
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\06\\06\\02" ;; global section with inconsistent count (2 declared, 1 given)
"\\7f\\00\\41\\00\\0b" ;; global 0
;; "\\7f\\00\\41\\00\\0b" ;; global 1 (missed)
)`), `unexpected end of section or function`);
// ./test/core/binary.wast:547
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\06\\0b\\01" ;; global section with inconsistent count (1 declared, 2 given)
"\\7f\\00\\41\\00\\0b" ;; global 0
"\\7f\\00\\41\\00\\0b" ;; global 1 (redundant)
)`), `section size mismatch`);
// ./test/core/binary.wast:558
let $12 = instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01" ;; type section
"\\60\\00\\00" ;; type 0
"\\03\\03\\02\\00\\00" ;; func section
"\\07\\01\\00" ;; export count can be zero
"\\0a\\07\\02" ;; code section
"\\02\\00\\0b" ;; function body 0
"\\02\\00\\0b" ;; function body 1
)`);
// ./test/core/binary.wast:570
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01" ;; type section
"\\60\\00\\00" ;; type 0
"\\03\\03\\02\\00\\00" ;; func section
"\\07\\06\\02" ;; export section with inconsistent count (2 declared, 1 given)
"\\02" ;; export 0
"\\66\\31" ;; export name
"\\00\\00" ;; export kind, export func index
;; "\\02" ;; export 1 (missed)
;; "\\66\\32" ;; export name
;; "\\00\\01" ;; export kind, export func index
"\\0a\\07\\02" ;; code section
"\\02\\00\\0b" ;; function body 0
"\\02\\00\\0b" ;; function body 1
)`), `unexpected end of section or function`);
// ./test/core/binary.wast:591
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01" ;; type section
"\\60\\00\\00" ;; type 0
"\\03\\03\\02\\00\\00" ;; func section
"\\07\\0b\\01" ;; export section with inconsistent count (1 declared, 2 given)
"\\02" ;; export 0
"\\66\\31" ;; export name
"\\00\\00" ;; export kind, export func index
"\\02" ;; export 1 (redundant)
"\\66\\32" ;; export name
"\\00\\01" ;; export kind, export func index
"\\0a\\07\\02" ;; code section
"\\02\\00\\0b" ;; function body 0
"\\02\\00\\0b" ;; function body 1
)`), `section size mismatch`);
// ./test/core/binary.wast:612
let $13 = instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01" ;; type section
"\\60\\00\\00" ;; type 0
"\\03\\02\\01\\00" ;; func section
"\\04\\04\\01" ;; table section
"\\70\\00\\01" ;; table 0
"\\09\\01\\00" ;; elem segment count can be zero
"\\0a\\04\\01" ;; code section
"\\02\\00\\0b" ;; function body
)`);
// ./test/core/binary.wast:625
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01" ;; type section
"\\60\\00\\00" ;; type 0
"\\03\\02\\01\\00" ;; func section
"\\04\\04\\01" ;; table section
"\\70\\00\\01" ;; table 0
"\\09\\07\\02" ;; elem with inconsistent segment count (2 declared, 1 given)
"\\00\\41\\00\\0b\\01\\00" ;; elem 0
;; "\\00\\41\\00\\0b\\01\\00" ;; elem 1 (missed)
"\\0a\\04\\01" ;; code section
"\\02\\00\\0b" ;; function body
)`), `invalid value type`);
// ./test/core/binary.wast:643
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01" ;; type section
"\\60\\00\\00" ;; type 0
"\\03\\02\\01\\00" ;; func section
"\\04\\04\\01" ;; table section
"\\70\\00\\01" ;; table 0
"\\09\\0d\\01" ;; elem with inconsistent segment count (1 declared, 2 given)
"\\00\\41\\00\\0b\\01\\00" ;; elem 0
"\\00\\41\\00\\0b\\01\\00" ;; elem 1 (redundant)
"\\0a\\04\\01" ;; code section
"\\02\\00\\0b" ;; function body
)`), `section size mismatch`);
// ./test/core/binary.wast:661
let $14 = instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\05\\03\\01" ;; memory section
"\\00\\01" ;; memory 0
"\\0b\\01\\00" ;; data segment count can be zero
)`);
// ./test/core/binary.wast:669
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\05\\03\\01" ;; memory section
"\\00\\01" ;; memory 0
"\\0b\\07\\02" ;; data with inconsistent segment count (2 declared, 1 given)
"\\00\\41\\00\\0b\\01\\61" ;; data 0
;; "\\00\\41\\01\\0b\\01\\62" ;; data 1 (missed)
)`), `unexpected end of section or function`);
// ./test/core/binary.wast:682
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\05\\03\\01" ;; memory section
"\\00\\01" ;; memory 0
"\\0b\\0d\\01" ;; data with inconsistent segment count (1 declared, 2 given)
"\\00\\41\\00\\0b\\01\\61" ;; data 0
"\\00\\41\\01\\0b\\01\\62" ;; data 1 (redundant)
)`), `section size mismatch`);
// ./test/core/binary.wast:695
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\05\\03\\01" ;; memory section
"\\00\\01" ;; memory 0
"\\0b\\0c\\01" ;; data section
"\\00\\41\\03\\0b" ;; data segment 0
"\\07" ;; data segment size with inconsistent lengths (7 declared, 6 given)
"\\61\\62\\63\\64\\65\\66" ;; 6 bytes given
)`), `unexpected end of section or function`);
// ./test/core/binary.wast:709
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\05\\03\\01" ;; memory section
"\\00\\01" ;; memory 0
"\\0b\\0c\\01" ;; data section
"\\00\\41\\00\\0b" ;; data segment 0
"\\05" ;; data segment size with inconsistent lengths (5 declared, 6 given)
"\\61\\62\\63\\64\\65\\66" ;; 6 bytes given
)`), `section size mismatch`);
// ./test/core/binary.wast:723
let $15 = instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01" ;; type section
"\\60\\00\\00" ;; type 0
"\\03\\02\\01\\00" ;; func section
"\\0a\\11\\01" ;; code section
"\\0f\\00" ;; func 0
"\\02\\40" ;; block 0
"\\41\\01" ;; condition of if 0
"\\04\\40" ;; if 0
"\\41\\01" ;; index of br_table element
"\\0e\\00" ;; br_table target count can be zero
"\\02" ;; break depth for default
"\\0b\\0b\\0b" ;; end
)`);
// ./test/core/binary.wast:740
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01" ;; type section
"\\60\\00\\00" ;; type 0
"\\03\\02\\01\\00" ;; func section
"\\0a\\12\\01" ;; code section
"\\10\\00" ;; func 0
"\\02\\40" ;; block 0
"\\41\\01" ;; condition of if 0
"\\04\\40" ;; if 0
"\\41\\01" ;; index of br_table element
"\\0e\\02" ;; br_table with inconsistent target count (2 declared, 1 given)
"\\00" ;; break depth 0
;; "\\01" ;; break depth 1 (missed)
"\\02" ;; break depth for default
"\\0b\\0b\\0b" ;; end
)`), `unexpected end of section or function`);
// ./test/core/binary.wast:762
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01" ;; type section
"\\60\\00\\00" ;; type 0
"\\03\\02\\01\\00" ;; func section
"\\0a\\12\\01" ;; code section
"\\11\\00" ;; func 0
"\\02\\40" ;; block 0
"\\41\\01" ;; condition of if 0
"\\04\\40" ;; if 0
"\\41\\01" ;; index of br_table element
"\\0e\\01" ;; br_table with inconsistent target count (1 declared, 2 given)
"\\00" ;; break depth 0
"\\01" ;; break depth 1
"\\02" ;; break depth for default
"\\0b\\0b\\0b" ;; end
)`), `invalid value type`);
// ./test/core/binary.wast:784
let $16 = instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\08\\01\\00" ;; Start section: function 0
"\\0a\\04\\01" ;; Code section
;; function 0
"\\02\\00"
"\\0b" ;; end
)`);
// ./test/core/binary.wast:797
assert_malformed(() =>
instantiate(`(module binary
"\\00asm" "\\01\\00\\00\\00"
"\\01\\04\\01\\60\\00\\00" ;; Type section
"\\03\\02\\01\\00" ;; Function section
"\\08\\01\\00" ;; Start section: function 0
"\\08\\01\\00" ;; Start section: function 0
"\\0a\\04\\01" ;; Code section
;; function 0
"\\02\\00"
"\\0b" ;; end
)`), `junk after last section`);

View File

@ -509,6 +509,7 @@ void ArrayBufferObject::detach(JSContext* cx,
buffer->setIsDetached();
}
/* clang-format off */
/*
* [SMDOC] WASM Linear Memory structure
*
@ -517,18 +518,31 @@ void ArrayBufferObject::detach(JSContext* cx,
* The linear heap in Wasm is an mmaped array buffer. Several
* constants manage its lifetime:
*
* - length - the wasm-visible current length of the buffer. Accesses in the
* range [0, length] succeed. May only increase.
* - length - the wasm-visible current length of the buffer in bytes. Accesses
* in the range [0, length] succeed. May only increase.
*
* - boundsCheckLimit - the size against which we perform bounds checks. It is
* always a constant offset smaller than mappedSize. Currently that constant
* offset is 64k (wasm::GuardSize).
*
* - maxSize - the optional declared limit on how much length can grow.
* - sourceMaxSize - the optional declared limit on how much length can grow in
* pages. This is the unmodified maximum size from the source module or
* JS-API invocation. This may not be representable in byte lengths, nor
* feasible for a module to actually grow to due to implementation limits.
* It is used for correct linking checks and js-types reflection.
*
* - clampedMaxSize - the maximum size on how much the length can grow to in
* pages. This value respects implementation limits and is always
* representable as a byte length. Every memory has a clampedMaxSize, even
* if no maximum was specified in source. When a memory has no
* sourceMaxSize, the clampedMaxSize will be the maximum amount of memory
* that can be grown to while still respecting implementation limits.
*
* - mappedSize - the actual mmaped size. Access in the range
* [0, mappedSize] will either succeed, or be handled by the wasm signal
* handlers.
* handlers. If sourceMaxSize is present at initialization, then we attempt
* to map the whole clampedMaxSize. Otherwise we only map the region needed
* for the initial size.
*
* The below diagram shows the layout of the wasm heap. The wasm-visible
* portion of the heap starts at 0. There is one extra page prior to the
@ -539,23 +553,25 @@ void ArrayBufferObject::detach(JSContext* cx,
* \ ArrayBufferObject::dataPointer()
* \ /
* \ |
* ______|_|____________________________________________________________
* |______|_|______________|___________________|____________|____________|
* 0 length maxSize boundsCheckLimit mappedSize
* ______|_|___________________________________________________________________
* |______|_|______________|___________________|___________________|____________|
* 0 length clampedMaxSize boundsCheckLimit mappedSize
*
* \_______________________/
* COMMITED
* \____________________________________________/
* \___________________________________________________/
* SLOP
* \_____________________________________________________________________/
* \____________________________________________________________________________/
* MAPPED
*
* Invariants:
* - length only increases
* - 0 <= length <= maxSize (if present) <= boundsCheckLimit <= mappedSize
* - 0 <= length <= clampedMaxSize <= boundsCheckLimit <= mappedSize
* - on ARM boundsCheckLimit must be a valid ARM immediate.
* - if maxSize is not specified, boundsCheckLimit/mappedSize may grow. They
* are otherwise constant.
* - if sourceMaxSize is not specified, boundsCheckLimit/mappedSize may grow.
* They are otherwise constant.
* - initialLength <= clampedMaxSize <= sourceMaxSize (if present)
* - clampedMaxSize <= wasm::MaxMemoryPages()
*
* NOTE: For asm.js on non-x64 we guarantee that
*
@ -569,22 +585,23 @@ void ArrayBufferObject::detach(JSContext* cx,
* two parts:
*
* - from length to boundsCheckLimit - this part of the SLOP serves to catch
* accesses to memory we have reserved but not yet grown into. This allows us
* to grow memory up to max (when present) without having to patch/update the
* bounds checks.
* accesses to memory we have reserved but not yet grown into. This allows us
* to grow memory up to max (when present) without having to patch/update the
* bounds checks.
*
* - from boundsCheckLimit to mappedSize - this part of the SLOP allows us to
* bounds check against base pointers and fold some constant offsets inside
* loads. This enables better Bounds Check Elimination.
* bounds check against base pointers and fold some constant offsets inside
* loads. This enables better Bounds Check Elimination.
*
*/
/* clang-format on */
[[nodiscard]] bool WasmArrayRawBuffer::growToPagesInPlace(Pages newPages) {
size_t newSize = newPages.byteLength();
size_t oldSize = byteLength();
MOZ_ASSERT(newSize >= oldSize);
MOZ_ASSERT_IF(maxPages(), newPages <= maxPages().value());
MOZ_ASSERT(newPages <= clampedMaxPages());
MOZ_ASSERT(newSize <= mappedSize());
size_t delta = newSize - oldSize;
@ -618,32 +635,42 @@ bool WasmArrayRawBuffer::extendMappedSize(Pages maxPages) {
}
void WasmArrayRawBuffer::tryGrowMaxPagesInPlace(Pages deltaMaxPages) {
Pages newMaxPages = *maxPages_;
Pages newMaxPages = clampedMaxPages_;
DebugOnly<bool> valid = newMaxPages.checkedIncrement(deltaMaxPages);
// Caller must ensure increment does not overflow or increase over the
// specified maximum pages.
MOZ_ASSERT(valid);
MOZ_ASSERT_IF(sourceMaxPages_.isSome(), newMaxPages <= *sourceMaxPages_);
if (!extendMappedSize(newMaxPages)) {
return;
}
maxPages_ = Some(newMaxPages);
clampedMaxPages_ = newMaxPages;
}
/* static */
WasmArrayRawBuffer* WasmArrayRawBuffer::AllocateWasm(
IndexType indexType, Pages initialPages, const Maybe<Pages>& maxPages,
const Maybe<size_t>& mapped) {
IndexType indexType, Pages initialPages, Pages clampedMaxPages,
const Maybe<Pages>& sourceMaxPages, const Maybe<size_t>& mapped) {
// Prior code has asserted that initial pages is within our implementation
// limits (wasm::MaxMemoryPages) and we can assume it is a valid size_t.
MOZ_ASSERT(initialPages.hasByteLength());
size_t numBytes = initialPages.byteLength();
// If there is a specified maximum, attempt to map the whole range for
// clampedMaxPages. Or else map only what's required for initialPages.
Pages initialMappedPages =
sourceMaxPages.isSome() ? clampedMaxPages : initialPages;
// Use an override mapped size, or else compute the mapped size from
// initialMappedPages.
size_t mappedSize =
mapped.isSome() ? *mapped
: wasm::ComputeMappedSize(maxPages.valueOr(initialPages));
mapped.isSome() ? *mapped : wasm::ComputeMappedSize(initialMappedPages);
MOZ_RELEASE_ASSERT(mappedSize <= SIZE_MAX - gc::SystemPageSize());
MOZ_RELEASE_ASSERT(numBytes <= SIZE_MAX - gc::SystemPageSize());
MOZ_RELEASE_ASSERT(initialPages <= maxPages.valueOr(wasm::MaxMemoryPages()));
MOZ_RELEASE_ASSERT(initialPages <= clampedMaxPages);
MOZ_ASSERT(numBytes % gc::SystemPageSize() == 0);
MOZ_ASSERT(mappedSize % gc::SystemPageSize() == 0);
@ -659,8 +686,8 @@ WasmArrayRawBuffer* WasmArrayRawBuffer::AllocateWasm(
uint8_t* base = reinterpret_cast<uint8_t*>(data) + gc::SystemPageSize();
uint8_t* header = base - sizeof(WasmArrayRawBuffer);
auto rawBuf = new (header)
WasmArrayRawBuffer(indexType, base, maxPages, mappedSize, numBytes);
auto rawBuf = new (header) WasmArrayRawBuffer(
indexType, base, clampedMaxPages, sourceMaxPages, mappedSize, numBytes);
return rawBuf;
}
@ -680,47 +707,6 @@ WasmArrayRawBuffer* ArrayBufferObject::BufferContents::wasmBuffer() const {
return (WasmArrayRawBuffer*)(data_ - sizeof(WasmArrayRawBuffer));
}
static Pages AdjustWasmMaxPages(Pages initialPages, Pages maxPages) {
#ifdef JS_64BIT
# ifdef ENABLE_WASM_CRANELIFT
// On 64-bit platforms when we aren't using huge memory and we're using
// Cranelift, clamp clampedMaxSize to a value that satisfies the 32-bit
// invariants clampedMaxSize + wasm::PageSize < UINT32_MAX and
// clampedMaxSize % wasm::PageSize == 0.
//
// Note MaxMemory32LimitField*PageSize == UINT32_MAX+1 == 4GB, as you would
// expect for a 32-bit memory.
//
// Note that this will correspond with MaxMemory32BoundsCheckLimit().
//
// We do this only when Cranelift is present because Cranelift has not been
// updated to handle a 64-bit boundsCheckLimit field on 64-bit systems.
static const uint64_t CraneliftMaxPages =
(UINT32_MAX - wasm::PageSize) / wasm::PageSize;
if (!useHugeMemory && maxPages.value() >= CraneliftMaxPages) {
maxPages = Pages(CraneliftMaxPages - 1);
}
# endif
#else
static_assert(sizeof(uintptr_t) == 4, "assuming not 64 bit implies 32 bit");
// On 32-bit platforms, prevent applications specifying a large max
// (like UINT32_MAX) from unintentially OOMing the browser: they just
// want "a lot of memory". Maintain the invariant that
// initialSize <= clampedMaxSize.
static const uint64_t OneGib = 1 << 30;
static const Pages OneGibPages = Pages(OneGib >> wasm::PageBits);
static_assert(wasm::HighestValidARMImmediate > OneGib,
"computing mapped size on ARM requires clamped max size");
Pages clampedPages = std::max(OneGibPages, initialPages);
maxPages = std::min(clampedPages, maxPages);
#endif
MOZ_RELEASE_ASSERT(initialPages <= maxPages);
return maxPages;
}
template <typename ObjT, typename RawbufT>
static bool CreateSpecificWasmBuffer32(
JSContext* cx, const wasm::MemoryDesc& memory,
@ -728,13 +714,9 @@ static bool CreateSpecificWasmBuffer32(
bool useHugeMemory =
memory.indexType() == IndexType::I32 && wasm::IsHugeMemoryEnabled();
Pages initialPages = memory.initialPages();
Maybe<Pages> maxPages = memory.maximumPages();
// Adjust the maximum pages specified to conform to extra invariants in the
// engine.
if (maxPages) {
maxPages = Some(AdjustWasmMaxPages(initialPages, *maxPages));
}
Maybe<Pages> sourceMaxPages = memory.maximumPages();
Pages clampedMaxPages =
wasm::ClampedMaxPages(initialPages, sourceMaxPages, useHugeMemory);
Maybe<size_t> mappedSize;
#ifdef WASM_SUPPORTS_HUGE_MEMORY
@ -745,8 +727,9 @@ static bool CreateSpecificWasmBuffer32(
}
#endif
RawbufT* buffer = RawbufT::AllocateWasm(memory.limits.indexType, initialPages,
maxPages, mappedSize);
RawbufT* buffer =
RawbufT::AllocateWasm(memory.limits.indexType, initialPages,
clampedMaxPages, sourceMaxPages, mappedSize);
if (!buffer) {
if (useHugeMemory) {
WarnNumberASCII(cx, JSMSG_WASM_HUGE_MEMORY_FAILED);
@ -758,19 +741,19 @@ static bool CreateSpecificWasmBuffer32(
return false;
}
// If we fail, and have a maxPages, try to reserve the biggest chunk
// in the range [initialPages, maxPages) using log backoff.
if (!maxPages) {
// If we fail, and have a sourceMaxPages, try to reserve the biggest
// chunk in the range [initialPages, clampedMaxPages) using log backoff.
if (!sourceMaxPages) {
wasm::Log(cx, "new Memory({initial=%" PRIu64 " pages}) failed",
initialPages.value());
ReportOutOfMemory(cx);
return false;
}
uint64_t cur = maxPages->value() / 2;
uint64_t cur = clampedMaxPages.value() / 2;
for (; Pages(cur) > initialPages; cur /= 2) {
buffer = RawbufT::AllocateWasm(memory.limits.indexType, initialPages,
Some(Pages(cur)), mappedSize);
Pages(cur), sourceMaxPages, mappedSize);
if (buffer) {
break;
}
@ -816,19 +799,19 @@ static bool CreateSpecificWasmBuffer32(
}
// Log the result with details on the memory allocation
if (maxPages) {
if (sourceMaxPages) {
if (useHugeMemory) {
wasm::Log(cx,
"new Memory({initial:%" PRIu64 " pages, maximum:%" PRIu64
" pages}) succeeded",
initialPages.value(), maxPages->value());
initialPages.value(), sourceMaxPages->value());
} else {
wasm::Log(cx,
"new Memory({initial:%" PRIu64 " pages, maximum:%" PRIu64
" pages}) succeeded "
"with internal maximum of %" PRIu64 " pages",
initialPages.value(), maxPages->value(),
object->wasmMaxPages()->value());
initialPages.value(), sourceMaxPages->value(),
object->wasmClampedMaxPages().value());
}
} else {
wasm::Log(cx, "new Memory({initial:%" PRIu64 " pages}) succeeded",
@ -1020,9 +1003,17 @@ Pages ArrayBufferObject::wasmPages() const {
return Pages::fromByteLengthExact(byteLength());
}
Maybe<Pages> ArrayBufferObject::wasmMaxPages() const {
Pages ArrayBufferObject::wasmClampedMaxPages() const {
if (isWasm()) {
return contents().wasmBuffer()->maxPages();
return contents().wasmBuffer()->clampedMaxPages();
}
MOZ_ASSERT(isPreparedForAsmJS());
return Pages::fromByteLengthExact(byteLength());
}
Maybe<Pages> ArrayBufferObject::wasmSourceMaxPages() const {
if (isWasm()) {
return contents().wasmBuffer()->sourceMaxPages();
}
MOZ_ASSERT(isPreparedForAsmJS());
return Some<Pages>(Pages::fromByteLengthExact(byteLength()));
@ -1048,12 +1039,19 @@ Pages js::WasmArrayBufferPages(const ArrayBufferObjectMaybeShared* buf) {
}
return buf->as<SharedArrayBufferObject>().volatileWasmPages();
}
Maybe<Pages> js::WasmArrayBufferMaxPages(
Pages js::WasmArrayBufferClampedMaxPages(
const ArrayBufferObjectMaybeShared* buf) {
if (buf->is<ArrayBufferObject>()) {
return buf->as<ArrayBufferObject>().wasmMaxPages();
return buf->as<ArrayBufferObject>().wasmClampedMaxPages();
}
return Some(buf->as<SharedArrayBufferObject>().wasmMaxPages());
return buf->as<SharedArrayBufferObject>().wasmClampedMaxPages();
}
Maybe<Pages> js::WasmArrayBufferSourceMaxPages(
const ArrayBufferObjectMaybeShared* buf) {
if (buf->is<ArrayBufferObject>()) {
return buf->as<ArrayBufferObject>().wasmSourceMaxPages();
}
return Some(buf->as<SharedArrayBufferObject>().wasmSourceMaxPages());
}
static void CheckStealPreconditions(Handle<ArrayBufferObject*> buffer,
@ -1073,8 +1071,17 @@ bool ArrayBufferObject::wasmGrowToPagesInPlace(
MOZ_ASSERT(oldBuf->isWasm());
// The caller must verify that the new page size won't overflow when
// converted to a byte length.
// Check that the new pages is within our allowable range. This will
// simultaneously check against the maximum specified in source and our
// implementation limits.
if (newPages > oldBuf->wasmClampedMaxPages()) {
return false;
}
MOZ_ASSERT(newPages <= wasm::MaxMemoryPages() &&
newPages.byteLength() < ArrayBufferObject::maxBufferByteLength());
// We have checked against the clamped maximum and so we know we can convert
// to byte lengths now.
size_t newSize = newPages.byteLength();
// On failure, do not throw and ensure that the original buffer is
@ -1082,11 +1089,6 @@ bool ArrayBufferObject::wasmGrowToPagesInPlace(
// wasm-visible length of the buffer has been increased so it must be the
// last fallible operation.
// Note, caller must guard on limit appropriate for the memory type
if (newSize > ArrayBufferObject::maxBufferByteLength()) {
return false;
}
newBuf.set(ArrayBufferObject::createEmpty(cx));
if (!newBuf) {
cx->clearPendingException();
@ -1124,14 +1126,18 @@ bool ArrayBufferObject::wasmMovingGrowToPages(
// On failure, do not throw and ensure that the original buffer is
// unmodified and valid.
// The caller must verify that the new page size won't overflow when
// converted to a byte length.
size_t newSize = newPages.byteLength();
// Note, caller must guard on the limit appropriate to the memory type
if (newSize > ArrayBufferObject::maxBufferByteLength()) {
// Check that the new pages is within our allowable range. This will
// simultaneously check against the maximum specified in source and our
// implementation limits.
if (newPages > oldBuf->wasmClampedMaxPages()) {
return false;
}
MOZ_ASSERT(newPages <= wasm::MaxMemoryPages() &&
newPages.byteLength() < ArrayBufferObject::maxBufferByteLength());
// We have checked against the clamped maximum and so we know we can convert
// to byte lengths now.
size_t newSize = newPages.byteLength();
if (wasm::ComputeMappedSize(newPages) <= oldBuf->wasmMappedSize() ||
oldBuf->contents().wasmBuffer()->extendMappedSize(newPages)) {
@ -1144,8 +1150,10 @@ bool ArrayBufferObject::wasmMovingGrowToPages(
return false;
}
Pages clampedMaxPages =
wasm::ClampedMaxPages(newPages, Nothing(), /* hugeMemory */ false);
WasmArrayRawBuffer* newRawBuf = WasmArrayRawBuffer::AllocateWasm(
oldBuf->wasmIndexType(), newPages, Nothing(), Nothing());
oldBuf->wasmIndexType(), newPages, clampedMaxPages, Nothing(), Nothing());
if (!newRawBuf) {
return false;
}

View File

@ -112,7 +112,9 @@ class ArrayBufferObjectMaybeShared;
wasm::IndexType WasmArrayBufferIndexType(
const ArrayBufferObjectMaybeShared* buf);
wasm::Pages WasmArrayBufferPages(const ArrayBufferObjectMaybeShared* buf);
mozilla::Maybe<wasm::Pages> WasmArrayBufferMaxPages(
wasm::Pages WasmArrayBufferClampedMaxPages(
const ArrayBufferObjectMaybeShared* buf);
mozilla::Maybe<wasm::Pages> WasmArrayBufferSourceMaxPages(
const ArrayBufferObjectMaybeShared* buf);
size_t WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared* buf);
@ -130,8 +132,11 @@ class ArrayBufferObjectMaybeShared : public NativeObject {
return WasmArrayBufferIndexType(this);
}
wasm::Pages wasmPages() const { return WasmArrayBufferPages(this); }
mozilla::Maybe<wasm::Pages> wasmMaxPages() const {
return WasmArrayBufferMaxPages(this);
wasm::Pages wasmClampedMaxPages() const {
return WasmArrayBufferClampedMaxPages(this);
}
mozilla::Maybe<wasm::Pages> wasmSourceMaxPages() const {
return WasmArrayBufferSourceMaxPages(this);
}
size_t wasmMappedSize() const { return WasmArrayBufferMappedSize(this); }
@ -456,7 +461,8 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared {
wasm::IndexType wasmIndexType() const;
wasm::Pages wasmPages() const;
mozilla::Maybe<wasm::Pages> wasmMaxPages() const;
wasm::Pages wasmClampedMaxPages() const;
mozilla::Maybe<wasm::Pages> wasmSourceMaxPages() const;
[[nodiscard]] static bool wasmGrowToPagesInPlace(
wasm::Pages newPages, Handle<ArrayBufferObject*> oldBuf,
@ -587,16 +593,19 @@ class MutableWrappedPtrOperations<InnerViewTable, Wrapper>
class WasmArrayRawBuffer {
wasm::IndexType indexType_;
mozilla::Maybe<wasm::Pages> maxPages_;
wasm::Pages clampedMaxPages_;
mozilla::Maybe<wasm::Pages> sourceMaxPages_;
size_t mappedSize_; // Not including the header page
size_t length_;
protected:
WasmArrayRawBuffer(wasm::IndexType indexType, uint8_t* buffer,
const mozilla::Maybe<wasm::Pages>& maxPages,
wasm::Pages clampedMaxPages,
const mozilla::Maybe<wasm::Pages>& sourceMaxPages,
size_t mappedSize, size_t length)
: indexType_(indexType),
maxPages_(maxPages),
clampedMaxPages_(clampedMaxPages),
sourceMaxPages_(sourceMaxPages),
mappedSize_(mappedSize),
length_(length) {
MOZ_ASSERT(buffer == dataPointer());
@ -605,7 +614,8 @@ class WasmArrayRawBuffer {
public:
static WasmArrayRawBuffer* AllocateWasm(
wasm::IndexType indexType, wasm::Pages initialPages,
const mozilla::Maybe<wasm::Pages>& maxPages,
wasm::Pages clampedMaxPages,
const mozilla::Maybe<wasm::Pages>& sourceMaxPages,
const mozilla::Maybe<size_t>& mappedSize);
static void Release(void* mem);
@ -631,7 +641,9 @@ class WasmArrayRawBuffer {
return wasm::Pages::fromByteLengthExact(length_);
}
mozilla::Maybe<wasm::Pages> maxPages() const { return maxPages_; }
wasm::Pages clampedMaxPages() const { return clampedMaxPages_; }
mozilla::Maybe<wasm::Pages> sourceMaxPages() const { return sourceMaxPages_; }
[[nodiscard]] bool growToPagesInPlace(wasm::Pages newPages);

View File

@ -46,11 +46,12 @@ static size_t SharedArrayMappedSize(size_t length) {
return SharedArrayAccessibleSize(length) + gc::SystemPageSize();
}
// `wasmMaxPages` must always be something for wasm and nothing for other
// users.
// `wasmSourceMaxPages` must always be something for wasm and nothing for
// other users.
SharedArrayRawBuffer* SharedArrayRawBuffer::AllocateInternal(
wasm::IndexType wasmIndexType, size_t length,
const Maybe<wasm::Pages>& wasmMaxPages,
const Maybe<wasm::Pages>& wasmClampedMaxPages,
const Maybe<wasm::Pages>& wasmSourceMaxPages,
const Maybe<size_t>& wasmMappedSize) {
MOZ_RELEASE_ASSERT(length <= ArrayBufferObject::maxBufferByteLength());
@ -59,12 +60,12 @@ SharedArrayRawBuffer* SharedArrayRawBuffer::AllocateInternal(
return nullptr;
}
bool preparedForWasm = wasmMaxPages.isSome();
bool preparedForWasm = wasmClampedMaxPages.isSome();
size_t computedMappedSize;
if (preparedForWasm) {
computedMappedSize = wasmMappedSize.isSome()
? *wasmMappedSize
: wasm::ComputeMappedSize(*wasmMaxPages);
: wasm::ComputeMappedSize(*wasmClampedMaxPages);
} else {
MOZ_ASSERT(wasmMappedSize.isNothing());
computedMappedSize = accessibleSize;
@ -82,33 +83,37 @@ SharedArrayRawBuffer* SharedArrayRawBuffer::AllocateInternal(
uint8_t* buffer = reinterpret_cast<uint8_t*>(p) + gc::SystemPageSize();
uint8_t* base = buffer - sizeof(SharedArrayRawBuffer);
SharedArrayRawBuffer* rawbuf = new (base) SharedArrayRawBuffer(
wasmIndexType, buffer, length, wasmMaxPages.valueOr(Pages(0)),
computedMappedSize, preparedForWasm);
wasmIndexType, buffer, length, wasmClampedMaxPages.valueOr(Pages(0)),
wasmSourceMaxPages.valueOr(Pages(0)), computedMappedSize,
preparedForWasm);
MOZ_ASSERT(rawbuf->length_ == length); // Deallocation needs this
return rawbuf;
}
SharedArrayRawBuffer* SharedArrayRawBuffer::Allocate(size_t length) {
return SharedArrayRawBuffer::AllocateInternal(wasm::IndexType::I32, length,
Nothing(), Nothing());
return SharedArrayRawBuffer::AllocateInternal(
wasm::IndexType::I32, length, Nothing(), Nothing(), Nothing());
}
SharedArrayRawBuffer* SharedArrayRawBuffer::AllocateWasm(
wasm::IndexType indexType, Pages initialPages,
const mozilla::Maybe<wasm::Pages>& maxPages,
wasm::IndexType indexType, Pages initialPages, wasm::Pages clampedMaxPages,
const mozilla::Maybe<wasm::Pages>& sourceMaxPages,
const mozilla::Maybe<size_t>& mappedSize) {
// Prior code has asserted that initial pages is within our implementation
// limits (wasm::MaxMemory32Pages) and we can assume it is a valid size_t.
MOZ_ASSERT(initialPages.hasByteLength());
size_t length = initialPages.byteLength();
return SharedArrayRawBuffer::AllocateInternal(indexType, length, maxPages,
mappedSize);
return SharedArrayRawBuffer::AllocateInternal(
indexType, length, Some(clampedMaxPages), sourceMaxPages, mappedSize);
}
void SharedArrayRawBuffer::tryGrowMaxPagesInPlace(Pages deltaMaxPages) {
Pages newMaxPages = wasmMaxPages_;
Pages newMaxPages = wasmClampedMaxPages_;
DebugOnly<bool> valid = newMaxPages.checkedIncrement(deltaMaxPages);
// Caller must ensure increment does not overflow or increase over the
// specified maximum pages.
MOZ_ASSERT(valid);
MOZ_ASSERT(newMaxPages <= wasmSourceMaxPages_);
size_t newMappedSize = wasm::ComputeMappedSize(newMaxPages);
MOZ_ASSERT(mappedSize_ <= newMappedSize);
@ -121,19 +126,23 @@ void SharedArrayRawBuffer::tryGrowMaxPagesInPlace(Pages deltaMaxPages) {
}
mappedSize_ = newMappedSize;
wasmMaxPages_ = newMaxPages;
wasmClampedMaxPages_ = newMaxPages;
}
bool SharedArrayRawBuffer::wasmGrowToPagesInPlace(const Lock&,
wasm::Pages newPages) {
// The caller must verify that the new page size won't overflow when
// converted to a byte length.
size_t newLength = newPages.byteLength();
// Note, caller must guard on the limit appropriate to the memory type
if (newLength > ArrayBufferObject::maxBufferByteLength()) {
// Check that the new pages is within our allowable range. This will
// simultaneously check against the maximum specified in source and our
// implementation limits.
if (newPages > wasmClampedMaxPages_) {
return false;
}
MOZ_ASSERT(newPages <= wasm::MaxMemoryPages() &&
newPages.byteLength() < ArrayBufferObject::maxBufferByteLength());
// We have checked against the clamped maximum and so we know we can convert
// to byte lengths now.
size_t newLength = newPages.byteLength();
MOZ_ASSERT(newLength >= length_);

View File

@ -56,7 +56,8 @@ class SharedArrayRawBuffer {
wasm::IndexType wasmIndexType_;
// The maximum size of this buffer in wasm pages. If this buffer was not
// prepared for wasm, then this is zero.
wasm::Pages wasmMaxPages_;
wasm::Pages wasmClampedMaxPages_;
wasm::Pages wasmSourceMaxPages_;
size_t mappedSize_; // Does not include the page for the header
bool preparedForWasm_;
@ -72,13 +73,15 @@ class SharedArrayRawBuffer {
protected:
SharedArrayRawBuffer(wasm::IndexType wasmIndexType, uint8_t* buffer,
size_t length, wasm::Pages wasmMaxPages,
size_t mappedSize, bool preparedForWasm)
size_t length, wasm::Pages wasmClampedMaxPages,
wasm::Pages wasmSourceMaxPages, size_t mappedSize,
bool preparedForWasm)
: refcount_(1),
length_(length),
growLock_(mutexid::SharedArrayGrow),
wasmIndexType_(wasmIndexType),
wasmMaxPages_(wasmMaxPages),
wasmClampedMaxPages_(wasmClampedMaxPages),
wasmSourceMaxPages_(wasmSourceMaxPages),
mappedSize_(mappedSize),
preparedForWasm_(preparedForWasm),
waiters_(nullptr) {
@ -86,11 +89,12 @@ class SharedArrayRawBuffer {
}
// Allocate a SharedArrayRawBuffer for either Wasm or other users.
// `wasmMaxPages` must always be something for wasm and nothing for other
// users.
// `wasmClampedMaxPages` and `wasmSourceMaxPages` must always be something
// for wasm and nothing for other users.
static SharedArrayRawBuffer* AllocateInternal(
wasm::IndexType wasmIndexType, size_t length,
const mozilla::Maybe<wasm::Pages>& wasmMaxPages,
const mozilla::Maybe<wasm::Pages>& wasmClampedMaxPages,
const mozilla::Maybe<wasm::Pages>& wasmSourceMaxPages,
const mozilla::Maybe<size_t>& wasmMappedSize);
public:
@ -110,7 +114,8 @@ class SharedArrayRawBuffer {
static SharedArrayRawBuffer* Allocate(size_t length);
static SharedArrayRawBuffer* AllocateWasm(
wasm::IndexType indexType, wasm::Pages initialPages,
const mozilla::Maybe<wasm::Pages>& maxPages,
wasm::Pages clampedMaxPages,
const mozilla::Maybe<wasm::Pages>& sourceMaxPages,
const mozilla::Maybe<size_t>& mappedSize);
// This may be called from multiple threads. The caller must take
@ -140,7 +145,8 @@ class SharedArrayRawBuffer {
return wasm::Pages::fromByteLengthExact(length_);
}
wasm::Pages wasmMaxPages() const { return wasmMaxPages_; }
wasm::Pages wasmClampedMaxPages() const { return wasmClampedMaxPages_; }
wasm::Pages wasmSourceMaxPages() const { return wasmSourceMaxPages_; }
size_t mappedSize() const { return mappedSize_; }
@ -261,7 +267,12 @@ class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared {
wasm::Pages volatileWasmPages() const {
return rawBufferObject()->volatileWasmPages();
}
wasm::Pages wasmMaxPages() const { return rawBufferObject()->wasmMaxPages(); }
wasm::Pages wasmClampedMaxPages() const {
return rawBufferObject()->wasmClampedMaxPages();
}
wasm::Pages wasmSourceMaxPages() const {
return rawBufferObject()->wasmSourceMaxPages();
}
size_t wasmMappedSize() const { return rawBufferObject()->mappedSize(); }

View File

@ -2542,7 +2542,7 @@ bool WasmMemoryObject::typeImpl(JSContext* cx, const CallArgs& args) {
cx, &args.thisv().toObject().as<WasmMemoryObject>());
Rooted<IdValueVector> props(cx, IdValueVector(cx));
Maybe<Pages> maxPages = memoryObj->maxPages();
Maybe<Pages> maxPages = memoryObj->sourceMaxPages();
if (maxPages.isSome()) {
uint32_t maxPages32 = mozilla::AssertedCast<uint32_t>(maxPages->value());
if (!props.append(IdValuePair(NameToId(cx->names().maximum),
@ -2592,11 +2592,18 @@ wasm::Pages WasmMemoryObject::volatilePages() const {
return buffer().wasmPages();
}
Maybe<wasm::Pages> WasmMemoryObject::maxPages() const {
wasm::Pages WasmMemoryObject::clampedMaxPages() const {
if (isShared()) {
return Some(sharedArrayRawBuffer()->wasmMaxPages());
return sharedArrayRawBuffer()->wasmClampedMaxPages();
}
return buffer().wasmMaxPages();
return buffer().wasmClampedMaxPages();
}
Maybe<wasm::Pages> WasmMemoryObject::sourceMaxPages() const {
if (isShared()) {
return Some(sharedArrayRawBuffer()->wasmSourceMaxPages());
}
return buffer().wasmSourceMaxPages();
}
wasm::IndexType WasmMemoryObject::indexType() const {
@ -2649,7 +2656,7 @@ bool WasmMemoryObject::isHuge() const {
}
bool WasmMemoryObject::movingGrowable() const {
return !isHuge() && !buffer().wasmMaxPages();
return !isHuge() && !buffer().wasmSourceMaxPages();
}
size_t WasmMemoryObject::boundsCheckLimit() const {
@ -2701,16 +2708,6 @@ uint32_t WasmMemoryObject::growShared(HandleWasmMemoryObject memory,
return -1;
}
// Always check against the max here, do not rely on the buffer resizers to
// use the correct limit, they don't have enough context.
if (newPages > MaxMemoryPages()) {
return -1;
}
if (newPages > rawBuf->wasmMaxPages()) {
return -1;
}
if (!rawBuf->wasmGrowToPagesInPlace(lock, newPages)) {
return -1;
}
@ -2747,12 +2744,6 @@ uint32_t WasmMemoryObject::grow(HandleWasmMemoryObject memory, uint32_t delta,
return -1;
}
// Always check against the max here, do not rely on the buffer resizers to
// use the correct limit, they don't have enough context.
if (newPages > MaxMemoryPages()) {
return -1;
}
RootedArrayBufferObject newBuf(cx);
if (memory->movingGrowable()) {
@ -2761,17 +2752,9 @@ uint32_t WasmMemoryObject::grow(HandleWasmMemoryObject memory, uint32_t delta,
cx)) {
return -1;
}
} else {
if (Maybe<Pages> maxPages = oldBuf->wasmMaxPages()) {
if (newPages > *maxPages) {
return -1;
}
}
if (!ArrayBufferObject::wasmGrowToPagesInPlace(newPages, oldBuf, &newBuf,
cx)) {
return -1;
}
} else if (!ArrayBufferObject::wasmGrowToPagesInPlace(newPages, oldBuf,
&newBuf, cx)) {
return -1;
}
memory->setReservedSlot(BUFFER_SLOT, ObjectValue(*newBuf));
@ -4849,3 +4832,53 @@ wasm::Pages wasm::MaxMemoryPages() {
size_t wasm::MaxMemoryBoundsCheckLimit() { return size_t(INT32_MAX) + 1; }
#endif
Pages wasm::ClampedMaxPages(Pages initialPages,
const Maybe<Pages>& sourceMaxPages,
bool useHugeMemory) {
Pages clampedMaxPages;
if (sourceMaxPages.isSome()) {
// There is a specified maximum, clamp it to the implementation limit of
// maximum pages
clampedMaxPages = std::min(*sourceMaxPages, wasm::MaxMemoryPages());
#if defined(JS_64BIT) && defined(ENABLE_WASM_CRANELIFT)
// On 64-bit platforms when we aren't using huge memory and we're using
// Cranelift, we must satisfy the 32-bit invariants that:
// clampedMaxSize + wasm::PageSize < UINT32_MAX
// This is ensured by clamping sourceMaxPages to wasm::MaxMemoryPages(),
// which has special logic for cranelift.
MOZ_ASSERT_IF(!useHugeMemory,
clampedMaxPages.byteLength() + wasm::PageSize < UINT32_MAX);
#endif
#ifndef JS_64BIT
static_assert(sizeof(uintptr_t) == 4, "assuming not 64 bit implies 32 bit");
// On 32-bit platforms, prevent applications specifying a large max
// (like MaxMemory32Pages) from unintentially OOMing the browser: they just
// want "a lot of memory". Maintain the invariant that
// initialPages <= clampedMaxPages.
static const uint64_t OneGib = 1 << 30;
static const Pages OneGibPages = Pages(OneGib >> wasm::PageBits);
static_assert(wasm::HighestValidARMImmediate > OneGib,
"computing mapped size on ARM requires clamped max size");
Pages clampedPages = std::max(OneGibPages, initialPages);
clampedMaxPages = std::min(clampedPages, clampedMaxPages);
#endif
} else {
// There is not a specified maximum, fill it in with the implementation
// limit of maximum pages
clampedMaxPages = wasm::MaxMemoryPages();
}
// Double-check our invariants
MOZ_RELEASE_ASSERT(sourceMaxPages.isNothing() ||
clampedMaxPages <= *sourceMaxPages);
MOZ_RELEASE_ASSERT(clampedMaxPages <= wasm::MaxMemoryPages());
MOZ_RELEASE_ASSERT(initialPages <= clampedMaxPages);
return clampedMaxPages;
}

View File

@ -166,6 +166,11 @@ static inline uint64_t MaxMemoryLimitField(IndexType indexType) {
: MaxMemory64LimitField;
}
// Compute the 'clamped' maximum size of a memory. See
// 'WASM Linear Memory structure' in ArrayBufferObject.cpp for background.
Pages ClampedMaxPages(Pages initialPages, const Maybe<Pages>& sourceMaxPages,
bool useHugeMemory);
// Compiles the given binary wasm module given the ArrayBufferObject
// and links the module's imports with the given import object.
@ -417,7 +422,8 @@ class WasmMemoryObject : public NativeObject {
// The maximum length of the memory in pages. This is not 'volatile' in
// contrast to the current length, as it cannot change for shared memories.
mozilla::Maybe<wasm::Pages> maxPages() const;
wasm::Pages clampedMaxPages() const;
mozilla::Maybe<wasm::Pages> sourceMaxPages() const;
wasm::IndexType indexType() const;
bool isShared() const;

View File

@ -719,7 +719,7 @@ bool Module::instantiateMemory(JSContext* cx,
if (!CheckLimits(cx, desc.initialPages(), desc.maximumPages(),
/* defaultMax */ MaxMemoryPages(),
/* actualLength */
memory->volatilePages(), memory->maxPages(),
memory->volatilePages(), memory->sourceMaxPages(),
metadata().isAsmJS(), "Memory")) {
return false;
}

View File

@ -56,6 +56,7 @@ struct Pages {
uint64_t value_;
public:
constexpr Pages() : value_(0) {}
constexpr explicit Pages(uint64_t value) : value_(value) {}
// Get the wrapped page value. Only use this if you must, prefer to use or

View File

@ -375,7 +375,8 @@ bool wasm::IsValidBoundsCheckImmediate(uint32_t i) {
}
size_t wasm::ComputeMappedSize(wasm::Pages maxPages) {
// TODO: memory64 maximum size may overflow size_t
// Caller is responsible to ensure that maxPages has been clamped to
// implementation limits.
size_t maxSize = maxPages.byteLength();
// It is the bounds-check limit, not the mapped size, that gets baked into