Skip to content

Commit 31e38d8

Browse files
committed
Use our own stack counter to determine when a StackOverflow happens.
1 parent 091d8b4 commit 31e38d8

3 files changed

Lines changed: 69 additions & 4 deletions

File tree

src/main/java/com/laytonsmith/core/environments/GlobalEnv.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ public List<Iterator> GetArrayAccessIteratorsFor(ArrayAccess array) {
400400
public StackTraceManager GetStackTraceManager() {
401401
Thread currentThread = Thread.currentThread();
402402
if(this.stackTraceManager == null || currentThread != this.stackTraceManagerThread) {
403-
this.stackTraceManager = new StackTraceManager();
403+
this.stackTraceManager = new StackTraceManager(this);
404404
this.stackTraceManagerThread = currentThread;
405405
}
406406
return this.stackTraceManager;

src/main/java/com/laytonsmith/core/exceptions/StackTraceManager.java

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package com.laytonsmith.core.exceptions;
22

3+
import com.laytonsmith.core.ArgumentValidation;
4+
import com.laytonsmith.core.constructs.CInt;
35
import com.laytonsmith.core.constructs.Target;
6+
import com.laytonsmith.core.environments.GlobalEnv;
7+
import com.laytonsmith.core.exceptions.CRE.CREStackOverflowError;
8+
import com.laytonsmith.core.natives.interfaces.Mixed;
49
import java.util.ArrayList;
510
import java.util.Collections;
611
import java.util.List;
@@ -11,22 +16,45 @@
1116
*/
1217
public class StackTraceManager {
1318

19+
/**
20+
* The runtime setting key for configuring the maximum call depth.
21+
*/
22+
public static final String MAX_CALL_DEPTH_SETTING = "system.max_call_depth";
23+
24+
/**
25+
* The default maximum call depth. Can be overridden at runtime via the
26+
* {@code system.max_call_depth} runtime setting.
27+
*/
28+
public static final int DEFAULT_MAX_CALL_DEPTH = 1024;
29+
30+
private static final CInt DEFAULT_MAX_DEPTH_MIXED
31+
= new CInt(DEFAULT_MAX_CALL_DEPTH, Target.UNKNOWN);
32+
1433
private final Stack<ConfigRuntimeException.StackTraceElement> elements = new Stack<>();
34+
private final GlobalEnv gEnv;
1535

1636
/**
1737
* Creates a new, empty StackTraceManager object.
38+
*
39+
* @param gEnv The global environment, used to read runtime settings for the call depth limit.
1840
*/
19-
public StackTraceManager() {
20-
//
41+
public StackTraceManager(GlobalEnv gEnv) {
42+
this.gEnv = gEnv;
2143
}
2244

2345
/**
24-
* Adds a new stack trace trail
46+
* Adds a new stack trace element and checks the call depth against the configured maximum.
47+
* If the depth exceeds the limit, a {@link CREStackOverflowError} is thrown.
2548
*
2649
* @param element The element to be pushed on
2750
*/
2851
public void addStackTraceElement(ConfigRuntimeException.StackTraceElement element) {
2952
elements.add(element);
53+
Mixed setting = gEnv.GetRuntimeSetting(MAX_CALL_DEPTH_SETTING, DEFAULT_MAX_DEPTH_MIXED);
54+
int maxDepth = ArgumentValidation.getInt32(setting, element.getDefinedAt(), null);
55+
if(elements.size() > maxDepth) {
56+
throw new CREStackOverflowError("Stack overflow", element.getDefinedAt());
57+
}
3058
}
3159

3260
/**
@@ -65,6 +93,15 @@ public boolean isStackSingle() {
6593
return elements.size() == 1;
6694
}
6795

96+
/**
97+
* Returns the current depth of the stack trace (the number of proc/closure frames currently active).
98+
*
99+
* @return The current stack depth
100+
*/
101+
public int getDepth() {
102+
return elements.size();
103+
}
104+
68105
/**
69106
* Sets the current element's target. This should be changed at every new element execution.
70107
*

src/test/java/com/laytonsmith/testing/ProcedureTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import com.laytonsmith.abstraction.MCPlayer;
44
import static com.laytonsmith.testing.StaticTest.SRun;
55

6+
import com.laytonsmith.core.environments.GlobalEnv;
67
import com.laytonsmith.core.exceptions.CRE.CRECastException;
8+
import com.laytonsmith.core.exceptions.CRE.CREStackOverflowError;
9+
import com.laytonsmith.core.exceptions.StackTraceManager;
710
import org.bukkit.plugin.Plugin;
811
import org.junit.Before;
912
import org.junit.BeforeClass;
@@ -133,4 +136,29 @@ public void testProcCalledMultipleTimesWithAssign() throws Exception {
133136
verify(fakePlayer, times(3)).sendMessage("{1, 3, 5, 7}");
134137
}
135138

139+
@Test
140+
public void testInfiniteRecursionThrowsStackOverflow() throws Exception {
141+
try {
142+
SRun("proc _recurse() { _recurse() } _recurse()", fakePlayer);
143+
fail("Expected CREStackOverflowError from infinite recursion");
144+
} catch(CREStackOverflowError ex) {
145+
// Test passed — infinite recursion was caught
146+
}
147+
}
148+
149+
@Test
150+
public void testCustomCallDepthLimit() throws Exception {
151+
try {
152+
SRun("set_runtime_setting('system.max_call_depth', 10)"
153+
+ " proc _recurse() { _recurse() } _recurse()", fakePlayer);
154+
fail("Expected CREStackOverflowError from infinite recursion");
155+
} catch(CREStackOverflowError ex) {
156+
// Test passed — custom limit was enforced
157+
} finally {
158+
// Reset the runtime setting so it doesn't affect other tests
159+
GlobalEnv gEnv = StaticTest.env.getEnv(GlobalEnv.class);
160+
gEnv.SetRuntimeSetting(StackTraceManager.MAX_CALL_DEPTH_SETTING, null);
161+
}
162+
}
163+
136164
}

0 commit comments

Comments
 (0)