Skip to content

fix(runtime): restore SIGSEGV signal handler for non-wasm platforms#1383

Merged
xushiwei merged 1 commit intogoplus:mainfrom
luoliwoshang:feat/nil/recover
Nov 11, 2025
Merged

fix(runtime): restore SIGSEGV signal handler for non-wasm platforms#1383
xushiwei merged 1 commit intogoplus:mainfrom
luoliwoshang:feat/nil/recover

Conversation

@luoliwoshang
Copy link
Member

@luoliwoshang luoliwoshang commented Nov 3, 2025

Summary

This PR fixes Issue #1364 by implementing a SIGSEGV signal handler that converts segmentation faults from nil pointer dereferences into recoverable Go panics, matching standard Go runtime behavior.

Changes

  1. Add SIGSEGV signal handler (runtime/internal/runtime/z_rt_default.go)

    • Converts SIGSEGV signals to Go panics with standard error message
    • Enables recover() to catch nil pointer dereference errors
    • Uses build tag !wasm && !baremetal to exclude wasm and baremetal platforms
  2. Add regression test (_demo/go/recover-nil/main.go)

    • Verifies nil pointer dereference can be recovered
    • Ensures signal handler works correctly on non-wasm platforms

Implementation Details

Why use hardcoded constant SIGSEGV = 0xb?

SIGSEGV has the same value (11 / 0xb) across all Unix-like systems (Linux, Darwin, BSD, Solaris, etc.). To avoid unnecessary dependencies, we define the constant directly instead of importing from syscall packages.

Dependency issues when importing syscall:

Using import "syscall" causes linking failures in c-shared/c-archive build modes:

ld64.lld: error: undefined symbol: internal/reflectlite.Value.IsNil
ld64.lld: error: undefined symbol: internal/reflectlite.TypeOf
ld64.lld: error: undefined symbol: internal/reflectlite.init
ld64.lld: error: undefined symbol: syscall.init

Using import "github.com/goplus/llgo/runtime/internal/clite/syscall" also fails:

ld64.lld: error: undefined symbol: internal/reflectlite.TypeOf
ld64.lld: error: undefined symbol: internal/reflectlite.init
ld64.lld: error: undefined symbol: sync.init

Root cause: Both syscall packages import the errors package, which depends on internal/reflectlite and sync. In c-shared/c-archive build modes, these internal packages are not fully linked by llgo's linker, causing undefined symbol errors.

Solution: Use hardcoded constant SIGSEGV = 0xb to completely avoid the dependency chain.

Testing

Tested on all build modes:

  • ✅ exe mode: llgo run - works
  • ✅ c-shared mode: llgo build -buildmode=c-shared - works
  • ✅ c-archive mode: llgo build -buildmode=c-archive - works

Related

@gemini-code-assist
Copy link

Summary of Changes

Hello @luoliwoshang, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request reintroduces the SIGSEGV signal handler for non-WebAssembly platforms within the llgo runtime. The primary goal is to ensure that nil pointer dereferences result in recoverable Go panics, rather than causing immediate segmentation faults, thereby enhancing the robustness and predictability of applications. This change is implemented using build tags to maintain full compatibility with WebAssembly environments, where such signal handling is not applicable.

Highlights

  • Signal Handler Restoration: The SIGSEGV signal handler has been restored for non-WebAssembly (wasm) platforms, re-enabling proper error handling for nil pointer dereferences.
  • Recoverable Panics: Nil pointer dereferences on non-wasm platforms will now trigger recoverable Go panics instead of immediate segmentation faults, aligning with standard Go behavior.
  • Platform Compatibility: Wasm platform compatibility is maintained by using build tags, ensuring the signal handler is only active on non-wasm environments.
  • Code Reorganization: The signal handler logic has been moved from z_rt.go to a new platform-specific file, z_rt_default.go, improving code organization.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly restores the SIGSEGV signal handler for non-wasm platforms by using a platform-specific build file. This change ensures that nil pointer dereferences result in a recoverable Go panic, which aligns with standard Go behavior. The implementation is clean and follows best practices for platform-specific code. I have one suggestion to simplify the new signal handler by removing a redundant switch statement.

