void CrossSite::MarshalPrototypeChain(ScriptContext* scriptContext, DynamicObject * object) { RecyclableObject * prototype = object->GetPrototype(); while (prototype->GetTypeId() != TypeIds_Null && prototype->GetTypeId() != TypeIds_HostDispatch) { // We should not see any static type or host dispatch here DynamicObject * prototypeObject = DynamicObject::FromVar(prototype); if (prototypeObject->IsCrossSiteObject()) { break; } if (scriptContext != prototypeObject->GetScriptContext() && !prototypeObject->IsExternal()) { MarshalDynamicObject(scriptContext, prototypeObject); } prototype = prototypeObject->GetPrototype(); } }
// For prototype chain to install cross-site thunk. // When we change prototype using __proto__, those prototypes might not have cross-site thunks // installed even though the CEO is accessed from a different context. During ChangePrototype time // we don't really know where the requestContext is. // Force installing cross-site thunk for all prototype changes. It's a relatively less frequently used // scenario. void CrossSite::ForceCrossSiteThunkOnPrototypeChain(RecyclableObject* object) { if (TaggedNumber::Is(object)) { return; } while (DynamicType::Is(object->GetTypeId()) && !JavascriptProxy::Is(object)) { DynamicObject* dynamicObject = DynamicObject::UnsafeFromVar(object); if (!dynamicObject->IsCrossSiteObject() && !dynamicObject->IsExternal()) { // force to install cross-site thunk on prototype objects. dynamicObject->MarshalToScriptContext(nullptr); } object = object->GetPrototype(); } return; }
Var CrossSite::CommonThunk(RecyclableObject* recyclableObject, JavascriptMethod entryPoint, Arguments args) { DynamicObject* function = DynamicObject::FromVar(recyclableObject); FunctionInfo * functionInfo = (JavascriptFunction::Is(function) ? JavascriptFunction::FromVar(function)->GetFunctionInfo() : nullptr); AutoDisableRedeferral autoDisableRedeferral(functionInfo); ScriptContext* targetScriptContext = function->GetScriptContext(); Assert(!targetScriptContext->IsClosed()); Assert(function->IsExternal() || function->IsCrossSiteObject()); Assert(targetScriptContext->GetThreadContext()->IsScriptActive()); HostScriptContext* calleeHostScriptContext = targetScriptContext->GetHostScriptContext(); HostScriptContext* callerHostScriptContext = targetScriptContext->GetThreadContext()->GetPreviousHostScriptContext(); if (callerHostScriptContext == calleeHostScriptContext || (callerHostScriptContext == nullptr && !calleeHostScriptContext->HasCaller())) { return JavascriptFunction::CallFunction<true>(function, entryPoint, args, true /*useLargeArgCount*/); } #if DBG_DUMP || defined(PROFILE_EXEC) || defined(PROFILE_MEM) calleeHostScriptContext->EnsureParentInfo(callerHostScriptContext->GetScriptContext()); #endif TTD_XSITE_LOG(recyclableObject->GetScriptContext(), "CommonThunk -- Pass Through", recyclableObject); uint i = 0; if (args.Values[0] == nullptr) { i = 1; Assert(args.IsNewCall()); Assert(JavascriptProxy::Is(function) || (JavascriptFunction::Is(function) && JavascriptFunction::FromVar(function)->GetFunctionInfo()->GetAttributes() & FunctionInfo::SkipDefaultNewObject)); } uint count = args.Info.Count; for (; i < count; i++) { args.Values[i] = CrossSite::MarshalVar(targetScriptContext, args.Values[i]); } if (args.HasExtraArg()) { // The final eval arg is a frame display that needs to be marshaled specially. args.Values[count] = CrossSite::MarshalFrameDisplay(targetScriptContext, args.GetFrameDisplay()); } #if ENABLE_NATIVE_CODEGEN CheckCodeGenFunction checkCodeGenFunction = GetCheckCodeGenFunction(entryPoint); if (checkCodeGenFunction != nullptr) { ScriptFunction* callFunc = ScriptFunction::FromVar(function); entryPoint = checkCodeGenFunction(callFunc); Assert(CrossSite::IsThunk(function->GetEntryPoint())); } #endif // We need to setup the caller chain when we go across script site boundary. Property access // is OK, and we need to let host know who the caller is when a call is from another script site. // CrossSiteObject is the natural place but it is in the target site. We build up the site // chain through PushDispatchExCaller/PopDispatchExCaller, and we call SetCaller in the target site // to indicate who the caller is. We first need to get the site from the previously pushed site // and set that as the caller for current call, and push a new DispatchExCaller for future calls // off this site. GetDispatchExCaller and ReleaseDispatchExCaller is used to get the current caller. // currentDispatchExCaller is cached to avoid multiple allocations. IUnknown* sourceCaller = nullptr, *previousSourceCaller = nullptr; HRESULT hr = NOERROR; Var result = nullptr; BOOL wasDispatchExCallerPushed = FALSE, wasCallerSet = FALSE; TryFinally([&]() { hr = callerHostScriptContext->GetDispatchExCaller((void**)&sourceCaller); if (SUCCEEDED(hr)) { hr = calleeHostScriptContext->SetCaller((IUnknown*)sourceCaller, (IUnknown**)&previousSourceCaller); } if (SUCCEEDED(hr)) { wasCallerSet = TRUE; hr = calleeHostScriptContext->PushHostScriptContext(); } if (FAILED(hr)) { // CONSIDER: Should this be callerScriptContext if we failed? JavascriptError::MapAndThrowError(targetScriptContext, hr); } wasDispatchExCallerPushed = TRUE; result = JavascriptFunction::CallFunction<true>(function, entryPoint, args, true /*useLargeArgCount*/); ScriptContext* callerScriptContext = callerHostScriptContext->GetScriptContext(); result = CrossSite::MarshalVar(callerScriptContext, result); }, [&](bool hasException) { if (sourceCaller != nullptr) { callerHostScriptContext->ReleaseDispatchExCaller(sourceCaller); } IUnknown* originalCaller = nullptr; if (wasDispatchExCallerPushed) { calleeHostScriptContext->PopHostScriptContext(); } if (wasCallerSet) { calleeHostScriptContext->SetCaller(previousSourceCaller, &originalCaller); if (previousSourceCaller) { previousSourceCaller->Release(); } if (originalCaller) { originalCaller->Release(); } } }); Assert(result != nullptr); return result; }
Var CrossSite::MarshalVarInner(ScriptContext* scriptContext, __in Js::RecyclableObject* object, bool fRequestWrapper) { if (scriptContext == object->GetScriptContext()) { if (DoRequestWrapper(object, fRequestWrapper)) { // If we get here then we need to either wrap in the caller's type system or we need to return undefined. // VBScript will pass in the scriptContext (requestContext) from the JavascriptDispatch and this will be the // same as the object's script context and so we have to safely pretend this value doesn't exist. return scriptContext->GetLibrary()->GetUndefined(); } return object; } AssertMsg(scriptContext->GetThreadContext() == object->GetScriptContext()->GetThreadContext(), "ScriptContexts should belong to same threadcontext for marshalling."); // In heapenum, we are traversing through the object graph to dump out the content of recyclable objects. The content // of the objects are duplicated to the heapenum result, and we are not storing/changing the object graph during heap enum. // We don't actually need to do cross site thunk here. if (scriptContext->GetRecycler()->IsHeapEnumInProgress()) { return object; } #if ENABLE_TTD if (scriptContext->IsTTDSnapshotOrInflateInProgress()) { return object; } #endif // Marshaling should not cause any re-entrancy. JS_REENTRANCY_LOCK(jsReentLock, scriptContext->GetThreadContext()); #if ENABLE_COPYONACCESS_ARRAY JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(object); #endif TypeId typeId = object->GetTypeId(); AssertMsg(typeId != TypeIds_Enumerator, "enumerator shouldn't be marshalled here"); // At the moment the mental model for WithScopeObject Marshaling is this: // Are we trying to marshal a WithScopeObject in the Frame Display? - then 1) unwrap in MarshalFrameDisplay, // 2) marshal the wrapped object, 3) Create a new WithScopeObject in the current scriptContext and re-wrap. // We can avoid copying the WithScopeObject because it has no properties and never should. // Thus creating a new WithScopeObject per context in MarshalFrameDisplay should be kosher. // If it is not a FrameDisplay then we should not marshal. We can wrap cross context objects with a // withscopeObject in a different context. When we unwrap for property lookups and the wrapped object // is cross context, then we marshal the wrapped object into the current scriptContext, thus avoiding // the need to copy the WithScopeObject itself. Thus We don't have to handle marshaling the WithScopeObject // in non-FrameDisplay cases. AssertMsg(typeId != TypeIds_WithScopeObject, "WithScopeObject shouldn't be marshalled here"); if (StaticType::Is(typeId)) { TTD_XSITE_LOG(object->GetScriptContext(), "CloneToScriptContext", object); return object->CloneToScriptContext(scriptContext); } if (typeId == TypeIds_ModuleRoot) { RootObjectBase *moduleRoot = static_cast<RootObjectBase*>(object); HostObjectBase * hostObject = moduleRoot->GetHostObject(); // When marshaling module root, all we need is the host object. // So, if the module root which is being marshaled has host object, marshal it. if (hostObject) { TTD_XSITE_LOG(object->GetScriptContext(), "hostObject", hostObject); Var hostDispatch = hostObject->GetHostDispatchVar(); return CrossSite::MarshalVar(scriptContext, hostDispatch); } } if (typeId == TypeIds_Function) { if (object == object->GetScriptContext()->GetLibrary()->GetDefaultAccessorFunction() ) { TTD_XSITE_LOG(object->GetScriptContext(), "DefaultAccessorFunction", object); return scriptContext->GetLibrary()->GetDefaultAccessorFunction(); } if (DoRequestWrapper(object, fRequestWrapper)) { TTD_XSITE_LOG(object->GetScriptContext(), "CreateWrappedExternalFunction", object); // Marshal as a cross-site thunk if necessary before re-wrapping in an external function thunk. MarshalVarInner(scriptContext, object, false); return scriptContext->GetLibrary()->CreateWrappedExternalFunction(static_cast<JavascriptExternalFunction*>(object)); } } // We have an object marshaled, we need to keep track of the related script context // so optimization overrides can be updated as a group scriptContext->optimizationOverrides.Merge(&object->GetScriptContext()->optimizationOverrides); DynamicObject * dynamicObject = DynamicObject::FromVar(object); if (!dynamicObject->IsExternal()) { if (!dynamicObject->IsCrossSiteObject()) { if (JavascriptProxy::Is(dynamicObject)) { // We don't need to marshal the prototype chain in the case of Proxy. Otherwise we will go to the user code. TTD_XSITE_LOG(object->GetScriptContext(), "MarshalDynamicObject", object); MarshalDynamicObject(scriptContext, dynamicObject); } else { TTD_XSITE_LOG(object->GetScriptContext(), "MarshalDynamicObjectAndPrototype", object); MarshalDynamicObjectAndPrototype(scriptContext, dynamicObject); } } } else { MarshalPrototypeChain(scriptContext, dynamicObject); if (Js::JavascriptConversion::IsCallable(dynamicObject)) { TTD_XSITE_LOG(object->GetScriptContext(), "MarshalToScriptContext", object); dynamicObject->MarshalToScriptContext(scriptContext); } } return dynamicObject; }