Commit bf505e60 authored by Erik Luo's avatar Erik Luo Committed by Commit Bot

[inspector] support BigInt in inspector

- Label as "bigint" in DevTools heap snapshot viewer
- Treat as new primitive in injected-script-source
- Show primitive value as property for BigIntObject
- Adds the "n" suffix onto description, both with/without inspector
  being present

Bug: v8:7486
Cq-Include-Trybots: luci.chromium.try:linux_chromium_rel_ng;master.tryserver.blink:linux_trusty_blink_rel;master.tryserver.chromium.linux:linux_chromium_rel_ng
Change-Id: I47a02e32f9bdd9124a6c91056965574ecd443867
Reviewed-on: https://chromium-review.googlesource.com/940804
Commit-Queue: Erik Luo <luoe@chromium.org>
Reviewed-by: 's avatarAdam Klein <adamk@chromium.org>
Reviewed-by: 's avatarAlexei Filippov <alph@chromium.org>
Reviewed-by: 's avatarDmitry Gozman <dgozman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51855}
parent 87956c74
......@@ -402,7 +402,8 @@ class V8_EXPORT HeapGraphNode {
// snapshot items together.
kConsString = 10, // Concatenated string. A pair of pointers to strings.
kSlicedString = 11, // Sliced string. A fragment of another string.
kSymbol = 12 // A Symbol (ES6).
kSymbol = 12, // A Symbol (ES6).
kBigInt = 13 // BigInt.
};
/** Returns node type (see HeapGraphNode::Type). */
......
......@@ -66,6 +66,8 @@ function toString(obj)
}
/**
* TODO(luoe): remove type-check suppression once bigint is supported by closure.
* @suppress {checkTypes}
* @param {*} obj
* @return {string}
*/
......@@ -73,6 +75,8 @@ function toStringDescription(obj)
{
if (typeof obj === "number" && obj === 0 && 1 / obj < 0)
return "-0"; // Negative zero.
if (typeof obj === "bigint")
return toString(obj) + "n";
return toString(obj);
}
......@@ -167,6 +171,7 @@ InjectedScript.primitiveTypes = {
"boolean": true,
"number": true,
"string": true,
"bigint": true,
__proto__: null
}
......@@ -747,6 +752,13 @@ InjectedScript.RemoteObject = function(object, objectGroupName, doNotBind, force
}
}
// The "n" suffix of bigint primitives are not JSON serializable.
if (this.type === "bigint") {
delete this.value;
this.description = toStringDescription(object);
this.unserializableValue = this.description;
}
return;
}
......@@ -1002,9 +1014,10 @@ InjectedScript.RemoteObject.prototype = {
var maxLength = 100;
if (InjectedScript.primitiveTypes[type]) {
if (type === "string" && value.length > maxLength)
value = this._abbreviateString(value, maxLength, true);
push(preview.properties, { name: name, type: type, value: toStringDescription(value), __proto__: null });
var valueString = type === "string" ? value : toStringDescription(value);
if (valueString.length > maxLength)
valueString = this._abbreviateString(valueString, maxLength, true);
push(preview.properties, { name: name, type: type, value: valueString, __proto__: null });
continue;
}
......
......@@ -50,6 +50,9 @@ namespace v8_inspector {
namespace {
static const char privateKeyName[] = "v8-inspector#injectedScript";
static const char kGlobalHandleLabel[] = "DevTools console";
static bool isResolvableNumberLike(String16 query) {
return query == "Infinity" || query == "-Infinity" || query == "NaN";
}
} // namespace
using protocol::Array;
......@@ -530,10 +533,17 @@ Response InjectedScript::resolveCallArgument(
return findObject(*remoteObjectId, result);
}
if (callArgument->hasValue() || callArgument->hasUnserializableValue()) {
String16 value =
callArgument->hasValue()
? "(" + callArgument->getValue(nullptr)->serialize() + ")"
: "Number(\"" + callArgument->getUnserializableValue("") + "\")";
String16 value;
if (callArgument->hasValue()) {
value = "(" + callArgument->getValue(nullptr)->serialize() + ")";
} else {
String16 unserializableValue = callArgument->getUnserializableValue("");
// Protect against potential identifier resolution for NaN and Infinity.
if (isResolvableNumberLike(unserializableValue))
value = "Number(\"" + unserializableValue + "\")";
else
value = unserializableValue;
}
if (!m_context->inspector()
->compileAndRunInternalScript(
m_context->context(), toV8String(m_context->isolate(), value))
......
......@@ -1781,14 +1781,8 @@
},
{
"id": "UnserializableValue",
"description": "Primitive value which cannot be JSON-stringified.",
"type": "string",
"enum": [
"Infinity",
"NaN",
"-Infinity",
"-0"
]
"description": "Primitive value which cannot be JSON-stringified. Includes values `-0`, `NaN`, `Infinity`,\n`-Infinity`, and bigint literals.",
"type": "string"
},
{
"id": "RemoteObject",
......@@ -1806,7 +1800,8 @@
"string",
"number",
"boolean",
"symbol"
"symbol",
"bigint"
]
},
{
......@@ -1922,7 +1917,8 @@
"string",
"number",
"boolean",
"symbol"
"symbol",
"bigint"
]
},
{
......@@ -1997,7 +1993,8 @@
"number",
"boolean",
"symbol",
"accessor"
"accessor",
"bigint"
]
},
{
......
......@@ -823,13 +823,9 @@ domain Runtime
# Unique object identifier.
type RemoteObjectId extends string
# Primitive value which cannot be JSON-stringified.
# Primitive value which cannot be JSON-stringified. Includes values `-0`, `NaN`, `Infinity`,
# `-Infinity`, and bigint literals.
type UnserializableValue extends string
enum
Infinity
NaN
-Infinity
-0
# Mirror object referencing original JavaScript object.
type RemoteObject extends object
......@@ -843,6 +839,7 @@ domain Runtime
number
boolean
symbol
bigint
# Object subtype hint. Specified for `object` type values only.
optional enum subtype
array
......@@ -895,6 +892,7 @@ domain Runtime
number
boolean
symbol
bigint
# Object subtype hint. Specified for `object` type values only.
optional enum subtype
array
......@@ -932,6 +930,7 @@ domain Runtime
boolean
symbol
accessor
bigint
# User-friendly property value string.
optional string value
# Nested value preview.
......
......@@ -92,6 +92,9 @@ class V8ValueStringBuilder {
if (value->IsString()) return append(v8::Local<v8::String>::Cast(value));
if (value->IsStringObject())
return append(v8::Local<v8::StringObject>::Cast(value)->ValueOf());
if (value->IsBigInt()) return append(v8::Local<v8::BigInt>::Cast(value));
if (value->IsBigIntObject())
return append(v8::Local<v8::BigIntObject>::Cast(value)->ValueOf());
if (value->IsSymbol()) return append(v8::Local<v8::Symbol>::Cast(value));
if (value->IsSymbolObject())
return append(v8::Local<v8::SymbolObject>::Cast(value)->ValueOf());
......@@ -156,6 +159,13 @@ class V8ValueStringBuilder {
return result;
}
bool append(v8::Local<v8::BigInt> bigint) {
bool result = append(bigint->ToString());
if (m_tryCatch.HasCaught()) return false;
m_builder.append('n');
return result;
}
bool append(v8::Local<v8::String> string) {
if (m_tryCatch.HasCaught()) return false;
if (!string.IsEmpty()) m_builder.append(toProtocolString(string));
......
......@@ -257,7 +257,8 @@ void V8InjectedScriptHost::getInternalPropertiesCallback(
std::unordered_set<String16> allowedProperties;
if (info[0]->IsBooleanObject() || info[0]->IsNumberObject() ||
info[0]->IsStringObject() || info[0]->IsSymbolObject()) {
info[0]->IsStringObject() || info[0]->IsSymbolObject() ||
info[0]->IsBigIntObject()) {
allowedProperties.insert(String16("[[PrimitiveValue]]"));
} else if (info[0]->IsPromise()) {
allowedProperties.insert(String16("[[PromiseStatus]]"));
......
......@@ -161,6 +161,8 @@ const char* HeapEntry::TypeAsString() {
case kConsString: return "/concatenated string/";
case kSlicedString: return "/sliced string/";
case kSymbol: return "/symbol/";
case kBigInt:
return "/bigint/";
default: return "???";
}
}
......@@ -639,6 +641,8 @@ HeapEntry* V8HeapExplorer::AddEntry(HeapObject* object) {
return AddEntry(object, HeapEntry::kHidden, "private symbol");
else
return AddEntry(object, HeapEntry::kSymbol, "symbol");
} else if (object->IsBigInt()) {
return AddEntry(object, HeapEntry::kBigInt, "bigint");
} else if (object->IsCode()) {
return AddEntry(object, HeapEntry::kCode, "");
} else if (object->IsSharedFunctionInfo()) {
......@@ -2813,7 +2817,8 @@ void HeapSnapshotJSONSerializer::SerializeSnapshot() {
JSON_S("synthetic") ","
JSON_S("concatenated string") ","
JSON_S("sliced string") ","
JSON_S("symbol")) ","
JSON_S("symbol") ","
JSON_S("bigint")) ","
JSON_S("string") ","
JSON_S("number") ","
JSON_S("number") ","
......
......@@ -99,7 +99,8 @@ class HeapEntry BASE_EMBEDDED {
kSynthetic = v8::HeapGraphNode::kSynthetic,
kConsString = v8::HeapGraphNode::kConsString,
kSlicedString = v8::HeapGraphNode::kSlicedString,
kSymbol = v8::HeapGraphNode::kSymbol
kSymbol = v8::HeapGraphNode::kSymbol,
kBigInt = v8::HeapGraphNode::kBigInt
};
static const int kNoEntry;
......
......@@ -431,6 +431,28 @@ TEST(HeapSnapshotHeapNumbers) {
CHECK_EQ(v8::HeapGraphNode::kHeapNumber, b->GetType());
}
TEST(HeapSnapshotHeapBigInts) {
// TODO(luoe): remove flag when it is on by default.
v8::internal::FLAG_harmony_bigint = true;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
v8::HeapProfiler* heap_profiler = env->GetIsolate()->GetHeapProfiler();
CompileRun(
"a = 1n;"
"b = Object(BigInt(2))");
const v8::HeapSnapshot* snapshot = heap_profiler->TakeHeapSnapshot();
CHECK(ValidateSnapshot(snapshot));
const v8::HeapGraphNode* global = GetGlobalObject(snapshot);
const v8::HeapGraphNode* a =
GetProperty(env->GetIsolate(), global, v8::HeapGraphEdge::kProperty, "a");
CHECK(a);
CHECK_EQ(v8::HeapGraphNode::kBigInt, a->GetType());
const v8::HeapGraphNode* b =
GetProperty(env->GetIsolate(), global, v8::HeapGraphEdge::kProperty, "b");
CHECK(b);
CHECK_EQ(v8::HeapGraphNode::kObject, b->GetType());
}
TEST(HeapSnapshotSlicedString) {
if (!i::FLAG_string_slices) return;
......
......@@ -35,6 +35,13 @@ expression: Object(Symbol(42))
}
}
expression: Object(BigInt(2))
{
name : [[PrimitiveValue]]
type : bigint
value : 2n
}
Running test: promise
expression: Promise.resolve(42)
......
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(luoe): remove flag when it is on by default.
// Flags: --harmony-bigint
let {session, contextGroup, Protocol} = InspectorTest.start("Check internal properties reported in object preview.");
......@@ -17,6 +19,7 @@ InspectorTest.runTestSuite([
.then(() => checkExpression("new Boolean(false)"))
.then(() => checkExpression("new String(\"abc\")"))
.then(() => checkExpression("Object(Symbol(42))"))
.then(() => checkExpression("Object(BigInt(2))"))
.then(next);
},
......
......@@ -13,6 +13,17 @@ Running test: testArguments
}
}
Running test: testUnserializableArguments
{
id : <messageId>
result : {
result : {
type : string
value : true|true|true|true|bigint
}
}
}
Running test: testComplexArguments
{
id : <messageId>
......
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(luoe): remove flag when it is on by default.
// Flags: --harmony-bigint
let {session, contextGroup, Protocol} = InspectorTest.start('Tests that Runtime.callFunctionOn works with awaitPromise flag.');
let callFunctionOn = Protocol.Runtime.callFunctionOn.bind(Protocol.Runtime);
......@@ -36,6 +38,17 @@ let testSuite = [
}));
},
async function testUnserializableArguments() {
InspectorTest.logMessage(await callFunctionOn({
objectId: remoteObject1.objectId,
functionDeclaration: 'function(arg1, arg2, arg3, arg4, arg5) { return \'\' + Object.is(arg1, -0) + \'|\' + Object.is(arg2, NaN) + \'|\' + Object.is(arg3, Infinity) + \'|\' + Object.is(arg4, -Infinity) + \'|\' + (typeof arg5); }',
arguments: prepareArguments([-0, NaN, Infinity, -Infinity, 2n]),
returnByValue: true,
generatePreview: false,
awaitPromise: false
}));
},
async function testComplexArguments() {
InspectorTest.logMessage(await callFunctionOn({
objectId: remoteObject1.objectId,
......@@ -143,6 +156,8 @@ function prepareArguments(args) {
return {unserializableValue: '-0'};
if (Object.is(arg, NaN) || Object.is(arg, Infinity) || Object.is(arg, -Infinity))
return {unserializableValue: arg + ''};
if (typeof arg === 'bigint')
return {unserializableValue: arg + 'n'};
if (arg && arg.objectId)
return {objectId: arg.objectId};
return {value: arg};
......
Tests Runtime.evaluate with unserializable results.
-0
{
id : <messageId>
result : {
result : {
description : -0
type : number
unserializableValue : -0
}
}
}
NaN
{
id : <messageId>
result : {
result : {
description : NaN
type : number
unserializableValue : NaN
}
}
}
Infinity
{
id : <messageId>
result : {
result : {
description : Infinity
type : number
unserializableValue : Infinity
}
}
}
-Infinity
{
id : <messageId>
result : {
result : {
description : -Infinity
type : number
unserializableValue : -Infinity
}
}
}
1n
{
id : <messageId>
result : {
result : {
description : 1n
type : bigint
unserializableValue : 1n
}
}
}
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(luoe): remove flag when it is on by default.
// Flags: --harmony-bigint
let {session, contextGroup, Protocol} =
InspectorTest.start("Tests Runtime.evaluate with unserializable results.");
Protocol.Runtime.enable();
(async function() {
await testCase("-0");
await testCase("NaN");
await testCase("Infinity");
await testCase("-Infinity");
await testCase("1n");
InspectorTest.completeTest();
})();
async function testCase(expression) {
InspectorTest.log(expression);
InspectorTest.logMessage(await Protocol.Runtime.evaluate({expression}));
}
......@@ -836,3 +836,23 @@ Running test: testObjWithArrayAsProto
]
type : object
}
Running test: testArrayWithLongValues
{
description : Array(2)
overflow : false
properties : [
[0] : {
name : 0
type : string
value : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa…aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
}
[1] : {
name : 1
type : bigint
value : 51644997561738171793118383440060237486594115856584…033682389259290706560275662871806343945494986752n
}
]
subtype : array
type : object
}
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(luoe): remove flag when it is on by default.
// Flags: --harmony-bigint
let {session, contextGroup, Protocol} = InspectorTest.start("Tests that Runtime.evaluate will generate correct previews.");
......@@ -63,6 +65,8 @@ Object.defineProperty(parentObj, 'propNotNamedProto', {
});
var objInheritsGetterProperty = {__proto__: parentObj};
inspector.allowAccessorFormatting(objInheritsGetterProperty);
var arrayWithLongValues = ["a".repeat(101), 2n**401n];
`);
contextGroup.setupInjectedScriptEnvironment();
......@@ -143,5 +147,12 @@ InspectorTest.runTestSuite([
Protocol.Runtime.evaluate({ "expression": "Object.create([1,2])", "generatePreview": true })
.then(result => InspectorTest.logMessage(result.result.result.preview))
.then(next);
},
function testArrayWithLongValues(next)
{
Protocol.Runtime.evaluate({ "expression": "arrayWithLongValues", "generatePreview": true })
.then(result => InspectorTest.logMessage(result.result.result.preview))
.then(next);
}
]);
......@@ -136,6 +136,22 @@ expression: Object(Symbol(42))
]
}
}
expression: Object(BigInt(2))
{
id : <messageId>
result : {
internalProperties : [
[0] : {
name : [[PrimitiveValue]]
value : {
description : 2n
type : bigint
unserializableValue : 2n
}
}
]
}
}
Running test: promise
expression: Promise.resolve(42)
......
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(luoe): remove flag when it is on by default.
// Flags: --harmony-bigint
let {session, contextGroup, Protocol} = InspectorTest.start('Checks internal properties in Runtime.getProperties output');
......@@ -29,6 +31,7 @@ InspectorTest.runTestSuite([
.then(() => checkExpression('new Boolean(false)'))
.then(() => checkExpression('new String(\'abc\')'))
.then(() => checkExpression('Object(Symbol(42))'))
.then(() => checkExpression("Object(BigInt(2))"))
.then(next);
},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment