Skip to content

fix: normalize 32-bit slice bounds#1680

Merged
cpunion merged 7 commits intogoplus:mainfrom
luoliwoshang:wip/fix-32bit-slice-index-regression
Mar 11, 2026
Merged

fix: normalize 32-bit slice bounds#1680
cpunion merged 7 commits intogoplus:mainfrom
luoliwoshang:wip/fix-32bit-slice-index-regression

Conversation

@luoliwoshang
Copy link
Member

@luoliwoshang luoliwoshang commented Mar 4, 2026

Summary

  • normalize low/high/max to target int width in SSA Slice before calling runtime helpers
  • add ESP serial startup regression output for int64 string slicing (slice64 ok)
  • make smoke assertion compare expected output tail by N lines (instead of only the last line)

Root Cause

On 32-bit targets, passing wider bounds (for example int64) directly into runtime slice helpers with int semantics can cause incorrect behavior (including hangs/no auto-exit).

Changes

  • ssa/datastruct.go
    • in Builder.Slice, apply fitIntSize to non-nil low/high/max before helper calls
  • _demo/embed/test-esp-serial-startup.sh
    • add int64 slicing regression output in generated main
    • update smoke matcher to validate expected tail lines

Verification

  • go install ./cmd/llgo
  • _demo/embed/test-esp-serial-startup.sh
    • ESP32-C3: pass (tail includes Hello World + slice64 ok)
    • ESP32: pass for this smoke case with local QEMU installed

Fixes #1679

@gemini-code-assist
Copy link

Summary of Changes

Hello, 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 addresses a regression where string slicing with int64 indices could cause issues or hangs on 32-bit esp32c3 targets. It ensures that all slice bounds are correctly sized to the target's integer width before being passed to runtime functions, thereby preventing incorrect behavior. A new test case has been introduced to specifically verify the fix for int64 string slicing.

Highlights

  • Slice Index Normalization: Normalized Slice bounds (low, high, max) to the target integer width before calling runtime slice helpers, addressing potential issues with wider integer indices on 32-bit targets.
  • Regression Test Added: Added a temporary esp32c3 serial startup regression output to verify int64 string slicing behavior in the generated func main().

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • _demo/embed/test-esp-serial-startup.sh
    • Added a Go code snippet to the main function to test int64 string slicing.
    • Updated the expected output for esp32c3-basic and esp32 emulator smoke tests to reflect the new slicing test.
  • ssa/datastruct.go
    • Modified the Slice builder to apply fitIntSize to low, high, and max slice bounds, ensuring they conform to the target integer width.
Activity
  • No specific activity (comments, reviews) has been recorded for this pull request yet.
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.

@luoliwoshang luoliwoshang marked this pull request as draft March 4, 2026 08:47
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 addresses a regression on 32-bit targets like esp32c3 where using a 64-bit integer for string slicing could lead to failures. While the fix aims to normalize slice bounds, the current implementation uses unsafe integer truncation which bypasses Go's bounds checking mechanism. This introduces a critical vulnerability, allowing for out-of-bounds memory access when using large int64 indices on 32-bit systems. A more robust approach that performs range checks before truncation is required to maintain memory safety. A temporary regression test has been added, and there is one minor suggestion to improve its readability.

