Skip to content

Commit 75bec08

Browse files
committed
[2.0>master] [MERGE #2936 @MSLaguana] Exposing a new JSRT method to get additional information about exceptions
Merge pull request #2936 from MSLaguana:exposeExceptionMetadataR20 This information includes the source file and line/column position of where the exception came from, as well as the actual source content of the line. The motivation for this method is for node to print it out on uncaught exceptions.
2 parents 3ecf7a6 + 58225fe commit 75bec08

23 files changed

+1662
-1196
lines changed

bin/NativeTests/JsRTApiTest.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,9 @@ namespace JsRTApiTest
764764
{
765765
bool value;
766766
JsValueRef exception = JS_INVALID_REFERENCE;
767+
JsValueRef exceptionMetadata = JS_INVALID_REFERENCE;
768+
JsValueRef metadataValue = JS_INVALID_REFERENCE;
769+
JsPropertyIdRef property = JS_INVALID_REFERENCE;
767770
JsValueType type;
768771

769772
REQUIRE(JsHasException(&value) == JsNoError);
@@ -782,6 +785,84 @@ namespace JsRTApiTest
782785
REQUIRE(JsSetException(exception) == JsNoError);
783786
REQUIRE(JsHasException(&value) == JsNoError);
784787
CHECK(value == true);
788+
789+
REQUIRE(JsGetAndClearExceptionWithMetadata(&exceptionMetadata) == JsNoError);
790+
REQUIRE(JsHasException(&value) == JsNoError);
791+
CHECK(value == false);
792+
793+
REQUIRE(JsGetPropertyIdFromName(_u("exception"), &property) == JsNoError);
794+
REQUIRE(JsGetProperty(exceptionMetadata, property, &metadataValue) == JsNoError);
795+
CHECK(metadataValue == exception);
796+
797+
REQUIRE(JsGetPropertyIdFromName(_u("line"), &property) == JsNoError);
798+
REQUIRE(JsGetProperty(exceptionMetadata, property, &metadataValue) == JsNoError);
799+
REQUIRE(JsGetValueType(metadataValue, &type) == JsNoError);
800+
CHECK(type == JsNumber);
801+
802+
REQUIRE(JsGetPropertyIdFromName(_u("column"), &property) == JsNoError);
803+
REQUIRE(JsGetProperty(exceptionMetadata, property, &metadataValue) == JsNoError);
804+
REQUIRE(JsGetValueType(metadataValue, &type) == JsNoError);
805+
CHECK(type == JsNumber);
806+
807+
REQUIRE(JsGetPropertyIdFromName(_u("length"), &property) == JsNoError);
808+
REQUIRE(JsGetProperty(exceptionMetadata, property, &metadataValue) == JsNoError);
809+
REQUIRE(JsGetValueType(metadataValue, &type) == JsNoError);
810+
CHECK(type == JsNumber);
811+
812+
REQUIRE(JsGetPropertyIdFromName(_u("url"), &property) == JsNoError);
813+
REQUIRE(JsGetProperty(exceptionMetadata, property, &metadataValue) == JsNoError);
814+
REQUIRE(JsGetValueType(metadataValue, &type) == JsNoError);
815+
CHECK(type == JsString);
816+
817+
REQUIRE(JsGetPropertyIdFromName(_u("source"), &property) == JsNoError);
818+
REQUIRE(JsGetProperty(exceptionMetadata, property, &metadataValue) == JsNoError);
819+
REQUIRE(JsGetValueType(metadataValue, &type) == JsNoError);
820+
CHECK(type == JsString);
821+
822+
823+
REQUIRE(JsHasException(&value) == JsNoError);
824+
CHECK(value == false);
825+
REQUIRE(JsGetAndClearExceptionWithMetadata(&exceptionMetadata) == JsErrorInvalidArgument);
826+
CHECK(exceptionMetadata == JS_INVALID_REFERENCE);
827+
828+
829+
REQUIRE(JsRunScript(_u("@ bad syntax"), JS_SOURCE_CONTEXT_NONE, _u(""), nullptr) == JsErrorScriptCompile);
830+
REQUIRE(JsHasException(&value) == JsNoError);
831+
CHECK(value == true);
832+
833+
REQUIRE(JsGetAndClearExceptionWithMetadata(&exceptionMetadata) == JsNoError);
834+
REQUIRE(JsHasException(&value) == JsNoError);
835+
CHECK(value == false);
836+
837+
REQUIRE(JsGetPropertyIdFromName(_u("exception"), &property) == JsNoError);
838+
REQUIRE(JsGetProperty(exceptionMetadata, property, &metadataValue) == JsNoError);
839+
REQUIRE(JsGetValueType(metadataValue, &type) == JsNoError);
840+
CHECK(type == JsError);
841+
842+
REQUIRE(JsGetPropertyIdFromName(_u("line"), &property) == JsNoError);
843+
REQUIRE(JsGetProperty(exceptionMetadata, property, &metadataValue) == JsNoError);
844+
REQUIRE(JsGetValueType(metadataValue, &type) == JsNoError);
845+
CHECK(type == JsNumber);
846+
847+
REQUIRE(JsGetPropertyIdFromName(_u("column"), &property) == JsNoError);
848+
REQUIRE(JsGetProperty(exceptionMetadata, property, &metadataValue) == JsNoError);
849+
REQUIRE(JsGetValueType(metadataValue, &type) == JsNoError);
850+
CHECK(type == JsNumber);
851+
852+
REQUIRE(JsGetPropertyIdFromName(_u("length"), &property) == JsNoError);
853+
REQUIRE(JsGetProperty(exceptionMetadata, property, &metadataValue) == JsNoError);
854+
REQUIRE(JsGetValueType(metadataValue, &type) == JsNoError);
855+
CHECK(type == JsNumber);
856+
857+
REQUIRE(JsGetPropertyIdFromName(_u("url"), &property) == JsNoError);
858+
REQUIRE(JsGetProperty(exceptionMetadata, property, &metadataValue) == JsNoError);
859+
REQUIRE(JsGetValueType(metadataValue, &type) == JsNoError);
860+
CHECK(type == JsString);
861+
862+
REQUIRE(JsGetPropertyIdFromName(_u("source"), &property) == JsNoError);
863+
REQUIRE(JsGetProperty(exceptionMetadata, property, &metadataValue) == JsNoError);
864+
REQUIRE(JsGetValueType(metadataValue, &type) == JsNoError);
865+
CHECK(type == JsString);
785866
}
786867

787868
TEST_CASE("ApiTest_ExceptionHandlingTest", "[ApiTest]")

lib/Jsrt/ChakraCore.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,39 @@ JsGetModuleHostInfo(
180180
_Outptr_result_maybenull_ void** hostInfo);
181181

182182
#ifdef CHAKRACOREBUILD_
183+
/// <summary>
184+
/// Returns metadata relating to the exception that caused the runtime of the current context
185+
/// to be in the exception state and resets the exception state for that runtime. The metadata
186+
/// includes a reference to the exception itself.
187+
/// </summary>
188+
/// <remarks>
189+
/// <para>
190+
/// If the runtime of the current context is not in an exception state, this API will return
191+
/// <c>JsErrorInvalidArgument</c>. If the runtime is disabled, this will return an exception
192+
/// indicating that the script was terminated, but it will not clear the exception (the
193+
/// exception will be cleared if the runtime is re-enabled using
194+
/// <c>JsEnableRuntimeExecution</c>).
195+
/// </para>
196+
/// <para>
197+
/// The metadata value is a javascript object with the following properties: <c>exception</c>, the
198+
/// thrown exception object; <c>line</c>, the 0 indexed line number where the exception was thrown;
199+
/// <c>column</c>, the 0 indexed column number where the exception was thrown; <c>length</c>, the
200+
/// source-length of the cause of the exception; <c>source</c>, a string containing the line of
201+
/// source code where the exception was thrown; and <c>url</c>, a string containing the name of
202+
/// the script file containing the code that threw the exception.
203+
/// </para>
204+
/// <para>
205+
/// Requires an active script context.
206+
/// </para>
207+
/// </remarks>
208+
/// <param name="metadata">The exception metadata for the runtime of the current context.</param>
209+
/// <returns>
210+
/// The code <c>JsNoError</c> if the operation succeeded, a failure code otherwise.
211+
/// </returns>
212+
CHAKRA_API
213+
JsGetAndClearExceptionWithMetadata(
214+
_Out_ JsValueRef *metadata);
215+
183216
/// <summary>
184217
/// Called by the runtime to load the source code of the serialized script.
185218
/// </summary>

lib/Jsrt/Jsrt.cpp

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "ByteCode/ByteCodeSerializer.h"
1313
#include "Common/ByteSwap.h"
1414
#include "Library/DataView.h"
15+
#include "Library/JavascriptExceptionMetadata.h"
1516
#include "Library/JavascriptSymbol.h"
1617
#include "Library/JavascriptPromise.h"
1718
#include "Base/ThreadContextTlsEntry.h"
@@ -855,15 +856,15 @@ CHAKRA_API JsSetContextData(_In_ JsContextRef context, _In_ void *data)
855856
END_JSRT_NO_EXCEPTION
856857
}
857858

