Skip to content

Commit

Permalink
Merge v2.0.17
Browse files Browse the repository at this point in the history
  • Loading branch information
thqby committed Jun 5, 2024
2 parents a00d4c0 + 2af14e2 commit 4699e94
Show file tree
Hide file tree
Showing 14 changed files with 113 additions and 119 deletions.
49 changes: 34 additions & 15 deletions source/Debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ int Debugger::PreExecLine(Line *aLine)
// mStack.mTop->line = aLine;
mCurrLine = aLine;

if (mProcessingCommands) // Reentry into ProcessCommands() isn't possible, so Break() would be ignored.
return DEBUGGER_E_OK; // Skip the rest; in particular, don't delete any temporary breakpoints.

// Check for a breakpoint on the current line:
Breakpoint *bp = aLine->mBreakpoint;
if (bp && bp->state == BS_Enabled)
Expand Down Expand Up @@ -194,6 +197,21 @@ int Debugger::PreExecLine(Line *aLine)
}


void Debugger::LeaveFunction()
{
// If the debugger is stepping out from a user-defined function, break at the line
// which called the function. Otherwise, a step might appear to jump from one
// function to another called by the same line, or to the next line, or to some line
// further up the stack. Into/Over count as stepping out only if the function we're
// leaving is the one in which the step command was issued.
if (mInternalState == DIS_StepInto
|| ((mInternalState == DIS_StepOut || mInternalState == DIS_StepOver)
&& mStack.Depth() < mContinuationDepth) // Always '<' and not '<=' (for StepOver) because we just returned from a function call.
&& mStack.mTop > mStack.mBottom) // More than one entry, otherwise mCurrLine has no meaning.
PreExecLine(mCurrLine);
}


