1// Copyright 2015 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "src/inspector/v8-injected-script-host.h"
6
7#include "src/base/macros.h"
8#include "src/inspector/injected-script-native.h"
9#include "src/inspector/string-util.h"
10#include "src/inspector/v8-debugger.h"
11#include "src/inspector/v8-inspector-impl.h"
12#include "src/inspector/v8-internal-value-type.h"
13#include "src/inspector/v8-value-copier.h"
14
15#include "include/v8-inspector.h"
16
17namespace v8_inspector {
18
19namespace {
20
21void setFunctionProperty(v8::Local<v8::Context> context,
22                         v8::Local<v8::Object> obj, const char* name,
23                         v8::FunctionCallback callback,
24                         v8::Local<v8::External> external) {
25  v8::Local<v8::String> funcName =
26      toV8StringInternalized(context->GetIsolate(), name);
27  v8::Local<v8::Function> func;
28  if (!v8::Function::New(context, callback, external, 0,
29                         v8::ConstructorBehavior::kThrow)
30           .ToLocal(&func))
31    return;
32  func->SetName(funcName);
33  createDataProperty(context, obj, funcName, func);
34}
35
36V8InspectorImpl* unwrapInspector(
37    const v8::FunctionCallbackInfo<v8::Value>& info) {
38  DCHECK(!info.Data().IsEmpty());
39  DCHECK(info.Data()->IsExternal());
40  V8InspectorImpl* inspector =
41      static_cast<V8InspectorImpl*>(info.Data().As<v8::External>()->Value());
42  DCHECK(inspector);
43  return inspector;
44}
45
46}  // namespace
47
48v8::Local<v8::Object> V8InjectedScriptHost::create(
49    v8::Local<v8::Context> context, V8InspectorImpl* inspector) {
50  v8::Isolate* isolate = inspector->isolate();
51  v8::Local<v8::Object> injectedScriptHost = v8::Object::New(isolate);
52  bool success = injectedScriptHost->SetPrototype(context, v8::Null(isolate))
53                     .FromMaybe(false);
54  DCHECK(success);
55  USE(success);
56  v8::Local<v8::External> debuggerExternal =
57      v8::External::New(isolate, inspector);
58  setFunctionProperty(context, injectedScriptHost, "nullifyPrototype",
59                      V8InjectedScriptHost::nullifyPrototypeCallback,
60                      debuggerExternal);
61  setFunctionProperty(context, injectedScriptHost, "internalConstructorName",
62                      V8InjectedScriptHost::internalConstructorNameCallback,
63                      debuggerExternal);
64  setFunctionProperty(
65      context, injectedScriptHost, "formatAccessorsAsProperties",
66      V8InjectedScriptHost::formatAccessorsAsProperties, debuggerExternal);
67  setFunctionProperty(context, injectedScriptHost, "subtype",
68                      V8InjectedScriptHost::subtypeCallback, debuggerExternal);
69  setFunctionProperty(context, injectedScriptHost, "getInternalProperties",
70                      V8InjectedScriptHost::getInternalPropertiesCallback,
71                      debuggerExternal);
72  setFunctionProperty(context, injectedScriptHost, "objectHasOwnProperty",
73                      V8InjectedScriptHost::objectHasOwnPropertyCallback,
74                      debuggerExternal);
75  setFunctionProperty(context, injectedScriptHost, "bind",
76                      V8InjectedScriptHost::bindCallback, debuggerExternal);
77  setFunctionProperty(context, injectedScriptHost, "proxyTargetValue",
78                      V8InjectedScriptHost::proxyTargetValueCallback,
79                      debuggerExternal);
80  return injectedScriptHost;
81}
82
83void V8InjectedScriptHost::nullifyPrototypeCallback(
84    const v8::FunctionCallbackInfo<v8::Value>& info) {
85  CHECK(info.Length() == 1 && info[0]->IsObject());
86  v8::Isolate* isolate = info.GetIsolate();
87  info[0]
88      .As<v8::Object>()
89      ->SetPrototype(isolate->GetCurrentContext(), v8::Null(isolate))
90      .ToChecked();
91}
92
93void V8InjectedScriptHost::internalConstructorNameCallback(
94    const v8::FunctionCallbackInfo<v8::Value>& info) {
95  if (info.Length() < 1 || !info[0]->IsObject()) return;
96
97  v8::Local<v8::Object> object = info[0].As<v8::Object>();
98  info.GetReturnValue().Set(object->GetConstructorName());
99}
100
101void V8InjectedScriptHost::formatAccessorsAsProperties(
102    const v8::FunctionCallbackInfo<v8::Value>& info) {
103  DCHECK_EQ(info.Length(), 2);
104  info.GetReturnValue().Set(false);
105  if (!info[1]->IsFunction()) return;
106  // Check that function is user-defined.
107  if (info[1].As<v8::Function>()->ScriptId() != v8::UnboundScript::kNoScriptId)
108    return;
109  info.GetReturnValue().Set(
110      unwrapInspector(info)->client()->formatAccessorsAsProperties(info[0]));
111}
112
113void V8InjectedScriptHost::subtypeCallback(
114    const v8::FunctionCallbackInfo<v8::Value>& info) {
115  if (info.Length() < 1) return;
116
117  v8::Isolate* isolate = info.GetIsolate();
118  v8::Local<v8::Value> value = info[0];
119  if (value->IsObject()) {
120    v8::Local<v8::Value> internalType = v8InternalValueTypeFrom(
121        isolate->GetCurrentContext(), v8::Local<v8::Object>::Cast(value));
122    if (internalType->IsString()) {
123      info.GetReturnValue().Set(internalType);
124      return;
125    }
126  }
127  if (value->IsArray() || value->IsArgumentsObject()) {
128    info.GetReturnValue().Set(toV8StringInternalized(isolate, "array"));
129    return;
130  }
131  if (value->IsTypedArray()) {
132    info.GetReturnValue().Set(toV8StringInternalized(isolate, "typedarray"));
133    return;
134  }
135  if (value->IsDate()) {
136    info.GetReturnValue().Set(toV8StringInternalized(isolate, "date"));
137    return;
138  }
139  if (value->IsRegExp()) {
140    info.GetReturnValue().Set(toV8StringInternalized(isolate, "regexp"));
141    return;
142  }
143  if (value->IsMap() || value->IsWeakMap()) {
144    info.GetReturnValue().Set(toV8StringInternalized(isolate, "map"));
145    return;
146  }
147  if (value->IsSet() || value->IsWeakSet()) {
148    info.GetReturnValue().Set(toV8StringInternalized(isolate, "set"));
149    return;
150  }
151  if (value->IsMapIterator() || value->IsSetIterator()) {
152    info.GetReturnValue().Set(toV8StringInternalized(isolate, "iterator"));
153    return;
154  }
155  if (value->IsGeneratorObject()) {
156    info.GetReturnValue().Set(toV8StringInternalized(isolate, "generator"));
157    return;
158  }
159  if (value->IsNativeError()) {
160    info.GetReturnValue().Set(toV8StringInternalized(isolate, "error"));
161    return;
162  }
163  if (value->IsProxy()) {
164    info.GetReturnValue().Set(toV8StringInternalized(isolate, "proxy"));
165    return;
166  }
167  if (value->IsPromise()) {
168    info.GetReturnValue().Set(toV8StringInternalized(isolate, "promise"));
169    return;
170  }
171  std::unique_ptr<StringBuffer> subtype =
172      unwrapInspector(info)->client()->valueSubtype(value);
173  if (subtype) {
174    info.GetReturnValue().Set(toV8String(isolate, subtype->string()));
175    return;
176  }
177}
178
179void V8InjectedScriptHost::getInternalPropertiesCallback(
180    const v8::FunctionCallbackInfo<v8::Value>& info) {
181  if (info.Length() < 1) return;
182
183  std::unordered_set<String16> allowedProperties;
184  if (info[0]->IsBooleanObject() || info[0]->IsNumberObject() ||
185      info[0]->IsStringObject() || info[0]->IsSymbolObject()) {
186    allowedProperties.insert(String16("[[PrimitiveValue]]"));
187  } else if (info[0]->IsPromise()) {
188    allowedProperties.insert(String16("[[PromiseStatus]]"));
189    allowedProperties.insert(String16("[[PromiseValue]]"));
190  } else if (info[0]->IsGeneratorObject()) {
191    allowedProperties.insert(String16("[[GeneratorStatus]]"));
192  } else if (info[0]->IsMapIterator() || info[0]->IsSetIterator()) {
193    allowedProperties.insert(String16("[[IteratorHasMore]]"));
194    allowedProperties.insert(String16("[[IteratorIndex]]"));
195    allowedProperties.insert(String16("[[IteratorKind]]"));
196    allowedProperties.insert(String16("[[Entries]]"));
197  } else if (info[0]->IsMap() || info[0]->IsWeakMap() || info[0]->IsSet() ||
198             info[0]->IsWeakSet()) {
199    allowedProperties.insert(String16("[[Entries]]"));
200  }
201  if (!allowedProperties.size()) return;
202
203  v8::Isolate* isolate = info.GetIsolate();
204  v8::Local<v8::Array> allProperties;
205  if (!unwrapInspector(info)
206           ->debugger()
207           ->internalProperties(isolate->GetCurrentContext(), info[0])
208           .ToLocal(&allProperties) ||
209      !allProperties->IsArray() || allProperties->Length() % 2 != 0)
210    return;
211
212  {
213    v8::Local<v8::Context> context = isolate->GetCurrentContext();
214    v8::TryCatch tryCatch(isolate);
215    v8::Isolate::DisallowJavascriptExecutionScope throwJs(
216        isolate,
217        v8::Isolate::DisallowJavascriptExecutionScope::THROW_ON_FAILURE);
218
219    v8::Local<v8::Array> properties = v8::Array::New(isolate);
220    if (tryCatch.HasCaught()) return;
221
222    uint32_t outputIndex = 0;
223    for (uint32_t i = 0; i < allProperties->Length(); i += 2) {
224      v8::Local<v8::Value> key;
225      if (!allProperties->Get(context, i).ToLocal(&key)) continue;
226      if (tryCatch.HasCaught()) {
227        tryCatch.Reset();
228        continue;
229      }
230      String16 keyString = toProtocolStringWithTypeCheck(key);
231      if (keyString.isEmpty() ||
232          allowedProperties.find(keyString) == allowedProperties.end())
233        continue;
234      v8::Local<v8::Value> value;
235      if (!allProperties->Get(context, i + 1).ToLocal(&value)) continue;
236      if (tryCatch.HasCaught()) {
237        tryCatch.Reset();
238        continue;
239      }
240      createDataProperty(context, properties, outputIndex++, key);
241      createDataProperty(context, properties, outputIndex++, value);
242    }
243    info.GetReturnValue().Set(properties);
244  }
245}
246
247void V8InjectedScriptHost::objectHasOwnPropertyCallback(
248    const v8::FunctionCallbackInfo<v8::Value>& info) {
249  if (info.Length() < 2 || !info[0]->IsObject() || !info[1]->IsString()) return;
250  bool result = info[0]
251                    .As<v8::Object>()
252                    ->HasOwnProperty(info.GetIsolate()->GetCurrentContext(),
253                                     v8::Local<v8::String>::Cast(info[1]))
254                    .FromMaybe(false);
255  info.GetReturnValue().Set(v8::Boolean::New(info.GetIsolate(), result));
256}
257
258void V8InjectedScriptHost::bindCallback(
259    const v8::FunctionCallbackInfo<v8::Value>& info) {
260  if (info.Length() < 2 || !info[1]->IsString()) return;
261  InjectedScriptNative* injectedScriptNative =
262      InjectedScriptNative::fromInjectedScriptHost(info.GetIsolate(),
263                                                   info.Holder());
264  if (!injectedScriptNative) return;
265
266  v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
267  v8::Local<v8::String> v8groupName =
268      info[1]->ToString(context).ToLocalChecked();
269  String16 groupName = toProtocolStringWithTypeCheck(v8groupName);
270  int id = injectedScriptNative->bind(info[0], groupName);
271  info.GetReturnValue().Set(id);
272}
273
274void V8InjectedScriptHost::proxyTargetValueCallback(
275    const v8::FunctionCallbackInfo<v8::Value>& info) {
276  if (info.Length() != 1 || !info[0]->IsProxy()) {
277    UNREACHABLE();
278    return;
279  }
280  v8::Local<v8::Object> target = info[0].As<v8::Proxy>();
281  while (target->IsProxy())
282    target = v8::Local<v8::Proxy>::Cast(target)->GetTarget();
283  info.GetReturnValue().Set(target);
284}
285
286}  // namespace v8_inspector
287