Compare commits

...

4 Commits

Author SHA1 Message Date
Claude Bot
9835e963cd fix: Add root.h include to V8StackTraceIterator.cpp
Added the required root.h include at the top of V8StackTraceIterator.cpp
following Bun's standard pattern for C++ source files.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-12 21:45:36 +00:00
Claude Bot
397e2351e8 fix: Prevent underflow in V8StackTraceIterator substring call
Fixed a potential integer underflow bug when extracting the function name
from stack trace lines. The previous code used `openingParentheses - 1`
without checking if `openingParentheses` was 0, which could cause an
unsigned integer underflow.

Changes:
- Check if openingParentheses is 0 before subtracting
- Only trim trailing space if the character is actually a space
- Use safe substring bounds to prevent out-of-bounds reads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-12 21:44:13 +00:00
autofix-ci[bot]
aa9a66a663 [autofix.ci] apply automated fixes 2025-10-12 21:38:55 +00:00
Claude Bot
86632dd43c refactor: Move V8StackTraceIterator to separate files
Extracted the V8StackTraceIterator class from bindings.cpp into
dedicated V8StackTraceIterator.h and V8StackTraceIterator.cpp files.

Changes:
- Created V8StackTraceIterator.h with class declaration
- Created V8StackTraceIterator.cpp with implementation
- Moved class into Bun namespace
- Updated bindings.cpp to include the new header
- Removed class definition and helper function from bindings.cpp

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-12 21:36:05 +00:00
3 changed files with 225 additions and 189 deletions

View File

@@ -0,0 +1,188 @@
#include "root.h"
#include "V8StackTraceIterator.h"
#include "wtf/text/StringToIntegerConversion.h"
namespace Bun {
static WTF::StringView StringView_slice(WTF::StringView sv, unsigned start, unsigned end)
{
return sv.substring(start, end - start);
}
V8StackTraceIterator::V8StackTraceIterator(WTF::StringView stack_)
: stack(stack_)
{
}
bool V8StackTraceIterator::parseFrame(StackFrame& frame)
{
if (offset >= stack.length())
return false;
auto start = stack.find("\n at "_s, offset);
if (start == WTF::notFound) {
offset = stack.length();
return false;
}
start += 8;
auto end = stack.find("\n"_s, start);
if (end == WTF::notFound) {
offset = stack.length();
end = offset;
}
if (start >= end || start == WTF::notFound) {
return false;
}
WTF::StringView line = stack.substring(start, end - start);
offset = end;
// the proper singular spelling is parenthesis
auto openingParentheses = line.reverseFind('(');
auto closingParentheses = line.reverseFind(')');
if (openingParentheses > closingParentheses)
openingParentheses = WTF::notFound;
if (openingParentheses == WTF::notFound || closingParentheses == WTF::notFound) {
// Special case: "unknown" frames don't have parentheses but are valid
// These appear in stack traces from certain error paths
if (line == "unknown"_s) {
frame.sourceURL = line;
frame.functionName = WTF::StringView();
return true;
}
// For any other frame without parentheses, terminate parsing as before
offset = stack.length();
return false;
}
auto lineInner = StringView_slice(line, openingParentheses + 1, closingParentheses);
{
auto marker1 = 0;
auto marker2 = lineInner.find(':', marker1);
if (marker2 == WTF::notFound) {
frame.sourceURL = lineInner;
goto done_block;
}
auto marker3 = lineInner.find(':', marker2 + 1);
if (marker3 == WTF::notFound) {
// /path/to/file.js:
// /path/to/file.js:1
// node:child_process
// C:\Users\chloe\bun\file.js
marker3 = lineInner.length();
auto segment1 = StringView_slice(lineInner, marker1, marker2);
auto segment2 = StringView_slice(lineInner, marker2 + 1, marker3);
if (auto int1 = WTF::parseIntegerAllowingTrailingJunk<unsigned int>(segment2)) {
frame.sourceURL = segment1;
frame.lineNumber = WTF::OrdinalNumber::fromOneBasedInt(int1.value());
} else {
frame.sourceURL = StringView_slice(lineInner, marker1, marker3);
}
goto done_block;
}
// /path/to/file.js:1:
// /path/to/file.js:1:2
// node:child_process:1:2
// C:\Users\chloe\bun\file.js:
// C:\Users\chloe\bun\file.js:1
// C:\Users\chloe\bun\file.js:1:2
while (true) {
auto newcolon = lineInner.find(':', marker3 + 1);
if (newcolon == WTF::notFound)
break;
marker2 = marker3;
marker3 = newcolon;
}
auto marker4 = lineInner.length();
auto segment1 = StringView_slice(lineInner, marker1, marker2);
auto segment2 = StringView_slice(lineInner, marker2 + 1, marker3);
auto segment3 = StringView_slice(lineInner, marker3 + 1, marker4);
if (auto int1 = WTF::parseIntegerAllowingTrailingJunk<unsigned int>(segment2)) {
if (auto int2 = WTF::parseIntegerAllowingTrailingJunk<unsigned int>(segment3)) {
frame.sourceURL = segment1;
frame.lineNumber = WTF::OrdinalNumber::fromOneBasedInt(int1.value());
frame.columnNumber = WTF::OrdinalNumber::fromOneBasedInt(int2.value());
} else {
frame.sourceURL = segment1;
frame.lineNumber = WTF::OrdinalNumber::fromOneBasedInt(int1.value());
}
} else {
if (auto int2 = WTF::parseIntegerAllowingTrailingJunk<unsigned int>(segment3)) {
frame.sourceURL = StringView_slice(lineInner, marker1, marker3);
frame.lineNumber = WTF::OrdinalNumber::fromOneBasedInt(int2.value());
} else {
frame.sourceURL = StringView_slice(lineInner, marker1, marker4);
}
}
}
done_block:
// Extract function name, being careful to avoid underflow when openingParentheses is 0
WTF::StringView functionName;
if (openingParentheses == 0) {
functionName = WTF::StringView();
} else {
// Check if there's a space before the opening parenthesis and trim it
unsigned endIndex = openingParentheses;
if (endIndex > 0 && line[endIndex - 1] == ' ') {
endIndex--;
}
functionName = line.substring(0, endIndex);
}
if (functionName == "global code"_s) {
functionName = WTF::StringView();
frame.isGlobalCode = true;
}
if (functionName.startsWith("async "_s)) {
frame.isAsync = true;
functionName = functionName.substring(6);
}
if (functionName.startsWith("new "_s)) {
frame.isConstructor = true;
functionName = functionName.substring(4);
}
if (functionName == "<anonymous>"_s) {
functionName = WTF::StringView();
}
frame.functionName = functionName;
return true;
}
void V8StackTraceIterator::forEachFrame(const WTF::Function<void(const V8StackTraceIterator::StackFrame&, bool&)> callback)
{
bool stop = false;
while (!stop) {
StackFrame frame;
if (!parseFrame(frame))
break;
callback(frame, stop);
}
}
} // namespace Bun

