Author: Chuanqi Xu
Date: 2026-03-23T03:43:57Z
New Revision: 5ef593b75010301528f665ddbe7d2999165b9238

URL: 
https://github.com/llvm/llvm-project/commit/5ef593b75010301528f665ddbe7d2999165b9238
DIFF: 
https://github.com/llvm/llvm-project/commit/5ef593b75010301528f665ddbe7d2999165b9238.diff

LOG: [clangd] [C++ Modules] Enable content validation for module input files 
(#187653)

The IsModuleFileUpToDate function was not properly validating input
files for C++20 modules. By default, ASTReader skips input file
validation for StandardCXXModule files unless
ForceCheckCXX20ModulesInputFiles and ValidateASTInputFilesContent are
both set.

This change:
- Passes ValidateASTInputFilesContent=true to ASTReader constructor
- Uses ARR_OutOfDate flag for cleaner error handling
- Simplifies the validation logic (ReadAST already validates internally)
- Adds a test to verify header changes in module units are detected

Assised with AI.

Added: 
    

Modified: 
    clang-tools-extra/clangd/ModulesBuilder.cpp
    clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clangd/ModulesBuilder.cpp 
b/clang-tools-extra/clangd/ModulesBuilder.cpp
index 950392b91ac7c..612c25b61a89c 100644
--- a/clang-tools-extra/clangd/ModulesBuilder.cpp
+++ b/clang-tools-extra/clangd/ModulesBuilder.cpp
@@ -268,29 +268,29 @@ bool IsModuleFileUpToDate(PathRef ModuleFilePath,
   std::shared_ptr<ModuleCache> ModCache = createCrossProcessModuleCache();
   PCHContainerOperations PCHOperations;
   CodeGenOptions CodeGenOpts;
-  ASTReader Reader(PP, *ModCache, /*ASTContext=*/nullptr,
-                   PCHOperations.getRawReader(), CodeGenOpts, {});
+  ASTReader Reader(
+      PP, *ModCache, /*ASTContext=*/nullptr, PCHOperations.getRawReader(),
+      CodeGenOpts, {},
+      /*isysroot=*/"",
+      /*DisableValidationKind=*/DisableValidationForModuleKind::None,
+      /*AllowASTWithCompilerErrors=*/false,
+      /*AllowConfigurationMismatch=*/false,
+      /*ValidateSystemInputs=*/false,
+      /*ForceValidateUserInputs=*/true,
+      /*ValidateASTInputFilesContent=*/true);
 
   // We don't need any listener here. By default it will use a validator
   // listener.
   Reader.setListener(nullptr);
 
-  if (Reader.ReadAST(ModuleFileName::makeExplicit(ModuleFilePath),
-                     serialization::MK_MainFile, SourceLocation(),
-                     ASTReader::ARR_None) != ASTReader::Success)
-    return false;
-
-  bool UpToDate = true;
-  Reader.getModuleManager().visit([&](serialization::ModuleFile &MF) -> bool {
-    Reader.visitInputFiles(
-        MF, /*IncludeSystem=*/false, /*Complain=*/false,
-        [&](const serialization::InputFile &IF, bool isSystem) {
-          if (!IF.getFile() || IF.isOutOfDate())
-            UpToDate = false;
-        });
-    return !UpToDate;
-  });
-  return UpToDate;
+  // Use ARR_OutOfDate so that ReadAST returns OutOfDate instead of Failure
+  // when input files are modified. This allows us to detect staleness
+  // without treating it as a hard error.
+  // ReadAST will validate all input files internally and return OutOfDate
+  // if any file is modified.
+  return Reader.ReadAST(ModuleFileName::makeExplicit(ModuleFilePath),
+                        serialization::MK_MainFile, SourceLocation(),
+                        ASTReader::ARR_OutOfDate) == ASTReader::Success;
 }
 
 bool IsModuleFilesUpToDate(

diff  --git a/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp 
b/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
index 3132959a5967c..f072c299cad22 100644
--- a/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
+++ b/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
@@ -644,6 +644,78 @@ import M;
   EXPECT_EQ(CDB.getGlobalScanningCount(), 1u);
 }
 
+// Test that canReuse detects changes to headers included in module units.
+// This verifies that the ASTReader correctly tracks header file dependencies
+// in BMI files and that IsModuleFileUpToDate correctly validates them.
+TEST_F(PrerequisiteModulesTests, CanReuseWithHeadersInModuleUnit) {
+  MockDirectoryCompilationDatabase CDB(TestDir, FS);
+
+  // Create a header file that will be included in a module unit
+  CDB.addFile("header1.h", R"cpp(
+inline int getValue() { return 42; }
+  )cpp");
+
+  // Module M includes header1.h in the global module fragment
+  CDB.addFile("M.cppm", R"cpp(
+module;
+#include "header1.h"
+export module M;
+export int m_value = getValue();
+  )cpp");
+
+  // Module N imports M (similar structure to ReusabilityTest)
+  CDB.addFile("N.cppm", R"cpp(
+export module N;
+import :Part;
+import M;
+  )cpp");
+
+  // Add a module partition (similar to ReusabilityTest)
+  CDB.addFile("N-part.cppm", R"cpp(
+export module N:Part;
+  )cpp");
+
+  ModulesBuilder Builder(CDB);
+
+  // Build prerequisite modules for N (which depends on M)
+  auto NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
+  ASSERT_TRUE(NInfo);
+
+  ParseInputs NInput = getInputs("N.cppm", CDB);
+  std::unique_ptr<CompilerInvocation> Invocation =
+      buildCompilerInvocation(NInput, DiagConsumer);
+
+  // Initially, canReuse should return true
+  EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
+
+  // Test 1: Modify header1.h (included by M)
+  // canReuse should detect this change since M's BMI records header1.h as 
input
+  CDB.addFile("header1.h", R"cpp(
+inline int getValue() { return 43; }
+  )cpp");
+  EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
+
+  // Rebuild and verify canReuse returns true again
+  NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
+  ASSERT_TRUE(NInfo);
+  EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
+
+  // Test 2: Modify the module source file itself
+  CDB.addFile("M.cppm", R"cpp(
+module;
+#include "header1.h"
+export module M;
+export int m_value = getValue();
+export int m_new_value = 10;
+  )cpp");
+  EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
+
+  // Rebuild after module source change
+  NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
+  ASSERT_TRUE(NInfo);
+  EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
+}
+
 TEST_F(PrerequisiteModulesTests, PrebuiltModuleFileTest) {
   MockDirectoryCompilationDatabase CDB(TestDir, FS);
 


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to