diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11246,6 +11246,9 @@ "'%0' included multiple times, additional include site in header from module '%1'">; def note_redefinition_include_same_file : Note< "'%0' included multiple times, additional include site here">; + +def err_use_is_exposure : Error<"use of TU-local entity %0 is an exposure">; + } let CategoryName = "Coroutines Issue" in { diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2296,6 +2296,25 @@ bool isAcceptableSlow(const NamedDecl *D, AcceptableKind Kind); public: + /// Determine of a Decl is TU-local. + bool isTULocal(const Decl *D) const { + if (auto *ND = dyn_cast(D)) { + if (ND->getFormalLinkage() <= Linkage::InternalLinkage) + return true; + // more here... + return false; + } + return true; + } + + /// Diagnose cases where use of a TU-local entity in a function body is an + /// 'exposure'. + bool diagnoseFunctionBodyExposures(const NamedDecl *D, SourceLocation Loc); + + /// Diagnose cases where use of a TU-local entity in a varibale intializer is + /// an 'exposure'. + bool diagnoseVarInitExposures(VarDecl *VDecl, Expr *Init); + /// Get the module unit whose scope we are currently within. Module *getCurrentModule() const { return ModuleScopes.empty() ? nullptr : ModuleScopes.back().Module; diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -13110,6 +13110,11 @@ VDecl->setInvalidDecl(); return; } + + if (diagnoseVarInitExposures(VDecl, Init)) { + VDecl->setInvalidDecl(); + return; + } } // OpenCL 1.1 6.5.2: "Variables allocated in the __local address space inside diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -154,16 +154,15 @@ static void diagnoseUseOfInternalDeclInInlineFunction(Sema &S, const NamedDecl *D, SourceLocation Loc) { - // This is disabled under C++; there are too many ways for this to fire in - // contexts where the warning is a false positive, or where it is technically - // correct but benign. - if (S.getLangOpts().CPlusPlus) - return; // Check if this is an inlined function or method. FunctionDecl *Current = S.getCurFunctionDecl(); if (!Current) return; + + // As noted above, this test should not be applied to C++. + assert(!S.getLangOpts().CPlusPlus); + if (!Current->isInlined()) return; if (!Current->isExternallyVisible()) @@ -371,7 +370,11 @@ DiagnoseUnusedOfDecl(*this, D, Loc); - diagnoseUseOfInternalDeclInInlineFunction(*this, D, Loc); + if (getLangOpts().CPlusPlus) { + if (diagnoseFunctionBodyExposures(D, Loc)) + return true; + } else + diagnoseUseOfInternalDeclInInlineFunction(*this, D, Loc); if (auto *VD = dyn_cast(D)) checkTypeSupport(VD->getType(), Loc, VD); diff --git a/clang/lib/Sema/SemaModule.cpp b/clang/lib/Sema/SemaModule.cpp --- a/clang/lib/Sema/SemaModule.cpp +++ b/clang/lib/Sema/SemaModule.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "clang/AST/ASTConsumer.h" +#include "clang/AST/StmtVisitor.h" #include "clang/Lex/HeaderSearch.h" #include "clang/Lex/Preprocessor.h" #include "clang/Sema/SemaInternal.h" @@ -1009,3 +1010,77 @@ return M->isSubModuleOf(CurrentModuleUnit->getTopLevelModule()); } + +bool Sema::diagnoseFunctionBodyExposures(const NamedDecl *D, + SourceLocation Loc) { + + if (!getLangOpts().CPlusPlusModules || !isCurrentModulePurview()) + return false; + + FunctionDecl *Current = getCurFunctionDecl(); + + if (!Current) + return false; + + if (Current->isInlineSpecified() && !Current->isStatic() && + Current->getTemplatedKind() != + FunctionDecl::TemplatedKind::TK_FunctionTemplate) { + if (auto *PD = dyn_cast(D)) + return false; + if (auto *VD = dyn_cast(D)) { + if (VD->getType()->isDependentType()) + return false; + } + if (isTULocal(D)) { + // llvm::dbgs() << "found exposure : "; D->dump(); + Diag(Loc, diag::err_use_is_exposure) << D; + return true; + } + return false; + } + return false; +} + +class DeclRefIsExposure : public ConstStmtVisitor { +public: + /// Find a DeclRefExpr in the given expression. + static bool check(Sema &S, const Expr *E, VarDecl *VDecl) { + DeclRefIsExposure R(S, VDecl); + R.Visit(E); + return R.getResult(); + } + + void VisitDeclRefExpr(const DeclRefExpr *DR) { + const ValueDecl *V = DR->getDecl(); + + if (S.isTULocal(V)) { + S.Diag(VDecl->getLocation(), diag::err_use_is_exposure) << V; + Result = true; + } + } + void VisitExpr(const Expr *E) { + for (auto *Ch : E->children()) + if (Ch) + Visit(Ch); + } + + bool getResult() { return Result; } + +private: + DeclRefIsExposure(Sema &S, VarDecl *VD) : S(S), VDecl(VD) {} + Sema &S; + VarDecl *VDecl; + bool Result = false; +}; + +bool Sema::diagnoseVarInitExposures(VarDecl *VDecl, Expr *Init) { + if (!getLangOpts().CPlusPlusModules || !isCurrentModulePurview() || + !VDecl->isInline() || VDecl->getStorageClass() == SC_Static) + return false; + + if (DeclRefIsExposure::check(*this, Init, VDecl)) { + // llvm::dbgs() << "inline var exposure: "; VDecl->dump(); Init->dump(); + return true; + } + return false; +} diff --git a/clang/test/CXX/basic/basic.link/p19-ex4.cpp b/clang/test/CXX/basic/basic.link/p19-ex4.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CXX/basic/basic.link/p19-ex4.cpp @@ -0,0 +1,63 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: split-file %s %t +// RUN: cd %t +// +// RUN: %clang_cc1 -std=c++20 A.cpp -fsyntax-only -DTEST_INTERFACE \ +// RUN: -Wno-unused-value -verify +// RUN: %clang_cc1 -std=c++20 A.cpp -emit-module-interface -o A.pcm \ +// RUN: -Wno-unused-value +// RUN: %clang_cc1 -std=c++20 A-impl.cpp -fsyntax-only -fmodule-file=A.pcm \ +// RUN: -Wno-unused-value +// -verify + +//--- A.cpp +export module A; +static void f() {} +static int fi() { return 1; } +#if TEST_INTERFACE +inline void it() { f(); } // expected-error {{use of TU-local entity 'f' is an exposure}} +#endif +static inline void its() { f(); } // OK +template void g() { its(); } // OK +template void g<0>(); + +#if TEST_INTERFACE +decltype(f) *fp; // error: f (though not its type) is TU-local +#endif +auto &fr = f; // OK +#if TEST_INTERFACE +constexpr auto &fr2 = fr; // error: is an exposure of f +#endif +constexpr static auto fp2 = fr; // OK + +struct S { void (&ref)(); } s{f}; // OK, value is TU-local +constexpr extern struct W { S &s; } wrap{s}; // OK, value is not TU-local + +static auto x = []{f();}; // OK +#if TEST_INTERFACE +auto x2 = x; // error: the closure type is TU-local +int y = ([]{f();}(),0); // error: the closure type is not TU-local +#endif +int y2 = (x,0); // OK + +namespace N { + struct A {}; + void adl(A); + static void adl(int); +} +void adl(double); + +inline void h(auto x) { adl(x); } // OK, but a specialization might be an exposure + +//--- A-impl.cpp +module A; +void other() { + g<0>(); // OK, specialization is explicitly instantiated + g<1>(); // error: instantiation uses TU-local its + h(N::A{}); // error: overload set contains TU-local N::adl(int) + h(0); // OK, calls adl(double) + adl(N::A{}); // OK; N::adl(int) not found, calls N::adl(N::A) + fr(); // OK, calls f + constexpr auto ptr = fr; // error: fr is not usable in constant expressions here +} diff --git a/clang/test/CXX/basic/basic.link/p19-inline-vars.cpp b/clang/test/CXX/basic/basic.link/p19-inline-vars.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CXX/basic/basic.link/p19-inline-vars.cpp @@ -0,0 +1,17 @@ +// RUN: %clang_cc1 -std=c++20 %s -fsyntax-only -Wno-unused-value -verify + +export module A; + +static int fi() { return 1; } +static int x = 10; + +inline int bad_f0() { return x; } // expected-error {{use of TU-local entity 'x' is an exposure}} + +static inline int ok_f0() { return x; } // OK + +inline int bad_v0 = fi(); // expected-error {{use of TU-local entity 'fi' is an exposure}} +inline int bad_v1 = 5 + x - fi(); // expected-error {{use of TU-local entity 'x' is an exposure}} + // expected-error@-1 {{use of TU-local entity 'fi' is an exposure}} + +static inline int ok_v0 = fi(); // OK +static inline int ok_v1 = x; // OK