-
Bug
-
Resolution: Fixed
-
Minecraft 1.12 Pre-Release 1, Minecraft 1.12 Pre-Release 2, Minecraft 1.12 Pre-Release 3
-
* OS: Windows 10
* Java: 1.8.0_112
-
Unconfirmed
Description
When executing the following command:
/function test:test
and the definition of test:test is as follows:
execute @s ~ ~ ~ function test:test
you get this error message in the chat and server log:
An unknown error occurred while attempting to perform this command
Note: this also gets around the maxCommandChainLength limit (untested, but assumed true based on the code analysis below).
Edit: as of 1.12-pre3, the execute command must be the last command in the function
Why this is a problem
Though the example above may be solvable by just replacing the execute command with a function command, it turns out that the execute command is the only way to create real conditionals, terminating loops and recursive functions. If I have a recursive function equivalent to a for loop from 1-1000, for example, it takes up a lot of space on the stack (see code analysis below). It may even be possible for more complex functions to run out of stack space, which kind of defeats the point of the maxCommandChainLength gamerule.
Code analysis
The bug is probably caused by a StackOverflowError. The current code used to execute functions is quite clever: it treats function commands inside functions differently to other commands such to avoid stack overflows from occurring. This is why we need it inside an execute command in the function definition, so the game doesn't recognize it as a function command.
What calls what:
- CommandFunction.execute(MinecraftServer, ICommandSender, String[]) (line 46)
- FunctionManager.runFunction(Function, ICommandSender) (line 114)
- FunctionManager$LineOfCode.execute(ArrayDeque<LineOfCode>, int) (line 176)
- Function$CommandInsn.execute(FunctionManager, ICommandSender, ArrayDeque<LineOfCode>, int) (line 60)
- AbstractCommandManager.execute(ICommandSender, String) (line 62)
- AbstractCommandManager.tryExecute(ICommandSender, String[], ICommand, String) (line 92)
- CommandExecute.execute(MinecraftServer, ICommandSender, String[]) (line 118)
- CommandFunction.execute(MinecraftServer, ICommandSender, String[]) (line 46)
- ...
The current implementation of FunctionManager.runFunction looks like this:
public int runFunction(Function func, ICommandSender sender) { int maxChainLength = getMaxCommandChainLength(); // Attempted fix for this bug (I think) // This should also be a >= 1 (if I'm not mistaken) if (this.linesLeftToExecute.size() > 1) { if (this.linesLeftToExecute.size() < maxChainLength) { this.linesLeftToExecute.addFirst(new LineOfCode(this, sender, new FunctionInsn(func))); } return 0; } try { int linesExecuted = 0; // ... read function to add all lines ... while (!this.linesLeftToExecute.isEmpty()) { // Since the line of code is removed *before* it is executed, // linesLeftToExecute could be empty before we reach the lines above again this.linesLeftToExecute.removeFirst().execute(this.linesLeftToExecute, maxChainLength); if (++linesExecuted >= maxChainLength) { return linesExecuted; } } return linesExecuted; } finally { this.linesLeftToExecute.clear(); } }
Alternative solution
(doesn't fix bug, but add a way round it)
Add proper support for conditionals in functions
- relates to
-
MC-117562 Executing a function as an entity doesn't use new sender
- Resolved