Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion de.peeeq.wurstscript/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ dependencies {
implementation 'com.github.albfernandez:juniversalchardet:2.4.0'
implementation 'org.xerial:sqlite-jdbc:3.46.1.3'
implementation 'com.github.inwc3:jmpq3:29b55f2c32'
implementation 'com.github.inwc3:wc3libs:cc49c8e63c'
implementation 'com.github.inwc3:wc3libs:6a96a79595'
implementation('com.github.wurstscript:wurstsetup:393cf5ea39') {
exclude group: 'org.eclipse.jgit', module: 'org.eclipse.jgit'
exclude group: 'org.eclipse.jgit', module: 'org.eclipse.jgit.ssh.apache'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,17 @@ public static MapRequest.CompilationResult apply(WurstProjectConfigData projectC
applyBuildMapData(projectConfig, mapScript, buildDir, w3data, w3I, result, configHash, outputScriptName);
} else if (!configNeedsApplying) {
WLogger.info("Using cached w3i configuration");
// Still need to set the result.script correctly
result.script = mapScript;
// Prefer the previously-injected script (with correct config() body) over the
// raw map script. If it doesn't exist yet (e.g. first Lua build after a JASS-only
// cache), fall through to re-inject so the config() body is never stale.
File cachedInjectedScript = new File(buildDir, outputScriptName);
if (cachedInjectedScript.exists()) {
result.script = cachedInjectedScript;
} else if (StringUtils.isNotBlank(buildMapData.getName())) {
WLogger.info("Cached injected script missing, re-injecting config");
applyBuildMapData(projectConfig, mapScript, buildDir, w3data, w3I, result, configHash, outputScriptName);
}
// else result.script stays as mapScript (no wurst.build name configured)
}

result.w3i = new File(buildDir, "war3map.w3i");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -612,13 +612,19 @@ protected void injectMapData(WurstGui gui, Optional<File> testMap, CompilationRe
mapScriptName = "war3map.j";
}

// Delete old scripts
// Delete old scripts (both root and scripts/ subdirectory locations)
if (mpqEditor.hasFile("war3map.j")) {
mpqEditor.deleteFile("war3map.j");
}
if (mpqEditor.hasFile("scripts\\war3map.j")) {
mpqEditor.deleteFile("scripts\\war3map.j");
}
if (mpqEditor.hasFile("war3map.lua")) {
mpqEditor.deleteFile("war3map.lua");
}
if (mpqEditor.hasFile("scripts\\war3map.lua")) {
mpqEditor.deleteFile("scripts\\war3map.lua");
}

// Insert new script
mpqEditor.insertFile(mapScriptName, result.script);
Expand Down Expand Up @@ -696,6 +702,10 @@ protected File executeBuildMapPipeline(ModelManager modelManager, WurstGui gui,
gui.sendProgress("Finalizing map");
try (MpqEditor mpq = MpqEditorFactory.getEditor(Optional.of(targetMapFile))) {
if (mpq != null) {
// Strip internal Wurst cache files — they are dev-only metadata and should
// not be present in the distributed map.
if (mpq.hasFile("wurst_cache_manifest.txt")) mpq.deleteFile("wurst_cache_manifest.txt");
if (mpq.hasFile("wurst_object_cache.txt")) mpq.deleteFile("wurst_object_cache.txt");
mpq.closeWithCompression();
}
}
Expand Down Expand Up @@ -823,6 +833,9 @@ protected byte[] extractMapScript(Optional<File> mapCopy) throws Exception {
if (mpqEditor.hasFile("war3map.j")) {
return mpqEditor.extractFile("war3map.j");
}
if (mpqEditor.hasFile("scripts\\war3map.j")) {
return mpqEditor.extractFile("scripts\\war3map.j");
}
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,11 @@ private boolean isInlineCandidate(ImFunction f) {
// this is only relevant for lua, because in JASS they are eliminated before inlining
return false;
}
if (translator.luaInitFunctions.containsKey(f)) {
// Lua package init functions must stay as ImFunctionCall nodes so StmtTranslation
// can wrap them in xpcall. Inlining removes the call site and loses the guard.
return false;
}
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,14 @@ public class ImTranslator {

private final ImProg imProg;

final Map<WPackage, ImFunction> initFuncMap = new Object2ObjectLinkedOpenHashMap<>();
public final Map<WPackage, ImFunction> initFuncMap = new Object2ObjectLinkedOpenHashMap<>();

/**
* When targeting Lua, package init functions that should be called directly via xpcall
* rather than through the JASS TriggerEvaluate thread-isolation pattern.
* Populated during translateProg() when isLuaTarget() is true.
*/
public final Map<ImFunction, String> luaInitFunctions = new Object2ObjectLinkedOpenHashMap<>();

private final Map<TranslatedToImFunction, ImVar> thisVarMap = new Object2ObjectLinkedOpenHashMap<>();

Expand Down Expand Up @@ -528,16 +535,23 @@ private void finishInitFunctions() {
}
Set<WPackage> calledInitializers = Sets.newLinkedHashSet();

ImVar initTrigVar = prepareTrigger();
if (isLuaTarget()) {
// In Lua mode, xpcall handles error isolation; no trigger handle is needed.
for (WPackage p : Utils.sortByName(initFuncMap.keySet())) {
callInitFunc(calledInitializers, p, null);
}
} else {
ImVar initTrigVar = prepareTrigger();

for (WPackage p : Utils.sortByName(initFuncMap.keySet())) {
callInitFunc(calledInitializers, p, initTrigVar);
}
for (WPackage p : Utils.sortByName(initFuncMap.keySet())) {
callInitFunc(calledInitializers, p, initTrigVar);
}

ImFunction native_DestroyTrigger = getNativeFunc("DestroyTrigger");
if (native_DestroyTrigger != null) {
getMainFunc().getBody().add(JassIm.ImFunctionCall(emptyTrace, native_DestroyTrigger, ImTypeArguments(),
JassIm.ImExprs(JassIm.ImVarAccess(initTrigVar)), false, CallType.NORMAL));
ImFunction native_DestroyTrigger = getNativeFunc("DestroyTrigger");
if (native_DestroyTrigger != null) {
getMainFunc().getBody().add(JassIm.ImFunctionCall(emptyTrace, native_DestroyTrigger, ImTypeArguments(),
JassIm.ImExprs(JassIm.ImVarAccess(initTrigVar)), false, CallType.NORMAL));
}
}
}

Expand All @@ -563,7 +577,7 @@ private ImFunction getNativeFunc(String funcName) {
return getFuncFor(Utils.getFirst(wurstFunc).getDef());
}

private void callInitFunc(Set<WPackage> calledInitializers, WPackage p, ImVar initTrigVar) {
private void callInitFunc(Set<WPackage> calledInitializers, WPackage p, @Nullable ImVar initTrigVar) {
Preconditions.checkNotNull(p);
if (calledInitializers.contains(p)) {
return;
Expand All @@ -580,6 +594,14 @@ private void callInitFunc(Set<WPackage> calledInitializers, WPackage p, ImVar in
if (initFunc.getBody().size() == 0) {
return;
}
if (isLuaTarget()) {
// In Lua mode, xpcall replaces TriggerEvaluate for error isolation without WC3 handle overhead.
// Record the init function so the Lua translator can wrap it with xpcall.
luaInitFunctions.put(initFunc, p.getName());
getMainFunc().getBody().add(ImFunctionCall(initFunc.getTrace(), initFunc, ImTypeArguments(), ImExprs(), false, CallType.NORMAL));
return;
}

boolean successful = createInitFuncCall(p, initTrigVar, initFunc);

if (!successful) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class ExprTranslation {

public static final String TYPE_ID = "__typeId__";
public static final String WURST_SUPERTYPES = "__wurst_supertypes";
private static final String WURST_ABORT_THREAD_SENTINEL = "__wurst_abort_thread";
static final String WURST_ABORT_THREAD_SENTINEL = "__wurst_abort_thread";
private static final Set<String> LUA_HANDLE_TO_INDEX = Set.of(
"widgetToIndex", "unitToIndex", "destructableToIndex", "itemToIndex", "abilityToIndex",
"forceToIndex", "groupToIndex", "triggerToIndex", "triggeractionToIndex", "triggerconditionToIndex",
Expand Down Expand Up @@ -89,7 +89,7 @@ public static LuaExpr translate(ImFuncRef e, LuaTranslator tr) {
return LuaAst.LuaExprFunctionAbstraction(LuaAst.LuaParams(dots), callbackBody);
}

private static String callErrorFunc(LuaTranslator tr, String msg) {
static String callErrorFunc(LuaTranslator tr, String msg) {
LuaFunction ef = tr.getErrorFunc();
if (ef != null) {
if (ef.getParams().size() == 2) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ public LuaMethod initFor(ImClass a) {
LuaFunction ensureRealFunction = LuaAst.LuaFunction(uniqueName("realEnsure"), LuaAst.LuaParams(), LuaAst.LuaStatements());

private final Lazy<LuaFunction> errorFunc = Lazy.create(() ->
Objects.requireNonNull(this.getProg().getFunctions().stream()
this.getProg().getFunctions().stream()
.flatMap(f -> {
Element trace = f.attrTrace();
if (trace instanceof FuncDef) {
Expand All @@ -233,8 +233,9 @@ public LuaMethod initFor(ImClass a) {
}
return Stream.empty();
})
.findFirst().orElse(null)));
private final ImTranslator imTr;
.findFirst().orElse(null));
final ImTranslator imTr;


public LuaTranslator(ImProg prog, ImTranslator imTr) {
this.prog = prog;
Expand Down Expand Up @@ -322,7 +323,6 @@ public LuaCompilationUnit translate() {
initClassTables(c);
}

emitExperimentalHashtableLeakGuards();
prependDeferredMainInitToMain();
cleanStatements();
enforceLuaLocalLimits();
Expand Down Expand Up @@ -354,22 +354,6 @@ private void ensureWurstContextCallbackHelpers() {
}
}

private void emitExperimentalHashtableLeakGuards() {
deferMainInit(LuaAst.LuaLiteral("-- Wurst experimental Lua assertion guards: raw WC3 hashtable natives must not be called."));
deferMainInit(LuaAst.LuaLiteral("do"));
deferMainInit(LuaAst.LuaLiteral(" local __wurst_guard_ok = pcall(function()"));
for (String nativeName : allHashtableNativeNames()) {
deferMainInit(LuaAst.LuaLiteral(" if " + nativeName + " ~= nil then " + nativeName
+ " = function(...) error(\"Wurst Lua assertion failed: unexpected call to native " + nativeName
+ ". Expected __wurst_" + nativeName + ".\") end end"));
}
deferMainInit(LuaAst.LuaLiteral(" end)"));
deferMainInit(LuaAst.LuaLiteral(" if not __wurst_guard_ok then"));
deferMainInit(LuaAst.LuaLiteral(" -- Some Lua runtimes lock native globals. Compile-time leak checks stay authoritative."));
deferMainInit(LuaAst.LuaLiteral(" end"));
deferMainInit(LuaAst.LuaLiteral("end"));
}

private void deferMainInit(LuaStatement statement) {
deferredMainInit.add(statement);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,40 @@

import java.util.List;

import static de.peeeq.wurstscript.translation.lua.translation.ExprTranslation.WURST_ABORT_THREAD_SENTINEL;
import de.peeeq.wurstscript.jassIm.ImFunction;

public class StmtTranslation {

public static void translate(ImExpr e, List<LuaStatement> res, LuaTranslator tr) {
// In Lua mode, package init functions are called directly and wrapped with xpcall.
if (e instanceof ImFunctionCall) {
ImFunctionCall call = (ImFunctionCall) e;
if (tr.imTr.luaInitFunctions.containsKey(call.getFunc())) {
emitLuaInitXpcall(call.getFunc(), res, tr);
return;
}
}
LuaExpr expr = e.translateToLua(tr);
res.add(expr);
}

private static void emitLuaInitXpcall(ImFunction initFunc, List<LuaStatement> res, LuaTranslator tr) {
String funcName = tr.luaFunc.getFor(initFunc).getName();
String packageName = tr.imTr.luaInitFunctions.getOrDefault(initFunc, "?");
String errHandler = "function(err) if err == \"" + WURST_ABORT_THREAD_SENTINEL + "\" then return end"
+ " BJDebugMsg(\"lua init error: \" .. tostring(err))"
+ " xpcall(function() " + ExprTranslation.callErrorFunc(tr, "tostring(err)") + " end,"
+ " function(err2) if err2 == \"" + WURST_ABORT_THREAD_SENTINEL + "\" then return end"
+ " BJDebugMsg(\"error reporting error: \" .. tostring(err2)) end) end";
res.add(LuaAst.LuaLiteral("do"));
res.add(LuaAst.LuaLiteral(" local __wurst_init_ok = xpcall(" + funcName + ", " + errHandler + ")"));
res.add(LuaAst.LuaLiteral(" if not __wurst_init_ok then"));
res.add(LuaAst.LuaLiteral(" " + ExprTranslation.callErrorFunc(tr, "\"Could not initialize package " + packageName + ".\"") + ""));
res.add(LuaAst.LuaLiteral(" end"));
res.add(LuaAst.LuaLiteral("end"));
}

public static void translate(ImExitwhen s, List<LuaStatement> res, LuaTranslator tr) {
LuaIf r = LuaAst.LuaIf(s.getCondition().translateToLua(tr),
LuaAst.LuaStatements(LuaAst.LuaBreak()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -477,10 +477,8 @@ public void luaHeavyBootstrapStateIsSeededFromMain() throws IOException {
String beforeMain = compiled.substring(0, mainPos);
assertFalse(beforeMain.contains("__wurst_objectIndexMap = ({"));
assertFalse(beforeMain.contains("__wurst_string_index_map = ({"));
assertFalse(beforeMain.contains("Wurst experimental Lua assertion guards"));
assertTrue(mainSection.contains("__wurst_objectIndexMap = ({"));
assertTrue(mainSection.contains("__wurst_string_index_map = ({"));
assertTrue(mainSection.contains("Wurst experimental Lua assertion guards"));
}

@Test
Expand Down Expand Up @@ -531,7 +529,6 @@ public void luaDeferredBootstrapRunsBeforeInitGlobalsInMain() throws IOException
assertOccursBefore(mainSection, "C.__wurst_supertypes =", "initGlobals()");
assertOccursBefore(mainSection, "C.__typeId__ =", "initGlobals()");
assertOccursBefore(mainSection, "C.C_f =", "initGlobals()");
assertOccursBefore(mainSection, "Wurst experimental Lua assertion guards", "initGlobals()");
}

@Test
Expand Down Expand Up @@ -1198,15 +1195,19 @@ public void reflectionNativeStubsAreGuardedByExistingDefinitions() throws IOExce
}

@Test
public void stdLibInitUsesTriggerEvaluateGuardInMain() throws IOException {
public void stdLibInitUsesXpcallInsteadOfTriggerEvaluateInMain() throws IOException {
test().testLua(true).withStdLib().lines(
"package Test",
"init",
" skip"
);
String compiled = Files.toString(new File("test-output/lua/LuaTranslationTests_stdLibInitUsesTriggerEvaluateGuardInMain.lua"), Charsets.UTF_8);
assertTrue(compiled.contains("if not(TriggerEvaluate("));
assertTrue(compiled.contains("TriggerClearConditions"));
String compiled = Files.toString(new File("test-output/lua/LuaTranslationTests_stdLibInitUsesXpcallInsteadOfTriggerEvaluateInMain.lua"), Charsets.UTF_8);
// Package inits use direct xpcall — no WC3 trigger handle overhead
assertTrue(compiled.contains("xpcall(init_"));
assertTrue(compiled.contains("__wurst_init_ok"));
// TriggerEvaluate pattern must NOT appear for init functions
assertFalse(compiled.contains("if not(TriggerEvaluate("));
assertFalse(compiled.contains("TriggerClearConditions"));
}

@Test
Expand Down Expand Up @@ -1261,22 +1262,23 @@ public void hashtableHandleExtensionsUseWurstLuaHelpers() throws IOException {
String compiled = Files.toString(new File("test-output/lua/LuaTranslationTests_hashtableHandleExtensionsUseWurstLuaHelpers.lua"), Charsets.UTF_8);
assertDoesNotContainRegex(compiled, "\\bHaveSavedHandle\\(");
assertContainsRegex(compiled, "\\b__wurst_HaveSavedHandle\\(");
assertTrue(compiled.contains("Wurst experimental Lua assertion guards"));
}

@Test
public void hashtableNativeOverrideGuardsAreRuntimeSafe() throws IOException {
public void hashtableNativesAreReplacedByWurstHelpers() throws IOException {
test().testLua(true).withStdLib().lines(
"package Test",
"init",
" let h = InitHashtable()",
" h.saveInt(1, 2, 7)"
);
String compiled = Files.toString(new File("test-output/lua/LuaTranslationTests_hashtableNativeOverrideGuardsAreRuntimeSafe.lua"), Charsets.UTF_8);
assertTrue(compiled.contains("Wurst experimental Lua assertion guards"));
assertContainsRegex(compiled, "\\blocal\\s+__wurst_guard_ok\\s*=\\s*pcall\\s*\\(\\s*function\\s*\\(");
assertContainsRegex(compiled, "\\bInitHashtable\\s*=\\s*function\\s*\\(");
assertContainsRegex(compiled, "\\bSaveInteger\\s*=\\s*function\\s*\\(");
String compiled = Files.toString(new File("test-output/lua/LuaTranslationTests_hashtableNativesAreReplacedByWurstHelpers.lua"), Charsets.UTF_8);
// Runtime guard overrides are removed; compile-time checks are authoritative.
assertFalse(compiled.contains("Wurst experimental Lua assertion guards"));
assertFalse(compiled.contains("__wurst_guard_ok"));
// __wurst_ helper definitions must still be present.
assertContainsRegex(compiled, "\\bfunction\\s+__wurst_InitHashtable\\s*\\(");
assertContainsRegex(compiled, "\\bfunction\\s+__wurst_SaveInteger\\s*\\(");
}

@Test
Expand Down
Loading