CrossSite.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. #include "RuntimeBasePch.h"
  6. #include "Library/JavascriptProxy.h"
  7. #include "Library/HostObjectBase.h"
  8. #include "Types/WithScopeObject.h"
  9. namespace Js
  10. {
  11. BOOL CrossSite::NeedMarshalVar(Var instance, ScriptContext * requestContext)
  12. {
  13. if (TaggedNumber::Is(instance))
  14. {
  15. return FALSE;
  16. }
  17. RecyclableObject * object = RecyclableObject::FromVar(instance);
  18. if (object->GetScriptContext() == requestContext)
  19. {
  20. return FALSE;
  21. }
  22. if (DynamicType::Is(object->GetTypeId()))
  23. {
  24. return !DynamicObject::FromVar(object)->IsCrossSiteObject() && !object->IsExternal();
  25. }
  26. return TRUE;
  27. }
  28. void CrossSite::MarshalDynamicObject(ScriptContext * scriptContext, DynamicObject * object)
  29. {
  30. Assert(!object->IsExternal() && !object->IsCrossSiteObject());
  31. object->MarshalToScriptContext(scriptContext);
  32. if (object->GetTypeId() == TypeIds_Function)
  33. {
  34. AssertMsg(object != object->GetScriptContext()->GetLibrary()->GetDefaultAccessorFunction(), "default accessor marshalled");
  35. JavascriptFunction * function = JavascriptFunction::FromVar(object);
  36. // See if this function is one that the host needs to handle
  37. HostScriptContext * hostScriptContext = scriptContext->GetHostScriptContext();
  38. if (!hostScriptContext || !hostScriptContext->SetCrossSiteForFunctionType(function))
  39. {
  40. if (function->GetDynamicType()->GetIsShared())
  41. {
  42. function->GetLibrary()->SetCrossSiteForSharedFunctionType(function);
  43. }
  44. else
  45. {
  46. function->SetEntryPoint(function->GetScriptContext()->CurrentCrossSiteThunk);
  47. }
  48. }
  49. }
  50. }
  51. void CrossSite::MarshalPrototypeChain(ScriptContext* scriptContext, DynamicObject * object)
  52. {
  53. RecyclableObject * prototype = object->GetPrototype();
  54. while (prototype->GetTypeId() != TypeIds_Null && prototype->GetTypeId() != TypeIds_HostDispatch)
  55. {
  56. // We should not see any static type or host dispatch here
  57. DynamicObject * prototypeObject = DynamicObject::FromVar(prototype);
  58. if (prototypeObject->IsCrossSiteObject())
  59. {
  60. break;
  61. }
  62. if (scriptContext != prototypeObject->GetScriptContext() && !prototypeObject->IsExternal())
  63. {
  64. MarshalDynamicObject(scriptContext, prototypeObject);
  65. }
  66. prototype = prototypeObject->GetPrototype();
  67. }
  68. }
  69. void CrossSite::MarshalDynamicObjectAndPrototype(ScriptContext* scriptContext, DynamicObject * object)
  70. {
  71. MarshalDynamicObject(scriptContext, object);
  72. MarshalPrototypeChain(scriptContext, object);
  73. }
  74. Var CrossSite::MarshalFrameDisplay(ScriptContext* scriptContext, FrameDisplay *display)
  75. {
  76. uint16 length = display->GetLength();
  77. FrameDisplay *newDisplay =
  78. RecyclerNewPlus(scriptContext->GetRecycler(), length * sizeof(Var), FrameDisplay, length);
  79. for (uint16 i = 0; i < length; i++)
  80. {
  81. Var value = display->GetItem(i);
  82. if (WithScopeObject::Is(value))
  83. {
  84. // Here we are marshalling the wrappedObject and then ReWrapping th object in the new context.
  85. value = JavascriptOperators::ToWithObject(CrossSite::MarshalVar(scriptContext, WithScopeObject::FromVar(value)->GetWrappedObject()), scriptContext);
  86. }
  87. else
  88. {
  89. value = CrossSite::MarshalVar(scriptContext, value);
  90. }
  91. newDisplay->SetItem(i, value);
  92. }
  93. return (Var)newDisplay;
  94. }
  95. /*
  96. Enumerators are always created in current script context, and if the underlying object is a cross
  97. site object, we'll marshal the enumerator by changing the vtbl of the base enumerator, such that
  98. we will marshal the return index before it's returned to caller.
  99. Notice that enumerator marshalling is somewhat different from the object marshalling. We have only
  100. one instance of object in cross site usage, but we can create multiple enumerators from different
  101. script context for the same cross site object.
  102. */
  103. Var CrossSite::MarshalEnumerator(ScriptContext* scriptContext, Var value)
  104. {
  105. TypeId typeId = JavascriptOperators::GetTypeId(value);
  106. if (typeId != TypeIds_Enumerator)
  107. {
  108. AssertMsg(FALSE, "invalid enumerator");
  109. return value;
  110. }
  111. JavascriptEnumerator* enumerator = JavascriptEnumerator::FromVar(value);
  112. enumerator->MarshalToScriptContext(scriptContext);
  113. return enumerator;
  114. }
  115. // static
  116. __inline Var CrossSite::MarshalVar(ScriptContext* scriptContext, Var value, bool fRequestWrapper)
  117. {
  118. // value might be null from disable implicit call
  119. if (value == nullptr || Js::TaggedNumber::Is(value))
  120. {
  121. return value;
  122. }
  123. Js::RecyclableObject* object = RecyclableObject::FromVar(value);
  124. if (fRequestWrapper || scriptContext != object->GetScriptContext())
  125. {
  126. return MarshalVarInner(scriptContext, object, fRequestWrapper);
  127. }
  128. return value;
  129. }
  130. bool CrossSite::DoRequestWrapper(Js::RecyclableObject* object, bool fRequestWrapper)
  131. {
  132. return fRequestWrapper && JavascriptFunction::Is(object) && JavascriptFunction::FromVar(object)->IsExternalFunction();
  133. }
  134. Var CrossSite::MarshalVarInner(ScriptContext* scriptContext, __in Js::RecyclableObject* object, bool fRequestWrapper)
  135. {
  136. if (scriptContext == object->GetScriptContext())
  137. {
  138. if (DoRequestWrapper(object, fRequestWrapper))
  139. {
  140. // If we get here then we need to either wrap in the caller's type system or we need to return undefined.
  141. // VBScript will pass in the scriptContext (requestContext) from the JavascriptDispatch and this will be the
  142. // same as the object's script context and so we have to safely pretend this value doesn't exist.
  143. return scriptContext->GetLibrary()->GetUndefined();
  144. }
  145. return object;
  146. }
  147. AssertMsg(scriptContext->GetThreadContext() == object->GetScriptContext()->GetThreadContext(), "ScriptContexts should belong to same threadcontext for marshalling.");
  148. // In heapenum, we are traversing through the object graph to dump out the content of recyclable objects. The content
  149. // of the objects are duplicated to the heapenum result, and we are not storing/changing the object graph during heap enum.
  150. // We don't actually need to do cross site thunk here.
  151. if (scriptContext->GetRecycler()->IsHeapEnumInProgress())
  152. {
  153. return object;
  154. }
  155. #if ENABLE_COPYONACCESS_ARRAY
  156. JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(object);
  157. #endif
  158. TypeId typeId = object->GetTypeId();
  159. AssertMsg(typeId != TypeIds_Enumerator, "enumerator shouldn't be marshalled here");
  160. // At the moment the mental model for WithScopeObject Marshaling is this:
  161. // Are we trying to marshal a WithScopeObject in the Frame Display? - then 1) unwrap in MarshalFrameDisplay,
  162. // 2) marshal the wrapped object, 3) Create a new WithScopeObject in the current scriptContext and re-wrap.
  163. // We can avoid copying the WithScopeObject because it has no properties and never should.
  164. // Thus creating a new WithScopeObject per context in MarshalFrameDisplay should be kosher.
  165. // If it is not a FrameDisplay then we should not marshal. We can wrap cross context objects with a
  166. // withscopeObject in a different context. When we unwrap for property lookups and the wrapped object
  167. // is cross context, then we marshal the wrapped object into the current scriptContext, thus avoiding
  168. // the need to copy the WithScopeObject itself. Thus We don't have to handle marshaling the WithScopeObject
  169. // in non-FrameDisplay cases.
  170. AssertMsg(typeId != TypeIds_WithScopeObject, "WithScopeObject shouldn't be marshalled here");
  171. if (StaticType::Is(typeId))
  172. {
  173. return object->CloneToScriptContext(scriptContext);
  174. }
  175. if (typeId == TypeIds_ModuleRoot)
  176. {
  177. RootObjectBase *moduleRoot = static_cast<RootObjectBase*>(object);
  178. HostObjectBase * hostObject = moduleRoot->GetHostObject();
  179. // When marshaling module root, all we need is the host object.
  180. // So, if the module root which is being marshaled has host object, marshal it.
  181. if (hostObject)
  182. {
  183. Var hostDispatch = hostObject->GetHostDispatchVar();
  184. return CrossSite::MarshalVar(scriptContext, hostDispatch);
  185. }
  186. }
  187. if (typeId == TypeIds_Function)
  188. {
  189. if (object == object->GetScriptContext()->GetLibrary()->GetDefaultAccessorFunction() )
  190. {
  191. return scriptContext->GetLibrary()->GetDefaultAccessorFunction();
  192. }
  193. if (DoRequestWrapper(object, fRequestWrapper))
  194. {
  195. // Marshal as a cross-site thunk if necessary before re-wrapping in an external function thunk.
  196. MarshalVarInner(scriptContext, object, false);
  197. return scriptContext->GetLibrary()->CreateWrappedExternalFunction(static_cast<JavascriptExternalFunction*>(object));
  198. }
  199. }
  200. // We have an object marshaled, we need to keep track of the related script context
  201. // so optimization overrides can be updated as a group
  202. scriptContext->optimizationOverrides.Merge(&object->GetScriptContext()->optimizationOverrides);
  203. DynamicObject * dynamicObject = DynamicObject::FromVar(object);
  204. if (!dynamicObject->IsExternal())
  205. {
  206. if (!dynamicObject->IsCrossSiteObject())
  207. {
  208. MarshalDynamicObjectAndPrototype(scriptContext, dynamicObject);
  209. }
  210. }
  211. else
  212. {
  213. MarshalPrototypeChain(scriptContext, dynamicObject);
  214. if (Js::JavascriptConversion::IsCallable(dynamicObject))
  215. {
  216. dynamicObject->MarshalToScriptContext(scriptContext);
  217. }
  218. }
  219. return dynamicObject;
  220. }
  221. bool CrossSite::IsThunk(JavascriptMethod thunk)
  222. {
  223. return (thunk == CrossSite::ProfileThunk || thunk == CrossSite::DefaultThunk);
  224. }
  225. #ifdef ENABLE_SCRIPT_PROFILING
  226. Var CrossSite::ProfileThunk(RecyclableObject* callable, CallInfo callInfo, ...)
  227. {
  228. JavascriptFunction* function = JavascriptFunction::FromVar(callable);
  229. Assert(function->GetTypeId() == TypeIds_Function);
  230. Assert(function->GetEntryPoint() == CrossSite::ProfileThunk);
  231. RUNTIME_ARGUMENTS(args, callInfo);
  232. ScriptContext * scriptContext = function->GetScriptContext();
  233. // It is not safe to access the function body if the script context is not alive.
  234. scriptContext->VerifyAliveWithHostContext(!function->IsExternal(),
  235. scriptContext->GetThreadContext()->GetPreviousHostScriptContext());
  236. JavascriptMethod entryPoint;
  237. FunctionInfo *funcInfo = function->GetFunctionInfo();
  238. if (funcInfo->HasBody())
  239. {
  240. #if ENABLE_DEBUG_CONFIG_OPTIONS
  241. char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
  242. #endif
  243. entryPoint = (JavascriptMethod)ScriptFunction::FromVar(function)->GetEntryPointInfo()->address;
  244. if (funcInfo->IsDeferred() && scriptContext->IsProfiling())
  245. {
  246. // if the current entrypoint is deferred parse we need to update it appropriately for the profiler mode.
  247. entryPoint = Js::ScriptContext::GetProfileModeThunk(entryPoint);
  248. }
  249. OUTPUT_TRACE(Js::ScriptProfilerPhase, _u("CrossSite::ProfileThunk FunctionNumber : %s, Entrypoint : 0x%08X\n"), funcInfo->GetFunctionProxy()->GetDebugNumberSet(debugStringBuffer), entryPoint);
  250. }
  251. else
  252. {
  253. entryPoint = ProfileEntryThunk;
  254. }
  255. return CommonThunk(function, entryPoint, args);
  256. }
  257. #endif
  258. Var CrossSite::DefaultThunk(RecyclableObject* callable, CallInfo callInfo, ...)
  259. {
  260. JavascriptFunction* function = JavascriptFunction::FromVar(callable);
  261. Assert(function->GetTypeId() == TypeIds_Function);
  262. Assert(function->GetEntryPoint() == CrossSite::DefaultThunk);
  263. RUNTIME_ARGUMENTS(args, callInfo);
  264. // It is not safe to access the function body if the script context is not alive.
  265. function->GetScriptContext()->VerifyAliveWithHostContext(!function->IsExternal(),
  266. ThreadContext::GetContextForCurrentThread()->GetPreviousHostScriptContext());
  267. JavascriptMethod entryPoint;
  268. FunctionInfo *funcInfo = function->GetFunctionInfo();
  269. if (funcInfo->HasBody())
  270. {
  271. #ifdef ASMJS_PLAT
  272. if (funcInfo->GetFunctionProxy()->IsFunctionBody() &&
  273. funcInfo->GetFunctionBody()->GetIsAsmJsFunction())
  274. {
  275. entryPoint = Js::AsmJsExternalEntryPoint;
  276. }
  277. else
  278. #endif
  279. {
  280. entryPoint = (JavascriptMethod)ScriptFunction::FromVar(function)->GetEntryPointInfo()->address;
  281. }
  282. }
  283. else
  284. {
  285. entryPoint = funcInfo->GetOriginalEntryPoint();
  286. }
  287. return CommonThunk(function, entryPoint, args);
  288. }
  289. Var CrossSite::CommonThunk(RecyclableObject* recyclableObject, JavascriptMethod entryPoint, Arguments args)
  290. {
  291. DynamicObject* function = DynamicObject::FromVar(recyclableObject);
  292. ScriptContext* targetScriptContext = function->GetScriptContext();
  293. Assert(!targetScriptContext->IsClosed());
  294. Assert(function->IsExternal() || function->IsCrossSiteObject());
  295. Assert(targetScriptContext->GetThreadContext()->IsScriptActive());
  296. HostScriptContext* calleeHostScriptContext = targetScriptContext->GetHostScriptContext();
  297. HostScriptContext* callerHostScriptContext = targetScriptContext->GetThreadContext()->GetPreviousHostScriptContext();
  298. if (callerHostScriptContext == calleeHostScriptContext || (callerHostScriptContext == nullptr && !calleeHostScriptContext->HasCaller()))
  299. {
  300. return JavascriptFunction::CallFunction<true>(function, entryPoint, args);
  301. }
  302. #if DBG_DUMP || defined(PROFILE_EXEC) || defined(PROFILE_MEM)
  303. calleeHostScriptContext->EnsureParentInfo(callerHostScriptContext->GetScriptContext());
  304. #endif
  305. uint i = 0;
  306. if (args.Values[0] == nullptr)
  307. {
  308. i = 1;
  309. Assert(args.Info.Flags & CallFlags_New);
  310. Assert(JavascriptFunction::Is(function) && JavascriptFunction::FromVar(function)->GetFunctionInfo()->GetAttributes() & FunctionInfo::SkipDefaultNewObject);
  311. }
  312. uint count = args.Info.Count;
  313. if ((args.Info.Flags & CallFlags_ExtraArg) && ((args.Info.Flags & CallFlags_NewTarget) == 0))
  314. {
  315. // The final eval arg is a frame display that needs to be marshaled specially.
  316. args.Values[count-1] = CrossSite::MarshalFrameDisplay(targetScriptContext, (FrameDisplay*)args.Values[count-1]);
  317. count--;
  318. }
  319. for (; i < count; i++)
  320. {
  321. args.Values[i] = CrossSite::MarshalVar(targetScriptContext, args.Values[i]);
  322. }
  323. #if ENABLE_NATIVE_CODEGEN
  324. CheckCodeGenFunction checkCodeGenFunction = GetCheckCodeGenFunction(entryPoint);
  325. if (checkCodeGenFunction != nullptr)
  326. {
  327. ScriptFunction* callFunc = ScriptFunction::FromVar(function);
  328. entryPoint = checkCodeGenFunction(callFunc);
  329. Assert(CrossSite::IsThunk(function->GetEntryPoint()));
  330. }
  331. #endif
  332. // We need to setup the caller chain when we go across script site boundary. Property access
  333. // is OK, and we need to let host know who the caller is when a call is from another script site.
  334. // CrossSiteObject is the natural place but it is in the target site. We build up the site
  335. // chain through PushDispatchExCaller/PopDispatchExCaller, and we call SetCaller in the target site
  336. // to indicate who the caller is. We first need to get the site from the previously pushed site
  337. // and set that as the caller for current call, and push a new DispatchExCaller for future calls
  338. // off this site. GetDispatchExCaller and ReleaseDispatchExCaller is used to get the current caller.
  339. // currentDispatchExCaller is cached to avoid multiple allocations.
  340. IUnknown* sourceCaller = nullptr, *previousSourceCaller = nullptr;
  341. HRESULT hr = NOERROR;
  342. Var result = nullptr;
  343. BOOL wasDispatchExCallerPushed = FALSE, wasCallerSet = FALSE;
  344. TryFinally([&]()
  345. {
  346. hr = callerHostScriptContext->GetDispatchExCaller((void**)&sourceCaller);
  347. if (SUCCEEDED(hr))
  348. {
  349. hr = calleeHostScriptContext->SetCaller((IUnknown*)sourceCaller, (IUnknown**)&previousSourceCaller);
  350. }
  351. if (SUCCEEDED(hr))
  352. {
  353. wasCallerSet = TRUE;
  354. hr = calleeHostScriptContext->PushHostScriptContext();
  355. }
  356. if (FAILED(hr))
  357. {
  358. // CONSIDER: Should this be callerScriptContext if we failed?
  359. JavascriptError::MapAndThrowError(targetScriptContext, hr);
  360. }
  361. wasDispatchExCallerPushed = TRUE;
  362. result = JavascriptFunction::CallFunction<true>(function, entryPoint, args);
  363. ScriptContext* callerScriptContext = callerHostScriptContext->GetScriptContext();
  364. result = CrossSite::MarshalVar(callerScriptContext, result);
  365. },
  366. [&](bool hasException)
  367. {
  368. if (sourceCaller != nullptr)
  369. {
  370. callerHostScriptContext->ReleaseDispatchExCaller(sourceCaller);
  371. }
  372. IUnknown* originalCaller = nullptr;
  373. if (wasDispatchExCallerPushed)
  374. {
  375. calleeHostScriptContext->PopHostScriptContext();
  376. }
  377. if (wasCallerSet)
  378. {
  379. calleeHostScriptContext->SetCaller(previousSourceCaller, &originalCaller);
  380. if (previousSourceCaller)
  381. {
  382. previousSourceCaller->Release();
  383. }
  384. if (originalCaller)
  385. {
  386. originalCaller->Release();
  387. }
  388. }
  389. });
  390. Assert(result != nullptr);
  391. return result;
  392. }
  393. // For prototype chain to install cross-site thunk.
  394. // When we change prototype using __proto__, those prototypes might not have cross-site thunks
  395. // installed even though the CEO is accessed from a different context. During ChangePrototype time
  396. // we don't really know where the requestContext is.
  397. // Force installing cross-site thunk for all prototype changes. It's a relatively less frequently used
  398. // scenario.
  399. void CrossSite::ForceCrossSiteThunkOnPrototypeChain(RecyclableObject* object)
  400. {
  401. if (TaggedNumber::Is(object))
  402. {
  403. return;
  404. }
  405. while (DynamicType::Is(object->GetTypeId()) && !JavascriptProxy::Is(object))
  406. {
  407. DynamicObject* dynamicObject = DynamicObject::FromVar(object);
  408. if (!dynamicObject->IsCrossSiteObject() && !dynamicObject->IsExternal())
  409. {
  410. // force to install cross-site thunk on prototype objects.
  411. dynamicObject->MarshalToScriptContext(nullptr);
  412. }
  413. object = object->GetPrototype();
  414. }
  415. return;
  416. }
  417. };