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/debug/debug-interface.h"
9 #include "src/inspector/injected-script.h"
10 #include "src/inspector/string-util.h"
11 #include "src/inspector/v8-debugger.h"
12 #include "src/inspector/v8-inspector-impl.h"
13 #include "src/inspector/v8-internal-value-type.h"
14 #include "src/inspector/v8-value-utils.h"
15
16 #include "include/v8-inspector.h"
17
18 namespace v8_inspector {
19
20 namespace {
21
setFunctionProperty(v8::Local<v8::Context> context,v8::Local<v8::Object> obj,const char * name,v8::FunctionCallback callback,v8::Local<v8::External> external)22 void setFunctionProperty(v8::Local<v8::Context> context,
23 v8::Local<v8::Object> obj, const char* name,
24 v8::FunctionCallback callback,
25 v8::Local<v8::External> external) {
26 v8::Local<v8::String> funcName =
27 toV8StringInternalized(context->GetIsolate(), name);
28 v8::Local<v8::Function> func;
29 if (!v8::Function::New(context, callback, external, 0,
30 v8::ConstructorBehavior::kThrow)
31 .ToLocal(&func))
32 return;
33 func->SetName(funcName);
34 createDataProperty(context, obj, funcName, func);
35 }
36
unwrapInspector(const v8::FunctionCallbackInfo<v8::Value> & info)37 V8InspectorImpl* unwrapInspector(
38 const v8::FunctionCallbackInfo<v8::Value>& info) {
39 DCHECK(!info.Data().IsEmpty());
40 DCHECK(info.Data()->IsExternal());
41 V8InspectorImpl* inspector =
42 static_cast<V8InspectorImpl*>(info.Data().As<v8::External>()->Value());
43 DCHECK(inspector);
44 return inspector;
45 }
46
47 template <typename TypedArray>
addTypedArrayProperty(std::vector<v8::Local<v8::Value>> * props,v8::Isolate * isolate,v8::Local<v8::ArrayBuffer> arraybuffer,String16 name,size_t length)48 void addTypedArrayProperty(std::vector<v8::Local<v8::Value>>* props,
49 v8::Isolate* isolate,
50 v8::Local<v8::ArrayBuffer> arraybuffer,
51 String16 name, size_t length) {
52 props->push_back(toV8String(isolate, name));
53 props->push_back(TypedArray::New(arraybuffer, 0, length));
54 }
55
56 } // namespace
57
create(v8::Local<v8::Context> context,V8InspectorImpl * inspector)58 v8::Local<v8::Object> V8InjectedScriptHost::create(
59 v8::Local<v8::Context> context, V8InspectorImpl* inspector) {
60 v8::Isolate* isolate = inspector->isolate();
61 v8::Local<v8::Object> injectedScriptHost = v8::Object::New(isolate);
62 bool success = injectedScriptHost->SetPrototype(context, v8::Null(isolate))
63 .FromMaybe(false);
64 DCHECK(success);
65 USE(success);
66 v8::Local<v8::External> debuggerExternal =
67 v8::External::New(isolate, inspector);
68 setFunctionProperty(context, injectedScriptHost, "nullifyPrototype",
69 V8InjectedScriptHost::nullifyPrototypeCallback,
70 debuggerExternal);
71 setFunctionProperty(context, injectedScriptHost, "getProperty",
72 V8InjectedScriptHost::getPropertyCallback,
73 debuggerExternal);
74 setFunctionProperty(context, injectedScriptHost, "internalConstructorName",
75 V8InjectedScriptHost::internalConstructorNameCallback,
76 debuggerExternal);
77 setFunctionProperty(
78 context, injectedScriptHost, "formatAccessorsAsProperties",
79 V8InjectedScriptHost::formatAccessorsAsProperties, debuggerExternal);
80 setFunctionProperty(context, injectedScriptHost, "subtype",
81 V8InjectedScriptHost::subtypeCallback, debuggerExternal);
82 setFunctionProperty(context, injectedScriptHost, "getInternalProperties",
83 V8InjectedScriptHost::getInternalPropertiesCallback,
84 debuggerExternal);
85 setFunctionProperty(context, injectedScriptHost, "objectHasOwnProperty",
86 V8InjectedScriptHost::objectHasOwnPropertyCallback,
87 debuggerExternal);
88 setFunctionProperty(context, injectedScriptHost, "bind",
89 V8InjectedScriptHost::bindCallback, debuggerExternal);
90 setFunctionProperty(context, injectedScriptHost, "proxyTargetValue",
91 V8InjectedScriptHost::proxyTargetValueCallback,
92 debuggerExternal);
93 setFunctionProperty(context, injectedScriptHost, "nativeAccessorDescriptor",
94 V8InjectedScriptHost::nativeAccessorDescriptorCallback,
95 debuggerExternal);
96 setFunctionProperty(context, injectedScriptHost, "typedArrayProperties",
97 V8InjectedScriptHost::typedArrayPropertiesCallback,
98 debuggerExternal);
99 createDataProperty(context, injectedScriptHost,
100 toV8StringInternalized(isolate, "keys"),
101 v8::debug::GetBuiltin(isolate, v8::debug::kObjectKeys));
102 createDataProperty(
103 context, injectedScriptHost,
104 toV8StringInternalized(isolate, "getPrototypeOf"),
105 v8::debug::GetBuiltin(isolate, v8::debug::kObjectGetPrototypeOf));
106 createDataProperty(
107 context, injectedScriptHost,
108 toV8StringInternalized(isolate, "getOwnPropertyDescriptor"),
109 v8::debug::GetBuiltin(isolate,
110 v8::debug::kObjectGetOwnPropertyDescriptor));
111 createDataProperty(
112 context, injectedScriptHost,
113 toV8StringInternalized(isolate, "getOwnPropertyNames"),
114 v8::debug::GetBuiltin(isolate, v8::debug::kObjectGetOwnPropertyNames));
115 createDataProperty(
116 context, injectedScriptHost,
117 toV8StringInternalized(isolate, "getOwnPropertySymbols"),
118 v8::debug::GetBuiltin(isolate, v8::debug::kObjectGetOwnPropertySymbols));
119 return injectedScriptHost;
120 }
121
nullifyPrototypeCallback(const v8::FunctionCallbackInfo<v8::Value> & info)122 void V8InjectedScriptHost::nullifyPrototypeCallback(
123 const v8::FunctionCallbackInfo<v8::Value>& info) {
124 CHECK_EQ(1, info.Length());
125 DCHECK(info[0]->IsObject());
126 if (!info[0]->IsObject()) return;
127 v8::Isolate* isolate = info.GetIsolate();
128 info[0]
129 .As<v8::Object>()
130 ->SetPrototype(isolate->GetCurrentContext(), v8::Null(isolate))
131 .ToChecked();
132 }
133
getPropertyCallback(const v8::FunctionCallbackInfo<v8::Value> & info)134 void V8InjectedScriptHost::getPropertyCallback(
135 const v8::FunctionCallbackInfo<v8::Value>& info) {
136 CHECK(info.Length() == 2 && info[1]->IsString());
137 if (!info[0]->IsObject()) return;
138 v8::Isolate* isolate = info.GetIsolate();
139 v8::Local<v8::Context> context = isolate->GetCurrentContext();
140 v8::TryCatch tryCatch(isolate);
141 v8::Isolate::DisallowJavascriptExecutionScope throwJs(
142 isolate, v8::Isolate::DisallowJavascriptExecutionScope::THROW_ON_FAILURE);
143 v8::Local<v8::Value> property;
144 if (info[0]
145 .As<v8::Object>()
146 ->Get(context, v8::Local<v8::String>::Cast(info[1]))
147 .ToLocal(&property)) {
148 info.GetReturnValue().Set(property);
149 }
150 }
151
internalConstructorNameCallback(const v8::FunctionCallbackInfo<v8::Value> & info)152 void V8InjectedScriptHost::internalConstructorNameCallback(
153 const v8::FunctionCallbackInfo<v8::Value>& info) {
154 if (info.Length() < 1 || !info[0]->IsObject()) return;
155
156 v8::Local<v8::Object> object = info[0].As<v8::Object>();
157 info.GetReturnValue().Set(object->GetConstructorName());
158 }
159
formatAccessorsAsProperties(const v8::FunctionCallbackInfo<v8::Value> & info)160 void V8InjectedScriptHost::formatAccessorsAsProperties(
161 const v8::FunctionCallbackInfo<v8::Value>& info) {
162 DCHECK_EQ(info.Length(), 2);
163 info.GetReturnValue().Set(false);
164 if (!info[1]->IsFunction()) return;
165 // Check that function is user-defined.
166 if (info[1].As<v8::Function>()->ScriptId() != v8::UnboundScript::kNoScriptId)
167 return;
168 info.GetReturnValue().Set(
169 unwrapInspector(info)->client()->formatAccessorsAsProperties(info[0]));
170 }
171
subtypeCallback(const v8::FunctionCallbackInfo<v8::Value> & info)172 void V8InjectedScriptHost::subtypeCallback(
173 const v8::FunctionCallbackInfo<v8::Value>& info) {
174 if (info.Length() < 1) return;
175
176 v8::Isolate* isolate = info.GetIsolate();
177 v8::Local<v8::Value> value = info[0];
178 if (value->IsObject()) {
179 v8::Local<v8::Value> internalType = v8InternalValueTypeFrom(
180 isolate->GetCurrentContext(), v8::Local<v8::Object>::Cast(value));
181 if (internalType->IsString()) {
182 info.GetReturnValue().Set(internalType);
183 return;
184 }
185 }
186 if (value->IsArray() || value->IsArgumentsObject()) {
187 info.GetReturnValue().Set(toV8StringInternalized(isolate, "array"));
188 return;
189 }
190 if (value->IsTypedArray()) {
191 info.GetReturnValue().Set(toV8StringInternalized(isolate, "typedarray"));
192 return;
193 }
194 if (value->IsDate()) {
195 info.GetReturnValue().Set(toV8StringInternalized(isolate, "date"));
196 return;
197 }
198 if (value->IsRegExp()) {
199 info.GetReturnValue().Set(toV8StringInternalized(isolate, "regexp"));
200 return;
201 }
202 if (value->IsMap()) {
203 info.GetReturnValue().Set(toV8StringInternalized(isolate, "map"));
204 return;
205 }
206 if (value->IsWeakMap()) {
207 info.GetReturnValue().Set(toV8StringInternalized(isolate, "weakmap"));
208 return;
209 }
210 if (value->IsSet()) {
211 info.GetReturnValue().Set(toV8StringInternalized(isolate, "set"));
212 return;
213 }
214 if (value->IsWeakSet()) {
215 info.GetReturnValue().Set(toV8StringInternalized(isolate, "weakset"));
216 return;
217 }
218 if (value->IsMapIterator() || value->IsSetIterator()) {
219 info.GetReturnValue().Set(toV8StringInternalized(isolate, "iterator"));
220 return;
221 }
222 if (value->IsGeneratorObject()) {
223 info.GetReturnValue().Set(toV8StringInternalized(isolate, "generator"));
224 return;
225 }
226 if (value->IsNativeError()) {
227 info.GetReturnValue().Set(toV8StringInternalized(isolate, "error"));
228 return;
229 }
230 if (value->IsProxy()) {
231 info.GetReturnValue().Set(toV8StringInternalized(isolate, "proxy"));
232 return;
233 }
234 if (value->IsPromise()) {
235 info.GetReturnValue().Set(toV8StringInternalized(isolate, "promise"));
236 return;
237 }
238 if (value->IsArrayBuffer() || value->IsSharedArrayBuffer()) {
239 info.GetReturnValue().Set(toV8StringInternalized(isolate, "arraybuffer"));
240 return;
241 }
242 if (value->IsDataView()) {
243 info.GetReturnValue().Set(toV8StringInternalized(isolate, "dataview"));
244 return;
245 }
246 std::unique_ptr<StringBuffer> subtype =
247 unwrapInspector(info)->client()->valueSubtype(value);
248 if (subtype) {
249 info.GetReturnValue().Set(toV8String(isolate, subtype->string()));
250 return;
251 }
252 }
253
getInternalPropertiesCallback(const v8::FunctionCallbackInfo<v8::Value> & info)254 void V8InjectedScriptHost::getInternalPropertiesCallback(
255 const v8::FunctionCallbackInfo<v8::Value>& info) {
256 if (info.Length() < 1) return;
257
258 std::unordered_set<String16> allowedProperties;
259 if (info[0]->IsBooleanObject() || info[0]->IsNumberObject() ||
260 info[0]->IsStringObject() || info[0]->IsSymbolObject() ||
261 info[0]->IsBigIntObject()) {
262 allowedProperties.insert(String16("[[PrimitiveValue]]"));
263 } else if (info[0]->IsPromise()) {
264 allowedProperties.insert(String16("[[PromiseStatus]]"));
265 allowedProperties.insert(String16("[[PromiseValue]]"));
266 } else if (info[0]->IsGeneratorObject()) {
267 allowedProperties.insert(String16("[[GeneratorStatus]]"));
268 } else if (info[0]->IsMap() || info[0]->IsWeakMap() || info[0]->IsSet() ||
269 info[0]->IsWeakSet() || info[0]->IsMapIterator() ||
270 info[0]->IsSetIterator()) {
271 allowedProperties.insert(String16("[[Entries]]"));
272 }
273 if (!allowedProperties.size()) return;
274
275 v8::Isolate* isolate = info.GetIsolate();
276 v8::Local<v8::Array> allProperties;
277 if (!unwrapInspector(info)
278 ->debugger()
279 ->internalProperties(isolate->GetCurrentContext(), info[0])
280 .ToLocal(&allProperties) ||
281 !allProperties->IsArray() || allProperties->Length() % 2 != 0)
282 return;
283
284 {
285 v8::Local<v8::Context> context = isolate->GetCurrentContext();
286 v8::TryCatch tryCatch(isolate);
287 v8::Isolate::DisallowJavascriptExecutionScope throwJs(
288 isolate,
289 v8::Isolate::DisallowJavascriptExecutionScope::THROW_ON_FAILURE);
290
291 v8::Local<v8::Array> properties = v8::Array::New(isolate);
292 if (tryCatch.HasCaught()) return;
293
294 uint32_t outputIndex = 0;
295 for (uint32_t i = 0; i < allProperties->Length(); i += 2) {
296 v8::Local<v8::Value> key;
297 if (!allProperties->Get(context, i).ToLocal(&key)) continue;
298 if (tryCatch.HasCaught()) {
299 tryCatch.Reset();
300 continue;
301 }
302 String16 keyString = toProtocolStringWithTypeCheck(isolate, key);
303 if (keyString.isEmpty() ||
304 allowedProperties.find(keyString) == allowedProperties.end())
305 continue;
306 v8::Local<v8::Value> value;
307 if (!allProperties->Get(context, i + 1).ToLocal(&value)) continue;
308 if (tryCatch.HasCaught()) {
309 tryCatch.Reset();
310 continue;
311 }
312 createDataProperty(context, properties, outputIndex++, key);
313 createDataProperty(context, properties, outputIndex++, value);
314 }
315 info.GetReturnValue().Set(properties);
316 }
317 }
318
objectHasOwnPropertyCallback(const v8::FunctionCallbackInfo<v8::Value> & info)319 void V8InjectedScriptHost::objectHasOwnPropertyCallback(
320 const v8::FunctionCallbackInfo<v8::Value>& info) {
321 if (info.Length() < 2 || !info[0]->IsObject() || !info[1]->IsString()) return;
322 bool result = info[0]
323 .As<v8::Object>()
324 ->HasOwnProperty(info.GetIsolate()->GetCurrentContext(),
325 v8::Local<v8::String>::Cast(info[1]))
326 .FromMaybe(false);
327 info.GetReturnValue().Set(v8::Boolean::New(info.GetIsolate(), result));
328 }
329
bindCallback(const v8::FunctionCallbackInfo<v8::Value> & info)330 void V8InjectedScriptHost::bindCallback(
331 const v8::FunctionCallbackInfo<v8::Value>& info) {
332 if (info.Length() < 2 || !info[1]->IsString()) return;
333 InjectedScript* injectedScript =
334 InjectedScript::fromInjectedScriptHost(info.GetIsolate(), info.Holder());
335 if (!injectedScript) return;
336
337 v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
338 v8::Local<v8::String> v8groupName =
339 info[1]->ToString(context).ToLocalChecked();
340 String16 groupName =
341 toProtocolStringWithTypeCheck(info.GetIsolate(), v8groupName);
342 int id = injectedScript->bindObject(info[0], groupName);
343 info.GetReturnValue().Set(id);
344 }
345
proxyTargetValueCallback(const v8::FunctionCallbackInfo<v8::Value> & info)346 void V8InjectedScriptHost::proxyTargetValueCallback(
347 const v8::FunctionCallbackInfo<v8::Value>& info) {
348 if (info.Length() != 1 || !info[0]->IsProxy()) {
349 UNREACHABLE();
350 return;
351 }
352 v8::Local<v8::Value> target = info[0].As<v8::Proxy>();
353 while (target->IsProxy())
354 target = v8::Local<v8::Proxy>::Cast(target)->GetTarget();
355 info.GetReturnValue().Set(target);
356 }
357
nativeAccessorDescriptorCallback(const v8::FunctionCallbackInfo<v8::Value> & info)358 void V8InjectedScriptHost::nativeAccessorDescriptorCallback(
359 const v8::FunctionCallbackInfo<v8::Value>& info) {
360 v8::Isolate* isolate = info.GetIsolate();
361 if (info.Length() != 2 || !info[0]->IsObject() || !info[1]->IsName()) {
362 info.GetReturnValue().Set(v8::Undefined(isolate));
363 return;
364 }
365 v8::Local<v8::Context> context = isolate->GetCurrentContext();
366 int flags = v8::debug::GetNativeAccessorDescriptor(
367 context, v8::Local<v8::Object>::Cast(info[0]),
368 v8::Local<v8::Name>::Cast(info[1]));
369 if (flags == static_cast<int>(v8::debug::NativeAccessorType::None)) {
370 info.GetReturnValue().Set(v8::Undefined(isolate));
371 return;
372 }
373
374 bool isBuiltin =
375 flags & static_cast<int>(v8::debug::NativeAccessorType::IsBuiltin);
376 bool hasGetter =
377 flags & static_cast<int>(v8::debug::NativeAccessorType::HasGetter);
378 bool hasSetter =
379 flags & static_cast<int>(v8::debug::NativeAccessorType::HasSetter);
380 v8::Local<v8::Object> result = v8::Object::New(isolate);
381 result->SetPrototype(context, v8::Null(isolate)).ToChecked();
382 createDataProperty(context, result, toV8String(isolate, "isBuiltin"),
383 v8::Boolean::New(isolate, isBuiltin));
384 createDataProperty(context, result, toV8String(isolate, "hasGetter"),
385 v8::Boolean::New(isolate, hasGetter));
386 createDataProperty(context, result, toV8String(isolate, "hasSetter"),
387 v8::Boolean::New(isolate, hasSetter));
388 info.GetReturnValue().Set(result);
389 }
390
typedArrayPropertiesCallback(const v8::FunctionCallbackInfo<v8::Value> & info)391 void V8InjectedScriptHost::typedArrayPropertiesCallback(
392 const v8::FunctionCallbackInfo<v8::Value>& info) {
393 v8::Isolate* isolate = info.GetIsolate();
394 if (info.Length() != 1 || !info[0]->IsArrayBuffer()) return;
395
396 v8::TryCatch tryCatch(isolate);
397 v8::Isolate::DisallowJavascriptExecutionScope throwJs(
398 isolate, v8::Isolate::DisallowJavascriptExecutionScope::THROW_ON_FAILURE);
399 v8::Local<v8::ArrayBuffer> arrayBuffer = info[0].As<v8::ArrayBuffer>();
400 size_t length = arrayBuffer->ByteLength();
401 if (length == 0) return;
402 std::vector<v8::Local<v8::Value>> arrays_vector;
403 addTypedArrayProperty<v8::Int8Array>(&arrays_vector, isolate, arrayBuffer,
404 "[[Int8Array]]", length);
405 addTypedArrayProperty<v8::Uint8Array>(&arrays_vector, isolate, arrayBuffer,
406 "[[Uint8Array]]", length);
407
408 if (length % 2 == 0) {
409 addTypedArrayProperty<v8::Int16Array>(&arrays_vector, isolate, arrayBuffer,
410 "[[Int16Array]]", length / 2);
411 }
412 if (length % 4 == 0) {
413 addTypedArrayProperty<v8::Int32Array>(&arrays_vector, isolate, arrayBuffer,
414 "[[Int32Array]]", length / 4);
415 }
416
417 if (tryCatch.HasCaught()) return;
418 v8::Local<v8::Context> context = isolate->GetCurrentContext();
419 v8::Local<v8::Array> arrays =
420 v8::Array::New(isolate, static_cast<uint32_t>(arrays_vector.size()));
421 for (uint32_t i = 0; i < static_cast<uint32_t>(arrays_vector.size()); i++)
422 createDataProperty(context, arrays, i, arrays_vector[i]);
423 if (tryCatch.HasCaught()) return;
424 info.GetReturnValue().Set(arrays);
425 }
426
427 } // namespace v8_inspector
428