858-
void HandleScriptCompileError(Js::ScriptContext * scriptContext, CompileScriptException * se)
859+
void HandleScriptCompileError(Js::ScriptContext * scriptContext, CompileScriptException * se, const WCHAR * sourceUrl)
859860
{
860861
HRESULT hr = se->ei.scode;
861862
if (hr == E_OUTOFMEMORY || hr == VBSERR_OutOfMemory || hr == VBSERR_OutOfStack || hr == ERRnoMemory)
862863
{
863864
Js::Throw::OutOfMemory();
864865
}
865866

866-
Js::JavascriptError* error = Js::JavascriptError::CreateFromCompileScriptException(scriptContext, se);
867+
Js::JavascriptError* error = Js::JavascriptError::CreateFromCompileScriptException(scriptContext, se, sourceUrl);
867868

868869
Js::JavascriptExceptionObject * exceptionObject = RecyclerNew(scriptContext->GetRecycler(),
869870
Js::JavascriptExceptionObject, error, scriptContext, nullptr);
@@ -2544,7 +2545,10 @@ CHAKRA_API JsGetAndClearException(_Out_ JsValueRef *exception)
25442545
Js::JavascriptExceptionObject *recordedException = nullptr;
25452546

25462547
BEGIN_TRANSLATE_OOM_TO_HRESULT
2547-
recordedException = scriptContext->GetAndClearRecordedException();
2548+
if (scriptContext->HasRecordedException())
2549+
{
2550+
recordedException = scriptContext->GetAndClearRecordedException();
2551+
}
25482552
END_TRANSLATE_OOM_TO_HRESULT(hr)
25492553

25502554
if (hr == E_OUTOFMEMORY)
@@ -2999,7 +3003,7 @@ JsErrorCode RunScriptCore(JsValueRef scriptSource, const byte *script, size_t cb
29993003
{
30003004
PERFORM_JSRT_TTD_RECORD_ACTION_NOT_IMPLEMENTED(scriptContext);
30013005

3002-
HandleScriptCompileError(scriptContext, &se);
3006+
HandleScriptCompileError(scriptContext, &se, sourceUrl);
30033007
return JsErrorScriptCompile;
30043008
}
30053009

@@ -4590,4 +4594,92 @@ CHAKRA_API JsGetWeakReferenceValue(
45904594
});
45914595
}
45924596

