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 @@ -4502,6 +4502,8 @@ def err_ovl_no_viable_function_in_call : Error< "no matching function for call to %0">; +def err_ovl_exposure_function_in_call : Error< + "the overload set for the call to %0 contains one or more exposures">; def err_ovl_no_viable_member_function_in_call : Error< "no matching member function for call to %0">; def err_ovl_ambiguous_call : Error< @@ -4589,6 +4591,7 @@ def note_ovl_candidate_has_pass_object_size_params: Note< "candidate address cannot be taken because parameter %0 has " "pass_object_size attribute">; +def note_ovl_candidate_is_exposure: Note<"candidate %0 is an exposure">; def err_diagnose_if_succeeded : Error<"%0">; def warn_diagnose_if_succeeded : Warning<"%0">, InGroup, ShowInSystemHeader; @@ -4724,6 +4727,8 @@ def note_ovl_builtin_candidate : Note<"built-in candidate %0">; def err_ovl_no_viable_function_in_init : Error< "no matching constructor for initialization of %0">; +def err_ovl_conversion_has_exposure : Error< + "the overload set for conversion from %1 to %2 contains an exposure">; def err_ovl_no_conversion_in_cast : Error< "cannot convert %1 to %2 without a conversion operator">; def err_ovl_no_viable_conversion_in_cast : Error< @@ -4790,6 +4795,8 @@ "reference to non-static member function must be called" "%select{|; did you mean to call it with no arguments?}0">; def note_possible_target_of_call : Note<"possible target for call">; +def err_ovl_destructor_has_exposure : Error< + "the overload set for class %0 destructor contains an exposure">; def err_no_viable_destructor : Error< "no viable destructor found for class %0">; def err_ambiguous_destructor : Error< @@ -9432,6 +9439,9 @@ "defaulted %0 is implicitly deleted because there is no viable " "%select{three-way comparison function|'operator=='}1 for " "%select{|member |base class }2%3">; +def err_ovl_defaulted_comparison_exposure : Error<"the overload set for " + "%select{three-way comparison function|'operator=='}1 for " + "%select{|member |base class }2%3 contains an exposure">; def note_defaulted_comparison_no_viable_function_synthesized : Note< "three-way comparison cannot be synthesized because there is no viable " "function for %select{'=='|'<'}0 comparison">; @@ -11320,6 +11330,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/Overload.h b/clang/include/clang/Sema/Overload.h --- a/clang/include/clang/Sema/Overload.h +++ b/clang/include/clang/Sema/Overload.h @@ -58,7 +58,10 @@ OR_Ambiguous, /// Succeeded, but refers to a deleted function. - OR_Deleted + OR_Deleted, + + /// At least one candidate is an exposure. + OR_Exposure }; enum OverloadCandidateDisplayKind { @@ -70,7 +73,10 @@ OCD_ViableCandidates, /// Requests that only tied-for-best candidates be shown. - OCD_AmbiguousCandidates + OCD_AmbiguousCandidates, + + /// Requests that only candidates that are exposures be shown. + OCD_ExposureCandidates }; /// The parameter ordering that will be used for the candidate. This is @@ -809,9 +815,13 @@ /// not satisfied. ovl_fail_constraints_not_satisfied, - /// This candidate was not viable because it has internal linkage and is - /// from a different module unit than the use. + /// This candidate was not viable because it has module linkage and is + /// from a different module than the use. ovl_fail_module_mismatched, + + /// This candidate was not viable because it has internal linkage and is + /// is an exposure of a declaration in a different TU of this module. + ovl_fail_module_exposure, }; /// A list of implicit conversion sequences for the arguments of an 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 @@ -2345,6 +2345,44 @@ bool isAcceptableSlow(const NamedDecl *D, AcceptableKind Kind); public: + /// Determine of a Decl is TU-local. + bool isTULocal(const NamedDecl *ND) const { + if (ND->getFormalLinkage() == Linkage::InternalLinkage) { +#ifdef PRE_P2788R0 + if (auto *VD = dyn_cast(ND)) { + QualType VT = VD->getType(); + if (!VT.isConstQualified() || VT.isVolatileQualified()) + return true; + if (VD->hasConstantInitialization()) + return false; // non-volatile, const with const init. + } +#endif + return true; + } + return false; + } + + bool isTULocal(const Decl *D) const { + if (auto *ND = dyn_cast(D)) + return isTULocal(ND); + 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, + bool OnlyLambdaExpr); + + /// 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/SemaCast.cpp b/clang/lib/Sema/SemaCast.cpp --- a/clang/lib/Sema/SemaCast.cpp +++ b/clang/lib/Sema/SemaCast.cpp @@ -484,6 +484,10 @@ msg = diag::err_ovl_deleted_conversion_in_cast; howManyCandidates = OCD_ViableCandidates; break; + case OR_Exposure: + msg = diag::err_ovl_conversion_has_exposure; + howManyCandidates = OCD_ExposureCandidates; + break; } candidates.NoteCandidates( 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 @@ -8785,6 +8785,22 @@ if (T->isRVVType()) checkRVVTypeSupport(T, NewVD->getLocation(), cast(CurContext)); + + // 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 @@ -12834,6 +12850,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->getType()->getAsCXXRecordDecl() && + VDecl->getType()->getAsCXXRecordDecl()->isLambda())) && + VDecl->getFormalLinkage() >= Linkage::ModuleLinkage) || + (VDecl->isConstexpr() && VDecl->getStorageClass() != SC_Static))) { + const Expr *E = Init; + 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, false)) + VDecl->setInvalidDecl(); + } + // Check the deduced type is valid for a variable declaration. CheckVariableDeclarationType(VDecl); return VDecl->isInvalidDecl(); @@ -13258,6 +13298,25 @@ 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()) { + if ((((VDecl->isInline() || VDecl->isConstexpr()) && + VDecl->getFormalLinkage() >= Linkage::ModuleLinkage) || + (VDecl->isConstexpr() && VDecl->getStorageClass() != SC_Static)) && + diagnoseVarInitExposures(VDecl, Init, /*OnlyLambdaExpr*/false)) { + VDecl->setInvalidDecl(); + return; + } else if (VDecl->getFormalLinkage() >= Linkage::ModuleLinkage && + !VDecl->isTemplated() && + diagnoseVarInitExposures(VDecl, Init, /*OnlyLambdaExpr*/true)) { + VDecl->setInvalidDecl(); + return; + } + } } // OpenCL 1.1 6.5.2: "Variables allocated in the __local address space inside @@ -15348,6 +15407,16 @@ AbstractReturnType))) 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); @@ -18539,6 +18608,11 @@ DisplayKind = OCD_AmbiguousCandidates; break; + case OR_Exposure: + Msg = diag::err_ovl_destructor_has_exposure; + DisplayKind = OCD_ExposureCandidates; + break; + case OR_No_Viable_Function: Msg = diag::err_no_viable_destructor; DisplayKind = OCD_AllCandidates; diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -8194,6 +8194,13 @@ R = Result::deleted(); break; + case OR_Exposure: + S.Diag(Subobj.Loc, diag::err_ovl_defaulted_comparison_exposure) + << (OO == OO_ExclaimEqual) << Subobj.Kind << Subobj.Decl; + CandidateSet.NoteCandidates(S, Args, + CandidateSet.CompleteCandidates(S, OCD_ExposureCandidates, Args, + FD->getLocation())); + break; case OR_No_Viable_Function: // If there's no usable candidate, we're done unless we can rewrite a // '<=>' in terms of '==' and '<'. 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 @@ -156,16 +156,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()) @@ -372,7 +371,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 (D->hasAttr()) { if (getLangOpts().getFPEvalMethod() != diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -2522,6 +2522,7 @@ return false; } + case OR_Exposure: case OR_No_Viable_Function: // C++17 [expr.new]p13: // If no matching function is found and the allocated object type has @@ -3863,6 +3864,7 @@ return false; } + case OR_Exposure: case OR_No_Viable_Function: Candidates.NoteCandidates( PartialDiagnosticAt(R.getNameLoc(), @@ -6243,6 +6245,7 @@ return false; } + case OR_Exposure: case OR_No_Viable_Function: // Emit a better diagnostic if one of the expressions is a null pointer diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -6768,6 +6768,7 @@ case OR_Success: break; + case OR_Exposure: case OR_No_Viable_Function: CandidateSet.NoteCandidates( PartialDiagnosticAt( @@ -6922,6 +6923,7 @@ // FIXME: Check default arguments as far as that's possible. break; + case OR_Exposure: case OR_No_Viable_Function: CandidateSet.NoteCandidates(PartialDiagnosticAt(Loc, Diag), S, OCD_AllCandidates, CurInitExpr); @@ -9641,6 +9643,7 @@ S, OCD_AmbiguousCandidates, Args); break; + case OR_Exposure: case OR_No_Viable_Function: { auto Cands = FailedCandidateSet.CompleteCandidates(S, OCD_AllCandidates, Args); if (!S.RequireCompleteType(Kind.getLocation(), @@ -9846,6 +9849,7 @@ S, OCD_AmbiguousCandidates, Args); break; + case OR_Exposure: case OR_No_Viable_Function: if (Kind.getKind() == InitializationKind::IK_Default && (Entity.getKind() == InitializedEntity::EK_Base || @@ -10843,6 +10847,7 @@ *this, OCD_AmbiguousCandidates, Inits); return QualType(); + case OR_Exposure: case OR_No_Viable_Function: { CXXRecordDecl *Primary = cast(Template)->getTemplatedDecl(); diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp --- a/clang/lib/Sema/SemaLookup.cpp +++ b/clang/lib/Sema/SemaLookup.cpp @@ -3551,6 +3551,7 @@ Result->setKind(SpecialMemberOverloadResult::Ambiguous); break; + case OR_Exposure: case OR_No_Viable_Function: Result->setMethod(nullptr); Result->setKind(SpecialMemberOverloadResult::NoMemberOrDeleted); 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" @@ -977,3 +978,148 @@ 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; +}; + +class LambdaExprIsExposure : public ConstStmtVisitor { +public: + /// Find a LambdaExpr in the given expression. + static bool check(Sema &S, const Expr *E, SourceLocation L) { + LambdaExprIsExposure R(S, L); + R.Visit(E); + return R.getResult(); + } + + void VisitLambdaExpr(const LambdaExpr *LM) { + auto *LD = LM->getLambdaClass(); + S.Diag(L, diag::err_use_is_exposure) << LD; + 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: + LambdaExprIsExposure(Sema &S, SourceLocation L) : S(S), L(L) {} + Sema &S; + SourceLocation L; + bool Result = false; +}; + +bool Sema::diagnoseVarInitExposures(const VarDecl *VDecl, const Expr *Init, + bool OnlyLambdaExpr) { + + bool Res; + if (OnlyLambdaExpr) { + Res = LambdaExprIsExposure::check(*this, Init, VDecl->getLocation()); + } else { + Res = DeclRefIsExposure::check(*this, Init, VDecl->getLocation()); + } + return Res; +} + +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; +} diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -1497,6 +1497,7 @@ break; // Fall through. + case OR_Exposure: case OR_No_Viable_Function: ICS.setBad(BadConversionSequence::no_conversion, From, ToType); break; @@ -3523,6 +3524,8 @@ return Result; } + case OR_Exposure: + return OR_Exposure; case OR_No_Viable_Function: return OR_No_Viable_Function; case OR_Ambiguous: @@ -3749,6 +3752,9 @@ case OR_No_Viable_Function: return OR_No_Viable_Function; + case OR_Exposure: + return OR_Exposure; + case OR_Ambiguous: return OR_Ambiguous; } @@ -4847,6 +4853,7 @@ ICS.Ambiguous.addConversion(Cand->FoundDecl, Cand->Function); return true; + case OR_Exposure: case OR_No_Viable_Function: case OR_Deleted: // There was no suitable conversion, or we found a deleted @@ -6378,6 +6385,7 @@ case OR_Ambiguous: return diagnoseAmbiguousConversion(*this, Loc, From, Converter, T, ViableConversions); + case OR_Exposure: case OR_No_Viable_Function: if (diagnoseNoViableConversion(*this, Loc, From, Converter, T, HadMultipleCandidates, @@ -6542,21 +6550,37 @@ return; } - // Functions with internal linkage are only viable in the same module unit. - if (getLangOpts().CPlusPlusModules && Function->isInAnotherModuleUnit()) { - /// FIXME: Currently, the semantics of linkage in clang is slightly - /// different from the semantics in C++ spec. In C++ spec, only names - /// have linkage. So that all entities of the same should share one - /// linkage. But in clang, different entities of the same could have - /// different linkage. - NamedDecl *ND = Function; - if (auto *SpecInfo = Function->getTemplateSpecializationInfo()) - ND = SpecInfo->getTemplate(); - - if (ND->getFormalLinkage() == Linkage::InternalLinkage) { - Candidate.Viable = false; - Candidate.FailureKind = ovl_fail_module_mismatched; - return; + if (auto *MF = Function->getOwningModule()) { + if (getLangOpts().CPlusPlusModules && !MF->isModuleMapModule()) { + /// FIXME: Currently, the semantics of linkage in clang is slightly + /// different from the semantics in C++ spec. In C++ spec, only names + /// have linkage. So that all entities of the same should share one + /// linkage. But in clang, different entities of the same could have + /// different linkage. + NamedDecl *ND = Function; + if (auto *SpecInfo = Function->getTemplateSpecializationInfo()) + ND = SpecInfo->getTemplate(); + + // We need only check cases that originate from a different TU (i.e. have + // been imported) + if (ND->isInAnotherModuleUnit()) { + if (ND->getFormalLinkage() == Linkage::InternalLinkage) { + // Functions with internal linkage are only viable in the same TU. + // llvm::dbgs() << "exposure: "; + // Function->dump(); + Candidate.Viable = false; + Candidate.FailureKind = ovl_fail_module_exposure; + return; + } else if (ND->getFormalLinkage() == Linkage::ModuleLinkage && + !arePartOfSameNamedModule(ND->getOwningModule(), MF)) { + // Functions with module linkage are only viable in the same module. + // llvm::dbgs() << "not part of this module: "; + // Function->dump(); + Candidate.Viable = false; + Candidate.FailureKind = ovl_fail_module_mismatched; + return; + } + } } } @@ -10334,6 +10358,10 @@ if (Best == end() || isBetterOverloadCandidate(S, *Cand, *Best, Loc, Kind)) Best = Cand; + } else if (Cand->FailureKind == ovl_fail_module_exposure) { + // This candidate is an exposure and invalidates the whole set. + Best = end(); + return OR_Exposure; } else if (Cand->NotValidBecauseConstraintExprHasError()) { // This candidate has constraint that we were unable to evaluate because // it referenced an expression that contained an error. Rather than fall @@ -11585,7 +11613,14 @@ if (S.CheckFunctionConstraints(Fn, Satisfaction)) break; S.DiagnoseUnsatisfiedConstraint(Satisfaction); + break; } + case ovl_fail_module_exposure: + S.Diag(Fn->getLocation(), + diag::note_ovl_candidate_is_exposure) + << Fn; + return; + break; } } @@ -11975,6 +12010,11 @@ if (!Cand->Best) continue; break; + + case OCD_ExposureCandidates: + if (Cand->Viable || Cand->FailureKind != ovl_fail_module_exposure) + continue; + break; } Cands.push_back(Cand); @@ -13452,6 +13492,7 @@ (*Best)->IsADLCandidate); } + case OR_Exposure: case OR_No_Viable_Function: { // Try to recover by looking for viable functions which the user might // have meant to call. @@ -13480,9 +13521,9 @@ CandidateSet->NoteCandidates( PartialDiagnosticAt( Fn->getBeginLoc(), - SemaRef.PDiag(diag::err_ovl_no_viable_function_in_call) + SemaRef.PDiag(OverloadResult == OR_Exposure ? diag::err_ovl_exposure_function_in_call : diag::err_ovl_no_viable_function_in_call) << ULE->getName() << Fn->getSourceRange()), - SemaRef, OCD_AllCandidates, Args); + SemaRef, OverloadResult == OR_Exposure ? OCD_ExposureCandidates : OCD_AllCandidates, Args); break; } @@ -13734,6 +13775,7 @@ } } + case OR_Exposure: case OR_No_Viable_Function: // This is an erroneous use of an operator which can be overloaded by // a non-member function. Check for non-member operators which were @@ -14181,6 +14223,7 @@ } } + case OR_Exposure: case OR_No_Viable_Function: { // C++ [over.match.oper]p9: // If the operator is the operator , [...] and there are no @@ -14554,6 +14597,7 @@ } } + case OR_Exposure: case OR_No_Viable_Function: { PartialDiagnostic PD = CandidateSet.empty() @@ -14773,6 +14817,7 @@ Succeeded = true; break; + case OR_Exposure: case OR_No_Viable_Function: CandidateSet.NoteCandidates( PartialDiagnosticAt( @@ -15001,6 +15046,7 @@ // below. break; + case OR_Exposure: case OR_No_Viable_Function: { PartialDiagnostic PD = CandidateSet.empty() @@ -15201,6 +15247,7 @@ // Overload resolution succeeded; we'll build the call below. break; + case OR_Exposure: case OR_No_Viable_Function: { auto Cands = CandidateSet.CompleteCandidates(*this, OCD_AllCandidates, Base); if (CandidateSet.empty()) { @@ -15297,6 +15344,7 @@ case OR_Deleted: break; + case OR_Exposure: case OR_No_Viable_Function: CandidateSet.NoteCandidates( PartialDiagnosticAt(UDSuffixLoc, @@ -15403,7 +15451,7 @@ OverloadingResult OverloadResult = CandidateSet->BestViableFunction(*this, Fn->getBeginLoc(), Best); - if (OverloadResult == OR_No_Viable_Function) { + if (OverloadResult == OR_No_Viable_Function || OverloadResult == OR_Exposure) { *CallExpr = ExprError(); return FRS_NoViableFunction; } 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 @@ -5120,6 +5120,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,67 @@ +// 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; // expected-error {{use of TU-local entity 'x' is an exposure}} +int y = ([]{f();}(),0); // expected-error-re {{use of TU-local entity '(lambda at {{.*}})' is an exposure}} +#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{}); // expected-error@A.cpp:38 {{the overload set for the call to 'adl' contains one or more exposures}} + // expected-note@-1 {{in instantiation of function template specialization 'h' requested here}} + // expected-note@A.cpp:34 {{candidate 'adl' is an exposure}} + 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 diff --git a/clang/test/CXX/basic/basic.lookup/basic.lookup.argdep/p5-ex2.cpp b/clang/test/CXX/basic/basic.lookup/basic.lookup.argdep/p5-ex2.cpp --- a/clang/test/CXX/basic/basic.lookup/basic.lookup.argdep/p5-ex2.cpp +++ b/clang/test/CXX/basic/basic.lookup/basic.lookup.argdep/p5-ex2.cpp @@ -63,6 +63,7 @@ // error: S::f is visible in instantiation context, but R::g has internal // linkage and cannot be used outside N.cpp - apply(x, S::Z()); // expected-error@N.cpp:10 {{no matching function for call to 'g'}} + apply(x, S::Z()); // expected-error@N.cpp:10 {{the overload set for the call to 'g' contains one or more exposures}} // expected-note@-1 {{in instantiation of function template specialization 'apply' requested here}} + // expected-note@N.cpp:5 {{candidate 'g' is an exposure}} } diff --git a/clang/test/Modules/lambdas.cppm b/clang/test/Modules/lambdas.cppm --- a/clang/test/Modules/lambdas.cppm +++ b/clang/test/Modules/lambdas.cppm @@ -6,11 +6,6 @@ // RUN: -o %t/lambdas.pcm // RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/Use.cpp -fsyntax-only \ // RUN: -verify -// -// RUN: %clang_cc1 -std=c++20 %t/lambdas2.cppm -emit-module-interface \ -// RUN: -o %t/lambdas2.pcm -// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/Use.cpp -fsyntax-only \ -// RUN: -verify -DUSE_LAMBDA2 //--- lambdas.h auto l1 = []() constexpr -> int { @@ -38,19 +33,9 @@ export using ::l3; export using ::l4; -//--- lambdas2.cppm -export module lambdas2; -export { -#include "lambdas.h" -} - //--- Use.cpp // expected-no-diagnostics -#ifndef USE_LAMBDA2 import lambdas; -#else -import lambdas2; -#endif static_assert(l1.operator()() == 43); diff --git a/clang/test/Modules/template-lambdas.cppm b/clang/test/Modules/template-lambdas.cppm --- a/clang/test/Modules/template-lambdas.cppm +++ b/clang/test/Modules/template-lambdas.cppm @@ -6,11 +6,6 @@ // RUN: -o %t/lambdas.pcm // RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/Use.cpp -fsyntax-only \ // RUN: -verify -// -// RUN: %clang_cc1 -std=c++20 %t/template_lambdas2.cppm -emit-module-interface \ -// RUN: -o %t/lambdas2.pcm -// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/Use.cpp -fsyntax-only \ -// RUN: -verify -DUSE_LAMBDA2 //--- lambdas.h auto l1 = []() constexpr -> int { @@ -38,19 +33,9 @@ export using ::l3; export using ::l4; -//--- template_lambdas2.cppm -export module lambdas2; -export { -#include "lambdas.h" -} - //--- Use.cpp // expected-no-diagnostics -#ifndef USE_LAMBDA2 import lambdas; -#else -import lambdas2; -#endif static_assert(l1.operator()<5>() == 5); static_assert(l1.operator()<6>() == 6);