Skip to content

Read constant pointer offsets and slice lengths from the pointer - #4976

Open
srivatsansamraj wants to merge 2 commits into
model-checking:mainfrom
srivatsansamraj:const-subslice-fat-pointer
Open

srivatsansamraj wants to merge 2 commits into
model-checking:mainfrom
srivatsansamraj:const-subslice-fat-pointer

Conversation

@srivatsansamraj

@srivatsansamraj srivatsansamraj commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

codegen_const_ptr built a constant &[T] or &str from the start and the length of the whole allocation it points into. A constant subslice such as A.split_at(1).1 therefore got the data and the length of A, and Kani proved assertions about it that are false when the program runs (#4971). A constant empty slice with a dangling pointer has no provenance and hit the "Expected &[T] to point to a single buffer" assertion (#3021).

While working on #4971, I noticed that a thin constant reference into another constant has the same problem: for const R: &u8 = &A[1], Kani read A[0], because the offset it passed on was the pointer's position inside the constant, which is always 0. This PR fixes it as well.

The offset, and a slice's length, are now read from the constant's own bytes, and the address itself is used when there is no provenance. A &str that is not a whole literal records nothing in str_literals; whole literals take the unchanged path. One visible change: a constant substring used as a kani::assert message is now rejected like any other non-literal message, where before it printed the enclosing literal.

tests/kani/Slice/const_subslice.rs checks the native values of subslices, substrings and element references, with u32 elements so that a byte offset and an element index differ, and through a symbolic index that rustc cannot fold into a constant. tests/expected/const-subslice pins the six whole-allocation assertions as failures; all six verified on main. const_dangling_empty.rs is the program from #3021. const_zst_slice.rs and const_str_non_utf8.rs cover two compiler crashes the same change removes.

Not included: a constant slice that points into a static still crashes the compiler, for a different reason.

Resolves #4971
Resolves #3021

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.

@srivatsansamraj
srivatsansamraj requested review from a team as code owners October 5, 2026 23:49
@srivatsansamraj

Copy link
Copy Markdown
Contributor Author

@CYJ904 @Tianshu-Huang @wodex1nhaoIeng @acearyanarun for review. Kani built every constant slice and string from the whole constant it points into, so [1, 2, 3, 4].split_at(1).1 looked like [1, 2, 3, 4] to Kani, and Kani proved false assertions about it (#4971, which we filed today). It now reads where the slice starts and how long it is from the slice itself. The same mistake made a constant reference like &A[1] read A[0], which this fixes too, along with three compiler crashes on constant slices, #3021 among them. We have not measured its effect on the standard-library run.

@github-actions github-actions Bot added Z-EndToEndBenchCI Tag a PR to run benchmark CI Z-CompilerBenchCI Tag a PR to run benchmark CI labels Oct 6, 2026
@srivatsansamraj
srivatsansamraj force-pushed the const-subslice-fat-pointer branch from 1480260 to 83a492e Compare October 9, 2026 16:53
@feliperodri feliperodri self-assigned this Oct 22, 2026
@feliperodri feliperodri added [C] Bug This is a bug. Something isn't working. [F] Crash Kani crashed Z-Autoharness Issue related to autoharness subcommand labels Nov 6, 2026
@feliperodri feliperodri added this to the Autoharness milestone Nov 6, 2026
@feliperodri feliperodri removed the Z-Autoharness Issue related to autoharness subcommand label Nov 6, 2026
@feliperodri feliperodri removed this from the Autoharness milestone Nov 6, 2026
@feliperodri
feliperodri requested a balanced review from Copilot November 6, 2026 18:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

3 open issues
What changed in this PR

Fixes constant pointer codegen to respect the pointer’s own offset/length metadata (rather than the full allocation), preventing unsound proofs and compiler ICEs for subslices, substrings, element refs, and dangling empty slices/strings.

Changes:

  • Read data offset (and fat-pointer length) from the constant pointer bytes in codegen_const_ptr, and use the address directly when provenance is absent.
  • Adjust &str handling so only whole literals follow the literal extraction path; substrings are treated as non-literals.
  • Add regression tests and expected-failure outputs covering subslices/element refs, ZST slices, dangling empty slices/strings, and non-UTF8 backing bytes.
File Description
tests/​kani/​Slice/​const_zst_slice.rs Reproduces and guards against ZST-slice length computation ICEs.
tests/​kani/​Slice/​const_subslice.rs Validates correct offsets/lengths for subslices, substrings, and element references (incl. symbolic indexing).
tests/​kani/​Slice/​const_str_non_utf8.rs Ensures substring &str over valid UTF-8 region doesn’t decode whole non-UTF8 allocation (no ICE).
tests/​kani/​Slice/​const_dangling_empty.rs Confirms dangling empty slices/strings codegen uses the stored address when provenance is absent (no ICE).
tests/​expected/​const-subslice/​main.rs Expected-failure harnesses that must fail when subslices/refs are handled correctly.
tests/​expected/​const-subslice/​expected Pins the 6 expected failures for the above harnesses.
kani-compiler/​src/​codegen_cprover_gotoc/​codegen/​operand.rs Core fix: derive offset/len from pointer bytes; handle no-provenance pointers; treat substrings as non-literals.

Review effort: Lite

Comment on lines +284 to +286
let offset = alloc.read_partial_uint(0..pointer_size).unwrap();
if self.use_fat_pointer_stable(inner_ty) {
let len = alloc.read_partial_uint(pointer_size..2 * pointer_size).unwrap();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in fbebfa5ed: the reads name the constant's type when they fail.

Comment on lines +291 to +297
let Some(&(_, prov)) = alloc.provenance.ptrs.first() else {
// A pointer with no provenance (an empty slice or `&str` from a dangling
// pointer, for example) holds its address in the offset.
let data_expr = Expr::size_constant(offset, &self.symbol_table)
.cast_to(Type::unsigned_int(8).to_pointer());
return slice_fat_ptr(typ, data_expr, len_expr, &self.symbol_table);
};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in fbebfa5ed: more than one provenance entry is an assertion failure again, and none is still handled.

let alloc_id = alloc.provenance.ptrs[0].1.0;
let typ = self.codegen_ty_stable(ty);
self.codegen_alloc_pointer(typ, alloc_id, ptr.0, loc)
self.codegen_alloc_pointer(typ, alloc_id, offset.try_into().unwrap(), loc)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in fbebfa5ed, with the offset and the type in the message.

@feliperodri feliperodri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The fix is right: a constant subslice now takes its offset and length from its own pointer, and the native-value tests show it.

Before merge:

  1. The old code asserted one provenance entry, and the new code takes .first() and ignores the rest. Please keep an assert, or document why more than one can't occur.
  2. Replace the unwrap() calls with expect(...) messages, as Copilot suggests, so a malformed constant gives a readable panic.
  3. audit fails. Please confirm it fails on main too. Six jobs are cancelled. Please re-run them.
  4. The branch is behind main. Please update it.

`codegen_const_ptr` built a constant `&[T]` or `&str` from the start
and the length of the whole allocation it points into, so a constant
subslice such as `A.split_at(1).1` got the data and the length of `A`
(model-checking#4971), and a constant empty slice with a dangling pointer, which has
no provenance, hit an assertion in the compiler (model-checking#3021). A thin
constant reference such as `&A[1]` read `A[0]` for the same reason:
the offset it passed on was the pointer's position inside the
constant, which is always 0. The offset, and a slice's length, are now
read from the constant's own bytes, the address itself is used when
there is no provenance, and a `&str` that is not a whole literal
records nothing in `str_literals`, so `extract_const_message` cannot
return the enclosing literal for it. The tests in
`tests/kani/Slice/const_subslice.rs` check the native values of such
subslices and references, also through a symbolic index that rustc
cannot fold into a constant, `tests/expected/const-subslice` checks
that the whole-allocation values now fail, `const_dangling_empty.rs`
is the program from model-checking#3021, and `const_zst_slice.rs` and
`const_str_non_utf8.rs` cover two compiler crashes in the same arms,
for zero-sized elements and for a `&str` inside bytes that are not
UTF-8.
Taking the first provenance entry would silently pick one buffer if a
constant slice or string ever had more than one. Assert at most one, as
before, now that a pointer with none is handled. The pointer reads also
name the constant's type when they fail.
@srivatsansamraj
srivatsansamraj force-pushed the const-subslice-fat-pointer branch from 83a492e to fbebfa5 Compare November 7, 2026 03:32
@srivatsansamraj

Copy link
Copy Markdown
Contributor Author

@feliperodri rebased onto main, which reruns the cancelled jobs; the assert and the messages are in fbebfa5ed. Cargo Deny passes on main; the failure came from the old base.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[C] Bug This is a bug. Something isn't working. [F] Crash Kani crashed Z-CompilerBenchCI Tag a PR to run benchmark CI Z-EndToEndBenchCI Tag a PR to run benchmark CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Constant subslice is modelled as the whole allocation ice: Expected '&[T]' to point to a single buffer

3 participants