4597+
CHAKRA_API JsGetAndClearExceptionWithMetadata(_Out_ JsValueRef *metadata)
4598+
{
4599+
PARAM_NOT_NULL(metadata);
4600+
*metadata = nullptr;
4601+
4602+
JsrtContext *currentContext = JsrtContext::GetCurrent();
4603+
4604+
if (currentContext == nullptr)
4605+
{
4606+
return JsErrorNoCurrentContext;
4607+
}
4608+
4609+
Js::ScriptContext *scriptContext = currentContext->GetScriptContext();
4610+
Assert(scriptContext != nullptr);
4611+
4612+
if (scriptContext->GetRecycler() && scriptContext->GetRecycler()->IsHeapEnumInProgress())
4613+
{
4614+
return JsErrorHeapEnumInProgress;
4615+
}
4616+
else if (scriptContext->GetThreadContext()->IsInThreadServiceCallback())
4617+
{
4618+
return JsErrorInThreadServiceCallback;
4619+
}
4620+
4621+
if (scriptContext->GetThreadContext()->IsExecutionDisabled())
4622+
{
4623+
return JsErrorInDisabledState;
4624+
}
4625+
4626+
HRESULT hr = S_OK;
4627+
Js::JavascriptExceptionObject *recordedException = nullptr;
4628+
4629+
BEGIN_TRANSLATE_OOM_TO_HRESULT
4630+
if (scriptContext->HasRecordedException())
4631+
{
4632+
recordedException = scriptContext->GetAndClearRecordedException();
4633+
}
4634+
END_TRANSLATE_OOM_TO_HRESULT(hr)
4635+
4636+
if (hr == E_OUTOFMEMORY)
4637+
{
4638+
recordedException = scriptContext->GetThreadContext()->GetRecordedException();
4639+
}
4640+
if (recordedException == nullptr)
4641+
{
4642+
return JsErrorInvalidArgument;
4643+
}
4644+
4645+
Js::Var exception = recordedException->GetThrownObject(nullptr);
4646+
4647+
if (exception == nullptr)
4648+
{
4649+
// TODO: How does this early bailout impact TTD?
4650+
return JsErrorInvalidArgument;
4651+
}
4652+
4653+
return ContextAPIWrapper<false>([&](Js::ScriptContext* scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode {
4654+
Js::Var exceptionMetadata = Js::JavascriptExceptionMetadata::CreateMetadataVar(scriptContext);
4655+
Js::JavascriptOperators::OP_SetProperty(exceptionMetadata, Js::PropertyIds::exception, exception, scriptContext);
4656+
4657+
Js::FunctionBody *functionBody = recordedException->GetFunctionBody();
4658+
if (functionBody == nullptr)
4659+
{
4660+
// This is probably a parse error. We can get the error location metadata from the thrown object.
4661+
Js::JavascriptExceptionMetadata::PopulateMetadataFromCompileException(exceptionMetadata, exception, scriptContext);
4662+
}
4663+
else
4664+
{
4665+
if (!Js::JavascriptExceptionMetadata::PopulateMetadataFromException(exceptionMetadata, recordedException, scriptContext))
4666+
{
4667+
return JsErrorInvalidArgument;
4668+
}
4669+
}
4670+
4671+
*metadata = exceptionMetadata;
4672+
4673+
#if ENABLE_TTD
4674+
if (hr != E_OUTOFMEMORY)
4675+
{
4676+
PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTGetAndClearExceptionWithMetadata);
4677+
PERFORM_JSRT_TTD_RECORD_ACTION_RESULT(scriptContext, metadata);
4678+
}
4679+
#endif
4680+
4681+
4682+
return JsNoError;
4683+
});
4684+
}
45934685
#endif // CHAKRACOREBUILD_

