loplugin:unusedfields fix false positive

When the field in question is read from inside a constructor
initializer.

In the process, create some needed infrastructure in the plugin classes.

Change-Id: I2f440efa6912801a236727c9fe3180404616958c
Reviewed-on: https://gerrit.libreoffice.org/38960
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
This commit is contained in:
Noel Grandin 2017-06-19 13:44:14 +02:00
parent 42ab759336
commit 3b60f59bc5
7 changed files with 102 additions and 22 deletions

View File

@ -265,6 +265,11 @@ SourceLocation Plugin::locationAfterToken( SourceLocation location )
return Lexer::getLocForEndOfToken( location, 0, compiler.getSourceManager(), compiler.getLangOpts()); return Lexer::getLocForEndOfToken( location, 0, compiler.getSourceManager(), compiler.getLangOpts());
} }
bool Plugin::isUnitTestMode()
{
return PluginHandler::isUnitTestMode();
}
RewritePlugin::RewritePlugin( const InstantiationData& data ) RewritePlugin::RewritePlugin( const InstantiationData& data )
: Plugin( data ) : Plugin( data )
, rewriter( data.rewriter ) , rewriter( data.rewriter )

View File

@ -78,6 +78,8 @@ class Plugin
bool isInUnoIncludeFile(const FunctionDecl*) const; bool isInUnoIncludeFile(const FunctionDecl*) const;
static void normalizeDotDotInFilePath(std::string&); static void normalizeDotDotInFilePath(std::string&);
static bool isUnitTestMode();
private: private:
static void registerPlugin( Plugin* (*create)( const InstantiationData& ), const char* optionName, bool isPPCallback, bool byDefault ); static void registerPlugin( Plugin* (*create)( const InstantiationData& ), const char* optionName, bool isPPCallback, bool byDefault );
template< typename T > static Plugin* createHelper( const InstantiationData& data ); template< typename T > static Plugin* createHelper( const InstantiationData& data );

View File

@ -56,13 +56,13 @@ const int MAX_PLUGINS = 100;
static PluginData plugins[ MAX_PLUGINS ]; static PluginData plugins[ MAX_PLUGINS ];
static int pluginCount = 0; static int pluginCount = 0;
static bool bPluginObjectsCreated = false; static bool bPluginObjectsCreated = false;
static bool unitTestMode = false;
PluginHandler::PluginHandler( CompilerInstance& compiler, const vector< string >& args ) PluginHandler::PluginHandler( CompilerInstance& compiler, const vector< string >& args )
: compiler( compiler ) : compiler( compiler )
, rewriter( compiler.getSourceManager(), compiler.getLangOpts()) , rewriter( compiler.getSourceManager(), compiler.getLangOpts())
, scope( "mainfile" ) , scope( "mainfile" )
, warningsAsErrors( false ) , warningsAsErrors( false )
, unitTestMode( false )
{ {
set< string > rewriters; set< string > rewriters;
for( string const & arg : args ) for( string const & arg : args )
@ -89,6 +89,11 @@ PluginHandler::~PluginHandler()
} }
} }
bool PluginHandler::isUnitTestMode()
{
return unitTestMode;
}
void PluginHandler::handleOption( const string& option ) void PluginHandler::handleOption( const string& option )
{ {
if( option.substr( 0, 6 ) == "scope=" ) if( option.substr( 0, 6 ) == "scope=" )
@ -121,10 +126,13 @@ void PluginHandler::createPlugins( set< string > rewriters )
i < pluginCount; i < pluginCount;
++i ) ++i )
{ {
if( rewriters.erase( plugins[i].optionName ) != 0 ) const char* name = plugins[i].optionName;
plugins[ i ].object = plugins[ i ].create( Plugin::InstantiationData { plugins[ i ].optionName, *this, compiler, &rewriter } ); if( rewriters.erase( name ) != 0 )
plugins[ i ].object = plugins[ i ].create( Plugin::InstantiationData { name, *this, compiler, &rewriter } );
else if( plugins[ i ].byDefault ) else if( plugins[ i ].byDefault )
plugins[ i ].object = plugins[ i ].create( Plugin::InstantiationData { plugins[ i ].optionName, *this, compiler, NULL } ); plugins[ i ].object = plugins[ i ].create( Plugin::InstantiationData { name, *this, compiler, NULL } );
else if( unitTestMode && strcmp(name, "unusedmethodsremove") != 0 && strcmp(name, "unusedfieldsremove") != 0)
plugins[ i ].object = plugins[ i ].create( Plugin::InstantiationData { name, *this, compiler, NULL } );
} }
for( auto r: rewriters ) for( auto r: rewriters )
report( DiagnosticsEngine::Fatal, "unknown plugin tool %0" ) << r; report( DiagnosticsEngine::Fatal, "unknown plugin tool %0" ) << r;