s := "hello"
var idx int64 = 1
tail := s[idx:]
if len(tail) == 4 && tail[0] == 'e' && tail[1] == 'l' && tail[2] == 'l' && tail[3] == 'o' {

Choose a reason for hiding this comment

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

medium

For improved readability and conciseness, you can directly compare the tail slice with the expected string literal. This makes the intent of the check clearer.

Suggested change
if len(tail) == 4 && tail[0] == 'e' && tail[1] == 'l' && tail[2] == 'l' && tail[3] == 'o' {
if tail == "ello" {

Comment on lines +344 to 352
} else {
low = b.fitIntSize(low)
}
if !high.IsNil() {
high = b.fitIntSize(high)
}
if !max.IsNil() {
max = b.fitIntSize(max)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

A brief comment above this block would help future maintainers understand the motivation, e.g.:

Suggested change
} else {
low = b.fitIntSize(low)
}
if !high.IsNil() {
high = b.fitIntSize(high)
}
if !max.IsNil() {
max = b.fitIntSize(max)
}
} else {
// Normalize bounds to target int width; on 32-bit targets,
// wider integer indices (e.g. int64) must be cast to int
// to match runtime helper signatures (StringSlice, NewSlice3).
low = b.fitIntSize(low)
}
if !high.IsNil() {
high = b.fitIntSize(high)
}
if !max.IsNil() {
max = b.fitIntSize(max)
}

func main() {
c.Printf(c.Str("Hello World\n"))
s := "hello"
var idx int64 = 1
Copy link
Contributor

Choose a reason for hiding this comment

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

Changing the expected output from "Hello World" to "slice64 ok" means the smoke test no longer independently validates basic c.Printf / c.Str functionality. The program still prints "Hello World" first (line 86), but run_emulator_smoke only checks the last non-empty line, so a silent regression in the basic print path would go undetected.

Consider checking for both strings in the output rather than replacing the original validation.

Copy link
Member Author

Choose a reason for hiding this comment

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

already change to compare all line bbe037c

@xgopilot
Copy link
Contributor

xgopilot bot commented Mar 4, 2026

Review Summary

The core fix in ssa/datastruct.go is correct — normalizing slice bounds via fitIntSize before runtime helper calls mirrors the existing MakeSlice pattern and aligns with Go's semantics on 32-bit targets. No performance or security concerns.

Key suggestions:

  • Add an inline comment explaining why fitIntSize is applied to slice bounds.
  • The smoke test replaces "Hello World" validation instead of supplementing it — consider checking both strings.
  • Test coverage is narrow (only signed int64 low-bound on string). Consider also testing uint64 indices, high/max bounds, and actual slice types (not just strings).
  • No cl/_test* unit test for this code path; on 64-bit CI hosts fitIntSize is a no-op, so the fix is effectively untested in CI.

@codecov
Copy link

codecov bot commented Mar 4, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.16%. Comparing base (eda01de) to head (cac15cc).
⚠️ Report is 21 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1680   +/-   ##
=======================================
  Coverage   93.16%   93.16%           
=======================================
  Files          48       48           
  Lines       13314    13320    +6     
=======================================
+ Hits        12404    12410    +6     
  Misses        724      724           
  Partials      186      186           

☔ 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 wip/fix-32bit-slice-index-regression branch from 8074167 to bbe037c Compare March 9, 2026 07:12
@luoliwoshang luoliwoshang changed the title [WIP] fix esp32c3 int64 slice index regression in emulator startup smoke fix: normalize 32-bit slice bounds and add esp emulator regression coverage Mar 9, 2026
@luoliwoshang luoliwoshang marked this pull request as ready for review March 9, 2026 08:42
@luoliwoshang luoliwoshang changed the title fix: normalize 32-bit slice bounds and add esp emulator regression coverage fix: normalize 32-bit slice bounds Mar 9, 2026
Comment on lines +344 to 352
} else {
low = b.fitIntSize(low)
}
if !high.IsNil() {
high = b.fitIntSize(high)
}
if !max.IsNil() {
max = b.fitIntSize(max)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Two observations on this block:

  1. Missing explanatory comment. The root cause (32-bit targets mishandling wider-than-int bounds) is well described in the PR body but absent from the source. A brief comment here would prevent future contributors from removing or reordering these calls without understanding why they exist. For example:
// Normalize slice bounds to target int width: on 32-bit targets,
// wider types (e.g. int64) must be truncated before passing to
// runtime helpers that expect int-sized arguments (see #1679).
  1. No repeatable unit test. The fix is only exercised by the ESP emulator smoke test, which requires specific toolchains/emulators. Consider adding a cl/_testgo/ or _cmptest/ test case with int64 slice bounds so the fix is covered by go test ./... on any host.

Comment on lines +75 to +76
local actual_tail
actual_tail=$(printf "%s\n" "$normalized_out" | awk 'NF{print}' | tail -n "$n")
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: actual_tail is filtered through awk 'NF{print}' (stripping blank lines), but normalized_expected is not. This asymmetry works today because the expected strings have no blank lines, but it will silently break if someone adds an expected string containing an intentional blank line — the actual side would strip it while the expected side preserves it, causing a spurious mismatch.

Consider either applying the same awk 'NF{print}' filter to normalized_expected, or adding a comment documenting this intentional asymmetry.

@xgopilot
Copy link
Contributor

xgopilot bot commented Mar 9, 2026

The core fix in ssa/datastruct.go is clean and follows the existing fitIntSize pattern already used by MakeSlice. On 64-bit hosts the size comparison short-circuits immediately, so there is zero overhead in the common case. The shell script refactor for multi-line tail matching is a solid improvement.

Two items worth addressing: (1) a brief code comment explaining why fitIntSize is needed here (the #1679 root cause), and (2) a repeatable unit test (e.g. in cl/_testgo/ or _cmptest/) so the fix is exercised by go test ./... without requiring ESP toolchains. See inline comments for details.

Comment on lines 113 to 117
@@ -93,10 +117,10 @@ echo ""
echo "=== ESP Serial Smoke Tests: Build + Emulator Run ==="
Copy link
Collaborator

Choose a reason for hiding this comment

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

Split test cases and test runner

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

@luoliwoshang luoliwoshang force-pushed the wip/fix-32bit-slice-index-regression branch from 4b9ad0c to bcf60e0 Compare March 10, 2026 06:18
@luoliwoshang luoliwoshang requested a review from cpunion March 10, 2026 08:01
@cpunion cpunion merged commit 8301bc3 into goplus:main Mar 11, 2026
43 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.

32-bit targets: wide slice bounds (e.g. int64) can miscompile/hang (esp32c3-basic repro)

2 participants