lib/Jsrt/JsrtCommonExports.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,5 @@
118118
JsCreatePromise
119119
JsCreateWeakReference
120120
JsGetWeakReferenceValue
121+
JsGetAndClearExceptionWithMetadata
121122
#endif

lib/Jsrt/JsrtInternal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ JsErrorCode SetContextAPIWrapper(JsrtContext* newContext, Fn fn)
374374
return errorCode;
375375
}
376376

377-
void HandleScriptCompileError(Js::ScriptContext * scriptContext, CompileScriptException * se);
377+
void HandleScriptCompileError(Js::ScriptContext * scriptContext, CompileScriptException * se, const WCHAR * sourceUrl = nullptr);
378378

379379
#if DBG
380380
#define _PREPARE_RETURN_NO_EXCEPTION __debugCheckNoException.hasException = false;

lib/Runtime/Base/JnDirectFields.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,10 @@ ENTRY(isLockFree)
765765
ENTRY(wait)
766766
ENTRY(wake)
767767

768+
ENTRY(column)
769+
ENTRY(url)
770+
ENTRY(exception)
771+
768772
// Note: Do not add fields for conditionally-compiled PropertyIds into this file.
769773
// The bytecode for internal javascript libraries is built on chk but re-used in fre builds.
770774
// Having a mismatch in the number of PropertyIds will cause a failure loading bytecode.