View File

@ -37,6 +37,7 @@ class PluginHandler
DiagnosticBuilder report( DiagnosticsEngine::Level level, const char * plugin, StringRef message, DiagnosticBuilder report( DiagnosticsEngine::Level level, const char * plugin, StringRef message,
CompilerInstance& compiler, SourceLocation loc = SourceLocation()); CompilerInstance& compiler, SourceLocation loc = SourceLocation());
bool addRemoval( SourceLocation loc ); bool addRemoval( SourceLocation loc );
static bool isUnitTestMode();
private: private:
void handleOption( const string& option ); void handleOption( const string& option );
void createPlugins( set< string > rewriters ); void createPlugins( set< string > rewriters );
@ -47,7 +48,6 @@ class PluginHandler
string scope; string scope;
string warningsOnly; string warningsOnly;
bool warningsAsErrors; bool warningsAsErrors;
bool unitTestMode;
}; };
/** /**

View File

@ -0,0 +1,28 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <rtl/ustring.hxx>
struct Foo
// expected-error@-1 {{read m_foo1 [loplugin:unusedfields]}}
{
int m_foo1;
};
struct Bar
// expected-error@-1 {{read m_bar2 [loplugin:unusedfields]}}
{
int m_bar1;
int m_bar2 = 1;
Bar(Foo const & foo) : m_bar1(foo.m_foo1) {}
int bar() { return m_bar2; }
};
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */

View File

