12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403 |
- /*
- AutoHotkey
- Copyright 2003-2009 Chris Mallett (support@autohotkey.com)
- This program is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License
- as published by the Free Software Foundation; either version 2
- of the License, or (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
- #include "stdafx.h"
- #include "script.h"
- #include "globaldata.h"
- #include "window.h"
- #include "TextIO.h"
- #include "abi.h"
- #include <richedit.h>
- Line *Line::PreparseError(LPTSTR aErrorText, LPTSTR aExtraInfo)
- // Returns a different type of result for use with the Pre-parsing methods.
- {
- // Make all preparsing errors critical because the runtime reliability
- // of the program relies upon the fact that the aren't any kind of
- // problems in the script (otherwise, unexpected behavior may result).
- // Update: It's okay to return FAIL in this case. CRITICAL_ERROR should
- // be avoided whenever OK and FAIL are sufficient by themselves, because
- // otherwise, callers can't use the NOT operator to detect if a function
- // failed (since FAIL is value zero, but CRITICAL_ERROR is non-zero):
- LineError(aErrorText, FAIL, aExtraInfo);
- return NULL; // Always return NULL because the callers use it as their return value.
- }
- #ifdef CONFIG_DEBUGGER
- LPCTSTR Debugger::WhatThrew()
- {
- // We want 'What' to indicate the function/sub/operation that *threw* the exception.
- // For BIFs, throwing is always explicit. For a UDF, 'What' should only name it if
- // it explicitly constructed the Exception object. This provides an easy way for
- // OnError and Catch to categorise errors. No information is lost because File/Line
- // can already be used locate the function/sub that was running.
- // So only return a name when a BIF is raising an error:
- if (mStack.mTop < mStack.mBottom || mStack.mTop->type != DbgStack::SE_BIF)
- return _T("");
- return mStack.mTop->func->mName;
- }
- #endif
- IObject *Line::CreateRuntimeException(LPCTSTR aErrorText, LPCTSTR aExtraInfo, Object *aPrototype)
- {
- // Build the parameters for Object::Create()
- ExprTokenType aParams[3]; int aParamCount = 2;
- ExprTokenType* aParam[3] { aParams + 0, aParams + 1, aParams + 2 };
- aParams[0].SetValue(const_cast<LPTSTR>(aErrorText));
- #ifdef CONFIG_DEBUGGER
- aParams[1].SetValue(const_cast<LPTSTR>(g_Debugger.WhatThrew()));
- #else
- // Without the debugger stack, there's no good way to determine what's throwing. It could be:
- //g_act[mActionType].Name; // A command implemented as an Action (g_act).
- //g->CurrentFunc->mName; // A user-defined function.
- //???; // A built-in function implemented as a Func (g_BIF).
- aParams[1].SetValue(_T(""), 0);
- #endif
- if (aExtraInfo && *aExtraInfo)
- aParams[aParamCount++].SetValue(const_cast<LPTSTR>(aExtraInfo));
- auto obj = Object::Create();
- if (!obj)
- return nullptr;
- if (!aPrototype)
- aPrototype = ErrorPrototype::Error;
- obj->SetBase(aPrototype);
- FuncResult rt;
- g_script.mCurrLine = this;
- g_script.mNewRuntimeException = obj;
- if (!obj->Construct(rt, aParam, aParamCount))
- obj = nullptr; // Construct released it.
- g_script.mNewRuntimeException = nullptr;
- return obj;
- }
- ResultType Line::ThrowRuntimeException(LPCTSTR aErrorText, LPCTSTR aExtraInfo)
- {
- return g_script.ThrowRuntimeException(aErrorText, aExtraInfo, this, FAIL);
- }
- ResultType Script::ThrowRuntimeException(LPCTSTR aErrorText, LPCTSTR aExtraInfo
- , Line *aLine, ResultType aErrorType, Object *aPrototype)
- {
- // ThrownToken should only be non-NULL while control is being passed up the
- // stack, which implies no script code can be executing.
- ASSERT(!g->ThrownToken);
- if (!aLine)
- aLine = mCurrLine;
- ResultToken *token;
- if ( !(token = new ResultToken)
- || !(token->object = aLine->CreateRuntimeException(aErrorText, aExtraInfo, aPrototype)) )
- {
- // Out of memory. It's likely that we were called for this very reason.
- // Since we don't even have enough memory to allocate an exception object,
- // just show an error message and exit the thread. Don't call LineError(),
- // since that would recurse into this function.
- if (token)
- delete token;
- if (!g->ThrownToken)
- {
- MsgBox(ERR_OUTOFMEM ERR_ABORT);
- return FAIL;
- }
- //else: Thrown by Error constructor?
- }
- else
- {
- token->symbol = SYM_OBJECT;
- token->mem_to_free = NULL;
- return aLine->SetThrownToken(*g, token, aErrorType);
- }
- // Returning FAIL causes each caller to also return FAIL, until either the
- // thread has fully exited or the recursion layer handling ACT_TRY is reached:
- return FAIL;
- }
- ResultType Script::ThrowRuntimeException(LPCTSTR aErrorText, LPCTSTR aExtraInfo)
- {
- return ThrowRuntimeException(aErrorText, aExtraInfo, mCurrLine, FAIL);
- }
- ResultType Line::SetThrownToken(global_struct &g, ResultToken *aToken, ResultType aErrorType)
- {
- #ifdef CONFIG_DEBUGGER
- if (g_Debugger.IsConnected())
- if (g_Debugger.IsAtBreak() || 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.
- // In our case, it seems more useful to suppress the dialog than to show it.
- g_script.FreeExceptionToken(aToken);
- return FAIL;
- }
- #endif
- g.ThrownToken = aToken;
- if (!(g.ExcptMode & EXCPTMODE_CATCH))
- return g_script.UnhandledException(this, aErrorType); // Usually returns FAIL; may return OK if aErrorType == FAIL_OR_OK.
- return FAIL;
- }
- BIF_DECL(BIF_Throw)
- {
- if (!aParamCount || aParam[aParamCount - 1]->symbol == SYM_MISSING)
- {
- if (g->ExcptMode & EXCPTMODE_CAUGHT) // Re-throw.
- _f_return_FAIL;
- _f_throw(ERR_EXCEPTION);
- }
- auto ¶m = *aParam[aParamCount - 1];
- ResultToken* token = new ResultToken;
- token->mem_to_free = nullptr;
- switch (param.symbol)
- {
- case SYM_OBJECT:
- token->SetValue(param.object);
- param.object->AddRef();
- break;
- case SYM_VAR:
- param.var->ToToken(*token);
- break;
- default:
- token->CopyValueFrom(param);
- }
- if (token->symbol == SYM_STRING && !token->Malloc(token->marker, token->marker_length))
- {
- delete token;
- _f_throw_oom;
- }
- // Throw() isn't made continuable in v2.1 because existing v2.0 code isn't
- // expected to deal with the possibility that the thread doesn't exit.
- g_script.mCurrLine->SetThrownToken(*g, token, FAIL);
- aResultToken.SetExitResult(FAIL);
- }
- ResultType Script::Win32Error(DWORD aError, ResultType aErrorType)
- {
- TCHAR number_string[_MAX_ULTOSTR_BASE10_COUNT];
- // Convert aError to string to pass it through RuntimeError, but it will ultimately
- // be converted to the error number and proper message by OSError.Prototype.__New.
- _ultot(aError, number_string, 10);
- return RuntimeError(number_string, _T(""), aErrorType, nullptr, ErrorPrototype::OS);
- }
- void Script::SetErrorStdOut(LPTSTR aParam)
- {
- mErrorStdOutCP = Line::ConvertFileEncoding(aParam);
- // Seems best not to print errors to stderr if the encoding was invalid. Current behaviour
- // for an encoding of -1 would be to print only the ASCII characters and drop the rest, but
- // if our caller is expecting UTF-16, it won't be readable.
- mErrorStdOut = mErrorStdOutCP != -1;
- // If invalid, no error is shown here because this function might be called early, before
- // Line::sSourceFile[0] is given its value. Instead, errors appearing as dialogs should
- // be a sufficient clue that the /ErrorStdOut= value was invalid.
- }
- void Script::PrintErrorStdOut(LPCTSTR aErrorText, int aLength, LPCTSTR aFile)
- {
- #ifdef CONFIG_DEBUGGER
- if (g_Debugger.OutputStdOut(aErrorText))
- return;
- #endif
- TextFile tf;
- tf.Open(aFile, TextStream::APPEND, mErrorStdOutCP);
- tf.Write(aErrorText, aLength);
- tf.Close();
- }
- int FormatStdErr(LPTSTR aBuf, int aBufSize, LPCTSTR aErrorText, LPCTSTR aExtraInfo, FileIndexType aFileIndex, LineNumberType aLineNumber, bool aWarn = false)
- {
- #define STD_ERROR_FORMAT _T("%s (%d) : ==> %s%s\n")
- int n = sntprintf(aBuf, aBufSize, STD_ERROR_FORMAT, Line::sSourceFile[aFileIndex], aLineNumber
- , aWarn ? _T("Warning: ") : _T(""), aErrorText);
- if (*aExtraInfo)
- n += sntprintf(aBuf + n, aBufSize - n, _T(" Specifically: %s\n"), aExtraInfo);
- return n;
- }
- // For backward compatibility, this actually prints to stderr, not stdout.
- void Script::PrintErrorStdOut(LPCTSTR aErrorText, LPCTSTR aExtraInfo, FileIndexType aFileIndex, LineNumberType aLineNumber)
- {
- TCHAR buf[LINE_SIZE * 2];
- auto n = FormatStdErr(buf, _countof(buf), aErrorText, aExtraInfo, aFileIndex, aLineNumber);
- PrintErrorStdOut(buf, n, _T("**"));
- }
- ResultType Line::LineError(LPCTSTR aErrorText, ResultType aErrorType, LPCTSTR aExtraInfo)
- {
- ASSERT(aErrorText);
- if (!aExtraInfo)
- aExtraInfo = _T("");
- if (g_script.mIsReadyToExecute)
- {
- return g_script.RuntimeError(aErrorText, aExtraInfo, aErrorType, this);
- }
-
- #ifdef CONFIG_DLL
- if (LibNotifyProblem(aErrorText, aExtraInfo, this))
- return aErrorType;
- #endif
-
- if (g_script.mErrorStdOut && aErrorType != WARN)
- {
- // JdeB said:
- // Just tested it in Textpad, Crimson and Scite. they all recognise the output and jump
- // to the Line containing the error when you double click the error line in the output
- // window (like it works in C++). Had to change the format of the line to:
- // printf("%s (%d) : ==> %s: \n%s \n%s\n",szInclude, nAutScriptLine, szText, szScriptLine, szOutput2 );
- // MY: Full filename is required, even if it's the main file, because some editors (EditPlus)
- // seem to rely on that to determine which file and line number to jump to when the user double-clicks
- // the error message in the output window.
- // v1.0.47: Added a space before the colon as originally intended. Toralf said, "With this minor
- // change the error lexer of Scite recognizes this line as a Microsoft error message and it can be
- // used to jump to that line."
- g_script.PrintErrorStdOut(aErrorText, aExtraInfo, mFileIndex, mLineNumber);
- return FAIL;
- }
- return g_script.ShowError(aErrorText, aErrorType, aExtraInfo, this);
- }
- ResultType Script::RuntimeError(LPCTSTR aErrorText, LPCTSTR aExtraInfo, ResultType aErrorType, Line *aLine, Object *aPrototype)
- {
- ASSERT(aErrorText);
- 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()
- #endif
- || aPrototype) && aErrorType != WARN)
- return ThrowRuntimeException(aErrorText, aExtraInfo, aLine, aErrorType, aPrototype);
-
- #ifdef CONFIG_DLL
- if (LibNotifyProblem(aErrorText, aExtraInfo, aLine))
- return aErrorType;
- #endif
-
- return ShowError(aErrorText, aErrorType, aExtraInfo, aLine);
- }
- FResult FError(LPCTSTR aErrorText, LPCTSTR aExtraInfo, Object *aPrototype)
- {
- return g_script.RuntimeError(aErrorText, aExtraInfo, FAIL_OR_OK, nullptr, aPrototype) ? FR_ABORTED : FR_FAIL;
- }
- struct ErrorBoxParam
- {
- LPCTSTR text;
- ResultType type;
- LPCTSTR info;
- Line *line;
- Object *obj;
- #ifdef CONFIG_DEBUGGER
- int stack_index;
- #endif
- };
- #ifdef CONFIG_DEBUGGER
- void InsertCallStack(HWND re, ErrorBoxParam &error)
- {
- TCHAR buf[SCRIPT_STACK_BUF_SIZE], *stack = _T("");
- if (error.obj && error.obj->IsOfType(Object::sPrototype))
- {
- auto obj = static_cast<Object*>(error.obj);
- if (auto temp = obj->GetOwnPropString(_T("Stack")))
- stack = temp;
- }
- else if (error.stack_index >= 0)
- {
- GetScriptStack(stack = buf, _countof(buf), g_Debugger.mStack.mBottom + error.stack_index);
- }
- CHARFORMAT cfBold;
- cfBold.cbSize = sizeof(cfBold);
- cfBold.dwMask = CFM_BOLD | CFM_LINK;
- cfBold.dwEffects = CFE_BOLD;
- SendMessage(re, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cfBold);
- SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)_T("Call stack:\n"));
- cfBold.dwEffects = 0;
- SendMessage(re, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cfBold);
- if (!*stack)
- return;
- // Prevent insertion of a blank line at the end (a bit pedantic, I know):
- auto stack_end = _tcschr(stack, '\0');
- if (stack_end[-1] == '\n')
- *--stack_end = '\0';
- if (stack_end > stack && stack_end[-1] == '\r')
- *--stack_end = '\0';
-
- CHARFORMAT cfLink;
- cfLink.cbSize = sizeof(cfLink);
- cfLink.dwMask = CFM_LINK | CFM_BOLD;
- cfLink.dwEffects = CFE_LINK;
- //cfLink.crTextColor = 0xbb4d00; // Has no effect on Windows 7 or 11 (even with CFM_COLOR).
- CHARRANGE cr;
- SendMessage(re, EM_EXGETSEL, 0, (LPARAM)&cr); // This will become the start position of the stack text.
- auto start_pos = cr.cpMin;
- SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)stack);
-
- for (auto cp = stack; ; )
- {
- if (auto ext = _tcsstr(cp, _T(".ahk (")))
- {
- // Apply CFE_LINK effect (and possibly colour) to the full path.
- cr.cpMax = cr.cpMin + int(ext - cp) + 4;
- SendMessage(re, EM_EXSETSEL, 0, (LPARAM)&cr);
- SendMessage(re, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cfLink);
- }
- auto cpn = _tcschr(cp, '\n');
- if (!cpn)
- break;
- cr.cpMin += int(cpn - cp);
- if (cpn == cp || cpn[-1] != '\r') // Count the \n only if \r wasn't already counted, since it seems RichEdit uses just \r internally.
- ++cr.cpMin;
- cp = cpn + 1;
- }
- cr.cpMin = cr.cpMax = start_pos - 1; // Remove selection.
- SendMessage(re, EM_EXSETSEL, 0, (LPARAM)&cr);
- }
- #endif
- void InitErrorBox(HWND hwnd, ErrorBoxParam &error)
- {
- TCHAR buf[1024];
- SetWindowText(hwnd, g_script.DefaultDialogTitle());
- SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)&error);
- HWND re = GetDlgItem(hwnd, IDC_ERR_EDIT);
- RECT rc, rcOffset {0,0,7,7};
- SendMessage(re, EM_GETRECT, 0, (LPARAM)&rc);
- MapDialogRect(hwnd, &rcOffset);
- rc.left += rcOffset.right;
- rc.top += rcOffset.bottom;
- rc.right -= rcOffset.right;
- rc.bottom -= rcOffset.bottom;
- SendMessage(re, EM_SETRECTNP, 0, (LPARAM)&rc);
- PARAFORMAT pf;
- pf.cbSize = sizeof(pf);
- pf.dwMask = PFM_TABSTOPS;
- pf.cTabCount = 1;
- pf.rgxTabs[0] = 300;
- SendMessage(re, EM_SETPARAFORMAT, 0, (LPARAM)&pf);
- SETTEXTEX t { ST_SELECTION | ST_UNICODE, CP_UTF16 };
- SETTEXTEX t_rtf { ST_SELECTION | ST_DEFAULT, CP_UTF8 };
- CHARFORMAT2 cf;
- cf.cbSize = sizeof(cf);
- cf.dwMask = CFM_SIZE;
- cf.yHeight = 9*20;
- SendMessage(re, EM_SETCHARFORMAT, SCF_DEFAULT, (LPARAM)&cf);
-
- cf.dwMask = CFM_SIZE | CFM_COLOR;
- cf.yHeight = 10*20;
- cf.crTextColor = 0x3399;
- cf.dwEffects = 0;
- SendMessage(re, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
-
- sntprintf(buf, _countof(buf), _T("%s: %.500s\n")
- , error.type == CRITICAL_ERROR ? _T("Critical Error")
- : error.type == WARN ? _T("Warning") : _T("Error")
- , error.text);
- auto cp = _tcschr(buf, '\n');
- if (auto c = *++cp) // Multiple lines in error.text.
- {
- // Insert a break *after* the \n so that formatting will revert to default afterward.
- *cp = '\0';
- SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)buf);
- *cp = c;
- }
- else
- cp = buf;
- SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)cp);
-
- bool file_needs_break = true;
- if (error.info && *error.info)
- {
- UINT suffix = _tcslen(error.info) > 80 ? 8230 : 0;
- if (file_needs_break = error.line || error.obj)
- sntprintf(buf, _countof(buf), _T("\nSpecifically: %.80s%s\n"), error.info, &suffix);
- else
- sntprintf(buf, _countof(buf), _T("\nText:\t%.80s%s\n"), error.info, &suffix);
- SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)buf);
- }
- if (error.line)
- {
- SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)_T("\n"));
- #define LINES_ABOVE_AND_BELOW 2
- // Determine the range of lines to be shown:
- Line *line_start = error.line, *line_end = error.line;
- if (g_AllowMainWindow)
- {
- for (int i = 0
- ; i < LINES_ABOVE_AND_BELOW && line_start->mPrevLine != NULL
- ; ++i, line_start = line_start->mPrevLine);
- for (int i = 0
- ; i < LINES_ABOVE_AND_BELOW && line_end->mNextLine != NULL
- ; ++i, line_end = line_end->mNextLine);
- }
- //else show only a single line, to conceal the script's source code.
- int last_file = 0; // Init to zero so path is omitted if it is the main file.
- for (auto line = line_start; ; line = line->mNextLine)
- {
- if (last_file != line->mFileIndex)
- {
- last_file = line->mFileIndex;
- sntprintf(buf, _countof(buf), _T("\t---- %s\n"), Line::sSourceFile[line->mFileIndex]);
- SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)buf);
- }
- int lead = 0;
- if (line == error.line)
- {
- cf.dwMask = CFM_COLOR | CFM_BACKCOLOR;
- cf.crTextColor = 0; // Use explicit black to ensure visibility if a high contrast theme is enabled.
- cf.crBackColor = 0x60ffff;
- SendMessage(re, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
- buf[lead++] = 9654; // ▶
- }
- buf[lead++] = '\t';
- buf[lead] = '\0';
- SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)buf);
- line->ToText(buf, _countof(buf), true);
- SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)buf);
- if (line == line_end)
- break;
- }
- }
- else
- {
- LPCTSTR file = nullptr;
- LineNumberType line = 0;
- if (error.obj)
- {
- file = error.obj->GetOwnPropString(_T("File"));
- line = (LineNumberType)error.obj->GetOwnPropInt64(_T("Line"));
- }
- else
- {
- file = g_script.CurrentFile();
- line = g_script.CurrentLine();
- }
- if (file && *file)
- {
- sntprintf(buf, _countof(buf), line ? _T("\nLine:\t%d\nFile:\t") : _T("\nFile: "), line);
- SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)(buf + !file_needs_break));
- cf.dwMask = CFM_LINK;
- cf.dwEffects = CFE_LINK; // Mark it as a link.
- SendMessage(re, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
- SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)file);
- SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)_T("\n"));
- }
- }
- LPCTSTR footer = error.obj ? error.obj->GetOwnPropString(_T("Hint")) : nullptr;
- if (footer) // Footer was specified.
- {
- if (!*footer) // Explicitly blank: omit default footer.
- footer = nullptr;
- }
- else // Use default footer for this error type, if any.
- {
- switch (error.type)
- {
- case WARN: footer = ERR_WARNING_FOOTER; break;
- case FAIL_OR_OK: break;
- case CRITICAL_ERROR: footer = UNSTABLE_WILL_EXIT; break;
- default: footer = (g->ExcptMode & EXCPTMODE_DELETE) ? ERR_ABORT_DELETE
- : g_script.mIsReadyToExecute ? ERR_ABORT_NO_SPACES
- : g_script.mIsRestart ? OLD_STILL_IN_EFFECT
- : WILL_EXIT;
- }
- }
- if (footer)
- {
- SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)_T("\n"));
- SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)footer);
- }
- #ifdef CONFIG_DEBUGGER
- LPCTSTR stack;
- if ( error.stack_index >= 0
- || error.obj && error.obj->IsOfType(Object::sPrototype)
- && (stack = error.obj->GetOwnPropString(_T("Stack"))) && *stack )
- {
- // Stack trace appears to be available, so add a link to show it.
- CHARRANGE cr;
- for (int i = footer ? 2 : 1; i; --i)
- SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)_T("\n"));
- SendMessage(re, EM_EXGETSEL, 0, (LPARAM)&cr);
- #define SHOW_CALL_STACK_TEXT _T("Show call stack »")
- SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)SHOW_CALL_STACK_TEXT);
- cr.cpMax = -1; // Select to end.
- SendMessage(re, EM_EXSETSEL, 0, (LPARAM)&cr);
- cf.dwMask = CFM_LINK;
- cf.dwEffects = CFE_LINK; // Mark it as a link.
- SendMessage(re, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
- cr.cpMin = -1; // Deselect (move selection anchor to insertion point).
- SendMessage(re, EM_EXSETSEL, 0, (LPARAM)&cr);
- }
- #endif
- SendMessage(re, EM_SETEVENTMASK, 0, ENM_REQUESTRESIZE | ENM_LINK | ENM_KEYEVENTS);
- SendMessage(re, EM_REQUESTRESIZE, 0, 0);
- #ifndef AUTOHOTKEYSC
- if (error.line && error.line->mFileIndex ? *Line::sSourceFile[error.line->mFileIndex] == '*'
- : g_script.mKind != Script::ScriptKindFile)
- // Source "file" is an embedded resource or stdin, so can't be edited.
- EnableWindow(GetDlgItem(hwnd, ID_FILE_EDITSCRIPT), FALSE);
- #endif
- if (error.type != FAIL_OR_OK)
- {
- HWND hide = GetDlgItem(hwnd, error.type == WARN ? IDCANCEL : IDCONTINUE);
- if (error.type == WARN)
- {
- // Hide "Abort" since it it's not applicable to warnings except as an alias of ExitApp,
- // shift "Continue" to the right for aesthetic purposes and make it the default button
- // (otherwise the left-most button would become the default).
- RECT rc;
- GetClientRect(hide, &rc);
- MapWindowPoints(hide, hwnd, (LPPOINT)&rc, 1);
- HWND keep = GetDlgItem(hwnd, IDCONTINUE);
- MoveWindow(keep, rc.left, rc.top, rc.right, rc.bottom, FALSE);
- DefDlgProc(hwnd, DM_SETDEFID, IDCONTINUE, 0);
- SetFocus(keep);
- }
- ShowWindow(hide, SW_HIDE);
- }
- }
- INT_PTR CALLBACK ErrorBoxProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
- {
- switch (msg)
- {
- case WM_COMMAND:
- switch (LOWORD(wParam))
- {
- case IDCONTINUE:
- case IDCANCEL:
- EndDialog(hwnd, wParam);
- return TRUE;
- case ID_FILE_EDITSCRIPT:
- {
- auto &error = *(ErrorBoxParam*)GetWindowLongPtr(hwnd, DWLP_USER);
- if (error.line)
- {
- g_script.Edit(Line::sSourceFile[error.line->mFileIndex]);
- return TRUE;
- }
- }
- default:
- if (LOWORD(wParam) >= ID_FILE_RELOADSCRIPT)
- {
- // Call the handler directly since g_hWnd might be NULL if this is a warning dialog.
- HandleMenuItem(NULL, LOWORD(wParam), NULL);
- if (LOWORD(wParam) == ID_FILE_RELOADSCRIPT)
- EndDialog(hwnd, IDCANCEL);
- return TRUE;
- }
- }
- break;
- case WM_NOTIFY:
- if (wParam == IDC_ERR_EDIT)
- {
- HWND re = ((NMHDR*)lParam)->hwndFrom;
- switch (((NMHDR*)lParam)->code)
- {
- case EN_MSGFILTER:
- {
- auto mf = (MSGFILTER*)lParam;
- if (mf->msg == WM_CHAR)
- // Forward it to any of the buttons so the dialog will process it as a mnemonic.
- PostMessage(GetDlgItem(hwnd, IDCANCEL), mf->msg, mf->wParam, mf->lParam);
- break;
- }
- case EN_REQUESTRESIZE: // Received when the RichEdit's content grows beyond its capacity to display all at once.
- {
- RECT &rcNew = ((REQRESIZE*)lParam)->rc;
- RECT rcOld, rcInner;
- GetWindowRect(re, &rcOld);
- SendMessage(re, EM_GETRECT, 0, (LPARAM)&rcInner);
- // Stack traces can get quite "tall" if the paths/lines are mostly short,
- // so impose a rough limit to ensure the dialog remains usable.
- int rough_limit = GetSystemMetrics(SM_CYSCREEN) * 3 / 4;
- if (rcNew.bottom > rough_limit)
- rcNew.bottom = rough_limit;
- int delta = rcNew.bottom - (rcInner.bottom - rcInner.top);
- if (rcNew.bottom == rough_limit)
- SendMessage(re, EM_SHOWSCROLLBAR, SB_VERT, TRUE);
- // Enable horizontal scroll bars if necessary.
- if (rcNew.right > (rcInner.right - rcInner.left)
- && !(GetWindowLong(re, GWL_STYLE) & WS_HSCROLL))
- {
- SendMessage(re, EM_SHOWSCROLLBAR, SB_HORZ, TRUE);
- delta += GetSystemMetrics(SM_CYHSCROLL);
- }
- // Move the buttons (and the RichEdit, temporarily).
- ScrollWindow(hwnd, 0, delta, NULL, NULL);
- // Resize the RichEdit, while also moving it back to the origin.
- MoveWindow(re, 0, 0, rcOld.right - rcOld.left, rcOld.bottom - rcOld.top + delta, TRUE);
- // Adjust the dialog's height and vertical position.
- GetWindowRect(hwnd, &rcOld);
- MoveWindow(hwnd, rcOld.left, rcOld.top - (delta / 2), rcOld.right - rcOld.left, rcOld.bottom - rcOld.top + delta, TRUE);
- break;
- }
- case EN_LINK: // Received when the user clicks or moves the mouse over text with the CFE_LINK effect.
- if (((ENLINK*)lParam)->msg == WM_LBUTTONUP)
- {
- TEXTRANGE tr { ((ENLINK*)lParam)->chrg };
- SendMessage(re, EM_EXSETSEL, 0, (LPARAM)&tr.chrg);
- tr.lpstrText = (LPTSTR)talloca(tr.chrg.cpMax - tr.chrg.cpMin + 1);
- *tr.lpstrText = '\0';
- SendMessage(re, EM_GETTEXTRANGE, 0, (LPARAM)&tr);
- PostMessage(hwnd, WM_NEXTDLGCTL, TRUE, FALSE); // Make it less edit-like by shifting focus away.
- #ifdef CONFIG_DEBUGGER
- if (!_tcscmp(tr.lpstrText, SHOW_CALL_STACK_TEXT))
- {
- auto &error = *(ErrorBoxParam*)GetWindowLongPtr(hwnd, DWLP_USER);
- InsertCallStack(re, error);
- return TRUE;
- }
- #endif
- g_script.Edit(tr.lpstrText);
- return TRUE;
- }
- break;
- }
- }
- break;
- case WM_INITDIALOG:
- InitErrorBox(hwnd, *(ErrorBoxParam *)lParam);
- return FALSE; // "return FALSE to prevent the system from setting the default keyboard focus"
- }
- return FALSE;
- }
- __declspec(noinline)
- ResultType Script::ShowError(LPCTSTR aErrorText, ResultType aErrorType, LPCTSTR aExtraInfo, Line *aLine)
- {
- // This overload reduces code size vs. using optional parameters since the the latter
- // works by the compiler implicitly inserting the default values in the compiled code.
- return ShowError(aErrorText, aErrorType, aExtraInfo, aLine, nullptr);
- }
- ResultType Script::ShowError(LPCTSTR aErrorText, ResultType aErrorType, LPCTSTR aExtraInfo, Line *aLine, Object *aException)
- {
- if (!aErrorText)
- aErrorText = _T("");
- if (!aExtraInfo)
- aExtraInfo = _T("");
- #ifdef CONFIG_DEBUGGER
- if (g_Debugger.HasStdErrHook())
- {
- TCHAR buf[LINE_SIZE * 2];
- Line *line = aLine ? aLine : mCurrLine;
- FormatStdErr(buf, _countof(buf), aErrorText, aExtraInfo
- , line ? line->mFileIndex : mCurrFileIndex
- , line ? line->mLineNumber : mCombinedLineNumber
- , aErrorType == WARN);
- g_Debugger.OutputStdErr(buf);
- }
- #endif
- static auto sMod = LoadLibrary(_T("riched20.dll")); // RichEdit20W
- //static auto sMod = LoadLibrary(_T("msftedit.dll")); // MSFTEDIT_CLASS (RICHEDIT50W)
- ErrorBoxParam error;
- error.text = aErrorText;
- error.type = aErrorType;
- error.info = aExtraInfo;
- error.line = aLine;
- error.obj = aException;
- #ifdef CONFIG_DEBUGGER
- error.stack_index = (aException || !g_script.mIsReadyToExecute) ? -1 : int(g_Debugger.mStack.mTop - g_Debugger.mStack.mBottom);
- #endif
- INT_PTR result = DialogBoxParam(NULL, MAKEINTRESOURCE(IDD_ERRORBOX), NULL, ErrorBoxProc, (LPARAM)&error);
- if (result == IDCONTINUE && aErrorType == FAIL_OR_OK)
- return OK;
- if (result == -1) // May have failed to show the custom dialog box.
- MsgBox(aErrorText, MB_TOPMOST); // Keep it simple since it will hopefully never be needed.
- if (aErrorType == CRITICAL_ERROR && mIsReadyToExecute)
- ExitApp(EXIT_CRITICAL); // Pass EXIT_CRITICAL to ensure the program always exits, regardless of OnExit.
- if (aErrorType == WARN && result == IDCANCEL && !mIsReadyToExecute) // Let Escape cancel loading the script.
- ExitApp(EXIT_EXIT);
- return FAIL; // Some callers rely on a FAIL result to propagate failure.
- }
- ResultType Script::ScriptError(LPCTSTR aErrorText, LPCTSTR aExtraInfo)
- // Even though this is a Script method, including it here since it shares
- // a common theme with the other error-displaying functions:
- {
- if (mCurrLine && g_script.mIsReadyToExecute)
- // If a line is available, do RuntimeError instead for exceptions and line context.
- return RuntimeError(aErrorText, aExtraInfo, FAIL, mCurrLine);
- // Otherwise: The fact that mCurrLine is NULL means that the line currently being loaded
- // has not yet been successfully added to the linked list. Such errors will always result
- // in the program exiting.
- if (!aErrorText)
- aErrorText = _T("Unk"); // Placeholder since it shouldn't be NULL.
- if (!aExtraInfo) // In case the caller explicitly called it with NULL.
- aExtraInfo = _T("");
-
- #ifdef CONFIG_DLL
- if (LibNotifyProblem(aErrorText, aExtraInfo, nullptr))
- return FAIL;
- #endif
-
- 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);
- }
- else
- {
- ShowError(aErrorText, FAIL, aExtraInfo, nullptr);
- }
- return FAIL; // See above for why it's better to return FAIL than CRITICAL_ERROR.
- }
- LPCTSTR VarKindForErrorMessage(Var *aVar)
- {
- switch (aVar->Type())
- {
- case VAR_VIRTUAL: return _T("built-in variable");
- case VAR_CONSTANT: return aVar->Object()->Type();
- default: return Var::DeclarationType(aVar->Scope());
- }
- }
- ResultType Script::ConflictingDeclarationError(LPCTSTR aDeclType, Var *aExisting)
- {
- TCHAR buf[127];
- sntprintf(buf, _countof(buf), _T("This %s declaration conflicts with an existing %s.")
- , aDeclType, VarKindForErrorMessage(aExisting));
- return ScriptError(buf, aExisting->mName);
- }
- ResultType Line::ValidateVarUsage(Var *aVar, int aUsage)
- {
- if (VARREF_IS_WRITE(aUsage) && aVar->IsReadOnly() && aUsage != VARREF_LVALUE_MAYBE)
- return VarIsReadOnlyError(aVar, aUsage);
- return OK;
- }
- __declspec(noinline)
- ResultType Script::VarIsReadOnlyError(Var *aVar, int aErrorType)
- {
- TCHAR buf[127];
- sntprintf(buf, _countof(buf), _T("This %s cannot %s.")
- , VarKindForErrorMessage(aVar)
- , aErrorType == VARREF_OUTPUT_VAR ? _T("be used as an output variable")
- : aErrorType == VARREF_REF ? _T("have its reference taken")
- : _T("be assigned a value"));
- return ScriptError(buf, aVar->mName);
- }
- ResultType Line::VarIsReadOnlyError(Var *aVar, int aErrorType)
- {
- g_script.mCurrLine = this;
- return g_script.VarIsReadOnlyError(aVar, aErrorType);
- }
- ResultType Line::LineUnexpectedError()
- {
- TCHAR buf[127];
- sntprintf(buf, _countof(buf), _T("Unexpected \"%s\""), g_act[mActionType].Name);
- return LineError(buf);
- }
- ResultType Script::CriticalError(LPCTSTR aErrorText, LPCTSTR aExtraInfo)
- {
- g->ExcptMode = EXCPTMODE_NONE; // Do not throw an exception.
- if (mCurrLine)
- mCurrLine->LineError(aErrorText, CRITICAL_ERROR, aExtraInfo);
- // mCurrLine should always be non-NULL during runtime, and CRITICAL_ERROR should
- // cause LineError() to exit even if an OnExit routine is present, so this is here
- // mainly for maintainability.
- TerminateApp(EXIT_CRITICAL, 0);
- return FAIL; // Never executed.
- }
- __declspec(noinline)
- ResultType ResultToken::Error(LPCTSTR aErrorText)
- {
- // Defining this overload separately rather than making aErrorInfo optional reduces code size
- // by not requiring the compiler to 'push' the second parameter's default value at each call site.
- return Error(aErrorText, _T(""));
- }
- __declspec(noinline)
- ResultType ResultToken::Error(LPCTSTR aErrorText, LPCTSTR aExtraInfo)
- {
- return Error(aErrorText, aExtraInfo, nullptr);
- }
- __declspec(noinline)
- ResultType ResultToken::Error(LPCTSTR aErrorText, Object *aPrototype)
- {
- return Error(aErrorText, nullptr, aPrototype);
- }
- __declspec(noinline)
- ResultType ResultToken::Error(LPCTSTR aErrorText, LPCTSTR aExtraInfo, Object *aPrototype)
- {
- // These two assertions should always pass, since anything else would imply returning a value,
- // not throwing an error. If they don't, the memory/object might not be freed since the caller
- // isn't expecting a value, or they might be freed twice (if the callee already freed it).
- //ASSERT(!mem_to_free); // At least one caller frees it after calling this function.
- ASSERT(symbol != SYM_OBJECT);
- return Fail(g_script.RuntimeError(aErrorText, aExtraInfo, FAIL_OR_OK, nullptr, aPrototype));
- }
- __declspec(noinline)
- ResultType ResultToken::Error(LPCTSTR aErrorText, ExprTokenType &aExtraInfo, Object *aPrototype)
- {
- TCHAR buf[MAX_NUMBER_SIZE];
- return Error(aErrorText, TokenToString(aExtraInfo, buf), aPrototype);
- }
- __declspec(noinline)
- ResultType ResultToken::MemoryError()
- {
- return Error(ERR_OUTOFMEM, nullptr, ErrorPrototype::Memory);
- }
- ResultType MemoryError()
- {
- return g_script.RuntimeError(ERR_OUTOFMEM, nullptr, FAIL, nullptr, ErrorPrototype::Memory);
- }
- void SimpleHeap::CriticalFail()
- {
- g_script.CriticalError(ERR_OUTOFMEM);
- }
- __declspec(noinline)
- ResultType ResultToken::ValueError(LPCTSTR aErrorText)
- {
- return Error(aErrorText, nullptr, ErrorPrototype::Value);
- }
- __declspec(noinline)
- ResultType ResultToken::ValueError(LPCTSTR aErrorText, LPCTSTR aExtraInfo)
- {
- return Error(aErrorText, aExtraInfo, ErrorPrototype::Value);
- }
- __declspec(noinline)
- ResultType ValueError(LPCTSTR aErrorText, LPCTSTR aExtraInfo, ResultType aErrorType)
- {
- if (!g_script.mIsReadyToExecute)
- return g_script.ScriptError(aErrorText, aExtraInfo);
- return g_script.RuntimeError(aErrorText, aExtraInfo, aErrorType, nullptr, ErrorPrototype::Value);
- }
- __declspec(noinline)
- FResult FValueError(LPCTSTR aErrorText, LPCTSTR aExtraInfo)
- {
- return FError(aErrorText, aExtraInfo, ErrorPrototype::Value);
- }
- __declspec(noinline)
- ResultType ResultToken::UnknownMemberError(ExprTokenType &aObject, int aFlags, LPCTSTR aMember)
- {
- TCHAR msg[512];
- if (!aMember)
- aMember = (aFlags & IT_CALL) ? _T("Call") : _T("__Item");
- sntprintf(msg, _countof(msg), _T("This value of type \"%s\" has no %s named \"%s\".")
- , TokenTypeString(aObject), (aFlags & IT_CALL) ? _T("method") : _T("property"), aMember);
- return Error(msg, nullptr, (aFlags & IT_CALL) ? ErrorPrototype::Method : ErrorPrototype::Property);
- }
- __declspec(noinline)
- ResultType ResultToken::Win32Error(DWORD aError)
- {
- if (g_script.Win32Error(aError) == FAIL)
- return SetExitResult(FAIL);
- SetValue(_T(""), 0);
- return FAIL;
- }
- void TokenTypeAndValue(ExprTokenType &aToken, LPCTSTR &aType, LPCTSTR &aValue, TCHAR *aNBuf)
- {
- if (aToken.symbol == SYM_VAR && aToken.var->IsUninitializedNormalVar())
- aType = _T("unset variable"), aValue = aToken.var->mName;
- else if (TokenIsEmptyString(aToken))
- aType = _T("empty string"), aValue = _T("");
- else
- aType = TokenTypeString(aToken), aValue = TokenToString(aToken, aNBuf);
- }
- __declspec(noinline)
- ResultType TypeError(LPCTSTR aExpectedType, ExprTokenType &aActualValue)
- {
- TCHAR number_buf[MAX_NUMBER_SIZE];
- LPCTSTR actual_type, value_as_string;
- TokenTypeAndValue(aActualValue, actual_type, value_as_string, number_buf);
- return TypeError(aExpectedType, actual_type, value_as_string);
- }
- ResultType TypeError(LPCTSTR aExpectedType, LPCTSTR aActualType, LPCTSTR aExtraInfo)
- {
- auto an = [](LPCTSTR thing) {
- return _tcschr(_T("aeiou"), ctolower(*thing)) ? _T("n") : _T("");
- };
- TCHAR msg[512];
- sntprintf(msg, _countof(msg), _T("Expected a%s %s but got a%s %s.")
- , an(aExpectedType), aExpectedType, an(aActualType), aActualType);
- return g_script.RuntimeError(msg, aExtraInfo, FAIL_OR_OK, nullptr, ErrorPrototype::Type);
- }
- ResultType ResultToken::TypeError(LPCTSTR aExpectedType, ExprTokenType &aActualValue)
- {
- return Fail(::TypeError(aExpectedType, aActualValue));
- }
- FResult FTypeError(LPCTSTR aExpectedType, ExprTokenType &aActualValue)
- {
- return TypeError(aExpectedType, aActualValue) == OK ? FR_ABORTED : FR_FAIL;
- }
- __declspec(noinline)
- ResultType ParamError(int aIndex, ExprTokenType *aParam, LPCTSTR aExpectedType, LPCTSTR aFunction)
- {
- auto an = [](LPCTSTR thing) {
- return _tcschr(_T("aeiou"), ctolower(*thing)) ? _T("n") : _T("");
- };
- TCHAR msg[512];
- TCHAR number_buf[MAX_NUMBER_SIZE];
- LPCTSTR actual_type, value_as_string;
- #ifdef CONFIG_DEBUGGER
- if (!aFunction)
- aFunction = g_Debugger.WhatThrew();
- #endif
- if (!aParam || aParam->symbol == SYM_MISSING)
- {
- #ifdef CONFIG_DEBUGGER
- sntprintf(msg, _countof(msg), _T("Parameter #%i of %s must not be omitted in this case.")
- , aIndex + 1, aFunction);
- #else
- sntprintf(msg, _countof(msg), _T("Parameter #%i must not be omitted in this case.")
- , aIndex + 1);
- #endif
- return g_script.RuntimeError(msg, nullptr, FAIL_OR_OK, nullptr, ErrorPrototype::Value);
- }
- TokenTypeAndValue(*aParam, actual_type, value_as_string, number_buf);
- if (!*value_as_string && !aExpectedType)
- value_as_string = actual_type;
- #ifdef CONFIG_DEBUGGER
- if (aExpectedType)
- sntprintf(msg, _countof(msg), _T("Parameter #%i of %s requires a%s %s, but received a%s %s.")
- , aIndex + 1, aFunction, an(aExpectedType), aExpectedType, an(actual_type), actual_type);
- else
- sntprintf(msg, _countof(msg), _T("Parameter #%i of %s is invalid."), aIndex + 1, g_Debugger.WhatThrew());
- #else
- if (aExpectedType)
- sntprintf(msg, _countof(msg), _T("Parameter #%i requires a%s %s, but received a%s %s.")
- , aIndex + 1, an(aExpectedType), aExpectedType, an(actual_type), actual_type);
- else
- sntprintf(msg, _countof(msg), _T("Parameter #%i invalid."), aIndex + 1);
- #endif
- return g_script.RuntimeError(msg, value_as_string, FAIL_OR_OK, nullptr
- , aExpectedType ? ErrorPrototype::Type : ErrorPrototype::Value);
- }
- __declspec(noinline)
- ResultType ResultToken::ParamError(int aIndex, ExprTokenType *aParam)
- {
- return Fail(::ParamError(aIndex, aParam, nullptr, nullptr));
- }
- __declspec(noinline)
- ResultType ResultToken::ParamError(int aIndex, ExprTokenType *aParam, LPCTSTR aExpectedType)
- {
- return Fail(::ParamError(aIndex, aParam, aExpectedType, nullptr));
- }
- __declspec(noinline)
- ResultType ResultToken::ParamError(int aIndex, ExprTokenType *aParam, LPCTSTR aExpectedType, LPCTSTR aFunction)
- {
- return Fail(::ParamError(aIndex, aParam, aExpectedType, aFunction));
- }
- FResult FParamError(int aIndex, ExprTokenType *aParam, LPCTSTR aExpectedType)
- {
- return ::ParamError(aIndex, aParam, aExpectedType, nullptr) == OK ? FR_ABORTED : FR_FAIL;
- }
- ResultType FResultToError(ResultToken &aResultToken, ExprTokenType *aParam[], int aParamCount, FResult aResult, int aFirstParam)
- {
- if (aResult & FR_OUR_FLAG)
- {
- if (aResult & FR_INT_FLAG)
- {
- // This is a bit of a hack and should probably be revised.
- TCHAR buf[12];
- return aResultToken.Error(ERR_FAILED, _itot(FR_GET_THROWN_INT(aResult), buf, 10));
- }
- auto code = HRESULT_CODE(aResult);
- switch (HRESULT_FACILITY(aResult))
- {
- case FR_FACILITY_CONTROL:
- ASSERT(!code);
- return aResultToken.SetExitResult(FAIL);
- case FR_FACILITY_ARG:
- if (aResult == FR_E_ARGS)
- return aResultToken.Error(ERR_PARAM_INVALID);
- return aResultToken.ParamError(code, code + aFirstParam < aParamCount ? aParam[code + aFirstParam] : nullptr);
- case FACILITY_WIN32:
- if (!code)
- code = GetLastError();
- return aResultToken.Win32Error(code);
- #ifndef _DEBUG
- default: // Using a default case may slightly reduce code size.
- #endif
- case FR_FACILITY_ERR:
- switch (code)
- {
- case HRESULT_CODE(FR_E_OUTOFMEM):
- return aResultToken.MemoryError();
- }
- }
- ASSERT(aResult == FR_E_FAILED); // Alert for any unhandled error codes in debug mode.
- return aResultToken.Error(ERR_FAILED);
- }
- else // Presumably a HRESULT error value.
- {
- return aResultToken.Win32Error(aResult);
- }
- }
- __declspec(noinline)
- ResultType Script::UnhandledException(Line* aLine, ResultType aErrorType)
- {
- global_struct &g = *::g;
-
- ResultToken *token = g.ThrownToken;
- // Clear ThrownToken to allow any applicable callbacks to execute correctly.
- // This includes OnError callbacks explicitly called below, but also COM events
- // and CallbackCreate callbacks that execute while MsgBox() is waiting.
- g.ThrownToken = NULL;
- // OnError: Allow the script to handle it via a global callback.
- static bool sOnErrorRunning = false;
- if (mOnError.Count() && !sOnErrorRunning)
- {
- __int64 retval;
- sOnErrorRunning = true;
- ExprTokenType param[2];
- param[0].CopyValueFrom(*token);
- param[1].SetValue(aErrorType == CRITICAL_ERROR ? _T("ExitApp")
- : aErrorType == FAIL_OR_OK ? _T("Return") : _T("Exit"));
- mOnError.Call(param, 2, INT_MAX, &retval);
- sOnErrorRunning = false;
- if (g.ThrownToken) // An exception was thrown by the callback.
- {
- // UnhandledException() has already been called recursively for g.ThrownToken,
- // so don't show a second error message. This allows `throw param1` to mean
- // "abort all OnError callbacks and show default message now".
- FreeExceptionToken(token);
- return FAIL;
- }
- if (retval < 0 && aErrorType == FAIL_OR_OK)
- {
- FreeExceptionToken(token);
- return OK; // Ignore error and continue.
- }
- // Some callers rely on g.ThrownToken!=NULL to unwind the stack, so it is restored
- // rather than freeing it immediately. If the exception object has __Delete, it
- // will be called after the stack unwinds.
- if (retval)
- {
- g.ThrownToken = token;
- return FAIL; // Exit thread.
- }
- }
-
- #ifdef CONFIG_DLL
- if (LibNotifyProblem(*token))
- {
- g.ThrownToken = token; // See comments above.
- return FAIL;
- }
- #endif
- if (ShowError(aLine, aErrorType, token) == OK)
- {
- FreeExceptionToken(token);
- return OK;
- }
- g.ThrownToken = token;
- return FAIL;
- }
- ResultType Script::ShowError(Line* aLine, ResultType aErrorType, ExprTokenType *token)
- {
- LPCTSTR message = _T(""), extra = _T("");
- TCHAR extra_buf[MAX_NUMBER_SIZE], message_buf[MAX_NUMBER_SIZE];
-
- Object *ex = dynamic_cast<Object *>(TokenToObject(*token));
- if (ex)
- {
- // For simplicity and safety, we call into the Object directly rather than via Invoke().
- ExprTokenType t;
- if (ex->GetOwnProp(t, _T("Message")))
- message = TokenToString(t, message_buf);
- if (ex->GetOwnProp(t, _T("Extra")))
- extra = TokenToString(t, extra_buf);
- LPCTSTR file = ex->GetOwnPropString(_T("File"));
- LineNumberType line_no = (LineNumberType)ex->GetOwnPropInt64(_T("Line"));
- if (file && *file && line_no)
- {
- // Locate the line by number and file index, then display that line instead
- // of the caller supplied one since it's probably more relevant.
- int file_index;
- for (file_index = 0; file_index < Line::sSourceFileCount; ++file_index)
- if (!_tcsicmp(file, Line::sSourceFile[file_index]))
- break;
- if (!aLine || aLine->mFileIndex != file_index || aLine->mLineNumber != line_no) // Keep aLine if it matches, in case of multiple Lines with the same number.
- {
- Line *line;
- for (line = mFirstLine;
- line && (line->mLineNumber != line_no || line->mFileIndex != file_index
- || !line->mArgc && line->mNextLine && line->mNextLine->mLineNumber == line_no); // Skip any same-line block-begin/end, try, else or finally.
- line = line->mNextLine);
- if (line)
- aLine = line;
- }
- }
- }
- else
- {
- // Assume it's a string or number.
- extra = TokenToString(*token, message_buf);
- }
- // If message is empty (or a string or number was thrown), display a default message for clarity.
- if (!*message)
- message = _T("Unhandled exception.");
- return ShowError(message, aErrorType, extra, aLine, ex);
- }
- void Object::Error_Show(ResultToken &aResultToken, int aID, int aFlags, ExprTokenType *aParam[], int aParamCount)
- {
- ResultType type = FAIL_OR_OK;
- if (aParamCount && aParam[0]->symbol != SYM_MISSING)
- {
- static LPCTSTR sKindName[] { _T("Return"), _T("Exit"), _T("ExitApp"), _T("Warn") };
- static ResultType sKindType[] { FAIL_OR_OK, FAIL, CRITICAL_ERROR, WARN };
- auto kind = TokenToString(*aParam[0]);
- for (int i = 0;; ++i)
- {
- if (i == _countof(sKindName))
- _f_throw_param(0);
- if (!_tcsicmp(kind, sKindName[i]))
- {
- type = sKindType[i];
- break;
- }
- }
- }
- ExprTokenType t_this(this);
- _o_return(g_script.ShowError(nullptr, type, &t_this) == OK ? -1 : 1);
- }
- void Script::FreeExceptionToken(ResultToken*& aToken)
- {
- // Release any potential content the token may hold
- aToken->Free();
- // Free the token itself.
- delete aToken;
- // Clear caller's variable.
- aToken = NULL;
- }
- bool Line::CatchThis(ExprTokenType &aThrown) // ACT_CATCH
- {
- auto args = (CatchStatementArgs *)mAttribute;
- if (!args || !args->prototype_count)
- return Object::HasBase(aThrown, ErrorPrototype::Error);
- for (int i = 0; i < args->prototype_count; ++i)
- if (Object::HasBase(aThrown, args->prototype[i]))
- return true;
- return false;
- }
- void Script::ScriptWarning(WarnMode warnMode, LPCTSTR aWarningText, LPCTSTR aExtraInfo, Line *line)
- {
- if (!line) line = mCurrLine;
- int fileIndex = line ? line->mFileIndex : mCurrFileIndex;
- FileIndexType lineNumber = line ? line->mLineNumber : mCombinedLineNumber;
-
- #ifdef CONFIG_DLL
- if (LibNotifyProblem(aWarningText, aExtraInfo, line, true))
- return;
- #endif
-
- if (warnMode == WARNMODE_OFF)
- return;
- TCHAR buf[MSGBOX_TEXT_SIZE];
- auto n = FormatStdErr(buf, _countof(buf), aWarningText, aExtraInfo, fileIndex, lineNumber, true);
- if (warnMode == WARNMODE_STDOUT)
- PrintErrorStdOut(buf, n);
- else
- #ifdef CONFIG_DEBUGGER
- if (!g_Debugger.OutputStdErr(buf))
- #endif
- OutputDebugString(buf);
- // In MsgBox mode, MsgBox is in addition to OutputDebug
- if (warnMode == WARNMODE_MSGBOX)
- {
- g_script.ShowError(aWarningText, WARN, aExtraInfo, line);
- }
- }
- void Script::WarnUnassignedVar(Var *var, Line *aLine)
- {
- auto warnMode = g_Warn_VarUnset;
- if (!warnMode)
- 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
- // var which is checked with IsSet().
- //if (warnMode == WARNMODE_MSGBOX)
- {
- // The following check uses a flag separate to IsAssignedSomewhere() because setting
- // that one for the purpose of preventing multiple MsgBoxes would cause other callers
- // of IsAssignedSomewhere() to get the wrong result if a MsgBox has been shown.
- if (var->HasAlreadyWarned())
- return;
- 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);
- }
- ResultType Script::VarUnsetError(Var *var)
- {
- 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);
- }
- void Script::WarnLocalSameAsGlobal(LPCTSTR aVarName)
- // Relies on the following pre-conditions:
- // 1) It is an implicit (not declared) variable.
- // 2) Caller has verified that a global variable exists with the same name.
- // 3) g_Warn_LocalSameAsGlobal is on (true).
- // 4) g->CurrentFunc is the function which contains this variable.
- {
- auto func_name = g->CurrentFunc ? g->CurrentFunc->mName : _T("");
- TCHAR buf[DIALOG_TITLE_SIZE];
- sntprintf(buf, _countof(buf), _T("%s (in function %s)"), aVarName, func_name);
- ScriptWarning(g_Warn_LocalSameAsGlobal, WARNING_LOCAL_SAME_AS_GLOBAL, buf);
- }
|