Comment on lines 13 to 21
signal.Signal(c.Int(syscall.SIGSEGV), func(v c.Int) {
switch syscall.Signal(v) {
case syscall.SIGSEGV:
panic(errorString("invalid memory address or nil pointer dereference"))
default:
var buf [20]byte
panic(errorString("unexpected signal value: " + string(itoa(buf[:], uint64(v)))))
}
})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The signal handler is registered specifically for syscall.SIGSEGV, so it will only be invoked for this signal. The switch statement is therefore redundant, and the default case is unreachable code. You can simplify the handler and remove the unused v parameter by renaming it to _.

	signal.Signal(c.Int(syscall.SIGSEGV), func(_ c.Int) {
		panic(errorString("invalid memory address or nil pointer dereference"))
	})

@@ -0,0 +1,22 @@
//go:build !wasm

package runtime
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Documentation

This file lacks explanatory comments. Given the platform-specific nature and relationship to PR #1059, please add:

// This file contains platform-specific runtime initialization for non-wasm targets.
// The SIGSEGV signal handler enables Go-style panic recovery for nil pointer dereferences
// instead of immediate process termination.
//
// For wasm platform compatibility, signal handling is excluded via build tags.
// See PR #1059 for wasm platform requirements.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

signal.Signal(c.Int(syscall.SIGSEGV), func(v c.Int) {
switch syscall.Signal(v) {
case syscall.SIGSEGV:
panic(errorString("invalid memory address or nil pointer dereference"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Security: Non-Async-Signal-Safe Operations

This signal handler violates async-signal-safety by calling panic(), which allocates memory via c.Malloc(). Per POSIX standards, signal handlers should only call async-signal-safe functions.

Risk: If SIGSEGV occurs while inside malloc/free, the handler calling malloc again can cause deadlock or memory corruption.

Recommendation: Consider pre-allocating the error value and using only async-signal-safe operations in the handler, similar to how standard Go's runtime handles signals with dedicated signal stacks and atomic operations.

@xgopilot
Copy link
Contributor

xgopilot bot commented Nov 3, 2025

Code Review Summary

Overall: Good approach to platform-specific signal handling with proper build tags. Implementation successfully restores nil pointer panic behavior for non-wasm platforms.

Key Issues:

  1. Missing Apache License header in new file
  2. Missing code documentation explaining platform-specific behavior
  3. Signal handler uses non-async-signal-safe operations (potential deadlock risk)

The security concern around async-signal-safety is the most significant technical issue, though it matches the original commented code pattern.

@luoliwoshang luoliwoshang force-pushed the feat/nil/recover branch 5 times, most recently from 93532a1 to 11690ba Compare November 3, 2025 10:41
@codecov
Copy link

codecov bot commented Nov 3, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.98%. Comparing base (830e8e7) to head (2363d28).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1383   +/-   ##
=======================================
  Coverage   90.98%   90.98%           
=======================================
  Files          43       43           
  Lines       11287    11287           
=======================================
  Hits        10269    10269           
  Misses        859      859           
  Partials      159      159           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@luoliwoshang luoliwoshang force-pushed the feat/nil/recover branch 5 times, most recently from 45a2521 to c5a32bc Compare November 6, 2025 08:15
@luoliwoshang luoliwoshang changed the title feat(runtime): restore SIGSEGV signal handler for non-wasm platforms fix(runtime): restore SIGSEGV signal handler for non-wasm platforms Nov 6, 2025
@luoliwoshang luoliwoshang force-pushed the feat/nil/recover branch 2 times, most recently from 6033929 to 6c36935 Compare November 10, 2025 06:02
…ference to recoverable panic

- Add z_rt_default.go with signal handler for SIGSEGV on non-wasm platforms
- Convert segmentation faults from nil pointer access to Go panic
- Enable recover() to catch nil pointer dereference errors
- Use build tag (!wasm) to maintain wasm platform compatibility
- Remove commented-out signal handling code from z_rt.go

This aligns llgo's behavior with standard Go, where accessing nil pointer
fields triggers a recoverable panic instead of immediate program crash.

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

Co-Authored-By: Claude <noreply@anthropic.com>
@luoliwoshang
Copy link
Member Author

@xushiwei

@xushiwei xushiwei merged commit 7ce2733 into goplus:main Nov 11, 2025
42 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

go/types scope.Insert got segmentation fault

3 participants