error.cpp 45 KB

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