Skip to content

Commit aa986c6

Browse files
authored
Make local-player analysis stack safe (#1268)
1 parent b923724 commit aa986c6

6 files changed

Lines changed: 221 additions & 78 deletions

File tree

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompiletimeFunctionRunner.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,7 @@ public void run() {
157157
de.peeeq.wurstscript.jassIm.Element s = interpreter.getLastStatement();
158158
Element origin = s == null ? null : s.attrTrace();
159159
if (origin != null) {
160-
String msg = e.getMessage();
161-
sendErrors(origin, msg, e);
160+
sendErrors(origin, describeFailure(e), e);
162161
} else {
163162
throw new Error("could not get origin", e);
164163
}
@@ -234,14 +233,21 @@ private boolean isUnitTestMode() {
234233
}
235234

236235
private void sendErrors(Element origin, String msg, Throwable ex) {
237-
gui.sendError(new CompileError(origin.attrSource(), msg, CompileError.ErrorType.ERROR, ex));
236+
gui.sendError(new CompileError(origin.attrSource(),
237+
msg == null || msg.isBlank() ? describeFailure(ex) : msg,
238+
CompileError.ErrorType.ERROR, ex));
238239

239240
// stackframe messages ...
240241
for (ILStackFrame sf : Utils.iterateReverse(interpreter.getStackFrames().getStackFrames())) {
241242
gui.sendError(sf.makeCompileError());
242243
}
243244
}
244245

246+
static String describeFailure(Throwable failure) {
247+
String message = failure.getMessage();
248+
return message == null || message.isBlank() ? failure.getClass().getSimpleName() : message;
249+
}
250+
245251
/**
246252
* Run actions that must be run after all other code
247253
*/

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/gui/WurstErrorWindow.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,15 @@ private void viewErrorDetail(CompileError err) {
240240
setVisible(true);
241241
this.errorDetailsPanel.setText(err.getMessage());
242242

243-
File errFile = new File(err.getSource().getFile());
244-
File workspaceErrFile = new File(workspaceRoot + "/" + err.getSource().getFile());
243+
String sourceFile = err.getSource().getFile();
244+
if (isSyntheticSource(sourceFile)) {
245+
currentFile = null;
246+
codeArea.setText("No source file is available for this compiler-generated error.");
247+
return;
248+
}
249+
250+
File errFile = new File(sourceFile);
251+
File workspaceErrFile = new File(workspaceRoot + "/" + sourceFile);
245252

246253
if (!errFile.exists() && workspaceErrFile.exists()) {
247254
errFile = workspaceErrFile;
@@ -314,6 +321,11 @@ private void viewErrorDetail(CompileError err) {
314321
}
315322
}
316323

324+
static boolean isSyntheticSource(String sourceFile) {
325+
return sourceFile == null || sourceFile.isBlank()
326+
|| sourceFile.startsWith("<") && sourceFile.endsWith(">");
327+
}
328+
317329
// @Override
318330
public void sendError(CompileError elem) {
319331
if (errorListModel.isEmpty()) {

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/optimizer/LocalPlayerContextAnalyzer.java

Lines changed: 133 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -189,55 +189,69 @@ private boolean methodReturnsLocalPlayerDependentValue(ImMethod method) {
189189
return false;
190190
}
191191

192-
private void indexElement(Element element, ImFunction owner, Object controlContext) {
193-
indexedElements.add(element);
192+
private record IndexTask(Element element, Object controlContext, boolean afterChildren) {
193+
}
194194

195-
Object branchControl = null;
196-
if (element instanceof ImIf) {
197-
ImIf ifStmt = (ImIf) element;
198-
branchControl = new Fact(FactKind.CONTROL, ifStmt);
199-
addDependency(ifStmt.getCondition(), branchControl);
200-
addEnclosingControlDependency(controlContext, branchControl);
201-
}
195+
private record ReturnTask(Element element, boolean afterChildren) {
196+
}
202197

203-
Object loopControl = null;
204-
if (element instanceof ImLoop) {
205-
ImLoop loop = (ImLoop) element;
206-
loopControl = new Fact(FactKind.CONTROL, loop);
207-
addEnclosingControlDependency(controlContext, loopControl);
208-
addLoopExitDependencies(loop.getBody(), loopControl);
209-
}
198+
private record LoopExitTask(Element element, boolean afterChildren) {
199+
}
210200

211-
if (element instanceof ImStmts) {
212-
indexStatementSequence((ImStmts) element, owner, controlContext);
213-
return;
214-
}
201+
private void indexElement(Element root, ImFunction owner, Object controlContext) {
202+
Deque<IndexTask> work = new ArrayDeque<>();
203+
work.addFirst(new IndexTask(root, controlContext, false));
204+
while (!work.isEmpty()) {
205+
IndexTask task = work.removeFirst();
206+
Element element = task.element();
207+
if (task.afterChildren()) {
208+
indexElementAfterChildren(element, owner, task.controlContext());
209+
continue;
210+
}
215211

216-
for (int i = 0; i < element.size(); i++) {
217-
Element child = element.get(i);
218-
if (element instanceof ImOperatorCall
219-
&& ((ImOperatorCall) element).getOp().isLazy()
220-
&& child == ((ImOperatorCall) element).getArguments()) {
221-
indexShortCircuitArguments(
222-
((ImOperatorCall) element).getArguments(),
223-
owner,
224-
controlContext);
225-
addDependency(child, element);
212+
indexedElements.add(element);
213+
Object branchControl = null;
214+
if (element instanceof ImIf ifStmt) {
215+
branchControl = new Fact(FactKind.CONTROL, ifStmt);
216+
addDependency(ifStmt.getCondition(), branchControl);
217+
addEnclosingControlDependency(task.controlContext(), branchControl);
218+
}
219+
220+
Object loopControl = null;
221+
if (element instanceof ImLoop loop) {
222+
loopControl = new Fact(FactKind.CONTROL, loop);
223+
addEnclosingControlDependency(task.controlContext(), loopControl);
224+
addLoopExitDependencies(loop.getBody(), loopControl);
225+
}
226+
227+
if (element instanceof ImStmts statements) {
228+
scheduleStatementSequence(statements, task.controlContext(), work);
226229
continue;
227230
}
228-
Object childControl = controlContext;
229-
if (element instanceof ImIf
230-
&& (child == ((ImIf) element).getThenBlock()
231-
|| child == ((ImIf) element).getElseBlock())) {
232-
childControl = branchControl;
233-
} else if (element instanceof ImLoop
234-
&& child == ((ImLoop) element).getBody()) {
235-
childControl = loopControl;
231+
232+
work.addFirst(new IndexTask(element, task.controlContext(), true));
233+
for (int i = element.size() - 1; i >= 0; i--) {
234+
Element child = element.get(i);
235+
addDependency(child, element);
236+
if (element instanceof ImOperatorCall operator
237+
&& operator.getOp().isLazy()
238+
&& child == operator.getArguments()) {
239+
scheduleShortCircuitArguments(operator.getArguments(), task.controlContext(), work);
240+
continue;
241+
}
242+
Object childControl = task.controlContext();
243+
if (element instanceof ImIf ifStmt
244+
&& (child == ifStmt.getThenBlock() || child == ifStmt.getElseBlock())) {
245+
childControl = branchControl;
246+
} else if (element instanceof ImLoop loop && child == loop.getBody()) {
247+
childControl = loopControl;
248+
}
249+
work.addFirst(new IndexTask(child, childControl, false));
236250
}
237-
indexElement(child, owner, childControl);
238-
addDependency(child, element);
239251
}
252+
}
240253

254+
private void indexElementAfterChildren(Element element, ImFunction owner, Object controlContext) {
241255
if (element instanceof ImVarAccess) {
242256
addDependency(variableFact(((ImVarAccess) element).getVar()), element);
243257
} else if (element instanceof ImVarArrayAccess) {
@@ -273,13 +287,14 @@ private void indexElement(Element element, ImFunction owner, Object controlConte
273287
}
274288
}
275289

276-
private void indexStatementSequence(ImStmts statements,
277-
ImFunction owner,
278-
Object controlContext) {
290+
private void scheduleStatementSequence(ImStmts statements,
291+
Object controlContext,
292+
Deque<IndexTask> work) {
293+
List<IndexTask> tasks = new ArrayList<>(statements.size());
279294
Object continuationControl = controlContext;
280295
for (ImStmt statement : statements) {
281-
indexElement(statement, owner, continuationControl);
282296
addDependency(statement, statements);
297+
tasks.add(new IndexTask(statement, continuationControl, false));
283298

284299
if (containsFunctionReturn(statement)) {
285300
Fact followingStatementControl =
@@ -291,36 +306,60 @@ private void indexStatementSequence(ImStmts statements,
291306
continuationControl = followingStatementControl;
292307
}
293308
}
309+
for (int i = tasks.size() - 1; i >= 0; i--) {
310+
work.addFirst(tasks.get(i));
311+
}
294312
}
295313

296-
private boolean containsFunctionReturn(Element element) {
297-
Boolean cached = containsReturnCache.get(element);
314+
private boolean containsFunctionReturn(Element root) {
315+
Boolean cached = containsReturnCache.get(root);
298316
if (cached != null) {
299317
return cached;
300318
}
301-
if (element instanceof ImReturn) {
302-
containsReturnCache.put(element, true);
303-
return true;
304-
}
305-
for (int i = 0; i < element.size(); i++) {
306-
if (containsFunctionReturn(element.get(i))) {
319+
Deque<ReturnTask> work = new ArrayDeque<>();
320+
work.addFirst(new ReturnTask(root, false));
321+
while (!work.isEmpty()) {
322+
ReturnTask task = work.removeFirst();
323+
Element element = task.element();
324+
if (containsReturnCache.containsKey(element)) {
325+
continue;
326+
}
327+
if (element instanceof ImReturn) {
307328
containsReturnCache.put(element, true);
308-
return true;
329+
continue;
330+
}
331+
if (!task.afterChildren()) {
332+
work.addFirst(new ReturnTask(element, true));
333+
for (int i = element.size() - 1; i >= 0; i--) {
334+
Element child = element.get(i);
335+
if (!containsReturnCache.containsKey(child)) {
336+
work.addFirst(new ReturnTask(child, false));
337+
}
338+
}
339+
continue;
309340
}
341+
boolean containsReturn = false;
342+
for (int i = 0; i < element.size(); i++) {
343+
if (Boolean.TRUE.equals(containsReturnCache.get(element.get(i)))) {
344+
containsReturn = true;
345+
break;
346+
}
347+
}
348+
containsReturnCache.put(element, containsReturn);
310349
}
311-
containsReturnCache.put(element, false);
312-
return false;
350+
return Boolean.TRUE.equals(containsReturnCache.get(root));
313351
}
314352

315-
private void indexShortCircuitArguments(ImExprs arguments,
316-
ImFunction owner,
317-
Object controlContext) {
353+
private void scheduleShortCircuitArguments(ImExprs arguments,
354+
Object controlContext,
355+
Deque<IndexTask> work) {
318356
indexedElements.add(arguments);
357+
List<IndexTask> tasks = new ArrayList<>(arguments.size());
319358
Object operandControl = controlContext;
320359
for (int i = 0; i < arguments.size(); i++) {
321360
ImExpr argument = arguments.get(i);
322-
indexElement(argument, owner, operandControl);
323361
addDependency(argument, arguments);
362+
tasks.add(new IndexTask(argument, operandControl, false));
324363

325364
if (i + 1 < arguments.size()) {
326365
Fact followingOperandControl =
@@ -332,6 +371,9 @@ private void indexShortCircuitArguments(ImExprs arguments,
332371
operandControl = followingOperandControl;
333372
}
334373
}
374+
for (int i = tasks.size() - 1; i >= 0; i--) {
375+
work.addFirst(tasks.get(i));
376+
}
335377
}
336378

337379
private void indexFunctionCall(ImFunctionCall call, ImFunction owner, Object controlContext) {
@@ -402,22 +444,40 @@ private void addEnclosingControlDependency(Object controlContext, Object depende
402444
}
403445
}
404446

405-
private boolean addLoopExitDependencies(Element element, Object loopControl) {
406-
if (element instanceof ImExitwhen) {
407-
addDependency(((ImExitwhen) element).getCondition(), loopControl);
408-
return true;
409-
} else if (element instanceof ImLoop || element instanceof ImVarargLoop) {
410-
return false;
411-
}
447+
private boolean addLoopExitDependencies(Element root, Object loopControl) {
448+
Map<Element, Boolean> containsExit = new IdentityHashMap<>();
449+
Deque<LoopExitTask> work = new ArrayDeque<>();
450+
work.addFirst(new LoopExitTask(root, false));
451+
while (!work.isEmpty()) {
452+
LoopExitTask task = work.removeFirst();
453+
Element element = task.element();
454+
if (element instanceof ImExitwhen exitwhen) {
455+
addDependency(exitwhen.getCondition(), loopControl);
456+
containsExit.put(element, true);
457+
continue;
458+
}
459+
if (element instanceof ImLoop || element instanceof ImVarargLoop) {
460+
containsExit.put(element, false);
461+
continue;
462+
}
463+
if (!task.afterChildren()) {
464+
work.addFirst(new LoopExitTask(element, true));
465+
for (int i = element.size() - 1; i >= 0; i--) {
466+
work.addFirst(new LoopExitTask(element.get(i), false));
467+
}
468+
continue;
469+
}
412470

413-
boolean containsExit = false;
414-
for (int i = 0; i < element.size(); i++) {
415-
containsExit |= addLoopExitDependencies(element.get(i), loopControl);
416-
}
417-
if (containsExit && element instanceof ImIf) {
418-
addDependency(((ImIf) element).getCondition(), loopControl);
471+
boolean elementContainsExit = false;
472+
for (int i = 0; i < element.size(); i++) {
473+
elementContainsExit |= Boolean.TRUE.equals(containsExit.get(element.get(i)));
474+
}
475+
if (elementContainsExit && element instanceof ImIf ifStmt) {
476+
addDependency(ifStmt.getCondition(), loopControl);
477+
}
478+
containsExit.put(element, elementContainsExit);
419479
}
420-
return containsExit;
480+
return Boolean.TRUE.equals(containsExit.get(root));
421481
}
422482

423483
private boolean collectMethodImplementations(ImMethod method,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package de.peeeq.wurstio;
2+
3+
import org.testng.annotations.Test;
4+
5+
import static org.testng.Assert.assertEquals;
6+
7+
public class CompiletimeFunctionRunnerTests {
8+
9+
@Test
10+
public void throwableWithoutMessageUsesItsType() {
11+
assertEquals(CompiletimeFunctionRunner.describeFailure(new StackOverflowError()), "StackOverflowError");
12+
}
13+
14+
@Test
15+
public void throwableMessageIsPreserved() {
16+
assertEquals(CompiletimeFunctionRunner.describeFailure(new RuntimeException("details")), "details");
17+
}
18+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package de.peeeq.wurstio.gui;
2+
3+
import org.testng.annotations.Test;
4+
5+
import static org.testng.Assert.assertFalse;
6+
import static org.testng.Assert.assertTrue;
7+
8+
public class WurstErrorWindowTests {
9+
10+
@Test
11+
public void syntheticSourceIsNotTreatedAsAFile() {
12+
assertTrue(WurstErrorWindow.isSyntheticSource("<source of NoExpr not found>"));
13+
assertTrue(WurstErrorWindow.isSyntheticSource(null));
14+
assertFalse(WurstErrorWindow.isSyntheticSource("wurst/Package.wurst"));
15+
}
16+
}

0 commit comments

Comments
 (0)