FStarLang / FStarLang/pal

Same-named struct tags in different source files are merged, making verification depend on input order

Open
#299 0 comments 0 reactions 0 assignees View on GitHub
bug
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.