From 4f2a6d6d73494f0503b934b0ba658bc04dee9e7b Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Sat, 6 Sep 2014 01:25:55 +0000 Subject: Add -Wunused-local-typedef, a warning that finds unused local typedefs. The warning warns on TypedefNameDecls -- typedefs and C++11 using aliases -- that are !isReferenced(). Since the isReferenced() bit on TypedefNameDecls wasn't used for anything before this warning it wasn't always set correctly, so this patch also adds a few missing MarkAnyDeclReferenced() calls in various places for TypedefNameDecls. This is made a bit complicated due to local typedefs possibly being used only after their local scope has closed. Consider: template void template_fun(T t) { typename T::Foo s3foo; // YYY (void)s3foo; } void template_fun_user() { struct Local { typedef int Foo; // XXX } p; template_fun(p); } Here the typedef in XXX is only used at end-of-translation unit, when YYY in template_fun() gets instantiated. To handle this, typedefs that are unused when their scope exits are added to a set of potentially unused typedefs, and that set gets checked at end-of-TU. Typedefs that are still unused at that point then get warned on. There's also serialization code for this set, so that the warning works with precompiled headers and modules. For modules, the warning is emitted when the module is built, for precompiled headers each time the header gets used. Finally, consider a function using C++14 auto return types to return a local type defined in a header: auto f() { struct S { typedef int a; }; return S(); } Here, the typedef escapes its local scope and could be used by only some translation units including the header. To not warn on this, add a RecursiveASTVisitor that marks all delcs on local types returned from auto functions as referenced. (Except if it's a function with internal linkage, or the decls are private and the local type has no friends -- in these cases, it _is_ safe to warn.) Several of the included testcases (most of the interesting ones) were provided by Richard Smith. (gcc's spelling -Wunused-local-typedefs is supported as an alias for this warning.) git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@217298 91177308-0d34-0410-b5e6-96231b3b80d8 --- lib/Sema/MultiplexExternalSemaSource.cpp | 6 +++++ lib/Sema/Sema.cpp | 19 +++++++++++++++ lib/Sema/SemaCXXScopeSpec.cpp | 3 +++ lib/Sema/SemaDecl.cpp | 42 +++++++++++++++++++++++++++++--- lib/Sema/SemaStmt.cpp | 40 ++++++++++++++++++++++++++++++ lib/Sema/SemaStmtAsm.cpp | 5 ++-- lib/Sema/SemaTemplate.cpp | 1 + lib/Sema/SemaTemplateInstantiateDecl.cpp | 5 +++- lib/Serialization/ASTReader.cpp | 18 ++++++++++++++ lib/Serialization/ASTWriter.cpp | 10 ++++++++ 10 files changed, 143 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/Sema/MultiplexExternalSemaSource.cpp b/lib/Sema/MultiplexExternalSemaSource.cpp index 97237dbf09..449ddf4311 100644 --- a/lib/Sema/MultiplexExternalSemaSource.cpp +++ b/lib/Sema/MultiplexExternalSemaSource.cpp @@ -242,6 +242,12 @@ void MultiplexExternalSemaSource::ReadDynamicClasses( Sources[i]->ReadDynamicClasses(Decls); } +void MultiplexExternalSemaSource::ReadUnusedLocalTypedefNameCandidates( + llvm::SmallSetVector &Decls) { + for(size_t i = 0; i < Sources.size(); ++i) + Sources[i]->ReadUnusedLocalTypedefNameCandidates(Decls); +} + void MultiplexExternalSemaSource::ReadLocallyScopedExternCDecls( SmallVectorImpl &Decls) { for(size_t i = 0; i < Sources.size(); ++i) diff --git a/lib/Sema/Sema.cpp b/lib/Sema/Sema.cpp index c762d5fdac..69a4356b45 100644 --- a/lib/Sema/Sema.cpp +++ b/lib/Sema/Sema.cpp @@ -597,6 +597,19 @@ static bool IsRecordFullyDefined(const CXXRecordDecl *RD, return Complete; } +void Sema::emitAndClearUnusedLocalTypedefWarnings() { + if (ExternalSource) + ExternalSource->ReadUnusedLocalTypedefNameCandidates( + UnusedLocalTypedefNameCandidates); + for (const TypedefNameDecl *TD : UnusedLocalTypedefNameCandidates) { + if (TD->isReferenced()) + continue; + Diag(TD->getLocation(), diag::warn_unused_local_typedef) + << isa(TD) << TD->getDeclName(); + } + UnusedLocalTypedefNameCandidates.clear(); +} + /// ActOnEndOfTranslationUnit - This is called at the very end of the /// translation unit when EOF is reached and all but the top-level scope is /// popped. @@ -719,6 +732,10 @@ void Sema::ActOnEndOfTranslationUnit() { } } + // Warnings emitted in ActOnEndOfTranslationUnit() should be emitted for + // modules when they are built, not every time they are used. + emitAndClearUnusedLocalTypedefWarnings(); + // Modules don't need any of the checking below. TUScope = nullptr; return; @@ -827,6 +844,8 @@ void Sema::ActOnEndOfTranslationUnit() { if (ExternalSource) ExternalSource->ReadUndefinedButUsed(UndefinedButUsed); checkUndefinedButUsed(*this); + + emitAndClearUnusedLocalTypedefWarnings(); } if (!Diags.isIgnored(diag::warn_unused_private_field, SourceLocation())) { diff --git a/lib/Sema/SemaCXXScopeSpec.cpp b/lib/Sema/SemaCXXScopeSpec.cpp index b1b8b5d1dc..d3f0fad315 100644 --- a/lib/Sema/SemaCXXScopeSpec.cpp +++ b/lib/Sema/SemaCXXScopeSpec.cpp @@ -612,6 +612,9 @@ bool Sema::BuildCXXNestedNameSpecifier(Scope *S, } } + if (auto *TD = dyn_cast_or_null(SD)) + MarkAnyDeclReferenced(TD->getLocation(), TD, /*OdrUse=*/false); + // If we're just performing this lookup for error-recovery purposes, // don't extend the nested-name-specifier. Just return now. if (ErrorRecoveryLookup) diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp index 8834705e74..27f69adff5 100644 --- a/lib/Sema/SemaDecl.cpp +++ b/lib/Sema/SemaDecl.cpp @@ -380,6 +380,7 @@ ParsedType Sema::getTypeName(const IdentifierInfo &II, SourceLocation NameLoc, DiagnoseUseOfDecl(IIDecl, NameLoc); T = Context.getTypeDeclType(TD); + MarkAnyDeclReferenced(TD->getLocation(), TD, /*OdrUse=*/false); // NOTE: avoid constructing an ElaboratedType(Loc) if this is a // constructor or destructor name (in such a case, the scope specifier @@ -928,6 +929,7 @@ Corrected: NamedDecl *FirstDecl = (*Result.begin())->getUnderlyingDecl(); if (TypeDecl *Type = dyn_cast(FirstDecl)) { DiagnoseUseOfDecl(Type, NameLoc); + MarkAnyDeclReferenced(Type->getLocation(), Type, /*OdrUse=*/false); QualType T = Context.getTypeDeclType(Type); if (SS.isNotEmpty()) return buildNestedType(*this, SS, T, NameLoc); @@ -1395,10 +1397,22 @@ static bool ShouldDiagnoseUnusedDecl(const NamedDecl *D) { if (isa(D)) return true; + + // Except for labels, we only care about unused decls that are local to + // functions. + bool WithinFunction = D->getDeclContext()->isFunctionOrMethod(); + if (const auto *R = dyn_cast(D->getDeclContext())) + // For dependent types, the diagnostic is deferred. + WithinFunction = + WithinFunction || (R->isLocalClass() && !R->isDependentType()); + if (!WithinFunction) + return false; + + if (isa(D)) + return true; // White-list anything that isn't a local variable. - if (!isa(D) || isa(D) || isa(D) || - !D->getDeclContext()->isFunctionOrMethod()) + if (!isa(D) || isa(D) || isa(D)) return false; // Types of valid local variables should be complete, so this should succeed. @@ -1461,11 +1475,30 @@ static void GenerateFixForUnusedDecl(const NamedDecl *D, ASTContext &Ctx, return; } +void Sema::DiagnoseUnusedNestedTypedefs(const RecordDecl *D) { + if (D->getTypeForDecl()->isDependentType()) + return; + + for (auto *TmpD : D->decls()) { + if (const auto *T = dyn_cast(TmpD)) + DiagnoseUnusedDecl(T); + else if(const auto *R = dyn_cast(TmpD)) + DiagnoseUnusedNestedTypedefs(R); + } +} + /// DiagnoseUnusedDecl - Emit warnings about declarations that are not used /// unless they are marked attr(unused). void Sema::DiagnoseUnusedDecl(const NamedDecl *D) { if (!ShouldDiagnoseUnusedDecl(D)) return; + + if (auto *TD = dyn_cast(D)) { + // typedefs can be referenced later on, so the diagnostics are emitted + // at end-of-translation-unit. + UnusedLocalTypedefNameCandidates.insert(TD); + return; + } FixItHint Hint; GenerateFixForUnusedDecl(D, Context, Hint); @@ -1505,8 +1538,11 @@ void Sema::ActOnPopScope(SourceLocation Loc, Scope *S) { if (!D->getDeclName()) continue; // Diagnose unused variables in this scope. - if (!S->hasUnrecoverableErrorOccurred()) + if (!S->hasUnrecoverableErrorOccurred()) { DiagnoseUnusedDecl(D); + if (const auto *RD = dyn_cast(D)) + DiagnoseUnusedNestedTypedefs(RD); + } // If this was a forward reference to a label, verify it was defined. if (LabelDecl *LD = dyn_cast(D)) diff --git a/lib/Sema/SemaStmt.cpp b/lib/Sema/SemaStmt.cpp index ab0bfcd9f9..8d2a3258b1 100644 --- a/lib/Sema/SemaStmt.cpp +++ b/lib/Sema/SemaStmt.cpp @@ -19,6 +19,7 @@ #include "clang/AST/EvaluatedExprVisitor.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/ExprObjC.h" +#include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/StmtCXX.h" #include "clang/AST/StmtObjC.h" #include "clang/AST/TypeLoc.h" @@ -2710,6 +2711,40 @@ Sema::ActOnCapScopeReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp) { return Result; } +namespace { +/// \brief Marks all typedefs in all local classes in a type referenced. +/// +/// In a function like +/// auto f() { +/// struct S { typedef int a; }; +/// return S(); +/// } +/// +/// the local type escapes and could be referenced in some TUs but not in +/// others. Pretend that all local typedefs are always referenced, to not warn +/// on this. This isn't necessary if f has internal linkage, or the typedef +/// is private. +class LocalTypedefNameReferencer + : public RecursiveASTVisitor { +public: + LocalTypedefNameReferencer(Sema &S) : S(S) {} + bool VisitRecordType(const RecordType *RT); +private: + Sema &S; +}; +bool LocalTypedefNameReferencer::VisitRecordType(const RecordType *RT) { + auto *R = dyn_cast(RT->getDecl()); + if (!R || !R->isLocalClass() || !R->isLocalClass()->isExternallyVisible() || + R->isDependentType()) + return true; + for (auto *TmpD : R->decls()) + if (auto *T = dyn_cast(TmpD)) + if (T->getAccess() != AS_private || R->hasFriends()) + S.MarkAnyDeclReferenced(T->getLocation(), T, /*OdrUse=*/false); + return true; +} +} + /// Deduce the return type for a function from a returned expression, per /// C++1y [dcl.spec.auto]p6. bool Sema::DeduceFunctionTypeFromReturnExpr(FunctionDecl *FD, @@ -2755,6 +2790,11 @@ bool Sema::DeduceFunctionTypeFromReturnExpr(FunctionDecl *FD, if (DAR != DAR_Succeeded) return true; + + // If a local type is part of the returned type, mark its fields as + // referenced. + LocalTypedefNameReferencer Referencer(*this); + Referencer.TraverseType(RetExpr->getType()); } else { // In the case of a return with no operand, the initializer is considered // to be void(). diff --git a/lib/Sema/SemaStmtAsm.cpp b/lib/Sema/SemaStmtAsm.cpp index 989999f18d..69cf6459d2 100644 --- a/lib/Sema/SemaStmtAsm.cpp +++ b/lib/Sema/SemaStmtAsm.cpp @@ -467,9 +467,10 @@ bool Sema::LookupInlineAsmField(StringRef Base, StringRef Member, NamedDecl *FoundDecl = BaseResult.getFoundDecl(); if (VarDecl *VD = dyn_cast(FoundDecl)) RT = VD->getType()->getAs(); - else if (TypedefNameDecl *TD = dyn_cast(FoundDecl)) + else if (TypedefNameDecl *TD = dyn_cast(FoundDecl)) { + MarkAnyDeclReferenced(TD->getLocation(), TD, /*OdrUse=*/false); RT = TD->getUnderlyingType()->getAs(); - else if (TypeDecl *TD = dyn_cast(FoundDecl)) + } else if (TypeDecl *TD = dyn_cast(FoundDecl)) RT = TD->getTypeForDecl()->getAs(); if (!RT) return true; diff --git a/lib/Sema/SemaTemplate.cpp b/lib/Sema/SemaTemplate.cpp index fb05718ff1..3c37a3e2d0 100644 --- a/lib/Sema/SemaTemplate.cpp +++ b/lib/Sema/SemaTemplate.cpp @@ -7945,6 +7945,7 @@ Sema::CheckTypenameType(ElaboratedTypeKeyword Keyword, if (TypeDecl *Type = dyn_cast(Result.getFoundDecl())) { // We found a type. Build an ElaboratedType, since the // typename-specifier was just sugar. + MarkAnyDeclReferenced(Type->getLocation(), Type, /*OdrUse=*/false); return Context.getElaboratedType(ETK_Typename, QualifierLoc.getNestedNameSpecifier(), Context.getTypeDeclType(Type)); diff --git a/lib/Sema/SemaTemplateInstantiateDecl.cpp b/lib/Sema/SemaTemplateInstantiateDecl.cpp index ce18f6e369..62a3e11ac9 100644 --- a/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -1200,6 +1200,9 @@ Decl *TemplateDeclInstantiator::VisitCXXRecordDecl(CXXRecordDecl *D) { SemaRef.InstantiateClassMembers(D->getLocation(), Record, TemplateArgs, TSK_ImplicitInstantiation); } + + SemaRef.DiagnoseUnusedNestedTypedefs(Record); + return Record; } @@ -3653,7 +3656,7 @@ void Sema::BuildVariableInstantiation( // Diagnose unused local variables with dependent types, where the diagnostic // will have been deferred. if (!NewVar->isInvalidDecl() && - NewVar->getDeclContext()->isFunctionOrMethod() && !NewVar->isUsed() && + NewVar->getDeclContext()->isFunctionOrMethod() && OldVar->getType()->isDependentType()) DiagnoseUnusedDecl(NewVar); } diff --git a/lib/Serialization/ASTReader.cpp b/lib/Serialization/ASTReader.cpp index 5b0aa1484b..8f1d88c6d5 100644 --- a/lib/Serialization/ASTReader.cpp +++ b/lib/Serialization/ASTReader.cpp @@ -3285,6 +3285,12 @@ ASTReader::ReadASTBlock(ModuleFile &F, unsigned ClientLoadCapabilities) { } OptimizeOffPragmaLocation = ReadSourceLocation(F, Record[0]); break; + + case UNUSED_LOCAL_TYPEDEF_NAME_CANDIDATES: + for (unsigned I = 0, N = Record.size(); I != N; ++I) + UnusedLocalTypedefNameCandidates.push_back( + getGlobalDeclID(F, Record[I])); + break; } } } @@ -7196,6 +7202,18 @@ void ASTReader::ReadDynamicClasses(SmallVectorImpl &Decls) { DynamicClasses.clear(); } +void ASTReader::ReadUnusedLocalTypedefNameCandidates( + llvm::SmallSetVector &Decls) { + for (unsigned I = 0, N = UnusedLocalTypedefNameCandidates.size(); I != N; + ++I) { + TypedefNameDecl *D = dyn_cast_or_null( + GetDecl(UnusedLocalTypedefNameCandidates[I])); + if (D) + Decls.insert(D); + } + UnusedLocalTypedefNameCandidates.clear(); +} + void ASTReader::ReadLocallyScopedExternCDecls(SmallVectorImpl &Decls) { for (unsigned I = 0, N = LocallyScopedExternCDecls.size(); I != N; ++I) { diff --git a/lib/Serialization/ASTWriter.cpp b/lib/Serialization/ASTWriter.cpp index 165119b7c6..973d8a28e1 100644 --- a/lib/Serialization/ASTWriter.cpp +++ b/lib/Serialization/ASTWriter.cpp @@ -4284,6 +4284,11 @@ void ASTWriter::WriteASTCore(Sema &SemaRef, } } + // Build a record containing all of the UnusedLocalTypedefNameCandidates. + RecordData UnusedLocalTypedefNameCandidates; + for (const TypedefNameDecl *TD : SemaRef.UnusedLocalTypedefNameCandidates) + AddDeclRef(TD, UnusedLocalTypedefNameCandidates); + // Build a record containing all of dynamic classes declarations. RecordData DynamicClasses; AddLazyVectorDecls(*this, SemaRef.DynamicClasses, DynamicClasses); @@ -4561,6 +4566,11 @@ void ASTWriter::WriteASTCore(Sema &SemaRef, if (!DynamicClasses.empty()) Stream.EmitRecord(DYNAMIC_CLASSES, DynamicClasses); + // Write the record containing potentially unused local typedefs. + if (!UnusedLocalTypedefNameCandidates.empty()) + Stream.EmitRecord(UNUSED_LOCAL_TYPEDEF_NAME_CANDIDATES, + UnusedLocalTypedefNameCandidates); + // Write the record containing pending implicit instantiations. if (!PendingInstantiations.empty()) Stream.EmitRecord(PENDING_IMPLICIT_INSTANTIATIONS, PendingInstantiations); -- cgit v1.2.1