Use BigInts in processor.mjs and related code to avoid unsafe ints in

calculations

Bug: v8:13440
Change-Id: Ie03b831b511a49fb475b9f303ef8662189bdaf3d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4017455
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#84698}
This commit is contained in:
Vasili Skurydzin 2022-11-24 14:40:18 -05:00 committed by V8 LUCI CQ
parent 122836d23c
commit efc1a98c53
6 changed files with 126 additions and 37 deletions

View File

@ -0,0 +1,59 @@
// Copyright 2020 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.
// Flags: --logfile='+' --log --log-maps --log-ic --log-code
// Flags: --log-function-events --no-predictable
import { Processor } from "../../../tools/system-analyzer/processor.mjs";
// log code start
function doWork() {
let array = [];
for (let i = 0; i < 500; i++) {
doWorkStep(i, array);
}
let sum = 0;
for (let i = 0; i < 500; i++) {
sum += array[i]["property" + i];
}
return sum;
}
function doWorkStep(i, array) {
const obj = {
["property" + i]: i,
};
array.push(obj);
obj.custom1 = 1;
obj.custom2 = 2;
}
const result = doWork();
// log code end
const logString = d8.log.getAndStop();
assertTrue(logString.length > 0);
const useBigInts = true;
const processor = new Processor(useBigInts);
await processor.processChunk(logString);
await processor.finalize();
const maps = processor.mapTimeline;
const ics = processor.icTimeline;
const scripts = processor.scripts;
(function testResults() {
assertEquals(result, 124750);
assertTrue(maps.length > 0);
assertTrue(ics.length > 0);
assertTrue(scripts.length > 0);
})();
(function testIcKeys() {
const keys = new Set();
ics.forEach(ic => keys.add(ic.key));
assertTrue(keys.has("custom1"));
assertTrue(keys.has("custom2"));
assertTrue(keys.has("push"));
})();

View File

