Skip to content

Commit 4c01fc0

Browse files
committed
[MERGE #5417 @sethbrenith] JsRunSerialized crash
Merge pull request #5417 from sethbrenith:user/sethb/script-buffer-lifetime JsrtSourceHolder needs to ensure that the input ArrayBuffer's data stays alive until after all related FunctionEntryPointInfos have finalized (because they fetch their name for ETW logging during Finalize). Fixes OS:18095136
2 parents 0aeb3a7 + c20ac22 commit 4c01fc0

11 files changed

+145
-13
lines changed

lib/Jsrt/ChakraCore.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -540,8 +540,8 @@ CHAKRA_API
540540
/// Requires an active script context.
541541
/// </para>
542542
/// <para>
543-
/// The runtime will hold on to the buffer until all instances of any functions created from
544-
/// the buffer are garbage collected.
543+
/// The runtime will detach the data from the buffer and hold on to it until all
544+
/// instances of any functions created from the buffer are garbage collected.
545545
/// </para>
546546
/// </remarks>
547547
/// <param name="buffer">The serialized script as an ArrayBuffer (preferably ExternalArrayBuffer).</param>

lib/Jsrt/Jsrt.cpp

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,26 @@ CHAKRA_API JsPrivateCollectGarbageSkipStack(_In_ JsRuntimeHandle runtimeHandle)
463463
{
464464
return JsCollectGarbageCommon<CollectNowExhaustiveSkipStack>(runtimeHandle);
465465
}
466+
467+
CHAKRA_API JsPrivateDetachArrayBuffer(_In_ JsValueRef ref, _Out_ void** detachedState)
468+
{
469+
return GlobalAPIWrapper_NoRecord([&]() -> JsErrorCode
470+
{
471+
VALIDATE_JSREF(ref);
472+
*detachedState = Js::JavascriptOperators::DetachVarAndGetState(ref);
473+
return JsNoError;
474+
});
475+
}
476+
477+
CHAKRA_API JsPrivateFreeDetachedArrayBuffer(_In_ void* detachedState)
478+
{
479+
return GlobalAPIWrapper_NoRecord([&]() -> JsErrorCode
480+
{
481+
auto state = reinterpret_cast<Js::ArrayBufferDetachedStateBase*>(detachedState);
482+
state->CleanUp();
483+
return JsNoError;
484+
});
485+
}
466486
#endif
467487

468488
CHAKRA_API JsDisposeRuntime(_In_ JsRuntimeHandle runtimeHandle)
@@ -3706,7 +3726,7 @@ template <typename TLoadCallback, typename TUnloadCallback>
37063726
JsErrorCode RunSerializedScriptCore(
37073727
TLoadCallback scriptLoadCallback, TUnloadCallback scriptUnloadCallback,
37083728
JsSourceContext scriptLoadSourceContext, // only used by scriptLoadCallback
3709-
unsigned char *buffer, JsValueRef bufferVal,
3729+
unsigned char *buffer, Js::ArrayBuffer* bufferVal,
37103730
JsSourceContext sourceContext, const WCHAR *sourceUrl,
37113731
bool parseOnly, JsValueRef *result)
37123732
{
@@ -4939,12 +4959,13 @@ CHAKRA_API JsParseSerialized(
49394959
return JsErrorInvalidArgument;
49404960
}
49414961

4942-
byte* buffer = Js::ArrayBuffer::FromVar(bufferVal)->GetBuffer();
4962+
Js::ArrayBuffer* arrayBuffer = Js::ArrayBuffer::FromVar(bufferVal);
4963+
byte* buffer = arrayBuffer->GetBuffer();
49434964

49444965
return RunSerializedScriptCore(
49454966
scriptLoadCallback, DummyScriptUnloadCallback,
49464967
sourceContext,// use the same user provided sourceContext as scriptLoadSourceContext
4947-
buffer, bufferVal, sourceContext, url, true, result);
4968+
buffer, arrayBuffer, sourceContext, url, true, result);
49484969
}
49494970

