Skip to content

Commit

Permalink
Add -Wunused-local-typedef, a warning that finds unused local typedefs.
Browse files Browse the repository at this point in the history
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 <class T>
    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.)

llvm-svn: 217298
  • Loading branch information
nico committed Sep 6, 2014
1 parent aaa0b81 commit 7288943
Show file tree
Hide file tree
Showing 25 changed files with 451 additions and 12 deletions.
5 changes: 4 additions & 1 deletion clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ def UnusedValue : DiagGroup<"unused-value", [UnusedComparison, UnusedResult]>;
def UnusedConstVariable : DiagGroup<"unused-const-variable">;
def UnusedVariable : DiagGroup<"unused-variable",
[UnusedConstVariable]>;
def UnusedLocalTypedef : DiagGroup<"unused-local-typedef">;
def UnusedPropertyIvar : DiagGroup<"unused-property-ivar">;
def UsedButMarkedUnused : DiagGroup<"used-but-marked-unused">;
def UserDefinedLiterals : DiagGroup<"user-defined-literals">;
Expand Down Expand Up @@ -526,7 +527,7 @@ def Unused : DiagGroup<"unused",
[UnusedArgument, UnusedFunction, UnusedLabel,
// UnusedParameter, (matches GCC's behavior)
// UnusedMemberFunction, (clean-up llvm before enabling)
UnusedPrivateField,
UnusedPrivateField, UnusedLocalTypedef,
UnusedValue, UnusedVariable, UnusedPropertyIvar]>,
DiagCategory<"Unused Entity Issue">;

Expand Down Expand Up @@ -622,6 +623,8 @@ def : DiagGroup<"int-conversions",
[IntConversion]>; // -Wint-conversions = -Wint-conversion
def : DiagGroup<"vector-conversions",
[VectorConversion]>; // -Wvector-conversions = -Wvector-conversion
def : DiagGroup<"unused-local-typedefs", [UnusedLocalTypedef]>;
// -Wunused-local-typedefs = -Wunused-local-typedef

// A warning group for warnings that we want to have on by default in clang,
// but which aren't on by default in GCC.
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ def warn_unused_parameter : Warning<"unused parameter %0">,
InGroup<UnusedParameter>, DefaultIgnore;
def warn_unused_variable : Warning<"unused variable %0">,
InGroup<UnusedVariable>, DefaultIgnore;
def warn_unused_local_typedef : Warning<
"unused %select{typedef|type alias}0 %1">,
InGroup<UnusedLocalTypedef>, DefaultIgnore;
def warn_unused_property_backing_ivar :
Warning<"ivar %0 which backs the property is not "
"referenced in this property's accessor">,
Expand Down
13 changes: 13 additions & 0 deletions clang/include/clang/Sema/ExternalSemaSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
#include "llvm/ADT/MapVector.h"
#include <utility>

namespace llvm {
template <class T, unsigned n> class SmallSetVector;
}

