/***************************************************************************
*
* File : ScriptDebugEngine.cpp
* Author : K. Skilling
* Description : This object wraps the JScript engine and intercepts the
* ParseScriptText method so that it can inject code for
* function entry/exit points.
*
* If the application creating the instance of this object
* is not registered under the JScriptDebug registry key
* a real instance of the JScript engine is returned and
* this object instance is deleted.
*
* Change History
* ---------------
* KS 28/02/2006 CREATED
*****************************************************************************/
#include "stdafx.h"
#include "JScriptDebug.h"
#include "ScriptDebugEngine.h"
#include "JSDUtils.h"
/////////////////////////////////////////////////////////////////////////////
HRESULT CScriptDebugEngine::FinalConstruct()
{
#ifdef _SHOWINSTANCES
sInfo.Format(L"JSD: NEW INSTANCE [%#x] - '%s' [PID = %d]",this,(LPCWSTR)oModule.Name(),GetCurrentProcessId());
OutputDebugString(sInfo);
if (g_bBlankLines)
OutputDebugString(L"JSD:");
#endif
return S_OK;
}
CScriptDebugEngine::CScriptDebugEngine()
{
}
CScriptDebugEngine::~CScriptDebugEngine()
{
ATLTRACE(L"~CScriptDebugEngine - %#x\n",this);
}
/////////////////////////////////////////////////////////////////////////////
// IActiveScript
STDMETHODIMP CScriptDebugEngine::SetScriptSite(
IActiveScriptSite *pass)
{
try
{
HRESULT hr = ::CoCreateInstance(CLSID_ScriptSiteProxy,NULL,CLSCTX_INPROC,IID_IScriptSiteProxy,(VOID**)
&m_pProxy);
if (SUCCEEDED(hr))
{
m_pProxy->Init(pass);
CComQIPtr<IActiveScriptSite> pSite = m_pProxy;
hr = m_pEngine->SetScriptSite(pSite);
// Add debugging object using our unique (hopefully) name
DWORD dwFlags = SCRIPTITEM_ISVISIBLE | SCRIPTITEM_ISSOURCE;
hr = m_pEngine->AddNamedItem(szJSDID,dwFlags);
return hr;
}
return m_pEngine->SetScriptSite(pass);
}
catch(...)
{
ATLTRACE(L"Unexpected exception in ScriptDebugEngine::SetScriptSite\n");
return E_FAIL;
}
}
/////////////////////////////////////////////////////////////////////////////
//IActiveScriptParse
STDMETHODIMP CScriptDebugEngine::AddScriptlet(
LPCOLESTR pstrDefaultName,
LPCOLESTR pstrCode,
LPCOLESTR pstrItemName,
LPCOLESTR pstrSubItemName,
LPCOLESTR pstrEventName,
LPCOLESTR pstrDelimiter,
DWORD dwSourceContextCookie,
ULONG ulStartingLineNumber,
DWORD dwFlags,
BSTR *pbstrName,
EXCEPINFO *pexcepinfo)
{
// NOT instrumented - this is usually just a simple function call as an
// event handler on an html element.
return m_pEngineParse->AddScriptlet(
pstrDefaultName,
pstrCode,
pstrItemName,
pstrSubItemName,
pstrEventName,
pstrDelimiter,
dwSourceContextCookie,
ulStartingLineNumber,
dwFlags,
pbstrName,
pexcepinfo);
}
#define _JSD_NO_TRACE_ 0x1
#define _JSD_FILE_ 0x2
DWORD readDirectives(LPCWSTR szCode,DWORD dwSourceContextCookie)
{
DWORD dwDirectives = 0;
// Copy first section of code and process this only
const nBUFSIZE = 256-1;
WCHAR szCodeBuffer[nBUFSIZE];
PWCHAR p = (LPWSTR)szCode;
int iCounter = 0;
bool bAllBlanks = true;
while (p && *p && iCounter < nBUFSIZE)
szCodeBuffer[iCounter++] = *p++;
szCodeBuffer[iCounter] = 0;
IRegExp2Ptr pRegExp;
HRESULT hr = pRegExp.CreateInstance(CLSID_RegExp);
if (SUCCEEDED(hr))
{
////////////////////////////////////////////////////////////////////////////
// _JSD_FILE_
_bstr_t bstrPattern(L"/\\*\\s*_JSD_FILE_\\s*([a-z|A-Z|.|0-9]+)"); // Has to be a proper BSTR
pRegExp->put_Pattern(bstrPattern);
IDispatchPtr pDispatch;
_bstr_t bstrCode = szCodeBuffer; // Has to be a proper BSTR
hr = pRegExp->raw_Execute(bstrCode,&pDispatch);
if (FAILED(hr)) throw hr;
CComQIPtr<IMatchCollection> pCollection = pDispatch;
long lCount = 0;
hr = pCollection->get_Count(&lCount);
if (FAILED(hr)) throw hr;
if (lCount == 1)
dwDirectives |= _JSD_FILE_;
////////////////////////////////////////////////////////////////////////////
// _JSD_NO_TRACE
bstrPattern = L"/\\*\\s*_JSD_NO_TRACE_"; // Has to be a proper BSTR
pRegExp->put_Pattern(bstrPattern);
hr = pRegExp->raw_Execute(bstrCode,&pDispatch);
if (FAILED(hr)) throw hr;
pCollection = pDispatch;
hr = pCollection->get_Count(&lCount);
if (FAILED(hr)) throw hr;
if (lCount == 1)
dwDirectives |= _JSD_NO_TRACE_;
}
return dwDirectives;
}
STDMETHODIMP CScriptDebugEngine::ParseScriptText(
LPCOLESTR pstrCode,
LPCOLESTR pstrItemName,
IUnknown __RPC_FAR *punkContext,
LPCOLESTR pstrDelimiter,
DWORD dwSourceContextCookie,
ULONG ulStartingLineNumber,
DWORD dwFlags,
VARIANT *pvarResult,
EXCEPINFO *pexcepinfo)
{
LPCWSTR szLOCATION = L"ParseScriptText";
HRESULT hRes = E_FAIL;
::EnterCriticalSection(&g_csInstrument);
try
{
BSTR bstrCode = NULL;
LPCWSTR pCode = NULL;
DWORD dwDirectives = readDirectives(pstrCode,dwSourceContextCookie);
DWORD dwNewFlags = dwFlags;
if (_JSD_NO_TRACE_ & dwDirectives || false == g_bInstrument)
{
pCode = pstrCode;
hRes = S_OK;
}
else
{
// Because the code is instrumented we turn off the host management
dwNewFlags = dwFlags & ~SCRIPTTEXT_HOSTMANAGESSOURCE;
hRes = _instrumentCode(pstrCode,&bstrCode);
pCode = bstrCode;
}
// Save script (if required)
DumpScripts(pCode);
if (SUCCEEDED(hRes))
{
hRes = m_pEngineParse->ParseScriptText(
pCode,
pstrItemName,
punkContext,
pstrDelimiter,
dwSourceContextCookie,
ulStartingLineNumber,
dwNewFlags,
pvarResult,
pexcepinfo);
}
if (bstrCode)
::SysFreeString(bstrCode);
}
catch(HRESULT hr)
{
hRes = ReportError(szLOCATION,hr);
}
catch(...)
{
hRes = ReportError(szLOCATION,E_UNEXPECTED);
}
// On errror just parse the original script
if (FAILED(hRes))
hRes = m_pEngineParse->ParseScriptText(pstrCode,
pstrItemName,
punkContext,
pstrDelimiter,
dwSourceContextCookie,
ulStartingLineNumber,
dwFlags,
pvarResult,
pexcepinfo);
::LeaveCriticalSection(&g_csInstrument);
return g_bRaiseErrors ? hRes : S_OK;
}
/////////////////////////////////////////////////////////////////////////
// Helpers
void CScriptDebugEngine::DumpScripts(LPCWSTR pCode)
{
if (g_szScriptDump[0] && pCode && wcslen(pCode))
{
CMemoryMappedFile mf;
CString sFile;
static int iFile = 0;
USES_CONVERSION;
sFile.Format(L"%s\\script_%d.js",g_szScriptDump,iFile++);
LPCSTR pp = W2A(pCode);
mf.CreateFile(sFile,(PVOID)pp,wcslen(pCode));
mf.Close();
}
}
HRESULT CScriptDebugEngine::GetScriptEngine(CScriptDebugEngine* pTHIS,LPVOID* ppv)
{
HRESULT hRes = S_OK;
if (! pTHIS->m_pEngine)
{
// Get the JScript engine that we delegate to and any other interface we need to wrap
hRes = GetJScriptEngine((VOID**)&pTHIS->m_pEngine);
if (SUCCEEDED(hRes))
pTHIS->m_pEngineParse = CComQIPtr<IActiveScriptParse>(pTHIS->m_pEngine);
}
if (SUCCEEDED(hRes))
{
*ppv = dynamic_cast<IActiveScript*>(pTHIS);
pTHIS->InternalAddRef();
}
return hRes;
}
HRESULT CScriptDebugEngine::GetJScriptEngine(LPVOID* ppv)
{
CComPtr<IClassFactory> pFactory;
HRESULT hRes = JSDUtils::GetJScriptClassObject((VOID**)&pFactory);
if (SUCCEEDED(hRes))
hRes = pFactory->CreateInstance(NULL,IID_IActiveScript,ppv);
return hRes;
}
HRESULT WINAPI CScriptDebugEngine::fnDelegate(void* pv, REFIID riid, LPVOID* ppv, DWORD dw)
{
try
{
HRESULT hr = E_NOINTERFACE;
CScriptDebugEngine* pTHIS = (CScriptDebugEngine*)pv;
if (pTHIS)
{
if (IsEqualGUID(riid,IID_IActiveScript))
{
return GetScriptEngine(pTHIS,ppv);
}
else
{
ATLASSERT(pTHIS->m_pEngine);
return pTHIS->m_pEngine->QueryInterface(riid,ppv);
}
}
return E_NOINTERFACE;
}
catch(...)
{
ATLTRACE(L"Unexpected exception in CScriptDebugEngine::fnDelegate\n");
}
return E_NOINTERFACE;
}