llvm-project/clang/unittests/Lex/ModuleDeclStateTest.cpp
yronglin e6e874ce8f
[clang] Allow trivial pp-directives before C++ module directive (#153641)
Consider the following code:

```cpp
# 1 __FILE__ 1 3
export module a;
```

According to the wording in
[P1857R3](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1857r3.html):
```
A module directive may only appear as the first preprocessing tokens in a file (excluding the global module fragment.)
```

and the wording in
[[cpp.pre]](https://eel.is/c++draft/cpp.pre#nt:module-file)
```
module-file:
    pp-global-module-fragment[opt] pp-module group[opt] pp-private-module-fragment[opt]
```

`#` is the first pp-token in the translation unit, and it was rejected
by clang, but they really should be exempted from this rule. The goal is
to not allow any preprocessor conditionals or most state changes, but
these don't fit that.

State change would mean most semantically observable preprocessor state,
particularly anything that is order dependent. Global flags like being a
system header/module shouldn't matter.

We should exempt a brunch of directives, even though it violates the
current standard wording.

In this patch, we introduce a `TrivialDirectiveTracer` to trace the
**State change** that described above and propose to exempt the
following kind of directive: `#line`, GNU line marker, `#ident`,
`#pragma comment`, `#pragma mark`, `#pragma detect_mismatch`, `#pragma
clang __debug`, `#pragma message`, `#pragma GCC warning`, `#pragma GCC
error`, `#pragma gcc diagnostic`, `#pragma OPENCL EXTENSION`, `#pragma
warning`, `#pragma execution_character_set`, `#pragma clang
assume_nonnull` and builtin macro expansion.

Fixes https://github.com/llvm/llvm-project/issues/145274

---------

Signed-off-by: yronglin <yronglin777@gmail.com>
2025-08-18 14:17:35 +08:00

331 lines
11 KiB
C++

//===- unittests/Lex/ModuleDeclStateTest.cpp - PPCallbacks tests ------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===--------------------------------------------------------------===//
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/TargetOptions.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/Lex/HeaderSearchOptions.h"
#include "clang/Lex/ModuleLoader.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Lex/PreprocessorOptions.h"
#include "gtest/gtest.h"
#include <cstddef>
#include <initializer_list>
using namespace clang;
namespace {
class CheckNamedModuleImportingCB : public PPCallbacks {
Preprocessor &PP;
std::vector<bool> IsImportingNamedModulesAssertions;
std::size_t NextCheckingIndex;
public:
CheckNamedModuleImportingCB(Preprocessor &PP,
std::initializer_list<bool> lists)
: PP(PP), IsImportingNamedModulesAssertions(lists), NextCheckingIndex(0) {
}
void moduleImport(SourceLocation ImportLoc, ModuleIdPath Path,
const Module *Imported) override {
ASSERT_TRUE(NextCheckingIndex < IsImportingNamedModulesAssertions.size());
EXPECT_EQ(PP.isInImportingCXXNamedModules(),
IsImportingNamedModulesAssertions[NextCheckingIndex]);
NextCheckingIndex++;
ASSERT_EQ(Imported, nullptr);
}
// Currently, only the named module will be handled by `moduleImport`
// callback.
std::size_t importNamedModuleNum() { return NextCheckingIndex; }
};
class ModuleDeclStateTest : public ::testing::Test {
protected:
ModuleDeclStateTest()
: FileMgr(FileMgrOpts),
Diags(DiagnosticIDs::create(), DiagOpts, new IgnoringDiagConsumer()),
SourceMgr(Diags, FileMgr), TargetOpts(new TargetOptions) {
TargetOpts->Triple = "x86_64-unknown-linux-gnu";
Target = TargetInfo::CreateTargetInfo(Diags, *TargetOpts);
}
std::unique_ptr<Preprocessor> getPreprocessor(const char *source,
Language Lang) {
std::unique_ptr<llvm::MemoryBuffer> Buf =
llvm::MemoryBuffer::getMemBuffer(source);
SourceMgr.setMainFileID(SourceMgr.createFileID(std::move(Buf)));
std::vector<std::string> Includes;
LangOptions::setLangDefaults(LangOpts, Lang, Target->getTriple(), Includes,
LangStandard::lang_cxx20);
LangOpts.CPlusPlusModules = true;
if (Lang != Language::CXX) {
LangOpts.Modules = true;
LangOpts.ImplicitModules = true;
}
HeaderInfo.emplace(HSOpts, SourceMgr, Diags, LangOpts, Target.get());
return std::make_unique<Preprocessor>(PPOpts, Diags, LangOpts, SourceMgr,
*HeaderInfo, ModLoader,
/*IILookup=*/nullptr,
/*OwnsHeaderSearch=*/false);
}
void preprocess(Preprocessor &PP, std::unique_ptr<PPCallbacks> C) {
PP.Initialize(*Target);
PP.addPPCallbacks(std::move(C));
PP.EnterMainSourceFile();
PP.LexTokensUntilEOF();
}
FileSystemOptions FileMgrOpts;
FileManager FileMgr;
DiagnosticOptions DiagOpts;
DiagnosticsEngine Diags;
SourceManager SourceMgr;
std::shared_ptr<TargetOptions> TargetOpts;
IntrusiveRefCntPtr<TargetInfo> Target;
LangOptions LangOpts;
TrivialModuleLoader ModLoader;
HeaderSearchOptions HSOpts;
std::optional<HeaderSearch> HeaderInfo;
PreprocessorOptions PPOpts;
};
TEST_F(ModuleDeclStateTest, NamedModuleInterface) {
const char *source = R"(
export module foo;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, Language::CXX);
std::initializer_list<bool> ImportKinds = {};
auto Callback =
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds);
CheckNamedModuleImportingCB *CallbackPtr = Callback.get();
preprocess(*PP, std::move(Callback));
EXPECT_EQ(CallbackPtr->importNamedModuleNum(), (size_t)0);
EXPECT_TRUE(PP->isInNamedModule());
EXPECT_TRUE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
EXPECT_EQ(PP->getNamedModuleName(), "foo");
}
TEST_F(ModuleDeclStateTest, NamedModuleImplementation) {
const char *source = R"(
module foo;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, Language::CXX);
std::initializer_list<bool> ImportKinds = {};
auto Callback =
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds);
CheckNamedModuleImportingCB *CallbackPtr = Callback.get();
preprocess(*PP, std::move(Callback));
EXPECT_EQ(CallbackPtr->importNamedModuleNum(), (size_t)0);
EXPECT_TRUE(PP->isInNamedModule());
EXPECT_FALSE(PP->isInNamedInterfaceUnit());
EXPECT_TRUE(PP->isInImplementationUnit());
EXPECT_EQ(PP->getNamedModuleName(), "foo");
}
TEST_F(ModuleDeclStateTest, ModuleImplementationPartition) {
const char *source = R"(
module foo:part;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, Language::CXX);
std::initializer_list<bool> ImportKinds = {};
auto Callback =
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds);
CheckNamedModuleImportingCB *CallbackPtr = Callback.get();
preprocess(*PP, std::move(Callback));
EXPECT_EQ(CallbackPtr->importNamedModuleNum(), (size_t)0);
EXPECT_TRUE(PP->isInNamedModule());
EXPECT_FALSE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
EXPECT_EQ(PP->getNamedModuleName(), "foo:part");
}
TEST_F(ModuleDeclStateTest, ModuleInterfacePartition) {
const char *source = R"(
export module foo:part;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, Language::CXX);
std::initializer_list<bool> ImportKinds = {};
auto Callback =
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds);
CheckNamedModuleImportingCB *CallbackPtr = Callback.get();
preprocess(*PP, std::move(Callback));
EXPECT_EQ(CallbackPtr->importNamedModuleNum(), (size_t)0);
EXPECT_TRUE(PP->isInNamedModule());
EXPECT_TRUE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
EXPECT_EQ(PP->getNamedModuleName(), "foo:part");
}
TEST_F(ModuleDeclStateTest, ModuleNameWithDot) {
const char *source = R"(
export module foo.dot:part.dot;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, Language::CXX);
std::initializer_list<bool> ImportKinds = {};
auto Callback =
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds);
CheckNamedModuleImportingCB *CallbackPtr = Callback.get();
preprocess(*PP, std::move(Callback));
EXPECT_EQ(CallbackPtr->importNamedModuleNum(), (size_t)0);
EXPECT_TRUE(PP->isInNamedModule());
EXPECT_TRUE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
EXPECT_EQ(PP->getNamedModuleName(), "foo.dot:part.dot");
}
TEST_F(ModuleDeclStateTest, NotModule) {
const char *source = R"(
// export module foo:part;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, Language::CXX);
std::initializer_list<bool> ImportKinds = {};
auto Callback =
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds);
CheckNamedModuleImportingCB *CallbackPtr = Callback.get();
preprocess(*PP, std::move(Callback));
EXPECT_EQ(CallbackPtr->importNamedModuleNum(), (size_t)0);
EXPECT_FALSE(PP->isInNamedModule());
EXPECT_FALSE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
}
TEST_F(ModuleDeclStateTest, ModuleWithGMF) {
const char *source = R"(
module;
#include "bar.h"
#include <zoo.h>
import "bar";
import <zoo>;
export module foo:part;
import "HU";
import M;
import :another;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, Language::CXX);
std::initializer_list<bool> ImportKinds = {true, true};
auto Callback =
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds);
CheckNamedModuleImportingCB *CallbackPtr = Callback.get();
preprocess(*PP, std::move(Callback));
EXPECT_EQ(CallbackPtr->importNamedModuleNum(), (size_t)2);
EXPECT_TRUE(PP->isInNamedModule());
EXPECT_TRUE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
EXPECT_EQ(PP->getNamedModuleName(), "foo:part");
}
TEST_F(ModuleDeclStateTest, ModuleWithGMFWithClangNamedModule) {
const char *source = R"(
module;
#include "bar.h"
#include <zoo.h>
import "bar";
import <zoo>;
export module foo:part;
import "HU";
import M;
import :another;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, Language::CXX);
std::initializer_list<bool> ImportKinds = {true, true};
auto Callback =
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds);
CheckNamedModuleImportingCB *CallbackPtr = Callback.get();
preprocess(*PP, std::move(Callback));
EXPECT_EQ(CallbackPtr->importNamedModuleNum(), (size_t)2);
EXPECT_TRUE(PP->isInNamedModule());
EXPECT_TRUE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
EXPECT_EQ(PP->getNamedModuleName(), "foo:part");
}
TEST_F(ModuleDeclStateTest, ImportsInNormalTU) {
const char *source = R"(
#include "bar.h"
#include <zoo.h>
import "bar";
import <zoo>;
import "HU";
import M;
// We can't import a partition in non-module TU.
import :another;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, Language::CXX);
std::initializer_list<bool> ImportKinds = {true};
auto Callback =
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds);
CheckNamedModuleImportingCB *CallbackPtr = Callback.get();
preprocess(*PP, std::move(Callback));
EXPECT_EQ(CallbackPtr->importNamedModuleNum(), (size_t)1);
EXPECT_FALSE(PP->isInNamedModule());
EXPECT_FALSE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
}
TEST_F(ModuleDeclStateTest, ImportAClangNamedModule) {
const char *source = R"(
@import anything;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, Language::ObjCXX);
std::initializer_list<bool> ImportKinds = {false};
auto Callback =
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds);
CheckNamedModuleImportingCB *CallbackPtr = Callback.get();
preprocess(*PP, std::move(Callback));
EXPECT_EQ(CallbackPtr->importNamedModuleNum(), (size_t)1);
EXPECT_FALSE(PP->isInNamedModule());
EXPECT_FALSE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
}
TEST_F(ModuleDeclStateTest, ImportWixedForm) {
const char *source = R"(
import "HU";
@import anything;
import M;
@import another;
import M2;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, Language::ObjCXX);
std::initializer_list<bool> ImportKinds = {false, true, false, true};
auto Callback =
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds);
CheckNamedModuleImportingCB *CallbackPtr = Callback.get();
preprocess(*PP, std::move(Callback));
EXPECT_EQ(CallbackPtr->importNamedModuleNum(), (size_t)4);
EXPECT_FALSE(PP->isInNamedModule());
EXPECT_FALSE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
}
} // namespace