@ -44,6 +44,7 @@ namespace {
struct MyFieldInfo struct MyFieldInfo
{ {
const RecordDecl* parentRecord;
std::string parentClass; std::string parentClass;
std::string fieldName; std::string fieldName;
std::string fieldType; std::string fieldType;
@ -71,10 +72,25 @@ class UnusedFields:
public: public:
explicit UnusedFields(InstantiationData const & data): Plugin(data) {} explicit UnusedFields(InstantiationData const & data): Plugin(data) {}
virtual void run() override virtual void run() override;
{
TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
bool shouldVisitTemplateInstantiations () const { return true; }
bool shouldVisitImplicitCode() const { return true; }
bool VisitFieldDecl( const FieldDecl* );
bool VisitMemberExpr( const MemberExpr* );
bool VisitDeclRefExpr( const DeclRefExpr* );
private:
MyFieldInfo niceName(const FieldDecl*);
void checkTouched(const FieldDecl* fieldDecl, const Expr* memberExpr);
};
void UnusedFields::run()
{
TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
if (!isUnitTestMode())
{
// dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
// writing to the same logfile // writing to the same logfile
std::string output; std::string output;
@ -95,17 +111,19 @@ public:
myfile << output; myfile << output;
myfile.close(); myfile.close();
} }
else
{
for (const MyFieldInfo & s : readFromSet)
{
report(
DiagnosticsEngine::Warning,
"read %0",
s.parentRecord->getLocStart())
<< s.fieldName;
}
}
}
bool shouldVisitTemplateInstantiations () const { return true; }
bool shouldVisitImplicitCode() const { return true; }
bool VisitFieldDecl( const FieldDecl* );
bool VisitMemberExpr( const MemberExpr* );
bool VisitDeclRefExpr( const DeclRefExpr* );
private:
MyFieldInfo niceName(const FieldDecl*);
void checkTouched(const FieldDecl* fieldDecl, const Expr* memberExpr);
};
MyFieldInfo UnusedFields::niceName(const FieldDecl* fieldDecl) MyFieldInfo UnusedFields::niceName(const FieldDecl* fieldDecl)
{ {
@ -117,10 +135,14 @@ MyFieldInfo UnusedFields::niceName(const FieldDecl* fieldDecl)
{ {
if (cxxRecordDecl->getTemplateInstantiationPattern()) if (cxxRecordDecl->getTemplateInstantiationPattern())
cxxRecordDecl = cxxRecordDecl->getTemplateInstantiationPattern(); cxxRecordDecl = cxxRecordDecl->getTemplateInstantiationPattern();
aInfo.parentRecord = cxxRecordDecl;
aInfo.parentClass = cxxRecordDecl->getQualifiedNameAsString(); aInfo.parentClass = cxxRecordDecl->getQualifiedNameAsString();
} }
else else
{
aInfo.parentRecord = recordDecl;
aInfo.parentClass = recordDecl->getQualifiedNameAsString(); aInfo.parentClass = recordDecl->getQualifiedNameAsString();
}
aInfo.fieldName = fieldDecl->getNameAsString(); aInfo.fieldName = fieldDecl->getNameAsString();
// sometimes the name (if it's anonymous thing) contains the full path of the build folder, which we don't need // sometimes the name (if it's anonymous thing) contains the full path of the build folder, which we don't need
@ -217,20 +239,33 @@ bool UnusedFields::VisitMemberExpr( const MemberExpr* memberExpr )
// for the write-only analysis // for the write-only analysis
auto parentsRange = compiler.getASTContext().getParents(*memberExpr);
const Stmt* child = memberExpr; const Stmt* child = memberExpr;
const Stmt* parent = parentStmt(memberExpr); const Stmt* parent = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
// walk up the tree until we find something interesting // walk up the tree until we find something interesting
bool bPotentiallyReadFrom = false; bool bPotentiallyReadFrom = false;
bool bDump = false; bool bDump = false;
do { do {
if (!parent) { if (!parent) {
return true; // check if we're inside a CXXCtorInitializer
auto parentsRange = compiler.getASTContext().getParents(*child);
if ( parentsRange.begin() != parentsRange.end())
{
const Decl* decl = parentsRange.begin()->get<Decl>();
if (decl && isa<CXXConstructorDecl>(decl))
bPotentiallyReadFrom = true;
}
if (!bPotentiallyReadFrom)
return true;
else
break;
} }
if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent) || isa<ParenListExpr>(parent) if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent) || isa<ParenListExpr>(parent)
|| isa<ExprWithCleanups>(parent) || isa<UnaryOperator>(parent)) || isa<ExprWithCleanups>(parent) || isa<UnaryOperator>(parent))
{ {
child = parent; child = parent;
parent = parentStmt(parent); auto parentsRange = compiler.getASTContext().getParents(*parent);
parent = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
} }
else if (isa<CaseStmt>(parent)) else if (isa<CaseStmt>(parent))
{ {
@ -278,7 +313,8 @@ bool UnusedFields::VisitMemberExpr( const MemberExpr* memberExpr )
// so walk up the tree. // so walk up the tree.
if (binaryOp->getLHS() == child && op == BO_Assign) { if (binaryOp->getLHS() == child && op == BO_Assign) {
child = parent; child = parent;
parent = parentStmt(parent); auto parentsRange = compiler.getASTContext().getParents(*parent);
parent = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
} else { } else {
bPotentiallyReadFrom = true; bPotentiallyReadFrom = true;
break; break;

View File

@ -27,6 +27,7 @@ $(eval $(call gb_CompilerTest_add_exception_objects,compilerplugins_clang, \
compilerplugins/clang/test/stringconstant \ compilerplugins/clang/test/stringconstant \
compilerplugins/clang/test/unnecessaryoverride-dtor \ compilerplugins/clang/test/unnecessaryoverride-dtor \
compilerplugins/clang/test/unoany \ compilerplugins/clang/test/unoany \
compilerplugins/clang/test/unusedfields \
compilerplugins/clang/test/useuniqueptr \ compilerplugins/clang/test/useuniqueptr \
compilerplugins/clang/test/vclwidgets \ compilerplugins/clang/test/vclwidgets \
)) ))