lib/Runtime/Debug/TTActionEvents.cpp

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
//-------------------------------------------------------------------------------------------------------
55
#include "RuntimeDebugPch.h"
66

7+
#include "Library/JavascriptExceptionMetadata.h"
8+
79
#if ENABLE_TTD
810

911
namespace TTD
@@ -390,6 +392,52 @@ namespace TTD
390392
throw TTDebuggerAbortException::CreateAbortEndOfLog(_u("End of log reached with Host Process Exit -- returning to top-level."));
391393
}
392394

395+
void GetAndClearExceptionWithMetadataAction_Execute(const EventLogEntry* evt, ThreadContextTTD* executeContext)
396+
{
397+
TTD_REPLAY_ACTIVE_CONTEXT(executeContext);
398+
399+
HRESULT hr = S_OK;
400+
Js::JavascriptExceptionObject *recordedException = nullptr;
401+
402+
BEGIN_TRANSLATE_OOM_TO_HRESULT
403+
if (ctx->HasRecordedException())
404+
{
405+
recordedException = ctx->GetAndClearRecordedException();
406+
}
407+
END_TRANSLATE_OOM_TO_HRESULT(hr)
408+
409+
Js::Var exception = nullptr;
410+
if (recordedException != nullptr)
411+
{
412+
exception = recordedException->GetThrownObject(nullptr);
413+
}
414+
415+
if (exception != nullptr)
416+
{
417+
Js::ScriptContext * scriptContext = executeContext->GetActiveScriptContext();
418+
Js::Var exceptionMetadata = Js::JavascriptExceptionMetadata::CreateMetadataVar(scriptContext);
419+
420+
Js::FunctionBody *functionBody = recordedException->GetFunctionBody();
421+
422+
Js::JavascriptOperators::OP_SetProperty(exceptionMetadata, Js::PropertyIds::exception, exception, scriptContext);
423+
424+
if (functionBody == nullptr)
425+
{
426+
// This is probably a parse error. We can get the error location metadata from the thrown object.
427+
Js::JavascriptExceptionMetadata::PopulateMetadataFromCompileException(exceptionMetadata, exception, scriptContext);
428+
}
429+
else
430+
{
431+
if (!Js::JavascriptExceptionMetadata::PopulateMetadataFromException(exceptionMetadata, recordedException, scriptContext))
432+
{
433+
return;
434+
}
435+
}
436+
437+
JsRTActionHandleResultForReplay<JsRTResultOnlyAction, EventKind::GetAndClearExceptionWithMetadataActionTag>(executeContext, evt, exceptionMetadata);
438+
}
439+
}
440+
393441
void GetAndClearExceptionAction_Execute(const EventLogEntry* evt, ThreadContextTTD* executeContext)
394442
{
395443
TTD_REPLAY_ACTIVE_CONTEXT(executeContext);
@@ -398,7 +446,10 @@ namespace TTD
398446
Js::JavascriptExceptionObject *recordedException = nullptr;
399447

400448
BEGIN_TRANSLATE_OOM_TO_HRESULT
401-
recordedException = ctx->GetAndClearRecordedException();
449+
if (ctx->HasRecordedException())
450+
{
451+
recordedException = ctx->GetAndClearRecordedException();
452+
}
402453
END_TRANSLATE_OOM_TO_HRESULT(hr)
403454

404455
Js::Var exception = nullptr;

lib/Runtime/Debug/TTActionEvents.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ namespace TTD
484484
void AllocateFunctionAction_Execute(const EventLogEntry* evt, ThreadContextTTD* executeContext);
485485

486486
void HostProcessExitAction_Execute(const EventLogEntry* evt, ThreadContextTTD* executeContext);
487+
void GetAndClearExceptionWithMetadataAction_Execute(const EventLogEntry* evt, ThreadContextTTD* executeContext);
487488
void GetAndClearExceptionAction_Execute(const EventLogEntry* evt, ThreadContextTTD* executeContext);
488489
void SetExceptionAction_Execute(const EventLogEntry* evt, ThreadContextTTD* executeContext);
489490

0 commit comments

Comments
 (0)