49504971
CHAKRA_API JsRunSerialized(
@@ -4972,12 +4993,13 @@ CHAKRA_API JsRunSerialized(
49724993
return JsErrorInvalidArgument;
49734994
}
49744995

4975-
byte* buffer = Js::ArrayBuffer::FromVar(bufferVal)->GetBuffer();
4996+
Js::ArrayBuffer* arrayBuffer = Js::ArrayBuffer::FromVar(bufferVal);
4997+
byte* buffer = arrayBuffer->GetBuffer();
49764998

49774999
return RunSerializedScriptCore(
49785000
scriptLoadCallback, DummyScriptUnloadCallback,
49795001
sourceContext, // use the same user provided sourceContext as scriptLoadSourceContext
4980-
buffer, bufferVal, sourceContext, url, false, result);
5002+
buffer, arrayBuffer, sourceContext, url, false, result);
49815003
}
49825004

49835005
CHAKRA_API JsCreatePromise(_Out_ JsValueRef *promise, _Out_ JsValueRef *resolve, _Out_ JsValueRef *reject)

lib/Jsrt/JsrtExternalArrayBuffer.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,41 @@ namespace Js
1919
}
2020

2121
void JsrtExternalArrayBuffer::Finalize(bool isShutdown)
22+
{
23+
if (finalizeCallback != nullptr && !isDetached)
24+
{
25+
finalizeCallback(callbackState);
26+
}
27+
}
28+
29+
ArrayBufferDetachedStateBase* JsrtExternalArrayBuffer::CreateDetachedState(BYTE* buffer, DECLSPEC_GUARD_OVERFLOW uint32 bufferLength)
30+
{
31+
return HeapNew(JsrtExternalArrayBufferDetachedState, buffer, bufferLength, finalizeCallback, callbackState);
32+
};
33+
34+
JsrtExternalArrayBuffer::JsrtExternalArrayBufferDetachedState::JsrtExternalArrayBufferDetachedState(
35+
BYTE* buffer, uint32 bufferLength, JsFinalizeCallback finalizeCallback, void *callbackState)
36+
: ExternalArrayBufferDetachedState(buffer, bufferLength), finalizeCallback(finalizeCallback), callbackState(callbackState)
37+
{}
38+
39+
void JsrtExternalArrayBuffer::JsrtExternalArrayBufferDetachedState::ClearSelfOnly()
40+
{
41+
HeapDelete(this);
42+
}
43+
44+
void JsrtExternalArrayBuffer::JsrtExternalArrayBufferDetachedState::DiscardState()
2245
{
2346
if (finalizeCallback != nullptr)
2447
{
2548
finalizeCallback(callbackState);
2649
}
50+
finalizeCallback = nullptr;
51+
}
52+
53+
ArrayBuffer* JsrtExternalArrayBuffer::JsrtExternalArrayBufferDetachedState::Create(JavascriptLibrary* library)
54+
{
55+
ArrayBuffer* arr = JsrtExternalArrayBuffer::New(buffer, bufferLength, finalizeCallback, callbackState, library->GetArrayBufferType());
56+
JS_ETW(EventWriteJSCRIPT_RECYCLER_ALLOCATE_OBJECT(arr));
57+
return arr;
2758
}
2859
}

lib/Jsrt/JsrtExternalArrayBuffer.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace Js {
1212
DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(JsrtExternalArrayBuffer);
1313

1414
JsrtExternalArrayBuffer(byte *buffer, uint32 length, JsFinalizeCallback finalizeCallback, void *callbackState, DynamicType *type);
15+
virtual ArrayBufferDetachedStateBase* CreateDetachedState(BYTE* buffer, DECLSPEC_GUARD_OVERFLOW uint32 bufferLength) override;
1516

1617
public:
1718
static JsrtExternalArrayBuffer* New(byte *buffer, uint32 length, JsFinalizeCallback finalizeCallback, void *callbackState, DynamicType *type);
@@ -20,6 +21,17 @@ namespace Js {
2021
private:
2122
FieldNoBarrier(JsFinalizeCallback) finalizeCallback;
2223
Field(void *) callbackState;
24+
25+
class JsrtExternalArrayBufferDetachedState : public ExternalArrayBufferDetachedState
26+
{
27+
FieldNoBarrier(JsFinalizeCallback) finalizeCallback;
28+
Field(void *) callbackState;
29+
public:
30+
JsrtExternalArrayBufferDetachedState(BYTE* buffer, uint32 bufferLength, JsFinalizeCallback finalizeCallback, void *callbackState);
31+
virtual void ClearSelfOnly() override;
32+
virtual void DiscardState() override;
33+
virtual ArrayBuffer* Create(JavascriptLibrary* library) override;
34+
};
2335
};
2436
}
2537
AUTO_REGISTER_RECYCLER_OBJECT_DUMPER(Js::JsrtExternalArrayBuffer, &Js::RecyclableObject::DumpObjectFunction);