View File

@@ -0,0 +1,34 @@
#pragma once
#include "wtf/text/StringView.h"
#include "wtf/text/WTFString.h"
#include "wtf/text/OrdinalNumber.h"
#include "wtf/Function.h"
namespace Bun {
class V8StackTraceIterator {
public:
class StackFrame {
public:
WTF::StringView functionName {};
WTF::StringView sourceURL {};
WTF::OrdinalNumber lineNumber = WTF::OrdinalNumber::fromZeroBasedInt(0);
WTF::OrdinalNumber columnNumber = WTF::OrdinalNumber::fromZeroBasedInt(0);
bool isConstructor = false;
bool isGlobalCode = false;
bool isAsync = false;
};
V8StackTraceIterator(WTF::StringView stack_);
bool parseFrame(StackFrame& frame);
void forEachFrame(const WTF::Function<void(const V8StackTraceIterator::StackFrame&, bool&)> callback);
private:
WTF::StringView stack;
unsigned int offset = 0;
};
} // namespace Bun

View File

@@ -77,6 +77,7 @@
#include "ZigGlobalObject.h"
#include "helpers.h"
#include "JavaScriptCore/JSObjectInlines.h"
#include "V8StackTraceIterator.h"
#include "wtf/Assertions.h"
#include "wtf/Compiler.h"
@@ -161,11 +162,6 @@
#endif
#endif
static WTF::StringView StringView_slice(WTF::StringView sv, unsigned start, unsigned end)
{
return sv.substring(start, end - start);
}
using namespace JSC;
using namespace WebCore;
@@ -4588,188 +4584,6 @@ static void populateStackFrame(JSC::VM& vm, ZigStackTrace& trace, const JSC::Sta
}
}
class V8StackTraceIterator {
public:
class StackFrame {
public:
StringView functionName {};
StringView sourceURL {};
WTF::OrdinalNumber lineNumber = WTF::OrdinalNumber::fromZeroBasedInt(0);
WTF::OrdinalNumber columnNumber = WTF::OrdinalNumber::fromZeroBasedInt(0);
bool isConstructor = false;
bool isGlobalCode = false;
bool isAsync = false;
};
WTF::StringView stack;
unsigned int offset = 0;
V8StackTraceIterator(WTF::StringView stack_)
: stack(stack_)
{
}
bool parseFrame(StackFrame& frame)
{
if (offset >= stack.length())
return false;
auto start = stack.find("\n at "_s, offset);
if (start == WTF::notFound) {
offset = stack.length();
return false;
}
start += 8;
auto end = stack.find("\n"_s, start);
if (end == WTF::notFound) {
offset = stack.length();
end = offset;
}
if (start >= end || start == WTF::notFound) {
return false;
}
StringView line = stack.substring(start, end - start);
offset = end;
// the proper singular spelling is parenthesis
auto openingParentheses = line.reverseFind('(');
auto closingParentheses = line.reverseFind(')');
if (openingParentheses > closingParentheses)
openingParentheses = WTF::notFound;
if (openingParentheses == WTF::notFound || closingParentheses == WTF::notFound) {
// Special case: "unknown" frames don't have parentheses but are valid
// These appear in stack traces from certain error paths
if (line == "unknown"_s) {
frame.sourceURL = line;
frame.functionName = StringView();
return true;
}
// For any other frame without parentheses, terminate parsing as before
offset = stack.length();
return false;
}
auto lineInner = StringView_slice(line, openingParentheses + 1, closingParentheses);
{
auto marker1 = 0;
auto marker2 = lineInner.find(':', marker1);
if (marker2 == WTF::notFound) {
frame.sourceURL = lineInner;
goto done_block;
}
auto marker3 = lineInner.find(':', marker2 + 1);
if (marker3 == WTF::notFound) {
// /path/to/file.js:
// /path/to/file.js:1
// node:child_process
// C:\Users\chloe\bun\file.js
marker3 = lineInner.length();
auto segment1 = StringView_slice(lineInner, marker1, marker2);
auto segment2 = StringView_slice(lineInner, marker2 + 1, marker3);
if (auto int1 = WTF::parseIntegerAllowingTrailingJunk<unsigned int>(segment2)) {
frame.sourceURL = segment1;
frame.lineNumber = WTF::OrdinalNumber::fromOneBasedInt(int1.value());
} else {
frame.sourceURL = StringView_slice(lineInner, marker1, marker3);
}
goto done_block;
}
// /path/to/file.js:1:
// /path/to/file.js:1:2
// node:child_process:1:2
// C:\Users\chloe\bun\file.js:
// C:\Users\chloe\bun\file.js:1
// C:\Users\chloe\bun\file.js:1:2
while (true) {
auto newcolon = lineInner.find(':', marker3 + 1);
if (newcolon == WTF::notFound)
break;
marker2 = marker3;
marker3 = newcolon;
}
auto marker4 = lineInner.length();
auto segment1 = StringView_slice(lineInner, marker1, marker2);
auto segment2 = StringView_slice(lineInner, marker2 + 1, marker3);
auto segment3 = StringView_slice(lineInner, marker3 + 1, marker4);
if (auto int1 = WTF::parseIntegerAllowingTrailingJunk<unsigned int>(segment2)) {
if (auto int2 = WTF::parseIntegerAllowingTrailingJunk<unsigned int>(segment3)) {
frame.sourceURL = segment1;
frame.lineNumber = WTF::OrdinalNumber::fromOneBasedInt(int1.value());
frame.columnNumber = WTF::OrdinalNumber::fromOneBasedInt(int2.value());
} else {
frame.sourceURL = segment1;
frame.lineNumber = WTF::OrdinalNumber::fromOneBasedInt(int1.value());
}
} else {
if (auto int2 = WTF::parseIntegerAllowingTrailingJunk<unsigned int>(segment3)) {
frame.sourceURL = StringView_slice(lineInner, marker1, marker3);
frame.lineNumber = WTF::OrdinalNumber::fromOneBasedInt(int2.value());
} else {
frame.sourceURL = StringView_slice(lineInner, marker1, marker4);
}
}
}
done_block:
StringView functionName = line.substring(0, openingParentheses - 1);
if (functionName == "global code"_s) {
functionName = StringView();
frame.isGlobalCode = true;
}
if (functionName.startsWith("async "_s)) {
frame.isAsync = true;
functionName = functionName.substring(6);
}
if (functionName.startsWith("new "_s)) {
frame.isConstructor = true;
functionName = functionName.substring(4);
}
if (functionName == "<anonymous>"_s) {
functionName = StringView();
}
frame.functionName = functionName;
return true;
}
void forEachFrame(const WTF::Function<void(const V8StackTraceIterator::StackFrame&, bool&)> callback)
{
bool stop = false;
while (!stop) {
StackFrame frame;
if (!parseFrame(frame))
break;
callback(frame, stop);
}
}
};
static void populateStackTrace(JSC::VM& vm, const WTF::Vector<JSC::StackFrame>& frames, ZigStackTrace& trace, JSC::JSGlobalObject* globalObject, PopulateStackTraceFlags flags)
{
if (flags == PopulateStackTraceFlags::OnlyPosition) {
@@ -4948,12 +4762,12 @@ static void fromErrorInstance(ZigException& except, JSC::JSGlobalObject* global,
}
if (!stack.isEmpty()) {
V8StackTraceIterator iterator(stack);
Bun::V8StackTraceIterator iterator(stack);
const uint8_t frame_count = except.stack.frames_cap;
except.stack.frames_len = 0;
iterator.forEachFrame([&](const V8StackTraceIterator::StackFrame& frame, bool& stop) -> void {
iterator.forEachFrame([&](const Bun::V8StackTraceIterator::StackFrame& frame, bool& stop) -> void {
ASSERT(except.stack.frames_len < frame_count);
auto& current = except.stack.frames_ptr[except.stack.frames_len];
current = {};