From 94a9bfbba3a3ffe58330766afa6605646e68f166 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sat, 25 Jul 2026 12:08:44 +0200 Subject: [PATCH 1/2] Replace `assert` statements used for argument validation Instead of an Exception they cause an AssertionError (an Error), or worse when assertions are disabled at runtime execution just continues despite the program being in an invalid state. --- .../endive/build/time/compiler/Generator.java | 8 +++++-- .../run/endive/runtime/GlobalInstance.java | 5 ++++- .../java/run/endive/runtime/Instance.java | 9 ++++++++ .../endive/runtime/InterpreterMachine.java | 2 +- .../src/main/java/run/endive/wasm/Parser.java | 6 +++++- .../main/java/run/endive/wasm/Validator.java | 4 +++- .../wasm/types/AnnotatedInstruction.java | 21 +++++++++++++------ .../run/endive/wasm/types/CatchOpCode.java | 5 ++++- .../endive/wasm/types/NameCustomSection.java | 5 ++++- .../java/run/endive/wasm/types/RecType.java | 4 +++- .../java/run/endive/wasm/types/Value.java | 17 ++++++++++----- .../run/endive/wasm/types/ValTypeTest.java | 2 +- 12 files changed, 67 insertions(+), 21 deletions(-) diff --git a/build-time-compiler/src/main/java/run/endive/build/time/compiler/Generator.java b/build-time-compiler/src/main/java/run/endive/build/time/compiler/Generator.java index 1aacbf2cd..7d79bc20b 100644 --- a/build-time-compiler/src/main/java/run/endive/build/time/compiler/Generator.java +++ b/build-time-compiler/src/main/java/run/endive/build/time/compiler/Generator.java @@ -155,7 +155,9 @@ public void generateMetaWasm(Set interpretedFunctions) throws IOExcepti int count = module.codeSection().functionBodyCount(); writeVarUInt32(out, count); var actual = readVarUInt32(source); - assert count == actual; + if (count != actual) { + throw new RuntimeException("wrong number of function bodies"); + } for (int i = 0; i < count; i++) { var funcId = importFuncs + i; if (interpretedFunctions.contains(funcId)) { @@ -177,7 +179,9 @@ public void generateMetaWasm(Set interpretedFunctions) throws IOExcepti var bodySize = (int) readVarUInt32(source); source.position(source.position() + bodySize - 1); var end_op = source.get(); - assert end_op == OpCode.END.opcode(); + if (end_op != OpCode.END.opcode()) { + throw new RuntimeException("unexpected end opcode: " + end_op); + } // Write an empty function body writeVarUInt32(out, 3); // function size in bytes diff --git a/runtime/src/main/java/run/endive/runtime/GlobalInstance.java b/runtime/src/main/java/run/endive/runtime/GlobalInstance.java index 35573be3b..aec4ca19e 100644 --- a/runtime/src/main/java/run/endive/runtime/GlobalInstance.java +++ b/runtime/src/main/java/run/endive/runtime/GlobalInstance.java @@ -89,7 +89,10 @@ public ValType getType() { } public void setValue(Value value) { - assert (value.type() == valType); + if (value.type() != valType) { + throw new IllegalArgumentException( + "Value has wrong type; expected " + valType + " got " + value.type()); + } this.valueLow = value.raw(); } diff --git a/runtime/src/main/java/run/endive/runtime/Instance.java b/runtime/src/main/java/run/endive/runtime/Instance.java index e9d5f57e4..cae2aba63 100644 --- a/runtime/src/main/java/run/endive/runtime/Instance.java +++ b/runtime/src/main/java/run/endive/runtime/Instance.java @@ -1072,6 +1072,15 @@ private Map genExports(ExportSection export) { return exports; } + /** + * Builds the instance. + * + *

When running in 'runtime compilation' mode, invalid or unsupported Wasm code might already cause an + * exception when calling this method. For 'interpreter' mode such exceptions might instead occur during + * execution of the Wasm code. + * + * @throws RuntimeException if the Wasm code is invalid or contains unsupported instructions + */ public Instance build() { Map exports = genExports(module.exportSection()); var globalInitializers = module.globalSection().globals(); diff --git a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java index 5a8b062ff..6849fa0f5 100644 --- a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java +++ b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java @@ -3215,7 +3215,7 @@ protected static StackFrame THROW_REF( frame = callStack.peek(); // peek, don't pop - keep catcher on callStack } } - throw new RuntimeException("unreacheable"); + throw new RuntimeException("unreachable"); } private static void BLOCK( diff --git a/wasm/src/main/java/run/endive/wasm/Parser.java b/wasm/src/main/java/run/endive/wasm/Parser.java index 3eda7a7c9..da031950f 100644 --- a/wasm/src/main/java/run/endive/wasm/Parser.java +++ b/wasm/src/main/java/run/endive/wasm/Parser.java @@ -95,6 +95,8 @@ /** * Parser for Web Assembly binaries. + * + *

If parsing fails, a {@link RuntimeException} or any subtype of it might be thrown. */ @SuppressWarnings("UnnecessaryCodeBlock") public final class Parser { @@ -785,7 +787,9 @@ private static TableSection parseTableSection(ByteBuffer buffer, TypeSection typ var firstByte = (int) readVarUInt32(buffer); if (firstByte == 0x40) { var secondByte = readVarUInt32(buffer); - assert secondByte == 0x00; + if (secondByte != 0x00) { + throw new MalformedException("incorrect second byte"); + } var tableType = readValueType(buffer, typeSection); var limits = readTableLimits(buffer); var init = parseExpression(buffer); diff --git a/wasm/src/main/java/run/endive/wasm/Validator.java b/wasm/src/main/java/run/endive/wasm/Validator.java index bf52b0e3d..994b44c1b 100644 --- a/wasm/src/main/java/run/endive/wasm/Validator.java +++ b/wasm/src/main/java/run/endive/wasm/Validator.java @@ -1033,7 +1033,9 @@ void validateFunction(int funcIdx, FunctionBody body, FunctionType functionType) } var type = module.typeSection().getType(getTagType(tagNumber).typeIdx()); popVals(type.params()); - assert (type.returns().size() == 0); + if (!type.returns().isEmpty()) { + throw new InvalidException("expected no returns"); + } unreachable(); break; } diff --git a/wasm/src/main/java/run/endive/wasm/types/AnnotatedInstruction.java b/wasm/src/main/java/run/endive/wasm/types/AnnotatedInstruction.java index d6034d3df..1548f006f 100644 --- a/wasm/src/main/java/run/endive/wasm/types/AnnotatedInstruction.java +++ b/wasm/src/main/java/run/endive/wasm/types/AnnotatedInstruction.java @@ -156,10 +156,14 @@ public AnnotatedInstruction build() { case END: case IF: case TRY_TABLE: - assert (scope.isPresent()); + if (scope.isEmpty()) { + throw new InvalidException("unknown scope"); + } break; default: - assert (scope.isEmpty()); + if (scope.isPresent()) { + throw new InvalidException("scope is not empty"); + } break; } switch (base.opcode()) { @@ -180,8 +184,9 @@ public AnnotatedInstruction build() { } break; default: - assert (labelTrue.isEmpty()); - assert (labelFalse.isEmpty()); + if (!(labelTrue.isEmpty() && labelFalse.isEmpty())) { + throw new InvalidException("labels are not empty"); + } break; } switch (base.opcode()) { @@ -191,7 +196,9 @@ public AnnotatedInstruction build() { } break; default: - assert (labelTable.isEmpty()); + if (labelTable.isPresent()) { + throw new InvalidException("label table is not empty"); + } break; } switch (base.opcode()) { @@ -201,7 +208,9 @@ public AnnotatedInstruction build() { } break; default: - assert (catches.isEmpty()); + if (catches.isPresent()) { + throw new InvalidException("catches is not empty"); + } break; } diff --git a/wasm/src/main/java/run/endive/wasm/types/CatchOpCode.java b/wasm/src/main/java/run/endive/wasm/types/CatchOpCode.java index 2a951fd70..2969fa3c1 100644 --- a/wasm/src/main/java/run/endive/wasm/types/CatchOpCode.java +++ b/wasm/src/main/java/run/endive/wasm/types/CatchOpCode.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import run.endive.wasm.WasmEngineException; public enum CatchOpCode { CATCH(0x00), @@ -98,7 +99,9 @@ public static List decode(long[] operands) { } } } - assert (result.size() == length); + if (result.size() != length) { + throw new WasmEngineException("wrong result size"); + } return result; } diff --git a/wasm/src/main/java/run/endive/wasm/types/NameCustomSection.java b/wasm/src/main/java/run/endive/wasm/types/NameCustomSection.java index 582116cd7..fe7bdb78f 100644 --- a/wasm/src/main/java/run/endive/wasm/types/NameCustomSection.java +++ b/wasm/src/main/java/run/endive/wasm/types/NameCustomSection.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.Optional; import java.util.function.ToIntFunction; +import run.endive.wasm.WasmEngineException; /** * The "name" custom section. @@ -74,7 +75,9 @@ public static NameCustomSection parse(byte[] bytes) { // todo: IDs 4 and 10 are reserved for the Host GC spec switch (id) { case 0: - assert (moduleName == null); + if (moduleName != null) { + throw new WasmEngineException("duplicate module name"); + } moduleName = readName(slice); break; case 1: diff --git a/wasm/src/main/java/run/endive/wasm/types/RecType.java b/wasm/src/main/java/run/endive/wasm/types/RecType.java index 05b82430b..4748b6e2b 100644 --- a/wasm/src/main/java/run/endive/wasm/types/RecType.java +++ b/wasm/src/main/java/run/endive/wasm/types/RecType.java @@ -21,7 +21,9 @@ public boolean isLegacy() { } public FunctionType legacy() { - assert subTypes.length == 1; + if (!isLegacy()) { + throw new IllegalStateException("type is not legacy"); + } return subTypes[0].compType().funcType(); } diff --git a/wasm/src/main/java/run/endive/wasm/types/Value.java b/wasm/src/main/java/run/endive/wasm/types/Value.java index adb717a79..e713183ab 100644 --- a/wasm/src/main/java/run/endive/wasm/types/Value.java +++ b/wasm/src/main/java/run/endive/wasm/types/Value.java @@ -65,23 +65,30 @@ public static Value fromFloat(float data) { return Value.f32(floatToLong(data)); } + private void expectType(ValType expected) { + if (type != expected) { + throw new IllegalStateException( + "Expected value to have type " + expected + " but is " + type); + } + } + public int asInt() { - assert (type == ValType.I32); + expectType(ValType.I32); return (int) data; } public long asLong() { - assert (type == ValType.I64); + expectType(ValType.I64); return data; } public float asFloat() { - assert (type == ValType.F32); + expectType(ValType.F32); return longToFloat(data); } public double asDouble() { - assert (type == ValType.F64); + expectType(ValType.F64); return longToDouble(data); } @@ -332,7 +339,7 @@ public String toString() { case ValType.ID.RefNull: return "refnull[" + (int) data + "]"; default: - throw new AssertionError("Unhandled type: " + type); + throw new RuntimeException("Unhandled type: " + type); } } diff --git a/wasm/src/test/java/run/endive/wasm/types/ValTypeTest.java b/wasm/src/test/java/run/endive/wasm/types/ValTypeTest.java index 080e10519..e1744a607 100644 --- a/wasm/src/test/java/run/endive/wasm/types/ValTypeTest.java +++ b/wasm/src/test/java/run/endive/wasm/types/ValTypeTest.java @@ -33,7 +33,7 @@ public void roundtrip() { for (var vt : cases) { long id = vt.id(); ValType roundTrip = ValType.builder().fromId(id).build(); - assert vt.equals(roundTrip) : "Failed to roundtrip: " + vt; + assertEquals(vt, roundTrip); } } From c79335eb97458938796a038147a1548ae75679b1 Mon Sep 17 00:00:00 2001 From: andreatp Date: Mon, 10 Aug 2026 12:54:42 +0200 Subject: [PATCH 2/2] Address review feedback and fix new assert from issue #139 - Value.toString(): use safe non-throwing default instead of RuntimeException - Instance.build(): drop confusing Javadoc about interpreter vs compiler modes - Generator: use MalformedException instead of bare RuntimeException - Parser: remove vague class-level Javadoc addition - Emitters.assertTempSlotInRange: replace assert with WasmEngineException --- .../endive/build/time/compiler/Generator.java | 6 ++++-- .../run/endive/compiler/internal/Emitters.java | 17 ++++++++++------- .../main/java/run/endive/runtime/Instance.java | 9 --------- .../java/run/endive/runtime/OpcodeImpl.java | 9 ++++++--- wasm/src/main/java/run/endive/wasm/Parser.java | 2 -- .../endive/wasm/types/AnnotatedInstruction.java | 1 - .../main/java/run/endive/wasm/types/Value.java | 2 +- 7 files changed, 21 insertions(+), 25 deletions(-) diff --git a/build-time-compiler/src/main/java/run/endive/build/time/compiler/Generator.java b/build-time-compiler/src/main/java/run/endive/build/time/compiler/Generator.java index 7d79bc20b..1e47938c7 100644 --- a/build-time-compiler/src/main/java/run/endive/build/time/compiler/Generator.java +++ b/build-time-compiler/src/main/java/run/endive/build/time/compiler/Generator.java @@ -40,6 +40,7 @@ import run.endive.runtime.CompiledModule; import run.endive.runtime.Instance; import run.endive.runtime.Machine; +import run.endive.wasm.MalformedException; import run.endive.wasm.Parser; import run.endive.wasm.WasmModule; import run.endive.wasm.WasmWriter; @@ -156,7 +157,7 @@ public void generateMetaWasm(Set interpretedFunctions) throws IOExcepti writeVarUInt32(out, count); var actual = readVarUInt32(source); if (count != actual) { - throw new RuntimeException("wrong number of function bodies"); + throw new MalformedException("wrong number of function bodies"); } for (int i = 0; i < count; i++) { var funcId = importFuncs + i; @@ -180,7 +181,8 @@ public void generateMetaWasm(Set interpretedFunctions) throws IOExcepti source.position(source.position() + bodySize - 1); var end_op = source.get(); if (end_op != OpCode.END.opcode()) { - throw new RuntimeException("unexpected end opcode: " + end_op); + throw new MalformedException( + "unexpected end opcode: " + end_op); } // Write an empty function body diff --git a/compiler/src/main/java/run/endive/compiler/internal/Emitters.java b/compiler/src/main/java/run/endive/compiler/internal/Emitters.java index 7b336649f..7c10aabe2 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Emitters.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Emitters.java @@ -38,6 +38,7 @@ import run.endive.runtime.Instance; import run.endive.runtime.OpCodeIdentifier; import run.endive.runtime.WasmException; +import run.endive.wasm.WasmEngineException; import run.endive.wasm.WasmModule; import run.endive.wasm.types.FunctionType; import run.endive.wasm.types.ValType; @@ -104,13 +105,15 @@ public static ValType valType(long id, Context ctx) { } private static void assertTempSlotInRange(Context ctx, int slotsNeeded) { - assert ctx.tempSlot() + slotsNeeded <= ctx.trySaveBaseSlot() - : "temp slot overflow: need " - + slotsNeeded - + " slots at " - + ctx.tempSlot() - + " but try-save starts at " - + ctx.trySaveBaseSlot(); + if (ctx.tempSlot() + slotsNeeded > ctx.trySaveBaseSlot()) { + throw new WasmEngineException( + "temp slot overflow: need " + + slotsNeeded + + " slots at " + + ctx.tempSlot() + + " but try-save starts at " + + ctx.trySaveBaseSlot()); + } } /** diff --git a/runtime/src/main/java/run/endive/runtime/Instance.java b/runtime/src/main/java/run/endive/runtime/Instance.java index cae2aba63..e9d5f57e4 100644 --- a/runtime/src/main/java/run/endive/runtime/Instance.java +++ b/runtime/src/main/java/run/endive/runtime/Instance.java @@ -1072,15 +1072,6 @@ private Map genExports(ExportSection export) { return exports; } - /** - * Builds the instance. - * - *

When running in 'runtime compilation' mode, invalid or unsupported Wasm code might already cause an - * exception when calling this method. For 'interpreter' mode such exceptions might instead occur during - * execution of the Wasm code. - * - * @throws RuntimeException if the Wasm code is invalid or contains unsupported instructions - */ public Instance build() { Map exports = genExports(module.exportSection()); var globalInitializers = module.globalSection().globals(); diff --git a/runtime/src/main/java/run/endive/runtime/OpcodeImpl.java b/runtime/src/main/java/run/endive/runtime/OpcodeImpl.java index 9db8a019b..450c68128 100644 --- a/runtime/src/main/java/run/endive/runtime/OpcodeImpl.java +++ b/runtime/src/main/java/run/endive/runtime/OpcodeImpl.java @@ -917,9 +917,12 @@ public static long unboxFromTable(int tableValue, Instance instance, ValType ele impl = java.lang.invoke.VarHandle::fullFence; } catch (NoSuchMethodError e) { try { - // Suppress IntelliJ warning about module-info.java needing `requires jdk.unsupported` for - // `sun.misc.Unsafe`. This code here is only a fallback when `VarHandle::fullFence` is unavailable, - // which is only the case for Java < 9 (and therefore module-info.java is irrelevant). + // Suppress IntelliJ warning about module-info.java needing `requires + // jdk.unsupported` for + // `sun.misc.Unsafe`. This code here is only a fallback when `VarHandle::fullFence` + // is unavailable, + // which is only the case for Java < 9 (and therefore module-info.java is + // irrelevant). @SuppressWarnings("Java9ReflectionClassVisibility") Class unsafeClass = Class.forName("sun.misc.Unsafe"); var theUnsafeField = unsafeClass.getDeclaredField("theUnsafe"); diff --git a/wasm/src/main/java/run/endive/wasm/Parser.java b/wasm/src/main/java/run/endive/wasm/Parser.java index da031950f..ce5383706 100644 --- a/wasm/src/main/java/run/endive/wasm/Parser.java +++ b/wasm/src/main/java/run/endive/wasm/Parser.java @@ -95,8 +95,6 @@ /** * Parser for Web Assembly binaries. - * - *

If parsing fails, a {@link RuntimeException} or any subtype of it might be thrown. */ @SuppressWarnings("UnnecessaryCodeBlock") public final class Parser { diff --git a/wasm/src/main/java/run/endive/wasm/types/AnnotatedInstruction.java b/wasm/src/main/java/run/endive/wasm/types/AnnotatedInstruction.java index 1548f006f..15959d4d6 100644 --- a/wasm/src/main/java/run/endive/wasm/types/AnnotatedInstruction.java +++ b/wasm/src/main/java/run/endive/wasm/types/AnnotatedInstruction.java @@ -4,7 +4,6 @@ import java.util.Objects; import java.util.Optional; import java.util.OptionalInt; - import run.endive.wasm.InvalidException; /* diff --git a/wasm/src/main/java/run/endive/wasm/types/Value.java b/wasm/src/main/java/run/endive/wasm/types/Value.java index e713183ab..a0627d843 100644 --- a/wasm/src/main/java/run/endive/wasm/types/Value.java +++ b/wasm/src/main/java/run/endive/wasm/types/Value.java @@ -339,7 +339,7 @@ public String toString() { case ValType.ID.RefNull: return "refnull[" + (int) data + "]"; default: - throw new RuntimeException("Unhandled type: " + type); + return data + "@" + type; } }