Skip to content

Commit 9c6f4fb

Browse files
committed
[1.8>1.9] [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 5958a42 + 4c01fc0 commit 9c6f4fb

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
@@ -618,8 +618,8 @@ CHAKRA_API
618618
/// Requires an active script context.
619619
/// </para>
620620
/// <para>
621-
/// The runtime will hold on to the buffer until all instances of any functions created from
622-
/// the buffer are garbage collected.
621+
/// The runtime will detach the data from the buffer and hold on to it until all
622+
/// instances of any functions created from the buffer are garbage collected.
623623
/// </para>
624624
/// </remarks>
625625
/// <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
@@ -464,6 +464,26 @@ CHAKRA_API JsPrivateCollectGarbageSkipStack(_In_ JsRuntimeHandle runtimeHandle)
464464
{
465465
return JsCollectGarbageCommon<CollectNowExhaustiveSkipStack>(runtimeHandle);
466466
}
467+
468+
CHAKRA_API JsPrivateDetachArrayBuffer(_In_ JsValueRef ref, _Out_ void** detachedState)
469+
{
470+
return GlobalAPIWrapper_NoRecord([&]() -> JsErrorCode
471+
{
472+
VALIDATE_JSREF(ref);
473+
*detachedState = Js::JavascriptOperators::DetachVarAndGetState(ref);
474+
return JsNoError;
475+
});
476+
}
477+
478+
CHAKRA_API JsPrivateFreeDetachedArrayBuffer(_In_ void* detachedState)
479+
{
480+
return GlobalAPIWrapper_NoRecord([&]() -> JsErrorCode
481+
{
482+
auto state = reinterpret_cast<Js::ArrayBufferDetachedStateBase*>(detachedState);
483+
state->CleanUp();
484+
return JsNoError;
485+
});
486+
}
467487
#endif
468488

469489
CHAKRA_API JsDisposeRuntime(_In_ JsRuntimeHandle runtimeHandle)
@@ -3790,7 +3810,7 @@ template <typename TLoadCallback, typename TUnloadCallback>
37903810
JsErrorCode RunSerializedScriptCore(
37913811
TLoadCallback scriptLoadCallback, TUnloadCallback scriptUnloadCallback,
37923812
JsSourceContext scriptLoadSourceContext, // only used by scriptLoadCallback
3793-
unsigned char *buffer, JsValueRef bufferVal,
3813+
unsigned char *buffer, Js::ArrayBuffer* bufferVal,
37943814
JsSourceContext sourceContext, const WCHAR *sourceUrl,
37953815
bool parseOnly, JsValueRef *result)
37963816
{
@@ -5066,12 +5086,13 @@ CHAKRA_API JsParseSerialized(
50665086
return JsErrorInvalidArgument;
50675087
}
50685088

5069-
byte* buffer = Js::ArrayBuffer::FromVar(bufferVal)->GetBuffer();
5089+
Js::ArrayBuffer* arrayBuffer = Js::ArrayBuffer::FromVar(bufferVal);
5090+
byte* buffer = arrayBuffer->GetBuffer();
50705091

50715092
return RunSerializedScriptCore(
50725093
scriptLoadCallback, DummyScriptUnloadCallback,
50735094
sourceContext,// use the same user provided sourceContext as scriptLoadSourceContext
5074-
buffer, bufferVal, sourceContext, url, true, result);
5095+
buffer, arrayBuffer, sourceContext, url, true, result);
50755096
}
50765097

50775098
CHAKRA_API JsRunSerialized(
@@ -5099,12 +5120,13 @@ CHAKRA_API JsRunSerialized(
50995120
return JsErrorInvalidArgument;
51005121
}
51015122

5102-
byte* buffer = Js::ArrayBuffer::FromVar(bufferVal)->GetBuffer();
5123+
Js::ArrayBuffer* arrayBuffer = Js::ArrayBuffer::FromVar(bufferVal);
5124+
byte* buffer = arrayBuffer->GetBuffer();
51035125

51045126
return RunSerializedScriptCore(
51055127
scriptLoadCallback, DummyScriptUnloadCallback,
51065128
sourceContext, // use the same user provided sourceContext as scriptLoadSourceContext
5107-
buffer, bufferVal, sourceContext, url, false, result);
5129+
buffer, arrayBuffer, sourceContext, url, false, result);
51085130
}
51095131

51105132
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
@@ -6589,6 +6589,13 @@ namespace Js
65896589
return arr;
65906590
}
65916591

6592+
ArrayBuffer* JavascriptLibrary::CreateExternalArrayBuffer(byte* buffer, uint32 length)
6593+
{
6594+
ArrayBuffer* arr = ExternalArrayBuffer::Create(buffer, length, arrayBufferType);
6595+
JS_ETW(EventWriteJSCRIPT_RECYCLER_ALLOCATE_OBJECT(arr));
6596+
return arr;
6597+
}
6598+
65926599
DataView* JavascriptLibrary::CreateDataView(ArrayBufferBase* arrayBuffer, uint32 offset, uint32 length)
65936600
{
65946601
DataView* dataView = RecyclerNew(this->GetRecycler(), DataView, arrayBuffer, offset, length, dataViewType);

0 commit comments

Comments
 (0)