bool Debugger::PreThrow(ExprTokenType *aException)
{
if (!mBreakOnException)
Expand Down Expand Up @@ -281,6 +299,13 @@ int Debugger::ProcessCommands(LPCSTR aBreakReason)
if (err = EnterBreakState(aBreakReason))
return err;
mProcessingCommands = true;
// Set a non-zero ExcptMode so that UnhandledException will be called for runtime
// errors, and use EXCPTMODE_DEBUGGER so that UnhandledException will immediately
// free the exception and return FAIL without doing any reporting.
// Any ExcptMode flags of the interrupted thread don't apply to code executed by the
// debugger, since it isn't executing within or being called by the current block.
auto excptmode = g->ExcptMode;
g->ExcptMode = EXCPTMODE_DEBUGGER;

// Disable notification of READ readiness and reset socket to synchronous mode.
u_long zero = 0;
Expand Down Expand Up @@ -380,6 +405,7 @@ int Debugger::ProcessCommands(LPCSTR aBreakReason)
}
ASSERT(mInternalState != DIS_Break);
mProcessingCommands = false;
g->ExcptMode = excptmode;
// Register for message-based notification of data arrival. If a command
// is received asynchronously, control will be passed back to the debugger
// to process it. This allows the debugger engine to respond even if the
Expand Down Expand Up @@ -1206,7 +1232,6 @@ void Object::DebugWriteProperty(IDebugProperties *aDebugger, int aPage, int aPag
{
int page_start = aPageSize * aPage, page_end = aPageSize * (aPage + 1);

int i = 0;
if (mBase)
{
// Since this object has a "base", let it count as the first field.
Expand All @@ -1215,12 +1240,15 @@ void Object::DebugWriteProperty(IDebugProperties *aDebugger, int aPage, int aPag
aDebugger->WriteBaseProperty(mBase);
// Now fall through and retrieve field[0] (unless aPageSize == 1).
}
i++; // Count it even if it wasn't within the current page.
// So 20..39 becomes 19..38 when there's a base object:
else --page_start;
--page_end;
}
int i = min(page_start, (int)mFields.Length());
// For each field in the requested page...
for (int j = page_start ? page_start - i : 0; i < page_end && (index_t)j < mFields.Length(); ++i, ++j)
for ( ; i < page_end && (index_t)i < mFields.Length(); ++i)
{
Object::FieldType &field = mFields[j];
Object::FieldType &field = mFields[i];
ExprTokenType value;
if (field.symbol == SYM_DYNAMIC)
{
Expand Down Expand Up @@ -1766,9 +1794,6 @@ int Debugger::ParsePropertyName(LPCSTR aFullName, int aDepth, int aVarScope, Exp
}
auto result = this_obj->Invoke(aResult.value, invoke_flags, name, t_this, param, param_count);

if (g->ThrownToken)
g_script->FreeExceptionToken(g->ThrownToken);

if (aResult.value.symbol == SYM_STRING && !aResult.value.mem_to_free && aResult.value.marker != aResult.value.buf)
{
// Before releasing the target object, make a copy of the string in case it points
Expand Down Expand Up @@ -3022,16 +3047,9 @@ void Debugger::PropertyWriter::WriteBaseProperty(IObject *aBase)
void Debugger::PropertyWriter::WriteDynamicProperty(LPTSTR aName)
{
FuncResult result_token;
auto excpt_mode = g->ExcptMode;
g->ExcptMode |= EXCPTMODE_CATCH;
auto result = mProp.invokee->Invoke(result_token, IT_GET, aName, mProp.value, nullptr, 0);
g->ExcptMode = excpt_mode;
if (!result)
{
if (g->ThrownToken)
g_script->FreeExceptionToken(g->ThrownToken);
result_token.SetValue(_T("<error>"), 7);
}
mDbg.AppendPropertyName(mProp.fullname, mNameLength, CStringUTF8FromTChar(aName));
_WriteProperty(result_token);
result_token.Free();
Expand All @@ -3043,7 +3061,8 @@ void Debugger::PropertyWriter::_WriteProperty(ExprTokenType &aValue, IObject *aI
if (mError)
return;
PropertyInfo prop(mProp.fullname, mProp.value.buf);
prop.invokee = aInvokee;
if (aInvokee)
prop.invokee = aInvokee;
// Find the property's "relative" name at the end of the buffer:
prop.name = mProp.fullname.GetString() + mNameLength;
if (*prop.name == '.')
Expand Down
23 changes: 1 addition & 22 deletions source/Debugger.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,36 +191,15 @@ class Debugger
void Exit(ExitReasons aExitReason, char *aCommandName=NULL); // Called when exiting AutoHotkey.
inline bool IsConnected() { return mSocket != INVALID_SOCKET; }
inline bool IsStepping() { return mInternalState >= DIS_StepInto; }
inline bool IsAtBreak() { return mProcessingCommands; }
inline bool HasStdErrHook() { return mStdErrMode != SR_Disabled; }
inline bool HasStdOutHook() { return mStdOutMode != SR_Disabled; }
inline bool BreakOnExceptionIsEnabled() { return mBreakOnException; }

LPCTSTR WhatThrew();

__declspec(noinline) // Avoiding inlining should reduce the code size of ExpandExpression(), which might help performance since this is only called when the debugger is connected.
void PostExecFunctionCall(Line *aExpressionLine)
{
// If the debugger is stepping into/over/out from a function call, we want to
// break at the line which called that function, since the next line to execute
// might be a line in some other function (i.e. because the line which called
// the function is "return func()" or calls another function after this one).
if ((mInternalState == DIS_StepInto
|| ((mInternalState == DIS_StepOut || mInternalState == DIS_StepOver)
// Always '<' since '<=' (for StepOver) shouldn't be possible,
// since we just returned from a function call:
&& mStack.Depth() < mContinuationDepth))
// The final check ensures we don't repeatedly break at a line containing
// multiple built-in function calls; i.e. don't break unless some script
// has been executed since we began evaluating aExpressionLine. Something
// like "return recursivefunc()" should work if this is StepInto or StepOver
// since mCurrLine would probably be the '}' of that function:
&& mCurrLine != aExpressionLine)
PreExecLine(aExpressionLine);
}

// Code flow notification functions:
int PreExecLine(Line *aLine); // Called before executing each line.
void LeaveFunction();
bool PreThrow(ExprTokenType *aException);

// Receive and process commands. Returns when a continuation command is received.
Expand Down
1 change: 1 addition & 0 deletions source/defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ enum ExcptModeType {EXCPTMODE_NONE = 0
, EXCPTMODE_CATCH = 2 // Exception will be suppressed or caught.
, EXCPTMODE_DELETE = 4 // Unhandled exceptions will display ERR_ABORT_DELETE vs. ERR_ABORT.
, EXCPTMODE_CAUGHT = 0x10 // An exception is already being handled within a CATCH, and is not shadowed by TRY.
, EXCPTMODE_DEBUGGER = 0x20 // Debugger is evaluating a property and wants uncaught errors suppressed.
};

#define SEND_MODES { _T("Event"), _T("Input"), _T("Play"), _T("InputThenPlay") } // Must match the enum below.
Expand Down
52 changes: 25 additions & 27 deletions source/error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,8 @@ ResultType Line::SetThrownToken(global_struct &g, ResultToken *aToken, ResultTyp
{
#ifdef CONFIG_DEBUGGER
if (g_Debugger->IsConnected())
if (g_Debugger->IsAtBreak() || g_Debugger->PreThrow(aToken) && !(g.ExcptMode & EXCPTMODE_CATCH))
if (g_Debugger->PreThrow(aToken) && !(g.ExcptMode & EXCPTMODE_CATCH))
{
// IsAtBreak() indicates the debugger was already in a break state, likely
// processing a property_get or context_get which caused script execution.
// In that case, silence all error dialogs and don't set g.ThrownToken since
// the debugger is lax about detecting/clearing it. If PreThrow was called:
// The debugger has entered (and left) a break state, so the client has had a
// chance to inspect the exception and report it. There's nothing in the DBGp
// spec about what to do next, probably since PHP would just log the error.
Expand Down Expand Up @@ -258,11 +254,6 @@ ResultType Script::RuntimeError(LPCTSTR aErrorText, LPCTSTR aExtraInfo, ResultTy
if (!aExtraInfo)
aExtraInfo = _T("");

#ifdef CONFIG_DEBUGGER
if (g_Debugger->IsAtBreak()) // i.e. the debugger is processing a property_get or context_get.
return FAIL; // Silent abort, no g->ThrownToken.
#endif

if ((g->ExcptMode || mOnError.Count()
#ifdef CONFIG_DEBUGGER
|| g_Debugger->BreakOnExceptionIsEnabled()
Expand Down Expand Up @@ -744,7 +735,7 @@ ResultType Script::ScriptError(LPCTSTR aErrorText, LPCTSTR aExtraInfo)
if (g_script->mErrorStdOut && !g_script->mIsReadyToExecute) // i.e. runtime errors are always displayed via dialog.
{
// See LineError() for details.
PrintErrorStdOut(aErrorText, aExtraInfo, mCurrFileIndex, mCombinedLineNumber);
PrintErrorStdOut(aErrorText, aExtraInfo, mCurrLine ? mCurrLine->mFileIndex : mCurrFileIndex, CurrentLine());
}
else
{
Expand Down Expand Up @@ -942,6 +933,8 @@ ResultType TypeError(LPCTSTR aExpectedType, ExprTokenType &aActualValue)
{
TCHAR number_buf[MAX_NUMBER_SIZE];
LPCTSTR actual_type, value_as_string;
if (aActualValue.symbol == SYM_VAR && aActualValue.var->IsUninitializedNormalVar())
return g_script->VarUnsetError(aActualValue.var);
TokenTypeAndValue(aActualValue, actual_type, value_as_string, number_buf);
return TypeError(aExpectedType, actual_type, value_as_string);
}
Expand Down Expand Up @@ -1094,9 +1087,17 @@ ResultType Script::UnhandledException(Line* aLine, ResultType aErrorType)
// This includes OnError callbacks explicitly called below, but also COM events
// and CallbackCreate callbacks that execute while MsgBox() is waiting.
g.ThrownToken = NULL;


#ifdef CONFIG_DEBUGGER
if (g.ExcptMode & EXCPTMODE_DEBUGGER)
{
FreeExceptionToken(token);
return FAIL;
}
#endif

// OnError: Allow the script to handle it via a global callback.
static bool sOnErrorRunning = false;
thread_local bool sOnErrorRunning = false;
if (mOnError.Count() && !sOnErrorRunning)
{
__int64 retval;
Expand Down Expand Up @@ -1258,7 +1259,7 @@ void Script::WarnUnassignedVar(Var *var, Line *aLine)
return;

// Currently only the first reference to each var generates a warning even when using
// StdOut/OutputDebug, since MarkAlreadyWarned() is used to suppress warnings for any
// StdOut/OutputDebug. MarkAlreadyWarned() is also used to suppress warnings for any
// var which is checked with IsSet().
//if (warnMode == WARNMODE_MSGBOX)
{
Expand All @@ -1270,26 +1271,23 @@ void Script::WarnUnassignedVar(Var *var, Line *aLine)
var->MarkAlreadyWarned();
}

bool isUndeclaredLocal = (var->Scope() & (VAR_LOCAL | VAR_DECLARED)) == VAR_LOCAL;
LPCTSTR sameNameAsGlobal = isUndeclaredLocal && FindGlobalVar(var->mName) ? _T(" (same name as a global)") : _T("");
TCHAR buf[DIALOG_TITLE_SIZE];
sntprintf(buf, _countof(buf), _T("%s %s%s"), Var::DeclarationType(var->Scope()), var->mName, sameNameAsGlobal);
ScriptWarning(warnMode, WARNING_ALWAYS_UNSET_VARIABLE, buf, aLine);
// No check for a global variable is done because var is unassigned and therefore should
// have resolved to a global if there was one, unless it was declared local.
TCHAR buf[1024];
sntprintf(buf, _countof(buf), _T("This %s appears to never be assigned a value."), Var::DeclarationType(var->Scope()));
ScriptWarning(warnMode, buf, var->mName, aLine);
}



ResultType Script::VarUnsetError(Var *var)
{
TCHAR buf[1024];
bool isUndeclaredLocal = (var->Scope() & (VAR_LOCAL | VAR_DECLARED)) == VAR_LOCAL;
TCHAR buf[DIALOG_TITLE_SIZE];
if (*var->mName) // Avoid showing "Specifically: global " for temporary VarRefs of unspecified scope, such as those used by Array::FromEnumerable or the debugger.
{
LPCTSTR sameNameAsGlobal = isUndeclaredLocal && FindGlobalVar(var->mName) ? _T(" (same name as a global)") : _T("");
sntprintf(buf, _countof(buf), _T("%s %s%s"), Var::DeclarationType(var->Scope()), var->mName, sameNameAsGlobal);
}
else *buf = '\0';
return RuntimeError(ERR_VAR_UNSET, buf, FAIL_OR_OK, nullptr, ErrorPrototype::Unset);
bool sameNameAsGlobal = isUndeclaredLocal && FindGlobalVar(var->mName);
sntprintf(buf, _countof(buf), _T("This %s has not been assigned a value.%s"), Var::DeclarationType(var->Scope())
, sameNameAsGlobal ? _T("\nA global declaration inside the function may be required.") : _T(""));
return RuntimeError(buf, var->mName, FAIL_OR_OK, nullptr, ErrorPrototype::Unset);
}


Expand Down
8 changes: 0 additions & 8 deletions source/exports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -329,10 +329,6 @@ UINT_PTR _addScript(LPTSTR script, int waitexecute, DWORD aThreadID, int _catch)
auto &fn = (UserFunc *&)funcs[i];
if (fn->mClass)
fn->mClass->Release();
#ifndef KEEP_FAT_ARROW_FUNCTIONS_IN_LINE_LIST
if (fn->mIsFuncExpression == -1)
fn->mJumpToLine->Free();
#endif // !KEEP_FAT_ARROW_FUNCTIONS_IN_LINE_LIST
fn->Release(), fn = nullptr;
}
g_script->mFuncs.mCount = aFuncCount;
Expand Down Expand Up @@ -485,10 +481,6 @@ int _ahkExec(LPTSTR script, DWORD aThreadID, int _catch)
auto &fn = (UserFunc *&)funcs[i];
if (fn->mClass)
fn->mClass->Release();
#ifndef KEEP_FAT_ARROW_FUNCTIONS_IN_LINE_LIST
if (fn->mIsFuncExpression == -1)
fn->mJumpToLine->Free();
#endif // !KEEP_FAT_ARROW_FUNCTIONS_IN_LINE_LIST
fn->Release(), fn = nullptr;
}
g_script->mFuncs.mCount = aFuncCount;
Expand Down
2 changes: 2 additions & 0 deletions source/lib/interop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,8 @@ BIF_DECL(BIF_StrGetPut) // BIF_DECL(BIF_StrGet), BIF_DECL(BIF_StrPut)
}
// Convert to target encoding.
char_count = WideCharToMultiByte(encoding, flags, (LPCWSTR)source_string, source_length, (LPSTR)address, length, NULL, NULL);
if (!char_count && flags && GetLastError() == ERROR_INVALID_FLAGS) // See the similar check above for comments; this one covers cases where length was specified.
char_count = WideCharToMultiByte(encoding, 0, (LPCWSTR)source_string, source_length, (LPSTR)address, length, NULL, NULL);
// Since above did not null-terminate, check for buffer space and null-terminate if there's room.
// It is tempting to always null-terminate (potentially replacing the last byte of data),
// but that would exclude this function as a means to copy a string into a fixed-length array.
Expand Down
17 changes: 4 additions & 13 deletions source/script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -528,10 +528,6 @@ Script::~Script() // Destructor.
auto fn = (UserFunc *&)mFuncs.mItem[i];
if (fn->mClass)
fn->mClass->Release();
#ifndef KEEP_FAT_ARROW_FUNCTIONS_IN_LINE_LIST
if (fn->mIsFuncExpression == -1)
fn->mJumpToLine->Free();
#endif // !KEEP_FAT_ARROW_FUNCTIONS_IN_LINE_LIST
fn->Release();
}
free(mFuncs.mItem);
Expand Down Expand Up @@ -1572,7 +1568,10 @@ ResultType Script::ExitApp(ExitReasons aExitReason)
// for times when it would be unsafe to call MsgBox() due to the possibility that it would
// make the situation even worse).
{
int aExitCode = mPendingExitCode; // It's done this way so Exit() can pass an exit code indirectly.
// If we're called before the script has loaded, it is almost certainly by the ExitApp
// button on an error/warning dialog. Treat it as a load-time error either way since
// that's probably what the user wants.
int aExitCode = mIsReadyToExecute ? mPendingExitCode : CRITICAL_ERROR;
auto aReload = g_Reloading;
if (!g_Reloading)
g_Reloading = -1;
Expand Down Expand Up @@ -8431,21 +8430,13 @@ Line *Script::PreparseCommands(Line *aStartingLine)
// all skip an initial ACT_BLOCK_BEGIN (to avoid an extra ExecUntil call),
// which would result in executing the function's body instead of skipping it.
Line *body = line->mNextLine;
#ifdef KEEP_FAT_ARROW_FUNCTIONS_IN_LINE_LIST // Currently unused.
block_begin = parent->mNextLine; // In case there are multiple fat arrow functions on one line.
Line *after_body = parent->mRelatedLine;
Line *body_end = after_body->mPrevLine; // In case body is multiple lines (such as a nested IF or LOOP).
// Swap the statement body and fat arrow functions around to make it work:
parent ->mNextLine = body , body ->mPrevLine = parent;
body_end ->mNextLine = block_begin, block_begin->mPrevLine = body_end;
line ->mNextLine = after_body , after_body ->mPrevLine = line;
#else
// Remove the fat arrow functions to allow the correct body to execute.
// This relies on there being no need for the fat arrow functions to
// remain in the Line list after this point (so for instance, there's
// no possibility of setting a breakpoint in one of these functions).
parent->mNextLine = body, body->mPrevLine = parent, func.mIsFuncExpression = -1;
#endif
}
else if (parent && parent->mActionType != ACT_BLOCK_BEGIN)
{
Expand Down
4 changes: 1 addition & 3 deletions source/script.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,6 @@ enum CommandIDs {CONTROL_ID_FIRST = IDCANCEL + 1
#define ERR_SOUND_COMPONENT _T("Component not found")
#define ERR_SOUND_CONTROLTYPE _T("Component doesn't support this control type")
#define ERR_TIMEOUT _T("Timeout")
#define ERR_VAR_UNSET _T("This variable has not been assigned a value.")
#define WARNING_ALWAYS_UNSET_VARIABLE _T("This variable appears to never be assigned a value.")
#define WARNING_LOCAL_SAME_AS_GLOBAL _T("This local variable has the same name as a global variable.")

//----------------------------------------------------------------------------------
Expand Down Expand Up @@ -1535,7 +1533,7 @@ class UserFunc : public Func
int mClosureCount = 0;

// Keep small members adjacent to each other to save space and improve perf. due to byte alignment:
char mIsFuncExpression; // Whether this function was defined *within* an expression and is therefore allowed under a control flow statement.
bool mIsFuncExpression; // Whether this function was defined *within* an expression and is therefore allowed under a control flow statement.
bool mIsStatic = false; // Whether the "static" keyword was used with a function (not method); this prevents a nested function from becoming a closure.
#define VAR_DECLARE_GLOBAL (VAR_DECLARED | VAR_GLOBAL)
#define VAR_DECLARE_LOCAL (VAR_DECLARED | VAR_LOCAL)
Expand Down
Loading

0 comments on commit 4699e94

Please sign in to comment.