error.cpp 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394
  1. /*
  2. AutoHotkey
  3. Copyright 2003-2009 Chris Mallett (support@autohotkey.com)
  4. This program is free software; you can redistribute it and/or
  5. modify it under the terms of the GNU General Public License
  6. as published by the Free Software Foundation; either version 2
  7. of the License, or (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. */
  13. #include "stdafx.h"
  14. #include "script.h"
  15. #include "globaldata.h"
  16. #include "window.h"
  17. #include "TextIO.h"
  18. #include "abi.h"
  19. #include <richedit.h>
  20. ResultType Line::PreparseError(LPTSTR aErrorText, LPTSTR aExtraInfo)
  21. {
  22. return LineError(aErrorText, FAIL, aExtraInfo);
  23. }
  24. #ifdef CONFIG_DEBUGGER
  25. LPCTSTR Debugger::WhatThrew()
  26. {
  27. // We want 'What' to indicate the function/sub/operation that *threw* the exception.
  28. // For BIFs, throwing is always explicit. For a UDF, 'What' should only name it if
  29. // it explicitly constructed the Exception object. This provides an easy way for
  30. // OnError and Catch to categorise errors. No information is lost because File/Line
  31. // can already be used locate the function/sub that was running.
  32. // So only return a name when a BIF is raising an error:
  33. if (mStack.mTop < mStack.mBottom || mStack.mTop->type != DbgStack::SE_BIF)
  34. return _T("");
  35. return mStack.mTop->func->mName;
  36. }
  37. #endif
  38. IObject *Line::CreateRuntimeException(LPCTSTR aErrorText, LPCTSTR aExtraInfo, Object *aPrototype)
  39. {
  40. // Build the parameters for Object::Create()
  41. ExprTokenType aParams[3]; int aParamCount = 2;
  42. ExprTokenType* aParam[3] { aParams + 0, aParams + 1, aParams + 2 };
  43. aParams[0].SetValue(const_cast<LPTSTR>(aErrorText));
  44. #ifdef CONFIG_DEBUGGER
  45. aParams[1].SetValue(const_cast<LPTSTR>(g_Debugger.WhatThrew()));
  46. #else
  47. // Without the debugger stack, there's no good way to determine what's throwing. It could be:
  48. //g_act[mActionType].Name; // A command implemented as an Action (g_act).
  49. //g->CurrentFunc->mName; // A user-defined function.
  50. //???; // A built-in function implemented as a Func (g_BIF).
  51. aParams[1].SetValue(_T(""), 0);
  52. #endif
  53. if (aExtraInfo && *aExtraInfo)
  54. aParams[aParamCount++].SetValue(const_cast<LPTSTR>(aExtraInfo));
  55. auto obj = Object::Create();
  56. if (!obj)
  57. return nullptr;
  58. if (!aPrototype)
  59. aPrototype = ErrorPrototype::Error;
  60. obj->SetBase(aPrototype);
  61. FuncResult rt;
  62. g_script.mCurrLine = this;
  63. g_script.mNewRuntimeException = obj;
  64. if (!obj->Construct(rt, aParam, aParamCount))
  65. obj = nullptr; // Construct released it.
  66. g_script.mNewRuntimeException = nullptr;
  67. return obj;
  68. }
  69. ResultType Line::ThrowRuntimeException(LPCTSTR aErrorText, LPCTSTR aExtraInfo)
  70. {
  71. return g_script.ThrowRuntimeException(aErrorText, aExtraInfo, this, FAIL);
  72. }
  73. ResultType Script::ThrowRuntimeException(LPCTSTR aErrorText, LPCTSTR aExtraInfo
  74. , Line *aLine, ResultType aErrorType, Object *aPrototype)
  75. {
  76. // ThrownToken should only be non-NULL while control is being passed up the
  77. // stack, which implies no script code can be executing.
  78. ASSERT(!g->ThrownToken);
  79. if (!aLine)
  80. aLine = mCurrLine;
  81. ResultToken *token;
  82. if ( !(token = new ResultToken)
  83. || !(token->object = aLine->CreateRuntimeException(aErrorText, aExtraInfo, aPrototype)) )
  84. {
  85. // Out of memory. It's likely that we were called for this very reason.
  86. // Since we don't even have enough memory to allocate an exception object,
  87. // just show an error message and exit the thread. Don't call LineError(),
  88. // since that would recurse into this function.
  89. if (token)
  90. delete token;
  91. if (!g->ThrownToken)
  92. {
  93. MsgBox(ERR_OUTOFMEM ERR_ABORT);
  94. return FAIL;
  95. }
  96. //else: Thrown by Error constructor?
  97. }
  98. else
  99. {
  100. token->symbol = SYM_OBJECT;
  101. token->mem_to_free = NULL;
  102. return aLine->SetThrownToken(*g, token, aErrorType);
  103. }
  104. // Returning FAIL causes each caller to also return FAIL, until either the
  105. // thread has fully exited or the recursion layer handling ACT_TRY is reached:
  106. return FAIL;
  107. }
  108. ResultType Script::ThrowRuntimeException(LPCTSTR aErrorText, LPCTSTR aExtraInfo)
  109. {
  110. return ThrowRuntimeException(aErrorText, aExtraInfo, mCurrLine, FAIL);
  111. }
  112. ResultType Line::SetThrownToken(global_struct &g, ResultToken *aToken, ResultType aErrorType)
  113. {
  114. #ifdef CONFIG_DEBUGGER
  115. if (g_Debugger.IsConnected())
  116. if (g_Debugger.PreThrow(aToken) && !(g.ExcptMode & EXCPTMODE_CATCH))
  117. {
  118. // The debugger has entered (and left) a break state, so the client has had a
  119. // chance to inspect the exception and report it. There's nothing in the DBGp
  120. // spec about what to do next, probably since PHP would just log the error.
  121. // In our case, it seems more useful to suppress the dialog than to show it.
  122. g_script.FreeExceptionToken(aToken);
  123. return FAIL;
  124. }
  125. #endif
  126. g.ThrownToken = aToken;
  127. if (!(g.ExcptMode & EXCPTMODE_CATCH))
  128. return g_script.UnhandledException(this, aErrorType); // Usually returns FAIL; may return OK if aErrorType == FAIL_OR_OK.
  129. return FAIL;
  130. }
  131. BIF_DECL(BIF_Throw)
  132. {
  133. if (!aParamCount || aParam[aParamCount - 1]->symbol == SYM_MISSING)
  134. {
  135. if (g->ExcptMode & EXCPTMODE_CAUGHT) // Re-throw.
  136. _f_return_FAIL;
  137. _f_throw(ERR_EXCEPTION);
  138. }
  139. auto &param = *aParam[aParamCount - 1];
  140. ResultToken* token = new ResultToken;
  141. token->mem_to_free = nullptr;
  142. switch (param.symbol)
  143. {
  144. case SYM_OBJECT:
  145. token->SetValue(param.object);
  146. param.object->AddRef();
  147. break;
  148. case SYM_VAR:
  149. param.var->ToToken(*token);
  150. break;
  151. default:
  152. token->CopyValueFrom(param);
  153. }
  154. if (token->symbol == SYM_STRING && !token->Malloc(token->marker, token->marker_length))
  155. {
  156. delete token;
  157. _f_throw_oom;
  158. }
  159. // Throw() isn't made continuable in v2.1 because existing v2.0 code isn't
  160. // expected to deal with the possibility that the thread doesn't exit.
  161. g_script.mCurrLine->SetThrownToken(*g, token, FAIL);
  162. aResultToken.SetExitResult(FAIL);
  163. }
  164. ResultType Script::Win32Error(DWORD aError, ResultType aErrorType)
  165. {
  166. TCHAR number_string[_MAX_ULTOSTR_BASE10_COUNT];
  167. // Convert aError to string to pass it through RuntimeError, but it will ultimately
  168. // be converted to the error number and proper message by OSError.Prototype.__New.
  169. _ultot(aError, number_string, 10);
  170. return RuntimeError(number_string, _T(""), aErrorType, nullptr, ErrorPrototype::OS);
  171. }
  172. void Script::SetErrorStdOut(LPTSTR aParam)
  173. {
  174. mErrorStdOutCP = Line::ConvertFileEncoding(aParam);
  175. // Seems best not to print errors to stderr if the encoding was invalid. Current behaviour
  176. // for an encoding of -1 would be to print only the ASCII characters and drop the rest, but
  177. // if our caller is expecting UTF-16, it won't be readable.
  178. mErrorStdOut = mErrorStdOutCP != -1;
  179. // If invalid, no error is shown here because this function might be called early, before
  180. // Line::sSourceFile[0] is given its value. Instead, errors appearing as dialogs should
  181. // be a sufficient clue that the /ErrorStdOut= value was invalid.
  182. }
  183. void Script::PrintErrorStdOut(LPCTSTR aErrorText, int aLength, LPCTSTR aFile)
  184. {
  185. #ifdef CONFIG_DEBUGGER
  186. if (g_Debugger.OutputStdOut(aErrorText))
  187. return;
  188. #endif
  189. TextFile tf;
  190. tf.Open(aFile, TextStream::APPEND, mErrorStdOutCP);
  191. tf.Write(aErrorText, aLength);
  192. tf.Close();
  193. }
  194. int FormatStdErr(LPTSTR aBuf, int aBufSize, LPCTSTR aErrorText, LPCTSTR aExtraInfo, FileIndexType aFileIndex, LineNumberType aLineNumber, bool aWarn = false)
  195. {
  196. #define STD_ERROR_FORMAT _T("%s (%d) : ==> %s%s\n")
  197. int n = sntprintf(aBuf, aBufSize, STD_ERROR_FORMAT, Line::sSourceFile[aFileIndex], aLineNumber
  198. , aWarn ? _T("Warning: ") : _T(""), aErrorText);
  199. if (*aExtraInfo)
  200. n += sntprintf(aBuf + n, aBufSize - n, _T(" Specifically: %s\n"), aExtraInfo);
  201. return n;
  202. }
  203. // For backward compatibility, this actually prints to stderr, not stdout.
  204. void Script::PrintErrorStdOut(LPCTSTR aErrorText, LPCTSTR aExtraInfo, FileIndexType aFileIndex, LineNumberType aLineNumber)
  205. {
  206. TCHAR buf[LINE_SIZE * 2];
  207. auto n = FormatStdErr(buf, _countof(buf), aErrorText, aExtraInfo, aFileIndex, aLineNumber);
  208. PrintErrorStdOut(buf, n, _T("**"));
  209. }
  210. ResultType Line::LineError(LPCTSTR aErrorText, ResultType aErrorType, LPCTSTR aExtraInfo)
  211. {
  212. ASSERT(aErrorText);
  213. if (!aExtraInfo)
  214. aExtraInfo = _T("");
  215. if (g_script.mIsReadyToExecute)
  216. {
  217. return g_script.RuntimeError(aErrorText, aExtraInfo, aErrorType, this);
  218. }
  219. #ifdef CONFIG_DLL
  220. if (LibNotifyProblem(aErrorText, aExtraInfo, this))
  221. return aErrorType;
  222. #endif
  223. if (g_script.mErrorStdOut && aErrorType != WARN)
  224. {
  225. // JdeB said:
  226. // Just tested it in Textpad, Crimson and Scite. they all recognise the output and jump
  227. // to the Line containing the error when you double click the error line in the output
  228. // window (like it works in C++). Had to change the format of the line to:
  229. // printf("%s (%d) : ==> %s: \n%s \n%s\n",szInclude, nAutScriptLine, szText, szScriptLine, szOutput2 );
  230. // MY: Full filename is required, even if it's the main file, because some editors (EditPlus)
  231. // seem to rely on that to determine which file and line number to jump to when the user double-clicks
  232. // the error message in the output window.
  233. // v1.0.47: Added a space before the colon as originally intended. Toralf said, "With this minor
  234. // change the error lexer of Scite recognizes this line as a Microsoft error message and it can be
  235. // used to jump to that line."
  236. g_script.PrintErrorStdOut(aErrorText, aExtraInfo, mFileIndex, mLineNumber);
  237. return FAIL;
  238. }
  239. return g_script.ShowError(aErrorText, aErrorType, aExtraInfo, this);
  240. }
  241. ResultType Script::RuntimeError(LPCTSTR aErrorText, LPCTSTR aExtraInfo, ResultType aErrorType, Line *aLine, Object *aPrototype)
  242. {
  243. ASSERT(aErrorText);
  244. if (!aExtraInfo)
  245. aExtraInfo = _T("");
  246. if ((g->ExcptMode || mOnError.Count()
  247. #ifdef CONFIG_DEBUGGER
  248. || g_Debugger.BreakOnExceptionIsEnabled()
  249. #endif
  250. || aPrototype) && aErrorType != WARN)
  251. return ThrowRuntimeException(aErrorText, aExtraInfo, aLine, aErrorType, aPrototype);
  252. #ifdef CONFIG_DLL
  253. if (LibNotifyProblem(aErrorText, aExtraInfo, aLine))
  254. return aErrorType;
  255. #endif
  256. return ShowError(aErrorText, aErrorType, aExtraInfo, aLine);
  257. }
  258. FResult FError(LPCTSTR aErrorText, LPCTSTR aExtraInfo, Object *aPrototype)
  259. {
  260. return g_script.RuntimeError(aErrorText, aExtraInfo, FAIL_OR_OK, nullptr, aPrototype) ? FR_ABORTED : FR_FAIL;
  261. }
  262. struct ErrorBoxParam
  263. {
  264. LPCTSTR text;
  265. ResultType type;
  266. LPCTSTR info;
  267. Line *line;
  268. Object *obj;
  269. #ifdef CONFIG_DEBUGGER
  270. int stack_index;
  271. #endif
  272. };
  273. #ifdef CONFIG_DEBUGGER
  274. void InsertCallStack(HWND re, ErrorBoxParam &error)
  275. {
  276. TCHAR buf[SCRIPT_STACK_BUF_SIZE], *stack = _T("");
  277. if (error.obj && error.obj->IsOfType(Object::sPrototype))
  278. {
  279. auto obj = static_cast<Object*>(error.obj);
  280. if (auto temp = obj->GetOwnPropString(_T("Stack")))
  281. stack = temp;
  282. }
  283. else if (error.stack_index >= 0)
  284. {
  285. GetScriptStack(stack = buf, _countof(buf), g_Debugger.mStack.mBottom + error.stack_index);
  286. }
  287. CHARFORMAT cfBold;
  288. cfBold.cbSize = sizeof(cfBold);
  289. cfBold.dwMask = CFM_BOLD | CFM_LINK;
  290. cfBold.dwEffects = CFE_BOLD;
  291. SendMessage(re, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cfBold);
  292. SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)_T("Call stack:\n"));
  293. cfBold.dwEffects = 0;
  294. SendMessage(re, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cfBold);
  295. if (!*stack)
  296. return;
  297. // Prevent insertion of a blank line at the end (a bit pedantic, I know):
  298. auto stack_end = _tcschr(stack, '\0');
  299. if (stack_end[-1] == '\n')
  300. *--stack_end = '\0';
  301. if (stack_end > stack && stack_end[-1] == '\r')
  302. *--stack_end = '\0';
  303. CHARFORMAT cfLink;
  304. cfLink.cbSize = sizeof(cfLink);
  305. cfLink.dwMask = CFM_LINK | CFM_BOLD;
  306. cfLink.dwEffects = CFE_LINK;
  307. //cfLink.crTextColor = 0xbb4d00; // Has no effect on Windows 7 or 11 (even with CFM_COLOR).
  308. CHARRANGE cr;
  309. SendMessage(re, EM_EXGETSEL, 0, (LPARAM)&cr); // This will become the start position of the stack text.
  310. auto start_pos = cr.cpMin;
  311. SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)stack);
  312. for (auto cp = stack; ; )
  313. {
  314. if (auto ext = _tcsstr(cp, _T(".ahk (")))
  315. {
  316. // Apply CFE_LINK effect (and possibly colour) to the full path.
  317. cr.cpMax = cr.cpMin + int(ext - cp) + 4;
  318. SendMessage(re, EM_EXSETSEL, 0, (LPARAM)&cr);
  319. SendMessage(re, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cfLink);
  320. }
  321. auto cpn = _tcschr(cp, '\n');
  322. if (!cpn)
  323. break;
  324. cr.cpMin += int(cpn - cp);
  325. if (cpn == cp || cpn[-1] != '\r') // Count the \n only if \r wasn't already counted, since it seems RichEdit uses just \r internally.
  326. ++cr.cpMin;
  327. cp = cpn + 1;
  328. }
  329. cr.cpMin = cr.cpMax = start_pos - 1; // Remove selection.
  330. SendMessage(re, EM_EXSETSEL, 0, (LPARAM)&cr);
  331. }
  332. #endif
  333. void InitErrorBox(HWND hwnd, ErrorBoxParam &error)
  334. {
  335. TCHAR buf[1024];
  336. SetWindowText(hwnd, g_script.DefaultDialogTitle());
  337. SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)&error);
  338. HWND re = GetDlgItem(hwnd, IDC_ERR_EDIT);
  339. RECT rc, rcOffset {0,0,7,7};
  340. SendMessage(re, EM_GETRECT, 0, (LPARAM)&rc);
  341. MapDialogRect(hwnd, &rcOffset);
  342. rc.left += rcOffset.right;
  343. rc.top += rcOffset.bottom;
  344. rc.right -= rcOffset.right;
  345. rc.bottom -= rcOffset.bottom;
  346. SendMessage(re, EM_SETRECTNP, 0, (LPARAM)&rc);
  347. PARAFORMAT pf;
  348. pf.cbSize = sizeof(pf);
  349. pf.dwMask = PFM_TABSTOPS;
  350. pf.cTabCount = 1;
  351. pf.rgxTabs[0] = 300;
  352. SendMessage(re, EM_SETPARAFORMAT, 0, (LPARAM)&pf);
  353. SETTEXTEX t { ST_SELECTION | ST_UNICODE, CP_UTF16 };
  354. SETTEXTEX t_rtf { ST_SELECTION | ST_DEFAULT, CP_UTF8 };
  355. CHARFORMAT2 cf;
  356. cf.cbSize = sizeof(cf);
  357. cf.dwMask = CFM_SIZE;
  358. cf.yHeight = 9*20;
  359. SendMessage(re, EM_SETCHARFORMAT, SCF_DEFAULT, (LPARAM)&cf);
  360. cf.dwMask = CFM_SIZE | CFM_COLOR;
  361. cf.yHeight = 10*20;
  362. cf.crTextColor = 0x3399;
  363. cf.dwEffects = 0;
  364. SendMessage(re, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
  365. sntprintf(buf, _countof(buf), _T("%s: %.500s\n")
  366. , error.type == CRITICAL_ERROR ? _T("Critical Error")
  367. : error.type == WARN ? _T("Warning") : _T("Error")
  368. , error.text);
  369. auto cp = _tcschr(buf, '\n');
  370. if (auto c = *++cp) // Multiple lines in error.text.
  371. {
  372. // Insert a break *after* the \n so that formatting will revert to default afterward.
  373. *cp = '\0';
  374. SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)buf);
  375. *cp = c;
  376. }
  377. else
  378. cp = buf;
  379. SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)cp);
  380. bool file_needs_break = true;
  381. if (error.info && *error.info)
  382. {
  383. UINT suffix = _tcslen(error.info) > 80 ? 8230 : 0;
  384. if (file_needs_break = error.line || error.obj)
  385. sntprintf(buf, _countof(buf), _T("\nSpecifically: %.80s%s\n"), error.info, &suffix);
  386. else
  387. sntprintf(buf, _countof(buf), _T("\nText:\t%.80s%s\n"), error.info, &suffix);
  388. SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)buf);
  389. }
  390. if (error.line)
  391. {
  392. SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)_T("\n"));
  393. #define LINES_ABOVE_AND_BELOW 2
  394. // Determine the range of lines to be shown:
  395. Line *line_start = error.line, *line_end = error.line;
  396. if (g_AllowMainWindow)
  397. {
  398. for (int i = 0
  399. ; i < LINES_ABOVE_AND_BELOW && line_start->mPrevLine != NULL
  400. ; ++i, line_start = line_start->mPrevLine);
  401. for (int i = 0
  402. ; i < LINES_ABOVE_AND_BELOW && line_end->mNextLine != NULL
  403. ; ++i, line_end = line_end->mNextLine);
  404. }
  405. //else show only a single line, to conceal the script's source code.
  406. int last_file = 0; // Init to zero so path is omitted if it is the main file.
  407. for (auto line = line_start; ; line = line->mNextLine)
  408. {
  409. if (last_file != line->mFileIndex)
  410. {
  411. last_file = line->mFileIndex;
  412. sntprintf(buf, _countof(buf), _T("\t---- %s\n"), Line::sSourceFile[line->mFileIndex]);
  413. SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)buf);
  414. }
  415. int lead = 0;
  416. if (line == error.line)
  417. {
  418. cf.dwMask = CFM_COLOR | CFM_BACKCOLOR;
  419. cf.crTextColor = 0; // Use explicit black to ensure visibility if a high contrast theme is enabled.
  420. cf.crBackColor = 0x60ffff;
  421. SendMessage(re, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
  422. buf[lead++] = 9654; // ▶
  423. }
  424. buf[lead++] = '\t';
  425. buf[lead] = '\0';
  426. SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)buf);
  427. line->ToText(buf, _countof(buf), true);
  428. SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)buf);
  429. if (line == line_end)
  430. break;
  431. }
  432. }
  433. else
  434. {
  435. LPCTSTR file = nullptr;
  436. LineNumberType line = 0;
  437. if (error.obj)
  438. {
  439. file = error.obj->GetOwnPropString(_T("File"));
  440. line = (LineNumberType)error.obj->GetOwnPropInt64(_T("Line"));
  441. }
  442. else
  443. {
  444. file = g_script.CurrentFile();
  445. line = g_script.CurrentLine();
  446. }
  447. if (file && *file)
  448. {
  449. sntprintf(buf, _countof(buf), line ? _T("\nLine:\t%d\nFile:\t") : _T("\nFile: "), line);
  450. SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)(buf + !file_needs_break));
  451. cf.dwMask = CFM_LINK;
  452. cf.dwEffects = CFE_LINK; // Mark it as a link.
  453. SendMessage(re, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
  454. SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)file);
  455. SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)_T("\n"));
  456. }
  457. }
  458. LPCTSTR footer = error.obj ? error.obj->GetOwnPropString(_T("Hint")) : nullptr;
  459. if (footer) // Footer was specified.
  460. {
  461. if (!*footer) // Explicitly blank: omit default footer.
  462. footer = nullptr;
  463. }
  464. else // Use default footer for this error type, if any.
  465. {
  466. switch (error.type)
  467. {
  468. case WARN: footer = ERR_WARNING_FOOTER; break;
  469. case FAIL_OR_OK: break;
  470. case CRITICAL_ERROR: footer = UNSTABLE_WILL_EXIT; break;
  471. default: footer = (g->ExcptMode & EXCPTMODE_DELETE) ? ERR_ABORT_DELETE
  472. : g_script.mIsReadyToExecute ? ERR_ABORT_NO_SPACES
  473. : g_script.mIsRestart ? OLD_STILL_IN_EFFECT
  474. : WILL_EXIT;
  475. }
  476. }
  477. if (footer)
  478. {
  479. SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)_T("\n"));
  480. SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)footer);
  481. }
  482. #ifdef CONFIG_DEBUGGER
  483. LPCTSTR stack;
  484. if ( error.stack_index >= 0
  485. || error.obj && error.obj->IsOfType(Object::sPrototype)
  486. && (stack = error.obj->GetOwnPropString(_T("Stack"))) && *stack )
  487. {
  488. // Stack trace appears to be available, so add a link to show it.
  489. CHARRANGE cr;
  490. for (int i = footer ? 2 : 1; i; --i)
  491. SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)_T("\n"));
  492. SendMessage(re, EM_EXGETSEL, 0, (LPARAM)&cr);
  493. #define SHOW_CALL_STACK_TEXT _T("Show call stack »")
  494. SendMessage(re, EM_REPLACESEL, FALSE, (LPARAM)SHOW_CALL_STACK_TEXT);
  495. cr.cpMax = -1; // Select to end.
  496. SendMessage(re, EM_EXSETSEL, 0, (LPARAM)&cr);
  497. cf.dwMask = CFM_LINK;
  498. cf.dwEffects = CFE_LINK; // Mark it as a link.
  499. SendMessage(re, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
  500. cr.cpMin = -1; // Deselect (move selection anchor to insertion point).
  501. SendMessage(re, EM_EXSETSEL, 0, (LPARAM)&cr);
  502. }
  503. #endif
  504. SendMessage(re, EM_SETEVENTMASK, 0, ENM_REQUESTRESIZE | ENM_LINK | ENM_KEYEVENTS);
  505. SendMessage(re, EM_REQUESTRESIZE, 0, 0);
  506. #ifndef AUTOHOTKEYSC
  507. if (error.line && error.line->mFileIndex ? *Line::sSourceFile[error.line->mFileIndex] == '*'
  508. : g_script.mKind != Script::ScriptKindFile)
  509. // Source "file" is an embedded resource or stdin, so can't be edited.
  510. EnableWindow(GetDlgItem(hwnd, ID_FILE_EDITSCRIPT), FALSE);
  511. #endif
  512. if (error.type != FAIL_OR_OK)
  513. {
  514. HWND hide = GetDlgItem(hwnd, error.type == WARN ? IDCANCEL : IDCONTINUE);
  515. if (error.type == WARN)
  516. {
  517. // Hide "Abort" since it it's not applicable to warnings except as an alias of ExitApp,
  518. // shift "Continue" to the right for aesthetic purposes and make it the default button
  519. // (otherwise the left-most button would become the default).
  520. RECT rc;
  521. GetClientRect(hide, &rc);
  522. MapWindowPoints(hide, hwnd, (LPPOINT)&rc, 1);
  523. HWND keep = GetDlgItem(hwnd, IDCONTINUE);
  524. MoveWindow(keep, rc.left, rc.top, rc.right, rc.bottom, FALSE);
  525. DefDlgProc(hwnd, DM_SETDEFID, IDCONTINUE, 0);
  526. SetFocus(keep);
  527. }
  528. ShowWindow(hide, SW_HIDE);
  529. }
  530. }
  531. INT_PTR CALLBACK ErrorBoxProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
  532. {
  533. switch (msg)
  534. {
  535. case WM_COMMAND:
  536. switch (LOWORD(wParam))
  537. {
  538. case IDCONTINUE:
  539. case IDCANCEL:
  540. EndDialog(hwnd, wParam);
  541. return TRUE;
  542. case ID_FILE_EDITSCRIPT:
  543. {
  544. auto &error = *(ErrorBoxParam*)GetWindowLongPtr(hwnd, DWLP_USER);
  545. if (error.line)
  546. {
  547. g_script.Edit(Line::sSourceFile[error.line->mFileIndex]);
  548. return TRUE;
  549. }
  550. }
  551. default:
  552. if (LOWORD(wParam) >= ID_FILE_RELOADSCRIPT)
  553. {
  554. // Call the handler directly since g_hWnd might be NULL if this is a warning dialog.
  555. HandleMenuItem(NULL, LOWORD(wParam), NULL);
  556. if (LOWORD(wParam) == ID_FILE_RELOADSCRIPT)
  557. EndDialog(hwnd, IDCANCEL);
  558. return TRUE;
  559. }
  560. }
  561. break;
  562. case WM_NOTIFY:
  563. if (wParam == IDC_ERR_EDIT)
  564. {
  565. HWND re = ((NMHDR*)lParam)->hwndFrom;
  566. switch (((NMHDR*)lParam)->code)
  567. {
  568. case EN_MSGFILTER:
  569. {
  570. auto mf = (MSGFILTER*)lParam;
  571. if (mf->msg == WM_CHAR)
  572. // Forward it to any of the buttons so the dialog will process it as a mnemonic.
  573. PostMessage(GetDlgItem(hwnd, IDCANCEL), mf->msg, mf->wParam, mf->lParam);
  574. break;
  575. }
  576. case EN_REQUESTRESIZE: // Received when the RichEdit's content grows beyond its capacity to display all at once.
  577. {
  578. RECT &rcNew = ((REQRESIZE*)lParam)->rc;
  579. RECT rcOld, rcInner;
  580. GetWindowRect(re, &rcOld);
  581. SendMessage(re, EM_GETRECT, 0, (LPARAM)&rcInner);
  582. // Stack traces can get quite "tall" if the paths/lines are mostly short,
  583. // so impose a rough limit to ensure the dialog remains usable.
  584. int rough_limit = GetSystemMetrics(SM_CYSCREEN) * 3 / 4;
  585. if (rcNew.bottom > rough_limit)
  586. rcNew.bottom = rough_limit;
  587. int delta = rcNew.bottom - (rcInner.bottom - rcInner.top);
  588. if (rcNew.bottom == rough_limit)
  589. SendMessage(re, EM_SHOWSCROLLBAR, SB_VERT, TRUE);
  590. // Enable horizontal scroll bars if necessary.
  591. if (rcNew.right > (rcInner.right - rcInner.left)
  592. && !(GetWindowLong(re, GWL_STYLE) & WS_HSCROLL))
  593. {
  594. SendMessage(re, EM_SHOWSCROLLBAR, SB_HORZ, TRUE);
  595. delta += GetSystemMetrics(SM_CYHSCROLL);
  596. }
  597. // Move the buttons (and the RichEdit, temporarily).
  598. ScrollWindow(hwnd, 0, delta, NULL, NULL);
  599. // Resize the RichEdit, while also moving it back to the origin.
  600. MoveWindow(re, 0, 0, rcOld.right - rcOld.left, rcOld.bottom - rcOld.top + delta, TRUE);
  601. // Adjust the dialog's height and vertical position.
  602. GetWindowRect(hwnd, &rcOld);
  603. MoveWindow(hwnd, rcOld.left, rcOld.top - (delta / 2), rcOld.right - rcOld.left, rcOld.bottom - rcOld.top + delta, TRUE);
  604. break;
  605. }
  606. case EN_LINK: // Received when the user clicks or moves the mouse over text with the CFE_LINK effect.
  607. if (((ENLINK*)lParam)->msg == WM_LBUTTONUP)
  608. {
  609. TEXTRANGE tr { ((ENLINK*)lParam)->chrg };
  610. SendMessage(re, EM_EXSETSEL, 0, (LPARAM)&tr.chrg);
  611. tr.lpstrText = (LPTSTR)talloca(tr.chrg.cpMax - tr.chrg.cpMin + 1);
  612. *tr.lpstrText = '\0';
  613. SendMessage(re, EM_GETTEXTRANGE, 0, (LPARAM)&tr);
  614. PostMessage(hwnd, WM_NEXTDLGCTL, TRUE, FALSE); // Make it less edit-like by shifting focus away.
  615. #ifdef CONFIG_DEBUGGER
  616. if (!_tcscmp(tr.lpstrText, SHOW_CALL_STACK_TEXT))
  617. {
  618. auto &error = *(ErrorBoxParam*)GetWindowLongPtr(hwnd, DWLP_USER);
  619. InsertCallStack(re, error);
  620. return TRUE;
  621. }
  622. #endif
  623. g_script.Edit(tr.lpstrText);
  624. return TRUE;
  625. }
  626. break;
  627. }
  628. }
  629. break;
  630. case WM_INITDIALOG:
  631. InitErrorBox(hwnd, *(ErrorBoxParam *)lParam);
  632. return FALSE; // "return FALSE to prevent the system from setting the default keyboard focus"
  633. }
  634. return FALSE;
  635. }
  636. __declspec(noinline)
  637. ResultType Script::ShowError(LPCTSTR aErrorText, ResultType aErrorType, LPCTSTR aExtraInfo, Line *aLine)
  638. {
  639. // This overload reduces code size vs. using optional parameters since the the latter
  640. // works by the compiler implicitly inserting the default values in the compiled code.
  641. return ShowError(aErrorText, aErrorType, aExtraInfo, aLine, nullptr);
  642. }
  643. ResultType Script::ShowError(LPCTSTR aErrorText, ResultType aErrorType, LPCTSTR aExtraInfo, Line *aLine, Object *aException)
  644. {
  645. if (!aErrorText)
  646. aErrorText = _T("");
  647. if (!aExtraInfo)
  648. aExtraInfo = _T("");
  649. #ifdef CONFIG_DEBUGGER
  650. if (g_Debugger.HasStdErrHook())
  651. {
  652. TCHAR buf[LINE_SIZE * 2];
  653. Line *line = aLine ? aLine : mCurrLine;
  654. FormatStdErr(buf, _countof(buf), aErrorText, aExtraInfo
  655. , line ? line->mFileIndex : mCurrFileIndex
  656. , line ? line->mLineNumber : mCombinedLineNumber
  657. , aErrorType == WARN);
  658. g_Debugger.OutputStdErr(buf);
  659. }
  660. #endif
  661. static auto sMod = LoadLibrary(_T("riched20.dll")); // RichEdit20W
  662. //static auto sMod = LoadLibrary(_T("msftedit.dll")); // MSFTEDIT_CLASS (RICHEDIT50W)
  663. ErrorBoxParam error;
  664. error.text = aErrorText;
  665. error.type = aErrorType;
  666. error.info = aExtraInfo;
  667. error.line = aLine;
  668. error.obj = aException;
  669. #ifdef CONFIG_DEBUGGER
  670. error.stack_index = (aException || !g_script.mIsReadyToExecute) ? -1 : int(g_Debugger.mStack.mTop - g_Debugger.mStack.mBottom);
  671. #endif
  672. INT_PTR result = DialogBoxParam(NULL, MAKEINTRESOURCE(IDD_ERRORBOX), NULL, ErrorBoxProc, (LPARAM)&error);
  673. if (result == IDCONTINUE && aErrorType == FAIL_OR_OK)
  674. return OK;
  675. if (result == -1) // May have failed to show the custom dialog box.
  676. MsgBox(aErrorText, MB_TOPMOST); // Keep it simple since it will hopefully never be needed.
  677. if (aErrorType == CRITICAL_ERROR && mIsReadyToExecute)
  678. ExitApp(EXIT_CRITICAL); // Pass EXIT_CRITICAL to ensure the program always exits, regardless of OnExit.
  679. if (aErrorType == WARN && result == IDCANCEL && !mIsReadyToExecute) // Let Escape cancel loading the script.
  680. ExitApp(EXIT_EXIT);
  681. return FAIL; // Some callers rely on a FAIL result to propagate failure.
  682. }
  683. ResultType Script::ScriptError(LPCTSTR aErrorText, LPCTSTR aExtraInfo)
  684. // Even though this is a Script method, including it here since it shares
  685. // a common theme with the other error-displaying functions:
  686. {
  687. if (mCurrLine && g_script.mIsReadyToExecute)
  688. // If a line is available, do RuntimeError instead for exceptions and line context.
  689. return RuntimeError(aErrorText, aExtraInfo, FAIL, mCurrLine);
  690. // Otherwise: The fact that mCurrLine is NULL means that the line currently being loaded
  691. // has not yet been successfully added to the linked list. Such errors will always result
  692. // in the program exiting.
  693. if (!aErrorText)
  694. aErrorText = _T("Unk"); // Placeholder since it shouldn't be NULL.
  695. if (!aExtraInfo) // In case the caller explicitly called it with NULL.
  696. aExtraInfo = _T("");
  697. #ifdef CONFIG_DLL
  698. if (LibNotifyProblem(aErrorText, aExtraInfo, nullptr))
  699. return FAIL;
  700. #endif
  701. if (g_script.mErrorStdOut && !g_script.mIsReadyToExecute) // i.e. runtime errors are always displayed via dialog.
  702. {
  703. // See LineError() for details.
  704. PrintErrorStdOut(aErrorText, aExtraInfo, mCurrLine ? mCurrLine->mFileIndex : mCurrFileIndex, CurrentLine());
  705. }
  706. else
  707. {
  708. ShowError(aErrorText, FAIL, aExtraInfo, nullptr);
  709. }
  710. return FAIL; // See above for why it's better to return FAIL than CRITICAL_ERROR.
  711. }
  712. LPCTSTR VarKindForErrorMessage(Var *aVar)
  713. {
  714. switch (aVar->Type())
  715. {
  716. case VAR_VIRTUAL: return _T("built-in variable");
  717. case VAR_CONSTANT: return aVar->Object()->Type();
  718. default: return Var::DeclarationType(aVar->Scope());
  719. }
  720. }
  721. ResultType Script::ConflictingDeclarationError(LPCTSTR aDeclType, Var *aExisting)
  722. {
  723. TCHAR buf[127];
  724. sntprintf(buf, _countof(buf), _T("This %s declaration conflicts with an existing %s.")
  725. , aDeclType, VarKindForErrorMessage(aExisting));
  726. return ScriptError(buf, aExisting->mName);
  727. }
  728. ResultType Line::ValidateVarUsage(Var *aVar, int aUsage)
  729. {
  730. if (VARREF_IS_WRITE(aUsage) && aVar->IsReadOnly() && aUsage != VARREF_LVALUE_MAYBE)
  731. return VarIsReadOnlyError(aVar, aUsage);
  732. return OK;
  733. }
  734. __declspec(noinline)
  735. ResultType Script::VarIsReadOnlyError(Var *aVar, int aErrorType)
  736. {
  737. TCHAR buf[127];
  738. sntprintf(buf, _countof(buf), _T("This %s cannot %s.")
  739. , VarKindForErrorMessage(aVar)
  740. , aErrorType == VARREF_OUTPUT_VAR ? _T("be used as an output variable")
  741. : aErrorType == VARREF_REF ? _T("have its reference taken")
  742. : _T("be assigned a value"));
  743. return ScriptError(buf, aVar->mName);
  744. }
  745. ResultType Line::VarIsReadOnlyError(Var *aVar, int aErrorType)
  746. {
  747. g_script.mCurrLine = this;
  748. return g_script.VarIsReadOnlyError(aVar, aErrorType);
  749. }
  750. ResultType Line::LineUnexpectedError()
  751. {
  752. TCHAR buf[127];
  753. sntprintf(buf, _countof(buf), _T("Unexpected \"%s\""), g_act[mActionType].Name);
  754. return LineError(buf);
  755. }
  756. ResultType Script::CriticalError(LPCTSTR aErrorText, LPCTSTR aExtraInfo)
  757. {
  758. g->ExcptMode = EXCPTMODE_NONE; // Do not throw an exception.
  759. if (mCurrLine)
  760. mCurrLine->LineError(aErrorText, CRITICAL_ERROR, aExtraInfo);
  761. // mCurrLine should always be non-NULL during runtime, and CRITICAL_ERROR should
  762. // cause LineError() to exit even if an OnExit routine is present, so this is here
  763. // mainly for maintainability.
  764. TerminateApp(EXIT_CRITICAL, 0);
  765. return FAIL; // Never executed.
  766. }
  767. __declspec(noinline)
  768. ResultType ResultToken::Error(LPCTSTR aErrorText)
  769. {
  770. // Defining this overload separately rather than making aErrorInfo optional reduces code size
  771. // by not requiring the compiler to 'push' the second parameter's default value at each call site.
  772. return Error(aErrorText, _T(""));
  773. }
  774. __declspec(noinline)
  775. ResultType ResultToken::Error(LPCTSTR aErrorText, LPCTSTR aExtraInfo)
  776. {
  777. return Error(aErrorText, aExtraInfo, nullptr);
  778. }
  779. __declspec(noinline)
  780. ResultType ResultToken::Error(LPCTSTR aErrorText, Object *aPrototype)
  781. {
  782. return Error(aErrorText, nullptr, aPrototype);
  783. }
  784. __declspec(noinline)
  785. ResultType ResultToken::Error(LPCTSTR aErrorText, LPCTSTR aExtraInfo, Object *aPrototype)
  786. {
  787. // These two assertions should always pass, since anything else would imply returning a value,
  788. // not throwing an error. If they don't, the memory/object might not be freed since the caller
  789. // isn't expecting a value, or they might be freed twice (if the callee already freed it).
  790. //ASSERT(!mem_to_free); // At least one caller frees it after calling this function.
  791. ASSERT(symbol != SYM_OBJECT);
  792. return Fail(g_script.RuntimeError(aErrorText, aExtraInfo, FAIL_OR_OK, nullptr, aPrototype));
  793. }
  794. __declspec(noinline)
  795. ResultType ResultToken::Error(LPCTSTR aErrorText, ExprTokenType &aExtraInfo, Object *aPrototype)
  796. {
  797. TCHAR buf[MAX_NUMBER_SIZE];
  798. return Error(aErrorText, TokenToString(aExtraInfo, buf), aPrototype);
  799. }
  800. __declspec(noinline)
  801. ResultType ResultToken::MemoryError()
  802. {
  803. return Error(ERR_OUTOFMEM, nullptr, ErrorPrototype::Memory);
  804. }
  805. ResultType MemoryError()
  806. {
  807. return g_script.RuntimeError(ERR_OUTOFMEM, nullptr, FAIL, nullptr, ErrorPrototype::Memory);
  808. }
  809. void SimpleHeap::CriticalFail()
  810. {
  811. g_script.CriticalError(ERR_OUTOFMEM);
  812. }
  813. __declspec(noinline)
  814. ResultType ResultToken::ValueError(LPCTSTR aErrorText)
  815. {
  816. return Error(aErrorText, nullptr, ErrorPrototype::Value);
  817. }
  818. __declspec(noinline)
  819. ResultType ResultToken::ValueError(LPCTSTR aErrorText, LPCTSTR aExtraInfo)
  820. {
  821. return Error(aErrorText, aExtraInfo, ErrorPrototype::Value);
  822. }
  823. __declspec(noinline)
  824. ResultType ValueError(LPCTSTR aErrorText, LPCTSTR aExtraInfo, ResultType aErrorType)
  825. {
  826. if (!g_script.mIsReadyToExecute)
  827. return g_script.ScriptError(aErrorText, aExtraInfo);
  828. return g_script.RuntimeError(aErrorText, aExtraInfo, aErrorType, nullptr, ErrorPrototype::Value);
  829. }
  830. __declspec(noinline)
  831. FResult FValueError(LPCTSTR aErrorText, LPCTSTR aExtraInfo)
  832. {
  833. return FError(aErrorText, aExtraInfo, ErrorPrototype::Value);
  834. }
  835. __declspec(noinline)
  836. ResultType ResultToken::UnknownMemberError(ExprTokenType &aObject, int aFlags, LPCTSTR aMember)
  837. {
  838. TCHAR msg[512];
  839. if (!aMember)
  840. aMember = (aFlags & IT_CALL) ? _T("Call") : _T("__Item");
  841. sntprintf(msg, _countof(msg), _T("This value of type \"%s\" has no %s named \"%s\".")
  842. , TokenTypeString(aObject), (aFlags & IT_CALL) ? _T("method") : _T("property"), aMember);
  843. return Error(msg, nullptr, (aFlags & IT_CALL) ? ErrorPrototype::Method : ErrorPrototype::Property);
  844. }
  845. __declspec(noinline)
  846. ResultType ResultToken::Win32Error(DWORD aError)
  847. {
  848. if (g_script.Win32Error(aError) == FAIL)
  849. return SetExitResult(FAIL);
  850. SetValue(_T(""), 0);
  851. return FAIL;
  852. }
  853. void TokenTypeAndValue(ExprTokenType &aToken, LPCTSTR &aType, LPCTSTR &aValue, TCHAR *aNBuf)
  854. {
  855. if (aToken.symbol == SYM_VAR && aToken.var->IsUninitializedNormalVar())
  856. aType = _T("unset variable"), aValue = aToken.var->mName;
  857. else if (TokenIsEmptyString(aToken))
  858. aType = _T("empty string"), aValue = _T("");
  859. else
  860. aType = TokenTypeString(aToken), aValue = TokenToString(aToken, aNBuf);
  861. }
  862. __declspec(noinline)
  863. ResultType TypeError(LPCTSTR aExpectedType, ExprTokenType &aActualValue)
  864. {
  865. TCHAR number_buf[MAX_NUMBER_SIZE];
  866. LPCTSTR actual_type, value_as_string;
  867. if (aActualValue.symbol == SYM_VAR && aActualValue.var->IsUninitializedNormalVar())
  868. return g_script.VarUnsetError(aActualValue.var);
  869. TokenTypeAndValue(aActualValue, actual_type, value_as_string, number_buf);
  870. return TypeError(aExpectedType, actual_type, value_as_string);
  871. }
  872. ResultType TypeError(LPCTSTR aExpectedType, LPCTSTR aActualType, LPCTSTR aExtraInfo)
  873. {
  874. auto an = [](LPCTSTR thing) {
  875. return _tcschr(_T("aeiou"), ctolower(*thing)) ? _T("n") : _T("");
  876. };
  877. TCHAR msg[512];
  878. sntprintf(msg, _countof(msg), _T("Expected a%s %s but got a%s %s.")
  879. , an(aExpectedType), aExpectedType, an(aActualType), aActualType);
  880. return g_script.RuntimeError(msg, aExtraInfo, FAIL_OR_OK, nullptr, ErrorPrototype::Type);
  881. }
  882. ResultType ResultToken::TypeError(LPCTSTR aExpectedType, ExprTokenType &aActualValue)
  883. {
  884. return Fail(::TypeError(aExpectedType, aActualValue));
  885. }
  886. FResult FTypeError(LPCTSTR aExpectedType, ExprTokenType &aActualValue)
  887. {
  888. return TypeError(aExpectedType, aActualValue) == OK ? FR_ABORTED : FR_FAIL;
  889. }
  890. __declspec(noinline)
  891. ResultType ParamError(int aIndex, ExprTokenType *aParam, LPCTSTR aExpectedType, LPCTSTR aFunction)
  892. {
  893. auto an = [](LPCTSTR thing) {
  894. return _tcschr(_T("aeiou"), ctolower(*thing)) ? _T("n") : _T("");
  895. };
  896. TCHAR msg[512];
  897. TCHAR number_buf[MAX_NUMBER_SIZE];
  898. LPCTSTR actual_type, value_as_string;
  899. #ifdef CONFIG_DEBUGGER
  900. if (!aFunction)
  901. aFunction = g_Debugger.WhatThrew();
  902. #endif
  903. if (!aParam || aParam->symbol == SYM_MISSING)
  904. {
  905. #ifdef CONFIG_DEBUGGER
  906. sntprintf(msg, _countof(msg), _T("Parameter #%i of %s must not be omitted in this case.")
  907. , aIndex + 1, aFunction);
  908. #else
  909. sntprintf(msg, _countof(msg), _T("Parameter #%i must not be omitted in this case.")
  910. , aIndex + 1);
  911. #endif
  912. return g_script.RuntimeError(msg, nullptr, FAIL_OR_OK, nullptr, ErrorPrototype::Value);
  913. }
  914. TokenTypeAndValue(*aParam, actual_type, value_as_string, number_buf);
  915. if (!*value_as_string && !aExpectedType)
  916. value_as_string = actual_type;
  917. #ifdef CONFIG_DEBUGGER
  918. if (aExpectedType)
  919. sntprintf(msg, _countof(msg), _T("Parameter #%i of %s requires a%s %s, but received a%s %s.")
  920. , aIndex + 1, aFunction, an(aExpectedType), aExpectedType, an(actual_type), actual_type);
  921. else
  922. sntprintf(msg, _countof(msg), _T("Parameter #%i of %s is invalid."), aIndex + 1, g_Debugger.WhatThrew());
  923. #else
  924. if (aExpectedType)
  925. sntprintf(msg, _countof(msg), _T("Parameter #%i requires a%s %s, but received a%s %s.")
  926. , aIndex + 1, an(aExpectedType), aExpectedType, an(actual_type), actual_type);
  927. else
  928. sntprintf(msg, _countof(msg), _T("Parameter #%i invalid."), aIndex + 1);
  929. #endif
  930. return g_script.RuntimeError(msg, value_as_string, FAIL_OR_OK, nullptr
  931. , aExpectedType ? ErrorPrototype::Type : ErrorPrototype::Value);
  932. }
  933. __declspec(noinline)
  934. ResultType ResultToken::ParamError(int aIndex, ExprTokenType *aParam)
  935. {
  936. return Fail(::ParamError(aIndex, aParam, nullptr, nullptr));
  937. }
  938. __declspec(noinline)
  939. ResultType ResultToken::ParamError(int aIndex, ExprTokenType *aParam, LPCTSTR aExpectedType)
  940. {
  941. return Fail(::ParamError(aIndex, aParam, aExpectedType, nullptr));
  942. }
  943. __declspec(noinline)
  944. ResultType ResultToken::ParamError(int aIndex, ExprTokenType *aParam, LPCTSTR aExpectedType, LPCTSTR aFunction)
  945. {
  946. return Fail(::ParamError(aIndex, aParam, aExpectedType, aFunction));
  947. }
  948. FResult FParamError(int aIndex, ExprTokenType *aParam, LPCTSTR aExpectedType)
  949. {
  950. return ::ParamError(aIndex, aParam, aExpectedType, nullptr) == OK ? FR_ABORTED : FR_FAIL;
  951. }
  952. ResultType FResultToError(ResultToken &aResultToken, ExprTokenType *aParam[], int aParamCount, FResult aResult, int aFirstParam)
  953. {
  954. if (aResult & FR_OUR_FLAG)
  955. {
  956. if (aResult & FR_INT_FLAG)
  957. {
  958. // This is a bit of a hack and should probably be revised.
  959. TCHAR buf[12];
  960. return aResultToken.Error(ERR_FAILED, _itot(FR_GET_THROWN_INT(aResult), buf, 10));
  961. }
  962. auto code = HRESULT_CODE(aResult);
  963. switch (HRESULT_FACILITY(aResult))
  964. {
  965. case FR_FACILITY_CONTROL:
  966. ASSERT(!code);
  967. return aResultToken.SetExitResult(FAIL);
  968. case FR_FACILITY_ARG:
  969. if (aResult == FR_E_ARGS)
  970. return aResultToken.Error(ERR_PARAM_INVALID);
  971. return aResultToken.ParamError(code, code + aFirstParam < aParamCount ? aParam[code + aFirstParam] : nullptr);
  972. case FACILITY_WIN32:
  973. if (!code)
  974. code = GetLastError();
  975. return aResultToken.Win32Error(code);
  976. #ifndef _DEBUG
  977. default: // Using a default case may slightly reduce code size.
  978. #endif
  979. case FR_FACILITY_ERR:
  980. switch (code)
  981. {
  982. case HRESULT_CODE(FR_E_OUTOFMEM):
  983. return aResultToken.MemoryError();
  984. }
  985. }
  986. ASSERT(aResult == FR_E_FAILED); // Alert for any unhandled error codes in debug mode.
  987. return aResultToken.Error(ERR_FAILED);
  988. }
  989. else // Presumably a HRESULT error value.
  990. {
  991. return aResultToken.Win32Error(aResult);
  992. }
  993. }
  994. __declspec(noinline)
  995. ResultType Script::UnhandledException(Line* aLine, ResultType aErrorType)
  996. {
  997. global_struct &g = *::g;
  998. ResultToken *token = g.ThrownToken;
  999. // Clear ThrownToken to allow any applicable callbacks to execute correctly.
  1000. // This includes OnError callbacks explicitly called below, but also COM events
  1001. // and CallbackCreate callbacks that execute while MsgBox() is waiting.
  1002. g.ThrownToken = NULL;
  1003. #ifdef CONFIG_DEBUGGER
  1004. if (g.ExcptMode & EXCPTMODE_DEBUGGER)
  1005. {
  1006. FreeExceptionToken(token);
  1007. return FAIL;
  1008. }
  1009. #endif
  1010. // OnError: Allow the script to handle it via a global callback.
  1011. static bool sOnErrorRunning = false;
  1012. if (mOnError.Count() && !sOnErrorRunning)
  1013. {
  1014. __int64 retval;
  1015. sOnErrorRunning = true;
  1016. ExprTokenType param[2];
  1017. param[0].CopyValueFrom(*token);
  1018. param[1].SetValue(aErrorType == CRITICAL_ERROR ? _T("ExitApp")
  1019. : aErrorType == FAIL_OR_OK ? _T("Return") : _T("Exit"));
  1020. mOnError.Call(param, 2, INT_MAX, &retval);
  1021. sOnErrorRunning = false;
  1022. if (g.ThrownToken) // An exception was thrown by the callback.
  1023. {
  1024. // UnhandledException() has already been called recursively for g.ThrownToken,
  1025. // so don't show a second error message. This allows `throw param1` to mean
  1026. // "abort all OnError callbacks and show default message now".
  1027. FreeExceptionToken(token);
  1028. return FAIL;
  1029. }
  1030. if (retval < 0 && aErrorType == FAIL_OR_OK)
  1031. {
  1032. FreeExceptionToken(token);
  1033. return OK; // Ignore error and continue.
  1034. }
  1035. // Some callers rely on g.ThrownToken!=NULL to unwind the stack, so it is restored
  1036. // rather than freeing it immediately. If the exception object has __Delete, it
  1037. // will be called after the stack unwinds.
  1038. if (retval)
  1039. {
  1040. g.ThrownToken = token;
  1041. return FAIL; // Exit thread.
  1042. }
  1043. }
  1044. #ifdef CONFIG_DLL
  1045. if (LibNotifyProblem(*token))
  1046. {
  1047. g.ThrownToken = token; // See comments above.
  1048. return FAIL;
  1049. }
  1050. #endif
  1051. if (ShowError(aLine, aErrorType, token) == OK)
  1052. {
  1053. FreeExceptionToken(token);
  1054. return OK;
  1055. }
  1056. g.ThrownToken = token;
  1057. return FAIL;
  1058. }
  1059. ResultType Script::ShowError(Line* aLine, ResultType aErrorType, ExprTokenType *token)
  1060. {
  1061. LPCTSTR message = _T(""), extra = _T("");
  1062. TCHAR extra_buf[MAX_NUMBER_SIZE], message_buf[MAX_NUMBER_SIZE];
  1063. Object *ex = dynamic_cast<Object *>(TokenToObject(*token));
  1064. if (ex)
  1065. {
  1066. // For simplicity and safety, we call into the Object directly rather than via Invoke().
  1067. ExprTokenType t;
  1068. if (ex->GetOwnProp(t, _T("Message")))
  1069. message = TokenToString(t, message_buf);
  1070. if (ex->GetOwnProp(t, _T("Extra")))
  1071. extra = TokenToString(t, extra_buf);
  1072. LPCTSTR file = ex->GetOwnPropString(_T("File"));
  1073. LineNumberType line_no = (LineNumberType)ex->GetOwnPropInt64(_T("Line"));
  1074. if (file && *file && line_no)
  1075. {
  1076. // Locate the line by number and file index, then display that line instead
  1077. // of the caller supplied one since it's probably more relevant.
  1078. int file_index;
  1079. for (file_index = 0; file_index < Line::sSourceFileCount; ++file_index)
  1080. if (!_tcsicmp(file, Line::sSourceFile[file_index]))
  1081. break;
  1082. if (!aLine || aLine->mFileIndex != file_index || aLine->mLineNumber != line_no) // Keep aLine if it matches, in case of multiple Lines with the same number.
  1083. {
  1084. Line *line = nullptr;
  1085. for (auto mod = mLastModule; mod; mod = mod->mPrev)
  1086. for (line = mod->mFirstLine;
  1087. line && (line->mLineNumber != line_no || line->mFileIndex != file_index
  1088. || !line->mArgc && line->mNextLine && line->mNextLine->mLineNumber == line_no); // Skip any same-line block-begin/end, try, else or finally.
  1089. line = line->mNextLine);
  1090. if (line)
  1091. aLine = line;
  1092. }
  1093. }
  1094. }
  1095. else
  1096. {
  1097. // Assume it's a string or number.
  1098. extra = TokenToString(*token, message_buf);
  1099. }
  1100. // If message is empty (or a string or number was thrown), display a default message for clarity.
  1101. if (!*message)
  1102. message = _T("Unhandled exception.");
  1103. return ShowError(message, aErrorType, extra, aLine, ex);
  1104. }
  1105. void Object::Error_Show(ResultToken &aResultToken, int aID, int aFlags, ExprTokenType *aParam[], int aParamCount)
  1106. {
  1107. ResultType type = FAIL_OR_OK;
  1108. if (aParamCount && aParam[0]->symbol != SYM_MISSING)
  1109. {
  1110. static LPCTSTR sKindName[] { _T("Return"), _T("Exit"), _T("ExitApp"), _T("Warn") };
  1111. static ResultType sKindType[] { FAIL_OR_OK, FAIL, CRITICAL_ERROR, WARN };
  1112. auto kind = TokenToString(*aParam[0]);
  1113. for (int i = 0;; ++i)
  1114. {
  1115. if (i == _countof(sKindName))
  1116. _f_throw_param(0);
  1117. if (!_tcsicmp(kind, sKindName[i]))
  1118. {
  1119. type = sKindType[i];
  1120. break;
  1121. }
  1122. }
  1123. }
  1124. ExprTokenType t_this(this);
  1125. _o_return(g_script.ShowError(nullptr, type, &t_this) == OK ? -1 : 1);
  1126. }
  1127. void Script::FreeExceptionToken(ResultToken*& aToken)
  1128. {
  1129. // Release any potential content the token may hold
  1130. aToken->Free();
  1131. // Free the token itself.
  1132. delete aToken;
  1133. // Clear caller's variable.
  1134. aToken = NULL;
  1135. }
  1136. bool Line::CatchThis(ExprTokenType &aThrown) // ACT_CATCH
  1137. {
  1138. auto args = (CatchStatementArgs *)mAttribute;
  1139. if (!args || !args->prototype_count)
  1140. return Object::HasBase(aThrown, ErrorPrototype::Error);
  1141. for (int i = 0; i < args->prototype_count; ++i)
  1142. if (Object::HasBase(aThrown, args->prototype[i]))
  1143. return true;
  1144. return false;
  1145. }
  1146. void Script::ScriptWarning(WarnMode warnMode, LPCTSTR aWarningText, LPCTSTR aExtraInfo, Line *line)
  1147. {
  1148. if (!line) line = mCurrLine;
  1149. int fileIndex = line ? line->mFileIndex : mCurrFileIndex;
  1150. FileIndexType lineNumber = line ? line->mLineNumber : mCombinedLineNumber;
  1151. #ifdef CONFIG_DLL
  1152. if (LibNotifyProblem(aWarningText, aExtraInfo, line, true))
  1153. return;
  1154. #endif
  1155. if (warnMode == WARNMODE_ON)
  1156. warnMode = g_WarnMode;
  1157. if (warnMode == WARNMODE_OFF)
  1158. return;
  1159. TCHAR buf[MSGBOX_TEXT_SIZE];
  1160. auto n = FormatStdErr(buf, _countof(buf), aWarningText, aExtraInfo, fileIndex, lineNumber, true);
  1161. if (warnMode == WARNMODE_STDOUT)
  1162. PrintErrorStdOut(buf, n);
  1163. else
  1164. #ifdef CONFIG_DEBUGGER
  1165. if (!g_Debugger.OutputStdErr(buf))
  1166. #endif
  1167. OutputDebugString(buf);
  1168. // In MsgBox mode, MsgBox is in addition to OutputDebug
  1169. if (warnMode == WARNMODE_MSGBOX)
  1170. {
  1171. g_script.ShowError(aWarningText, WARN, aExtraInfo, line);
  1172. }
  1173. }
  1174. void Script::WarnUnassignedVar(Var *var, Line *aLine)
  1175. {
  1176. auto warnMode = mCurrentModule->Warn_VarUnset;
  1177. if (!warnMode)
  1178. return;
  1179. // Currently only the first reference to each var generates a warning even when using
  1180. // StdOut/OutputDebug. MarkAlreadyWarned() is also used to suppress warnings for any
  1181. // var which is checked with IsSet().
  1182. //if (warnMode == WARNMODE_MSGBOX)
  1183. {
  1184. // The following check uses a flag separate to IsAssignedSomewhere() because setting
  1185. // that one for the purpose of preventing multiple MsgBoxes would cause other callers
  1186. // of IsAssignedSomewhere() to get the wrong result if a MsgBox has been shown.
  1187. if (var->HasAlreadyWarned())
  1188. return;
  1189. var->MarkAlreadyWarned();
  1190. }
  1191. // No check for a global variable is done because var is unassigned and therefore should
  1192. // have resolved to a global if there was one, unless it was declared local.
  1193. TCHAR buf[1024];
  1194. sntprintf(buf, _countof(buf), _T("This %s appears to never be assigned a value."), Var::DeclarationType(var->Scope()));
  1195. ScriptWarning(warnMode, buf, var->mName, aLine);
  1196. }
  1197. ResultType Script::VarUnsetError(Var *var)
  1198. {
  1199. TCHAR buf[1024];
  1200. bool isUndeclaredLocal = (var->Scope() & (VAR_LOCAL | VAR_DECLARED)) == VAR_LOCAL;
  1201. bool sameNameAsGlobal = isUndeclaredLocal && FindGlobalVar(var->mName);
  1202. sntprintf(buf, _countof(buf), _T("This %s has not been assigned a value.%s"), Var::DeclarationType(var->Scope())
  1203. , sameNameAsGlobal ? _T("\nA global declaration inside the function may be required.") : _T(""));
  1204. return RuntimeError(buf, var->mName, FAIL_OR_OK, nullptr, ErrorPrototype::Unset);
  1205. }
  1206. void Script::WarnLocalSameAsGlobal(LPCTSTR aVarName)
  1207. // Relies on the following pre-conditions:
  1208. // 1) It is an implicit (not declared) variable.
  1209. // 2) Caller has verified that a global variable exists with the same name.
  1210. // 3) g->CurrentFunc->mModule->Warn_LocalSameAsGlobal is on (true).
  1211. // 4) g->CurrentFunc is the function which contains this variable.
  1212. {
  1213. TCHAR buf[DIALOG_TITLE_SIZE];
  1214. sntprintf(buf, _countof(buf), _T("%s (in function %s)"), aVarName, g->CurrentFunc->mName);
  1215. ScriptWarning(g->CurrentFunc->mModule->Warn_LocalSameAsGlobal, WARNING_LOCAL_SAME_AS_GLOBAL, buf);
  1216. }