[clang] [Clang][AST] Fix crash attaching doc comments across files in attachCommentsToJustParsedDecls (PR #234708)

via cfe-commits cfe-commits at lists.llvm.org
Thu Oct 22 11:03:04 PDT 2026


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 <stephenduong at google.com>
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;



More information about the cfe-commits mailing list