blob: 9a710cc19cb47f57cdee047d4fbffbcfa6100825 [file] [log] [blame]
/*
* Copyright (C) 2013-2019 Apple Inc. All rights reserved.
* Copyright (C) 2011 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "InspectorRuntimeAgent.h"
#include "Completion.h"
#include "DFGWorklist.h"
#include "HeapIterationScope.h"
#include "InjectedScript.h"
#include "InjectedScriptHost.h"
#include "InjectedScriptManager.h"
#include "InspectorFrontendRouter.h"
#include "JSLock.h"
#include "ParserError.h"
#include "ScriptDebugServer.h"
#include "SourceCode.h"
#include "TypeProfiler.h"
#include "TypeProfilerLog.h"
#include <wtf/JSONValues.h>
namespace Inspector {
using namespace JSC;
static bool asBool(const bool* b)
{
return b && *b;
}
static int asInt(const int* i)
{
if (i)
return *i;
return 0;
}
InspectorRuntimeAgent::InspectorRuntimeAgent(AgentContext& context)
: InspectorAgentBase("Runtime"_s)
, m_injectedScriptManager(context.injectedScriptManager)
, m_scriptDebugServer(context.environment.scriptDebugServer())
, m_vm(context.environment.vm())
{
}
InspectorRuntimeAgent::~InspectorRuntimeAgent()
{
}
static ScriptDebugServer::PauseOnExceptionsState setPauseOnExceptionsState(ScriptDebugServer& scriptDebugServer, ScriptDebugServer::PauseOnExceptionsState newState)
{
auto presentState = scriptDebugServer.pauseOnExceptionsState();
if (presentState != newState)
scriptDebugServer.setPauseOnExceptionsState(newState);
return presentState;
}
static Ref<Protocol::Runtime::ErrorRange> buildErrorRangeObject(const JSTokenLocation& tokenLocation)
{
return Protocol::Runtime::ErrorRange::create()
.setStartOffset(tokenLocation.startOffset)
.setEndOffset(tokenLocation.endOffset)
.release();
}
void InspectorRuntimeAgent::parse(ErrorString&, const String& expression, Protocol::Runtime::SyntaxErrorType* result, Optional<String>& message, RefPtr<Protocol::Runtime::ErrorRange>& range)
{
JSLockHolder lock(m_vm);
ParserError error;
checkSyntax(m_vm, JSC::makeSource(expression, { }), error);
switch (error.syntaxErrorType()) {
case ParserError::SyntaxErrorNone:
*result = Protocol::Runtime::SyntaxErrorType::None;
break;
case ParserError::SyntaxErrorIrrecoverable:
*result = Protocol::Runtime::SyntaxErrorType::Irrecoverable;
break;
case ParserError::SyntaxErrorUnterminatedLiteral:
*result = Protocol::Runtime::SyntaxErrorType::UnterminatedLiteral;
break;
case ParserError::SyntaxErrorRecoverable:
*result = 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* objectGroup, const bool* includeCommandLineAPI, const bool* doNotPauseOnExceptionsAndMuteConsole, const int* executionContextId, const bool* returnByValue, const bool* generatePreview, const bool* saveResult, const bool* /* emulateUserGesture */, RefPtr<Protocol::Runtime::RemoteObject>& result, Optional<bool>& wasThrown, Optional<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::awaitPromise(const String& promiseObjectId, const bool* returnByValue, const bool* generatePreview, const bool* saveResult, Ref<AwaitPromiseCallback>&& callback)
{
InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(promiseObjectId);
if (injectedScript.hasNoValue()) {
callback->sendFailure("Missing injected script for given promiseObjectId"_s);
return;
}
injectedScript.awaitPromise(promiseObjectId, asBool(returnByValue), asBool(generatePreview), asBool(saveResult), [callback = WTFMove(callback)] (ErrorString& errorString, RefPtr<Protocol::Runtime::RemoteObject>&& result, Optional<bool>& wasThrown, Optional<int>& savedResultIndex) {
if (!errorString.isEmpty())
callback->sendFailure(errorString);
else
callback->sendSuccess(WTFMove(result), wasThrown, savedResultIndex);
});
}
void InspectorRuntimeAgent::callFunctionOn(ErrorString& errorString, const String& objectId, const String& expression, const JSON::Array* optionalArguments, const bool* doNotPauseOnExceptionsAndMuteConsole, const bool* returnByValue, const bool* generatePreview, const bool* /* emulateUserGesture */, RefPtr<Protocol::Runtime::RemoteObject>& result, Optional<bool>& wasThrown)
{
InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(objectId);
if (injectedScript.hasNoValue()) {
errorString = "Missing injected script for given objectId"_s;
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::getPreview(ErrorString& errorString, const String& objectId, RefPtr<Protocol::Runtime::ObjectPreview>& preview)
{
InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(objectId);
if (injectedScript.hasNoValue()) {
errorString = "Missing injected script for given objectId"_s;
return;
}
ScriptDebugServer::PauseOnExceptionsState previousPauseOnExceptionsState = setPauseOnExceptionsState(m_scriptDebugServer, ScriptDebugServer::DontPauseOnExceptions);
muteConsole();
injectedScript.getPreview(errorString, objectId, preview);
unmuteConsole();
setPauseOnExceptionsState(m_scriptDebugServer, previousPauseOnExceptionsState);
}
void InspectorRuntimeAgent::getProperties(ErrorString& errorString, const String& objectId, const bool* ownProperties, const int* fetchStart, const int* fetchCount, const bool* generatePreview, RefPtr<JSON::ArrayOf<Protocol::Runtime::PropertyDescriptor>>& properties, RefPtr<JSON::ArrayOf<Protocol::Runtime::InternalPropertyDescriptor>>& internalProperties)
{
InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(objectId);
if (injectedScript.hasNoValue()) {
errorString = "Missing injected script for given objectId"_s;
return;
}
int start = asInt(fetchStart);
if (start < 0) {
errorString = "fetchStart cannot be negative"_s;
return;
}
int count = asInt(fetchCount);
if (count < 0) {
errorString = "fetchCount cannot be negative"_s;
return;
}
ScriptDebugServer::PauseOnExceptionsState previousPauseOnExceptionsState = setPauseOnExceptionsState(m_scriptDebugServer, ScriptDebugServer::DontPauseOnExceptions);
muteConsole();
injectedScript.getProperties(errorString, objectId, asBool(ownProperties), start, count, asBool(generatePreview), properties);
// Only include internal properties for the first fetch.
if (!start)
injectedScript.getInternalProperties(errorString, objectId, asBool(generatePreview), internalProperties);
unmuteConsole();
setPauseOnExceptionsState(m_scriptDebugServer, previousPauseOnExceptionsState);
}
void InspectorRuntimeAgent::getDisplayableProperties(ErrorString& errorString, const String& objectId, const int* fetchStart, const int* fetchCount, const bool* generatePreview, RefPtr<JSON::ArrayOf<Protocol::Runtime::PropertyDescriptor>>& properties, RefPtr<JSON::ArrayOf<Protocol::Runtime::InternalPropertyDescriptor>>& internalProperties)
{
InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(objectId);
if (injectedScript.hasNoValue()) {
errorString = "Missing injected script for given objectId"_s;
return;
}
int start = asInt(fetchStart);
if (start < 0) {
errorString = "fetchStart cannot be negative"_s;
return;
}
int count = asInt(fetchCount);
if (count < 0) {
errorString = "fetchCount cannot be negative"_s;
return;
}
ScriptDebugServer::PauseOnExceptionsState previousPauseOnExceptionsState = setPauseOnExceptionsState(m_scriptDebugServer, ScriptDebugServer::DontPauseOnExceptions);
muteConsole();
injectedScript.getDisplayableProperties(errorString, objectId, start, count, asBool(generatePreview), properties);
// Only include internal properties for the first fetch.
if (!start)
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* fetchStart, const int* fetchCount, RefPtr<JSON::ArrayOf<Protocol::Runtime::CollectionEntry>>& entries)
{
InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(objectId);
if (injectedScript.hasNoValue()) {
errorString = "Missing injected script for given objectId"_s;
return;
}
int start = asInt(fetchStart);
if (start < 0) {
errorString = "fetchStart cannot be negative"_s;
return;
}
int count = asInt(fetchCount);
if (count < 0) {
errorString = "fetchCount cannot be negative"_s;
return;
}
injectedScript.getCollectionEntries(errorString, objectId, objectGroup ? *objectGroup : String(), start, count, entries);
}
void InspectorRuntimeAgent::saveResult(ErrorString& errorString, const JSON::Object& callArgument, const int* executionContextId, Optional<int>& savedResultIndex)
{
InjectedScript injectedScript;
String objectId;
if (callArgument.getString("objectId"_s, objectId)) {
injectedScript = m_injectedScriptManager.injectedScriptForObjectId(objectId);
if (injectedScript.hasNoValue()) {
errorString = "Missing injected script for given objectId"_s;
return;
}
} else {
injectedScript = injectedScriptForEval(errorString, executionContextId);
if (injectedScript.hasNoValue())
return;
}
injectedScript.saveResult(errorString, callArgument.toJSONString(), savedResultIndex);
}
void InspectorRuntimeAgent::setSavedResultAlias(ErrorString&, const String* alias)
{
Optional<String> savedResultAlias;
if (alias && !alias->isEmpty())
savedResultAlias = *alias;
m_injectedScriptManager.injectedScriptHost().setSavedResultAlias(savedResultAlias);
}
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::getRuntimeTypesForVariablesAtOffsets(ErrorString& errorString, const JSON::Array& locations, RefPtr<JSON::ArrayOf<Protocol::Runtime::TypeDescription>>& typeDescriptions)
{
static constexpr bool verbose = false;
typeDescriptions = JSON::ArrayOf<Protocol::Runtime::TypeDescription>::create();
if (!m_vm.typeProfiler()) {
errorString = "VM has no type information"_s;
return;
}
MonotonicTime start = MonotonicTime::now();
m_vm.typeProfilerLog()->processLogEntries(m_vm, "User Query"_s);
for (size_t i = 0; i < locations.length(); i++) {
RefPtr<JSON::Value> value = locations.get(i);
RefPtr<JSON::Object> location;
if (!value->asObject(location)) {
errorString = "Unexpected non-object item in locations"_s;
return;
}
int descriptor;
String sourceIDAsString;
int divot;
location->getInteger("typeInformationDescriptor"_s, descriptor);
location->getString("sourceID"_s, sourceIDAsString);
location->getInteger("divot"_s, divot);
bool okay;
TypeLocation* typeLocation = m_vm.typeProfiler()->findLocation(divot, sourceIDAsString.toIntPtrStrict(&okay), static_cast<TypeProfilerSearchDescriptor>(descriptor), m_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 = 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(WTFMove(description));
}
MonotonicTime end = MonotonicTime::now();
if (verbose)
dataLogF("Inspector::getRuntimeTypesForVariablesAtOffsets took %lfms\n", (end - start).milliseconds());
}
void InspectorRuntimeAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*)
{
}
void InspectorRuntimeAgent::willDestroyFrontendAndBackend(DisconnectReason reason)
{
if (reason != DisconnectReason::InspectedTargetDestroyed && m_isTypeProfilingEnabled)
setTypeProfilerEnabledState(false);
String unused;
disable(unused);
}
void InspectorRuntimeAgent::enableTypeProfiler(ErrorString&)
{
setTypeProfilerEnabledState(true);
}
void InspectorRuntimeAgent::disableTypeProfiler(ErrorString&)
{
setTypeProfilerEnabledState(false);
}
void InspectorRuntimeAgent::enableControlFlowProfiler(ErrorString&)
{
setControlFlowProfilerEnabledState(true);
}
void InspectorRuntimeAgent::disableControlFlowProfiler(ErrorString&)
{
setControlFlowProfilerEnabledState(false);
}
void InspectorRuntimeAgent::setTypeProfilerEnabledState(bool isTypeProfilingEnabled)
{
if (m_isTypeProfilingEnabled == isTypeProfilingEnabled)
return;
m_isTypeProfilingEnabled = isTypeProfilingEnabled;
VM& vm = m_vm;
vm.whenIdle([&vm, isTypeProfilingEnabled] () {
bool shouldRecompileFromTypeProfiler = (isTypeProfilingEnabled ? vm.enableTypeProfiler() : vm.disableTypeProfiler());
if (shouldRecompileFromTypeProfiler)
vm.deleteAllCode(PreventCollectionAndDeleteAllCode);
});
}
void InspectorRuntimeAgent::setControlFlowProfilerEnabledState(bool isControlFlowProfilingEnabled)
{
if (m_isControlFlowProfilingEnabled == isControlFlowProfilingEnabled)
return;
m_isControlFlowProfilingEnabled = isControlFlowProfilingEnabled;
VM& vm = m_vm;
vm.whenIdle([&vm, isControlFlowProfilingEnabled] () {
bool shouldRecompileFromControlFlowProfiler = (isControlFlowProfilingEnabled ? vm.enableControlFlowProfiler() : vm.disableControlFlowProfiler());
if (shouldRecompileFromControlFlowProfiler)
vm.deleteAllCode(PreventCollectionAndDeleteAllCode);
});
}
void InspectorRuntimeAgent::getBasicBlocks(ErrorString& errorString, const String& sourceIDAsString, RefPtr<JSON::ArrayOf<Protocol::Runtime::BasicBlock>>& basicBlocks)
{
if (!m_vm.controlFlowProfiler()) {
errorString = "VM has no control flow information"_s;
return;
}
bool okay;
intptr_t sourceID = sourceIDAsString.toIntPtrStrict(&okay);
ASSERT(okay);
const Vector<BasicBlockRange>& basicBlockRanges = m_vm.controlFlowProfiler()->getBasicBlocksForSourceID(sourceID, m_vm);
basicBlocks = JSON::ArrayOf<Protocol::Runtime::BasicBlock>::create();
for (const BasicBlockRange& block : basicBlockRanges) {
auto location = Protocol::Runtime::BasicBlock::create()
.setStartOffset(block.m_startOffset)
.setEndOffset(block.m_endOffset)
.setHasExecuted(block.m_hasExecuted)
.setExecutionCount(block.m_executionCount)
.release();
basicBlocks->addItem(WTFMove(location));
}
}
} // namespace Inspector