InspectorRuntimeAgent.cpp [plain text]
#include "config.h"
#include "InspectorRuntimeAgent.h"
#include "Completion.h"
#include "HeapIterationScope.h"
#include "InjectedScript.h"
#include "InjectedScriptManager.h"
#include "InspectorValues.h"
#include "JSLock.h"
#include "ParserError.h"
#include "ScriptDebugServer.h"
#include "SourceCode.h"
#include "TypeProfiler.h"
#include "TypeProfilerLog.h"
#include "VMEntryScope.h"
#include <wtf/CurrentTime.h>
using namespace JSC;
namespace Inspector {
static bool asBool(const bool* const b)
{
return b ? *b : false;
}
InspectorRuntimeAgent::InspectorRuntimeAgent(InjectedScriptManager* injectedScriptManager)
: InspectorAgentBase(ASCIILiteral("Runtime"))
, m_injectedScriptManager(injectedScriptManager)
, m_scriptDebugServer(nullptr)
, m_enabled(false)
, m_isTypeProfilingEnabled(false)
{
}
InspectorRuntimeAgent::~InspectorRuntimeAgent()
{
}
static ScriptDebugServer::PauseOnExceptionsState setPauseOnExceptionsState(ScriptDebugServer* scriptDebugServer, ScriptDebugServer::PauseOnExceptionsState newState)
{
ASSERT(scriptDebugServer);
ScriptDebugServer::PauseOnExceptionsState presentState = scriptDebugServer->pauseOnExceptionsState();
if (presentState != newState)
scriptDebugServer->setPauseOnExceptionsState(newState);
return presentState;
}
static Ref<Inspector::Protocol::Runtime::ErrorRange> buildErrorRangeObject(const JSTokenLocation& tokenLocation)
{
return Inspector::Protocol::Runtime::ErrorRange::create()
.setStartOffset(tokenLocation.startOffset)
.setEndOffset(tokenLocation.endOffset)
.release();
}
void InspectorRuntimeAgent::parse(ErrorString&, const String& expression, Inspector::Protocol::Runtime::SyntaxErrorType* result, Inspector::Protocol::OptOutput<String>* message, RefPtr<Inspector::Protocol::Runtime::ErrorRange>& range)
{
VM& vm = globalVM();
JSLockHolder lock(vm);
ParserError error;
checkSyntax(vm, JSC::makeSource(expression), error);
switch (error.syntaxErrorType()) {
case ParserError::SyntaxErrorNone:
*result = Inspector::Protocol::Runtime::SyntaxErrorType::None;
break;
case ParserError::SyntaxErrorIrrecoverable:
*result = Inspector::Protocol::Runtime::SyntaxErrorType::Irrecoverable;
break;
case ParserError::SyntaxErrorUnterminatedLiteral:
*result = Inspector::Protocol::Runtime::SyntaxErrorType::UnterminatedLiteral;
break;
case ParserError::SyntaxErrorRecoverable:
*result = Inspector::Protocol::Runtime::SyntaxErrorType::Recoverable;
break;
}
if (error.syntaxErrorType() != ParserError::SyntaxErrorNone) {
*message = error.message();
range = buildErrorRangeObject(error.token().m_location);
}
}
void InspectorRuntimeAgent::evaluate(ErrorString& errorString, const String& expression, const String* const objectGroup, const bool* const includeCommandLineAPI, const bool* const doNotPauseOnExceptionsAndMuteConsole, const int* executionContextId, const bool* const returnByValue, const bool* generatePreview, const bool* saveResult, RefPtr<Inspector::Protocol::Runtime::RemoteObject>& result, Inspector::Protocol::OptOutput<bool>* wasThrown, Inspector::Protocol::OptOutput<int>* savedResultIndex)
{
InjectedScript injectedScript = injectedScriptForEval(errorString, executionContextId);
if (injectedScript.hasNoValue())
return;
ScriptDebugServer::PauseOnExceptionsState previousPauseOnExceptionsState = ScriptDebugServer::DontPauseOnExceptions;
if (asBool(doNotPauseOnExceptionsAndMuteConsole))
previousPauseOnExceptionsState = setPauseOnExceptionsState(m_scriptDebugServer, ScriptDebugServer::DontPauseOnExceptions);
if (asBool(doNotPauseOnExceptionsAndMuteConsole))
muteConsole();
injectedScript.evaluate(errorString, expression, objectGroup ? *objectGroup : String(), asBool(includeCommandLineAPI), asBool(returnByValue), asBool(generatePreview), asBool(saveResult), &result, wasThrown, savedResultIndex);
if (asBool(doNotPauseOnExceptionsAndMuteConsole)) {
unmuteConsole();
setPauseOnExceptionsState(m_scriptDebugServer, previousPauseOnExceptionsState);
}
}
void InspectorRuntimeAgent::callFunctionOn(ErrorString& errorString, const String& objectId, const String& expression, const InspectorArray* optionalArguments, const bool* const doNotPauseOnExceptionsAndMuteConsole, const bool* const returnByValue, const bool* generatePreview, RefPtr<Inspector::Protocol::Runtime::RemoteObject>& result, Inspector::Protocol::OptOutput<bool>* wasThrown)
{
InjectedScript injectedScript = m_injectedScriptManager->injectedScriptForObjectId(objectId);
if (injectedScript.hasNoValue()) {
errorString = ASCIILiteral("Inspected frame has gone");
return;
}
String arguments;
if (optionalArguments)
arguments = optionalArguments->toJSONString();
ScriptDebugServer::PauseOnExceptionsState previousPauseOnExceptionsState = ScriptDebugServer::DontPauseOnExceptions;
if (asBool(doNotPauseOnExceptionsAndMuteConsole))
previousPauseOnExceptionsState = setPauseOnExceptionsState(m_scriptDebugServer, ScriptDebugServer::DontPauseOnExceptions);
if (asBool(doNotPauseOnExceptionsAndMuteConsole))
muteConsole();
injectedScript.callFunctionOn(errorString, objectId, expression, arguments, asBool(returnByValue), asBool(generatePreview), &result, wasThrown);
if (asBool(doNotPauseOnExceptionsAndMuteConsole)) {
unmuteConsole();
setPauseOnExceptionsState(m_scriptDebugServer, previousPauseOnExceptionsState);
}
}
void InspectorRuntimeAgent::getProperties(ErrorString& errorString, const String& objectId, const bool* const ownProperties, const bool* const generatePreview, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::PropertyDescriptor>>& result, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::InternalPropertyDescriptor>>& internalProperties)
{
InjectedScript injectedScript = m_injectedScriptManager->injectedScriptForObjectId(objectId);
if (injectedScript.hasNoValue()) {
errorString = ASCIILiteral("Inspected frame has gone");
return;
}
ScriptDebugServer::PauseOnExceptionsState previousPauseOnExceptionsState = setPauseOnExceptionsState(m_scriptDebugServer, ScriptDebugServer::DontPauseOnExceptions);
muteConsole();
injectedScript.getProperties(errorString, objectId, asBool(ownProperties), asBool(generatePreview), &result);
injectedScript.getInternalProperties(errorString, objectId, asBool(generatePreview), &internalProperties);
unmuteConsole();
setPauseOnExceptionsState(m_scriptDebugServer, previousPauseOnExceptionsState);
}
void InspectorRuntimeAgent::getDisplayableProperties(ErrorString& errorString, const String& objectId, const bool* const generatePreview, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::PropertyDescriptor>>& result, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::InternalPropertyDescriptor>>& internalProperties)
{
InjectedScript injectedScript = m_injectedScriptManager->injectedScriptForObjectId(objectId);
if (injectedScript.hasNoValue()) {
errorString = ASCIILiteral("Inspected frame has gone");
return;
}
ScriptDebugServer::PauseOnExceptionsState previousPauseOnExceptionsState = setPauseOnExceptionsState(m_scriptDebugServer, ScriptDebugServer::DontPauseOnExceptions);
muteConsole();
injectedScript.getDisplayableProperties(errorString, objectId, asBool(generatePreview), &result);
injectedScript.getInternalProperties(errorString, objectId, asBool(generatePreview), &internalProperties);
unmuteConsole();
setPauseOnExceptionsState(m_scriptDebugServer, previousPauseOnExceptionsState);
}
void InspectorRuntimeAgent::getCollectionEntries(ErrorString& errorString, const String& objectId, const String* objectGroup, const int* startIndex, const int* numberToFetch, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::CollectionEntry>>& entries)
{
InjectedScript injectedScript = m_injectedScriptManager->injectedScriptForObjectId(objectId);
if (injectedScript.hasNoValue()) {
errorString = ASCIILiteral("Inspected frame has gone");
return;
}
int start = startIndex && *startIndex >= 0 ? *startIndex : 0;
int fetch = numberToFetch && *numberToFetch >= 0 ? *numberToFetch : 0;
injectedScript.getCollectionEntries(errorString, objectId, objectGroup ? *objectGroup : String(), start, fetch, &entries);
}
void InspectorRuntimeAgent::saveResult(ErrorString& errorString, const Inspector::InspectorObject& callArgument, const int* executionContextId, Inspector::Protocol::OptOutput<int>* savedResultIndex)
{
InjectedScript injectedScript;
String objectId;
if (callArgument.getString(ASCIILiteral("objectId"), objectId)) {
injectedScript = m_injectedScriptManager->injectedScriptForObjectId(objectId);
if (injectedScript.hasNoValue()) {
errorString = ASCIILiteral("Inspected frame has gone");
return;
}
} else {
injectedScript = injectedScriptForEval(errorString, executionContextId);
if (injectedScript.hasNoValue())
return;
}
injectedScript.saveResult(errorString, callArgument.toJSONString(), savedResultIndex);
}
void InspectorRuntimeAgent::releaseObject(ErrorString&, const String& objectId)
{
InjectedScript injectedScript = m_injectedScriptManager->injectedScriptForObjectId(objectId);
if (!injectedScript.hasNoValue())
injectedScript.releaseObject(objectId);
}
void InspectorRuntimeAgent::releaseObjectGroup(ErrorString&, const String& objectGroup)
{
m_injectedScriptManager->releaseObjectGroup(objectGroup);
}
void InspectorRuntimeAgent::run(ErrorString&)
{
}
void InspectorRuntimeAgent::getRuntimeTypesForVariablesAtOffsets(ErrorString& errorString, const Inspector::InspectorArray& locations, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::TypeDescription>>& typeDescriptions)
{
static const bool verbose = false;
VM& vm = globalVM();
typeDescriptions = Inspector::Protocol::Array<Inspector::Protocol::Runtime::TypeDescription>::create();
if (!vm.typeProfiler()) {
errorString = ASCIILiteral("The VM does not currently have Type Information.");
return;
}
double start = currentTimeMS();
vm.typeProfilerLog()->processLogEntries(ASCIILiteral("User Query"));
for (size_t i = 0; i < locations.length(); i++) {
RefPtr<Inspector::InspectorValue> value = locations.get(i);
RefPtr<InspectorObject> location;
if (!value->asObject(location)) {
errorString = ASCIILiteral("Array of TypeLocation objects has an object that does not have type of TypeLocation.");
return;
}
int descriptor;
String sourceIDAsString;
int divot;
location->getInteger(ASCIILiteral("typeInformationDescriptor"), descriptor);
location->getString(ASCIILiteral("sourceID"), sourceIDAsString);
location->getInteger(ASCIILiteral("divot"), divot);
bool okay;
TypeLocation* typeLocation = vm.typeProfiler()->findLocation(divot, sourceIDAsString.toIntPtrStrict(&okay), static_cast<TypeProfilerSearchDescriptor>(descriptor), vm);
ASSERT(okay);
RefPtr<TypeSet> typeSet;
if (typeLocation) {
if (typeLocation->m_globalTypeSet && typeLocation->m_globalVariableID != TypeProfilerNoGlobalIDExists)
typeSet = typeLocation->m_globalTypeSet;
else
typeSet = typeLocation->m_instructionTypeSet;
}
bool isValid = typeLocation && typeSet && !typeSet->isEmpty();
auto description = Inspector::Protocol::Runtime::TypeDescription::create()
.setIsValid(isValid)
.release();
if (isValid) {
description->setLeastCommonAncestor(typeSet->leastCommonAncestor());
description->setStructures(typeSet->allStructureRepresentations());
description->setTypeSet(typeSet->inspectorTypeSet());
description->setIsTruncated(typeSet->isOverflown());
}
typeDescriptions->addItem(WTF::move(description));
}
double end = currentTimeMS();
if (verbose)
dataLogF("Inspector::getRuntimeTypesForVariablesAtOffsets took %lfms\n", end - start);
}
class TypeRecompiler : public MarkedBlock::VoidFunctor {
public:
inline void visit(JSCell* cell)
{
if (!cell->inherits(FunctionExecutable::info()))
return;
FunctionExecutable* executable = jsCast<FunctionExecutable*>(cell);
executable->clearCode();
executable->clearUnlinkedCodeForRecompilation();
}
inline IterationStatus operator()(JSCell* cell)
{
visit(cell);
return IterationStatus::Continue;
}
};
static void recompileAllJSFunctionsForTypeProfiling(VM& vm, bool shouldEnableTypeProfiling)
{
bool shouldRecompileFromTypeProfiler = (shouldEnableTypeProfiling ? vm.enableTypeProfiler() : vm.disableTypeProfiler());
bool shouldRecompileFromControlFlowProfiler = (shouldEnableTypeProfiling ? vm.enableControlFlowProfiler() : vm.disableControlFlowProfiler());
bool needsToRecompile = shouldRecompileFromTypeProfiler || shouldRecompileFromControlFlowProfiler;
if (needsToRecompile) {
vm.prepareToDiscardCode();
TypeRecompiler recompiler;
HeapIterationScope iterationScope(vm.heap);
vm.heap.objectSpace().forEachLiveCell(iterationScope, recompiler);
}
}
void InspectorRuntimeAgent::willDestroyFrontendAndBackend(DisconnectReason reason)
{
if (reason != DisconnectReason::InspectedTargetDestroyed && m_isTypeProfilingEnabled)
setTypeProfilerEnabledState(false);
}
void InspectorRuntimeAgent::enableTypeProfiler(ErrorString&)
{
setTypeProfilerEnabledState(true);
}
void InspectorRuntimeAgent::disableTypeProfiler(ErrorString&)
{
setTypeProfilerEnabledState(false);
}
void InspectorRuntimeAgent::setTypeProfilerEnabledState(bool shouldEnableTypeProfiling)
{
if (m_isTypeProfilingEnabled == shouldEnableTypeProfiling)
return;
m_isTypeProfilingEnabled = shouldEnableTypeProfiling;
VM& vm = globalVM();
if (vm.entryScope) {
vm.entryScope->setEntryScopeDidPopListener(this,
[=] (VM& vm, JSGlobalObject*) {
recompileAllJSFunctionsForTypeProfiling(vm, shouldEnableTypeProfiling);
}
);
} else
recompileAllJSFunctionsForTypeProfiling(vm, shouldEnableTypeProfiling);
}
void InspectorRuntimeAgent::getBasicBlocks(ErrorString& errorString, const String& sourceIDAsString, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::BasicBlock>>& basicBlocks)
{
VM& vm = globalVM();
if (!vm.controlFlowProfiler()) {
errorString = ASCIILiteral("The VM does not currently have a Control Flow Profiler.");
return;
}
bool okay;
intptr_t sourceID = sourceIDAsString.toIntPtrStrict(&okay);
ASSERT(okay);
const Vector<BasicBlockRange>& basicBlockRanges = vm.controlFlowProfiler()->getBasicBlocksForSourceID(sourceID, vm);
basicBlocks = Inspector::Protocol::Array<Inspector::Protocol::Runtime::BasicBlock>::create();
for (const BasicBlockRange& block : basicBlockRanges) {
Ref<Inspector::Protocol::Runtime::BasicBlock> location = Inspector::Protocol::Runtime::BasicBlock::create()
.setStartOffset(block.m_startOffset)
.setEndOffset(block.m_endOffset)
.setHasExecuted(block.m_hasExecuted)
.release();
basicBlocks->addItem(WTF::move(location));
}
}
}