Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions deps/v8/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2393,6 +2393,7 @@ filegroup(
"src/profiler/heap-profiler.h",
"src/profiler/heap-snapshot-common.h",
"src/profiler/heap-snapshot-generator.cc",
"src/profiler/output-stream-writer.cc",
"src/profiler/heap-snapshot-generator.h",
"src/profiler/heap-snapshot-generator-inl.h",
"src/profiler/output-stream-writer.h",
Expand Down
1 change: 1 addition & 0 deletions deps/v8/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -6100,6 +6100,7 @@ v8_source_set("v8_base_without_compiler") {
"src/profiler/cpu-profiler.cc",
"src/profiler/heap-profiler.cc",
"src/profiler/heap-snapshot-generator.cc",
"src/profiler/output-stream-writer.cc",
"src/profiler/profile-generator.cc",
"src/profiler/profiler-listener.cc",
"src/profiler/profiler-stats.cc",
Expand Down
73 changes: 73 additions & 0 deletions deps/v8/src/profiler/output-stream-writer.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2026 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.

#include "src/profiler/output-stream-writer.h"

#include "src/strings/unicode-inl.h"

namespace v8 {
namespace internal {

namespace {

void WriteUChar(OutputStreamWriter* w, unibrow::uchar u) {
static const char hex_chars[] = "0123456789ABCDEF";
w->AddString("\\u");
w->AddCharacter(hex_chars[(u >> 12) & 0xF]);
w->AddCharacter(hex_chars[(u >> 8) & 0xF]);
w->AddCharacter(hex_chars[(u >> 4) & 0xF]);
w->AddCharacter(hex_chars[u & 0xF]);
}

} // namespace

void OutputStreamWriter::AddJsonEscapedString(const unsigned char* s) {
AddCharacter('"');
for (; *s != '\0'; ++s) {
switch (*s) {
case '\b':
AddString("\\b");
continue;
case '\f':
AddString("\\f");
continue;
case '\n':
AddString("\\n");
continue;
case '\r':
AddString("\\r");
continue;
case '\t':
AddString("\\t");
continue;
case '"':
case '\\':
AddCharacter('\\');
AddCharacter(*s);
continue;
default:
if (*s > 31 && *s < 128) {
AddCharacter(*s);
} else if (*s <= 31) {
WriteUChar(this, *s);
} else {
size_t length = 1, cursor = 0;
for (; length <= 4 && *(s + length) != '\0'; ++length) {
}
unibrow::uchar c = unibrow::Utf8::CalculateValue(s, length, &cursor);
if (c != unibrow::Utf8::kBadChar) {
WriteUChar(this, c);
DCHECK_NE(cursor, 0);
s += cursor - 1;
} else {
AddCharacter('?');
}
}
}
}
AddCharacter('"');
}

} // namespace internal
} // namespace v8
1 change: 1 addition & 0 deletions deps/v8/src/profiler/output-stream-writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class OutputStreamWriter {
AddNumber(n);
}
}
void AddJsonEscapedString(const unsigned char* s);
void Finalize() {
if (aborted_) return;
DCHECK(chunk_pos_ < chunk_size_);
Expand Down
19 changes: 10 additions & 9 deletions deps/v8/src/profiler/profile-generator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -846,17 +846,18 @@ void CpuProfileJSONSerializer::SerializePositionTicks(

void CpuProfileJSONSerializer::SerializeCallFrame(
const v8::CpuProfileNode* node) {
writer_->AddString("\"functionName\":\"");
writer_->AddString(node->GetFunctionNameStr());
writer_->AddString("\",\"lineNumber\":");
writer_->AddString("\"functionName\":");
writer_->AddJsonEscapedString(
reinterpret_cast<const unsigned char*>(node->GetFunctionNameStr()));
writer_->AddString(",\"lineNumber\":");
writer_->AddNumber(node->GetLineNumber() - 1);
writer_->AddString(",\"columnNumber\":");
writer_->AddNumber(node->GetColumnNumber() - 1);
writer_->AddString(",\"scriptId\":");
writer_->AddNumber(node->GetScriptId());
writer_->AddString(",\"url\":\"");
writer_->AddString(node->GetScriptResourceNameStr());
writer_->AddCharacter('"');
writer_->AddString(",\"url\":");
writer_->AddJsonEscapedString(
reinterpret_cast<const unsigned char*>(node->GetScriptResourceNameStr()));
}

void CpuProfileJSONSerializer::SerializeChildren(const v8::CpuProfileNode* node,
Expand Down Expand Up @@ -888,9 +889,9 @@ void CpuProfileJSONSerializer::SerializeNode(const v8::CpuProfileNode* node) {

const char* deoptReason = node->GetBailoutReason();
if (deoptReason && deoptReason[0] && strcmp(deoptReason, "no reason")) {
writer_->AddString(",\"deoptReason\":\"");
writer_->AddString(deoptReason);
writer_->AddCharacter('"');
writer_->AddString(",\"deoptReason\":");
writer_->AddJsonEscapedString(
reinterpret_cast<const unsigned char*>(deoptReason));
}

unsigned lineCount = node->GetHitLineCount();
Expand Down
38 changes: 38 additions & 0 deletions test/parallel/test-worker-cpu-profile-regexp-frame.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict';
// Regression test for https://github.com/nodejs/node/issues/67097
// worker.startCpuProfile() must return valid JSON when the profile contains a
// RegExp code frame whose name includes characters that need JSON escaping.
const common = require('../common');
const assert = require('assert');
const { Worker } = require('worker_threads');

const pattern = '^(\\d{6})';
const expectedFunctionName = `RegExp: ${pattern}`;

const worker = new Worker(`
const re = new RegExp(${JSON.stringify(pattern)});
setInterval(() => {
let matches = 0;
for (let i = 0; i < 30000; i++) {
if (re.test(String(i))) matches++;
}
return matches;
}, 0);
`, { eval: true });

worker.on('online', common.mustCall(async () => {
try {
let found = false;
for (let attempt = 0; attempt < 50 && !found; attempt++) {
const handle = await worker.startCpuProfile();
await new Promise((resolve) => setTimeout(resolve, 100));
const profile = JSON.parse(await handle.stop());
found = profile.nodes.some(
({ callFrame }) => callFrame.functionName === expectedFunctionName);
}
assert.ok(found,
`No "${expectedFunctionName}" frame found in the CPU profile`);
} finally {
await worker.terminate();
}
}));