Weird critical error Topic is solved

Report problems with documented functionality
Descolada
Posts: 1431
Joined: 23 Dec 2021, 02:30

Weird critical error

Post by Descolada » 18 Mar 2024, 13:24

Running the following code in v2.0.11 in debugging mode (I'm using VSCode) and typing afaik a few times causes
Critical error: Function recursion limit exceeded
and crashes the script. It happens in "regular" mode as well (without debugging), but I haven't managed to find a good reproducable example.

Code: Select all

#Requires AutoHotkey v2

:XB0:afaik:: ; removing B0 solves the error
{
    ih := InputHook("V")
    ih.OnKeyUp := OnKeyUp ; removing this line solves the error
    ih.KeyOpt("{All}", "N S")
    ih.Start()
    SendMode "Event"
    Send("{BS 6}")
    ih.Stop()
}

OnKeyUp(*) {
    Sleep 1 ; removing this line makes the error more rare
}

; removing this class solves the error
class SomeClass {
    ; making this non-static solves the error
    static SendLevel {
        get => 1
    }
}

lexikos
Posts: 9780
Joined: 30 Sep 2013, 04:07
Contact:

Re: Weird critical error

Post by lexikos » 22 Mar 2024, 02:26

I could not produce any crashes.

If a class has a dynamic static property, it is likely to be invoked whenever VS Code updates its list of global variables. The global variables don't appear to be shown or updated if the script is running (not paused or at a breakpoint), but maybe they are if you use tracepoint, debug directives or any similarly tricky features of vscode-autohotkey-debug.

If what you are calling "regular" mode really is without debugging, and this is the exact script you ran, there is no way for the SendLevel property to be invoked. Change it to do something visible, like show a MsgBox. If it gets invoked, figure out how. If it doesn't get invoked and the problem still happens, it probably has nothing to do with the static property. If an error causes an invalid pointer to be dereferenced (or something like that), seemingly unrelated changes to the script can have unpredictable effects - i.e. because the exact arrangement of bytes in memory affects the end result, even if the changes don't have any logical connection to the real issue.

Don't use OutputDebug in the property; when I tried it, vscode-autohotkey-debug failed to list the property under Globals or any property below it. I'm assuming that's an issue with the extension, since it worked in SciTE4AutoHotkey while at a breakpoint.

It would be interesting to know what native functions were on the stack prior to the Critical Error, but to find that out you would need to reproduce the issue while running AutoHotkey in true debug mode (i.e. in Visual Studio, or VS Code with the C++ extension). vscode-autohotkey-debug (in a separate VS Code instance) can attach to the process after it starts.

Descolada
Posts: 1431
Joined: 23 Dec 2021, 02:30

Re: Weird critical error

Post by Descolada » 22 Mar 2024, 14:51

I ran AutoHotkey.exe in debug mode in Visual Studio + the script in debug mode in VSCode with vscode-autohotkey-debug (attach mode), and the stack before the crash was the following:

Code: Select all

 	ntdll.dll!00007ff949cbb3c2()	Unknown
>	AutoHotkey64.exe!heap_alloc_dbg_internal(const unsigned __int64 size, const int block_use, const char * const file_name, const int line_number) Line 359	C++
 	AutoHotkey64.exe!heap_alloc_dbg(const unsigned __int64 size, const int block_use, const char * const file_name, const int line_number) Line 450	C++
 	AutoHotkey64.exe!_malloc_dbg(unsigned __int64 size, int block_use, const char * file_name, int line_number) Line 496	C++
 	AutoHotkey64.exe!malloc(unsigned __int64 size) Line 27	C++
 	[External Code]	
 	AutoHotkey64.exe!CKuStringT<char,CKuStringUtilA>::New(bool bCopy) Line 406	C++
 	AutoHotkey64.exe!CKuStringT<char,CKuStringUtilA>::GetBuffer() Line 549	C++
 	AutoHotkey64.exe!CKuStringT<char,CKuStringUtilA>::GetBufferSetLength(__int64 len) Line 555	C++
 	AutoHotkey64.exe!StringWCharToUTF8(const wchar_t * sWChar, CKuStringT<char,CKuStringUtilA> & sUTF8, int iChars) Line 49	C++
 	AutoHotkey64.exe!CStringUTF8FromWChar::CStringUTF8FromWChar(const wchar_t * sWChar, int iChars) Line 58	C++
 	AutoHotkey64.exe!Debugger::PropertyWriter::BeginProperty(const char * aName, const char * aType, int aNumChildren, void * & aCookie) Line 3015	C++
 	AutoHotkey64.exe!Object::DebugWriteProperty(IDebugProperties * aDebugger, int aPage, int aPageSize, int aDepth) Line 1193	C++
 	AutoHotkey64.exe!Debugger::WritePropertyXml(Debugger::PropertyInfo & aProp, IObject * aObject) Line 1177	C++
 	AutoHotkey64.exe!Debugger::WritePropertyXml(Debugger::PropertyInfo & aProp) Line 1323	C++
 	AutoHotkey64.exe!Debugger::PropertyWriter::_WriteProperty(ExprTokenType & aValue, IObject * aThisOverride) Line 2999	C++
 	AutoHotkey64.exe!Debugger::PropertyWriter::WriteBaseProperty(IObject * aBase) Line 2954	C++
 	AutoHotkey64.exe!Object::DebugWriteProperty(IDebugProperties * aDebugger, int aPage, int aPageSize, int aDepth) Line 1204	C++
 	AutoHotkey64.exe!Debugger::WritePropertyXml(Debugger::PropertyInfo & aProp, IObject * aObject) Line 1177	C++
 	AutoHotkey64.exe!Debugger::WritePropertyXml(Debugger::PropertyInfo & aProp) Line 1323	C++
 	AutoHotkey64.exe!Debugger::WritePropertyXml(Debugger::PropertyInfo & aProp, wchar_t * aName) Line 1346	C++
 	AutoHotkey64.exe!Debugger::context_get(char * * aArgV, int aArgCount, char * aTransactionId) Line 1090	C++
 	AutoHotkey64.exe!Debugger::ProcessCommands(const char * aBreakReason) Line 323	C++
 	AutoHotkey64.exe!Debugger::PreExecLine(Line * aLine) Line 190	C++
 	AutoHotkey64.exe!Line::ExecUntil(ExecUntilMode aMode, ResultToken * aResultToken, Line * * apJumpToLine) Line 9602	C++
 	AutoHotkey64.exe!UserFunc::Execute(ResultToken * aResultToken) Line 1622	C++
 	AutoHotkey64.exe!UserFunc::Call(ResultToken & aResultToken, ExprTokenType * * aParam, int aParamCount, FreeVars * aUpVars) Line 2056	C++
 	AutoHotkey64.exe!UserFunc::Call(ResultToken & aResultToken, ExprTokenType * * aParam, int aParamCount) Line 1796	C++
 	AutoHotkey64.exe!Func::Invoke(ResultToken & aResultToken, int aFlags, wchar_t * aName, ExprTokenType & aThisToken, ExprTokenType * * aParam, int aParamCount) Line 2568	C++
 	AutoHotkey64.exe!Object::Invoke(ResultToken & aResultToken, int aFlags, wchar_t * aName, ExprTokenType & aThisToken, ExprTokenType * * aParam, int aParamCount) Line 638	C++
 	AutoHotkey64.exe!Debugger::PropertyWriter::WriteDynamicProperty(wchar_t * aName) Line 2963	C++
 	AutoHotkey64.exe!Object::DebugWriteProperty(IDebugProperties * aDebugger, int aPage, int aPageSize, int aDepth) Line 1221	C++
------------ the following part repeats for a long while
 	AutoHotkey64.exe!Debugger::WritePropertyXml(Debugger::PropertyInfo & aProp, IObject * aObject) Line 1177	C++
 	AutoHotkey64.exe!Debugger::WritePropertyXml(Debugger::PropertyInfo & aProp) Line 1323	C++
 	AutoHotkey64.exe!Debugger::WritePropertyXml(Debugger::PropertyInfo & aProp, wchar_t * aName) Line 1346	C++
 	AutoHotkey64.exe!Debugger::context_get(char * * aArgV, int aArgCount, char * aTransactionId) Line 1090	C++
 	AutoHotkey64.exe!Debugger::ProcessCommands(const char * aBreakReason) Line 323	C++
 	AutoHotkey64.exe!Debugger::PreExecLine(Line * aLine) Line 190	C++
 	AutoHotkey64.exe!Line::ExecUntil(ExecUntilMode aMode, ResultToken * aResultToken, Line * * apJumpToLine) Line 9602	C++
 	AutoHotkey64.exe!UserFunc::Execute(ResultToken * aResultToken) Line 1622	C++
 	AutoHotkey64.exe!UserFunc::Call(ResultToken & aResultToken, ExprTokenType * * aParam, int aParamCount, FreeVars * aUpVars) Line 2056	C++
 	AutoHotkey64.exe!UserFunc::Call(ResultToken & aResultToken, ExprTokenType * * aParam, int aParamCount) Line 1796	C++
 	AutoHotkey64.exe!Func::Invoke(ResultToken & aResultToken, int aFlags, wchar_t * aName, ExprTokenType & aThisToken, ExprTokenType * * aParam, int aParamCount) Line 2568	C++
 	AutoHotkey64.exe!Object::Invoke(ResultToken & aResultToken, int aFlags, wchar_t * aName, ExprTokenType & aThisToken, ExprTokenType * * aParam, int aParamCount) Line 638	C++
 	AutoHotkey64.exe!Debugger::PropertyWriter::WriteDynamicProperty(wchar_t * aName) Line 2963	C++
 	AutoHotkey64.exe!Object::DebugWriteProperty(IDebugProperties * aDebugger, int aPage, int aPageSize, int aDepth) Line 1221	C++
---------------
 	AutoHotkey64.exe!Debugger::WritePropertyXml(Debugger::PropertyInfo & aProp, IObject * aObject) Line 1177	C++
 	AutoHotkey64.exe!Debugger::WritePropertyXml(Debugger::PropertyInfo & aProp) Line 1323	C++
 	AutoHotkey64.exe!Debugger::WritePropertyXml(Debugger::PropertyInfo & aProp, wchar_t * aName) Line 1346	C++
 	AutoHotkey64.exe!Debugger::context_get(char * * aArgV, int aArgCount, char * aTransactionId) Line 1090	C++
 	AutoHotkey64.exe!Debugger::ProcessCommands(const char * aBreakReason) Line 323	C++
 	AutoHotkey64.exe!Debugger::PreExecLine(Line * aLine) Line 190	C++
 	AutoHotkey64.exe!Line::ExecUntil(ExecUntilMode aMode, ResultToken * aResultToken, Line * * apJumpToLine) Line 9602	C++
 	AutoHotkey64.exe!UserFunc::Execute(ResultToken * aResultToken) Line 1622	C++
 	AutoHotkey64.exe!UserFunc::Call(ResultToken & aResultToken, ExprTokenType * * aParam, int aParamCount, FreeVars * aUpVars) Line 2056	C++
 	AutoHotkey64.exe!UserFunc::Call(ResultToken & aResultToken, ExprTokenType * * aParam, int aParamCount) Line 1796	C++
 	AutoHotkey64.exe!Func::Invoke(ResultToken & aResultToken, int aFlags, wchar_t * aName, ExprTokenType & aThisToken, ExprTokenType * * aParam, int aParamCount) Line 2568	C++
 	AutoHotkey64.exe!Object::Invoke(ResultToken & aResultToken, int aFlags, wchar_t * aName, ExprTokenType & aThisToken, ExprTokenType * * aParam, int aParamCount) Line 638	C++
 	AutoHotkey64.exe!Debugger::PropertyWriter::WriteDynamicProperty(wchar_t * aName) Line 2963	C++
 	AutoHotkey64.exe!Object::DebugWriteProperty(IDebugProperties * aDebugger, int aPage, int aPageSize, int aDepth) Line 1221	C++
 	AutoHotkey64.exe!Debugger::WritePropertyXml(Debugger::PropertyInfo & aProp, IObject * aObject) Line 1177	C++
 	AutoHotkey64.exe!Debugger::WritePropertyXml(Debugger::PropertyInfo & aProp) Line 1323	C++
 	AutoHotkey64.exe!Debugger::WritePropertyXml(Debugger::PropertyInfo & aProp, wchar_t * aName) Line 1346	C++
 	AutoHotkey64.exe!Debugger::context_get(char * * aArgV, int aArgCount, char * aTransactionId) Line 1090	C++
 	AutoHotkey64.exe!Debugger::ProcessCommands(const char * aBreakReason) Line 323	C++
 	AutoHotkey64.exe!Debugger::PreExecLine(Line * aLine) Line 190	C++
 	AutoHotkey64.exe!Line::ExecUntil(ExecUntilMode aMode, ResultToken * aResultToken, Line * * apJumpToLine) Line 9602	C++
 	AutoHotkey64.exe!UserFunc::Execute(ResultToken * aResultToken) Line 1622	C++
 	AutoHotkey64.exe!UserFunc::Call(ResultToken & aResultToken, ExprTokenType * * aParam, int aParamCount, FreeVars * aUpVars) Line 2056	C++
 	AutoHotkey64.exe!UserFunc::Call(ResultToken & aResultToken, ExprTokenType * * aParam, int aParamCount) Line 1796	C++
 	AutoHotkey64.exe!Func::Invoke(ResultToken & aResultToken, int aFlags, wchar_t * aName, ExprTokenType & aThisToken, ExprTokenType * * aParam, int aParamCount) Line 2568	C++
 	AutoHotkey64.exe!Object::Invoke(ResultToken & aResultToken, int aFlags, wchar_t * aName, ExprTokenType & aThisToken, ExprTokenType * * aParam, int aParamCount) Line 638	C++
 	The maximum number of stack frames supported by Visual Studio has been exceeded.	
and the final error occurred in C:\Program Files (x86)\Windows Kits\10\Source\10.0.22621.0\ucrt\heap\debug_heap.cpp at line 359 _CrtMemBlockHeader* const header{static_cast<_CrtMemBlockHeader*>(HeapAlloc(__acrt_heap, 0, block_size))};

When I added the following lines here before the return

Code: Select all

OutputDebugStringA(aProp.fullname);
OutputDebugStringA("\n");
then the output was as follows:

Code: Select all

<irrelevant data cropped>
Send.<base>
Send
SendMode.<base>
SendMode
Sleep.<base>
Sleep
SomeClass.<base>
SomeClass.__Init
SomeClass.Prototype
this.<base>
this.__Init
this.Prototype
this.<base>
this.__Init
this.Prototype
this.<base>
this.__Init
this.Prototype
this.<base>
this.__Init
... repeating
I tried get => FileAppend("a", "*") instead of get => 1, but the line wasn't executed.

Hopefully this is of some help, and I'll try to debug this further when I get some free time...

lexikos
Posts: 9780
Joined: 30 Sep 2013, 04:07
Contact:

Re: Weird critical error

Post by lexikos » 22 Mar 2024, 17:19

That's definitely not going to happen "in regular mode as well (without debugging)".

Probably something like this is happening:
  • Debugger::ProcessCommands waits for commands, receives context_get.
  • context_get calls Debugger::WritePropertyXml to write SomeClass.
  • Debugger::WritePropertyXml is called recursively to write SendLevel.
  • Object is invoked, property getter is called.
  • Debugger::PreExecLine is called prior to execution of return 1 in the getter.
  • Debugger::ProcessCommands is called directly from Debugger::PreExecLine, meaning that no breakpoint was hit, but a command was sent asynchronously by the debugger client.
  • Goto 1.
I don't know what to make of the repeating this queries at the end.

Perhaps the program could partially protect itself from a flood of commands causing recursion by implementing an "uninterruptible" period like it does for new threads, but ultimately the issue is with the extension sending these commands.

Descolada
Posts: 1431
Joined: 23 Dec 2021, 02:30

Re: Weird critical error  Topic is solved

Post by Descolada » 30 Mar 2024, 07:31

Confirmed as a bug in the debugger plugin that I was using (zero-plusplus). Most likely I was running AHK accidentally with a debugger when I thought I was running it without, as I definitely haven't been able to reproduce it again without a debugger. Can mark this as solved.

lexikos
Posts: 9780
Joined: 30 Sep 2013, 04:07
Contact:

Re: Weird critical error

Post by lexikos » 27 Apr 2024, 21:15

The debug server (within AutoHotkey) is supposed to buffer any commands which are received while a previous command is being processed, because the command processor isn't designed to support re-entry. However, the flood of commands from the extension is causing re-entry, which I realized shouldn't be possible. The existing safeguard is to check whether the debugger is already in a break state, but in this case it isn't in a break state; i.e. a command is received asynchronously, and while it is still being processed, another command is received asynchronously.

Even if the extension limits the frequency or number of commands it sends to avoid exhausting the stack, re-entry would corrupt any response being built for the previous command. That could be fixed, but it would increase code size/complexity. Due to the single-threaded nature of the program, only the most recently received command can receive a response, so allowing re-entry may require some safeguards (perhaps in both the debug server and client) to avoid problems like stack exhaustion or deadlock. Most of the time it is probably better to queue the commands and not allow interruption.

So future AutoHotkey releases will properly prevent re-entry. With the exception of continuation commands, each command will only be processed once a response has been sent for the previous command. (A debug client need not wait for a response before sending another command; that could add significant latency to the session.)

Post Reply

Return to “Bug Reports”