`#pragma include_alias` registers the wrong alias key when spelling cleaning is needed
- 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
Assessment
This issue has not been assessed yet.