// Copyright (c) 1999 Microsoft Corporation. All rights reserved. // // Implementation of Executor. // #include "stdinc.h" #include "enginc.h" #include "engexec.h" #include "math.h" #include "packexception.h" ////////////////////////////////////////////////////////////////////// // CallStack HRESULT CallStack::Push(UINT i) { UINT iInitialSize = m_vec.size(); if (!m_vec.AccessTo(m_iNext + i - 1)) return E_OUTOFMEMORY; UINT iNewNext = m_iNext + i; for (UINT iInit = std::_MAX(m_iNext, iInitialSize); iInit < iNewNext; ++iInit) DMS_VariantInit(g_fUseOleAut, &m_vec[iInit]); m_iNext = iNewNext; return S_OK; } void CallStack::PopTo(UINT i) { for (UINT iInit = i; iInit < m_iNext; ++iInit) DMS_VariantClear(g_fUseOleAut, &m_vec[iInit]); m_iNext = std::_MIN(m_iNext, i); } ////////////////////////////////////////////////////////////////////// // Executor Executor::Executor(Script &script, IDispatch *pGlobalDispatch) : m_fInitialized(false), m_script(script), m_scomGlobalDispatch(pGlobalDispatch) { DMS_VariantInit(g_fUseOleAut, &m_varEmpty); } Executor::~Executor() { m_stack.PopTo(0); // clear any varients on the stack that might be holding refs } HRESULT Executor::SetGlobal(Variables::index ivar, const VARIANT &varValue, bool fPutRef, EXCEPINFO *pExcepInfo) { HRESULT hr = EnsureInitialized(); if (FAILED(hr)) return hr; hr = ErrorIfImproperRef(varValue, fPutRef, m_script.globals[ivar].istrIdentifier, pExcepInfo); if (FAILED(hr)) return hr; assert(ivar <= m_script.globals.Next()); return DMS_VariantCopy(g_fUseOleAut, &m_stack[ivar], &varValue); } const VARIANT & Executor::GetGlobal(Variables::index ivar) { if (!m_fInitialized) { // No variable gets or routine calls have been performed yet (or they failed). // But we don't want to return an error here. Since nothing's been used yet, the correct // thing to do is to return an empty value. return m_varEmpty; } assert(ivar <= m_script.globals.Next()); return m_stack[ivar]; } HRESULT Executor::ExecRoutine(Routines::index irtn, EXCEPINFO *pExcepInfo) { HRESULT hr = EnsureInitialized(); if (FAILED(hr)) return hr; Routine r = m_script.routines[irtn]; UINT iLocals = m_stack.Next(); hr = m_stack.Push(r.ivarNextLocal); if (FAILED(hr)) return hr; hr = ExecStatements(r.istmtBody, pExcepInfo, iLocals); m_stack.PopTo(iLocals); return hr; } HRESULT Executor::EnsureInitialized() { if (m_fInitialized) return S_OK; // we'll keep the global variables right at the bottom of the stack // this function ensures that they get pushed on before any operations that use them HRESULT hr = m_stack.Push(m_script.globals.Next()); if (FAILED(hr)) return hr; // Also set the first items to the build in constant values True, False, and Nothing. // See also engparse.cpp which creates these global variables before parsing each script. if (m_stack.Next() < 3) { assert(false); return E_UNEXPECTED; } VARIANT &vTrue = m_stack[0]; vTrue.vt = VT_I4; vTrue.lVal = VARIANT_TRUE; VARIANT &vFalse = m_stack[1]; vFalse.vt = VT_I4; vFalse.lVal = VARIANT_FALSE; VARIANT &vNothing = m_stack[2]; vNothing.vt = VT_UNKNOWN; vNothing.punkVal = NULL; m_fInitialized = true; return S_OK; } HRESULT Executor::Error(EXCEPINFO *pExcepInfo, bool fOperation, const WCHAR *pwszBeginning, const char *paszMiddle, const WCHAR *pwszEnd) { if (!pExcepInfo) { assert(false); // our script host should always request error info return DISP_E_EXCEPTION; } // NULL for beginning, middle, or end treated as empty string if (!pwszBeginning) pwszBeginning = L""; if (!paszMiddle) paszMiddle = ""; if (!pwszEnd) pwszEnd = L""; pExcepInfo->wCode = 0; pExcepInfo->wReserved = 0; pExcepInfo->bstrSource = DMS_SysAllocString(g_fUseOleAut, fOperation ? L"Microsoft AudioVBScript Operation Failed" : L"Microsoft AudioVBScript Runtime Error"); SmartRef::WString wstrMiddle = paszMiddle; WCHAR *pwszDescription = NULL; if (wstrMiddle) { pwszDescription = new WCHAR[wcslen(pwszBeginning) + wcslen(wstrMiddle) + wcslen(pwszEnd) + 1]; } if (!pwszDescription) { // Oh well. Just return no description if we're out of memory. pExcepInfo->bstrDescription = NULL; } else { wcscpy(pwszDescription, pwszBeginning); wcscat(pwszDescription, wstrMiddle); wcscat(pwszDescription, pwszEnd); pExcepInfo->bstrDescription = DMS_SysAllocString(g_fUseOleAut, pwszDescription); delete[] pwszDescription; } pExcepInfo->bstrHelpFile = NULL; pExcepInfo->pvReserved = NULL; pExcepInfo->pfnDeferredFillIn = NULL; pExcepInfo->scode = fOperation ? DMUS_E_AUDIOVBSCRIPT_OPERATIONFAILURE : DMUS_E_AUDIOVBSCRIPT_RUNTIMEERROR; return DISP_E_EXCEPTION; } HRESULT Executor::ErrorIfImproperRef(const VARIANT &v, bool fRef, Strings::index istrIdentifier, EXCEPINFO *pExcepInfo) { bool fIsObject = v.vt == VT_DISPATCH || v.vt == VT_UNKNOWN; if (fRef != fIsObject) { if (fRef) return ErrorObjectRequired(istrIdentifier, pExcepInfo); else return Error(pExcepInfo, false, L"Type mismatch: '", m_script.strings[istrIdentifier], L"'. Likely cause is missing Set statement."); } return S_OK; } // Check for the error HRESULTs returned by IDispatch::Invoke. Those that we expect to occur in AudioVBScript need to // be converted into exception (DISP_E_EXCEPTION) so that the user gets a nice error message. // The first parameter lets us know the kind of Invoke call that was made (property get, property set, function/sub call) // so that we can tailor the message. HRESULT Executor::ErrorIfInvokeProblem(DispatchOperationType e, HRESULT hr, Strings::index istrIdentifier, EXCEPINFO *pExcepInfo) { if (SUCCEEDED(hr) || HRESULT_FACILITY(hr) != FACILITY_DISPATCH || hr == DISP_E_EXCEPTION) return hr; const char *pszName = m_script.strings[istrIdentifier]; if (hr == DISP_E_BADPARAMCOUNT) { // This can happen with a _call (obviously) and also with a get because property gets are also treated as function // calls with no arguments. "x=GetMasterVolume" is valid but "x=Trace" would produce this error. But I can't // see that this should occur with property sets. assert(e == _get || e == _call); return Error(pExcepInfo, false, L"Wrong number of parameters in call to '", pszName, L"'"); } else if (hr == DISP_E_MEMBERNOTFOUND) { if (e == _call) { // Because Invoke was called, GetIDsOfNames must have succeeded, so the thing's name exists // but it must not be a method. return Error(pExcepInfo, false, L"Type mismatch: '", pszName, L"' is not a routine or method"); } else if (e == _put || e == _putref) { return Error(pExcepInfo, false, L"Type mismatch: '", pszName, L"' is not a variable or is a read-only property"); } else { // As mentioned above, a property get can be treated as either gets or function calls so they // shouldn't fail in this way. assert(false); } } else if (hr == DISP_E_TYPEMISMATCH) { // This indicates that one of the parameters was of the wrong type. if (e == _call) { return Error(pExcepInfo, false, L"Type mismatch: a parameter in call to '", pszName, L"' is not of the expected type"); } else if (e == _put || e == _putref) { return Error(pExcepInfo, false, L"Type mismatch: value assigned to '", pszName, L"' is not of the expected type"); } else { // Property gets don't have any parameters so this shouldn't happen. assert(false); } } else if (hr == DISP_E_PARAMNOTOPTIONAL) { if (e == _call) { return Error(pExcepInfo, false, L"A required parameter was omitted in call to '", pszName, L"'"); } else { // Only calls should send an optional parameters. assert(false); } } // The other errors shouldn't normally occur in AudioVBScript. They could occur if someone was // doing something ususual in a custom IDispatch interface, but we'll consider them exceptional cases and // just return the error HRESULT (meaning the user won't get a friendly text message). Assert so we'll // find out if there are regular cases where this is happening in our testing. assert(false); // DISP_E_BADVARTYPE: We just use standard variant types. // DISP_E_NONAMEDARGS: We don't do named args. // DISP_E_OVERFLOW: AudioVBScript uses VT_I4 and so do our DMusic dispatch interfaces. // DISP_E_PARAMNOTFOUND: Only applies with named args. // DISP_E_UNKNOWNINTERFACE, DISP_E_UNKNOWNLCID: AudioVBScript uses calling convention and locale matching the DMusic dispatch interfaces. return hr; } HRESULT Executor::ExecStatements(Statements::index istmt, EXCEPINFO *pExcepInfo, UINT iLocals) { HRESULT hr = S_OK; for (Statements::index istmtCur = istmt; /* ever */; ++istmtCur) { // §§ Check if this generates fast retail code. If not, walk a pointer instead of using the index. Statement s = m_script.statements[istmtCur]; switch (s.k) { case Statement::_end: return hr; case Statement::_asgn: hr = ExecAssignment(s.iasgn, pExcepInfo, iLocals); break; case Statement::_if: hr = ExecIf(s.iif, pExcepInfo, iLocals); istmtCur = s.istmtIfTail - 1; break; case Statement::_call: hr = ExecCall(s.icall, false, pExcepInfo, iLocals); break; } if (FAILED(hr)) { if (hr == DISP_E_EXCEPTION) { // Save the statement's line number in the exception info. // Hack: See packexception.h for more info ULONG ulLine = s.iLine - 1; // The IActiveScript interfaces expects zero-based line and column numbers while we have them one-based. PackExceptionFileAndLine(g_fUseOleAut, pExcepInfo, NULL, &ulLine); } return hr; } } } HRESULT Executor::ExecAssignment(Assignments::index iasgn, EXCEPINFO *pExcepInfo, UINT iLocals) { Assignment a = m_script.asgns[iasgn]; VARIANT var; DMS_VariantInit(g_fUseOleAut, &var); HRESULT hr = EvalExpression(var, a.iexprRHS, pExcepInfo, iLocals); if (FAILED(hr)) return hr; hr = SetVariableReference(a.fSet, a.ivarrefLHS, var, pExcepInfo, iLocals); DMS_VariantClear(g_fUseOleAut, &var); if (FAILED(hr)) return hr; return S_OK; } HRESULT Executor::ExecIf(IfBlocks::index iif, EXCEPINFO *pExcepInfo, UINT iLocals) { for (IfBlocks::index i = iif; /* ever */; ++i) { IfBlock &ib = m_script.ifs[i]; if (ib.k == IfBlock::_end) return S_OK; bool fMatch = true; // default to true because an else block always matches if (ib.k == IfBlock::_cond) { // if the condition isn't true, set match to false SmartVariant svar; EvalExpression(svar, ib.iexprCondition, pExcepInfo, iLocals); VARTYPE vt = static_cast(svar).vt; if (vt != VT_I4) { if (vt == VT_BSTR) 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); else if (vt == VT_UNKNOWN || vt == VT_DISPATCH) 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); return Error(pExcepInfo, false, L"Type mismatch: the condition of an if statement did not evaluate to a numeric True/False value", NULL, NULL); } if (static_cast(svar).lVal != VARIANT_TRUE) fMatch = false; } if (fMatch) { // found the block to take -- execute its statements and we're done return ExecStatements(ib.istmtBlock, pExcepInfo, iLocals); } } return S_OK; } // Helper function that eats up a set amount of stack space. const UINT g_uiExecCallCheckStackBytes = 1484 * 4; void ExecCallCheckStack(); // Helper function that returns true if the exception code needs to be caught. LONG ExecCallExceptionFilter(DWORD dwExceptionCode) { // We need to access violations as well as stack overflows. The first time we run out // of stack space we get a stack overflow. The next time we get an access violation. return dwExceptionCode == EXCEPTION_STACK_OVERFLOW || dwExceptionCode == EXCEPTION_ACCESS_VIOLATION; } HRESULT Executor::ExecCall(Calls::index icall, bool fPushResult, EXCEPINFO *pExcepInfo, UINT iLocals) { // This is a wrapper for ExecCallInternal, which actually does the work. Here, we just want // to catch a potential stack overflow and return it as an error instead of GPF-ing. HRESULT hr = E_FAIL; __try { // It is better to fail now than to actually go ahead and call the routine and fail at some point we // can't predict. Routines could do lots of different things including calling into DirectMusic or the // OS and we can't be sure we'd get the stack overflow exception and return in a good state. This // routine uses more stack space than we'd expect recursive calls to require to get back to this point // again. In essence, it clears the way, checking if there's enough stack space in a way we know is safe. ExecCallCheckStack(); #ifdef DBG // The value for g_uiExecCallCheckStackBytes was determined by experiment. Each time through ExecCall, // the following code prints out the address of a char on the current stack and the difference between // the previous call. I found that two scripts, which each evaluated an if statement (always true) and // then called the other one produced a difference of 1476. Then I multiplied that by 4 for good measure. char c; static char *s_pchPrev = &c; DWORD s_dwPrevThreadID = 0; DWORD dwGrowth = 0; if (s_pchPrev > &c && s_dwPrevThreadID == GetCurrentThreadId()) dwGrowth = s_pchPrev-&c; TraceI(4, "Stack: 0x%08x, -%lu\n", &c, dwGrowth); // This assert will fire if a path is executed where a recursive path back to this function takes // more stack space than g_uiExecCallCheckStackBytes. If that's the case then g_uiExecCallCheckStackBytes // probably needs to be increased. assert(dwGrowth <= g_uiExecCallCheckStackBytes); s_pchPrev = &c; s_dwPrevThreadID = GetCurrentThreadId(); #endif // If we fail inside this call, it means g_uiExecCallCheckStackBytes probably needs to be increased because // ExecCallCheckStack didn't catch the stack overflow. hr = ExecCallInternal(icall, fPushResult, pExcepInfo, iLocals); } __except(ExecCallExceptionFilter(GetExceptionCode())) { Trace(1, "Error: Stack overflow.\n"); // determine routine name Call &c = m_script.calls[icall]; const char *pszCall = NULL; if (c.k == Call::_global) { pszCall = m_script.strings[c.istrname]; } else { // name to use is last of the call's reference names for (ReferenceNames::index irname = m_script.varrefs[c.ivarref].irname; m_script.rnames[irname].istrIdentifier != -1; ++irname) {} pszCall = m_script.strings[m_script.rnames[irname - 1].istrIdentifier]; } if (GetExceptionCode() == EXCEPTION_STACK_OVERFLOW) { hr = Error(pExcepInfo, false, L"Out of stack space: '", pszCall, L"'. Too many nested function calls."); } else { hr = Error(pExcepInfo, false, L"Out of stack space or catastrophic error: '", pszCall, L"'."); } } return hr; } // This function doesn't actually do anything besides occupying stack space. Turn off optimization so the // copiler doesn't get all clever on us and skip it. #pragma optimize("", off) void ExecCallCheckStack() { char chFiller[g_uiExecCallCheckStackBytes]; chFiller[g_uiExecCallCheckStackBytes - 1] = '\0'; } #pragma optimize("", on) HRESULT Executor::ExecCallInternal(Calls::index icall, bool fPushResult, EXCEPINFO *pExcepInfo, UINT iLocals) { HRESULT hr = S_OK; SmartVariant svar; // holds temporary variant values at various points SmartVariant svar2; // ditto Call &c = m_script.calls[icall]; IDispatch *pDispCall = NULL; Strings::index istrCall = 0; const char *pszCall = NULL; if (c.k == Call::_global) { istrCall = c.istrname; pszCall = m_script.strings[istrCall]; // Handle the call directly if it is a call to one of the script's own Routines. Routines::index irtnLast = m_script.routines.Next(); for (Routines::index irtn = 0; irtn < irtnLast; ++irtn) { if (0 == _stricmp(pszCall, m_script.strings[m_script.routines[irtn].istrIdentifier])) { return ExecRoutine(irtn, pExcepInfo); } } // Must be a call to the global script API. pDispCall = m_scomGlobalDispatch; } else { assert(c.k == Call::_dereferenced); // count the reference names (needed later) for (ReferenceNames::index irname = m_script.varrefs[c.ivarref].irname; m_script.rnames[irname].istrIdentifier != -1; ++irname) {} assert(irname - m_script.varrefs[c.ivarref].irname > 1); // if there was only one name, this should have been a global call hr = VariableReferenceInternal(_call, c.ivarref, svar, pExcepInfo, iLocals); if (FAILED(hr)) return hr; hr = ChangeToDispatch(svar, pExcepInfo, irname - 2); if (FAILED(hr)) return hr; pDispCall = static_cast(svar).pdispVal; // the method name is the last reference name istrCall = m_script.rnames[irname - 1].istrIdentifier; pszCall = m_script.strings[istrCall]; } DISPID dispidCall = GetDispID(pDispCall, pszCall); if (dispidCall == DISPID_UNKNOWN) { return Error(pExcepInfo, false, L"The routine '", pszCall, L"' does not exist"); } // We'll push the parameters onto the stack. (The function we're calling doesn't actually read them directly using the stack, but // it is a convenient place for us to keep them temporarily.) // First, count the parameters. UINT cParams = 0; for (ExprBlocks::index iexpr = c.iexprParams; m_script.exprs[iexpr]; ++iexpr) { // each parameter is an expression terminated by an end block ++cParams; while (m_script.exprs[++iexpr]) {} } // Make space for them. UINT iParamSlots = m_stack.Next(); hr = m_stack.Push(std::_MAX(cParams, fPushResult ? 1 : 0)); // even if there are no params, leave one slot for the result if fPushResult is true if (FAILED(hr)) return hr; // Fill the params in reverse order. iexpr = c.iexprParams; for (UINT iParam = iParamSlots + cParams - 1; iParam >= iParamSlots; --iParam) { if (m_script.exprs[iexpr].k == ExprBlock::_omitted) { // write the variant value IDispatch::Invoke uses for an omitted parameter m_stack[iParam].vt = VT_ERROR; m_stack[iParam].scode = DISP_E_PARAMNOTFOUND; } else { hr = EvalExpression(svar, iexpr, pExcepInfo, iLocals); if (FAILED(hr)) return hr; hr = DMS_VariantCopy(g_fUseOleAut, &m_stack[iParam], &svar); if (FAILED(hr)) return hr; } // each parameter is an expression terminated by an end block ++iexpr; while (m_script.exprs[iexpr++]) {} } DISPPARAMS dispparams; Zero(&dispparams); dispparams.rgvarg = cParams > 0 ? &m_stack[iParamSlots] : NULL; dispparams.rgdispidNamedArgs = NULL; dispparams.cArgs = cParams; dispparams.cNamedArgs = 0; // Make the call. // Push the result onto the stack if fPushResult is true. hr = InvokeAttemptingNotToUseOleAut( pDispCall, dispidCall, DISPATCH_METHOD, &dispparams, 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. pExcepInfo, NULL); hr = ErrorIfInvokeProblem(_call, hr, istrCall, pExcepInfo); if (SUCCEEDED(hr) && fPushResult) { hr = DMS_VariantCopy(g_fUseOleAut, &m_stack[iParamSlots], &svar2); } m_stack.PopTo(iParamSlots + (fPushResult ? 1 : 0)); if (FAILED(hr)) return hr; return S_OK; } // possible error-message type returns: DISP_E_TYPEMISMATCH HRESULT Executor::EvalExpression(VARIANT &varResult, ExprBlocks::index iexpr, EXCEPINFO *pExcepInfo, UINT iLocals) { HRESULT hr = S_OK; UINT iTempSlots = m_stack.Next(); for (ExprBlocks::index iexprCur = iexpr; /* ever */; ++iexprCur) { ExprBlock &e = m_script.exprs[iexprCur]; switch (e.k) { case ExprBlock::_end: // pop the result and return it if (m_stack.Next() != iTempSlots + 1) { assert(false); return E_FAIL; } DMS_VariantCopy(g_fUseOleAut, &varResult, &m_stack[iTempSlots]); m_stack.PopTo(iTempSlots); return hr; case ExprBlock::_op: // Pop one (unary operator) or two (binary operator) items, apply the operator, and push the result. // (Actually, I just assign the result into the stack instead of pushing it, but conceptually the is // the same as popping and pushing the new value.) { Token t = e.op; bool fUnary = t == TOKEN_op_not || t == TOKEN_sub; UINT iNext = m_stack.Next(); if (iNext < iTempSlots + (fUnary ? 1 : 2)) { assert(false); return E_FAIL; } VARIANT &v1 = m_stack[iNext - 1]; if (fUnary) hr = EvalUnaryOp(t, v1); else { VARIANT &v2 = m_stack[iNext - 2]; hr = EvalBinaryOp(t, v1, v2, pExcepInfo); m_stack.PopTo(iNext - 1); } } break; case ExprBlock::_val: { // push it hr = m_stack.Push(1); VARIANT &varToPush = m_stack[m_stack.Next() - 1]; if (SUCCEEDED(hr)) hr = EvalValue(e.ival, varToPush, pExcepInfo, iLocals); if (varToPush.vt == VT_EMPTY) { // treat an empty value as zero varToPush.vt = VT_I4; varToPush.lVal = 0; } } break; case ExprBlock::_call: // push it if (SUCCEEDED(hr)) hr = ExecCall(e.icall, true, pExcepInfo, iLocals); break; } if (FAILED(hr)) { m_stack.PopTo(iTempSlots); return hr; } } return S_OK; } HRESULT Executor::EvalValue(Values::index ival, VARIANT &v, EXCEPINFO *pExcepInfo, UINT iLocals) { Value val = m_script.vals[ival]; switch (val.k) { case Value::_numvalue: v.vt = VT_I4; v.lVal = val.inumvalue; break; case Value::_strvalue: { v.vt = VT_BSTR; SmartRef::WString wstr = m_script.strings[val.istrvalue]; if (!wstr) return E_OUTOFMEMORY; v.bstrVal = DMS_SysAllocString(g_fUseOleAut, wstr); if (!v.bstrVal) return E_OUTOFMEMORY; break; } case Value::_varref: HRESULT hr = GetVariableReference(val.ivarref, v, pExcepInfo, iLocals); if (FAILED(hr)) return hr; } return S_OK; } HRESULT Executor::EvalUnaryOp(Token t, VARIANT &v) { if (v.vt != VT_I4) { assert(false); return DISP_E_TYPEMISMATCH; } if (t == TOKEN_op_not) { v.lVal = ~v.lVal; } else { assert(t == TOKEN_sub); v.lVal = -v.lVal; } return S_OK; } // Returns a proper VB boolean value (0 for false, -1 for true) inline LONG BoolForVB(bool f) { return f ? VARIANT_TRUE : VARIANT_FALSE; } HRESULT Executor::EvalBinaryOp(Token t, VARIANT &v1, VARIANT &v2, EXCEPINFO *pExcepInfo) { if (v1.vt == VT_DISPATCH || v1.vt == VT_UNKNOWN) { // the only operator that accepts object values is is if (t != TOKEN_is || !(v2.vt == VT_DISPATCH || v2.vt == VT_UNKNOWN)) { assert(false); return DISP_E_TYPEMISMATCH; } HRESULT hr = DMS_VariantChangeType(g_fUseOleAut, &v1, &v1, 0, VT_UNKNOWN); if (FAILED(hr)) return hr; hr = DMS_VariantChangeType(g_fUseOleAut, &v2, &v2, 0, VT_UNKNOWN); if (FAILED(hr)) return hr; bool fIs = v1.punkVal == v2.punkVal; hr = DMS_VariantClear(g_fUseOleAut, &v2); if (FAILED(hr)) return hr; v2.vt = VT_I4; v2.lVal = BoolForVB(fIs); return S_OK; } if (v1.vt != VT_I4 || v2.vt != VT_I4) { assert(false); return DISP_E_TYPEMISMATCH; } switch (t) { case TOKEN_op_minus: v2.lVal -= v1.lVal; break; case TOKEN_op_pow: v2.lVal = _Pow_int(v2.lVal, v1.lVal); break; case TOKEN_op_mult: v2.lVal *= v1.lVal; break; case TOKEN_op_div: if (v1.lVal == 0) return Error(pExcepInfo, false, L"Division by zero", NULL, NULL); v2.lVal /= v1.lVal; break; case TOKEN_op_mod: if (v1.lVal == 0) return Error(pExcepInfo, false, L"Mod by zero", NULL, NULL); v2.lVal %= v1.lVal; break; case TOKEN_op_plus: v2.lVal += v1.lVal; break; case TOKEN_op_lt: v2.lVal = BoolForVB(v2.lVal < v1.lVal); break; case TOKEN_op_leq: v2.lVal = BoolForVB(v2.lVal <= v1.lVal); break; case TOKEN_op_gt: v2.lVal = BoolForVB(v2.lVal > v1.lVal); break; case TOKEN_op_geq: v2.lVal = BoolForVB(v2.lVal >= v1.lVal); break; case TOKEN_op_eq: v2.lVal = BoolForVB(v2.lVal == v1.lVal); break; case TOKEN_op_neq: v2.lVal = BoolForVB(v2.lVal != v1.lVal); break; case TOKEN_and: v2.lVal &= v1.lVal; break; case TOKEN_or: v2.lVal |= v1.lVal; break; default: assert(false); return E_UNEXPECTED; } return S_OK; } // O.K. This is a bit funky, but bear with me. This function has four different behaviors determined by the first (e) parameter. // This is some ugly code, but at least this way I get to use it for multiple purposes. // _get: Returns the value of the variable reference via out parameter v. // _put: Sets the value of the variable reference to the in parameter v. // _putref: Same as _put, but assigns by reference ala VB's 'set' statements. // _call: Same as _get, but returns the second-to-last value in the chain via out parameter v. // 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. // It is an error to call VariableReferenceInternal in this way with only a single item such as 'a'. HRESULT Executor::VariableReferenceInternal(DispatchOperationType e, Variables::index ivarref, VARIANT &v, EXCEPINFO *pExcepInfo, UINT iLocals) { HRESULT hr = S_OK; VariableReference r = m_script.varrefs[ivarref]; bool fGlobal = r.k == VariableReference::_global; SmartVariant svar; assert(m_script.rnames[r.irname].istrIdentifier != -1); bool fJustOnePart = m_script.rnames[r.irname + 1].istrIdentifier == -1; if (fJustOnePart && e == _call) { assert(false); return E_UNEXPECTED; } // // Handle the base item of the reference, which is either a script variable or an item on the global dispatch. // If we're doing a set and there aren't more parts to the rnames, just do the set. // Otherwise, get the result into 'var'. // // Example: // x = 1 // 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, // and we're done. // x.y = 1 // 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 // setting the y property later in this function. // // check if the base is part of the global dispatch DISPID dispid = DISPID_UNKNOWN; if (fGlobal) { dispid = m_script.globals[r.ivar].dispid; } if (dispid != DISPID_UNKNOWN) { // base is part of global dispatch if (fJustOnePart && (e == _put || e == _putref)) { // set it and we're done hr = SetDispatchProperty(m_scomGlobalDispatch, dispid, e == _putref, v, pExcepInfo); hr = ErrorIfInvokeProblem(e, hr, m_script.globals[r.ivar].istrIdentifier, pExcepInfo); return hr; } else { hr = GetDispatchProperty(m_scomGlobalDispatch, dispid, svar, pExcepInfo); hr = ErrorIfInvokeProblem(e, hr, m_script.globals[r.ivar].istrIdentifier, pExcepInfo); if (FAILED(hr)) return hr; } } else { // base is in script VARIANT &vVariable = m_stack[r.ivar + (fGlobal ? 0 : iLocals)]; if (fJustOnePart && (e == _put || e == _putref)) { // set it and we're done hr = ErrorIfImproperRef(v, e == _putref, m_script.rnames[r.irname].istrIdentifier, pExcepInfo); if (FAILED(hr)) return hr; hr = DMS_VariantCopy(g_fUseOleAut, &vVariable, &v); return hr; } else { hr = DMS_VariantCopy(g_fUseOleAut, &svar, &vVariable); if (FAILED(hr)) return hr; } } // // Great! The base value is now held in svar. Any remaining rnames are a chain of properties we need to get from that object. // 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. // if (m_script.rnames[r.irname + 1].istrIdentifier != -1) { // the base value must be of object type hr = ErrorIfImproperRef(svar, true, m_script.rnames[r.irname].istrIdentifier, pExcepInfo); if (FAILED(hr)) return hr; for (ReferenceNames::index irname = r.irname + 1; /* ever */; ++irname) { bool fLastPart = m_script.rnames[irname + 1].istrIdentifier == -1; if (fLastPart && e == _call) break; // get its IDispatch interface hr = ChangeToDispatch(svar, pExcepInfo, irname - 1); if (FAILED(hr)) return hr; IDispatch *pDisp = static_cast(svar).pdispVal; // get the dispid ReferenceName &rname = m_script.rnames[irname]; DISPID dispidName = GetDispID(pDisp, m_script.strings[rname.istrIdentifier]); if (dispidName == DISPID_UNKNOWN) return Error(pExcepInfo, false, L"The property '", m_script.strings[rname.istrIdentifier], L"' does not exist"); if (fLastPart && (e == _put || e == _putref)) { // set it and we're done hr = SetDispatchProperty(pDisp, dispidName, e == _putref, v, pExcepInfo); hr = ErrorIfInvokeProblem(e, hr, rname.istrIdentifier, pExcepInfo); return hr; } else { hr = GetDispatchProperty(pDisp, dispidName, svar, pExcepInfo); hr = ErrorIfInvokeProblem(e, hr, rname.istrIdentifier, pExcepInfo); if (FAILED(hr)) return hr; } if (fLastPart) { // we've done all the names break; } else { // the new value must be of object type hr = ErrorIfImproperRef(svar, true, rname.istrIdentifier, pExcepInfo); if (FAILED(hr)) return hr; } } } // // We're done. Now we just have to return the value we calculated. (We know that a set would have already returned.) // hr = DMS_VariantCopy(g_fUseOleAut, &v, &svar); return hr; } HRESULT Executor::ChangeToDispatch(VARIANT &var, EXCEPINFO *pExcepInfo, ReferenceNames::index irnameIdentifier) { HRESULT hr = DMS_VariantChangeType(g_fUseOleAut, &var, &var, 0, VT_DISPATCH); if (FAILED(hr)) return ErrorObjectRequired(m_script.rnames[irnameIdentifier].istrIdentifier, pExcepInfo); return S_OK; }