From acf9aa30f99fe7cf4e319b9c63b7562f216fb666 Mon Sep 17 00:00:00 2001 From: Frotty Date: Fri, 31 Jul 2026 15:05:05 +0200 Subject: [PATCH 1/5] Enforce Jass vararg parameter limit --- .../validation/WurstValidator.java | 21 +++++- .../tests/wurstscript/tests/VarargTests.java | 64 +++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java index 985dc3a48..765047219 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java @@ -41,6 +41,8 @@ * attributes */ public class WurstValidator { + private static final int JASS_MAX_PARAMETERS = 31; + private enum Phase { LIGHT, HEAVY } private Phase phase = Phase.LIGHT; private boolean isHeavy() { return phase == Phase.HEAVY; } @@ -1852,7 +1854,24 @@ private void checkCall(StmtCall call) { throw new Error("unhandled case: " + Utils.printElement(call)); } - call.attrCallSignature().checkSignatureCompatibility(call.attrFunctionSignature(), funcName, call); + FunctionSignature signature = call.attrFunctionSignature(); + call.attrCallSignature().checkSignatureCompatibility(signature, funcName, call); + checkVarargJassParameterLimit(call, signature); + } + + private void checkVarargJassParameterLimit(StmtCall call, FunctionSignature signature) { + if (!signature.isVararg()) { + return; + } + int generatedParameterCount = call.getArgs().size(); + if (signature.getReceiverType() != null || call instanceof ExprNewObject) { + generatedParameterCount++; + } + if (generatedParameterCount > JASS_MAX_PARAMETERS) { + call.addError("Vararg call would generate " + generatedParameterCount + + " Jass parameters; the maximum is " + JASS_MAX_PARAMETERS + + ". Use multiple calls (for example with the cascade operator) or pass a collection instead."); + } } /** Error when a field in this class hides a field from a superclass. */ diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java index d706301eb..9fd4679f5 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java @@ -4,6 +4,17 @@ public class VarargTests extends WurstScriptTest { + private String arguments(int count) { + StringBuilder result = new StringBuilder(); + for (int i = 1; i <= count; i++) { + if (i > 1) { + result.append(","); + } + result.append(i); + } + return result.toString(); + } + @Test public void testVarargSyntax() { @@ -45,6 +56,59 @@ public void testVarargInput() { ); } + @Test + public void varargAllows31JassParameters() { + testAssertOkLines(false, + "package Test", + "function foo(vararg int ints)", + "init", + " foo(" + arguments(31) + ")" + ); + } + + @Test + public void varargRejectsMoreThan31JassParameters() { + testAssertErrorsLines(false, "would generate 32 Jass parameters; the maximum is 31", + "package Test", + "function foo(vararg int ints)", + "init", + " foo(" + arguments(32) + ")" + ); + } + + @Test + public void varargReceiverCountsAsJassParameter() { + testAssertErrorsLines(false, "would generate 32 Jass parameters; the maximum is 31", + "package Test", + "class C", + " function foo(vararg int ints)", + "init", + " new C.foo(" + arguments(31) + ")" + ); + } + + @Test + public void varargReceiverAllows31JassParameters() { + testAssertOkLines(false, + "package Test", + "class C", + " function foo(vararg int ints)", + "init", + " new C.foo(" + arguments(30) + ")" + ); + } + + @Test + public void varargConstructedObjectCountsAsJassParameter() { + testAssertErrorsLines(false, "would generate 32 Jass parameters; the maximum is 31", + "package Test", + "class C", + " construct(vararg int ints)", + "init", + " new C(" + arguments(31) + ")" + ); + } + @Test public void testVarargForeach() { testAssertOkLines(true, From 9690e33a06960224bf0eeea69dfe2f0ca4a74df9 Mon Sep 17 00:00:00 2001 From: Frotty Date: Fri, 31 Jul 2026 15:33:39 +0200 Subject: [PATCH 2/5] Apply vararg limit only to Jass --- .../imtranslation/VarargEliminator.java | 15 ++++++++++--- .../validation/WurstValidator.java | 21 +------------------ .../tests/wurstscript/tests/VarargTests.java | 16 ++++++++++++++ 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/VarargEliminator.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/VarargEliminator.java index 3c6c1f972..5b81ed65e 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/VarargEliminator.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/VarargEliminator.java @@ -2,6 +2,7 @@ import com.google.common.collect.HashBasedTable; import com.google.common.collect.Table; +import de.peeeq.wurstscript.attributes.CompileError; import de.peeeq.wurstscript.jassIm.*; import org.jetbrains.annotations.NotNull; @@ -18,6 +19,7 @@ */ public class VarargEliminator { + private static final int JASS_MAX_PARAMETERS = 31; private final ImProg prog; // original + number of args --> new function private final Table varargFuncs = HashBasedTable.create(); @@ -30,7 +32,7 @@ public void run() { // create new vararg functions for (ImFunctionCall c : collectVarargCalls()) { if (c.getFunc().hasFlag(IS_VARARG)) { - generateVarargFunc(c.getFunc(), c.getArguments().size()); + generateVarargFunc(c); } } @@ -66,7 +68,14 @@ public void visit(ImFunctionCall c) { * Generates a function based on the vararg function with the appropriate amount of parameters * for the function call. */ - private void generateVarargFunc(ImFunction func, int numberOfParams) { + private void generateVarargFunc(ImFunctionCall sourceCall) { + ImFunction func = sourceCall.getFunc(); + int numberOfParams = sourceCall.getArguments().size(); + if (numberOfParams > JASS_MAX_PARAMETERS) { + throw new CompileError(sourceCall, "Vararg call would generate " + numberOfParams + + " Jass parameters; the maximum is " + JASS_MAX_PARAMETERS + + ". Use multiple calls (for example with the cascade operator) or pass a collection instead."); + } if (varargFuncs.contains(func, numberOfParams)) { // already generated return; @@ -118,7 +127,7 @@ public void visit(ImVarargLoop imLoop) { params.addAll(list); // generate function for this new call - generateVarargFunc(call.getFunc(), call.getArguments().size()); + generateVarargFunc(call); } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java index 765047219..985dc3a48 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java @@ -41,8 +41,6 @@ * attributes */ public class WurstValidator { - private static final int JASS_MAX_PARAMETERS = 31; - private enum Phase { LIGHT, HEAVY } private Phase phase = Phase.LIGHT; private boolean isHeavy() { return phase == Phase.HEAVY; } @@ -1854,24 +1852,7 @@ private void checkCall(StmtCall call) { throw new Error("unhandled case: " + Utils.printElement(call)); } - FunctionSignature signature = call.attrFunctionSignature(); - call.attrCallSignature().checkSignatureCompatibility(signature, funcName, call); - checkVarargJassParameterLimit(call, signature); - } - - private void checkVarargJassParameterLimit(StmtCall call, FunctionSignature signature) { - if (!signature.isVararg()) { - return; - } - int generatedParameterCount = call.getArgs().size(); - if (signature.getReceiverType() != null || call instanceof ExprNewObject) { - generatedParameterCount++; - } - if (generatedParameterCount > JASS_MAX_PARAMETERS) { - call.addError("Vararg call would generate " + generatedParameterCount - + " Jass parameters; the maximum is " + JASS_MAX_PARAMETERS - + ". Use multiple calls (for example with the cascade operator) or pass a collection instead."); - } + call.attrCallSignature().checkSignatureCompatibility(call.attrFunctionSignature(), funcName, call); } /** Error when a field in this class hides a field from a superclass. */ diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java index 9fd4679f5..a0c7ed966 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java @@ -76,6 +76,22 @@ public void varargRejectsMoreThan31JassParameters() { ); } + @Test + public void varargAllowsMoreThan31ArgumentsInLua() { + test().testLua(true).executeProg().lines( + "package Test", + "native testSuccess()", + "function foo(vararg int ints)", + " var sum = 0", + " for i in ints", + " sum += i", + " if sum == 528", + " testSuccess()", + "init", + " foo(" + arguments(32) + ")" + ); + } + @Test public void varargReceiverCountsAsJassParameter() { testAssertErrorsLines(false, "would generate 32 Jass parameters; the maximum is 31", From 6f47a8898069af716dd221235d94a502de3a46d4 Mon Sep 17 00:00:00 2001 From: Frotty Date: Fri, 31 Jul 2026 15:45:59 +0200 Subject: [PATCH 3/5] Account for stacktrace vararg parameter --- .../peeeq/wurstio/WurstCompilerJassImpl.java | 14 ++++----- .../tests/wurstscript/tests/VarargTests.java | 30 +++++++++++++++++-- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java index b4b503730..27771de5d 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java @@ -407,6 +407,12 @@ public JassProg transformProgToJass() { printDebugImProg("./test-output/im " + stage++ + "_classesEliminated.im"); timeTaker.endPhase(); + if (!runArgs.isNoDebugMessages() && runArgs.isIncludeStacktraces()) { + beginPhase(4, "add stack traces"); + new StackTraceInjector2(imProg2, imTranslator2).transform(timeTaker); + timeTaker.endPhase(); + } + new VarargEliminator(imProg2).run(); printDebugImProg("./test-output/im " + stage++ + "_varargEliminated.im"); imTranslator2.assertProperties(); @@ -417,14 +423,8 @@ public JassProg transformProgToJass() { beginPhase(3, "remove debug messages"); DebugMessageRemover.removeDebugMessages(imProg2); timeTaker.endPhase(); - } else { - // debug: add stacktraces - if (runArgs.isIncludeStacktraces()) { - beginPhase(4, "add stack traces"); - new StackTraceInjector2(imProg2, imTranslator2).transform(timeTaker); - timeTaker.endPhase(); - } } + imTranslator2.assertProperties(); ImOptimizer optimizer = new ImOptimizer(timeTaker, imTranslator2); diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java index a0c7ed966..98959ed5b 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java @@ -66,6 +66,32 @@ public void varargAllows31JassParameters() { ); } + @Test + public void stacktracedVarargRejectsMoreThan31JassParameters() { + testAssertErrorsLines(false, "would generate 32 Jass parameters; the maximum is 31", + "package Test", + "function getStackTraceString() returns string", + " return \"\"", + "function foo(vararg int ints)", + " getStackTraceString()", + "init", + " foo(" + arguments(31) + ")" + ); + } + + @Test + public void stacktracedVarargAllows31JassParameters() { + testAssertOkLines(false, + "package Test", + "function getStackTraceString() returns string", + " return \"\"", + "function foo(vararg int ints)", + " getStackTraceString()", + "init", + " foo(" + arguments(30) + ")" + ); + } + @Test public void varargRejectsMoreThan31JassParameters() { testAssertErrorsLines(false, "would generate 32 Jass parameters; the maximum is 31", @@ -104,13 +130,13 @@ public void varargReceiverCountsAsJassParameter() { } @Test - public void varargReceiverAllows31JassParameters() { + public void varargReceiverAllows31JassParametersWithStacktraces() { testAssertOkLines(false, "package Test", "class C", " function foo(vararg int ints)", "init", - " new C.foo(" + arguments(30) + ")" + " new C.foo(" + arguments(29) + ")" ); } From ac713036f786bc66c7f57356fcd4e7b44b766682 Mon Sep 17 00:00:00 2001 From: Frotty Date: Fri, 31 Jul 2026 15:48:08 +0200 Subject: [PATCH 4/5] Test delegating vararg constructor limit --- .../java/tests/wurstscript/tests/VarargTests.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java index 98959ed5b..e10e68202 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java @@ -151,6 +151,17 @@ public void varargConstructedObjectCountsAsJassParameter() { ); } + @Test + public void varargDelegatingConstructorCountsConstructedObject() { + testAssertErrorsLines(false, "would generate 32 Jass parameters; the maximum is 31", + "package Test", + "class C", + " construct()", + " this(" + arguments(31) + ")", + " construct(vararg int ints)" + ); + } + @Test public void testVarargForeach() { testAssertOkLines(true, From 9dc089b3b34a13c18ef4f8bcefad1d2db333464d Mon Sep 17 00:00:00 2001 From: Frotty Date: Fri, 31 Jul 2026 15:58:45 +0200 Subject: [PATCH 5/5] Count tuple fields in vararg limit --- .../imtranslation/VarargEliminator.java | 16 +++++++-- .../tests/wurstscript/tests/VarargTests.java | 33 +++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/VarargEliminator.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/VarargEliminator.java index 5b81ed65e..54287da50 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/VarargEliminator.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/VarargEliminator.java @@ -71,8 +71,11 @@ public void visit(ImFunctionCall c) { private void generateVarargFunc(ImFunctionCall sourceCall) { ImFunction func = sourceCall.getFunc(); int numberOfParams = sourceCall.getArguments().size(); - if (numberOfParams > JASS_MAX_PARAMETERS) { - throw new CompileError(sourceCall, "Vararg call would generate " + numberOfParams + int jassParameterCount = sourceCall.getArguments().stream() + .mapToInt(argument -> flattenedJassArity(argument.attrTyp())) + .sum(); + if (jassParameterCount > JASS_MAX_PARAMETERS) { + throw new CompileError(sourceCall, "Vararg call would generate " + jassParameterCount + " Jass parameters; the maximum is " + JASS_MAX_PARAMETERS + ". Use multiple calls (for example with the cascade operator) or pass a collection instead."); } @@ -144,6 +147,15 @@ public void visit(ImVarargLoop imLoop) { varargFuncs.put(func, numberOfParams, newFunc); } + private int flattenedJassArity(ImType type) { + if (type instanceof ImTupleType) { + return ((ImTupleType) type).getTypes().stream() + .mapToInt(this::flattenedJassArity) + .sum(); + } + return 1; + } + @NotNull private List collectUsesOfVar(ImFunction newFunc, ImVar varargParam) { List varargParamUses = new ArrayList<>(); diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java index e10e68202..3bb01c67f 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java @@ -15,6 +15,17 @@ private String arguments(int count) { return result.toString(); } + private String tupleArguments(int count) { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < count; i++) { + if (i > 0) { + result.append(","); + } + result.append("pair(1,2)"); + } + return result.toString(); + } + @Test public void testVarargSyntax() { @@ -102,6 +113,28 @@ public void varargRejectsMoreThan31JassParameters() { ); } + @Test + public void varargCountsFlattenedTupleParameters() { + testAssertErrorsLines(false, "would generate 32 Jass parameters; the maximum is 31", + "package Test", + "tuple pair(int x, int y)", + "function foo(vararg pair pairs)", + "init", + " foo(" + tupleArguments(16) + ")" + ); + } + + @Test + public void varargAllowsFlattenedTupleParametersWithinLimit() { + testAssertOkLines(false, + "package Test", + "tuple pair(int x, int y)", + "function foo(vararg pair pairs)", + "init", + " foo(" + tupleArguments(15) + ")" + ); + } + @Test public void varargAllowsMoreThan31ArgumentsInLua() { test().testLua(true).executeProg().lines(