@ -65,6 +65,12 @@ export class CodeMap {
*/
pages_ = new Set();
constructor(useBigInt=false) {
this.useBigInt = useBigInt;
this.kPageSize = useBigInt ? BigInt(kPageSize) : kPageSize;
this.kOne = useBigInt ? 1n : 1;
this.kZero = useBigInt ? 0n : 0;
}
/**
* Adds a code entry that might overlap with static code (e.g. for builtins).
@ -73,7 +79,7 @@ export class CodeMap {
* @param {CodeEntry} codeEntry Code entry object.
*/
addAnyCode(start, codeEntry) {
const pageAddr = (start / kPageSize) | 0;
const pageAddr = (start / this.kPageSize) | this.kZero;
if (!this.pages_.has(pageAddr)) return this.addCode(start, codeEntry);
// We might have loaded static code (builtins, bytecode handlers)
// and we get more information later in v8.log with code-creation events.
@ -147,8 +153,8 @@ export class CodeMap {
* @private
*/
markPages_(start, end) {
for (let addr = start; addr <= end; addr += kPageSize) {
this.pages_.add((addr / kPageSize) | 0);
for (let addr = start; addr <= end; addr += this.kPageSize) {
this.pages_.add((addr / this.kPageSize) | this.kZero);
}
}
@ -157,13 +163,13 @@ export class CodeMap {
*/
deleteAllCoveredNodes_(tree, start, end) {
const to_delete = [];
let addr = end - 1;
let addr = end - this.kOne;
while (addr >= start) {
const node = tree.findGreatestLessThan(addr);
if (node === null) break;
const start2 = node.key, end2 = start2 + node.value.size;
if (start2 < end && start < end2) to_delete.push(start2);
addr = start2 - 1;
addr = start2 - this.kOne;
}
for (let i = 0, l = to_delete.length; i < l; ++i) tree.remove(to_delete[i]);
}
@ -191,7 +197,7 @@ export class CodeMap {
* @param {number} addr Address.
*/
findAddress(addr) {
const pageAddr = (addr / kPageSize) | 0;
const pageAddr = (addr / this.kPageSize) | this.kZero;
if (this.pages_.has(pageAddr)) {
// Static code entries can contain "holes" of unnamed code.
// In this case, the whole library is assigned to this address.

View File

@ -35,6 +35,16 @@
export function parseString(field) { return field };
export const parseVarArgs = 'parse-var-args';
// Checks fields for numbers that are not safe integers. Returns true if any are
// found.
function containsUnsafeInts(fields) {
for (let i = 0; i < fields.length; i++) {
let field = fields[i];
if ('number' == typeof(field) && !Number.isSafeInteger(field)) return true;
}
return false;
}
/**
* Base class for processing log files.
*
@ -44,7 +54,7 @@ export const parseVarArgs = 'parse-var-args';
* @constructor
*/
export class LogReader {
constructor(timedRange=false, pairwiseTimedRange=false) {
constructor(timedRange=false, pairwiseTimedRange=false, useBigInt=false) {
this.dispatchTable_ = new Map();
this.timedRange_ = timedRange;
this.pairwiseTimedRange_ = pairwiseTimedRange;
@ -54,6 +64,11 @@ export class LogReader {
// Variables for tracking of 'current-time' log entries:
this.hasSeenTimerMarker_ = false;
this.logLinesSinceLastTimerMarker_ = [];
// Flag to parse all numeric fields as BigInt to avoid arithmetic errors
// caused by memory addresses being greater than MAX_SAFE_INTEGER
this.useBigInt = useBigInt;
this.parseFrame = useBigInt ? BigInt : parseInt;
this.hasSeenUnsafeIntegers = false;
}
/**
@ -180,11 +195,11 @@ export class LogReader {
const firstChar = frame[0];
if (firstChar === '+' || firstChar === '-') {
// An offset from the previous frame.
prevFrame += parseInt(frame, 16);
prevFrame += this.parseFrame(frame);
fullStack.push(prevFrame);
// Filter out possible 'overflow' string.
} else if (firstChar !== 'o') {
fullStack.push(parseInt(frame, 16));
fullStack.push(this.parseFrame(frame));
} else {
console.error(`Dropping unknown tick frame: ${frame}`);
}
@ -216,6 +231,12 @@ export class LogReader {
parsedFields[i] = parser(fields[1 + i]);
}
}
if (!this.useBigInt) {
if (!this.hasSeenUnsafeIntegers && containsUnsafeInts(parsedFields)) {
console.warn(`Log line containts unsafe integers: ${fields}`);
this.hasSeenUnsafeIntegers = true;
}
}
// Run the processor.
await dispatch.processor(...parsedFields);
}

View File

@ -305,7 +305,6 @@ const kProfileOperationTick = 2;
* @constructor
*/
export class Profile {
codeMap_ = new CodeMap();
topDownTree_ = new CallTree();
bottomUpTree_ = new CallTree();
c_entries_ = {__proto__:null};
@ -313,6 +312,11 @@ export class Profile {
urlToScript_ = new Map();
warnings = new Set();
constructor(useBigInt=false) {
this.useBigInt = useBigInt;
this.codeMap_ = new CodeMap(useBigInt);
}
serializeVMSymbols() {
let result = this.codeMap_.getAllStaticEntriesWithAddresses();
result.concat(this.codeMap_.getAllLibraryEntriesWithAddresses())
@ -513,7 +517,7 @@ export class Profile {
// it is safe to put them in a single code map.
let func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr);
if (func === null) {
func = new FunctionEntry(name);
func = new FunctionEntry(name, this.useBigInt);
this.codeMap_.addCode(funcAddr, func);
} else if (func.name !== name) {
// Function object has been overwritten with a new one.
@ -961,8 +965,8 @@ class FunctionEntry extends CodeEntry {
/** @type {Set<DynamicCodeEntry>} */
_codeEntries = new Set();
constructor(name) {
super(0, name);
constructor(name, useBigInt=false) {
super(useBigInt ? 0n : 0, name);
const index = name.lastIndexOf(' ');
this.functionName = 1 <= index ? name.substring(0, index) : '<anonymous>';
}

View File

@ -42,7 +42,7 @@ export class TickLogEntry extends LogEntry {
return 'Idle';
}
const topOfStack = processedStack[0];
if (typeof topOfStack === 'number') {
if (typeof topOfStack === 'number' || typeof topOfStack === 'bigint') {
// TODO(cbruni): Handle VmStack and native ticks better.
return 'Other';
}

View File

@ -47,7 +47,6 @@ class AsyncConsumer {
}
export class Processor extends LogReader {
_profile = new Profile();
_codeTimeline = new Timeline();
_deoptTimeline = new Timeline();
_icTimeline = new Timeline();
@ -70,12 +69,16 @@ export class Processor extends LogReader {
MAJOR_VERSION = 7;
MINOR_VERSION = 6;
constructor() {
super();
constructor(useBigInt = false) {
super(false, false, useBigInt);
this.useBigInt = useBigInt;
this.kZero = useBigInt ? 0n : 0;
this.parseAddress = useBigInt ? BigInt : parseInt;
this._chunkConsumer =
new AsyncConsumer((chunk) => this._processChunk(chunk));
this._profile = new Profile(useBigInt);
const propertyICParser = [
parseInt, parseInt, parseInt, parseInt, parseString, parseString,
this.parseAddress, parseInt, parseInt, parseInt, parseString, parseString,
parseString, parseString, parseString, parseString
];
this.setDispatchTable({
@ -88,14 +91,14 @@ export class Processor extends LogReader {
processor: this.processV8Version,
},
'shared-library': {
parsers: [parseString, parseInt, parseInt, parseInt],
parsers: [parseString, this.parseAddress, this.parseAddress, parseInt],
processor: this.processSharedLibrary.bind(this),
isAsync: true,
},
'code-creation': {
parsers: [
parseString, parseInt, parseInt, parseInt, parseInt, parseString,
parseVarArgs
parseString, parseInt, parseInt, this.parseAddress, this.parseAddress,
parseString, parseVarArgs
],
processor: this.processCodeCreation
},
@ -111,17 +114,13 @@ export class Processor extends LogReader {
'code-delete': {parsers: [parseInt], processor: this.processCodeDelete},
'code-source-info': {
parsers: [
parseInt, parseInt, parseInt, parseInt, parseString, parseString,
parseString
this.parseAddress, parseInt, parseInt, parseInt, parseString,
parseString, parseString
],
processor: this.processCodeSourceInfo
},
'code-disassemble': {
parsers: [
parseInt,
parseString,
parseString,
],
parsers: [this.parseAddress, parseString, parseString],
processor: this.processCodeDisassemble
},
'feedback-vector': {
@ -138,8 +137,10 @@ export class Processor extends LogReader {
'sfi-move':
{parsers: [parseInt, parseInt], processor: this.processFunctionMove},
'tick': {
parsers:
[parseInt, parseInt, parseInt, parseInt, parseInt, parseVarArgs],
parsers: [
this.parseAddress, parseInt, parseInt, this.parseAddress, parseInt,
parseVarArgs
],
processor: this.processTick
},
'active-runtime-timer': undefined,
@ -157,8 +158,8 @@ export class Processor extends LogReader {
{parsers: [parseInt, parseString], processor: this.processMapCreate},
'map': {
parsers: [
parseString, parseInt, parseString, parseString, parseInt, parseInt,
parseInt, parseString, parseString
parseString, parseInt, parseString, parseString, this.parseAddress,
parseInt, parseInt, parseString, parseString
],
processor: this.processMap
},
@ -310,11 +311,9 @@ export class Processor extends LogReader {
// Many events rely on having a script around, creating fake entries for
// shared libraries.
this._profile.addScriptSource(-1, name, '');
if (this._cppEntriesProvider == undefined) {
await this._setupCppEntriesProvider();
}
await this._cppEntriesProvider.parseVmSymbols(
name, startAddr, endAddr, aslrSlide, (fName, fStart, fEnd) => {
const entry = this._profile.addStaticCode(fName, fStart, fEnd);
@ -352,7 +351,7 @@ export class Processor extends LogReader {
let profilerEntry;
let stateName = '';
if (maybe_func.length) {
const funcAddr = parseInt(maybe_func[0]);
const funcAddr = this.parseAddress(maybe_func[0]);
stateName = maybe_func[1] ?? '';
const state = Profile.parseState(maybe_func[1]);
profilerEntry = this._profile.addFuncCode(
@ -439,13 +438,13 @@ export class Processor extends LogReader {
// that a callback calls itself. Instead we use tos_or_external_callback,
// as simply resetting PC will produce unaccounted ticks.
pc = tos_or_external_callback;
tos_or_external_callback = 0;
tos_or_external_callback = this.kZero;
} else if (tos_or_external_callback) {
// Find out, if top of stack was pointing inside a JS function
// meaning that we have encountered a frameless invocation.
const funcEntry = this._profile.findEntry(tos_or_external_callback);
if (!funcEntry?.isJSFunction?.()) {
tos_or_external_callback = 0;
tos_or_external_callback = this.kZero;
}
}
const entryStack = this._profile.recordTick(