BreakpointProbe* DebugDocument::SetBreakPoint(StatementLocation statement, BREAKPOINT_STATE bps) { ScriptContext* scriptContext = this->utf8SourceInfo->GetScriptContext(); if (scriptContext == nullptr || scriptContext->IsClosed()) { return nullptr; } switch (bps) { default: AssertMsg(FALSE, "Bad breakpoint state"); // Fall thru case BREAKPOINT_DISABLED: case BREAKPOINT_DELETED: { BreakpointProbeList* pBreakpointList = this->GetBreakpointList(); if (pBreakpointList) { ArenaAllocator arena(_u("TemporaryBreakpointList"), scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticPageAllocator(), Throw::OutOfMemory); BreakpointProbeList* pDeleteList = this->NewBreakpointList(&arena); pBreakpointList->Map([&statement, scriptContext, pDeleteList](int index, BreakpointProbe * breakpointProbe) { if (breakpointProbe->Matches(statement.function, statement.statement.begin)) { scriptContext->GetDebugContext()->GetProbeContainer()->RemoveProbe(breakpointProbe); pDeleteList->Add(breakpointProbe); } }); pDeleteList->Map([pBreakpointList](int index, BreakpointProbe * breakpointProbe) { pBreakpointList->Remove(breakpointProbe); }); pDeleteList->Clear(); } break; } case BREAKPOINT_ENABLED: { BreakpointProbe* pProbe = Anew(scriptContext->AllocatorForDiagnostics(), BreakpointProbe, this, statement, scriptContext->GetThreadContext()->GetDebugManager()->GetNextBreakpointId()); scriptContext->GetDebugContext()->GetProbeContainer()->AddProbe(pProbe); BreakpointProbeList* pBreakpointList = this->GetBreakpointList(); pBreakpointList->Add(pProbe); return pProbe; break; } } return nullptr; }
void __cdecl JavascriptExternalFunction::DeferredInitializer(DynamicObject* instance, DeferredTypeHandlerBase* typeHandler, DeferredInitializeMode mode) { JavascriptExternalFunction* object = static_cast<JavascriptExternalFunction*>(instance); HRESULT hr = E_FAIL; ScriptContext* scriptContext = object->GetScriptContext(); AnalysisAssert(scriptContext); // Don't call the implicit call if disable implicit call if (scriptContext->GetThreadContext()->IsDisableImplicitCall()) { scriptContext->GetThreadContext()->AddImplicitCallFlags(ImplicitCall_External); //we will return if we get call further into implicitcalls. return; } if (scriptContext->IsClosed() || scriptContext->IsInvalidatedForHostObjects()) { Js::JavascriptError::MapAndThrowError(scriptContext, E_ACCESSDENIED); } ThreadContext* threadContext = scriptContext->GetThreadContext(); typeHandler->Convert(instance, mode, object->typeSlots, object->hasAccessors); BEGIN_LEAVE_SCRIPT_INTERNAL(scriptContext) { ASYNC_HOST_OPERATION_START(threadContext); hr = object->initMethod(instance); ASYNC_HOST_OPERATION_END(threadContext); } END_LEAVE_SCRIPT_INTERNAL(scriptContext); if (FAILED(hr)) { Js::JavascriptError::MapAndThrowError(scriptContext, hr); } JavascriptString * functionName = nullptr; if (scriptContext->GetConfig()->IsES6FunctionNameEnabled() && object->GetFunctionName(&functionName)) { object->SetPropertyWithAttributes(PropertyIds::name, functionName, PropertyConfigurable, nullptr); } }
HRESULT DebugDocument::SetBreakPoint(int32 ibos, BREAKPOINT_STATE breakpointState) { ScriptContext* scriptContext = this->utf8SourceInfo->GetScriptContext(); if (scriptContext == nullptr || scriptContext->IsClosed()) { return E_UNEXPECTED; } StatementLocation statement; if (!this->GetStatementLocation(ibos, &statement)) { return E_FAIL; } this->SetBreakPoint(statement, breakpointState); return S_OK; }
BreakpointProbeList* DebugDocument::GetBreakpointList() { if (m_breakpointList != nullptr) { return m_breakpointList; } ScriptContext * scriptContext = this->utf8SourceInfo->GetScriptContext(); if (scriptContext == nullptr || scriptContext->IsClosed()) { return nullptr; } ArenaAllocator* diagnosticArena = scriptContext->AllocatorForDiagnostics(); AssertMem(diagnosticArena); m_breakpointList = this->NewBreakpointList(diagnosticArena); return m_breakpointList; }
JavascriptString* CharStringCache::GetStringForCharW(char16 c) { Assert(!JavascriptString::IsASCII7BitChar(c)); JavascriptString* str; ScriptContext * scriptContext = JavascriptLibrary::FromCharStringCache(this)->GetScriptContext(); if (!scriptContext->IsClosed()) { if (charStringCache == nullptr) { Recycler * recycler = scriptContext->GetRecycler(); charStringCache = RecyclerNew(recycler, CharStringCacheMap, recycler, 17); } if (!charStringCache->TryGetValue(c, &str)) { str = SingleCharString::New(c, scriptContext); charStringCache->Add(c, str); } } else { str = SingleCharString::New(c, scriptContext); } return str; }
// // Enumerate through all the script contexts in the process and log events // for each function loaded. Depending on the argument, start or end events are logged. // In particular, a rundown is needed for the 'Attach' scenario of profiling. // void EtwTrace::PerformRundown(bool start) { // Lock threadContext list during etw rundown AutoCriticalSection autoThreadContextCs(ThreadContext::GetCriticalSection()); ThreadContext * threadContext = ThreadContext::GetThreadContextList(); if(start) { JS_ETW(EventWriteDCStartInit()); } else { JS_ETW(EventWriteDCEndInit()); } while(threadContext != nullptr) { // Take etw rundown lock on this thread context AutoCriticalSection autoEtwRundownCs(threadContext->GetEtwRundownCriticalSection()); ScriptContext* scriptContext = threadContext->GetScriptContextList(); while(scriptContext != NULL) { if(scriptContext->IsClosed()) { scriptContext = scriptContext->next; continue; } if(start) { JS_ETW(EventWriteScriptContextDCStart(scriptContext)); if(scriptContext->GetSourceContextInfoMap() != nullptr) { scriptContext->GetSourceContextInfoMap()->Map( [=] (DWORD_PTR sourceContext, SourceContextInfo * sourceContextInfo) { if (sourceContext != Constants::NoHostSourceContext) { JS_ETW(LogSourceEvent(EventWriteSourceDCStart, sourceContext, scriptContext, /* sourceFlags*/ 0, sourceContextInfo->url)); } }); } } else { JS_ETW(EventWriteScriptContextDCEnd(scriptContext)); if(scriptContext->GetSourceContextInfoMap() != nullptr) { scriptContext->GetSourceContextInfoMap()->Map( [=] (DWORD_PTR sourceContext, SourceContextInfo * sourceContextInfo) { if (sourceContext != Constants::NoHostSourceContext) { JS_ETW(LogSourceEvent(EventWriteSourceDCEnd, sourceContext, scriptContext, /* sourceFlags*/ 0, sourceContextInfo->url)); } }); } } scriptContext->MapFunction([&start] (FunctionBody* body) { #if DYNAMIC_INTERPRETER_THUNK if(body->HasInterpreterThunkGenerated()) { if(start) { LogMethodInterpretedThunkEvent(EventWriteMethodDCStart, body); } else { LogMethodInterpretedThunkEvent(EventWriteMethodDCEnd, body); } } #endif #if ENABLE_NATIVE_CODEGEN body->MapEntryPoints([&](int index, FunctionEntryPointInfo * entryPoint) { if(entryPoint->IsCodeGenDone()) { if (start) { LogMethodNativeEvent(EventWriteMethodDCStart, body, entryPoint); } else { LogMethodNativeEvent(EventWriteMethodDCEnd, body, entryPoint); } } }); body->MapLoopHeadersWithLock([&](uint loopNumber, LoopHeader* header) { header->MapEntryPoints([&](int index, LoopEntryPointInfo * entryPoint) { if(entryPoint->IsCodeGenDone()) { if(start) { LogLoopBodyEventBG(EventWriteMethodDCStart, body, header, entryPoint, ((uint16)body->GetLoopNumberWithLock(header))); } else { LogLoopBodyEventBG(EventWriteMethodDCEnd, body, header, entryPoint, ((uint16)body->GetLoopNumberWithLock(header))); } } }); }); #endif }); scriptContext = scriptContext->next; } #ifdef NTBUILD if (EventEnabledJSCRIPT_HOSTING_CEO_START()) { threadContext->EtwLogPropertyIdList(); } #endif threadContext = threadContext->Next(); } if(start) { JS_ETW(EventWriteDCStartComplete()); } else { JS_ETW(EventWriteDCEndComplete()); } }
BOOL DebugDocument::GetStatementLocation(int32 ibos, StatementLocation* plocation) { if (ibos < 0) { return FALSE; } ScriptContext* scriptContext = this->utf8SourceInfo->GetScriptContext(); if (scriptContext == nullptr || scriptContext->IsClosed()) { return FALSE; } uint32 ubos = static_cast<uint32>(ibos); // Getting the appropriate statement on the asked position works on the heuristic which requires two // probable candidates. These candidates will be closest to the ibos where first.range.start < ibos and // second.range.start >= ibos. They will be fetched out by going into each FunctionBody. StatementLocation candidateMatch1 = {}; StatementLocation candidateMatch2 = {}; this->utf8SourceInfo->MapFunction([&](FunctionBody* pFuncBody) { uint32 functionStart = pFuncBody->StartInDocument(); uint32 functionEnd = functionStart + pFuncBody->LengthInBytes(); // For the first candidate, we should allow the current function to participate if its range // (instead of just start offset) is closer to the ubos compared to already found candidate1. if (candidateMatch1.function == nullptr || ((candidateMatch1.statement.begin <= static_cast<int>(functionStart) || candidateMatch1.statement.end <= static_cast<int>(functionEnd)) && ubos > functionStart) || candidateMatch2.function == nullptr || (candidateMatch2.statement.begin > static_cast<int>(functionStart) && ubos <= functionStart) || (functionStart <= ubos && ubos < functionEnd)) { // We need to find out two possible candidate from the current FunctionBody. pFuncBody->FindClosestStatements(ibos, &candidateMatch1, &candidateMatch2); } }); if (candidateMatch1.function == nullptr && candidateMatch2.function == nullptr) { return FALSE; // No Match found } if (candidateMatch1.function == nullptr || candidateMatch2.function == nullptr) { *plocation = (candidateMatch1.function == nullptr) ? candidateMatch2 : candidateMatch1; return TRUE; } // If one of the func is inner to another one, and ibos is in the inner one, disregard the outer one/let the inner one win. // See WinBlue 575634. Scenario is like this: var foo = function () {this;} -- and BP is set to 'this;' 'function'. if (candidateMatch1.function != candidateMatch2.function) { Assert(candidateMatch1.function && candidateMatch2.function); regex::Interval func1Range(candidateMatch1.function->StartInDocument()); func1Range.End(func1Range.Begin() + candidateMatch1.function->LengthInBytes()); regex::Interval func2Range(candidateMatch2.function->StartInDocument()); func2Range.End(func2Range.Begin() + candidateMatch2.function->LengthInBytes()); // If cursor (ibos) is just after the closing braces of the inner function then we can't // directly choose inner function and have to make line break check, so fallback // function foo(){function bar(){var y=1;}#var x=1;bar();}foo(); - ibos is # if (func1Range.Includes(func2Range) && func2Range.Includes(ibos) && func2Range.End() != ibos) { *plocation = candidateMatch2; return TRUE; } else if (func2Range.Includes(func1Range) && func1Range.Includes(ibos) && func1Range.End() != ibos) { *plocation = candidateMatch1; return TRUE; } } // At this point we have both candidate to consider. Assert(candidateMatch1.statement.begin < candidateMatch2.statement.begin); Assert(candidateMatch1.statement.begin < ibos); Assert(candidateMatch2.statement.begin >= ibos); // Default selection *plocation = candidateMatch1; // If the second candidate start at ibos or // if the first candidate has line break between ibos and the second candidate is on the same line as ibos // then consider the second one. BOOL fNextHasLineBreak = this->HasLineBreak(ibos, candidateMatch2.statement.begin); if ((candidateMatch2.statement.begin == ibos) || (this->HasLineBreak(candidateMatch1.statement.begin, ibos) && !fNextHasLineBreak)) { *plocation = candidateMatch2; } // If ibos is out of the range of first candidate, choose second candidate if ibos is on the same line as second candidate // or ibos is not on the same line of the end of the first candidate. else if (candidateMatch1.statement.end < ibos && (!fNextHasLineBreak || this->HasLineBreak(candidateMatch1.statement.end, ibos))) { *plocation = candidateMatch2; } return TRUE; }
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 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; }