Skip to content

Commit 99f5dae

Browse files
committed
Keep a preserved name and a self reference on the vararg original
Two ways a generated copy could take over something that belongs only to the original it was copied from. A preserved name is part of the map's Warcraft-facing API, set by @preserveName and by ExecuteFunc. The copy inherits the flag, and because it also shares the original's trace, collectPredefinedNames resets both to the same source name: the emitted Lua then defines that name twice and the later definition wins. ExecuteFunc makes this easy to reach, since it marks its target preserved and emits a zero-argument call, which is exactly what makes a copy. The flag is now dropped from copies on Lua, where the original is retained and keeps the name that external code calls. The other is the reference case of the recursion fix. ReferenceRewritingCopy retargets a function's own references, and the repair visitor undid that for calls but not for function references, so a self reference inside a copy kept naming the copy: registering it as a callback would invoke a fixed-arity body at an arity nobody checked. Those two node types are the only ones the copy retargets that name a function at all, so the visitor now covers the pair rather than the reported half. Lua only, because nothing redirects a reference afterwards and only that target keeps the original; on Jass it is removed and the reference would dangle. Jass keeps both flags and both reference kinds exactly as before.
1 parent 83f361d commit 99f5dae

2 files changed

Lines changed: 58 additions & 7 deletions

File tree

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/VarargEliminator.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.util.stream.Collectors;
1414

1515
import static de.peeeq.wurstscript.translation.imtranslation.FunctionFlagEnum.IS_VARARG;
16+
import static de.peeeq.wurstscript.translation.imtranslation.FunctionFlagEnum.PRESERVE_NAME;
1617

1718
/**
1819
* Takes a program and eliminates vararg functions, replacing them with
@@ -207,10 +208,11 @@ private void generateVarargFunc(ImFunction func, List<ImExpr> arguments, Element
207208

208209
// Create new function
209210
ImFunction newFunc = ReferenceRewritingCopy.copy(func);
210-
// The copy retargets the function's own references, so a recursive call inside it now names
211-
// the copy. That is wrong whenever the recursion uses a different argument count: the call
212-
// must go back to naming the vararg original, so the rewrite below maps it to a copy of its
213-
// own arity like any other call.
211+
// ReferenceRewritingCopy retargets the function's own references - both call and reference
212+
// nodes - so inside the copy they now name the copy. That is wrong for either kind. A
213+
// recursive call must go back to naming the vararg original, so the rewrite below maps it to
214+
// a copy of its own arity like any other call; a self reference must name the original too,
215+
// because it is invoked at an arity this pass never sees.
214216
newFunc.accept(new Element.DefaultVisitor() {
215217
@Override
216218
public void visit(ImFunctionCall call) {
@@ -219,6 +221,17 @@ public void visit(ImFunctionCall call) {
219221
call.setFunc(func);
220222
}
221223
}
224+
225+
@Override
226+
public void visit(ImFuncRef ref) {
227+
super.visit(ref);
228+
// Lua only: nothing redirects a reference afterwards, so it keeps naming whatever it
229+
// is set to here, and only this target retains the original. On Jass the original is
230+
// removed below and pointing at it would leave the reference dangling.
231+
if (luaTarget && ref.getFunc() == newFunc) {
232+
ref.setFunc(func);
233+
}
234+
}
222235
});
223236
newFunc.setName(func.getName() + "_" + argumentSize);
224237
// replace vararg with special parameters:
@@ -266,12 +279,17 @@ public void visit(ImVarargLoop imLoop) {
266279
}
267280

268281

269-
// Remove vararg flag
282+
// Drop the vararg flag, and on Lua the name preservation with it. A preserved name is part
283+
// of the map's Warcraft-facing API and belongs to the retained original, which is what
284+
// external code calls at an arity this pass never sees. Since a copy shares the original's
285+
// trace, and LuaTranslator.collectPredefinedNames() resets every preserved function to its
286+
// trace's source name, an inherited flag would emit both under one name.
270287
List<FunctionFlag> list = new ArrayList<>();
271288
for (FunctionFlag flag : newFunc.getFlags()) {
272-
if (flag != IS_VARARG) {
273-
list.add(flag);
289+
if (flag == IS_VARARG || (luaTarget && flag == PRESERVE_NAME)) {
290+
continue;
274291
}
292+
list.add(flag);
275293
}
276294
newFunc.setFlags(list);
277295
// Add new function to prog

de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaBackendAuditTests.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2201,6 +2201,39 @@ public void varargParameterCannotBeForwardedToAMethod() {
22012201
}
22022202

22032203

2204+
/**
2205+
* `@preserveName` and `ExecuteFunc` mark a function's emitted name as part of the map's
2206+
* WC3-facing API, and `LuaTranslator.collectPredefinedNames()` resets every function carrying
2207+
* that flag to its trace's source name. A generated copy shares the original's trace, so an
2208+
* inherited flag would emit the original and every copy under one name and let the last
2209+
* definition win. The preserved name belongs to the retained original: that is the one external
2210+
* code calls, at an arity this pass never gets to see.
2211+
*/
2212+
@Test
2213+
public void preservedNameStaysOnTheVarargOriginalNotItsCopies() {
2214+
String compiled = compileOptimizedLua(
2215+
"preservedNameStaysOnTheVarargOriginalNotItsCopies",
2216+
"package Test",
2217+
"native consume(int i)",
2218+
"@preserveName @noinline public function tally(vararg int xs) returns int",
2219+
" var sum = 0",
2220+
" for x in xs",
2221+
" sum += x",
2222+
" return sum",
2223+
"init",
2224+
" consume(tally(1, 2))"
2225+
);
2226+
assertTrue("the fixed-arity copy must keep its own suffixed name:\n" + compiled,
2227+
compiled.contains("function tally_2("));
2228+
int definitions = 0;
2229+
for (int at = compiled.indexOf("function tally("); at >= 0;
2230+
at = compiled.indexOf("function tally(", at + 1)) {
2231+
definitions++;
2232+
}
2233+
assertEquals("the preserved name must name exactly one function:\n" + compiled,
2234+
1, definitions);
2235+
}
2236+
22042237
/**
22052238
* The inliner used to refuse every function whose return fact the local-player analysis had
22062239
* marked, and that fact fires for anything reachable from a client-local branch anywhere in the

0 commit comments

Comments
 (0)