From 8d69638b8df6474ceeefc924385ded2090522e1c Mon Sep 17 00:00:00 2001 From: Frotty Date: Sun, 30 Aug 2026 21:14:35 +0200 Subject: [PATCH 1/2] Expand generic dispatch differential fuzz coverage --- .../tests/LuaBackendAuditTests.java | 47 +++++++++++++++---- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaBackendAuditTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaBackendAuditTests.java index 786cb0361..d2be3efc4 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaBackendAuditTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaBackendAuditTests.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.EnumSet; import java.util.List; import java.util.Random; @@ -1544,7 +1545,10 @@ public void randomizedClassInterfaceModuleDispatchMatchesAllBackends() { public void randomizedGenericTupleDispatchMatchesJassAndLua() { Random random = new Random(0x71A9D15CL); for (int caseIndex = 0; caseIndex < 8; caseIndex++) { - List source = genericTupleDispatchCase(random, caseIndex); + EnumSet covered = EnumSet.noneOf(GenericDispatchShape.class); + List source = genericTupleDispatchCase(random, covered); + assertEquals("dispatch fuzz case must cover every semantic specialization shape", + EnumSet.allOf(GenericDispatchShape.class), covered); String[] lines = source.toArray(new String[0]); try { test().executeProg().lines(lines); @@ -1565,6 +1569,17 @@ public void randomizedGenericTupleDispatchMatchesJassAndLua() { } } + private enum GenericDispatchShape { + TUPLE_INT, + TUPLE_TEXT, + NESTED_TUPLE_INT, + NESTED_TUPLE_TEXT, + CLASS_PREDICATE, + OTHER_CLASS_PREDICATE, + OTHER_CLASS_PREDICATE_SECOND_TYPE, + CLASS_IMPLEMENTATION + } + /** A user method beginning with {@code destroy} is ordinary virtual dispatch, not lifecycle * destruction. The lifecycle slot is identified from the generated OnDestroy function. */ @Test @@ -1592,7 +1607,8 @@ public void ordinaryDestroyNamedMethodDoesNotUseLifecycleDispatchSlot() throws I assertFalse(dispatchBody.contains(".__wurst_destroy")); } - private List genericTupleDispatchCase(Random random, int caseIndex) { + private List genericTupleDispatchCase(Random random, + EnumSet covered) { List source = new ArrayList<>(); Collections.addAll(source, "package Test", @@ -1636,46 +1652,57 @@ private List genericTupleDispatchCase(Random random, int caseIndex) { " int successes = 0"); int expected = 0; + GenericDispatchShape[] shapes = GenericDispatchShape.values(); for (int i = 0; i < 16; i++) { int value = random.nextInt(100) + 1; - switch (random.nextInt(7)) { - case 0 -> { + GenericDispatchShape shape = i < shapes.length + ? shapes[i] + : shapes[random.nextInt(shapes.length)]; + covered.add(shape); + switch (shape) { + case TUPLE_INT -> { source.add(" let value" + i + " = new Box(PairInt(" + value + ", " + (value + 1) + "))"); source.add(" if value" + i + ".matches(x -> x.a == " + value + ")"); source.add(" successes++"); source.add(" destroy value" + i); } - case 1 -> { + case TUPLE_TEXT -> { source.add(" let value" + i + " = new Box(PairText(\"v" + value + "\", " + value + "))"); source.add(" if value" + i + ".matches(x -> x.a == \"v" + value + "\")"); source.add(" successes++"); source.add(" destroy value" + i); } - case 2 -> { + case NESTED_TUPLE_INT -> { source.add(" let value" + i + " = new Nested(PairInt(" + value + ", " + (value + 2) + "))"); source.add(" if value" + i + ".matches(x -> x.b == " + (value + 2) + ")"); source.add(" successes++"); source.add(" destroy value" + i); } - case 3 -> { + case NESTED_TUPLE_TEXT -> { + source.add(" let value" + i + " = new Nested(PairText(\"v" + value + "\", " + value + "))"); + source.add(" if value" + i + ".matches(x -> x.a == \"v" + value + "\")"); + source.add(" successes++"); + source.add(" destroy value" + i); + } + case CLASS_PREDICATE -> { source.add(" let value" + i + " = new Box(new Foo())"); source.add(" if value" + i + ".matches(x -> x != null)"); source.add(" successes++"); source.add(" destroy value" + i); } - case 4 -> { + case OTHER_CLASS_PREDICATE -> { source.add(" let value" + i + " = new OtherBox(new Foo())"); source.add(" if value" + i + ".matches(x -> x != null)"); source.add(" successes++"); source.add(" destroy value" + i); } - case 5 -> { + case OTHER_CLASS_PREDICATE_SECOND_TYPE -> { source.add(" let value" + i + " = new OtherBox(new Bar())"); source.add(" if value" + i + ".matches(x -> x != null)"); source.add(" successes++"); source.add(" destroy value" + i); } - default -> { + case CLASS_IMPLEMENTATION -> { source.add(" let value" + i + " = new Box(new Foo())"); source.add(" if value" + i + ".matches(new FooPredicate())"); source.add(" successes++"); From 21fbe5ad2b5a5573ded09e95337a4b5aec0d99fd Mon Sep 17 00:00:00 2001 From: Frotty Date: Sun, 30 Aug 2026 21:59:32 +0200 Subject: [PATCH 2/2] Randomize required dispatch fuzz shapes --- .../tests/wurstscript/tests/LuaBackendAuditTests.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaBackendAuditTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaBackendAuditTests.java index d2be3efc4..ae565f849 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaBackendAuditTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaBackendAuditTests.java @@ -1652,12 +1652,14 @@ private List genericTupleDispatchCase(Random random, " int successes = 0"); int expected = 0; - GenericDispatchShape[] shapes = GenericDispatchShape.values(); + List shapes = new ArrayList<>( + EnumSet.allOf(GenericDispatchShape.class)); + Collections.shuffle(shapes, random); for (int i = 0; i < 16; i++) { int value = random.nextInt(100) + 1; - GenericDispatchShape shape = i < shapes.length - ? shapes[i] - : shapes[random.nextInt(shapes.length)]; + GenericDispatchShape shape = i < shapes.size() + ? shapes.get(i) + : shapes.get(random.nextInt(shapes.size())); covered.add(shape); switch (shape) { case TUPLE_INT -> {