llvm / llvm/llvm-project

`#pragma include_alias` registers the wrong alias key when spelling cleaning is needed

Open
#196,478 2 comments 0 reactions 0 assignees View on GitHub
clang:frontend
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

## What behavior we encountered

While testing `#pragma include_alias` with `-ftrigraphs`, I found that the alias is not applied when both pragma operands need spelling cleanup.

This reproduces on an unmodified Clang built from upstream commit d593279c0b2891f0b0c8af3f70a1a0383b4ad1b5.

`??=` is the trigraph for `#`, so the effective filenames below are `a#b.h` and `c#d.h`.

Create `/tmp/include_alias_repro/c#d.h`:

```c
int target = 1;
```

`control.c`:

```c
#pragma include_alias("a??=b.h", "c#d.h")
#include "a#b.h"
int main(void) { return target; }
```

`test.c`:

```c
#pragma include_alias("a??=b.h", "c??=d.h")
#include "a#b.h"
int main(void) { return target; }
```

Commands:

```sh
clang -cc1 -fms-extensions -ftrigraphs -I /tmp/include_alias_repro \
-fsyntax-only control.c

clang -cc1 -fms-extensions -ftrigraphs -I /tmp/include_alias_repro \
-fsyntax-only test.c
```

Observed results:

- `control.c` succeeds.
- `test.c` fails with:

```text
fatal error: 'a#b.h' file not found
```

## What is the intended behavior

Both files should succeed.

Under `-ftrigraphs`, these two pragmas should be equivalent:

```c
#pragma include_alias("a??=b.h", "c#d.h")
#pragma include_alias("a??=b.h", "c??=d.h")
```

In both cases, the alias should map:

```text
"a#b.h" -> "c#d.h"
```

and `#include "a#b.h"` should resolve to the existing file `c#d.h`.

## Possible root cause analysis

A likely cause is in `Preprocessor::HandlePragmaIncludeAlias` in `clang/lib/Lex/Pragma.cpp`.

The function uses a single `SmallString<128> FileNameBuffer` for two consecutive `getSpelling` calls:

```c++
StringRef SourceFileName;
SmallString<128> FileNameBuffer;

SourceFileName = getSpelling(SourceFilenameTok, FileNameBuffer);
FileNameBuffer.clear();
ReplaceFileName = getSpelling(ReplaceFilenameTok, FileNameBuffer);
```

`getSpelling` may return a `StringRef` that points into the caller-provided buffer when the token needs cleaning. That happens here because trigraphs require spelling cleanup.

`SourceFileName` is already a stale non-owning view after `FileNameBuffer.clear()`. In practice it may appear to keep working only because `SmallString::clear()` resets the logical size but does not erase the underlying bytes. If both header-name tokens need cleaning, the second `getSpelling` call then reuses and overwrites `FileNameBuffer`, while `SourceFileName` still refers to bytes in that same storage. Later, `OriginalSource` is taken from `SourceFileName` and passed to `AddIncludeAlias`, so the source alias key is no longer the original source name.

That would explain the observed behavior:

- when only one operand needs cleaning, the alias can still be registered correctly
- when both operands need cleaning, the source key is corrupted before insertion

This looks like a non-owning `StringRef` / shared scratch buffer lifetime bug: the code keeps a borrowed view alive across mutation of its backing storage.

Possibly introduced by 611306eae6c901916443b528b4fe789fd1574a90

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.