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 @@ -11261,6 +11261,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 @@ -2326,6 +2326,31 @@ 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 variable initializer is + /// an 'exposure'. + bool diagnoseVarInitExposures(const VarDecl *VDecl, const Expr *Init); + + /// Diagnose 'exposure's in an expression. + bool diagnoseExprExposure(const Expr *E); + + /// Diagnose 'exposure's in a function instantiation. + bool diagnoseInstantiationExposure(const FunctionDecl *F, SourceLocation L); + /// 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 @@ -8705,6 +8705,22 @@ return; } } + + // C++ [basic.link]/14 check exposures where the type of a variable is + // specified by a decltype(). + if (getLangOpts().CPlusPlusModules && isCurrentModulePurview() && + NewVD->getFormalLinkage() >= Linkage::ModuleLinkage) { + QualType Ty = T; + while (Ty->isPointerType() || Ty->isReferenceType()) + Ty = Ty->getPointeeType(); + if (Ty->isDecltypeType()) { + const Expr *E = Ty->getAs()->getUnderlyingExpr(); + if (diagnoseExprExposure(E)) { + NewVD->setInvalidDecl(); + return; + } + } + } } /// Perform semantic checking on a newly-created variable @@ -12721,6 +12737,30 @@ MergeVarDeclTypes(VDecl, Old, /*MergeTypeWithPrevious*/ false); } + // C++ [basic.link]/14 check exposures in the type deduced for a variable. + // We cannot check this in CheckVariableDeclarationType() since auto-ness is + // discarded above. + if (getLangOpts().CPlusPlusModules && !VDecl->isInvalidDecl() && + isCurrentModulePurview() && + (((VDecl->isInline() || VDecl->isConstexpr()) && + VDecl->getFormalLinkage() >= Linkage::ModuleLinkage) || + (VDecl->isConstexpr() && VDecl->getStorageClass() != SC_Static))) { + const Expr *E = Init; + // FIXME: we maybe should recurse here through as many layers of reference + // as are present. + if (auto *DRE = dyn_cast(Init)) { + // look through references. + if (auto *VD = dyn_cast(DRE->getDecl())) { + if (VD->getType()->isReferenceType()) { + assert(VD->hasInit()); + E = VD->getInit(); + } + } + } + if (E && diagnoseVarInitExposures(VDecl, E)) + VDecl->setInvalidDecl(); + } + // Check the deduced type is valid for a variable declaration. CheckVariableDeclarationType(VDecl); return VDecl->isInvalidDecl(); @@ -13137,6 +13177,19 @@ VDecl->setInvalidDecl(); return; } + + // C++ [basic.link]/14 : handle exposures in variable initializers. + // A declaration is an exposure if it either names a TU-local entity + // ... + // or defines a constexpr variable initialized to a TU-local value. + if (getLangOpts().CPlusPlusModules && isCurrentModulePurview() && + (((VDecl->isInline() || VDecl->isConstexpr()) && + VDecl->getFormalLinkage() >= Linkage::ModuleLinkage) || + (VDecl->isConstexpr() && VDecl->getStorageClass() != SC_Static)) && + diagnoseVarInitExposures(VDecl, Init)) { + VDecl->setInvalidDecl(); + return; + } } // OpenCL 1.1 6.5.2: "Variables allocated in the __local address space inside @@ -15213,6 +15266,16 @@ diag::err_func_def_incomplete_result)) FD->setInvalidDecl(); + // C++ [basic.link]/14 : Diagnose exposure of TU-local entities in function + // return types specified with decltype(). + if (getLangOpts().CPlusPlusModules && !FD->isInvalidDecl() && + !FD->isStatic() && isCurrentModulePurview() && + ResultType->isDecltypeType()) { + const Expr *E = ResultType->getAs()->getUnderlyingExpr(); + if (diagnoseExprExposure(E)) + FD->setInvalidDecl(); + } + if (FnBodyScope) PushDeclContext(FnBodyScope, FD); 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 @@ -155,16 +155,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()) @@ -373,7 +372,12 @@ DiagnoseUnusedOfDecl(*this, D, Loc); - diagnoseUseOfInternalDeclInInlineFunction(*this, D, Loc); + if (getLangOpts().CPlusPlus) { + // C++ [basic.link]/14 : handle exposures in function bodies. + if (getLangOpts().CPlusPlusModules && 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" @@ -1021,3 +1022,109 @@ return M->isSubModuleOf(CurrentModuleUnit->getTopLevelModule()); } + +bool Sema::diagnoseFunctionBodyExposures(const NamedDecl *D, + SourceLocation Loc) { + + FunctionDecl *Current = getCurFunctionDecl(); + + if (!Current || Current->isInvalidDecl() || D->isInvalidDecl()) + return false; + + if (Current->isStatic()) + return false; + + if (auto *PD = dyn_cast(D)) + return false; + if (auto *VD = dyn_cast(D)) { + if (VD->getType()->isDependentType()) + return false; + } + + if ((Current->isInlineSpecified() || Current->isConstexpr()) && + isCurrentModulePurview() && + Current->getTemplatedKind() == + FunctionDecl::TemplatedKind::TK_NonTemplate) { + if (isTULocal(D)) { + Diag(Loc, diag::err_use_is_exposure) << D; + return true; + } + } + return false; +} + +class DeclRefIsExposure : public ConstStmtVisitor { +public: + /// Find a DeclRefExpr in the given expression. + static bool check(Sema &S, const Expr *E, SourceLocation L) { + DeclRefIsExposure R(S, L); + R.Visit(E); + return R.getResult(); + } + + /// Find a DeclRefExpr in the given statment. + static bool check(Sema &S, const Stmt *C, SourceLocation L) { + DeclRefIsExposure R(S, L); + R.Visit(C); + return R.getResult(); + } + + void VisitDeclRefExpr(const DeclRefExpr *DR) { + const ValueDecl *V = DR->getDecl(); + + if (S.isTULocal(V)) { + S.Diag(L, diag::err_use_is_exposure) << V; + Result = true; + } + } + void VisitExpr(const Expr *E) { + for (auto *Ch : E->children()) + if (Ch) + Visit(Ch); + } + + void VisitStmt(const Stmt *S) { + for (auto *Ch : S->children()) + if (Ch) + Visit(Ch); + } + + bool getResult() { return Result; } + +private: + DeclRefIsExposure(Sema &S, SourceLocation L) : S(S), L(L) {} + Sema &S; + SourceLocation L; + bool Result = false; +}; + +bool Sema::diagnoseVarInitExposures(const VarDecl *VDecl, const Expr *Init) { + + if (DeclRefIsExposure::check(*this, Init, VDecl->getLocation())) { + // llvm::dbgs() << "inline var exposure: "; VDecl->dump(); Init->dump(); + return true; + } + return false; +} + +bool Sema::diagnoseExprExposure(const Expr *E) { + + if (DeclRefIsExposure::check(*this, E, E->getExprLoc())) { + // llvm::dbgs() << "expr exposure: "; E->dump(); + return true; + } + return false; +} + +bool Sema::diagnoseInstantiationExposure(const FunctionDecl *F, + SourceLocation L) { + + if (F->getOwningModule() && + (F->getOwningModule() != getCurrentModule() || + !currentModuleIsInterface()) && + DeclRefIsExposure::check(*this, F->getBody(), L)) { + // llvm::dbgs() << "expr exposure: "; E->dump(); + return true; + } + return false; +} \ No newline at end of file diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -5107,6 +5107,9 @@ ActOnFinishFunctionBody(Function, Body.get(), /*IsInstantiation=*/true); PerformDependentDiagnostics(PatternDecl, TemplateArgs); + if (getLangOpts().CPlusPlusModules && !Function->isInvalidDecl() && + diagnoseInstantiationExposure(Function, PointOfInstantiation)) + Function->setInvalidDecl(); if (auto *Listener = getASTMutationListener()) Listener->FunctionDefinitionInstantiated(Function); diff --git a/clang/test/CXX/basic/basic.link/p14-additions.cpp b/clang/test/CXX/basic/basic.link/p14-additions.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CXX/basic/basic.link/p14-additions.cpp @@ -0,0 +1,46 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: split-file %s %t +// +// RUN: %clang_cc1 -std=c++20 %t/A.cpp -fsyntax-only -Wno-unused-value -verify + +//--- A.cpp + +export module A; + +static constexpr int f() { return 0; } +int a = 10; + +// extra decltype() tests + +static decltype(f()) s; // OK + decltype(f()) g; // expected-error {{use of TU-local entity 'f' is an exposure}} +export decltype(f()) e; // expected-error {{use of TU-local entity 'f' is an exposure}} + +static decltype(f()) *fps; // OK + decltype(f()) *fpg; // expected-error {{use of TU-local entity 'f' is an exposure}} +export decltype(f()) *fpe; // expected-error {{use of TU-local entity 'f' is an exposure}} + +static decltype(f()) &frs = a; // OK + decltype(f()) &frg = a; // expected-error {{use of TU-local entity 'f' is an exposure}} +export decltype(f()) &fre = a; // expected-error {{use of TU-local entity 'f' is an exposure}} + +// auto cases .. + +static auto fa_internal() { return f(); } // OK + auto fa_module() { return f(); } // OK +export auto fa_export() { return f(); } // OK + +static auto inline fai_internal() { return f(); } // OK + auto inline fai_module() { return f(); } // expected-error {{use of TU-local entity 'f' is an exposure}} +export auto inline fai_export() { return f(); } // expected-error {{use of TU-local entity 'f' is an exposure}}K + +static auto constexpr fac_internal() { return f(); } // OK + auto constexpr fac_module() { return f(); } // expected-error {{use of TU-local entity 'f' is an exposure}} +export auto constexpr fac_export() { return f(); } // expected-error {{use of TU-local entity 'f' is an exposure}}K + +// extra constexpr variable checks. + +static constexpr int v_internal_constexpr = f(); // OK + constexpr int v_module_constexpr = f(); // expected-error {{use of TU-local entity 'f' is an exposure}} +export constexpr int v_exported_constexpr = f(); // expected-error {{use of TU-local entity 'f' is an exposure}} diff --git a/clang/test/CXX/basic/basic.link/p1498r1-2-2-1.cpp b/clang/test/CXX/basic/basic.link/p1498r1-2-2-1.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CXX/basic/basic.link/p1498r1-2-2-1.cpp @@ -0,0 +1,31 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: split-file %s %t +// +// RUN: %clang_cc1 -std=c++20 %t/A.cpp -fsyntax-only -Wno-unused-value -verify + +//--- A.cpp + +export module A; + +static constexpr int f() { return 0; } + +static int f_internal() { return f(); } // OK + int f_module() { return f(); } // OK +export int f_exported() { return f(); } // OK + +static inline int f_internal_inline() { return f(); } // OK + inline int f_module_inline() { return f(); } // expected-error {{use of TU-local entity 'f' is an exposure}} +export inline int f_exported_inline() { return f(); } // expected-error {{use of TU-local entity 'f' is an exposure}} + +static constexpr int f_internal_constexpr() { return f(); } // OK + constexpr int f_module_constexpr() { return f(); } // expected-error {{use of TU-local entity 'f' is an exposure}} +export constexpr int f_exported_constexpr() { return f(); } // expected-error {{use of TU-local entity 'f' is an exposure}} + +static consteval int f_internal_consteval() { return f(); } // OK + consteval int f_module_consteval() { return f(); } // expected-error {{use of TU-local entity 'f' is an exposure}} +export consteval int f_exported_consteval() { return f(); } // expected-error {{use of TU-local entity 'f' is an exposure}} + +static decltype(f()) f_internal_decltype() { return 0; } // OK + decltype(f()) f_module_decltype() { return 0; } // expected-error {{use of TU-local entity 'f' is an exposure}} +export decltype(f()) f_exported_decltype() { return 0; } // expected-error {{use of TU-local entity 'f' is an exposure}} diff --git a/clang/test/CXX/basic/basic.link/p1498r1-2-2-5.cpp b/clang/test/CXX/basic/basic.link/p1498r1-2-2-5.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CXX/basic/basic.link/p1498r1-2-2-5.cpp @@ -0,0 +1,26 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: split-file %s %t +// +// RUN: %clang_cc1 -std=c++20 %t/A.cpp -fsyntax-only -Wno-unused-value -verify + +//--- A.cpp + +export module A; + +static constexpr int f() { return 0; } + +static int v_internal = f(); // OK + int v_module = f(); // OK +export int v_exported = f(); // OK + +static inline int v_internal_inline = f(); // OK + inline int v_module_inline = f(); // expected-error {{use of TU-local entity 'f' is an exposure}} +export inline int v_exported_inline = f(); // expected-error {{use of TU-local entity 'f' is an exposure}} + +struct c_sdm_module { + static int sdm_module; + static constexpr int sdm_module_constexpr = f(); // expected-error {{use of TU-local entity 'f' is an exposure}} +}; + +int c_sdm_module::sdm_module = f(); // OK 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,65 @@ +// 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=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 + // error: f (though not its type) is TU-local +decltype(f) *fp; // expected-error {{use of TU-local entity 'f' is an exposure}} +#endif +auto &fr = f; // OK +#if TEST_INTERFACE +constexpr auto &fr2 = fr; // expected-error {{use of TU-local entity 'f' is an exposure}} +#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>(); // expected-error {{use of TU-local entity 'its' is an exposure}} + // expected-note@-1 {{in instantiation of function template specialization 'g<1>' requested here}} + 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 + // error: fr is not usable in constant expressions here + constexpr auto ptr = fr; // expected-error {{use of TU-local entity 'f' is an exposure}} +} 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