Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1013 lines
29 KiB

  1. // Copyright (c) 1999 Microsoft Corporation. All rights reserved.
  2. //
  3. // Implementation of Executor.
  4. //
  5. #include "stdinc.h"
  6. #include "enginc.h"
  7. #include "engexec.h"
  8. #include "math.h"
  9. #include "packexception.h"
  10. //////////////////////////////////////////////////////////////////////
  11. // CallStack
  12. HRESULT
  13. CallStack::Push(UINT i)
  14. {
  15. UINT iInitialSize = m_vec.size();
  16. if (!m_vec.AccessTo(m_iNext + i - 1))
  17. return E_OUTOFMEMORY;
  18. UINT iNewNext = m_iNext + i;
  19. for (UINT iInit = std::_MAX<UINT>(m_iNext, iInitialSize); iInit < iNewNext; ++iInit)
  20. DMS_VariantInit(g_fUseOleAut, &m_vec[iInit]);
  21. m_iNext = iNewNext;
  22. return S_OK;
  23. }
  24. void
  25. CallStack::PopTo(UINT i)
  26. {
  27. for (UINT iInit = i; iInit < m_iNext; ++iInit)
  28. DMS_VariantClear(g_fUseOleAut, &m_vec[iInit]);
  29. m_iNext = std::_MIN<UINT>(m_iNext, i);
  30. }
  31. //////////////////////////////////////////////////////////////////////
  32. // Executor
  33. Executor::Executor(Script &script, IDispatch *pGlobalDispatch)
  34. : m_fInitialized(false),
  35. m_script(script),
  36. m_scomGlobalDispatch(pGlobalDispatch)
  37. {
  38. DMS_VariantInit(g_fUseOleAut, &m_varEmpty);
  39. }
  40. Executor::~Executor()
  41. {
  42. m_stack.PopTo(0); // clear any varients on the stack that might be holding refs
  43. }
  44. HRESULT
  45. Executor::SetGlobal(Variables::index ivar, const VARIANT &varValue, bool fPutRef, EXCEPINFO *pExcepInfo)
  46. {
  47. HRESULT hr = EnsureInitialized();
  48. if (FAILED(hr))
  49. return hr;
  50. hr = ErrorIfImproperRef(varValue, fPutRef, m_script.globals[ivar].istrIdentifier, pExcepInfo);
  51. if (FAILED(hr))
  52. return hr;
  53. assert(ivar <= m_script.globals.Next());
  54. return DMS_VariantCopy(g_fUseOleAut, &m_stack[ivar], &varValue);
  55. }
  56. const VARIANT &
  57. Executor::GetGlobal(Variables::index ivar)
  58. {
  59. if (!m_fInitialized)
  60. {
  61. // No variable gets or routine calls have been performed yet (or they failed).
  62. // But we don't want to return an error here. Since nothing's been used yet, the correct
  63. // thing to do is to return an empty value.
  64. return m_varEmpty;
  65. }
  66. assert(ivar <= m_script.globals.Next());
  67. return m_stack[ivar];
  68. }
  69. HRESULT
  70. Executor::ExecRoutine(Routines::index irtn, EXCEPINFO *pExcepInfo)
  71. {
  72. HRESULT hr = EnsureInitialized();
  73. if (FAILED(hr))
  74. return hr;
  75. Routine r = m_script.routines[irtn];
  76. UINT iLocals = m_stack.Next();
  77. hr = m_stack.Push(r.ivarNextLocal);
  78. if (FAILED(hr))
  79. return hr;
  80. hr = ExecStatements(r.istmtBody, pExcepInfo, iLocals);
  81. m_stack.PopTo(iLocals);
  82. return hr;
  83. }
  84. HRESULT
  85. Executor::EnsureInitialized()
  86. {
  87. if (m_fInitialized)
  88. return S_OK;
  89. // we'll keep the global variables right at the bottom of the stack
  90. // this function ensures that they get pushed on before any operations that use them
  91. HRESULT hr = m_stack.Push(m_script.globals.Next());
  92. if (FAILED(hr))
  93. return hr;
  94. // Also set the first items to the build in constant values True, False, and Nothing.
  95. // See also engparse.cpp which creates these global variables before parsing each script.
  96. if (m_stack.Next() < 3)
  97. {
  98. assert(false);
  99. return E_UNEXPECTED;
  100. }
  101. VARIANT &vTrue = m_stack[0];
  102. vTrue.vt = VT_I4;
  103. vTrue.lVal = VARIANT_TRUE;
  104. VARIANT &vFalse = m_stack[1];
  105. vFalse.vt = VT_I4;
  106. vFalse.lVal = VARIANT_FALSE;
  107. VARIANT &vNothing = m_stack[2];
  108. vNothing.vt = VT_UNKNOWN;
  109. vNothing.punkVal = NULL;
  110. m_fInitialized = true;
  111. return S_OK;
  112. }
  113. HRESULT
  114. Executor::Error(EXCEPINFO *pExcepInfo, bool fOperation, const WCHAR *pwszBeginning, const char *paszMiddle, const WCHAR *pwszEnd)
  115. {
  116. if (!pExcepInfo)
  117. {
  118. assert(false); // our script host should always request error info
  119. return DISP_E_EXCEPTION;
  120. }
  121. // NULL for beginning, middle, or end treated as empty string
  122. if (!pwszBeginning)
  123. pwszBeginning = L"";
  124. if (!paszMiddle)
  125. paszMiddle = "";
  126. if (!pwszEnd)
  127. pwszEnd = L"";
  128. pExcepInfo->wCode = 0;
  129. pExcepInfo->wReserved = 0;
  130. pExcepInfo->bstrSource = DMS_SysAllocString(g_fUseOleAut, fOperation ? L"Microsoft AudioVBScript Operation Failed" : L"Microsoft AudioVBScript Runtime Error");
  131. SmartRef::WString wstrMiddle = paszMiddle;
  132. WCHAR *pwszDescription = NULL;
  133. if (wstrMiddle)
  134. {
  135. pwszDescription = new WCHAR[wcslen(pwszBeginning) + wcslen(wstrMiddle) + wcslen(pwszEnd) + 1];
  136. }
  137. if (!pwszDescription)
  138. {
  139. // Oh well. Just return no description if we're out of memory.
  140. pExcepInfo->bstrDescription = NULL;
  141. }
  142. else
  143. {
  144. wcscpy(pwszDescription, pwszBeginning);
  145. wcscat(pwszDescription, wstrMiddle);
  146. wcscat(pwszDescription, pwszEnd);
  147. pExcepInfo->bstrDescription = DMS_SysAllocString(g_fUseOleAut, pwszDescription);
  148. delete[] pwszDescription;
  149. }
  150. pExcepInfo->bstrHelpFile = NULL;
  151. pExcepInfo->pvReserved = NULL;
  152. pExcepInfo->pfnDeferredFillIn = NULL;
  153. pExcepInfo->scode = fOperation ? DMUS_E_AUDIOVBSCRIPT_OPERATIONFAILURE : DMUS_E_AUDIOVBSCRIPT_RUNTIMEERROR;
  154. return DISP_E_EXCEPTION;
  155. }
  156. HRESULT
  157. Executor::ErrorIfImproperRef(const VARIANT &v, bool fRef, Strings::index istrIdentifier, EXCEPINFO *pExcepInfo)
  158. {
  159. bool fIsObject = v.vt == VT_DISPATCH || v.vt == VT_UNKNOWN;
  160. if (fRef != fIsObject)
  161. {
  162. if (fRef)
  163. return ErrorObjectRequired(istrIdentifier, pExcepInfo);
  164. else
  165. return Error(pExcepInfo, false, L"Type mismatch: '", m_script.strings[istrIdentifier], L"'. Likely cause is missing Set statement.");
  166. }
  167. return S_OK;
  168. }
  169. // Check for the error HRESULTs returned by IDispatch::Invoke. Those that we expect to occur in AudioVBScript need to
  170. // be converted into exception (DISP_E_EXCEPTION) so that the user gets a nice error message.
  171. // The first parameter lets us know the kind of Invoke call that was made (property get, property set, function/sub call)
  172. // so that we can tailor the message.
  173. HRESULT
  174. Executor::ErrorIfInvokeProblem(DispatchOperationType e, HRESULT hr, Strings::index istrIdentifier, EXCEPINFO *pExcepInfo)
  175. {
  176. if (SUCCEEDED(hr) || HRESULT_FACILITY(hr) != FACILITY_DISPATCH || hr == DISP_E_EXCEPTION)
  177. return hr;
  178. const char *pszName = m_script.strings[istrIdentifier];
  179. if (hr == DISP_E_BADPARAMCOUNT)
  180. {
  181. // This can happen with a _call (obviously) and also with a get because property gets are also treated as function
  182. // calls with no arguments. "x=GetMasterVolume" is valid but "x=Trace" would produce this error. But I can't
  183. // see that this should occur with property sets.
  184. assert(e == _get || e == _call);
  185. return Error(pExcepInfo, false, L"Wrong number of parameters in call to '", pszName, L"'");
  186. }
  187. else if (hr == DISP_E_MEMBERNOTFOUND)
  188. {
  189. if (e == _call)
  190. {
  191. // Because Invoke was called, GetIDsOfNames must have succeeded, so the thing's name exists
  192. // but it must not be a method.
  193. return Error(pExcepInfo, false, L"Type mismatch: '", pszName, L"' is not a routine or method");
  194. }
  195. else if (e == _put || e == _putref)
  196. {
  197. return Error(pExcepInfo, false, L"Type mismatch: '", pszName, L"' is not a variable or is a read-only property");
  198. }
  199. else
  200. {
  201. // As mentioned above, a property get can be treated as either gets or function calls so they
  202. // shouldn't fail in this way.
  203. assert(false);
  204. }
  205. }
  206. else if (hr == DISP_E_TYPEMISMATCH)
  207. {
  208. // This indicates that one of the parameters was of the wrong type.
  209. if (e == _call)
  210. {
  211. return Error(pExcepInfo, false, L"Type mismatch: a parameter in call to '", pszName, L"' is not of the expected type");
  212. }
  213. else if (e == _put || e == _putref)
  214. {
  215. return Error(pExcepInfo, false, L"Type mismatch: value assigned to '", pszName, L"' is not of the expected type");
  216. }
  217. else
  218. {
  219. // Property gets don't have any parameters so this shouldn't happen.
  220. assert(false);
  221. }
  222. }
  223. else if (hr == DISP_E_PARAMNOTOPTIONAL)
  224. {
  225. if (e == _call)
  226. {
  227. return Error(pExcepInfo, false, L"A required parameter was omitted in call to '", pszName, L"'");
  228. }
  229. else
  230. {
  231. // Only calls should send an optional parameters.
  232. assert(false);
  233. }
  234. }
  235. // The other errors shouldn't normally occur in AudioVBScript. They could occur if someone was
  236. // doing something ususual in a custom IDispatch interface, but we'll consider them exceptional cases and
  237. // just return the error HRESULT (meaning the user won't get a friendly text message). Assert so we'll
  238. // find out if there are regular cases where this is happening in our testing.
  239. assert(false);
  240. // DISP_E_BADVARTYPE: We just use standard variant types.
  241. // DISP_E_NONAMEDARGS: We don't do named args.
  242. // DISP_E_OVERFLOW: AudioVBScript uses VT_I4 and so do our DMusic dispatch interfaces.
  243. // DISP_E_PARAMNOTFOUND: Only applies with named args.
  244. // DISP_E_UNKNOWNINTERFACE, DISP_E_UNKNOWNLCID: AudioVBScript uses calling convention and locale matching the DMusic dispatch interfaces.
  245. return hr;
  246. }
  247. HRESULT
  248. Executor::ExecStatements(Statements::index istmt, EXCEPINFO *pExcepInfo, UINT iLocals)
  249. {
  250. HRESULT hr = S_OK;
  251. for (Statements::index istmtCur = istmt; /* ever */; ++istmtCur)
  252. {
  253. // �� Check if this generates fast retail code. If not, walk a pointer instead of using the index.
  254. Statement s = m_script.statements[istmtCur];
  255. switch (s.k)
  256. {
  257. case Statement::_end:
  258. return hr;
  259. case Statement::_asgn:
  260. hr = ExecAssignment(s.iasgn, pExcepInfo, iLocals);
  261. break;
  262. case Statement::_if:
  263. hr = ExecIf(s.iif, pExcepInfo, iLocals);
  264. istmtCur = s.istmtIfTail - 1;
  265. break;
  266. case Statement::_call:
  267. hr = ExecCall(s.icall, false, pExcepInfo, iLocals);
  268. break;
  269. }
  270. if (FAILED(hr))
  271. {
  272. if (hr == DISP_E_EXCEPTION)
  273. {
  274. // Save the statement's line number in the exception info.
  275. // Hack: See packexception.h for more info
  276. ULONG ulLine = s.iLine - 1; // The IActiveScript interfaces expects zero-based line and column numbers while we have them one-based.
  277. PackExceptionFileAndLine(g_fUseOleAut, pExcepInfo, NULL, &ulLine);
  278. }
  279. return hr;
  280. }
  281. }
  282. }
  283. HRESULT
  284. Executor::ExecAssignment(Assignments::index iasgn, EXCEPINFO *pExcepInfo, UINT iLocals)
  285. {
  286. Assignment a = m_script.asgns[iasgn];
  287. VARIANT var;
  288. DMS_VariantInit(g_fUseOleAut, &var);
  289. HRESULT hr = EvalExpression(var, a.iexprRHS, pExcepInfo, iLocals);
  290. if (FAILED(hr))
  291. return hr;
  292. hr = SetVariableReference(a.fSet, a.ivarrefLHS, var, pExcepInfo, iLocals);
  293. DMS_VariantClear(g_fUseOleAut, &var);
  294. if (FAILED(hr))
  295. return hr;
  296. return S_OK;
  297. }
  298. HRESULT
  299. Executor::ExecIf(IfBlocks::index iif, EXCEPINFO *pExcepInfo, UINT iLocals)
  300. {
  301. for (IfBlocks::index i = iif; /* ever */; ++i)
  302. {
  303. IfBlock &ib = m_script.ifs[i];
  304. if (ib.k == IfBlock::_end)
  305. return S_OK;
  306. bool fMatch = true; // default to true because an else block always matches
  307. if (ib.k == IfBlock::_cond)
  308. {
  309. // if the condition isn't true, set match to false
  310. SmartVariant svar;
  311. EvalExpression(svar, ib.iexprCondition, pExcepInfo, iLocals);
  312. VARTYPE vt = static_cast<VARIANT&>(svar).vt;
  313. if (vt != VT_I4)
  314. {
  315. if (vt == VT_BSTR)
  316. return Error(pExcepInfo, false, L"Type mismatch: the condition of an if statement evaluated as a string where a numeric True/False value was expected", NULL, NULL);
  317. else if (vt == VT_UNKNOWN || vt == VT_DISPATCH)
  318. return Error(pExcepInfo, false, L"Type mismatch: the condition of an if statement evaluated as an object where a numeric True/False value was expected", NULL, NULL);
  319. return Error(pExcepInfo, false, L"Type mismatch: the condition of an if statement did not evaluate to a numeric True/False value", NULL, NULL);
  320. }
  321. if (static_cast<VARIANT&>(svar).lVal != VARIANT_TRUE)
  322. fMatch = false;
  323. }
  324. if (fMatch)
  325. {
  326. // found the block to take -- execute its statements and we're done
  327. return ExecStatements(ib.istmtBlock, pExcepInfo, iLocals);
  328. }
  329. }
  330. return S_OK;
  331. }
  332. // Helper function that eats up a set amount of stack space.
  333. const UINT g_uiExecCallCheckStackBytes = 1484 * 4;
  334. void ExecCallCheckStack();
  335. // Helper function that returns true if the exception code needs to be caught.
  336. LONG ExecCallExceptionFilter(DWORD dwExceptionCode)
  337. {
  338. // We need to access violations as well as stack overflows. The first time we run out
  339. // of stack space we get a stack overflow. The next time we get an access violation.
  340. return dwExceptionCode == EXCEPTION_STACK_OVERFLOW || dwExceptionCode == EXCEPTION_ACCESS_VIOLATION;
  341. }
  342. HRESULT Executor::ExecCall(Calls::index icall, bool fPushResult, EXCEPINFO *pExcepInfo, UINT iLocals)
  343. {
  344. // This is a wrapper for ExecCallInternal, which actually does the work. Here, we just want
  345. // to catch a potential stack overflow and return it as an error instead of GPF-ing.
  346. HRESULT hr = E_FAIL;
  347. __try
  348. {
  349. // It is better to fail now than to actually go ahead and call the routine and fail at some point we
  350. // can't predict. Routines could do lots of different things including calling into DirectMusic or the
  351. // OS and we can't be sure we'd get the stack overflow exception and return in a good state. This
  352. // routine uses more stack space than we'd expect recursive calls to require to get back to this point
  353. // again. In essence, it clears the way, checking if there's enough stack space in a way we know is safe.
  354. ExecCallCheckStack();
  355. #ifdef DBG
  356. // The value for g_uiExecCallCheckStackBytes was determined by experiment. Each time through ExecCall,
  357. // the following code prints out the address of a char on the current stack and the difference between
  358. // the previous call. I found that two scripts, which each evaluated an if statement (always true) and
  359. // then called the other one produced a difference of 1476. Then I multiplied that by 4 for good measure.
  360. char c;
  361. static char *s_pchPrev = &c;
  362. DWORD s_dwPrevThreadID = 0;
  363. DWORD dwGrowth = 0;
  364. if (s_pchPrev > &c && s_dwPrevThreadID == GetCurrentThreadId())
  365. dwGrowth = s_pchPrev-&c;
  366. TraceI(4, "Stack: 0x%08x, -%lu\n", &c, dwGrowth);
  367. // This assert will fire if a path is executed where a recursive path back to this function takes
  368. // more stack space than g_uiExecCallCheckStackBytes. If that's the case then g_uiExecCallCheckStackBytes
  369. // probably needs to be increased.
  370. assert(dwGrowth <= g_uiExecCallCheckStackBytes);
  371. s_pchPrev = &c;
  372. s_dwPrevThreadID = GetCurrentThreadId();
  373. #endif
  374. // If we fail inside this call, it means g_uiExecCallCheckStackBytes probably needs to be increased because
  375. // ExecCallCheckStack didn't catch the stack overflow.
  376. hr = ExecCallInternal(icall, fPushResult, pExcepInfo, iLocals);
  377. }
  378. __except(ExecCallExceptionFilter(GetExceptionCode()))
  379. {
  380. Trace(1, "Error: Stack overflow.\n");
  381. // determine routine name
  382. Call &c = m_script.calls[icall];
  383. const char *pszCall = NULL;
  384. if (c.k == Call::_global)
  385. {
  386. pszCall = m_script.strings[c.istrname];
  387. }
  388. else
  389. {
  390. // name to use is last of the call's reference names
  391. for (ReferenceNames::index irname = m_script.varrefs[c.ivarref].irname; m_script.rnames[irname].istrIdentifier != -1; ++irname)
  392. {}
  393. pszCall = m_script.strings[m_script.rnames[irname - 1].istrIdentifier];
  394. }
  395. if (GetExceptionCode() == EXCEPTION_STACK_OVERFLOW)
  396. {
  397. hr = Error(pExcepInfo, false, L"Out of stack space: '", pszCall, L"'. Too many nested function calls.");
  398. }
  399. else
  400. {
  401. hr = Error(pExcepInfo, false, L"Out of stack space or catastrophic error: '", pszCall, L"'.");
  402. }
  403. }
  404. return hr;
  405. }
  406. // This function doesn't actually do anything besides occupying stack space. Turn off optimization so the
  407. // copiler doesn't get all clever on us and skip it.
  408. #pragma optimize("", off)
  409. void ExecCallCheckStack()
  410. {
  411. char chFiller[g_uiExecCallCheckStackBytes];
  412. chFiller[g_uiExecCallCheckStackBytes - 1] = '\0';
  413. }
  414. #pragma optimize("", on)
  415. HRESULT Executor::ExecCallInternal(Calls::index icall, bool fPushResult, EXCEPINFO *pExcepInfo, UINT iLocals)
  416. {
  417. HRESULT hr = S_OK;
  418. SmartVariant svar; // holds temporary variant values at various points
  419. SmartVariant svar2; // ditto
  420. Call &c = m_script.calls[icall];
  421. IDispatch *pDispCall = NULL;
  422. Strings::index istrCall = 0;
  423. const char *pszCall = NULL;
  424. if (c.k == Call::_global)
  425. {
  426. istrCall = c.istrname;
  427. pszCall = m_script.strings[istrCall];
  428. // Handle the call directly if it is a call to one of the script's own Routines.
  429. Routines::index irtnLast = m_script.routines.Next();
  430. for (Routines::index irtn = 0; irtn < irtnLast; ++irtn)
  431. {
  432. if (0 == _stricmp(pszCall, m_script.strings[m_script.routines[irtn].istrIdentifier]))
  433. {
  434. return ExecRoutine(irtn, pExcepInfo);
  435. }
  436. }
  437. // Must be a call to the global script API.
  438. pDispCall = m_scomGlobalDispatch;
  439. }
  440. else
  441. {
  442. assert(c.k == Call::_dereferenced);
  443. // count the reference names (needed later)
  444. for (ReferenceNames::index irname = m_script.varrefs[c.ivarref].irname; m_script.rnames[irname].istrIdentifier != -1; ++irname)
  445. {}
  446. assert(irname - m_script.varrefs[c.ivarref].irname > 1); // if there was only one name, this should have been a global call
  447. hr = VariableReferenceInternal(_call, c.ivarref, svar, pExcepInfo, iLocals);
  448. if (FAILED(hr))
  449. return hr;
  450. hr = ChangeToDispatch(svar, pExcepInfo, irname - 2);
  451. if (FAILED(hr))
  452. return hr;
  453. pDispCall = static_cast<VARIANT>(svar).pdispVal;
  454. // the method name is the last reference name
  455. istrCall = m_script.rnames[irname - 1].istrIdentifier;
  456. pszCall = m_script.strings[istrCall];
  457. }
  458. DISPID dispidCall = GetDispID(pDispCall, pszCall);
  459. if (dispidCall == DISPID_UNKNOWN)
  460. {
  461. return Error(pExcepInfo, false, L"The routine '", pszCall, L"' does not exist");
  462. }
  463. // We'll push the parameters onto the stack. (The function we're calling doesn't actually read them directly using the stack, but
  464. // it is a convenient place for us to keep them temporarily.)
  465. // First, count the parameters.
  466. UINT cParams = 0;
  467. for (ExprBlocks::index iexpr = c.iexprParams; m_script.exprs[iexpr]; ++iexpr)
  468. {
  469. // each parameter is an expression terminated by an end block
  470. ++cParams;
  471. while (m_script.exprs[++iexpr])
  472. {}
  473. }
  474. // Make space for them.
  475. UINT iParamSlots = m_stack.Next();
  476. hr = m_stack.Push(std::_MAX<UINT>(cParams, fPushResult ? 1 : 0)); // even if there are no params, leave one slot for the result if fPushResult is true
  477. if (FAILED(hr))
  478. return hr;
  479. // Fill the params in reverse order.
  480. iexpr = c.iexprParams;
  481. for (UINT iParam = iParamSlots + cParams - 1; iParam >= iParamSlots; --iParam)
  482. {
  483. if (m_script.exprs[iexpr].k == ExprBlock::_omitted)
  484. {
  485. // write the variant value IDispatch::Invoke uses for an omitted parameter
  486. m_stack[iParam].vt = VT_ERROR;
  487. m_stack[iParam].scode = DISP_E_PARAMNOTFOUND;
  488. }
  489. else
  490. {
  491. hr = EvalExpression(svar, iexpr, pExcepInfo, iLocals);
  492. if (FAILED(hr))
  493. return hr;
  494. hr = DMS_VariantCopy(g_fUseOleAut, &m_stack[iParam], &svar);
  495. if (FAILED(hr))
  496. return hr;
  497. }
  498. // each parameter is an expression terminated by an end block
  499. ++iexpr;
  500. while (m_script.exprs[iexpr++])
  501. {}
  502. }
  503. DISPPARAMS dispparams;
  504. Zero(&dispparams);
  505. dispparams.rgvarg = cParams > 0 ? &m_stack[iParamSlots] : NULL;
  506. dispparams.rgdispidNamedArgs = NULL;
  507. dispparams.cArgs = cParams;
  508. dispparams.cNamedArgs = 0;
  509. // Make the call.
  510. // Push the result onto the stack if fPushResult is true.
  511. hr = InvokeAttemptingNotToUseOleAut(
  512. pDispCall,
  513. dispidCall,
  514. DISPATCH_METHOD,
  515. &dispparams,
  516. fPushResult ? &svar2 : NULL, // We can't save the result directly onto the stack because we could be makeing a recursive script call that could cause a stack to be reallocation, invalidating the address so we use svar2 instead.
  517. pExcepInfo,
  518. NULL);
  519. hr = ErrorIfInvokeProblem(_call, hr, istrCall, pExcepInfo);
  520. if (SUCCEEDED(hr) && fPushResult)
  521. {
  522. hr = DMS_VariantCopy(g_fUseOleAut, &m_stack[iParamSlots], &svar2);
  523. }
  524. m_stack.PopTo(iParamSlots + (fPushResult ? 1 : 0));
  525. if (FAILED(hr))
  526. return hr;
  527. return S_OK;
  528. }
  529. // possible error-message type returns: DISP_E_TYPEMISMATCH
  530. HRESULT
  531. Executor::EvalExpression(VARIANT &varResult, ExprBlocks::index iexpr, EXCEPINFO *pExcepInfo, UINT iLocals)
  532. {
  533. HRESULT hr = S_OK;
  534. UINT iTempSlots = m_stack.Next();
  535. for (ExprBlocks::index iexprCur = iexpr; /* ever */; ++iexprCur)
  536. {
  537. ExprBlock &e = m_script.exprs[iexprCur];
  538. switch (e.k)
  539. {
  540. case ExprBlock::_end:
  541. // pop the result and return it
  542. if (m_stack.Next() != iTempSlots + 1)
  543. {
  544. assert(false);
  545. return E_FAIL;
  546. }
  547. DMS_VariantCopy(g_fUseOleAut, &varResult, &m_stack[iTempSlots]);
  548. m_stack.PopTo(iTempSlots);
  549. return hr;
  550. case ExprBlock::_op:
  551. // Pop one (unary operator) or two (binary operator) items, apply the operator, and push the result.
  552. // (Actually, I just assign the result into the stack instead of pushing it, but conceptually the is
  553. // the same as popping and pushing the new value.)
  554. {
  555. Token t = e.op;
  556. bool fUnary = t == TOKEN_op_not || t == TOKEN_sub;
  557. UINT iNext = m_stack.Next();
  558. if (iNext < iTempSlots + (fUnary ? 1 : 2))
  559. {
  560. assert(false);
  561. return E_FAIL;
  562. }
  563. VARIANT &v1 = m_stack[iNext - 1];
  564. if (fUnary)
  565. hr = EvalUnaryOp(t, v1);
  566. else
  567. {
  568. VARIANT &v2 = m_stack[iNext - 2];
  569. hr = EvalBinaryOp(t, v1, v2, pExcepInfo);
  570. m_stack.PopTo(iNext - 1);
  571. }
  572. }
  573. break;
  574. case ExprBlock::_val:
  575. {
  576. // push it
  577. hr = m_stack.Push(1);
  578. VARIANT &varToPush = m_stack[m_stack.Next() - 1];
  579. if (SUCCEEDED(hr))
  580. hr = EvalValue(e.ival, varToPush, pExcepInfo, iLocals);
  581. if (varToPush.vt == VT_EMPTY)
  582. {
  583. // treat an empty value as zero
  584. varToPush.vt = VT_I4;
  585. varToPush.lVal = 0;
  586. }
  587. }
  588. break;
  589. case ExprBlock::_call:
  590. // push it
  591. if (SUCCEEDED(hr))
  592. hr = ExecCall(e.icall, true, pExcepInfo, iLocals);
  593. break;
  594. }
  595. if (FAILED(hr))
  596. {
  597. m_stack.PopTo(iTempSlots);
  598. return hr;
  599. }
  600. }
  601. return S_OK;
  602. }
  603. HRESULT
  604. Executor::EvalValue(Values::index ival, VARIANT &v, EXCEPINFO *pExcepInfo, UINT iLocals)
  605. {
  606. Value val = m_script.vals[ival];
  607. switch (val.k)
  608. {
  609. case Value::_numvalue:
  610. v.vt = VT_I4;
  611. v.lVal = val.inumvalue;
  612. break;
  613. case Value::_strvalue:
  614. {
  615. v.vt = VT_BSTR;
  616. SmartRef::WString wstr = m_script.strings[val.istrvalue];
  617. if (!wstr)
  618. return E_OUTOFMEMORY;
  619. v.bstrVal = DMS_SysAllocString(g_fUseOleAut, wstr);
  620. if (!v.bstrVal)
  621. return E_OUTOFMEMORY;
  622. break;
  623. }
  624. case Value::_varref:
  625. HRESULT hr = GetVariableReference(val.ivarref, v, pExcepInfo, iLocals);
  626. if (FAILED(hr))
  627. return hr;
  628. }
  629. return S_OK;
  630. }
  631. HRESULT
  632. Executor::EvalUnaryOp(Token t, VARIANT &v)
  633. {
  634. if (v.vt != VT_I4)
  635. {
  636. assert(false);
  637. return DISP_E_TYPEMISMATCH;
  638. }
  639. if (t == TOKEN_op_not)
  640. {
  641. v.lVal = ~v.lVal;
  642. }
  643. else
  644. {
  645. assert(t == TOKEN_sub);
  646. v.lVal = -v.lVal;
  647. }
  648. return S_OK;
  649. }
  650. // Returns a proper VB boolean value (0 for false, -1 for true)
  651. inline LONG
  652. BoolForVB(bool f) { return f ? VARIANT_TRUE : VARIANT_FALSE; }
  653. HRESULT
  654. Executor::EvalBinaryOp(Token t, VARIANT &v1, VARIANT &v2, EXCEPINFO *pExcepInfo)
  655. {
  656. if (v1.vt == VT_DISPATCH || v1.vt == VT_UNKNOWN)
  657. {
  658. // the only operator that accepts object values is is
  659. if (t != TOKEN_is || !(v2.vt == VT_DISPATCH || v2.vt == VT_UNKNOWN))
  660. {
  661. assert(false);
  662. return DISP_E_TYPEMISMATCH;
  663. }
  664. HRESULT hr = DMS_VariantChangeType(g_fUseOleAut, &v1, &v1, 0, VT_UNKNOWN);
  665. if (FAILED(hr))
  666. return hr;
  667. hr = DMS_VariantChangeType(g_fUseOleAut, &v2, &v2, 0, VT_UNKNOWN);
  668. if (FAILED(hr))
  669. return hr;
  670. bool fIs = v1.punkVal == v2.punkVal;
  671. hr = DMS_VariantClear(g_fUseOleAut, &v2);
  672. if (FAILED(hr))
  673. return hr;
  674. v2.vt = VT_I4;
  675. v2.lVal = BoolForVB(fIs);
  676. return S_OK;
  677. }
  678. if (v1.vt != VT_I4 || v2.vt != VT_I4)
  679. {
  680. assert(false);
  681. return DISP_E_TYPEMISMATCH;
  682. }
  683. switch (t)
  684. {
  685. case TOKEN_op_minus:
  686. v2.lVal -= v1.lVal;
  687. break;
  688. case TOKEN_op_pow:
  689. v2.lVal = _Pow_int(v2.lVal, v1.lVal);
  690. break;
  691. case TOKEN_op_mult:
  692. v2.lVal *= v1.lVal;
  693. break;
  694. case TOKEN_op_div:
  695. if (v1.lVal == 0)
  696. return Error(pExcepInfo, false, L"Division by zero", NULL, NULL);
  697. v2.lVal /= v1.lVal;
  698. break;
  699. case TOKEN_op_mod:
  700. if (v1.lVal == 0)
  701. return Error(pExcepInfo, false, L"Mod by zero", NULL, NULL);
  702. v2.lVal %= v1.lVal;
  703. break;
  704. case TOKEN_op_plus:
  705. v2.lVal += v1.lVal;
  706. break;
  707. case TOKEN_op_lt:
  708. v2.lVal = BoolForVB(v2.lVal < v1.lVal);
  709. break;
  710. case TOKEN_op_leq:
  711. v2.lVal = BoolForVB(v2.lVal <= v1.lVal);
  712. break;
  713. case TOKEN_op_gt:
  714. v2.lVal = BoolForVB(v2.lVal > v1.lVal);
  715. break;
  716. case TOKEN_op_geq:
  717. v2.lVal = BoolForVB(v2.lVal >= v1.lVal);
  718. break;
  719. case TOKEN_op_eq:
  720. v2.lVal = BoolForVB(v2.lVal == v1.lVal);
  721. break;
  722. case TOKEN_op_neq:
  723. v2.lVal = BoolForVB(v2.lVal != v1.lVal);
  724. break;
  725. case TOKEN_and:
  726. v2.lVal &= v1.lVal;
  727. break;
  728. case TOKEN_or:
  729. v2.lVal |= v1.lVal;
  730. break;
  731. default:
  732. assert(false);
  733. return E_UNEXPECTED;
  734. }
  735. return S_OK;
  736. }
  737. // O.K. This is a bit funky, but bear with me. This function has four different behaviors determined by the first (e) parameter.
  738. // This is some ugly code, but at least this way I get to use it for multiple purposes.
  739. // _get: Returns the value of the variable reference via out parameter v.
  740. // _put: Sets the value of the variable reference to the in parameter v.
  741. // _putref: Same as _put, but assigns by reference ala VB's 'set' statements.
  742. // _call: Same as _get, but returns the second-to-last value in the chain via out parameter v.
  743. // For example, if the reference is 'a.b.c' this returns the value of 'a.b', which can then be used to invoke function c.
  744. // It is an error to call VariableReferenceInternal in this way with only a single item such as 'a'.
  745. HRESULT
  746. Executor::VariableReferenceInternal(DispatchOperationType e, Variables::index ivarref, VARIANT &v, EXCEPINFO *pExcepInfo, UINT iLocals)
  747. {
  748. HRESULT hr = S_OK;
  749. VariableReference r = m_script.varrefs[ivarref];
  750. bool fGlobal = r.k == VariableReference::_global;
  751. SmartVariant svar;
  752. assert(m_script.rnames[r.irname].istrIdentifier != -1);
  753. bool fJustOnePart = m_script.rnames[r.irname + 1].istrIdentifier == -1;
  754. if (fJustOnePart && e == _call)
  755. {
  756. assert(false);
  757. return E_UNEXPECTED;
  758. }
  759. //
  760. // Handle the base item of the reference, which is either a script variable or an item on the global dispatch.
  761. // If we're doing a set and there aren't more parts to the rnames, just do the set.
  762. // Otherwise, get the result into 'var'.
  763. //
  764. // Example:
  765. // x = 1
  766. // There is just one part and it is a set. Determine whether x is in the script or part of the global dispatch, set it to 1,
  767. // and we're done.
  768. // x.y = 1
  769. // x is the base. Determine whether x is in the script or part of the global dispatch and get its value. (We'll worry about
  770. // setting the y property later in this function.
  771. //
  772. // check if the base is part of the global dispatch
  773. DISPID dispid = DISPID_UNKNOWN;
  774. if (fGlobal)
  775. {
  776. dispid = m_script.globals[r.ivar].dispid;
  777. }
  778. if (dispid != DISPID_UNKNOWN)
  779. {
  780. // base is part of global dispatch
  781. if (fJustOnePart && (e == _put || e == _putref))
  782. {
  783. // set it and we're done
  784. hr = SetDispatchProperty(m_scomGlobalDispatch, dispid, e == _putref, v, pExcepInfo);
  785. hr = ErrorIfInvokeProblem(e, hr, m_script.globals[r.ivar].istrIdentifier, pExcepInfo);
  786. return hr;
  787. }
  788. else
  789. {
  790. hr = GetDispatchProperty(m_scomGlobalDispatch, dispid, svar, pExcepInfo);
  791. hr = ErrorIfInvokeProblem(e, hr, m_script.globals[r.ivar].istrIdentifier, pExcepInfo);
  792. if (FAILED(hr))
  793. return hr;
  794. }
  795. }
  796. else
  797. {
  798. // base is in script
  799. VARIANT &vVariable = m_stack[r.ivar + (fGlobal ? 0 : iLocals)];
  800. if (fJustOnePart && (e == _put || e == _putref))
  801. {
  802. // set it and we're done
  803. hr = ErrorIfImproperRef(v, e == _putref, m_script.rnames[r.irname].istrIdentifier, pExcepInfo);
  804. if (FAILED(hr))
  805. return hr;
  806. hr = DMS_VariantCopy(g_fUseOleAut, &vVariable, &v);
  807. return hr;
  808. }
  809. else
  810. {
  811. hr = DMS_VariantCopy(g_fUseOleAut, &svar, &vVariable);
  812. if (FAILED(hr))
  813. return hr;
  814. }
  815. }
  816. //
  817. // Great! The base value is now held in svar. Any remaining rnames are a chain of properties we need to get from that object.
  818. // And the last rname needs to be a set if we're in one of the put modes or the last name is ignored if we're in the _call mode.
  819. //
  820. if (m_script.rnames[r.irname + 1].istrIdentifier != -1)
  821. {
  822. // the base value must be of object type
  823. hr = ErrorIfImproperRef(svar, true, m_script.rnames[r.irname].istrIdentifier, pExcepInfo);
  824. if (FAILED(hr))
  825. return hr;
  826. for (ReferenceNames::index irname = r.irname + 1; /* ever */; ++irname)
  827. {
  828. bool fLastPart = m_script.rnames[irname + 1].istrIdentifier == -1;
  829. if (fLastPart && e == _call)
  830. break;
  831. // get its IDispatch interface
  832. hr = ChangeToDispatch(svar, pExcepInfo, irname - 1);
  833. if (FAILED(hr))
  834. return hr;
  835. IDispatch *pDisp = static_cast<VARIANT>(svar).pdispVal;
  836. // get the dispid
  837. ReferenceName &rname = m_script.rnames[irname];
  838. DISPID dispidName = GetDispID(pDisp, m_script.strings[rname.istrIdentifier]);
  839. if (dispidName == DISPID_UNKNOWN)
  840. return Error(pExcepInfo, false, L"The property '", m_script.strings[rname.istrIdentifier], L"' does not exist");
  841. if (fLastPart && (e == _put || e == _putref))
  842. {
  843. // set it and we're done
  844. hr = SetDispatchProperty(pDisp, dispidName, e == _putref, v, pExcepInfo);
  845. hr = ErrorIfInvokeProblem(e, hr, rname.istrIdentifier, pExcepInfo);
  846. return hr;
  847. }
  848. else
  849. {
  850. hr = GetDispatchProperty(pDisp, dispidName, svar, pExcepInfo);
  851. hr = ErrorIfInvokeProblem(e, hr, rname.istrIdentifier, pExcepInfo);
  852. if (FAILED(hr))
  853. return hr;
  854. }
  855. if (fLastPart)
  856. {
  857. // we've done all the names
  858. break;
  859. }
  860. else
  861. {
  862. // the new value must be of object type
  863. hr = ErrorIfImproperRef(svar, true, rname.istrIdentifier, pExcepInfo);
  864. if (FAILED(hr))
  865. return hr;
  866. }
  867. }
  868. }
  869. //
  870. // We're done. Now we just have to return the value we calculated. (We know that a set would have already returned.)
  871. //
  872. hr = DMS_VariantCopy(g_fUseOleAut, &v, &svar);
  873. return hr;
  874. }
  875. HRESULT
  876. Executor::ChangeToDispatch(VARIANT &var, EXCEPINFO *pExcepInfo, ReferenceNames::index irnameIdentifier)
  877. {
  878. HRESULT hr = DMS_VariantChangeType(g_fUseOleAut, &var, &var, 0, VT_DISPATCH);
  879. if (FAILED(hr))
  880. return ErrorObjectRequired(m_script.rnames[irnameIdentifier].istrIdentifier, pExcepInfo);
  881. return S_OK;
  882. }