namespace clang {

class CXXConstructorDecl;
Expand Down Expand Up @@ -132,6 +136,15 @@ class ExternalSemaSource : public ExternalASTSource {
/// introduce the same declarations repeatedly.
virtual void ReadDynamicClasses(SmallVectorImpl<CXXRecordDecl *> &Decls) {}

/// \brief Read the set of potentially unused typedefs known to the source.
///
/// The external source should append its own potentially unused local
/// typedefs to the given vector of declarations. Note that this routine may
/// be invoked multiple times; the external source should take care not to
/// introduce the same declarations repeatedly.
virtual void ReadUnusedLocalTypedefNameCandidates(
llvm::SmallSetVector<const TypedefNameDecl *, 4> &Decls) {};

/// \brief Read the set of locally-scoped external declarations known to the
/// external Sema source.
///
Expand Down
9 changes: 9 additions & 0 deletions clang/include/clang/Sema/MultiplexExternalSemaSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,15 @@ class MultiplexExternalSemaSource : public ExternalSemaSource {
/// introduce the same declarations repeatedly.
void ReadDynamicClasses(SmallVectorImpl<CXXRecordDecl*> &Decls) override;

/// \brief Read the set of potentially unused typedefs known to the source.
///
/// The external source should append its own potentially unused local
/// typedefs to the given vector of declarations. Note that this routine may
/// be invoked multiple times; the external source should take care not to
/// introduce the same declarations repeatedly.
void ReadUnusedLocalTypedefNameCandidates(
llvm::SmallSetVector<const TypedefNameDecl *, 4> &Decls) override;

/// \brief Read the set of locally-scoped extern "C" declarations known to the
/// external Sema source.
///
Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,10 @@ class Sema {
/// \brief Set containing all declared private fields that are not used.
NamedDeclSetType UnusedPrivateFields;

/// \brief Set containing all typedefs that are likely unused.
llvm::SmallSetVector<const TypedefNameDecl *, 4>
UnusedLocalTypedefNameCandidates;

typedef llvm::SmallPtrSet<const CXXRecordDecl*, 8> RecordDeclSetTy;

/// PureVirtualClassDiagSet - a set of class declarations which we have
Expand Down Expand Up @@ -1048,6 +1052,8 @@ class Sema {
/// \brief Retrieve the module loader associated with the preprocessor.
ModuleLoader &getModuleLoader() const;

void emitAndClearUnusedLocalTypedefWarnings();

void ActOnEndOfTranslationUnit();

void CheckDelegatingCtorCycles();
Expand Down Expand Up @@ -3209,6 +3215,7 @@ class Sema {
/// DiagnoseUnusedExprResult - If the statement passed in is an expression
/// whose result is unused, warn.
void DiagnoseUnusedExprResult(const Stmt *S);
void DiagnoseUnusedNestedTypedefs(const RecordDecl *D);
void DiagnoseUnusedDecl(const NamedDecl *ND);

/// Emit \p DiagID if statement located on \p StmtLoc has a suspicious null
Expand Down
5 changes: 4 additions & 1 deletion clang/include/clang/Serialization/ASTBitCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,10 @@ namespace clang {
LATE_PARSED_TEMPLATE = 50,

/// \brief Record code for \#pragma optimize options.
OPTIMIZE_PRAGMA_OPTIONS = 51
OPTIMIZE_PRAGMA_OPTIONS = 51,

/// \brief Record code for potentially unused local typedef names.
UNUSED_LOCAL_TYPEDEF_NAME_CANDIDATES = 52,
};

/// \brief Record types used within a source manager block.
Expand Down
8 changes: 8 additions & 0 deletions clang/include/clang/Serialization/ASTReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,11 @@ class ASTReader
/// at the end of the TU, in which case it directs CodeGen to emit the VTable.
SmallVector<uint64_t, 16> DynamicClasses;

/// \brief The IDs of all potentially unused typedef names in the chain.
///
/// Sema tracks these to emit warnings.
SmallVector<uint64_t, 16> UnusedLocalTypedefNameCandidates;

/// \brief The IDs of the declarations Sema stores directly.
///
/// Sema tracks a few important decls, such as namespace std, directly.
Expand Down Expand Up @@ -1789,6 +1794,9 @@ class ASTReader

void ReadDynamicClasses(SmallVectorImpl<CXXRecordDecl *> &Decls) override;

void ReadUnusedLocalTypedefNameCandidates(
llvm::SmallSetVector<const TypedefNameDecl *, 4> &Decls) override;

void ReadLocallyScopedExternCDecls(
SmallVectorImpl<NamedDecl *> &Decls) override;

Expand Down
6 changes: 6 additions & 0 deletions clang/lib/Sema/MultiplexExternalSemaSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,12 @@ void MultiplexExternalSemaSource::ReadDynamicClasses(
Sources[i]->ReadDynamicClasses(Decls);
}

void MultiplexExternalSemaSource::ReadUnusedLocalTypedefNameCandidates(
llvm::SmallSetVector<const TypedefNameDecl *, 4> &Decls) {
for(size_t i = 0; i < Sources.size(); ++i)
Sources[i]->ReadUnusedLocalTypedefNameCandidates(Decls);
}

void MultiplexExternalSemaSource::ReadLocallyScopedExternCDecls(
SmallVectorImpl<NamedDecl*> &Decls) {
for(size_t i = 0; i < Sources.size(); ++i)
Expand Down
19 changes: 19 additions & 0 deletions clang/lib/Sema/Sema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<TypeAliasDecl>(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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -827,6 +844,8 @@ void Sema::ActOnEndOfTranslationUnit() {
if (ExternalSource)
ExternalSource->ReadUndefinedButUsed(UndefinedButUsed);
checkUndefinedButUsed(*this);

emitAndClearUnusedLocalTypedefWarnings();
}

if (!Diags.isIgnored(diag::warn_unused_private_field, SourceLocation())) {
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Sema/SemaCXXScopeSpec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,9 @@ bool Sema::BuildCXXNestedNameSpecifier(Scope *S,
}
}

if (auto *TD = dyn_cast_or_null<TypedefNameDecl>(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)
Expand Down
42 changes: 39 additions & 3 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -928,6 +929,7 @@ Sema::NameClassification Sema::ClassifyName(Scope *S,
NamedDecl *FirstDecl = (*Result.begin())->getUnderlyingDecl();
if (TypeDecl *Type = dyn_cast<TypeDecl>(FirstDecl)) {
DiagnoseUseOfDecl(Type, NameLoc);
MarkAnyDeclReferenced(Type->getLocation(), Type, /*OdrUse=*/false);
QualType T = Context.getTypeDeclType(Type);
if (SS.isNotEmpty())
return buildNestedType(*this, SS, T, NameLoc);
Expand Down Expand Up @@ -1395,10 +1397,22 @@ static bool ShouldDiagnoseUnusedDecl(const NamedDecl *D) {

if (isa<LabelDecl>(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<CXXRecordDecl>(D->getDeclContext()))
// For dependent types, the diagnostic is deferred.
WithinFunction =
WithinFunction || (R->isLocalClass() && !R->isDependentType());
if (!WithinFunction)
return false;

if (isa<TypedefNameDecl>(D))
return true;

// White-list anything that isn't a local variable.
if (!isa<VarDecl>(D) || isa<ParmVarDecl>(D) || isa<ImplicitParamDecl>(D) ||
!D->getDeclContext()->isFunctionOrMethod())
if (!isa<VarDecl>(D) || isa<ParmVarDecl>(D) || isa<ImplicitParamDecl>(D))
return false;

// Types of valid local variables should be complete, so this should succeed.
Expand Down Expand Up @@ -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<TypedefNameDecl>(TmpD))
DiagnoseUnusedDecl(T);
else if(const auto *R = dyn_cast<RecordDecl>(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<TypedefNameDecl>(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);
Expand Down Expand Up @@ -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<RecordDecl>(D))
DiagnoseUnusedNestedTypedefs(RD);
}

// If this was a forward reference to a label, verify it was defined.
if (LabelDecl *LD = dyn_cast<LabelDecl>(D))
Expand Down
40 changes: 40 additions & 0 deletions clang/lib/Sema/SemaStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<LocalTypedefNameReferencer> {
public:
LocalTypedefNameReferencer(Sema &S) : S(S) {}
bool VisitRecordType(const RecordType *RT);
private:
Sema &S;
};
bool LocalTypedefNameReferencer::VisitRecordType(const RecordType *RT) {
auto *R = dyn_cast<CXXRecordDecl>(RT->getDecl());
if (!R || !R->isLocalClass() || !R->isLocalClass()->isExternallyVisible() ||
R->isDependentType())
return true;
for (auto *TmpD : R->decls())
if (auto *T = dyn_cast<TypedefNameDecl>(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,
Expand Down Expand Up @@ -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().
Expand Down
5 changes: 3 additions & 2 deletions clang/lib/Sema/SemaStmtAsm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -467,9 +467,10 @@ bool Sema::LookupInlineAsmField(StringRef Base, StringRef Member,
NamedDecl *FoundDecl = BaseResult.getFoundDecl();
if (VarDecl *VD = dyn_cast<VarDecl>(FoundDecl))
RT = VD->getType()->getAs<RecordType>();
else if (TypedefNameDecl *TD = dyn_cast<TypedefNameDecl>(FoundDecl))
else if (TypedefNameDecl *TD = dyn_cast<TypedefNameDecl>(FoundDecl)) {
MarkAnyDeclReferenced(TD->getLocation(), TD, /*OdrUse=*/false);
RT = TD->getUnderlyingType()->getAs<RecordType>();
else if (TypeDecl *TD = dyn_cast<TypeDecl>(FoundDecl))
} else if (TypeDecl *TD = dyn_cast<TypeDecl>(FoundDecl))
RT = TD->getTypeForDecl()->getAs<RecordType>();
if (!RT)
return true;
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7945,6 +7945,7 @@ Sema::CheckTypenameType(ElaboratedTypeKeyword Keyword,
if (TypeDecl *Type = dyn_cast<TypeDecl>(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));
Expand Down
5 changes: 4 additions & 1 deletion clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,9 @@ Decl *TemplateDeclInstantiator::VisitCXXRecordDecl(CXXRecordDecl *D) {
SemaRef.InstantiateClassMembers(D->getLocation(), Record, TemplateArgs,
TSK_ImplicitInstantiation);
}

SemaRef.DiagnoseUnusedNestedTypedefs(Record);

return Record;
}

Expand Down Expand Up @@ -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);
}
Expand Down
Loading

0 comments on commit 7288943

Please sign in to comment.