-
Notifications
You must be signed in to change notification settings - Fork 21
Description
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 4096 bytes) in /home/parser-generator/vendor/antlr/antlr4-php-runtime/src/Atn/ParserATNSimulator.php on line 2036
Hi, I am trying to use the PHP target, but it throws the above error, while parsing a file using a grammar. The same file and same grammar works fine on the Java target. If I remove the PHP process memory limit, it succeeds after a few seconds, but thats not right, as the file is very simple. Also, it is just a particular rule that creates this memory overuse, every other rule seems to be parsing fine.
I tried to make it so you can relatively easy reproduce the error.
The repo is https://github.com/npgeorgiou/say, and the PHP target experiment is in the parser-generator directory, which also has instructions on how to reproduce the error: https://github.com/npgeorgiou/say/tree/main/parser-generator
Let me know if I can do anything else to make it easier for you.
Activity
npgeorgiou-zz commentedon Apr 18, 2021
Ok, I have more info. This memory bug happens when a subrule starts with itself. For example, here I have a section of my grammar:
expression:
|SAY expression # Say
|EXCLAMATION expression # Prefix
|expression EXCLAMATION # Postfix
;
say !foo will be parsed without issues
say foo! will run out of memory or, if I give it unlimited memory, will be very very slow. Of course this cannot work for big files that contain a few of these expresions.
marcospassos commentedon Apr 18, 2021
Hi @npgeorgiou!
Thank you for reporting the issue. It seems like a recursion-related bug. I don't have time to track down the problem right now. If you have time to investigate this, PR’s are always welcome.
npgeorgiou-zz commentedon Apr 18, 2021
I would love to, but I am not smart enough for that. What I can offer to any brave adventurer is these:
A grammar:
A file:
Traveller, observe how the postfix **foo!**s are creating this memory error. The more of them, the slower it goes until it runs out of memory.
Also, observe how changing the postixes to prefixes (!foos) eliminates the bug.
Then, oberve how taking the **foo!**s out of the is_leap function body eliminates the bug as well.
Finally, oberve how, weirdly enough changing the
improves the situation, although with enough **foo!**s it appears again.
May the gods of the crossroads and the in-betweens be with you.
kaby76 commentedon Dec 17, 2022
It seems I ran into a similar bug with the aql grammar with input for.aql. I'm trying to track it down, but also ran into #33, which makes the tracing impossible to use.
kaby76 commentedon Dec 17, 2022
In the driver, I turned on the new "tracing" features in v4.11.2 (current dev tip). I also edited the source to output a newline for the standard trace parser visitor.
PHP:
CSharp:
With tracing set, from the command line, the parse completes for PHP! Without trace set, the first call to AdaptivePredict() does not complete, and leads to out of memory exception. Note, regardless of trace set or not, in the XDebug/PHPStorm debugger, the run does not terminate! From the command line, with trace, there are ATN set differences between CSharp, which works fine, and PHP, which terminates because it's at the command line.
As for the diffs, it happens on the first "addDFAState".
PHP:
CSharp:
I don't know enough about the output to understand this, but it seems one is an aggregate because it contains two '[' in CSharp, but only one '[' for PHP. This sounds like the runtime is written with a misinterpretation of the data structure.
@parrt Is there a detailed description of this output?
I plan to add to the output the name of the type it is printing out (ATN, ATNSet, etc.). Again, my guess is that there is a data structure that isn't conforming to the expected type.
All of this point to some serious problems with PHP:
kaby76 commentedon Dec 17, 2022
Since I can't tell what a
[
opens for a type (ATNConfig, ArrayPredicateConfig, ....), I decided to add identifiers to the "ToString()" output methods to tell me the object type it is trying to print out. I now am starting to see WTH is going on.PHP:
CSharp:
Notice the missing "[apc" tag. Whatever object PHP is printing, it is NOT an ArrayPredictionContext in PHP (but it is in CSharp) because I specifically modified the toString() method with tags. I will now debug toString() and see what the heck the object is.
@parrt Please, please, please add some kind of tagging system to note what type of object is being printed in the ATN trace output. I cannot tell what '[' opens.
parrt commentedon Dec 17, 2022
hi @kaby76 thanks for the heads up. As usual an excellent analysis. The issue is that there are lots of different types that represent the same abstract concept of context. Not a bad idea, but the real issue here is that we don't have find enough granularity on the simulation trace. Is all of the output perfect up until that add the first DFA state? If so, then we need to add more output to the targets so that they identify why it is not generating the right stuff. Given the grammar, I can see that it is the left recursive stuff that's the problem. That will involve the precedence semantic predicates.
My head is stuck in something else at the moment so I don't have time to dig into this but maybe this gives you a bit of a clue? There's definitely a flaw in the ATN sim here. You might try reducing the offending rule to have one left recursive call and one non-recursive call. Also try using the recursive rule as the start symbol and then have a symbol above it. That could give a clue or at least a smaller test set.
kaby76 commentedon Dec 17, 2022
The first DFAState added between C# and PHP may be different.
C# in VC2022, for "D" at this line, first time hit, for dev branch, for.aql file input. The "configSet.configs" field doesn't even contain the same number of items. In C#, it's 13 elements.
PHP in PHPStorm, at this line, the configSet.configs field has 14 items. This is bad.
kaby76 commentedon Dec 17, 2022
CSharp and PHP code look completely different--missing "else" in PHP code. But, this is where the ArrayPredictionContext is create in CSharp, but it's never called in PHP.
So, there's more than one error likely.
kaby76 commentedon Dec 18, 2022
Found the problem. Or, in all likelihood, it may be only one of several.
In ATNConfigSet, there is a table called
configLookup
. It is a Dictionary<ATNConfig, ATNConfig> in C#, but a Set of ATNConfig in PHP. (Note, we've seen this difference in implementation before, between other targets.)In C#, the code calls "GetOrAdd()", which is here. That table has a special comparer and hash function set for the class. The hash function that is executed is in class ConfigEqualityComparer, which has code that uses three fields of the ATNConfig.
Over in PHP, the code uses a generic Set implementation for $configLookup. That set is allocated here. Notice the hash function defined in this anonymous class here. That calls the standard hash function for ATNConfig. That computes the hash value using four fields--which is wrong!
I've verified that the call stack is indeed calling the wrong hash function for ATNConfig for this table in ParserATNSimulator. This is quite serious.
parrt commentedon Dec 18, 2022
hahah. it's ALWAYS the hash function. @marcospassos looks like there might be an issue. We need a map from X->X not a set so we can reuse the same instance.
kaby76 commentedon Dec 19, 2022
I have an initial set of changes that seems to get past some of the parser tracing diffs.
diffs.txt
Essentially, as per note by @parrt I replaced the
Set
type for$configLookup
in ATNConfigSet.php withMap
as done with Java and CSharp. I also corrected the hash function/comparsion class for the map to be identical in CSharp. TheMap
interface needed some changes in the API forgetOrAdd()
andisEmpty()
--which is just a hack to move past the first problem. It also contains some "print()'s" that end trace output with a new line ("echo()" does not write a newline!!). #33. People can wordsmith the "correct" design.The trace is starting to now look better, getting past the diff with "addDFAState" in the output. But I still notice diffs. There are more problems.
32 remaining items
parrt commentedon Dec 22, 2022
we could simply add a new flag to the runtime test mechanism that says to compare the tracing output.
marcospassos commentedon Dec 22, 2022
Exactly. It would help to ensure all targets work as expected. We can even add these trace files to the grammar repository as a ground truth.
marcospassos commentedon Dec 22, 2022
Providing a built-in target-independent check would be amazing. However, running it on the PHP repository would allow for faster feedback and error prevention.
parrt commentedon Dec 22, 2022
Sounds good. Let me think about this more. I'd like to get more find grand trace information out first and then we can figure out what sort of testing to do. There are about 350 runtime tests. I wonder if it makes sense to simply always compare the output of the trace with the known good output. It makes some sense and they trace output would not be that big.
parrt commentedon Dec 22, 2022
oh right. can't capture stdout unless we run in single-threaded mode...all tracing sends to stdout.
kaby76 commentedon Dec 23, 2022
I added some code to the trgen template driver generator with a switch for ATN tracing. But it's just for C# and PHP right now. Unfortunately, many of the v4 grammars just aren't PHP compatible because the grammars contain common names between lexer and parser rules (e.g., "COMMENT"/"comment"; see this and this). I plan to add the switch to the other templates today (Cpp, Dart, Go, Java, JavaScript, Python3). Eventually, I'll try to test all v4 grammars with ATN trace diffing. It can't be done in the CI builds because it'll take many hours of CPU time, but at least I can run it once in a while.
Dealing with large trace files can become quite cumbersome. MSYS
diff
craps out on 1.5GB files, giving bad results. GNU Emacs definitely helps because it can load large files, and offers a "compare-windows" command. Ironically, for the html grammar with abc.com.html, the traces differ pretty much in the beginning of the 1.5GB files, at line 733. (Note, PHP doesn't stop parsing, so I have to kill the process when it reaches 1.5GB.)A problem with the trace in PHP is that I added a
print("\n")
to the code. That's not an OS-dependent "newline". So, I have to usedos2unix
to standardize the files for comparison. I'm not sure what the call is in PHP to just write a newline customary for the OS.If a full run of the parser isn't needed to find ATN trace differences, then I might use
trwdog
,head
,tail
,split
to create a window on the output that I can compare. I could then work in chunks of ~10MB.Ideally, it would be best if there was some way of stepping through two parsers side-by-size, "one rule at a time" or some standardized step increment, so I can compare two targets side-by-side and not have to run to completion the parse of each target in order to find the first ATN trace difference. I think it's possible with a program that spawns a command-line debugger for each target.
I really don't know the cause of the memory-overflow condition in PHP. I had to start somewhere and decided to just start with ATN trace diffing. My philosophy is to do white-box testing of the internals. I don't think it's enough to test for identical parse trees between targets.
kaby76 commentedon Dec 23, 2022
Looks like ATN tracing is not available for Cpp and Dart.
marcospassos commentedon Dec 23, 2022
@kaby76, just use
echo \PHP_EOL;
parrt commentedon Dec 23, 2022
parrt commentedon Dec 23, 2022
Oh yeah, maybe not dart, but definitely C++. The issue with C++ is that it's a macro TRACE_ATN_SIM that must be set during the runtime build. Here is the argument to the build file. It's annoying that it is not changeable at runtime but we have no choice since it's set up as a macro and C++ people are obsessed with speed.
I think this means that I manually run a build with
cmake TRACE_ATN=ON
or whatever then run traceatn.sh.mpdude commentedon Oct 12, 2024
I am currently looking into ANTLR to research how it could replace a larger, hand-written parser in the Doctrine ORM project, basically Java's Hibernate in the PHP ecosystem.
The above reads as if you put tons of work into fixing bugs in the PHP runtime. May I ask whether all of these fixes found their way into the runtime already, and this issue here is open only because of a particular problem with some special recursion edge case? Or did all the efforts stall at some point and all of those mentioned bugs are still sitting there?
marcospassos commentedon Oct 12, 2024
Hi @mpdude, these are definitely edge cases I haven't had time to address yet, but I plan to. We've been using this runtime in production for mission-critical applications for years, and it's absolutely production-ready.