https://github.com/stephenduong1004 created
https://github.com/llvm/llvm-project/pull/234708
[Clang][AST] Fix crash attaching doc comments across files in
attachCommentsToJustParsedDecls
### Summary
After #70550, `getLocsForCommentSearch` returns up to two locations for a
declaration expanded from a macro:
1. The macro expansion location (`DeclLocs[0]`), and
2. The spelling location inside the macro definition (`DeclLocs[1]`), which may
be in a different file (e.g., an `#include`d header).
While #70550 updated `ASTContext::getRawCommentNoCache` to look up
`Comments.getCommentsInFile(File)` for each `Loc` in `Locs`,
`ASTContext::attachCommentsToJustParsedDecls` continued passing
`*CommentsInThisFile` (the comment map for the first declaration in `Decls`) to
`getRawCommentNoCacheImpl` for every `DeclLoc` in `DeclLocs`.
When a declaration group (such as an Objective-C `@implementation` block passed
to `ActOnDocumentableDecls`) contains a declaration in the main file followed
by a declaration expanded from a macro defined in a header:
- `CommentsInThisFile` is the comment map of the `.m` file.
- For `DeclLocs[1]`, `RepresentativeLoc` is in the `.h` file.
- `getRawCommentNoCacheImpl` searches the `.m` file's comment map using the
`.h` file's byte offset (`LocDecomp.second`).
- If an unattached comment in the `.m` file starts at an offset `<=
LocDecomp.second` and ends at an offset `CommentEndOffset > LocDecomp.second`,
`LocDecomp.second - CommentEndOffset` underflows `unsigned` and passes a ~4 GB
length to `StringRef(Buffer + CommentEndOffset, LocDecomp.second -
CommentEndOffset)`, crashing in `Text.find_last_of(";{}#@")` (or reading out of
bounds / attaching an unrelated comment from the wrong file).
This test will crash:
```objc
// RUN: rm -rf %t
// RUN: split-file %s %t
// RUN: %clang_cc1 -fsyntax-only -Wdocumentation -Wno-objc-root-class -I %t
%t/repro.m -verify
//--- header.h
#define DECLARE_MACRO_METHOD() \
- (void)macroMethod {}
//--- repro.m
#include "header.h"
/**
* A multiline doc comment whose byte offset range
* straddles the macro method offset in header.h.
*/
@interface Foo
@end
@implementation Foo
- (void)normalMethod {}
DECLARE_MACRO_METHOD()
@end
// expected-no-diagnostics
```
### Fix
1. In `ASTContext::attachCommentsToJustParsedDecls`, look up the `FileID` for
each `DeclLoc` and use the comment map for that file (reusing
`CommentsInThisFile` when `DeclFile == File`), matching
`ASTContext::getRawCommentNoCache`.
2. In `ASTContext::getRawCommentNoCacheImpl`, guard against `CommentEndOffset >
LocDecomp.second` before subtracting.
AI-assisted
>From 45a48ba4031f5a11e0850af0824763d4ca00d00d Mon Sep 17 00:00:00 2001
From: stephen <[email protected]>
Date: Thu, 22 Oct 2026 17:57:18 +0000
Subject: [PATCH] [Clang][AST] Fix crash attaching doc comments across files in
attachCommentsToJustParsedDecls
### Summary
After #70550, `getLocsForCommentSearch` returns up to two locations for a
declaration expanded from a macro:
1. The macro expansion location (`DeclLocs[0]`), and
2. The spelling location inside the macro definition (`DeclLocs[1]`), which may
be in a different file (e.g., an `#include`d header).
While #70550 updated `ASTContext::getRawCommentNoCache` to look up
`Comments.getCommentsInFile(File)` for each `Loc` in `Locs`,
`ASTContext::attachCommentsToJustParsedDecls` continued passing
`*CommentsInThisFile` (the comment map for the first declaration in `Decls`) to
`getRawCommentNoCacheImpl` for every `DeclLoc` in `DeclLocs`.
When a declaration group (such as an Objective-C `@implementation` block passed
to `ActOnDocumentableDecls`) contains a declaration in the main file followed
by a declaration expanded from a macro defined in a header:
- `CommentsInThisFile` is the comment map of the `.m` file.
- For `DeclLocs[1]`, `RepresentativeLoc` is in the `.h` file.
- `getRawCommentNoCacheImpl` searches the `.m` file's comment map using the
`.h` file's byte offset (`LocDecomp.second`).
- If an unattached comment in the `.m` file starts at an offset `<=
LocDecomp.second` and ends at an offset `CommentEndOffset > LocDecomp.second`,
`LocDecomp.second - CommentEndOffset` underflows `unsigned` and passes a ~4 GB
length to `StringRef(Buffer + CommentEndOffset, LocDecomp.second -
CommentEndOffset)`, crashing in `Text.find_last_of(";{}#@")` (or reading out of
bounds / attaching an unrelated comment from the wrong file).
This test will crash:
```objc
// RUN: rm -rf %t
// RUN: split-file %s %t
// RUN: %clang_cc1 -fsyntax-only -Wdocumentation -Wno-objc-root-class -I %t
%t/repro.m -verify
//--- header.h
#define DECLARE_MACRO_METHOD() \
- (void)macroMethod {}
//--- repro.m
#include "header.h"
/**
* A multiline doc comment whose byte offset range
* straddles the macro method offset in header.h.
*/
@interface Foo
@end
@implementation Foo
- (void)normalMethod {}
DECLARE_MACRO_METHOD()
@end
// expected-no-diagnostics
```
### Fix
1. In `ASTContext::attachCommentsToJustParsedDecls`, look up the `FileID` for
each `DeclLoc` and use the comment map for that file (reusing
`CommentsInThisFile` when `DeclFile == File`), matching
`ASTContext::getRawCommentNoCache`.
2. In `ASTContext::getRawCommentNoCacheImpl`, guard against `CommentEndOffset >
LocDecomp.second` before subtracting.
---
clang/lib/AST/ASTContext.cpp | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 8037085e6d585..3e278773b5a7d 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -304,6 +304,8 @@ RawComment *ASTContext::getRawCommentNoCacheImpl(
// Decompose the end of the comment.
const unsigned CommentEndOffset =
Comments.getCommentEndOffset(CommentBeforeDecl);
+ if (CommentEndOffset > LocDecomp.second)
+ return nullptr;
// Get the corresponding buffer.
bool Invalid = false;
@@ -540,8 +542,18 @@ void
ASTContext::attachCommentsToJustParsedDecls(ArrayRef<Decl *> Decls,
if (DeclLoc.isInvalid() || !DeclLoc.isFileID())
continue;
+ const FileID DeclFile = SourceMgr.getDecomposedLoc(DeclLoc).first;
+ if (!DeclFile.isValid())
+ continue;
+
+ const auto *CommentsInDeclFile =
+ DeclFile == File ? CommentsInThisFile
+ : Comments.getCommentsInFile(DeclFile);
+ if (!CommentsInDeclFile || CommentsInDeclFile->empty())
+ continue;
+
if (RawComment *const DocComment =
- getRawCommentNoCacheImpl(D, DeclLoc, *CommentsInThisFile)) {
+ getRawCommentNoCacheImpl(D, DeclLoc, *CommentsInDeclFile)) {
cacheRawComment(D, *DocComment);
comments::FullComment *FC = DocComment->parse(*this, PP, D);
ParsedComments[D->getCanonicalDecl()] = FC;
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits