Compare commits

..

4 Commits

Author SHA1 Message Date
Alistair Smith
81e91069df Merge branch 'main' into claude/fix-standalone-sourcemap-alignment 2026-02-24 05:50:52 +00:00
robobun
6b1d6c769b fix(sys): remove MSG_NOSIGNAL from recvfrom flags (#27390)
## Summary

- `MSG_NOSIGNAL` is only valid for send operations (`send`, `sendto`,
`sendmsg`), not receive operations (`recv`, `recvfrom`, `recvmsg`).
Passing it to `recvfrom` causes `EINVAL` in strict environments like
gVisor (Google Cloud Run).
- Split the shared `socket_flags_nonblock` constant into
`recv_flags_nonblock` (`MSG_DONTWAIT` only) and `send_flags_nonblock`
(`MSG_DONTWAIT | MSG_NOSIGNAL`).

Closes #27389

## Test plan

- [x] Added regression test `test/regression/issue/27389.test.ts` that
exercises the socket recv path
- [x] Debug build passes (`bun bd test
test/regression/issue/27389.test.ts`)

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

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-02-23 18:24:37 -08:00
Claude Bot
7735063aa0 test: remove ineffective stderr panic assertion
Checking stderr for "panic" is redundant since a panic causes a
non-zero exit code, which is already asserted.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-23 23:39:48 +00:00
Claude Bot
b6a6c880b5 fix(standalone): remove incorrect @alignCast on sourcemap bytes (#27383)
The `LazySourceMap` union's payload is aligned to the maximum variant
alignment (8 bytes due to the `*SourceMap.ParsedSourceMap` pointer).
When initializing the `serialized` variant, `@alignCast` promoted the
alignment of the sourcemap byte slice from 1 to 8. However, serialized
sourcemap data in standalone binaries can be at any offset, not
necessarily 8-byte aligned. On Windows ARM64 ReleaseSafe builds, this
causes `panic: incorrect alignment`.

Remove the unnecessary `@alignCast()` calls since `SerializedSourceMap.bytes`
is `[]const u8` which naturally has alignment 1.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-23 23:21:34 +00:00
11 changed files with 152 additions and 34 deletions

View File

@@ -462,7 +462,7 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
#ifdef _WIN32
const int recv_flags = MSG_PUSH_IMMEDIATE;
#else
const int recv_flags = MSG_DONTWAIT | MSG_NOSIGNAL;
const int recv_flags = MSG_DONTWAIT;
#endif
int length;

View File

@@ -336,7 +336,7 @@ pub const StandaloneModuleGraph = struct {
.contents = sliceToZ(raw_bytes, module.contents),
.sourcemap = if (module.sourcemap.length > 0)
.{ .serialized = .{
.bytes = @alignCast(sliceTo(raw_bytes, module.sourcemap)),
.bytes = sliceTo(raw_bytes, module.sourcemap),
} }
else
.none,
@@ -588,7 +588,7 @@ pub const StandaloneModuleGraph = struct {
if (comptime Environment.isDebug) {
// An expensive sanity check:
var graph = try fromBytes(allocator, @alignCast(output_bytes), offsets);
var graph = try fromBytes(allocator, output_bytes, offsets);
defer {
graph.files.unlockPointers();
graph.files.deinit();

View File

@@ -15,7 +15,9 @@ void JSHTTPParser::finishCreation(VM& vm)
Base::finishCreation(vm);
ASSERT(inherits(info()));
// llhttp callbacks need JSHTTPParser for the connections list.
// llhttp callbacks need JSHTTParser for the connections list.
// The pointer does not need to be kept alive with WriteBarrier because
// this is basically a self-reference.
m_impl.m_thisParser = this;
}
@@ -25,8 +27,6 @@ void JSHTTPParser::visitChildrenImpl(JSCell* cell, Visitor& visitor)
JSHTTPParser* thisObject = jsCast<JSHTTPParser*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor);
visitor.appendUnbarriered(thisObject->m_impl.m_globalObject);
visitor.appendUnbarriered(thisObject->m_impl.m_thisParser);
visitor.append(thisObject->m_impl.m_connectionsList);
}

View File

@@ -313,11 +313,11 @@ bool HTTPParser::lessThan(HTTPParser& other) const
int HTTPParser::onMessageBegin()
{
JSGlobalObject* globalObject = m_globalObject.get();
JSGlobalObject* globalObject = m_globalObject;
auto& vm = globalObject->vm();
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
JSHTTPParser* thisParser = m_thisParser.get();
JSHTTPParser* thisParser = m_thisParser;
if (JSConnectionsList* connections = m_connectionsList.get()) {
connections->pop(globalObject, thisParser);
@@ -377,7 +377,7 @@ int HTTPParser::onStatus(const char* at, size_t length)
int HTTPParser::onHeaderField(const char* at, size_t length)
{
JSGlobalObject* globalObject = m_globalObject.get();
JSGlobalObject* globalObject = m_globalObject;
auto& vm = globalObject->vm();
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
@@ -450,10 +450,10 @@ int HTTPParser::onChunkExtensionValue(const char* at, size_t length)
int HTTPParser::onHeadersComplete()
{
JSGlobalObject* globalObject = m_globalObject.get();
JSGlobalObject* globalObject = m_globalObject;
auto& vm = globalObject->vm();
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
JSHTTPParser* thisParser = m_thisParser.get();
JSHTTPParser* thisParser = m_thisParser;
m_headersCompleted = true;
m_headerNread = 0;
@@ -537,12 +537,12 @@ int HTTPParser::onBody(const char* at, size_t length)
return 0;
}
JSGlobalObject* lexicalGlobalObject = m_globalObject.get();
JSGlobalObject* lexicalGlobalObject = m_globalObject;
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
auto& vm = lexicalGlobalObject->vm();
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
JSValue onBodyCallback = m_thisParser.get()->get(lexicalGlobalObject, Identifier::from(vm, kOnBody));
JSValue onBodyCallback = m_thisParser->get(lexicalGlobalObject, Identifier::from(vm, kOnBody));
RETURN_IF_EXCEPTION(scope, 0);
if (!onBodyCallback.isCallable()) {
return 0;
@@ -557,7 +557,7 @@ int HTTPParser::onBody(const char* at, size_t length)
MarkedArgumentBuffer args;
args.append(buffer);
JSC::profiledCall(lexicalGlobalObject, ProfilingReason::API, onBodyCallback, callData, m_thisParser.get(), args);
JSC::profiledCall(lexicalGlobalObject, ProfilingReason::API, onBodyCallback, callData, m_thisParser, args);
if (scope.exception()) [[unlikely]] {
llhttp_set_error_reason(&m_parserData, "HPE_USER:JS Exception");
@@ -569,10 +569,10 @@ int HTTPParser::onBody(const char* at, size_t length)
int HTTPParser::onMessageComplete()
{
JSGlobalObject* globalObject = m_globalObject.get();
JSGlobalObject* globalObject = m_globalObject;
auto& vm = globalObject->vm();
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
JSHTTPParser* thisParser = m_thisParser.get();
JSHTTPParser* thisParser = m_thisParser;
if (JSConnectionsList* connections = m_connectionsList.get()) {
connections->pop(globalObject, thisParser);
@@ -634,11 +634,11 @@ int HTTPParser::trackHeader(size_t len)
void HTTPParser::flush()
{
JSGlobalObject* globalObject = m_globalObject.get();
JSGlobalObject* globalObject = m_globalObject;
auto& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSHTTPParser* thisParser = m_thisParser.get();
JSHTTPParser* thisParser = m_thisParser;
JSValue onHeadersCallback = thisParser->get(globalObject, Identifier::from(vm, kOnHeaders));
RETURN_IF_EXCEPTION(scope, );

View File

@@ -37,12 +37,12 @@ Isolate* Isolate::GetCurrent()
Local<Context> Isolate::GetCurrentContext()
{
return currentHandleScope()->createLocal<Context>(m_globalObject->vm(), m_globalObject.get());
return currentHandleScope()->createLocal<Context>(m_globalObject->vm(), m_globalObject);
}
Isolate::Isolate(shim::GlobalInternals* globalInternals)
: m_globalInternals(globalInternals, JSC::WriteBarrierEarlyInit)
, m_globalObject(globalInternals->m_globalObject.get(), JSC::WriteBarrierEarlyInit)
: m_globalInternals(globalInternals)
, m_globalObject(globalInternals->m_globalObject)
{
m_roots[kUndefinedValueRootIndex] = TaggedPointer(&globalInternals->m_undefinedValue);
m_roots[kNullValueRootIndex] = TaggedPointer(&globalInternals->m_nullValue);

View File

@@ -2,7 +2,6 @@
#include "v8.h"
#include "V8Local.h"
#include <JavaScriptCore/WriteBarrier.h>
namespace v8 {
@@ -35,9 +34,9 @@ public:
BUN_EXPORT Local<Context> GetCurrentContext();
Zig::GlobalObject* globalObject() { return m_globalObject.get(); }
Zig::GlobalObject* globalObject() { return m_globalObject; }
JSC::VM& vm() { return globalObject()->vm(); }
shim::GlobalInternals* globalInternals() { return m_globalInternals.get(); }
shim::GlobalInternals* globalInternals() { return m_globalInternals; }
HandleScope* currentHandleScope();
TaggedPointer* undefinedSlot() { return &m_roots[Isolate::kUndefinedValueRootIndex]; }
@@ -48,8 +47,8 @@ public:
TaggedPointer* falseSlot() { return &m_roots[Isolate::kFalseValueRootIndex]; }
JSC::WriteBarrier<shim::GlobalInternals> m_globalInternals;
JSC::WriteBarrier<Zig::GlobalObject> m_globalObject;
shim::GlobalInternals* m_globalInternals;
Zig::GlobalObject* m_globalObject;
uintptr_t m_padding[78];

View File

@@ -48,7 +48,7 @@ void GlobalInternals::finishCreation(VM& vm)
});
m_globalHandles.initLater([](const LazyProperty<GlobalInternals, HandleScopeBuffer>::Initializer& init) {
init.set(HandleScopeBuffer::create(init.vm,
init.owner->handleScopeBufferStructure(init.owner->m_globalObject.get())));
init.owner->handleScopeBufferStructure(init.owner->m_globalObject)));
});
}
@@ -59,7 +59,6 @@ void GlobalInternals::visitChildrenImpl(JSCell* cell, Visitor& visitor)
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor);
visitor.append(thisObject->m_globalObject);
thisObject->m_objectTemplateStructure.visit(visitor);
thisObject->m_handleScopeBufferStructure.visit(visitor);
thisObject->m_functionTemplateStructure.visit(visitor);

View File

@@ -72,7 +72,7 @@ public:
friend class ::v8::Context;
private:
JSC::WriteBarrier<Zig::GlobalObject> m_globalObject;
Zig::GlobalObject* m_globalObject;
JSC::LazyClassStructure m_objectTemplateStructure;
JSC::LazyClassStructure m_handleScopeBufferStructure;
JSC::LazyClassStructure m_functionTemplateStructure;
@@ -95,8 +95,8 @@ private:
, m_nullValue(Oddball::Kind::kNull)
, m_trueValue(Oddball::Kind::kTrue)
, m_falseValue(Oddball::Kind::kFalse)
, m_globalObject(globalObject, JSC::WriteBarrierEarlyInit)
, m_isolate(this)
, m_globalObject(globalObject)
{
}
};

View File

@@ -2035,10 +2035,10 @@ pub fn readAll(fd: bun.FileDescriptor, buf: []u8) Maybe(usize) {
return .{ .result = total_read };
}
const socket_flags_nonblock = c.MSG_DONTWAIT | c.MSG_NOSIGNAL;
const send_flags_nonblock = c.MSG_DONTWAIT | c.MSG_NOSIGNAL;
pub fn recvNonBlock(fd: bun.FileDescriptor, buf: []u8) Maybe(usize) {
return recv(fd, buf, socket_flags_nonblock);
return recv(fd, buf, recv_flags_nonblock);
}
pub fn poll(fds: []std.posix.pollfd, timeout: i32) Maybe(usize) {
@@ -2119,7 +2119,7 @@ pub fn kevent(fd: bun.FileDescriptor, changelist: []const std.c.Kevent, eventlis
}
pub fn sendNonBlock(fd: bun.FileDescriptor, buf: []const u8) Maybe(usize) {
return send(fd, buf, socket_flags_nonblock);
return send(fd, buf, send_flags_nonblock);
}
pub fn send(fd: bun.FileDescriptor, buf: []const u8, flag: u32) Maybe(usize) {
@@ -4359,11 +4359,13 @@ const bun = @import("bun");
const Environment = bun.Environment;
const FD = bun.FD;
const MAX_PATH_BYTES = bun.MAX_PATH_BYTES;
const c = bun.c; // translated c headers
const jsc = bun.jsc;
const libc_stat = bun.Stat;
const darwin_nocancel = bun.darwin.nocancel;
const c = bun.c; // translated c headers
const recv_flags_nonblock = c.MSG_DONTWAIT;
const windows = bun.windows;
const kernel32 = bun.windows.kernel32;
const ntdll = bun.windows.ntdll;

View File

@@ -0,0 +1,61 @@
import { expect, test } from "bun:test";
import { bunEnv, tempDir } from "harness";
import { join } from "path";
// Regression test for https://github.com/oven-sh/bun/issues/27383
// Standalone executables with inline sourcemaps could crash with
// "panic: incorrect alignment" on Windows ARM64 (ReleaseSafe builds)
// because @alignCast promoted the alignment of sourcemap byte slices
// inside the LazySourceMap union from 1 to 8, but serialized sourcemap
// data in standalone binaries can be at any offset.
test("standalone compile with inline sourcemap does not crash from alignment", async () => {
// Use files with varying name lengths to increase the chance of
// non-8-byte-aligned sourcemap offsets in the standalone binary.
using dir = tempDir("issue-27383", {
"a.js": `export function a() { throw new Error("error from a"); }`,
"bb.js": `export function bb() { throw new Error("error from bb"); }`,
"ccc.js": `export function ccc() { throw new Error("error from ccc"); }`,
"ddddd.js": `export function ddddd() { throw new Error("error from ddddd"); }`,
"entry.js": `
import { a } from "./a.js";
import { bb } from "./bb.js";
import { ccc } from "./ccc.js";
import { ddddd } from "./ddddd.js";
const fns = [a, bb, ccc, ddddd];
const fn = fns[Math.floor(Math.random() * fns.length)];
try { fn(); } catch (e) {
// Accessing the stack triggers sourcemap parsing
console.log(e.stack);
}
`,
});
const result = await Bun.build({
entrypoints: [join(String(dir), "entry.js")],
compile: true,
sourcemap: "inline",
});
expect(result.success).toBe(true);
expect(result.outputs.length).toBe(1);
const executablePath = result.outputs[0].path;
await using proc = Bun.spawn({
cmd: [executablePath],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// The stack trace should contain original file names (sourcemap worked)
expect(stdout).toMatch(/error from (a|bb|ccc|ddddd)/);
// Should not crash
expect(exitCode).toBe(0);
});

View File

@@ -0,0 +1,57 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
// Regression test for #27389: recvfrom() was called with MSG_NOSIGNAL which
// is only valid for send operations. This caused EINVAL in strict environments
// like gVisor (Google Cloud Run). The fix removes MSG_NOSIGNAL from recv flags.
//
// On standard Linux the kernel silently ignores the invalid flag, so we verify
// the fix by ensuring socket recv operations complete without error.
test("socket recv works without EINVAL from invalid flags", async () => {
// Start a simple echo server and client that exercises the recv path
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const server = Bun.listen({
hostname: "127.0.0.1",
port: 0,
socket: {
open(socket) {},
data(socket, data) {
// Echo back the data
socket.write(data);
socket.end();
},
},
});
const client = await Bun.connect({
hostname: "127.0.0.1",
port: server.port,
socket: {
open(socket) {
socket.write("hello");
},
data(socket, data) {
console.log(Buffer.from(data).toString());
socket.end();
},
close() {
server.stop(true);
},
},
});
`,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout.trim()).toBe("hello");
expect(exitCode).toBe(0);
});