lib/Jsrt/JsrtSourceHolder.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,6 @@ namespace Js
299299
this->mappedSource = nullptr;
300300
}
301301
this->mappedScriptValue = nullptr;
302-
this->mappedSerializedScriptValue = nullptr;
303302

304303
// Don't allow load or unload again after told to unload.
305304
scriptLoadCallback = nullptr;

lib/Jsrt/JsrtSourceHolder.h

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace Js
1919

2020
#ifndef NTBUILD
2121
Field(JsValueRef) mappedScriptValue;
22-
Field(JsValueRef) mappedSerializedScriptValue;
22+
Field(DetachedStateBase*) mappedSerializedScriptValue;
2323
#endif
2424
Field(utf8char_t const *) mappedSource;
2525
Field(size_t) mappedSourceByteLength;
@@ -46,13 +46,13 @@ namespace Js
4646
JsrtSourceHolder(_In_ TLoadCallback scriptLoadCallback,
4747
_In_ TUnloadCallback scriptUnloadCallback,
4848
_In_ JsSourceContext sourceContext,
49-
JsValueRef serializedScriptValue = nullptr) :
49+
Js::ArrayBuffer* serializedScriptValue = nullptr) :
5050
scriptLoadCallback(scriptLoadCallback),
5151
scriptUnloadCallback(scriptUnloadCallback),
5252
sourceContext(sourceContext),
5353
#ifndef NTBUILD
5454
mappedScriptValue(nullptr),
55-
mappedSerializedScriptValue(serializedScriptValue),
55+
mappedSerializedScriptValue(serializedScriptValue == nullptr ? nullptr : serializedScriptValue->DetachAndGetState()),
5656
#endif
5757
mappedSourceByteLength(0),
5858
mappedSource(nullptr)
@@ -89,6 +89,15 @@ namespace Js
8989

9090
virtual void Dispose(bool isShutdown) override
9191
{
92+
#ifndef NTBUILD
93+
if (this->mappedSerializedScriptValue != nullptr)
94+
{
95+
// We have to extend the buffer data's lifetime until Dispose because
96+
// it might be used during finalization of other objects, such as
97+
// FunctionEntryPointInfo which wants to log its name.
98+
this->mappedSerializedScriptValue->CleanUp();
99+
}
100+
#endif
92101
}
93102

94103
virtual void Mark(Recycler * recycler) override

lib/Runtime/DetachedStateBase.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ namespace Js
4747
{
4848
Heap = 0x0,
4949
CoTask = 0x1,
50-
MemAlloc = 0x02
50+
MemAlloc = 0x02,
51+
External = 0x03,
5152
} ArrayBufferAllocationType;
5253

5354
class ArrayBufferDetachedStateBase : public DetachedStateBase

lib/Runtime/Library/ArrayBuffer.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ namespace Js
3737
case ArrayBufferAllocationType::MemAlloc:
3838
toReturn = library->CreateArrayBuffer(arrayBufferState->buffer, arrayBufferState->bufferLength);
3939
break;
40+
case ArrayBufferAllocationType::External:
41+
toReturn = static_cast<ExternalArrayBufferDetachedState*>(state)->Create(library);
42+
break;
4043
default:
4144
AssertMsg(false, "Unknown allocationType of ArrayBufferDetachedStateBase ");
4245
}
@@ -1028,11 +1031,27 @@ namespace Js
10281031
CoTaskMemFree(buffer);
10291032
}
10301033

1034+
ArrayBuffer* ExternalArrayBufferDetachedState::Create(JavascriptLibrary* library)
1035+
{
1036+
return library->CreateExternalArrayBuffer(buffer, bufferLength);
1037+
}
1038+
10311039
ExternalArrayBuffer::ExternalArrayBuffer(byte *buffer, uint32 length, DynamicType *type)
10321040
: ArrayBuffer(buffer, length, type)
10331041
{
10341042
}
10351043

