1 /*
2  * Copyright (C) 2011 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include "src/inspector/v8-runtime-agent-impl.h"
32 
33 #include <inttypes.h>
34 
35 #include "../../third_party/inspector_protocol/crdtp/json.h"
36 #include "include/v8-container.h"
37 #include "include/v8-context.h"
38 #include "include/v8-function.h"
39 #include "include/v8-inspector.h"
40 #include "include/v8-microtask-queue.h"
41 #include "src/debug/debug-interface.h"
42 #include "src/inspector/injected-script.h"
43 #include "src/inspector/inspected-context.h"
44 #include "src/inspector/protocol/Protocol.h"
45 #include "src/inspector/remote-object-id.h"
46 #include "src/inspector/v8-console-message.h"
47 #include "src/inspector/v8-debugger-agent-impl.h"
48 #include "src/inspector/v8-debugger.h"
49 #include "src/inspector/v8-inspector-impl.h"
50 #include "src/inspector/v8-inspector-session-impl.h"
51 #include "src/inspector/v8-stack-trace-impl.h"
52 #include "src/inspector/v8-value-utils.h"
53 #include "src/tracing/trace-event.h"
54 
55 namespace v8_inspector {
56 
57 namespace V8RuntimeAgentImplState {
58 static const char customObjectFormatterEnabled[] =
59     "customObjectFormatterEnabled";
60 static const char runtimeEnabled[] = "runtimeEnabled";
61 static const char bindings[] = "bindings";
62 static const char globalBindingsKey[] = "";
63 }  // namespace V8RuntimeAgentImplState
64 
65 using protocol::Runtime::RemoteObject;
66 
67 namespace {
68 
69 template <typename ProtocolCallback>
70 class EvaluateCallbackWrapper : public EvaluateCallback {
71  public:
wrap(std::unique_ptr<ProtocolCallback> callback)72   static std::unique_ptr<EvaluateCallback> wrap(
73       std::unique_ptr<ProtocolCallback> callback) {
74     return std::unique_ptr<EvaluateCallback>(
75         new EvaluateCallbackWrapper(std::move(callback)));
76   }
sendSuccess(std::unique_ptr<protocol::Runtime::RemoteObject> result,protocol::Maybe<protocol::Runtime::ExceptionDetails> exceptionDetails)77   void sendSuccess(std::unique_ptr<protocol::Runtime::RemoteObject> result,
78                    protocol::Maybe<protocol::Runtime::ExceptionDetails>
79                        exceptionDetails) override {
80     return m_callback->sendSuccess(std::move(result),
81                                    std::move(exceptionDetails));
82   }
sendFailure(const protocol::DispatchResponse & response)83   void sendFailure(const protocol::DispatchResponse& response) override {
84     return m_callback->sendFailure(response);
85   }
86 
87  private:
EvaluateCallbackWrapper(std::unique_ptr<ProtocolCallback> callback)88   explicit EvaluateCallbackWrapper(std::unique_ptr<ProtocolCallback> callback)
89       : m_callback(std::move(callback)) {}
90 
91   std::unique_ptr<ProtocolCallback> m_callback;
92 };
93 
94 template <typename ProtocolCallback>
wrapEvaluateResultAsync(InjectedScript * injectedScript,v8::MaybeLocal<v8::Value> maybeResultValue,const v8::TryCatch & tryCatch,const String16 & objectGroup,WrapMode wrapMode,ProtocolCallback * callback)95 bool wrapEvaluateResultAsync(InjectedScript* injectedScript,
96                              v8::MaybeLocal<v8::Value> maybeResultValue,
97                              const v8::TryCatch& tryCatch,
98                              const String16& objectGroup, WrapMode wrapMode,
99                              ProtocolCallback* callback) {
100   std::unique_ptr<RemoteObject> result;
101   Maybe<protocol::Runtime::ExceptionDetails> exceptionDetails;
102 
103   Response response = injectedScript->wrapEvaluateResult(
104       maybeResultValue, tryCatch, objectGroup, wrapMode, &result,
105       &exceptionDetails);
106   if (response.IsSuccess()) {
107     callback->sendSuccess(std::move(result), std::move(exceptionDetails));
108     return true;
109   }
110   callback->sendFailure(response);
111   return false;
112 }
113 
innerCallFunctionOn(V8InspectorSessionImpl * session,InjectedScript::Scope & scope,v8::Local<v8::Value> recv,const String16 & expression,Maybe<protocol::Array<protocol::Runtime::CallArgument>> optionalArguments,bool silent,WrapMode wrapMode,bool userGesture,bool awaitPromise,const String16 & objectGroup,bool throw_on_side_effect,std::unique_ptr<V8RuntimeAgentImpl::CallFunctionOnCallback> callback)114 void innerCallFunctionOn(
115     V8InspectorSessionImpl* session, InjectedScript::Scope& scope,
116     v8::Local<v8::Value> recv, const String16& expression,
117     Maybe<protocol::Array<protocol::Runtime::CallArgument>> optionalArguments,
118     bool silent, WrapMode wrapMode, bool userGesture, bool awaitPromise,
119     const String16& objectGroup, bool throw_on_side_effect,
120     std::unique_ptr<V8RuntimeAgentImpl::CallFunctionOnCallback> callback) {
121   V8InspectorImpl* inspector = session->inspector();
122 
123   std::unique_ptr<v8::Local<v8::Value>[]> argv = nullptr;
124   int argc = 0;
125   if (optionalArguments.isJust()) {
126     protocol::Array<protocol::Runtime::CallArgument>* arguments =
127         optionalArguments.fromJust();
128     argc = static_cast<int>(arguments->size());
129     argv.reset(new v8::Local<v8::Value>[argc]);
130     for (int i = 0; i < argc; ++i) {
131       v8::Local<v8::Value> argumentValue;
132       Response response = scope.injectedScript()->resolveCallArgument(
133           (*arguments)[i].get(), &argumentValue);
134       if (!response.IsSuccess()) {
135         callback->sendFailure(response);
136         return;
137       }
138       argv[i] = argumentValue;
139     }
140   }
141 
142   if (silent) scope.ignoreExceptionsAndMuteConsole();
143   if (userGesture) scope.pretendUserGesture();
144 
145   // Temporarily enable allow evals for inspector.
146   scope.allowCodeGenerationFromStrings();
147 
148   v8::MaybeLocal<v8::Value> maybeFunctionValue;
149   v8::Local<v8::Script> functionScript;
150   if (inspector
151           ->compileScript(scope.context(), "(" + expression + ")", String16())
152           .ToLocal(&functionScript)) {
153     v8::MicrotasksScope microtasksScope(inspector->isolate(),
154                                         v8::MicrotasksScope::kRunMicrotasks);
155     maybeFunctionValue = functionScript->Run(scope.context());
156   }
157   // Re-initialize after running client's code, as it could have destroyed
158   // context or session.
159   Response response = scope.initialize();
160   if (!response.IsSuccess()) {
161     callback->sendFailure(response);
162     return;
163   }
164 
165   if (scope.tryCatch().HasCaught()) {
166     wrapEvaluateResultAsync(scope.injectedScript(), maybeFunctionValue,
167                             scope.tryCatch(), objectGroup, WrapMode::kNoPreview,
168                             callback.get());
169     return;
170   }
171 
172   v8::Local<v8::Value> functionValue;
173   if (!maybeFunctionValue.ToLocal(&functionValue) ||
174       !functionValue->IsFunction()) {
175     callback->sendFailure(Response::ServerError(
176         "Given expression does not evaluate to a function"));
177     return;
178   }
179 
180   v8::MaybeLocal<v8::Value> maybeResultValue;
181   {
182     v8::MicrotasksScope microtasksScope(inspector->isolate(),
183                                         v8::MicrotasksScope::kRunMicrotasks);
184     maybeResultValue = v8::debug::CallFunctionOn(
185         scope.context(), functionValue.As<v8::Function>(), recv, argc,
186         argv.get(), throw_on_side_effect);
187   }
188   // Re-initialize after running client's code, as it could have destroyed
189   // context or session.
190   response = scope.initialize();
191   if (!response.IsSuccess()) {
192     callback->sendFailure(response);
193     return;
194   }
195 
196   if (!awaitPromise || scope.tryCatch().HasCaught()) {
197     wrapEvaluateResultAsync(scope.injectedScript(), maybeResultValue,
198                             scope.tryCatch(), objectGroup, wrapMode,
199                             callback.get());
200     return;
201   }
202 
203   scope.injectedScript()->addPromiseCallback(
204       session, maybeResultValue, objectGroup, wrapMode, false /* replMode */,
205       EvaluateCallbackWrapper<V8RuntimeAgentImpl::CallFunctionOnCallback>::wrap(
206           std::move(callback)));
207 }
208 
ensureContext(V8InspectorImpl * inspector,int contextGroupId,Maybe<int> executionContextId,Maybe<String16> uniqueContextId,int * contextId)209 Response ensureContext(V8InspectorImpl* inspector, int contextGroupId,
210                        Maybe<int> executionContextId,
211                        Maybe<String16> uniqueContextId, int* contextId) {
212   if (executionContextId.isJust()) {
213     if (uniqueContextId.isJust()) {
214       return Response::InvalidParams(
215           "contextId and uniqueContextId are mutually exclusive");
216     }
217     *contextId = executionContextId.fromJust();
218   } else if (uniqueContextId.isJust()) {
219     V8DebuggerId uniqueId(uniqueContextId.fromJust());
220     if (!uniqueId.isValid())
221       return Response::InvalidParams("invalid uniqueContextId");
222     int id = inspector->resolveUniqueContextId(uniqueId);
223     if (!id) return Response::InvalidParams("uniqueContextId not found");
224     *contextId = id;
225   } else {
226     v8::HandleScope handles(inspector->isolate());
227     v8::Local<v8::Context> defaultContext =
228         inspector->client()->ensureDefaultContextInGroup(contextGroupId);
229     if (defaultContext.IsEmpty())
230       return Response::ServerError("Cannot find default execution context");
231     *contextId = InspectedContext::contextId(defaultContext);
232   }
233 
234   return Response::Success();
235 }
236 
237 }  // namespace
238 
V8RuntimeAgentImpl(V8InspectorSessionImpl * session,protocol::FrontendChannel * FrontendChannel,protocol::DictionaryValue * state)239 V8RuntimeAgentImpl::V8RuntimeAgentImpl(
240     V8InspectorSessionImpl* session, protocol::FrontendChannel* FrontendChannel,
241     protocol::DictionaryValue* state)
242     : m_session(session),
243       m_state(state),
244       m_frontend(FrontendChannel),
245       m_inspector(session->inspector()),
246       m_enabled(false) {}
247 
248 V8RuntimeAgentImpl::~V8RuntimeAgentImpl() = default;
249 
evaluate(const String16 & expression,Maybe<String16> objectGroup,Maybe<bool> includeCommandLineAPI,Maybe<bool> silent,Maybe<int> executionContextId,Maybe<bool> returnByValue,Maybe<bool> generatePreview,Maybe<bool> userGesture,Maybe<bool> maybeAwaitPromise,Maybe<bool> throwOnSideEffect,Maybe<double> timeout,Maybe<bool> disableBreaks,Maybe<bool> maybeReplMode,Maybe<bool> allowUnsafeEvalBlockedByCSP,Maybe<String16> uniqueContextId,std::unique_ptr<EvaluateCallback> callback)250 void V8RuntimeAgentImpl::evaluate(
251     const String16& expression, Maybe<String16> objectGroup,
252     Maybe<bool> includeCommandLineAPI, Maybe<bool> silent,
253     Maybe<int> executionContextId, Maybe<bool> returnByValue,
254     Maybe<bool> generatePreview, Maybe<bool> userGesture,
255     Maybe<bool> maybeAwaitPromise, Maybe<bool> throwOnSideEffect,
256     Maybe<double> timeout, Maybe<bool> disableBreaks, Maybe<bool> maybeReplMode,
257     Maybe<bool> allowUnsafeEvalBlockedByCSP, Maybe<String16> uniqueContextId,
258     std::unique_ptr<EvaluateCallback> callback) {
259   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
260                "EvaluateScript");
261   int contextId = 0;
262   Response response = ensureContext(m_inspector, m_session->contextGroupId(),
263                                     std::move(executionContextId),
264                                     std::move(uniqueContextId), &contextId);
265   if (!response.IsSuccess()) {
266     callback->sendFailure(response);
267     return;
268   }
269 
270   InjectedScript::ContextScope scope(m_session, contextId);
271   response = scope.initialize();
272   if (!response.IsSuccess()) {
273     callback->sendFailure(response);
274     return;
275   }
276 
277   if (silent.fromMaybe(false)) scope.ignoreExceptionsAndMuteConsole();
278   if (userGesture.fromMaybe(false)) scope.pretendUserGesture();
279 
280   if (includeCommandLineAPI.fromMaybe(false)) scope.installCommandLineAPI();
281 
282   const bool replMode = maybeReplMode.fromMaybe(false);
283 
284   if (allowUnsafeEvalBlockedByCSP.fromMaybe(true)) {
285     // Temporarily enable allow evals for inspector.
286     scope.allowCodeGenerationFromStrings();
287   }
288   v8::MaybeLocal<v8::Value> maybeResultValue;
289   {
290     V8InspectorImpl::EvaluateScope evaluateScope(scope);
291     if (timeout.isJust()) {
292       response = evaluateScope.setTimeout(timeout.fromJust() / 1000.0);
293       if (!response.IsSuccess()) {
294         callback->sendFailure(response);
295         return;
296       }
297     }
298     v8::MicrotasksScope microtasksScope(m_inspector->isolate(),
299                                         v8::MicrotasksScope::kRunMicrotasks);
300     v8::debug::EvaluateGlobalMode mode =
301         v8::debug::EvaluateGlobalMode::kDefault;
302     if (throwOnSideEffect.fromMaybe(false)) {
303       mode = v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect;
304     } else if (disableBreaks.fromMaybe(false)) {
305       mode = v8::debug::EvaluateGlobalMode::kDisableBreaks;
306     }
307     const v8::Local<v8::String> source =
308         toV8String(m_inspector->isolate(), expression);
309     maybeResultValue = v8::debug::EvaluateGlobal(m_inspector->isolate(), source,
310                                                  mode, replMode);
311   }  // Run microtasks before returning result.
312 
313   // Re-initialize after running client's code, as it could have destroyed
314   // context or session.
315   response = scope.initialize();
316   if (!response.IsSuccess()) {
317     callback->sendFailure(response);
318     return;
319   }
320 
321   WrapMode mode = generatePreview.fromMaybe(false) ? WrapMode::kWithPreview
322                                                    : WrapMode::kNoPreview;
323   if (returnByValue.fromMaybe(false)) mode = WrapMode::kForceValue;
324 
325   // REPL mode always returns a promise that must be awaited.
326   const bool await = replMode || maybeAwaitPromise.fromMaybe(false);
327   if (!await || scope.tryCatch().HasCaught()) {
328     wrapEvaluateResultAsync(scope.injectedScript(), maybeResultValue,
329                             scope.tryCatch(), objectGroup.fromMaybe(""), mode,
330                             callback.get());
331     return;
332   }
333   scope.injectedScript()->addPromiseCallback(
334       m_session, maybeResultValue, objectGroup.fromMaybe(""), mode, replMode,
335       EvaluateCallbackWrapper<EvaluateCallback>::wrap(std::move(callback)));
336 }
337 
awaitPromise(const String16 & promiseObjectId,Maybe<bool> returnByValue,Maybe<bool> generatePreview,std::unique_ptr<AwaitPromiseCallback> callback)338 void V8RuntimeAgentImpl::awaitPromise(
339     const String16& promiseObjectId, Maybe<bool> returnByValue,
340     Maybe<bool> generatePreview,
341     std::unique_ptr<AwaitPromiseCallback> callback) {
342   InjectedScript::ObjectScope scope(m_session, promiseObjectId);
343   Response response = scope.initialize();
344   if (!response.IsSuccess()) {
345     callback->sendFailure(response);
346     return;
347   }
348   if (!scope.object()->IsPromise()) {
349     callback->sendFailure(
350         Response::ServerError("Could not find promise with given id"));
351     return;
352   }
353   WrapMode mode = generatePreview.fromMaybe(false) ? WrapMode::kWithPreview
354                                                    : WrapMode::kNoPreview;
355   if (returnByValue.fromMaybe(false)) mode = WrapMode::kForceValue;
356   scope.injectedScript()->addPromiseCallback(
357       m_session, scope.object(), scope.objectGroupName(), mode,
358       false /* replMode */,
359       EvaluateCallbackWrapper<AwaitPromiseCallback>::wrap(std::move(callback)));
360 }
361 
callFunctionOn(const String16 & expression,Maybe<String16> objectId,Maybe<protocol::Array<protocol::Runtime::CallArgument>> optionalArguments,Maybe<bool> silent,Maybe<bool> returnByValue,Maybe<bool> generatePreview,Maybe<bool> userGesture,Maybe<bool> awaitPromise,Maybe<int> executionContextId,Maybe<String16> objectGroup,Maybe<bool> throwOnSideEffect,std::unique_ptr<CallFunctionOnCallback> callback)362 void V8RuntimeAgentImpl::callFunctionOn(
363     const String16& expression, Maybe<String16> objectId,
364     Maybe<protocol::Array<protocol::Runtime::CallArgument>> optionalArguments,
365     Maybe<bool> silent, Maybe<bool> returnByValue, Maybe<bool> generatePreview,
366     Maybe<bool> userGesture, Maybe<bool> awaitPromise,
367     Maybe<int> executionContextId, Maybe<String16> objectGroup,
368     Maybe<bool> throwOnSideEffect,
369     std::unique_ptr<CallFunctionOnCallback> callback) {
370   if (objectId.isJust() && executionContextId.isJust()) {
371     callback->sendFailure(Response::ServerError(
372         "ObjectId must not be specified together with executionContextId"));
373     return;
374   }
375   if (!objectId.isJust() && !executionContextId.isJust()) {
376     callback->sendFailure(Response::ServerError(
377         "Either ObjectId or executionContextId must be specified"));
378     return;
379   }
380   WrapMode mode = generatePreview.fromMaybe(false) ? WrapMode::kWithPreview
381                                                    : WrapMode::kNoPreview;
382   if (returnByValue.fromMaybe(false)) mode = WrapMode::kForceValue;
383   if (objectId.isJust()) {
384     InjectedScript::ObjectScope scope(m_session, objectId.fromJust());
385     Response response = scope.initialize();
386     if (!response.IsSuccess()) {
387       callback->sendFailure(response);
388       return;
389     }
390     innerCallFunctionOn(
391         m_session, scope, scope.object(), expression,
392         std::move(optionalArguments), silent.fromMaybe(false), mode,
393         userGesture.fromMaybe(false), awaitPromise.fromMaybe(false),
394         objectGroup.isJust() ? objectGroup.fromMaybe(String16())
395                              : scope.objectGroupName(),
396         throwOnSideEffect.fromMaybe(false), std::move(callback));
397   } else {
398     int contextId = 0;
399     Response response = ensureContext(m_inspector, m_session->contextGroupId(),
400                                       std::move(executionContextId.fromJust()),
401                                       /* uniqueContextId */ {}, &contextId);
402     if (!response.IsSuccess()) {
403       callback->sendFailure(response);
404       return;
405     }
406     InjectedScript::ContextScope scope(m_session, contextId);
407     response = scope.initialize();
408     if (!response.IsSuccess()) {
409       callback->sendFailure(response);
410       return;
411     }
412     innerCallFunctionOn(
413         m_session, scope, scope.context()->Global(), expression,
414         std::move(optionalArguments), silent.fromMaybe(false), mode,
415         userGesture.fromMaybe(false), awaitPromise.fromMaybe(false),
416         objectGroup.fromMaybe(""), throwOnSideEffect.fromMaybe(false),
417         std::move(callback));
418   }
419 }
420 
getProperties(const String16 & objectId,Maybe<bool> ownProperties,Maybe<bool> accessorPropertiesOnly,Maybe<bool> generatePreview,Maybe<bool> nonIndexedPropertiesOnly,std::unique_ptr<protocol::Array<protocol::Runtime::PropertyDescriptor>> * result,Maybe<protocol::Array<protocol::Runtime::InternalPropertyDescriptor>> * internalProperties,Maybe<protocol::Array<protocol::Runtime::PrivatePropertyDescriptor>> * privateProperties,Maybe<protocol::Runtime::ExceptionDetails> * exceptionDetails)421 Response V8RuntimeAgentImpl::getProperties(
422     const String16& objectId, Maybe<bool> ownProperties,
423     Maybe<bool> accessorPropertiesOnly, Maybe<bool> generatePreview,
424     Maybe<bool> nonIndexedPropertiesOnly,
425     std::unique_ptr<protocol::Array<protocol::Runtime::PropertyDescriptor>>*
426         result,
427     Maybe<protocol::Array<protocol::Runtime::InternalPropertyDescriptor>>*
428         internalProperties,
429     Maybe<protocol::Array<protocol::Runtime::PrivatePropertyDescriptor>>*
430         privateProperties,
431     Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) {
432   using protocol::Runtime::InternalPropertyDescriptor;
433   using protocol::Runtime::PrivatePropertyDescriptor;
434 
435   InjectedScript::ObjectScope scope(m_session, objectId);
436   Response response = scope.initialize();
437   if (!response.IsSuccess()) return response;
438 
439   scope.ignoreExceptionsAndMuteConsole();
440   v8::MicrotasksScope microtasks_scope(m_inspector->isolate(),
441                                        v8::MicrotasksScope::kRunMicrotasks);
442   if (!scope.object()->IsObject())
443     return Response::ServerError("Value with given id is not an object");
444 
445   v8::Local<v8::Object> object = scope.object().As<v8::Object>();
446   response = scope.injectedScript()->getProperties(
447       object, scope.objectGroupName(), ownProperties.fromMaybe(false),
448       accessorPropertiesOnly.fromMaybe(false),
449       nonIndexedPropertiesOnly.fromMaybe(false),
450       generatePreview.fromMaybe(false) ? WrapMode::kWithPreview
451                                        : WrapMode::kNoPreview,
452       result, exceptionDetails);
453   if (!response.IsSuccess()) return response;
454   if (exceptionDetails->isJust() || accessorPropertiesOnly.fromMaybe(false))
455     return Response::Success();
456   std::unique_ptr<protocol::Array<InternalPropertyDescriptor>>
457       internalPropertiesProtocolArray;
458   std::unique_ptr<protocol::Array<PrivatePropertyDescriptor>>
459       privatePropertiesProtocolArray;
460   response = scope.injectedScript()->getInternalAndPrivateProperties(
461       object, scope.objectGroupName(), &internalPropertiesProtocolArray,
462       &privatePropertiesProtocolArray);
463   if (!response.IsSuccess()) return response;
464   if (!internalPropertiesProtocolArray->empty())
465     *internalProperties = std::move(internalPropertiesProtocolArray);
466   if (!privatePropertiesProtocolArray->empty())
467     *privateProperties = std::move(privatePropertiesProtocolArray);
468   return Response::Success();
469 }
470 
releaseObject(const String16 & objectId)471 Response V8RuntimeAgentImpl::releaseObject(const String16& objectId) {
472   InjectedScript::ObjectScope scope(m_session, objectId);
473   Response response = scope.initialize();
474   if (!response.IsSuccess()) return response;
475   scope.injectedScript()->releaseObject(objectId);
476   return Response::Success();
477 }
478 
releaseObjectGroup(const String16 & objectGroup)479 Response V8RuntimeAgentImpl::releaseObjectGroup(const String16& objectGroup) {
480   m_session->releaseObjectGroup(objectGroup);
481   return Response::Success();
482 }
483 
runIfWaitingForDebugger()484 Response V8RuntimeAgentImpl::runIfWaitingForDebugger() {
485   m_inspector->client()->runIfWaitingForDebugger(m_session->contextGroupId());
486   return Response::Success();
487 }
488 
setCustomObjectFormatterEnabled(bool enabled)489 Response V8RuntimeAgentImpl::setCustomObjectFormatterEnabled(bool enabled) {
490   m_state->setBoolean(V8RuntimeAgentImplState::customObjectFormatterEnabled,
491                       enabled);
492   if (!m_enabled) return Response::ServerError("Runtime agent is not enabled");
493   m_session->setCustomObjectFormatterEnabled(enabled);
494   return Response::Success();
495 }
496 
setMaxCallStackSizeToCapture(int size)497 Response V8RuntimeAgentImpl::setMaxCallStackSizeToCapture(int size) {
498   if (size < 0) {
499     return Response::ServerError(
500         "maxCallStackSizeToCapture should be non-negative");
501   }
502   V8StackTraceImpl::maxCallStackSizeToCapture = size;
503   return Response::Success();
504 }
505 
discardConsoleEntries()506 Response V8RuntimeAgentImpl::discardConsoleEntries() {
507   V8ConsoleMessageStorage* storage =
508       m_inspector->ensureConsoleMessageStorage(m_session->contextGroupId());
509   storage->clear();
510   return Response::Success();
511 }
512 
compileScript(const String16 & expression,const String16 & sourceURL,bool persistScript,Maybe<int> executionContextId,Maybe<String16> * scriptId,Maybe<protocol::Runtime::ExceptionDetails> * exceptionDetails)513 Response V8RuntimeAgentImpl::compileScript(
514     const String16& expression, const String16& sourceURL, bool persistScript,
515     Maybe<int> executionContextId, Maybe<String16>* scriptId,
516     Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) {
517   if (!m_enabled) return Response::ServerError("Runtime agent is not enabled");
518 
519   int contextId = 0;
520   Response response = ensureContext(m_inspector, m_session->contextGroupId(),
521                                     std::move(executionContextId),
522                                     /*uniqueContextId*/ {}, &contextId);
523   if (!response.IsSuccess()) return response;
524   InjectedScript::ContextScope scope(m_session, contextId);
525   response = scope.initialize();
526   if (!response.IsSuccess()) return response;
527 
528   if (!persistScript) m_inspector->debugger()->muteScriptParsedEvents();
529   v8::Local<v8::Script> script;
530   bool isOk = m_inspector->compileScript(scope.context(), expression, sourceURL)
531                   .ToLocal(&script);
532   if (!persistScript) m_inspector->debugger()->unmuteScriptParsedEvents();
533   if (!isOk) {
534     if (scope.tryCatch().HasCaught()) {
535       response = scope.injectedScript()->createExceptionDetails(
536           scope.tryCatch(), String16(), exceptionDetails);
537       if (!response.IsSuccess()) return response;
538       return Response::Success();
539     } else {
540       return Response::ServerError("Script compilation failed");
541     }
542   }
543 
544   if (!persistScript) return Response::Success();
545 
546   String16 scriptValueId =
547       String16::fromInteger(script->GetUnboundScript()->GetId());
548   std::unique_ptr<v8::Global<v8::Script>> global(
549       new v8::Global<v8::Script>(m_inspector->isolate(), script));
550   m_compiledScripts[scriptValueId] = std::move(global);
551   *scriptId = scriptValueId;
552   return Response::Success();
553 }
554 
runScript(const String16 & scriptId,Maybe<int> executionContextId,Maybe<String16> objectGroup,Maybe<bool> silent,Maybe<bool> includeCommandLineAPI,Maybe<bool> returnByValue,Maybe<bool> generatePreview,Maybe<bool> awaitPromise,std::unique_ptr<RunScriptCallback> callback)555 void V8RuntimeAgentImpl::runScript(
556     const String16& scriptId, Maybe<int> executionContextId,
557     Maybe<String16> objectGroup, Maybe<bool> silent,
558     Maybe<bool> includeCommandLineAPI, Maybe<bool> returnByValue,
559     Maybe<bool> generatePreview, Maybe<bool> awaitPromise,
560     std::unique_ptr<RunScriptCallback> callback) {
561   if (!m_enabled) {
562     callback->sendFailure(
563         Response::ServerError("Runtime agent is not enabled"));
564     return;
565   }
566 
567   auto it = m_compiledScripts.find(scriptId);
568   if (it == m_compiledScripts.end()) {
569     callback->sendFailure(Response::ServerError("No script with given id"));
570     return;
571   }
572 
573   int contextId = 0;
574   Response response = ensureContext(m_inspector, m_session->contextGroupId(),
575                                     std::move(executionContextId),
576                                     /*uniqueContextId*/ {}, &contextId);
577   if (!response.IsSuccess()) {
578     callback->sendFailure(response);
579     return;
580   }
581 
582   InjectedScript::ContextScope scope(m_session, contextId);
583   response = scope.initialize();
584   if (!response.IsSuccess()) {
585     callback->sendFailure(response);
586     return;
587   }
588 
589   if (silent.fromMaybe(false)) scope.ignoreExceptionsAndMuteConsole();
590 
591   std::unique_ptr<v8::Global<v8::Script>> scriptWrapper = std::move(it->second);
592   m_compiledScripts.erase(it);
593   v8::Local<v8::Script> script = scriptWrapper->Get(m_inspector->isolate());
594   if (script.IsEmpty()) {
595     callback->sendFailure(Response::ServerError("Script execution failed"));
596     return;
597   }
598 
599   if (includeCommandLineAPI.fromMaybe(false)) scope.installCommandLineAPI();
600 
601   v8::MaybeLocal<v8::Value> maybeResultValue;
602   {
603     v8::MicrotasksScope microtasksScope(m_inspector->isolate(),
604                                         v8::MicrotasksScope::kRunMicrotasks);
605     maybeResultValue = script->Run(scope.context());
606   }
607 
608   // Re-initialize after running client's code, as it could have destroyed
609   // context or session.
610   response = scope.initialize();
611   if (!response.IsSuccess()) {
612     callback->sendFailure(response);
613     return;
614   }
615 
616   WrapMode mode = generatePreview.fromMaybe(false) ? WrapMode::kWithPreview
617                                                    : WrapMode::kNoPreview;
618   if (returnByValue.fromMaybe(false)) mode = WrapMode::kForceValue;
619   if (!awaitPromise.fromMaybe(false) || scope.tryCatch().HasCaught()) {
620     wrapEvaluateResultAsync(scope.injectedScript(), maybeResultValue,
621                             scope.tryCatch(), objectGroup.fromMaybe(""), mode,
622                             callback.get());
623     return;
624   }
625   scope.injectedScript()->addPromiseCallback(
626       m_session, maybeResultValue.ToLocalChecked(), objectGroup.fromMaybe(""),
627       mode, false /* replMode */,
628       EvaluateCallbackWrapper<RunScriptCallback>::wrap(std::move(callback)));
629 }
630 
queryObjects(const String16 & prototypeObjectId,Maybe<String16> objectGroup,std::unique_ptr<protocol::Runtime::RemoteObject> * objects)631 Response V8RuntimeAgentImpl::queryObjects(
632     const String16& prototypeObjectId, Maybe<String16> objectGroup,
633     std::unique_ptr<protocol::Runtime::RemoteObject>* objects) {
634   InjectedScript::ObjectScope scope(m_session, prototypeObjectId);
635   Response response = scope.initialize();
636   if (!response.IsSuccess()) return response;
637   if (!scope.object()->IsObject()) {
638     return Response::ServerError("Prototype should be instance of Object");
639   }
640   v8::Local<v8::Array> resultArray = m_inspector->debugger()->queryObjects(
641       scope.context(), scope.object().As<v8::Object>());
642   return scope.injectedScript()->wrapObject(
643       resultArray, objectGroup.fromMaybe(scope.objectGroupName()),
644       WrapMode::kNoPreview, objects);
645 }
646 
globalLexicalScopeNames(Maybe<int> executionContextId,std::unique_ptr<protocol::Array<String16>> * outNames)647 Response V8RuntimeAgentImpl::globalLexicalScopeNames(
648     Maybe<int> executionContextId,
649     std::unique_ptr<protocol::Array<String16>>* outNames) {
650   int contextId = 0;
651   Response response = ensureContext(m_inspector, m_session->contextGroupId(),
652                                     std::move(executionContextId),
653                                     /*uniqueContextId*/ {}, &contextId);
654   if (!response.IsSuccess()) return response;
655 
656   InjectedScript::ContextScope scope(m_session, contextId);
657   response = scope.initialize();
658   if (!response.IsSuccess()) return response;
659 
660   v8::PersistentValueVector<v8::String> names(m_inspector->isolate());
661   v8::debug::GlobalLexicalScopeNames(scope.context(), &names);
662   *outNames = std::make_unique<protocol::Array<String16>>();
663   for (size_t i = 0; i < names.Size(); ++i) {
664     (*outNames)->emplace_back(
665         toProtocolString(m_inspector->isolate(), names.Get(i)));
666   }
667   return Response::Success();
668 }
669 
getIsolateId(String16 * outIsolateId)670 Response V8RuntimeAgentImpl::getIsolateId(String16* outIsolateId) {
671   char buf[40];
672   std::snprintf(buf, sizeof(buf), "%" PRIx64, m_inspector->isolateId());
673   *outIsolateId = buf;
674   return Response::Success();
675 }
676 
getHeapUsage(double * out_usedSize,double * out_totalSize)677 Response V8RuntimeAgentImpl::getHeapUsage(double* out_usedSize,
678                                           double* out_totalSize) {
679   v8::HeapStatistics stats;
680   m_inspector->isolate()->GetHeapStatistics(&stats);
681   *out_usedSize = stats.used_heap_size();
682   *out_totalSize = stats.total_heap_size();
683   return Response::Success();
684 }
685 
terminateExecution(std::unique_ptr<TerminateExecutionCallback> callback)686 void V8RuntimeAgentImpl::terminateExecution(
687     std::unique_ptr<TerminateExecutionCallback> callback) {
688   m_inspector->debugger()->terminateExecution(std::move(callback));
689 }
690 
691 namespace {
getOrCreateDictionary(protocol::DictionaryValue * dict,const String16 & key)692 protocol::DictionaryValue* getOrCreateDictionary(
693     protocol::DictionaryValue* dict, const String16& key) {
694   if (protocol::DictionaryValue* bindings = dict->getObject(key))
695     return bindings;
696   dict->setObject(key, protocol::DictionaryValue::create());
697   return dict->getObject(key);
698 }
699 }  // namespace
700 
addBinding(const String16 & name,Maybe<int> executionContextId,Maybe<String16> executionContextName)701 Response V8RuntimeAgentImpl::addBinding(const String16& name,
702                                         Maybe<int> executionContextId,
703                                         Maybe<String16> executionContextName) {
704   if (executionContextId.isJust()) {
705     if (executionContextName.isJust()) {
706       return Response::InvalidParams(
707           "executionContextName is mutually exclusive with executionContextId");
708     }
709     int contextId = executionContextId.fromJust();
710     InspectedContext* context =
711         m_inspector->getContext(m_session->contextGroupId(), contextId);
712     if (!context) {
713       return Response::InvalidParams(
714           "Cannot find execution context with given executionContextId");
715     }
716     addBinding(context, name);
717     return Response::Success();
718   }
719 
720   // If it's a globally exposed binding, i.e. no context name specified, use
721   // a special value for the context name.
722   String16 contextKey = V8RuntimeAgentImplState::globalBindingsKey;
723   if (executionContextName.isJust()) {
724     contextKey = executionContextName.fromJust();
725     if (contextKey == V8RuntimeAgentImplState::globalBindingsKey) {
726       return Response::InvalidParams("Invalid executionContextName");
727     }
728   }
729   // Only persist non context-specific bindings, as contextIds don't make
730   // any sense when state is restored in a different process.
731   protocol::DictionaryValue* bindings =
732       getOrCreateDictionary(m_state, V8RuntimeAgentImplState::bindings);
733   protocol::DictionaryValue* contextBindings =
734       getOrCreateDictionary(bindings, contextKey);
735   contextBindings->setBoolean(name, true);
736 
737   m_inspector->forEachContext(
738       m_session->contextGroupId(),
739       [&name, &executionContextName, this](InspectedContext* context) {
740         if (executionContextName.isJust() &&
741             executionContextName.fromJust() != context->humanReadableName())
742           return;
743         addBinding(context, name);
744       });
745   return Response::Success();
746 }
747 
bindingCallback(const v8::FunctionCallbackInfo<v8::Value> & info)748 void V8RuntimeAgentImpl::bindingCallback(
749     const v8::FunctionCallbackInfo<v8::Value>& info) {
750   v8::Isolate* isolate = info.GetIsolate();
751   if (info.Length() != 1 || !info[0]->IsString()) {
752     info.GetIsolate()->ThrowError(
753         "Invalid arguments: should be exactly one string.");
754     return;
755   }
756   V8InspectorImpl* inspector =
757       static_cast<V8InspectorImpl*>(v8::debug::GetInspector(isolate));
758   int contextId = InspectedContext::contextId(isolate->GetCurrentContext());
759   int contextGroupId = inspector->contextGroupId(contextId);
760 
761   String16 name = toProtocolString(isolate, info.Data().As<v8::String>());
762   String16 payload = toProtocolString(isolate, info[0].As<v8::String>());
763 
764   inspector->forEachSession(
765       contextGroupId,
766       [&name, &payload, &contextId](V8InspectorSessionImpl* session) {
767         session->runtimeAgent()->bindingCalled(name, payload, contextId);
768       });
769 }
770 
addBinding(InspectedContext * context,const String16 & name)771 void V8RuntimeAgentImpl::addBinding(InspectedContext* context,
772                                     const String16& name) {
773   auto it = m_activeBindings.find(name);
774   if (it != m_activeBindings.end() && it->second.count(context->contextId())) {
775     return;
776   }
777   v8::HandleScope handles(m_inspector->isolate());
778   v8::Local<v8::Context> localContext = context->context();
779   v8::Local<v8::Object> global = localContext->Global();
780   v8::Local<v8::String> v8Name = toV8String(m_inspector->isolate(), name);
781   v8::Local<v8::Value> functionValue;
782   v8::MicrotasksScope microtasks(m_inspector->isolate(),
783                                  v8::MicrotasksScope::kDoNotRunMicrotasks);
784   if (v8::Function::New(localContext, bindingCallback, v8Name)
785           .ToLocal(&functionValue)) {
786     v8::Maybe<bool> success = global->Set(localContext, v8Name, functionValue);
787     USE(success);
788     if (it == m_activeBindings.end()) {
789       m_activeBindings.emplace(name,
790                                std::unordered_set<int>(context->contextId()));
791     } else {
792       m_activeBindings.at(name).insert(context->contextId());
793     }
794   }
795 }
796 
removeBinding(const String16 & name)797 Response V8RuntimeAgentImpl::removeBinding(const String16& name) {
798   protocol::DictionaryValue* bindings =
799       m_state->getObject(V8RuntimeAgentImplState::bindings);
800   if (bindings) bindings->remove(name);
801   m_activeBindings.erase(name);
802   return Response::Success();
803 }
804 
bindingCalled(const String16 & name,const String16 & payload,int executionContextId)805 void V8RuntimeAgentImpl::bindingCalled(const String16& name,
806                                        const String16& payload,
807                                        int executionContextId) {
808   if (!m_activeBindings.count(name)) return;
809   m_frontend.bindingCalled(name, payload, executionContextId);
810   m_frontend.flush();
811 }
812 
addBindings(InspectedContext * context)813 void V8RuntimeAgentImpl::addBindings(InspectedContext* context) {
814   const String16 contextName = context->humanReadableName();
815   if (!m_enabled) return;
816   protocol::DictionaryValue* bindings =
817       m_state->getObject(V8RuntimeAgentImplState::bindings);
818   if (!bindings) return;
819   protocol::DictionaryValue* globalBindings =
820       bindings->getObject(V8RuntimeAgentImplState::globalBindingsKey);
821   if (globalBindings) {
822     for (size_t i = 0; i < globalBindings->size(); ++i)
823       addBinding(context, globalBindings->at(i).first);
824   }
825   protocol::DictionaryValue* contextBindings =
826       contextName.isEmpty() ? nullptr : bindings->getObject(contextName);
827   if (contextBindings) {
828     for (size_t i = 0; i < contextBindings->size(); ++i)
829       addBinding(context, contextBindings->at(i).first);
830   }
831 }
832 
restore()833 void V8RuntimeAgentImpl::restore() {
834   if (!m_state->booleanProperty(V8RuntimeAgentImplState::runtimeEnabled, false))
835     return;
836   m_frontend.executionContextsCleared();
837   enable();
838   if (m_state->booleanProperty(
839           V8RuntimeAgentImplState::customObjectFormatterEnabled, false))
840     m_session->setCustomObjectFormatterEnabled(true);
841 
842   m_inspector->forEachContext(
843       m_session->contextGroupId(),
844       [this](InspectedContext* context) { addBindings(context); });
845 }
846 
enable()847 Response V8RuntimeAgentImpl::enable() {
848   if (m_enabled) return Response::Success();
849   m_inspector->client()->beginEnsureAllContextsInGroup(
850       m_session->contextGroupId());
851   m_enabled = true;
852   m_state->setBoolean(V8RuntimeAgentImplState::runtimeEnabled, true);
853   m_inspector->enableStackCapturingIfNeeded();
854   m_session->reportAllContexts(this);
855   V8ConsoleMessageStorage* storage =
856       m_inspector->ensureConsoleMessageStorage(m_session->contextGroupId());
857   for (const auto& message : storage->messages()) {
858     if (!reportMessage(message.get(), false)) break;
859   }
860   return Response::Success();
861 }
862 
disable()863 Response V8RuntimeAgentImpl::disable() {
864   if (!m_enabled) return Response::Success();
865   m_enabled = false;
866   m_state->setBoolean(V8RuntimeAgentImplState::runtimeEnabled, false);
867   m_state->remove(V8RuntimeAgentImplState::bindings);
868   m_inspector->disableStackCapturingIfNeeded();
869   m_session->setCustomObjectFormatterEnabled(false);
870   reset();
871   m_inspector->client()->endEnsureAllContextsInGroup(
872       m_session->contextGroupId());
873   if (m_session->debuggerAgent() && !m_session->debuggerAgent()->enabled()) {
874     m_session->debuggerAgent()->setAsyncCallStackDepth(0);
875   }
876   return Response::Success();
877 }
878 
reset()879 void V8RuntimeAgentImpl::reset() {
880   m_compiledScripts.clear();
881   if (m_enabled) {
882     int sessionId = m_session->sessionId();
883     m_inspector->forEachContext(m_session->contextGroupId(),
884                                 [&sessionId](InspectedContext* context) {
885                                   context->setReported(sessionId, false);
886                                 });
887     m_frontend.executionContextsCleared();
888   }
889 }
890 
reportExecutionContextCreated(InspectedContext * context)891 void V8RuntimeAgentImpl::reportExecutionContextCreated(
892     InspectedContext* context) {
893   if (!m_enabled) return;
894   context->setReported(m_session->sessionId(), true);
895   std::unique_ptr<protocol::Runtime::ExecutionContextDescription> description =
896       protocol::Runtime::ExecutionContextDescription::create()
897           .setId(context->contextId())
898           .setName(context->humanReadableName())
899           .setOrigin(context->origin())
900           .setUniqueId(context->uniqueId().toString())
901           .build();
902   const String16& aux = context->auxData();
903   if (!aux.isEmpty()) {
904     std::vector<uint8_t> cbor;
905     v8_crdtp::json::ConvertJSONToCBOR(
906         v8_crdtp::span<uint16_t>(aux.characters16(), aux.length()), &cbor);
907     description->setAuxData(protocol::DictionaryValue::cast(
908         protocol::Value::parseBinary(cbor.data(), cbor.size())));
909   }
910   m_frontend.executionContextCreated(std::move(description));
911 }
912 
reportExecutionContextDestroyed(InspectedContext * context)913 void V8RuntimeAgentImpl::reportExecutionContextDestroyed(
914     InspectedContext* context) {
915   if (m_enabled && context->isReported(m_session->sessionId())) {
916     context->setReported(m_session->sessionId(), false);
917     m_frontend.executionContextDestroyed(context->contextId());
918   }
919 }
920 
inspect(std::unique_ptr<protocol::Runtime::RemoteObject> objectToInspect,std::unique_ptr<protocol::DictionaryValue> hints,int executionContextId)921 void V8RuntimeAgentImpl::inspect(
922     std::unique_ptr<protocol::Runtime::RemoteObject> objectToInspect,
923     std::unique_ptr<protocol::DictionaryValue> hints, int executionContextId) {
924   if (m_enabled)
925     m_frontend.inspectRequested(std::move(objectToInspect), std::move(hints),
926                                 executionContextId);
927 }
928 
messageAdded(V8ConsoleMessage * message)929 void V8RuntimeAgentImpl::messageAdded(V8ConsoleMessage* message) {
930   if (m_enabled) reportMessage(message, true);
931 }
932 
reportMessage(V8ConsoleMessage * message,bool generatePreview)933 bool V8RuntimeAgentImpl::reportMessage(V8ConsoleMessage* message,
934                                        bool generatePreview) {
935   message->reportToFrontend(&m_frontend, m_session, generatePreview);
936   m_frontend.flush();
937   return m_inspector->hasConsoleMessageStorage(m_session->contextGroupId());
938 }
939 }  // namespace v8_inspector
940