| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272 |
- //-------------------------------------------------------------------------------------------------------
- // Copyright (C) Microsoft. All rights reserved.
- // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
- //-------------------------------------------------------------------------------------------------------
- #include "RuntimeLanguagePch.h"
- #include "shlwapi.h"
- #include "Language\InterpreterStackFrame.h"
- #ifdef _M_IX86
- #ifdef _CONTROL_FLOW_GUARD
- extern "C" PVOID __guard_check_icall_fptr;
- #endif
- #endif
- namespace Js
- {
- void JavascriptExceptionOperators::AutoCatchHandlerExists::FetchNonUserCodeStatus(ScriptContext * scriptContext)
- {
- Assert(scriptContext);
- bool fFound = false;
- // If the outer try catch was already in the user code, no need to go any further.
- if (!m_previousCatchHandlerToUserCodeStatus)
- {
- Js::JavascriptFunction* caller;
- if (JavascriptStackWalker::GetCaller(&caller, scriptContext))
- {
- Js::FunctionBody *funcBody = NULL;
- if (caller != NULL && (funcBody = caller->GetFunctionBody()) != NULL)
- {
- m_threadContext->SetIsUserCode(funcBody->IsNonUserCode() == false);
- fFound = true;
- }
- }
- }
- if (!fFound)
- {
- // If not successfully able to find the caller, set this catch handler belongs to the user code.
- m_threadContext->SetIsUserCode(true);
- }
- }
- JavascriptExceptionOperators::AutoCatchHandlerExists::AutoCatchHandlerExists(ScriptContext* scriptContext)
- {
- Assert(scriptContext);
- m_threadContext = scriptContext->GetThreadContext();
- Assert(m_threadContext);
- m_previousCatchHandlerExists = m_threadContext->HasCatchHandler();
- m_threadContext->SetHasCatchHandler(TRUE);
- m_previousCatchHandlerToUserCodeStatus = m_threadContext->IsUserCode();
- if (scriptContext->IsInDebugMode())
- {
- FetchNonUserCodeStatus(scriptContext);
- }
- }
- JavascriptExceptionOperators::AutoCatchHandlerExists::~AutoCatchHandlerExists()
- {
- m_threadContext->SetHasCatchHandler(m_previousCatchHandlerExists);
- m_threadContext->SetIsUserCode(m_previousCatchHandlerToUserCodeStatus);
- }
- bool JavascriptExceptionOperators::CrawlStackForWER(Js::ScriptContext& scriptContext)
- {
- return Js::Configuration::Global.flags.WERExceptionSupport && !scriptContext.GetThreadContext()->HasCatchHandler();
- }
- uint64 JavascriptExceptionOperators::StackCrawlLimitOnThrow(Var thrownObject, ScriptContext& scriptContext)
- {
- return CrawlStackForWER(scriptContext) ? MaxStackTraceLimit : GetStackTraceLimit(thrownObject, &scriptContext);
- }
- #ifdef _M_X64
- void *JavascriptExceptionOperators::OP_TryCatch(void *tryAddr,
- void *catchAddr,
- void *frame,
- size_t spillSize,
- size_t argsSize,
- int hasBailedOutOffset,
- ScriptContext *scriptContext)
- {
- void *continuation = nullptr;
- JavascriptExceptionObject *exception = nullptr;
- PROBE_STACK(scriptContext, Constants::MinStackDefault + spillSize + argsSize);
- try
- {
- Js::JavascriptExceptionOperators::AutoCatchHandlerExists autoCatchHandlerExists(scriptContext);
- continuation = amd64_CallWithFakeFrame(tryAddr, frame, spillSize, argsSize);
- }
- catch (JavascriptExceptionObject *caughtException)
- {
- exception = caughtException;
- }
- if (exception)
- {
- exception = exception->CloneIfStaticExceptionObject(scriptContext);
- bool hasBailedOut = *(bool*)((char*)frame + hasBailedOutOffset); // stack offsets are negative
- if (hasBailedOut)
- {
- // If we have bailed out, this exception is coming from the interpreter. It should not have been caught;
- // it so happens that this catch was on the stack and caught the exception.
- // Re-throw!
- throw exception;
- }
- Var exceptionObject = exception->GetThrownObject(scriptContext);
- AssertMsg(exceptionObject, "Caught object is null.");
- continuation = amd64_CallWithFakeFrame(catchAddr, frame, spillSize, argsSize, exceptionObject);
- }
- return continuation;
- }
- void *JavascriptExceptionOperators::OP_TryFinally(void *tryAddr,
- void *finallyAddr,
- void *frame,
- size_t spillSize,
- size_t argsSize,
- ScriptContext *scriptContext)
- {
- void *tryContinuation = nullptr;
- void *finallyContinuation = nullptr;
- JavascriptExceptionObject *exception = nullptr;
- PROBE_STACK(scriptContext, Constants::MinStackDefault + spillSize + argsSize);
- try
- {
- tryContinuation = amd64_CallWithFakeFrame(tryAddr, frame, spillSize, argsSize);
- }
- catch (JavascriptExceptionObject *caughtException)
- {
- exception = caughtException;
- }
- if (exception)
- {
- // Clone static exception object early in case finally block overwrites it
- exception = exception->CloneIfStaticExceptionObject(scriptContext);
- }
- finallyContinuation = amd64_CallWithFakeFrame(finallyAddr, frame, spillSize, argsSize);
- if (finallyContinuation)
- {
- return finallyContinuation;
- }
- if (exception)
- {
- throw exception;
- }
- return tryContinuation;
- }
- #elif defined(_M_ARM32_OR_ARM64)
- void *JavascriptExceptionOperators::OP_TryCatch(
- void *tryAddr,
- void *catchAddr,
- void *framePtr,
- void *localsPtr,
- size_t argsSize,
- int hasBailedOutOffset,
- ScriptContext *scriptContext)
- {
- void *continuation = nullptr;
- JavascriptExceptionObject *exception = nullptr;
- PROBE_STACK(scriptContext, Constants::MinStackDefault + argsSize);
- try
- {
- Js::JavascriptExceptionOperators::AutoCatchHandlerExists autoCatchHandlerExists(scriptContext);
- #if defined(_M_ARM)
- continuation = arm_CallEhFrame(tryAddr, framePtr, localsPtr, argsSize);
- #elif defined(_M_ARM64)
- continuation = arm64_CallEhFrame(tryAddr, framePtr, localsPtr, argsSize);
- #endif
- }
- catch (JavascriptExceptionObject *caughtException)
- {
- exception = caughtException;
- }
- if (exception)
- {
- exception = exception->CloneIfStaticExceptionObject(scriptContext);
- bool hasBailedOut = *(bool*)((char*)localsPtr + hasBailedOutOffset); // stack offsets are sp relative
- if (hasBailedOut)
- {
- // If we have bailed out, this exception is coming from the interpreter. It should not have been caught;
- // it so happens that this catch was on the stack and caught the exception.
- // Re-throw!
- throw exception;
- }
- Var exceptionObject = exception->GetThrownObject(scriptContext);
- AssertMsg(exceptionObject, "Caught object is null.");
- #if defined(_M_ARM)
- continuation = arm_CallCatch(catchAddr, framePtr, localsPtr, argsSize, exceptionObject);
- #elif defined(_M_ARM64)
- continuation = arm64_CallCatch(catchAddr, framePtr, localsPtr, argsSize, exceptionObject);
- #endif
- }
- return continuation;
- }
- void *JavascriptExceptionOperators::OP_TryFinally(
- void *tryAddr,
- void *finallyAddr,
- void *framePtr,
- void *localsPtr,
- size_t argsSize,
- ScriptContext *scriptContext)
- {
- void *tryContinuation = nullptr;
- void *finallyContinuation = nullptr;
- JavascriptExceptionObject *exception = nullptr;
- PROBE_STACK(scriptContext, Constants::MinStackDefault + argsSize);
- try
- {
- #if defined(_M_ARM)
- tryContinuation = arm_CallEhFrame(tryAddr, framePtr, localsPtr, argsSize);
- #elif defined(_M_ARM64)
- tryContinuation = arm64_CallEhFrame(tryAddr, framePtr, localsPtr, argsSize);
- #endif
- }
- catch (JavascriptExceptionObject *caughtException)
- {
- exception = caughtException;
- }
- if (exception)
- {
- // Clone static exception object early in case finally block overwrites it
- exception = exception->CloneIfStaticExceptionObject(scriptContext);
- }
- #if defined(_M_ARM)
- finallyContinuation = arm_CallEhFrame(finallyAddr, framePtr, localsPtr, argsSize);
- #elif defined(_M_ARM64)
- finallyContinuation = arm64_CallEhFrame(finallyAddr, framePtr, localsPtr, argsSize);
- #endif
- if (finallyContinuation)
- {
- return finallyContinuation;
- }
- if (exception)
- {
- throw exception;
- }
- return tryContinuation;
- }
- #else
- #pragma warning(push)
- #pragma warning(disable:4731) // frame pointer register 'ebp' modified by inline assembly code
- void* JavascriptExceptionOperators::OP_TryCatch(void* tryAddr, void* handlerAddr, void* framePtr, int hasBailedOutOffset, ScriptContext *scriptContext)
- {
- void* continuationAddr = NULL;
- Js::JavascriptExceptionObject* pExceptionObject = NULL;
- PROBE_STACK(scriptContext, Constants::MinStackDefault);
- try
- {
- Js::JavascriptExceptionOperators::AutoCatchHandlerExists autoCatchHandlerExists(scriptContext);
- // Adjust the frame pointer and call into the try.
- // If the try completes without raising an exception, it will pass back the continuation address.
- // Bug in compiler optimizer: try-catch can be optimized away if the try block contains __asm calls into function
- // that may throw. The current workaround is to add the following dummy throw to prevent this optimization.
- if (!tryAddr)
- {
- Js::Throw::InternalError();
- }
- #ifdef _M_IX86
- void *savedEsp;
- __asm
- {
- // Save and restore the callee-saved registers around the call.
- // TODO: track register kills by region and generate per-region prologs and epilogs
- push esi
- push edi
- push ebx
- // 8-byte align frame to improve floating point perf of our JIT'd code.
- // Save ESP
- mov ecx, esp
- mov savedEsp, ecx
- and esp, -8
- // Set up the call target, save the current frame ptr, and adjust the frame to access
- // locals in native code.
- mov eax, tryAddr
- #if 0 && defined(_CONTROL_FLOW_GUARD)
- // verify that the call target is valid
- mov ebx, eax ; save call target
- mov ecx, eax
- call [__guard_check_icall_fptr]
- mov eax, ebx ; restore call target
- #endif
- push ebp
- mov ebp, framePtr
- call eax
- pop ebp
- // The native code gives us the address where execution should continue on exit
- // from the region.
- mov continuationAddr, eax
- // Restore ESP
- mov ecx, savedEsp
- mov esp, ecx
- pop ebx
- pop edi
- pop esi
- }
- #else
- AssertMsg(FALSE, "Unsupported native try-catch handler");
- #endif
- }
- catch(Js::JavascriptExceptionObject * exceptionObject)
- {
- pExceptionObject = exceptionObject;
- }
- // Let's run user catch handler code only after the stack has been unwound.
- if(pExceptionObject)
- {
- pExceptionObject = pExceptionObject->CloneIfStaticExceptionObject(scriptContext);
- bool hasBailedOut = *(bool*)((char*)framePtr + hasBailedOutOffset); // stack offsets are negative
- if (hasBailedOut)
- {
- // If we have bailed out, this exception is coming from the interpreter. It should not have been caught;
- // it so happens that this catch was on the stack and caught the exception.
- // Re-throw!
- throw pExceptionObject;
- }
- Var catchObject = pExceptionObject->GetThrownObject(scriptContext);
- AssertMsg(catchObject, "Caught object is NULL");
- #ifdef _M_IX86
- void *savedEsp;
- __asm
- {
- // Save and restore the callee-saved registers around the call.
- // TODO: track register kills by region and generate per-region prologs and epilogs
- push esi
- push edi
- push ebx
- // 8-byte align frame to improve floating point perf of our JIT'd code.
- // Save ESP
- mov ecx, esp
- mov savedEsp, ecx
- and esp, -8
- // Set up the call target
- mov ecx, handlerAddr
- #if 0 && defined(_CONTROL_FLOW_GUARD)
- // verify that the call target is valid
- mov ebx, ecx ; save call target
- call [__guard_check_icall_fptr]
- mov ecx, ebx ; restore call target
- #endif
- // Set up catch object, save the current frame ptr, and adjust the frame to access
- // locals in native code.
- mov eax, catchObject
- push ebp
- mov ebp, framePtr
- call ecx
- pop ebp
- // The native code gives us the address where execution should continue on exit
- // from the region.
- mov continuationAddr, eax
- // Restore ESP
- mov ecx, savedEsp
- mov esp, ecx
- pop ebx
- pop edi
- pop esi
- }
- #else
- AssertMsg(FALSE, "Unsupported native try-catch handler");
- #endif
- }
- return continuationAddr;
- }
- void* JavascriptExceptionOperators::OP_TryFinally(void* tryAddr, void* handlerAddr, void* framePtr, ScriptContext *scriptContext)
- {
- Js::JavascriptExceptionObject* pExceptionObject = NULL;
- void* continuationAddr = NULL;
- PROBE_STACK(scriptContext, Constants::MinStackDefault);
- try
- {
- // Bug in compiler optimizer: try-catch can be optimized away if the try block contains __asm calls into function
- // that may throw. The current workaround is to add the following dummy throw to prevent this optimization.
- // It seems like compiler got smart and still optimizes if the exception is not JavascriptExceptionObject (see catch handler below).
- // In order to circumvent that we are throwing OutOfMemory.
- if (!tryAddr)
- {
- Assert(false);
- ThrowOutOfMemory(scriptContext);
- }
- #ifdef _M_IX86
- void *savedEsp;
- __asm
- {
- // Save and restore the callee-saved registers around the call.
- // TODO: track register kills by region and generate per-region prologs and epilogs
- push esi
- push edi
- push ebx
- // 8-byte align frame to improve floating point perf of our JIT'd code.
- // Save ESP
- mov ecx, esp
- mov savedEsp, ecx
- and esp, -8
- // Set up the call target, save the current frame ptr, and adjust the frame to access
- // locals in native code.
- mov eax, tryAddr
- #if 0 && defined(_CONTROL_FLOW_GUARD)
- // verify that the call target is valid
- mov ebx, eax ; save call target
- mov ecx, eax
- call [__guard_check_icall_fptr]
- mov eax, ebx ; restore call target
- #endif
- push ebp
- mov ebp, framePtr
- call eax
- pop ebp
- // The native code gives us the address where execution should continue on exit
- // from the region.
- mov continuationAddr, eax
- // Restore ESP
- mov ecx, savedEsp
- mov esp, ecx
- pop ebx
- pop edi
- pop esi
- }
- #else
- AssertMsg(FALSE, "Unsupported native try-finally handler");
- #endif
- }
- catch(Js::JavascriptExceptionObject* e)
- {
- pExceptionObject = e;
- }
- if (pExceptionObject)
- {
- // Clone static exception object early in case finally block overwrites it
- pExceptionObject = pExceptionObject->CloneIfStaticExceptionObject(scriptContext);
- }
- void* newContinuationAddr = NULL;
- #ifdef _M_IX86
- void *savedEsp;
- __asm
- {
- // Save and restore the callee-saved registers around the call.
- // TODO: track register kills by region and generate per-region prologs and epilogs
- push esi
- push edi
- push ebx
- // 8-byte align frame to improve floating point perf of our JIT'd code.
- // Save ESP
- mov ecx, esp
- mov savedEsp, ecx
- and esp, -8
- // Set up the call target
- mov eax, handlerAddr
- #if 0 && defined(_CONTROL_FLOW_GUARD)
- // verify that the call target is valid
- mov ebx, eax ; save call target
- mov ecx, eax
- call [__guard_check_icall_fptr]
- mov eax, ebx ; restore call target
- #endif
- // save the current frame ptr, and adjust the frame to access
- // locals in native code.
- push ebp
- mov ebp, framePtr
- call eax
- pop ebp
- // The native code gives us the address where execution should continue on exit
- // from the finally, but only if flow leaves the finally before it completes.
- mov newContinuationAddr, eax
- // Restore ESP
- mov ecx, savedEsp
- mov esp, ecx
- pop ebx
- pop edi
- pop esi
- }
- #else
- AssertMsg(FALSE, "Unsupported native try-finally handler");
- #endif
- if (newContinuationAddr != NULL)
- {
- // Non-null return value from the finally indicates that the finally seized the flow
- // with a jump/return out of the region. Continue at that address instead of handling
- // the exception.
- return newContinuationAddr;
- }
- if (pExceptionObject)
- {
- throw pExceptionObject;
- }
- return continuationAddr;
- }
- #endif
- void __declspec(noreturn) JavascriptExceptionOperators::OP_Throw(Var object, ScriptContext* scriptContext)
- {
- Throw(object, scriptContext);
- }
- #if defined(DBG) && defined(_M_IX86)
- extern "C" void * _except_handler4;
- void JavascriptExceptionOperators::DbgCheckEHChain()
- {
- #if 0
- // This debug check is disabled until we figure out how to trace a fs:0 chain if we throw from inside
- // a finally.
- void *currentFS0;
- ThreadContext * threadContext = ThreadContext::GetContextForCurrentThread();
- if (!threadContext->IsScriptActive())
- {
- return;
- }
- // Walk the FS:0 chain of exception handlers, until the FS:0 handler in CallRootFunction.
- // We should only see SEH frames on the way.
- // We do allow C++ EH frames as long as there is no active objects (state = -1).
- // That's because we may see frames that have calls to new(). This introduces an EH frame
- // to call delete if the constructor throws. Our constructors shouldn't throw, so we should be fine.
- currentFS0 = (void*)__readfsdword(0);
- while (currentFS0 != threadContext->callRootFS0)
- {
- // EH struct:
- // void * next;
- // void * handler;
- // int state;
- AssertMsg(*((void**)currentFS0 + 1) == &_except_handler4
- || *((int*)currentFS0 + 2) == -1, "Found a non SEH exception frame on stack");
- currentFS0 = *(void**)currentFS0;
- }
- #endif
- }
- #endif
- void JavascriptExceptionOperators::Throw(Var object, ScriptContext * scriptContext)
- {
- #if defined(DBG) && defined(_M_IX86)
- DbgCheckEHChain();
- #endif
- Assert(scriptContext != nullptr);
- // TODO: FastDOM Trampolines will throw JS Exceptions but are not isScriptActive
- //AssertMsg(scriptContext->GetThreadContext()->IsScriptActive() ||
- // (JavascriptError::Is(object) && (JavascriptError::FromVar(object))->IsExternalError()),
- // "Javascript exception raised when script is not active");
- AssertMsg(scriptContext->GetThreadContext()->IsInScript() ||
- (JavascriptError::Is(object) && (JavascriptError::FromVar(object))->IsExternalError()),
- "Javascript exception raised without being in CallRootFunction");
- JavascriptError *javascriptError = nullptr;
- if (JavascriptError::Is(object))
- {
- // We keep track of the JavascriptExceptionObject that was created when this error
- // was first thrown so that we can always get the correct metadata.
- javascriptError = JavascriptError::FromVar(object);
- JavascriptExceptionObject *exceptionObject = javascriptError->GetJavascriptExceptionObject();
- if (exceptionObject)
- {
- JavascriptExceptionOperators::ThrowExceptionObject(exceptionObject, scriptContext, true);
- }
- }
- JavascriptExceptionObject * exceptionObject =
- RecyclerNew(scriptContext->GetRecycler(), JavascriptExceptionObject, object, scriptContext, NULL);
- bool resetStack = false;
- if (javascriptError)
- {
- if (!javascriptError->IsStackPropertyRedefined())
- {
- /*
- Throwing an error object. Original stack property will be pointing to the stack created at time of Error constructor.
- Reset the stack property to match IE11 behavior
- */
- resetStack = true;
- }
- javascriptError->SetJavascriptExceptionObject(exceptionObject);
- }
- JavascriptExceptionOperators::ThrowExceptionObject(exceptionObject, scriptContext, /*considerPassingToDebugger=*/ true, /*returnAddress=*/ nullptr, resetStack);
- }
- void
- JavascriptExceptionOperators::WalkStackForExceptionContext(ScriptContext& scriptContext, JavascriptExceptionContext& exceptionContext, Var thrownObject, uint64 stackCrawlLimit, PVOID returnAddress, bool isThrownException, bool resetSatck)
- {
- uint32 callerBytecodeOffset;
- JavascriptFunction * jsFunc = WalkStackForExceptionContextInternal(scriptContext, exceptionContext, thrownObject, callerBytecodeOffset, stackCrawlLimit, returnAddress, isThrownException, resetSatck);
- if (jsFunc)
- {
- // If found, the caller is a function, and we can retrieve the debugger info from there
- // otherwise it's probably just accessing property. While it is still possible to throw
- // from that context, we just won't be able to get the line number etc., which make sense.
- exceptionContext.SetThrowingFunction(jsFunc, callerBytecodeOffset, returnAddress);
- }
- }
- JavascriptFunction *
- JavascriptExceptionOperators::WalkStackForExceptionContextInternal(ScriptContext& scriptContext, JavascriptExceptionContext& exceptionContext, Var thrownObject,
- uint32& callerByteCodeOffset, uint64 stackCrawlLimit, PVOID returnAddress, bool isThrownException, bool resetStack)
- {
- JavascriptStackWalker walker(&scriptContext, true, returnAddress);
- JavascriptFunction* jsFunc = nullptr;
- if (!GetCaller(walker, jsFunc))
- {
- return nullptr;
- }
- // Skip to first non-Library code
- // Similar behavior to GetCaller returning false
- if(jsFunc->IsLibraryCode() && !walker.GetNonLibraryCodeCaller(&jsFunc))
- {
- return nullptr;
- }
- JavascriptFunction * caller = jsFunc;
- callerByteCodeOffset = walker.GetByteCodeOffset();
- Assert(!caller->IsLibraryCode());
- // NOTE Don't set the throwing exception here, because we might need to box it and will cause a nested stack walker
- // instead, return it to be set in WalkStackForExceptionContext
- if (stackCrawlLimit == 0)
- {
- return caller;
- }
- const bool crawlStackForWER = CrawlStackForWER(scriptContext);
- // If we take an OOM (JavascriptException for OOM if script is active), just bail early and return what we've got
- HRESULT hr;
- JavascriptExceptionContext::StackTrace *stackTrace = NULL;
- BEGIN_TRANSLATE_EXCEPTION_AND_ERROROBJECT_TO_HRESULT_NESTED
- {
- // In WER scenario, we should combine the original stack with latest throw stack as the final throw might be coming form
- // a different stack.
- uint64 i = 1;
- if (crawlStackForWER && thrownObject && Js::JavascriptError::Is(thrownObject))
- {
- Js::JavascriptError* errorObject = Js::JavascriptError::FromVar(thrownObject);
- Js::JavascriptExceptionContext::StackTrace *originalStackTrace = NULL;
- const Js::JavascriptExceptionObject* originalExceptionObject = errorObject->GetJavascriptExceptionObject();
- if (!resetStack && errorObject->GetInternalProperty(errorObject, InternalPropertyIds::StackTrace, (Js::Var*) &originalStackTrace, NULL, &scriptContext) &&
- (originalStackTrace != nullptr))
- {
- exceptionContext.SetOriginalStackTrace(originalStackTrace);
- }
- else
- {
- if (originalExceptionObject != nullptr)
- {
- exceptionContext.SetOriginalStackTrace(originalExceptionObject->GetExceptionContext()->GetStackTrace());
- }
- }
- }
- stackTrace = RecyclerNew(scriptContext.GetRecycler(), JavascriptExceptionContext::StackTrace, scriptContext.GetRecycler());
- do
- {
- JavascriptExceptionContext::StackFrame stackFrame(jsFunc, walker, crawlStackForWER);
- stackTrace->Add(stackFrame);
- } while (walker.GetDisplayCaller(&jsFunc) && i++ < stackCrawlLimit);
- }
- END_TRANSLATE_EXCEPTION_AND_ERROROBJECT_TO_HRESULT_INSCRIPT(hr);
- exceptionContext.SetStackTrace(stackTrace);
- DumpStackTrace(exceptionContext, isThrownException);
- return caller;
- }
- // We might be trying to raise a stack overflow exception from the interpreter before
- // we've executed code in the current script stack frame. In that case the current byte
- // code offset is 0. In such cases walk to the caller's caller.
- BOOL JavascriptExceptionOperators::GetCaller(JavascriptStackWalker& walker, JavascriptFunction*& jsFunc)
- {
- if (! walker.GetCaller(&jsFunc))
- {
- return FALSE;
- }
- if (! walker.GetCurrentInterpreterFrame() ||
- walker.GetCurrentInterpreterFrame()->GetReader()->GetCurrentOffset() > 0)
- {
- return TRUE;
- }
- return walker.GetCaller(&jsFunc);
- }
- void JavascriptExceptionOperators::DumpStackTrace(JavascriptExceptionContext& exceptionContext, bool isThrownException)
- {
- #ifdef ENABLE_DEBUG_CONFIG_OPTIONS
- if (! exceptionContext.GetStackTrace()
- || ! Configuration::Global.flags.Dump.IsEnabled(ExceptionStackTracePhase)
- || ! isThrownException)
- {
- return;
- }
- Output::Print(L"\nStack trace for thrown exception\n");
- JavascriptExceptionContext::StackTrace *stackTrace = exceptionContext.GetStackTrace();
- for (int i=0; i < stackTrace->Count(); i++)
- {
- Js::JavascriptExceptionContext::StackFrame currFrame = stackTrace->Item(i);
- ULONG lineNumber = 0;
- LONG characterPosition = 0;
- if (currFrame.IsScriptFunction() && !currFrame.GetFunctionBody()->GetUtf8SourceInfo()->GetIsLibraryCode())
- {
- currFrame.GetFunctionBody()->GetLineCharOffset(currFrame.GetByteCodeOffset(), &lineNumber, &characterPosition);
- }
- Output::Print(L" %3d: %s (%d, %d)\n", i, currFrame.GetFunctionName(), lineNumber, characterPosition);
- }
- Output::Flush();
- #endif
- }
- /// ---------------------------------------------------------------------------------------------------
- /// When allocators throw out of memory exception - scriptContext is NULL
- /// ---------------------------------------------------------------------------------------------------
- JavascriptExceptionObject * JavascriptExceptionOperators::GetOutOfMemoryExceptionObject(ScriptContext *scriptContext)
- {
- ThreadContext *threadContext = scriptContext ?
- scriptContext->GetThreadContext() :
- ThreadContext::GetContextForCurrentThread();
- JavascriptExceptionObject *oomExceptionObject = threadContext->GetPendingOOMErrorObject();
- Assert(oomExceptionObject);
- return oomExceptionObject;
- }
- void JavascriptExceptionOperators::ThrowOutOfMemory(ScriptContext *scriptContext)
- {
- ThreadContext *threadContext = scriptContext ?
- scriptContext->GetThreadContext() :
- ThreadContext::GetContextForCurrentThread();
- threadContext->ClearDisableImplicitFlags();
- JavascriptExceptionObject *oom = JavascriptExceptionOperators::GetOutOfMemoryExceptionObject(scriptContext);
- JavascriptExceptionOperators::ThrowExceptionObject(oom, scriptContext);
- }
- void JavascriptExceptionOperators::ThrowStackOverflow(ScriptContext *scriptContext, PVOID returnAddress)
- {
- Assert(scriptContext);
- ThreadContext *threadContext = scriptContext->GetThreadContext();
- JavascriptExceptionObject *so = threadContext->GetPendingSOErrorObject();
- Assert(so);
- // Disable implicit call before calling into recycler (to prevent QueryContinue/dispose from leave script and stack overflow again)
- threadContext->DisableImplicitCall();
- Var thrownObject = scriptContext->GetLibrary()->CreateStackOverflowError();
- so->SetThrownObject(thrownObject);
- // NOTE: Do not ClearDisableImplicitFlags() here. We still need to allocate StackTrace, etc. Keep implicit call disabled till actual
- // throw (ThrowExceptionObjectInternal will ClearDisableImplicitFlags before throw). If anything wrong happens in between which throws
- // a new exception, the new throw will ClearDisableImplicitFlags.
- JavascriptExceptionOperators::ThrowExceptionObject(so, scriptContext, false, returnAddress);
- }
- void JavascriptExceptionOperators::ThrowExceptionObjectInternal(Js::JavascriptExceptionObject * exceptionObject, ScriptContext* scriptContext, bool fillExceptionContext, bool considerPassingToDebugger, PVOID returnAddress, bool resetStack)
- {
- if (scriptContext)
- {
- if (fillExceptionContext)
- {
- Assert(exceptionObject);
- JavascriptExceptionContext exceptionContext;
- Var thrownObject = exceptionObject->GetThrownObject(nullptr);
- WalkStackForExceptionContext(*scriptContext, exceptionContext, thrownObject, StackCrawlLimitOnThrow(thrownObject, *scriptContext), returnAddress, /*isThrownException=*/ true, resetStack);
- exceptionObject->FillError(exceptionContext, scriptContext);
- AddStackTraceToObject(thrownObject, exceptionContext.GetStackTrace(), *scriptContext, /*isThrownException=*/ true, resetStack);
- if (considerPassingToDebugger)
- {
- DispatchExceptionToDebugger(exceptionObject, scriptContext);
- }
- }
- Assert(!scriptContext ||
- // If we disabled implicit calls and we did record an implicit call, do not throw.
- // Check your helper to see if a call recorded an implicit call that might cause an invalid value
- !(
- scriptContext->GetThreadContext()->IsDisableImplicitCall() &&
- scriptContext->GetThreadContext()->GetImplicitCallFlags() & (~ImplicitCall_None)
- ) ||
- // Make sure we didn't disable exceptions
- !scriptContext->GetThreadContext()->IsDisableImplicitException()
- );
- scriptContext->GetThreadContext()->ClearDisableImplicitFlags();
- }
- if (exceptionObject->IsPendingExceptionObject())
- {
- ThreadContext * threadContext = scriptContext? scriptContext->GetThreadContext() : ThreadContext::GetContextForCurrentThread();
- threadContext->SetHasThrownPendingException();
- }
- throw exceptionObject;
- }
- void JavascriptExceptionOperators::DispatchExceptionToDebugger(Js::JavascriptExceptionObject * exceptionObject, ScriptContext* scriptContext)
- {
- Assert(exceptionObject != NULL);
- Assert(scriptContext != NULL);
- if (scriptContext->IsInDebugMode()
- && scriptContext->GetDebugContext()->GetProbeContainer()->HasAllowedForException(exceptionObject))
- {
- InterpreterHaltState haltState(STOP_EXCEPTIONTHROW, /*executingFunction*/nullptr);
- haltState.exceptionObject = exceptionObject;
- scriptContext->GetDebugContext()->GetProbeContainer()->DispatchExceptionBreakpoint(&haltState);
- }
- }
- void JavascriptExceptionOperators::ThrowExceptionObject(Js::JavascriptExceptionObject * exceptionObject, ScriptContext* scriptContext, bool considerPassingToDebugger, PVOID returnAddress, bool resetStack)
- {
- ThrowExceptionObjectInternal(exceptionObject, scriptContext, true, considerPassingToDebugger, returnAddress, resetStack);
- }
- // The purpose of RethrowExceptionObject is to determine if ThrowExceptionObjectInternal should fill in the exception context.
- //
- // We pretty much always want to fill in the exception context when we throw an exception. The only case where we don't want to do it
- // is if we are rethrowing and have the JavascriptExceptionObject from the previous throw with its exception context intact. If
- // RethrowExceptionObject is passed a JavascriptExceptionObject with the function already there, that implies we have existing
- // exception context and shouldn't step on it on the throw.
- //
- // RethrowExceptionObject is called only for cross-host calls. When throwing across host calls, we stash our internal JavascriptExceptionObject
- // in the TLS. When we are throwing on the same thread (e.g. a throw from one frame to another), we can retrieve that stashed JavascriptExceptionObject
- // from the TLS and rethrow it with its exception context intact, so we don't want to step on it. In other cases, e.g. when we throw across threads,
- // we cannot retrieve the internal JavascriptExceptionObject from the TLS and have to create a new one. In this case, we need to fill the exception context.
- //
- void JavascriptExceptionOperators::RethrowExceptionObject(Js::JavascriptExceptionObject * exceptionObject, ScriptContext* scriptContext, bool considerPassingToDebugger)
- {
- ThrowExceptionObjectInternal(exceptionObject, scriptContext, ! exceptionObject->GetFunction(), considerPassingToDebugger, /*returnAddress=*/ nullptr, /*resetStack=*/ false);
- }
- // Trim the stack trace down to the amount specified for Error.stackTraceLimit. This happens when we do a full crawl for WER, but we only want to store the specified amount in the error object for consistency.
- JavascriptExceptionContext::StackTrace* JavascriptExceptionOperators::TrimStackTraceForThrownObject(JavascriptExceptionContext::StackTrace* stackTraceIn, Var thrownObject, ScriptContext& scriptContext)
- {
- Assert(CrawlStackForWER(scriptContext)); // Don't trim if crawl for Error.stack
- Assert(stackTraceIn);
- int stackTraceLimit = static_cast<int>(GetStackTraceLimit(thrownObject, &scriptContext));
- Assert(stackTraceLimit == 0 || IsErrorInstance(thrownObject));
- if (stackTraceIn->Count() <= stackTraceLimit)
- {
- return stackTraceIn;
- }
- JavascriptExceptionContext::StackTrace* stackTraceTrimmed = NULL;
- if (stackTraceLimit > 0)
- {
- HRESULT hr;
- BEGIN_TRANSLATE_EXCEPTION_AND_ERROROBJECT_TO_HRESULT_NESTED
- {
- stackTraceTrimmed = RecyclerNew(scriptContext.GetRecycler(), JavascriptExceptionContext::StackTrace, scriptContext.GetRecycler());
- for (int i = 0; i < stackTraceLimit; i++)
- {
- stackTraceTrimmed->Add(stackTraceIn->Item(i));
- }
- }
- END_TRANSLATE_EXCEPTION_AND_ERROROBJECT_TO_HRESULT_INSCRIPT(hr);
- }
- // ignore OOM and just return what we can get
- return stackTraceTrimmed;
- }
- //
- // Check if thrownObject is instanceof Error (but not an Error prototype).
- //
- bool JavascriptExceptionOperators::IsErrorInstance(Var thrownObject)
- {
- if (thrownObject && JavascriptError::Is(thrownObject))
- {
- return !JavascriptError::FromVar(thrownObject)->IsPrototype();
- }
- if (thrownObject && RecyclableObject::Is(thrownObject))
- {
- RecyclableObject* obj = RecyclableObject::FromVar(thrownObject);
- while (true)
- {
- obj = JavascriptOperators::GetPrototype(obj);
- if (JavascriptOperators::GetTypeId(obj) == TypeIds_Null)
- {
- break;
- }
- if (JavascriptError::Is(obj))
- {
- return true;
- }
- }
- }
- return false;
- }
- void JavascriptExceptionOperators::AddStackTraceToObject(Var targetObject, JavascriptExceptionContext::StackTrace* stackTrace, ScriptContext& scriptContext, bool isThrownException, bool resetStack)
- {
- if (!stackTrace || stackTrace->Count() == 0 || !scriptContext.GetConfig()->IsErrorStackTraceEnabled())
- {
- return;
- }
- if (isThrownException && CrawlStackForWER(scriptContext)) // Trim stack trace for WER
- {
- stackTrace = TrimStackTraceForThrownObject(stackTrace, targetObject, scriptContext);
- if (!stackTrace)
- {
- return;
- }
- }
- // If we still have stack trace to store and obj is a thrown exception object, obj must be an Error instance.
- Assert(!isThrownException || IsErrorInstance(targetObject));
- RecyclableObject* obj = RecyclableObject::FromVar(targetObject);
- if (!resetStack && obj->HasProperty(PropertyIds::stack))
- {
- return; // we don't want to overwrite an existing "stack" property
- }
- JavascriptFunction* accessor = scriptContext.GetLibrary()->GetStackTraceAccessorFunction();
- PropertyDescriptor stackPropertyDescriptor;
- stackPropertyDescriptor.SetSetter(accessor);
- stackPropertyDescriptor.SetGetter(accessor);
- stackPropertyDescriptor.SetConfigurable(true);
- stackPropertyDescriptor.SetEnumerable(false);
- HRESULT hr;
- BEGIN_TRANSLATE_EXCEPTION_AND_ERROROBJECT_TO_HRESULT_NESTED
- {
- if (JavascriptOperators::DefineOwnPropertyDescriptor(obj, PropertyIds::stack, stackPropertyDescriptor, false, &scriptContext))
- {
- obj->SetInternalProperty(InternalPropertyIds::StackTrace, stackTrace, PropertyOperationFlags::PropertyOperation_None, NULL);
- obj->SetInternalProperty(InternalPropertyIds::StackTraceCache, NULL, PropertyOperationFlags::PropertyOperation_None, NULL);
- }
- }
- END_TRANSLATE_EXCEPTION_AND_ERROROBJECT_TO_HRESULT_INSCRIPT(hr)
- }
- Var JavascriptExceptionOperators::OP_RuntimeTypeError(MessageId messageId, ScriptContext *scriptContext)
- {
- JavascriptError::ThrowTypeError(scriptContext, MAKE_HR(messageId));
- }
- Var JavascriptExceptionOperators::OP_RuntimeRangeError(MessageId messageId, ScriptContext *scriptContext)
- {
- JavascriptError::ThrowRangeError(scriptContext, MAKE_HR(messageId));
- }
- Var JavascriptExceptionOperators::OP_RuntimeReferenceError(MessageId messageId, ScriptContext *scriptContext)
- {
- JavascriptError::ThrowReferenceError(scriptContext, MAKE_HR(messageId));
- }
- Var JavascriptExceptionOperators::ThrowTypeErrorAccessor(RecyclableObject* function, CallInfo callInfo, ...)
- {
- JavascriptError::ThrowTypeError(function->GetScriptContext(), VBSERR_ActionNotSupported);
- }
- // Throw type error on access caller when in a restricted context
- Var JavascriptExceptionOperators::ThrowTypeErrorCallerAccessor(RecyclableObject* function, CallInfo callInfo, ...)
- {
- JavascriptError::ThrowTypeError(function->GetScriptContext(), JSERR_AccessCallerRestricted);
- }
- // Throw type error on access on callee when strict mode
- Var JavascriptExceptionOperators::ThrowTypeErrorCalleeAccessor(RecyclableObject* function, CallInfo callInfo, ...)
- {
- JavascriptError::ThrowTypeError(function->GetScriptContext(), JSERR_AccessCallee);
- }
- // Throw type error on access arguments when in a restricted context
- Var JavascriptExceptionOperators::ThrowTypeErrorArgumentsAccessor(RecyclableObject* function, CallInfo callInfo, ...)
- {
- JavascriptError::ThrowTypeError(function->GetScriptContext(), JSERR_AccessArgumentsRestricted);
- }
- Var JavascriptExceptionOperators::StackTraceAccessor(RecyclableObject* function, CallInfo callInfo, ...)
- {
- ARGUMENTS(args, callInfo);
- AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
- ScriptContext *scriptContext = function->GetScriptContext();
- AnalysisAssert(scriptContext);
- // If the first argument to the accessor is not a recyclable object, return undefined
- // for compat with other browsers
- if (!RecyclableObject::Is(args[0]))
- {
- return scriptContext->GetLibrary()->GetUndefined();
- }
- RecyclableObject *obj = RecyclableObject::FromVar(args[0]);
- // If an argument was passed to the accessor, it is being called as a setter.
- // Set the internal StackTraceCache property accordingly.
- if (args.Info.Count > 1)
- {
- obj->SetInternalProperty(InternalPropertyIds::StackTraceCache, args[1], PropertyOperationFlags::PropertyOperation_None, NULL);
- if (JavascriptError::Is(obj))
- {
- ((JavascriptError *)obj)->SetStackPropertyRedefined(true);
- }
- return scriptContext->GetLibrary()->GetEmptyString();
- }
- // Otherwise, the accessor is being called as a getter.
- // Return existing cached value, or obtain the string representation of the StackTrace to return.
- Var cache = NULL;
- if (obj->GetInternalProperty(obj,InternalPropertyIds::StackTraceCache, (Var*)&cache, NULL, scriptContext) && cache)
- {
- return cache;
- }
- JavascriptString* stringMessage = scriptContext->GetLibrary()->GetEmptyString();
- HRESULT hr;
- BEGIN_TRANSLATE_EXCEPTION_AND_ERROROBJECT_TO_HRESULT_NESTED
- {
- Js::JavascriptExceptionContext::StackTrace *stackTrace = NULL;
- if (!obj->GetInternalProperty(obj,InternalPropertyIds::StackTrace, (Js::Var*) &stackTrace, NULL, scriptContext) ||
- stackTrace == nullptr)
- {
- obj->SetInternalProperty(InternalPropertyIds::StackTraceCache, stringMessage, PropertyOperationFlags::PropertyOperation_None, NULL);
- return stringMessage;
- }
- if (IsErrorInstance(obj))
- {
- stringMessage = JavascriptConversion::ToString(obj, scriptContext);
- }
- CompoundString *const stringBuilder = CompoundString::NewWithCharCapacity(40, scriptContext->GetLibrary());
- stringBuilder->AppendChars(stringMessage);
- for (int i = 0; i < stackTrace->Count(); i++)
- {
- Js::JavascriptExceptionContext::StackFrame currentFrame = stackTrace->Item(i);
- // Defend in depth. Discard cross domain frames if somehow they creped in.
- if (currentFrame.IsScriptFunction())
- {
- ScriptContext* funcScriptContext = currentFrame.GetFunctionBody()->GetScriptContext();
- AnalysisAssert(funcScriptContext);
- if (scriptContext != funcScriptContext && FAILED(scriptContext->GetHostScriptContext()->CheckCrossDomainScriptContext(funcScriptContext)))
- {
- continue; // Ignore this frame
- }
- }
- FunctionBody* functionBody = currentFrame.GetFunctionBody();
- const bool isLibraryCode = !functionBody || functionBody->GetUtf8SourceInfo()->GetIsLibraryCode();
- if (isLibraryCode)
- {
- AppendLibraryFrameToStackTrace(stringBuilder, currentFrame.GetFunctionName());
- }
- else
- {
- LPCWSTR pUrl = NULL;
- ULONG lineNumber = 0;
- LONG characterPosition = 0;
- functionBody->GetLineCharOffset(currentFrame.GetByteCodeOffset(), &lineNumber, &characterPosition);
- pUrl = functionBody->GetSourceName();
- LPCWSTR functionName = nullptr;
- if (CONFIG_FLAG(ExtendedErrorStackForTestHost))
- {
- BEGIN_LEAVE_SCRIPT_INTERNAL(scriptContext)
- {
- if (currentFrame.GetFunctionNameWithArguments(&functionName) != S_OK)
- {
- functionName = functionBody->GetExternalDisplayName();
- }
- }
- END_LEAVE_SCRIPT_INTERNAL(scriptContext)
- }
- else
- {
- functionName = functionBody->GetExternalDisplayName();
- }
- AppendExternalFrameToStackTrace(stringBuilder, functionName, pUrl ? pUrl : L"", lineNumber + 1, characterPosition + 1);
- }
- }
- // Try to create the string object even if we did OOM, but if can't, just return what we've got. We catch and ignore OOM so it doesn’t propagate up.
- // With all the stack trace functionality, we do best effort to produce the stack trace in the case of OOM, but don’t want it to trigger an OOM. Idea is if do take
- // an OOM, have some chance of producing a stack trace to see where it happened.
- stringMessage = stringBuilder;
- }
- END_TRANSLATE_EXCEPTION_AND_ERROROBJECT_TO_HRESULT_INSCRIPT(hr);
- obj->SetInternalProperty(InternalPropertyIds::StackTraceCache, stringMessage, PropertyOperationFlags::PropertyOperation_None, NULL);
- return stringMessage;
- }
- uint64 JavascriptExceptionOperators::GetStackTraceLimit(Var thrownObject, ScriptContext* scriptContext)
- {
- uint64 limit = 0;
- if (scriptContext->GetConfig()->IsErrorStackTraceEnabled()
- && IsErrorInstance(thrownObject))
- {
- HRESULT hr = JavascriptError::GetRuntimeError(RecyclableObject::FromVar(thrownObject), NULL);
- JavascriptFunction* error = scriptContext->GetLibrary()->GetErrorConstructor();
- // If we are throwing StackOverflow and Error.stackTraceLimit is a custom getter, we can't make the getter
- // call as we don't have stack space. Just bail out without stack trace in such case. Only proceed to get
- // Error.stackTraceLimit property if we are not throwing StackOverflow, or there is no implicitCall (in getter case).
- DisableImplicitFlags disableImplicitFlags = scriptContext->GetThreadContext()->GetDisableImplicitFlags();
- if (hr == VBSERR_OutOfStack)
- {
- scriptContext->GetThreadContext()->SetDisableImplicitFlags(DisableImplicitCallAndExceptionFlag);
- }
- Var var;
- if (JavascriptOperators::GetProperty(error, PropertyIds::stackTraceLimit, &var, scriptContext))
- {
- // Only accept the value if it is a "Number". Avoid potential valueOf() call.
- switch (JavascriptOperators::GetTypeId(var))
- {
- case TypeIds_Integer:
- case TypeIds_Number:
- case TypeIds_Int64Number:
- case TypeIds_UInt64Number:
- double value = JavascriptConversion::ToNumber(var, scriptContext);
- limit = JavascriptNumber::IsNan(value) ? 0 :
- (NumberUtilities::IsFinite(value) ? JavascriptConversion::ToUInt32(var, scriptContext) : MaxStackTraceLimit);
- break;
- }
- }
- if (hr == VBSERR_OutOfStack)
- {
- scriptContext->GetThreadContext()->SetDisableImplicitFlags(disableImplicitFlags);
- }
- }
- return limit;
- }
- void JavascriptExceptionOperators::AppendExternalFrameToStackTrace(CompoundString* bs, LPCWSTR functionName, LPCWSTR fileName, ULONG lineNumber, LONG characterPosition)
- {
- // format is equivalent to printf("\n at %s (%s:%d:%d)", functionName, filename, lineNumber, characterPosition);
- const CharCount maxULongStringLength = 10; // excluding null terminator
- const auto ConvertULongToString = [](const ULONG value, wchar_t *const buffer, const CharCount charCapacity)
- {
- const errno_t err = _ultow_s(value, buffer, charCapacity, 10);
- Assert(err == 0);
- };
- if (CONFIG_FLAG(ExtendedErrorStackForTestHost))
- {
- bs->AppendChars(L"\n\tat ");
- }
- else
- {
- bs->AppendChars(L"\n at ");
- }
- bs->AppendCharsSz(functionName);
- bs->AppendChars(L" (");
- if (CONFIG_FLAG(ExtendedErrorStackForTestHost) && *fileName != L'\0')
- {
- wchar_t shortfilename[_MAX_FNAME];
- wchar_t ext[_MAX_EXT];
- errno_t err = _wsplitpath_s(fileName, NULL, 0, NULL, 0, shortfilename, _MAX_FNAME, ext, _MAX_EXT);
- if (err != 0)
- {
- bs->AppendCharsSz(fileName);
- }
- else
- {
- bs->AppendCharsSz(shortfilename);
- bs->AppendCharsSz(ext);
- }
- }
- else
- {
- bs->AppendCharsSz(fileName);
- }
- bs->AppendChars(L':');
- bs->AppendChars(lineNumber, maxULongStringLength, ConvertULongToString);
- bs->AppendChars(L':');
- bs->AppendChars(characterPosition, maxULongStringLength, ConvertULongToString);
- bs->AppendChars(L')');
- }
- void JavascriptExceptionOperators::AppendLibraryFrameToStackTrace(CompoundString* bs, LPCWSTR functionName)
- {
- // format is equivalent to printf("\n at %s (native code)", functionName);
- bs->AppendChars(L"\n at ");
- bs->AppendCharsSz(functionName);
- bs->AppendChars(L" (native code)");
- }
- } // namespace Js
|