1044+
ExternalArrayBuffer* ExternalArrayBuffer::Create(byte* buffer, uint32 length, DynamicType * type)
1045+
{
1046+
// This type does not own the external memory, so don't AddExternalMemoryUsage like other ArrayBuffer types do
1047+
return RecyclerNewFinalized(type->GetScriptContext()->GetRecycler(), ExternalArrayBuffer, buffer, length, type);
1048+
}
1049+
1050+
ArrayBufferDetachedStateBase* ExternalArrayBuffer::CreateDetachedState(BYTE* buffer, DECLSPEC_GUARD_OVERFLOW uint32 bufferLength)
1051+
{
1052+
return HeapNew(ExternalArrayBufferDetachedState, buffer, bufferLength);
1053+
};
1054+
10361055
#if ENABLE_TTD
10371056
TTD::NSSnapObjects::SnapObjectType ExternalArrayBuffer::GetSnapTag_TTD() const
10381057
{
@@ -1058,4 +1077,23 @@ namespace Js
10581077
TTD::NSSnapObjects::StdExtractSetKindSpecificInfo<TTD::NSSnapObjects::SnapArrayBufferInfo*, TTD::NSSnapObjects::SnapObjectType::SnapArrayBufferObject>(objData, sabi);
10591078
}
10601079
#endif
1080+
1081+
ExternalArrayBufferDetachedState::ExternalArrayBufferDetachedState(BYTE* buffer, uint32 bufferLength)
1082+
: ArrayBufferDetachedStateBase(TypeIds_ArrayBuffer, buffer, bufferLength, ArrayBufferAllocationType::External)
1083+
{}
1084+
1085+
void ExternalArrayBufferDetachedState::ClearSelfOnly()
1086+
{
1087+
HeapDelete(this);
1088+
}
1089+
1090+
void ExternalArrayBufferDetachedState::DiscardState()
1091+
{
1092+
// Nothing to do as buffer is external
1093+
}
1094+
1095+
void ExternalArrayBufferDetachedState::Discard()
1096+
{
1097+
ClearSelfOnly();
1098+
}
10611099
}

lib/Runtime/Library/ArrayBuffer.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,15 +322,27 @@ namespace Js
322322
protected:
323323
DEFINE_VTABLE_CTOR(ExternalArrayBuffer, ArrayBuffer);
324324
DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(ExternalArrayBuffer);
325+
325326
public:
326327
ExternalArrayBuffer(byte *buffer, DECLSPEC_GUARD_OVERFLOW uint32 length, DynamicType *type);
328+
static ExternalArrayBuffer* Create(byte* buffer, DECLSPEC_GUARD_OVERFLOW uint32 length, DynamicType * type);
327329
protected:
328-
virtual ArrayBufferDetachedStateBase* CreateDetachedState(BYTE* buffer, DECLSPEC_GUARD_OVERFLOW uint32 bufferLength) override { Assert(UNREACHED); Throw::InternalError(); };
330+
virtual ArrayBufferDetachedStateBase* CreateDetachedState(BYTE* buffer, DECLSPEC_GUARD_OVERFLOW uint32 bufferLength) override;
329331

330332
#if ENABLE_TTD
331333
public:
332334
virtual TTD::NSSnapObjects::SnapObjectType GetSnapTag_TTD() const override;
333335
virtual void ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc) override;
334336
#endif
335337
};
338+
339+
class ExternalArrayBufferDetachedState : public ArrayBufferDetachedStateBase
340+
{
341+
public:
342+
ExternalArrayBufferDetachedState(BYTE* buffer, uint32 bufferLength);
343+
virtual void ClearSelfOnly() override;
344+
virtual void DiscardState() override;
345+
virtual void Discard() override;
346+
virtual ArrayBuffer* Create(JavascriptLibrary* library);
347+
};
336348
}

lib/Runtime/Library/JavascriptLibrary.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6545,6 +6545,13 @@ namespace Js
65456545
return arr;
65466546
}
65476547

6548+
ArrayBuffer* JavascriptLibrary::CreateExternalArrayBuffer(byte* buffer, uint32 length)
6549+
{
6550+
ArrayBuffer* arr = ExternalArrayBuffer::Create(buffer, length, arrayBufferType);
6551+
JS_ETW(EventWriteJSCRIPT_RECYCLER_ALLOCATE_OBJECT(arr));
6552+
return arr;
6553+
}
6554+
65486555
DataView* JavascriptLibrary::CreateDataView(ArrayBufferBase* arrayBuffer, uint32 offset, uint32 length)
65496556
{
65506557
DataView* dataView = RecyclerNew(this->GetRecycler(), DataView, arrayBuffer, offset, length, dataViewType);

0 commit comments

Comments
 (0)