authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2024-11-30 10:20:20+01:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-01-22 03:01:02+01:00
loge800ea0fdd4ecbe2e28867550a8c36715f3c5ea9
treecc70cb1dc7b483e7fb49510c520452fb49bda8ee
parent97b97ae6202811e207196920018c3b9610957f90
signaturebadge-check Signed by SSH key SHA256:7B/LJ7bpR1eX8aCXSr4mtd5M45VMPKcx9zY8e95b5QM

wasm: Add a check for zero length around uses of memory.copy/memory.fill.

Apparently the WebAssembly spec requires these instructions to trap if the computed memory access could be out of bounds, even if the length is zero. Really a rather bizarre design choice.

1 files changed, 35 insertions(+), 0 deletions(-)

src/arch/wasm/CodeGen.zig+35
......@@ -1591,10 +1591,28 @@ fn memcpy(cg: *CodeGen, dst: WValue, src: WValue, len: WValue) !void {
15911591 // When bulk_memory is enabled, we lower it to wasm's memcpy instruction.
15921592 // If not, we lower it ourselves manually
15931593 if (std.Target.wasm.featureSetHas(cg.target.cpu.features, .bulk_memory)) {
1594 try cg.startBlock(.block, .empty);
1595
1596 // Even if `len` is zero, the spec requires an implementation to trap if `src + len` or
1597 // `dst + len` are out of memory bounds. This can easily happen in Zig in a case such as:
1598 //
1599 // const dst: [*]u8 = undefined;
1600 // const src: [*]u8 = undefined;
1601 // var len: usize = runtime_zero();
1602 // @memcpy(dst[0..len], src[0..len]);
1603 //
1604 // So explicitly avoid using `memory.copy` in the `len == 0` case. Lovely design.
1605 try cg.emitWValue(len);
1606 try cg.addTag(.i32_eqz);
1607 try cg.addLabel(.br_if, 0);
1608
15941609 try cg.lowerToStack(dst);
15951610 try cg.lowerToStack(src);
15961611 try cg.emitWValue(len);
15971612 try cg.addExtended(.memory_copy);
1613
1614 try cg.endBlock();
1615
15981616 return;
15991617 }
16001618
......@@ -4782,10 +4800,27 @@ fn memset(cg: *CodeGen, elem_ty: Type, ptr: WValue, len: WValue, value: WValue)
47824800 // When bulk_memory is enabled, we lower it to wasm's memset instruction.
47834801 // If not, we lower it ourselves.
47844802 if (std.Target.wasm.featureSetHas(cg.target.cpu.features, .bulk_memory) and abi_size == 1) {
4803 try cg.startBlock(.block, .empty);
4804
4805 // Even if `len` is zero, the spec requires an implementation to trap if `ptr + len` is
4806 // out of memory bounds. This can easily happen in Zig in a case such as:
4807 //
4808 // const ptr: [*]u8 = undefined;
4809 // var len: usize = runtime_zero();
4810 // @memset(ptr[0..len], 42);
4811 //
4812 // So explicitly avoid using `memory.fill` in the `len == 0` case. Lovely design.
4813 try cg.emitWValue(len);
4814 try cg.addTag(.i32_eqz);
4815 try cg.addLabel(.br_if, 0);
4816
47854817 try cg.lowerToStack(ptr);
47864818 try cg.emitWValue(value);
47874819 try cg.emitWValue(len);
47884820 try cg.addExtended(.memory_fill);
4821
4822 try cg.endBlock();
4823
47894824 return;
47904825 }
47914826