Same-named struct tags in different source files are merged, making verification depend on input order
- Dominant language
- Rust
- Stars
- 11
- Forks
- 1
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 47
Description
## Summary
PAL merges unrelated struct declarations from different translation units solely because their tags have the same spelling.
This can silently change field types and values. A false contract verifies in one input-file order and is rejected in the reverse order.
Reproduced on `main` at `9ec4165`.
## Reproducer
`a.c`:
```c
#include "pal.h"
struct state {
unsigned value;
};
unsigned wide(void) _ensures(return == 300)
{
struct state s = { .value = 300 };
return s.value;
}
unsigned wide_wrong(void) _ensures(return == 44)
{
struct state s = { .value = 300 };
return s.value;
}
```
`b.c`:
```c
#include "pal.h"
struct state {
unsigned char value;
};
unsigned narrow(void) _ensures(return == 7)
{
struct state s = { .value = 7 };
return s.value;
}
```
Translate and verify both input orders:
```sh
pal --outdir out-ab a.c b.c
pal --outdir out-ba b.c a.c
```
## Expected behavior
These structs are separate types used only within their respective source files. No struct objects or pointers are passed between translation units.
Native execution returns:
- `wide()`: `300`
- `wide_wrong()`: `300`
- `narrow()`: `7`
Regardless of input-file order, `wide` and `narrow` should verify, while `wide_wrong` should fail.
## Actual behavior
With `a.c b.c`, PAL retains the narrow definition of `struct state` and uses it in both files. It inserts a conversion to an 8-bit field, truncating `300` to `44` on the tested target.
| Input order | `wide`: requires result `300` | `wide_wrong`: requires result `44` |
|-------------|--------------------------------|----------------------------------|
| `a.c b.c` | Fails | Verifies |
| `b.c a.c` | Verifies | Fails |
`narrow` verifies in both orders. Neither translation produces PAL diagnostics.
A related example with different field names produces missing-field errors when translated together, although each source file verifies independently.
## Suspected cause
In `src/pass/merge.rs:242–254`, declaration deduplication uses a key containing only the declaration kind and name. Distinct struct declarations from different translation units are therefore treated as duplicates, with the later definition replacing the earlier one.
## Suggested direction
Preserve translation-unit-aware type identity rather than identifying types solely by tag spelling. Distinguish unrelated declarations from genuinely corresponding declarations in shared headers, then allocate distinct output module names where necessary.
Add regression coverage for both input orders and ensure that false contracts cannot become valid merely by adding or reordering another source file.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in src/pass/merge.rs:242–254 and trace how declaration keys and translation-unit identity are represented during merging. Reproduce with a.c and b.c in both input orders, then add regression coverage showing that wide and narrow verify while wide_wrong fails regardless of order.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, rust
- Domain
- compilers, testing
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100