https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127857
Bug ID: 127857
Summary: [GCC C++ contracts] a contract condition that spans a
whitespace-only line: ICE in `char_span::subspan`
(file-cache.h:52) from `get_source_text_between`
Product: gcc
Version: 17.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: 0599jiangyc at gmail dot com
Target Milestone: ---
**Repo:** gcc-mirror/gcc · **Commit:** `d373724d1` (master, 2026-09-17) ·
**Build:** `--enable-languages=c,c++ --disable-bootstrap
--enable-checking=yes,extra,rtl` (x86_64-pc-linux-gnu, Ubuntu 22.04)
## What happens
`build_comment` (cp/contracts.cc) asks `get_source_text_between` (input.cc) for
the source text of
the contract predicate. For a multi-line range, the loop over the lines
"combines spaces at the
beginning of later lines":
```c++
for (off = 0; off < line.length(); ++off)
if (line[off] != ' ' && line[off] != '\t')
break;
if (off > 0)
{
obstack_1grow (&buf_obstack, ' ');
line = line.subspan (off, line.length() - off);
}
```
When a middle line consists only of blanks, `off == line.length ()`, and
`subspan (off, 0)` trips `gcc_assert (offset < (int)m_n_elts)`. (The first-line
branch has the
same shape: `line.length () < off` lets `off == line.length ()` through to
`subspan`.) The
contract itself is valid; the source text is only used for the violation
message.
```
gcc-contract-condition-whitespace-only-line.cc:3:5: internal compiler error: in
subspan, at file-cache.h:52
3 | > 0);
| ^
0x3046dcb internal_error(char const*, ...)
gcc/diagnostic-global-context.cc:787
0xdd9a1e fancy_abort(char const*, int, char const*)
gcc/diagnostics/context.cc:1813
0xddca85 diagnostics::char_span::subspan(int, int) const
gcc/diagnostics/file-cache.h:52
0xddca85 get_source_text_between(diagnostics::file_cache&, unsigned long,
unsigned long)
gcc/input.cc:181
0xe9226b build_comment
gcc/cp/contracts.cc:2061
0xe9469e grok_contract(tree_node*, tree_node*, tree_node*, cp_expr, unsigned
long)
gcc/cp/contracts.cc:2136
0xff25d3 cp_parser_function_contract_specifier
gcc/cp/parser.cc:34112
0xff25d3 cp_parser_function_contract_specifier_seq
gcc/cp/parser.cc:34156
0xfed726 cp_parser_direct_declarator
gcc/cp/parser.cc:26926
```
## Reproducer
`gcc-contract-condition-whitespace-only-line.cc` (line 2 is two spaces):
```c++
void f (int x) pre (x
> 0);
```
```bash
g++ -fsyntax-only -std=c++26 gcc-contract-condition-whitespace-only-line.cc
```
Also ICEs with `-std=c++20 -fcontracts`. An empty line 2 (no blanks) is fine.
Inside a class the same predicate ICEs through
`cp_parser_late_contract_condition` → `update_late_contract` → `build_comment`.
## Expected
The condition text `x > 0` (or a fallback to `expr_to_string`); never an
assertion. Checking `off < line.length ()` before both `subspan` calls, or
skipping a blank-only middle line the way an empty one is skipped, would do.
## Notes
Found by fusion: in a member contract `pre (px->m ())` from a g++.dg contracts
test, the fusion substituted the macro `O_NOATIME` (defined 156 lines earlier
by the other seed) for `px`. The range then starts at the macro's *spelling*
location — `expand_location_to_spelling_point` — and runs from the `#define`
line to the contract, across blank-only lines. So a second trigger, with no
whitespace inside the condition itself, is a condition that begins with an
object-like macro defined above a whitespace-only line:
```c++
#define M 1
int f (int x) pre (M
> 0);
```
(line 2 is two spaces; verified to ICE the same way). Using the expansion point
rather than the spelling point would be the more fundamental fix for the
garbled comment text. Neither seed alone crashes. 3/3 reproductions,
empty-input control clean. `gcc/input.cc` is unchanged upstream since the
checkout; the two `contracts.cc` commits since (PR127281, PR127295) do not
touch `build_comment`.