// // Load persisted scope info. // void ScopeInfo::GetScopeInfo(Parser *parser, ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo, Scope* scope) { ScriptContext* scriptContext; ArenaAllocator* alloc; // Load scope attributes and push onto scope stack. scope->SetIsDynamic(this->isDynamic); if (this->isObject) { scope->SetIsObject(); } scope->SetMustInstantiate(this->mustInstantiate); if (!this->GetCanMergeWithBodyScope()) { scope->SetCannotMergeWithBodyScope(); } scope->SetHasOwnLocalInClosure(this->hasLocalInClosure); if (parser) { scriptContext = parser->GetScriptContext(); alloc = parser->GetAllocator(); } else { TRACE_BYTECODE(_u("\nRestore ScopeInfo: %s #symbols: %d %s\n"), funcInfo->name, symbolCount, isObject ? _u("isObject") : _u("")); Assert(!this->isCached || scope == funcInfo->GetBodyScope()); funcInfo->SetHasCachedScope(this->isCached); byteCodeGenerator->PushScope(scope); // The scope is already populated, so we're done. return; } // Load scope symbols // On first access to the scopeinfo, replace the ID's with PropertyRecord*'s to save the dictionary lookup // on later accesses. Replace them all before allocating Symbol's to prevent inconsistency on OOM. if (!this->areNamesCached && !PHASE_OFF1(Js::CacheScopeInfoNamesPhase)) { for (int i = 0; i < symbolCount; i++) { PropertyId propertyId = GetSymbolId(i); if (propertyId != 0) // There may be empty slots, e.g. "arguments" may have no slot { PropertyRecord const* name = scriptContext->GetPropertyName(propertyId); this->SetPropertyName(i, name); } } this->areNamesCached = true; } for (int i = 0; i < symbolCount; i++) { PropertyRecord const* name = nullptr; if (this->areNamesCached) { name = this->GetPropertyName(i); } else { PropertyId propertyId = GetSymbolId(i); if (propertyId != 0) // There may be empty slots, e.g. "arguments" may have no slot { name = scriptContext->GetPropertyName(propertyId); } } if (name != nullptr) { SymbolType symbolType = GetSymbolType(i); SymbolName symName(name->GetBuffer(), name->GetLength()); Symbol *sym = Anew(alloc, Symbol, symName, nullptr, symbolType); sym->SetScopeSlot(static_cast<PropertyId>(i)); sym->SetIsBlockVar(GetIsBlockVariable(i)); sym->SetIsFuncExpr(GetIsFuncExpr(i)); sym->SetIsModuleExportStorage(GetIsModuleExportStorage(i)); sym->SetIsModuleImport(GetIsModuleImport(i)); if (GetHasFuncAssignment(i)) { sym->RestoreHasFuncAssignment(); } scope->AddNewSymbol(sym); sym->SetHasNonLocalReference(); Assert(parser); parser->RestorePidRefForSym(sym); TRACE_BYTECODE(_u("%12s %d\n"), sym->GetName().GetBuffer(), sym->GetScopeSlot()); } } this->scope = scope; DebugOnly(scope->isRestored = true); }
void JavascriptExternalFunction::PrepareExternalCall(Js::Arguments * args) { ScriptContext * scriptContext = this->type->GetScriptContext(); Assert(!scriptContext->GetThreadContext()->IsDisableImplicitException()); scriptContext->VerifyAlive(); Assert(scriptContext->GetThreadContext()->IsScriptActive()); if (args->Info.Count == 0) { JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined); } Var &thisVar = args->Values[0]; Js::TypeId typeId = Js::JavascriptOperators::GetTypeId(thisVar); this->callCount++; if (IS_JS_ETW(EventEnabledJSCRIPT_HOSTING_EXTERNAL_FUNCTION_CALL_START())) { JavascriptFunction* caller = nullptr; // Lot the caller function if the call count of the external function pass certain threshold (randomly pick 256) // we don't want to call stackwalk too often. The threshold can be adjusted as needed. if (callCount >= ETW_MIN_COUNT_FOR_CALLER && ((callCount % ETW_MIN_COUNT_FOR_CALLER) == 0)) { Js::JavascriptStackWalker stackWalker(scriptContext); bool foundScriptCaller = false; while(stackWalker.GetCaller(&caller)) { if(caller != nullptr && Js::ScriptFunction::Is(caller)) { foundScriptCaller = true; break; } } if(foundScriptCaller) { Var sourceString = caller->EnsureSourceString(); Assert(JavascriptString::Is(sourceString)); const char16* callerString = Js::JavascriptString::FromVar(sourceString)->GetSz(); char16* outString = (char16*)callerString; int length = 0; if (wcschr(callerString, _u('\n')) != NULL || wcschr(callerString, _u('\n')) != NULL) { length = Js::JavascriptString::FromVar(sourceString)->GetLength(); outString = HeapNewArray(char16, length+1); int j = 0; for (int i = 0; i < length; i++) { if (callerString[i] != _u('\n') && callerString[i] != _u('\r')) { outString[j++] = callerString[i]; } } outString[j] = _u('\0'); } JS_ETW(EventWriteJSCRIPT_HOSTING_CALLER_TO_EXTERNAL(scriptContext, this, typeId, outString, callCount)); if (outString != callerString) { HeapDeleteArray(length+1, outString); } #if DBG_DUMP if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::HostPhase)) { Output::Print(_u("Large number of Call to trampoline: methodAddr= %p, Object typeid= %d, caller method= %s, callcount= %d\n"), this, typeId, callerString, callCount); } #endif } } JS_ETW(EventWriteJSCRIPT_HOSTING_EXTERNAL_FUNCTION_CALL_START(scriptContext, this, typeId)); #if DBG_DUMP if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::HostPhase)) { Output::Print(_u("Call to trampoline: methodAddr= %p, Object typeid= %d\n"), this, typeId); } #endif } Js::RecyclableObject* directHostObject = nullptr; switch(typeId) { case TypeIds_Integer: #if FLOATVAR case TypeIds_Number: #endif // FLOATVAR Assert(!Js::RecyclableObject::Is(thisVar)); break; default: { Assert(Js::RecyclableObject::Is(thisVar)); ScriptContext* scriptContextThisVar = Js::RecyclableObject::FromVar(thisVar)->GetScriptContext(); // We need to verify "this" pointer is active as well. The problem is that DOM prototype functions are // the same across multiple frames, and caller can do function.call(closedthis) Assert(!scriptContext->GetThreadContext()->IsDisableImplicitException()); scriptContextThisVar->VerifyAlive(); // translate direct host for fastDOM. switch(typeId) { case Js::TypeIds_GlobalObject: { Js::GlobalObject* srcGlobalObject = static_cast<Js::GlobalObject*>(thisVar); directHostObject = srcGlobalObject->GetDirectHostObject(); // For jsrt, direct host object can be null. If thats the case don't change it. if (directHostObject != nullptr) { thisVar = directHostObject; } } break; case Js::TypeIds_Undefined: case Js::TypeIds_Null: { // Call to DOM function with this as "undefined" or "null" // This should be converted to Global object Js::GlobalObject* srcGlobalObject = scriptContextThisVar->GetGlobalObject() ; directHostObject = srcGlobalObject->GetDirectHostObject(); // For jsrt, direct host object can be null. If thats the case don't change it. if (directHostObject != nullptr) { thisVar = directHostObject; } } break; } } break; } }
Var JavascriptGenerator::CallGenerator(ResumeYieldData* yieldData, const wchar_t* apiNameForErrorMessage) { ScriptContext* scriptContext = this->GetScriptContext(); JavascriptLibrary* library = scriptContext->GetLibrary(); Var result = nullptr; if (this->IsExecuting()) { JavascriptError::ThrowTypeError(scriptContext, JSERR_GeneratorAlreadyExecuting, apiNameForErrorMessage); } { // RAII helper to set the state of the generator to completed if an exception is thrown // or if the save state InterpreterStackFrame is never created implying the generator // is JITed and returned without ever yielding. class GeneratorStateHelper { JavascriptGenerator* g; bool didThrow; public: GeneratorStateHelper(JavascriptGenerator* g) : g(g), didThrow(true) { g->SetState(GeneratorState::Executing); } ~GeneratorStateHelper() { g->SetState(didThrow || g->frame == nullptr ? GeneratorState::Completed : GeneratorState::Suspended); } void DidNotThrow() { didThrow = false; } } helper(this); Var thunkArgs[] = { this, yieldData }; Arguments arguments(_countof(thunkArgs), thunkArgs); try { result = JavascriptFunction::CallFunction<1>(this->scriptFunction, this->scriptFunction->GetEntryPoint(), arguments); helper.DidNotThrow(); } catch (Js::JavascriptExceptionObject* exceptionObj) { if (!exceptionObj->IsGeneratorReturnException()) { throw exceptionObj; } result = exceptionObj->GetThrownObject(nullptr); } } if (this->IsCompleted()) { result = library->CreateIteratorResultObject(result, library->GetTrue()); } else { int nextOffset = this->frame->GetReader()->GetCurrentOffset(); int endOffset = this->frame->GetFunctionBody()->GetByteCode()->GetLength(); if (nextOffset != endOffset - 1) { result = library->CreateIteratorResultObjectValueFalse(result); } else { result = library->CreateIteratorResultObject(result, library->GetTrue()); this->SetState(GeneratorState::Completed); } } return result; }
Var JavascriptWeakMap::NewInstance(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); ScriptContext* scriptContext = function->GetScriptContext(); JavascriptLibrary* library = scriptContext->GetLibrary(); Var newTarget = callInfo.Flags & CallFlags_NewTarget ? args.Values[args.Info.Count] : args[0]; bool isCtorSuperCall = (callInfo.Flags & CallFlags_New) && newTarget != nullptr && !JavascriptOperators::IsUndefined(newTarget); Assert(isCtorSuperCall || !(callInfo.Flags & CallFlags_New) || args[0] == nullptr); CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(WeakMapCount); JavascriptWeakMap* weakMapObject = nullptr; if (callInfo.Flags & CallFlags_New) { weakMapObject = library->CreateWeakMap(); } else { JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("WeakMap"), _u("WeakMap")); } Assert(weakMapObject != nullptr); Var iterable = (args.Info.Count > 1) ? args[1] : library->GetUndefined(); RecyclableObject* iter = nullptr; RecyclableObject* adder = nullptr; if (JavascriptConversion::CheckObjectCoercible(iterable, scriptContext)) { iter = JavascriptOperators::GetIterator(iterable, scriptContext); Var adderVar = JavascriptOperators::GetProperty(weakMapObject, PropertyIds::set, scriptContext); if (!JavascriptConversion::IsCallable(adderVar)) { JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedFunction); } adder = RecyclableObject::FromVar(adderVar); } if (iter != nullptr) { Var undefined = library->GetUndefined(); JavascriptOperators::DoIteratorStepAndValue(iter, scriptContext, [&](Var nextItem) { if (!JavascriptOperators::IsObject(nextItem)) { JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedObject); } RecyclableObject* obj = RecyclableObject::FromVar(nextItem); Var key, value; if (!JavascriptOperators::GetItem(obj, 0u, &key, scriptContext)) { key = undefined; } if (!JavascriptOperators::GetItem(obj, 1u, &value, scriptContext)) { value = undefined; } CALL_FUNCTION(adder, CallInfo(CallFlags_Value, 3), weakMapObject, key, value); }); } return isCtorSuperCall ? JavascriptOperators::OrdinaryCreateFromConstructor(RecyclableObject::FromVar(newTarget), weakMapObject, nullptr, scriptContext) : weakMapObject; }
void PerfTrace::WritePerfMap() { #if ENABLE_NATIVE_CODEGEN // Lock threadContext list during etw rundown AutoCriticalSection autoThreadContextCs(ThreadContext::GetCriticalSection()); ThreadContext * threadContext = ThreadContext::GetThreadContextList(); FILE * perfMapFile; { const size_t PERFMAP_FILENAME_MAX_LENGTH = 30; char perfMapFilename[PERFMAP_FILENAME_MAX_LENGTH]; pid_t processId = getpid(); snprintf(perfMapFilename, PERFMAP_FILENAME_MAX_LENGTH, "/tmp/perf-%d.map", processId); perfMapFile = fopen(perfMapFilename, "w"); if (perfMapFile == NULL) { return; } } while(threadContext != nullptr) { // Take etw rundown lock on this thread context AutoCriticalSection autoEtwRundownCs(threadContext->GetFunctionBodyLock()); ScriptContext* scriptContext = threadContext->GetScriptContextList(); while(scriptContext != NULL) { if(scriptContext->IsClosed()) { scriptContext = scriptContext->next; continue; } scriptContext->MapFunction([=] (FunctionBody* body) { #if DYNAMIC_INTERPRETER_THUNK if(body->HasInterpreterThunkGenerated()) { const char16* functionName = body->GetExternalDisplayName(); fwprintf(perfMapFile, _u("%llX %llX %s(Interpreted)\n"), body->GetDynamicInterpreterEntryPoint(), body->GetDynamicInterpreterThunkSize(), functionName); } #endif #if ENABLE_NATIVE_CODEGEN body->MapEntryPoints([&](int index, FunctionEntryPointInfo * entryPoint) { if(entryPoint->IsCodeGenDone()) { const ExecutionMode jitMode = entryPoint->GetJitMode(); if (jitMode == ExecutionMode::SimpleJit) { fwprintf(perfMapFile, _u("%llX %llX %s(SimpleJIT)\n"), entryPoint->GetNativeAddress(), entryPoint->GetCodeSize(), body->GetExternalDisplayName()); } else { fwprintf(perfMapFile, _u("%llX %llX %s(FullJIT)\n"), entryPoint->GetNativeAddress(), entryPoint->GetCodeSize(), body->GetExternalDisplayName()); } } }); body->MapLoopHeadersWithLock([&](uint loopNumber, LoopHeader* header) { header->MapEntryPoints([&](int index, LoopEntryPointInfo * entryPoint) { if(entryPoint->IsCodeGenDone()) { const uint16 loopNumber = ((uint16)body->GetLoopNumberWithLock(header)); fwprintf(perfMapFile, _u("%llX %llX %s(Loop%u)\n"), entryPoint->GetNativeAddress(), entryPoint->GetCodeSize(), body->GetExternalDisplayName(), loopNumber+1); } }); }); #endif }); scriptContext = scriptContext->next; } threadContext = threadContext->Next(); } fflush(perfMapFile); fclose(perfMapFile); #endif PerfTrace::mapsRequested = 0; }
Var JavascriptMap::NewInstance(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); ScriptContext* scriptContext = function->GetScriptContext(); JavascriptLibrary* library = scriptContext->GetLibrary(); AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, _u("Map")); Var newTarget = callInfo.Flags & CallFlags_NewTarget ? args.Values[args.Info.Count] : args[0]; bool isCtorSuperCall = (callInfo.Flags & CallFlags_New) && newTarget != nullptr && !JavascriptOperators::IsUndefined(newTarget); Assert(isCtorSuperCall || !(callInfo.Flags & CallFlags_New) || args[0] == nullptr); CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(MapCount); JavascriptMap* mapObject = nullptr; if (callInfo.Flags & CallFlags_New) { mapObject = library->CreateMap(); } else { JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map"), _u("Map")); } Assert(mapObject != nullptr); Var iterable = (args.Info.Count > 1) ? args[1] : library->GetUndefined(); RecyclableObject* iter = nullptr; RecyclableObject* adder = nullptr; if (JavascriptConversion::CheckObjectCoercible(iterable, scriptContext)) { iter = JavascriptOperators::GetIterator(iterable, scriptContext); Var adderVar = JavascriptOperators::GetProperty(mapObject, PropertyIds::set, scriptContext); if (!JavascriptConversion::IsCallable(adderVar)) { JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedFunction); } adder = RecyclableObject::FromVar(adderVar); } if (mapObject->map != nullptr) { JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_ObjectIsAlreadyInitialized, _u("Map"), _u("Map")); } mapObject->map = RecyclerNew(scriptContext->GetRecycler(), MapDataMap, scriptContext->GetRecycler()); if (iter != nullptr) { Var undefined = library->GetUndefined(); JavascriptOperators::DoIteratorStepAndValue(iter, scriptContext, [&](Var nextItem) { if (!JavascriptOperators::IsObject(nextItem)) { JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedObject); } RecyclableObject* obj = RecyclableObject::FromVar(nextItem); Var key, value; if (!JavascriptOperators::GetItem(obj, 0u, &key, scriptContext)) { key = undefined; } if (!JavascriptOperators::GetItem(obj, 1u, &value, scriptContext)) { value = undefined; } // CONSIDER: if adder is the default built-in, fast path it and skip the JS call? CALL_FUNCTION(adder, CallInfo(CallFlags_Value, 3), mapObject, key, value); }); } return isCtorSuperCall ? JavascriptOperators::OrdinaryCreateFromConstructor(RecyclableObject::FromVar(newTarget), mapObject, nullptr, scriptContext) : mapObject; }
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; }