diff options
Diffstat (limited to 'compilerplugins')
-rw-r--r-- | compilerplugins/.gitignore | 1 | ||||
-rw-r--r-- | compilerplugins/Makefile | 23 | ||||
-rw-r--r-- | compilerplugins/Makefile-clang.mk | 73 | ||||
-rw-r--r-- | compilerplugins/Makefile.mk | 32 | ||||
-rw-r--r-- | compilerplugins/README | 46 | ||||
-rw-r--r-- | compilerplugins/clang/bodynotinblock.cxx | 138 | ||||
-rw-r--r-- | compilerplugins/clang/bodynotinblock.hxx | 35 | ||||
-rw-r--r-- | compilerplugins/clang/compileplugin.cxx | 100 | ||||
-rw-r--r-- | compilerplugins/clang/compileplugin.hxx | 47 | ||||
-rw-r--r-- | compilerplugins/clang/unusedvariablecheck.cxx | 102 | ||||
-rw-r--r-- | compilerplugins/clang/unusedvariablecheck.hxx | 31 |
11 files changed, 628 insertions, 0 deletions
diff --git a/compilerplugins/.gitignore b/compilerplugins/.gitignore new file mode 100644 index 000000000000..b672fdeaf35b --- /dev/null +++ b/compilerplugins/.gitignore @@ -0,0 +1 @@ +obj diff --git a/compilerplugins/Makefile b/compilerplugins/Makefile new file mode 100644 index 000000000000..4281c12da996 --- /dev/null +++ b/compilerplugins/Makefile @@ -0,0 +1,23 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +ifeq ($(SOLARENV),) +ifeq ($(gb_Side),) +gb_Side:=host +endif +include $(dir $(realpath $(lastword $(MAKEFILE_LIST))))../config_$(gb_Side).mk +endif + +include $(SRCDIR)/compilerplugins/Makefile.mk + +all: build +build: compilerplugins +clean: compilerplugins-clean + +# vim: set noet sw=4 ts=4: diff --git a/compilerplugins/Makefile-clang.mk b/compilerplugins/Makefile-clang.mk new file mode 100644 index 000000000000..e9192fe4a1ac --- /dev/null +++ b/compilerplugins/Makefile-clang.mk @@ -0,0 +1,73 @@ +# +# 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/. +# + +# Make sure variables in this Makefile do not conflict with other variables (e.g. from gbuild). + +# The list of source files. +CLANGSRC=compileplugin.cxx \ + bodynotinblock.cxx \ + unusedvariablecheck.cxx \ + + +# You may occassionally want to override some of these + +# Compile flags ('make CLANGCXXFLAGS=-g' if you need to debug the plugin) +CLANGCXXFLAGS=-O2 -Wall -g +# The prefix where Clang resides, override to where Clang resides if using a source build. +CLANGDIR=/usr +# The build directory (different from CLANGDIR if using a Clang out-of-source build) +CLANGBUILD=/usr + +# The uninteresting rest. + +# Clang headers require these. +CLANGDEFS=-D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -fno-rtti +# All include locations needed. +CLANGINCLUDES=-I$(CLANGDIR)/include -I$(CLANGDIR)/tools/clang/include -I$(CLANGBUILD)/include -I$(CLANGBUILD)/tools/clang/include + +# Clang/LLVM libraries are intentionally not linked in, they are usually built as static libraries, which means the resulting +# plugin would be big (even though the clang binary already includes it all) and it'd be necessary to explicitly specify +# also all the dependency libraries. + +CLANGINDIR=$(SRCDIR)/compilerplugins/clang +# Cannot use $(WORKDIR), the plugin should survive even 'make clean', otherwise the rebuilt +# plugin will cause cache misses with ccache. +CLANGOUTDIR=$(SRCDIR)/compilerplugins/obj + +compilerplugins: $(CLANGOUTDIR) $(CLANGOUTDIR)/compileplugin.so + +compilerplugins-clean: + rm -rf $(CLANGOUTDIR) + +$(CLANGOUTDIR): + mkdir -p $(CLANGOUTDIR) + +CLANGOBJS= + +define clangbuildsrc +$(3): $(2) $(SRCDIR)/compilerplugins/Makefile-clang.mk $(CLANGOUTDIR)/clang-timestamp + @echo [build CXX] $(subst $(SRCDIR)/,,$(2)) + $(CXX) $(CLANGCXXFLAGS) $(CLANGDEFS) $(CLANGINCLUDES) $(2) -fPIC -c -o $(3) -MMD -MT $(3) -MP -MF $(CLANGOUTDIR)/$(1).d + +-include $(CLANGOUTDIR)/$(1).d + +$(CLANGOUTDIR)/compileplugin.so: $(3) +$(CLANGOUTDIR)/compileplugin.so: CLANGOBJS += $(3) +endef + +$(foreach src, $(CLANGSRC), $(eval $(call clangbuildsrc,$(src),$(CLANGINDIR)/$(src),$(CLANGOUTDIR)/$(src:.cxx=.o)))) + +$(CLANGOUTDIR)/compileplugin.so: $(CLANGOBJS) + @echo [build LNK] $(subst $(SRCDIR)/,,$@) + $(CXX) -shared $(CLANGOBJS) -o $@ + +# Clang most probably doesn't maintain binary compatibility, so rebuild when clang changes. +$(CLANGOUTDIR)/clang-timestamp: $(CLANGBUILD)/bin/clang + touch $@ -r $^ + +# vim: set noet sw=4 ts=4: diff --git a/compilerplugins/Makefile.mk b/compilerplugins/Makefile.mk new file mode 100644 index 000000000000..c3b5290c9b2d --- /dev/null +++ b/compilerplugins/Makefile.mk @@ -0,0 +1,32 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +.PHONY: compilerplugins compilerplugins-clean + +ifeq ($(COMPILER_PLUGINS),) + +# no support + +compilerplugins: +compilerplugins-clean: +compilerplugins.clean: + +else + +ifeq ($(COM_GCC_IS_CLANG),TRUE) + +include $(SRCDIR)/compilerplugins/Makefile-clang.mk + +compilerplugins.clean: compilerplugins-clean + +endif + +endif + +# vim: set noet sw=4 ts=4: diff --git a/compilerplugins/README b/compilerplugins/README new file mode 100644 index 000000000000..50c7505dd72e --- /dev/null +++ b/compilerplugins/README @@ -0,0 +1,46 @@ +Compiler plugins. + +== Overview == + +This directory contains code for compiler plugins. These are used to perform +additional actions during compilation (such as additional warnings) and +also to perform mass code refactoring. + +Currently only the Clang compiler is supported (http://clang.llvm.org). + +== Usage == + +Compiler plugins are enabled automatically by --enable-dbgutil if Clang headers +are found or explicitly using --enable-compiler-plugins. + + +== Functionality == + +=== Compile plugin === + +The compile plugin is used during normal compilation to perform additional checks. +All warnings and errors are marked '[loplugin]' in the message. + +==== Unused variable check ==== + +- unused parameter 'foo' [loplugin] +- unused variable 'foo' [loplugin] + +Additional check for unused variables. + +==== Body of if/while/for not in {} ==== + +- statement aligned as second statement in if/while/for body but not in a statement block [loplugin] + +Warn about the following construct: + + if( a != 0 ) + b = 2; + c = 3; + +Here either both statements should be inside {} or the second statement in indented wrong. + + +== Code documentation / howtos == + +TBD diff --git a/compilerplugins/clang/bodynotinblock.cxx b/compilerplugins/clang/bodynotinblock.cxx new file mode 100644 index 000000000000..f13eb9392357 --- /dev/null +++ b/compilerplugins/clang/bodynotinblock.cxx @@ -0,0 +1,138 @@ +/* + * This file is part of the LibreOffice project. + * + * Based on LLVM/Clang. + * + * This file is distributed under the University of Illinois Open Source + * License. See LICENSE.TXT for details. + * + */ + +#include "bodynotinblock.hxx" + +#include <clang/Basic/SourceManager.h> + +namespace loplugin +{ + +/* +Check for two statements that are both indented to look like a body of if/while/for +but are not inside a compound statement and thus the second one is unrelated. +*/ + +BodyNotInBlock::BodyNotInBlock( ASTContext& context ) + : Plugin( context ) + { + } + +void BodyNotInBlock::run() + { + TraverseDecl( context.getTranslationUnitDecl()); + } + +bool BodyNotInBlock::VisitFunctionDecl( FunctionDecl* declaration ) + { + if( ignoreLocation( declaration )) + return true; + if( !declaration->doesThisDeclarationHaveABody()) + return true; + StmtParents parents; + traverseStatement( declaration->getBody(), parents ); + return true; + } + +void BodyNotInBlock::traverseStatement( const Stmt* stmt, StmtParents& parents ) + { + parents.push_back( stmt ); + for( ConstStmtIterator it = stmt->child_begin(); + it != stmt->child_end(); + ++it ) + { + if( *it != NULL ) // some children can be apparently NULL + { + traverseStatement( *it, parents ); // substatements first + parents.push_back( *it ); + if( const IfStmt* ifstmt = dyn_cast< IfStmt >( *it )) + { + checkBody( ifstmt->getThen(), parents, 0, ifstmt->getElse() != NULL ); + checkBody( ifstmt->getElse(), parents, 0 ); + } + else if( const WhileStmt* whilestmt = dyn_cast< WhileStmt >( *it )) + checkBody( whilestmt->getBody(), parents, 1 ); + else if( const ForStmt* forstmt = dyn_cast< ForStmt >( *it )) + checkBody( forstmt->getBody(), parents, 2 ); + else if( const CXXForRangeStmt* forstmt = dyn_cast< CXXForRangeStmt >( *it )) + checkBody( forstmt->getBody(), parents, 2 ); + parents.pop_back(); + } + } + assert( parents.back() == stmt ); + parents.pop_back(); + } + +void BodyNotInBlock::checkBody( const Stmt* body, const StmtParents& parents, int stmtType, bool dontGoUp ) + { + if( body == NULL || parents.size() < 2 ) + return; + // TODO: If the if/while/for comes from a macro expansion, ignore it completely for + // now. The code below could assume everything is in the same place (and thus also column) + // and give a false warning. Moreover some macros are rather lousily written and would + // result in poor formatting. To be evaluated later, maybe this could be handled + // including macro expansion. + if( parents.back()->getLocStart().isMacroID()) + return; + if( dyn_cast< CompoundStmt >( body )) + return; // if body is a compound statement, then it is in {} + // Find the next statement (in source position) after 'body'. + for( int parent_pos = parents.size() - 2; // start from grandparent + parent_pos >= 0; + --parent_pos ) + { + for( ConstStmtIterator it = parents[ parent_pos ]->child_begin(); + it != parents[ parent_pos ]->child_end(); + ) + { + if( *it == parents[ parent_pos + 1 ] ) // found grand(grand...)parent + { + // get next statement after our (grand...)parent + ++it; + while( it != parents[ parent_pos ]->child_end() && *it == NULL ) + ++it; // skip empty ones (missing 'else' bodies for example) + if( it != parents[ parent_pos ]->child_end()) + { + bool invalid1, invalid2; + unsigned bodyColumn = context.getSourceManager() + .getPresumedColumnNumber( body->getLocStart(), &invalid1 ); + unsigned nextStatementColumn = context.getSourceManager() + .getPresumedColumnNumber( (*it)->getLocStart(), &invalid2 ); + if( invalid1 || invalid2 ) + return; + if( bodyColumn == nextStatementColumn ) + { + report( DiagnosticsEngine::Warning, + "statement aligned as second statement in %select{if|while|for}0 body but not in a statement block [loplugin]", + (*it)->getLocStart()) << stmtType; + report( DiagnosticsEngine::Note, + "%select{if|while|for}0 body statement is here [loplugin]", + body->getLocStart()) << stmtType; + } + return; + } + // else we need to go higher to find the next statement + } + else + ++it; + } + // If going up would mean leaving a {} block, stop, because the } should + // make it visible the two statements are not in the same body. + if( dyn_cast< CompoundStmt >( parents[ parent_pos ] )) + return; + // If the body to be checked is a body of an if statement that has also + // an else part, don't go up, the else is after the body and should make + // it clear the body does not continue there. + if( dontGoUp ) + return; + } + } + +} // namespace diff --git a/compilerplugins/clang/bodynotinblock.hxx b/compilerplugins/clang/bodynotinblock.hxx new file mode 100644 index 000000000000..a2c47e6683cd --- /dev/null +++ b/compilerplugins/clang/bodynotinblock.hxx @@ -0,0 +1,35 @@ +/* + * This file is part of the LibreOffice project. + * + * Based on LLVM/Clang. + * + * This file is distributed under the University of Illinois Open Source + * License. See LICENSE.TXT for details. + * + */ + +#ifndef BODYNOTINBLOCK_H +#define BODYNOTINBLOCK_H + +#include "compileplugin.hxx" + +namespace loplugin +{ + +class BodyNotInBlock + : public RecursiveASTVisitor< BodyNotInBlock > + , public Plugin + { + public: + explicit BodyNotInBlock( ASTContext& context ); + void run(); + bool VisitFunctionDecl( FunctionDecl* declaration ); + private: + typedef std::vector< const Stmt* > StmtParents; + void traverseStatement( const Stmt* stmt, StmtParents& parents ); + void checkBody( const Stmt* body, const StmtParents& parents, int stmtType, bool dontGoUp = false ); + }; + +} // namespace + +#endif // BODYNOTINBLOCK_H diff --git a/compilerplugins/clang/compileplugin.cxx b/compilerplugins/clang/compileplugin.cxx new file mode 100644 index 000000000000..a61a3bab66a4 --- /dev/null +++ b/compilerplugins/clang/compileplugin.cxx @@ -0,0 +1,100 @@ +/* + * This file is part of the LibreOffice project. + * + * Based on LLVM/Clang. + * + * This file is distributed under the University of Illinois Open Source + * License. See LICENSE.TXT for details. + * + */ + +#include "compileplugin.hxx" + +#include <clang/AST/ASTConsumer.h> +#include <clang/AST/ASTContext.h> +#include <clang/Frontend/CompilerInstance.h> +#include <clang/Frontend/FrontendAction.h> +#include <clang/Frontend/FrontendPluginRegistry.h> +#include <clang/Rewrite/Rewriter.h> + +#include "bodynotinblock.hxx" +#include "unusedvariablecheck.hxx" + +using namespace clang; + +namespace loplugin +{ + +Plugin::Plugin( ASTContext& context ) + : context( context ) + { + } + +DiagnosticBuilder Plugin::report( DiagnosticsEngine::Level level, StringRef message, SourceLocation loc ) + { + DiagnosticsEngine& diag = context.getDiagnostics(); +#if 0 + // Do some mappings (e.g. for -Werror) that clang does not do for custom messages for some reason. + if( level == DiagnosticsEngine::Warning && diag.getWarningsAsErrors()) + level = DiagnosticsEngine::Error; + if( level == DiagnosticsEngine::Error && diag.getErrorsAsFatal()) + level = DiagnosticsEngine::Fatal; +#endif + return diag.Report( loc, diag.getCustomDiagID( level, message )); + } + +bool Plugin::ignoreLocation( SourceLocation loc ) + { + return context.getSourceManager().isInSystemHeader( context.getSourceManager().getExpansionLoc( loc )); + } + +/** + Class that manages all LO modules. +*/ +class PluginHandler + : public ASTConsumer + { + public: + explicit PluginHandler( ASTContext& context ) + : rewriter( context.getSourceManager(), context.getLangOpts()) + , bodyNotInBlock( context ) + , unusedVariableCheck( context ) + { + } + virtual void HandleTranslationUnit( ASTContext& context ) + { + if( context.getDiagnostics().hasErrorOccurred()) + return; + bodyNotInBlock.run(); + unusedVariableCheck.run(); + // TODO also LO header files? or a subdir? + if( const RewriteBuffer* buf = rewriter.getRewriteBufferFor( context.getSourceManager().getMainFileID())) + buf->write( llvm::outs()); + // TODO else write out the original file? + } + private: + Rewriter rewriter; + BodyNotInBlock bodyNotInBlock; + UnusedVariableCheck unusedVariableCheck; + }; + +/** + The Clang plugin class, just forwards to PluginHandler. +*/ +class LibreOfficeAction + : public PluginASTAction + { + public: + virtual ASTConsumer* CreateASTConsumer( CompilerInstance& Compiler, StringRef InFile ) + { + return new PluginHandler( Compiler.getASTContext()); + } + virtual bool ParseArgs( const CompilerInstance& CI, const std::vector< std::string >& args ) + { + return true; + } + }; + +} // namespace + +static FrontendPluginRegistry::Add< loplugin::LibreOfficeAction > X( "loplugin", "LibreOffice compile check plugin" ); diff --git a/compilerplugins/clang/compileplugin.hxx b/compilerplugins/clang/compileplugin.hxx new file mode 100644 index 000000000000..6c854d1e0179 --- /dev/null +++ b/compilerplugins/clang/compileplugin.hxx @@ -0,0 +1,47 @@ +/* + * This file is part of the LibreOffice project. + * + * Based on LLVM/Clang. + * + * This file is distributed under the University of Illinois Open Source + * License. See LICENSE.TXT for details. + * + */ + +#ifndef COMPILEPLUGIN_H +#define COMPILEPLUGIN_H + +#include <clang/AST/RecursiveASTVisitor.h> + +using namespace clang; + +namespace loplugin +{ + +class Plugin + { + public: + explicit Plugin( ASTContext& context ); + protected: + DiagnosticBuilder report( DiagnosticsEngine::Level level, StringRef message, SourceLocation loc ); + bool ignoreLocation( SourceLocation loc ); + bool ignoreLocation( const Decl* decl ); + bool ignoreLocation( const Stmt* stmt ); + ASTContext& context; + }; + +inline +bool Plugin::ignoreLocation( const Decl* decl ) + { + return ignoreLocation( decl->getLocation()); + } + +inline +bool Plugin::ignoreLocation( const Stmt* stmt ) + { + return ignoreLocation( stmt->getLocStart()); + } + +} // namespace + +#endif // COMPILEPLUGIN_H diff --git a/compilerplugins/clang/unusedvariablecheck.cxx b/compilerplugins/clang/unusedvariablecheck.cxx new file mode 100644 index 000000000000..3d9ca3de0513 --- /dev/null +++ b/compilerplugins/clang/unusedvariablecheck.cxx @@ -0,0 +1,102 @@ +/* + * This file is part of the LibreOffice project. + * + * Based on LLVM/Clang. + * + * This file is distributed under the University of Illinois Open Source + * License. See LICENSE.TXT for details. + * + */ + +#include "unusedvariablecheck.hxx" + +#include <clang/Basic/SourceManager.h> + +namespace loplugin +{ + +/* +Check for unused classes where the compiler cannot decide (e.g. because of +non-trivial or extern ctors) if a variable is unused if only its ctor/dtor +are called and nothing else. For example std::vector is a class where +the ctor may call further functions, but an unused std::string variable +does nothing. On the other hand, std::auto_ptr instances are used +for their dtors and so are not unused even if not otherwise accessed. + +Classes which are safe to be warned about need to be marked using +SAL_WARN_UNUSED (see e.g. OUString). For external classes such as std::vector +that cannot be edited there is a manual list below. +*/ + +UnusedVariableCheck::UnusedVariableCheck( ASTContext& context ) + : Plugin( context ) + { + } + +void UnusedVariableCheck::run() + { + TraverseDecl( context.getTranslationUnitDecl()); + } + +bool UnusedVariableCheck::VisitNamedDecl( NamedDecl* declaration ) + { + if( ignoreLocation( declaration )) + return true; + if( !isa< VarDecl >( declaration )) + return true; + const VarDecl* var = cast< VarDecl >( declaration ); + if( var->isReferenced() || var->isUsed()) + return true; + if( var->isDefinedOutsideFunctionOrMethod()) + return true; + if( CXXRecordDecl* type = var->getType()->getAsCXXRecordDecl()) + { + bool warn_unused = false; + if( type->hasAttrs()) + { + // Clang currently has no support for custom attributes, but + // the annotate attribute comes close, so check for __attribute__((annotate("lo_warn_unused"))) + for( specific_attr_iterator<AnnotateAttr> i = type->specific_attr_begin<AnnotateAttr>(), + e = type->specific_attr_end<AnnotateAttr>(); + i != e; + ++i ) + { + if( (*i)->getAnnotation() == "lo_warn_unused" ) + { + warn_unused = true; + break; + } + } + } + if( !warn_unused ) + { + std::string n = type->getQualifiedNameAsString(); + // Check some common non-LO types. + if( n == "std::string" || n == "std::basic_string" + || n == "std::list" || n == "std::__debug::list" + || n == "std::vector" || n == "std::__debug::vector" ) + warn_unused = true; + } + if( warn_unused ) + { + if( const ParmVarDecl* param = dyn_cast< ParmVarDecl >( var )) + { + if( !param->getDeclName()) + return true; // unnamed parameter -> unused + // If this declaration does not have a body, then the parameter is indeed not used, + // so ignore. + if( const FunctionDecl* func = dyn_cast_or_null< FunctionDecl >( param->getParentFunctionOrMethod())) + if( !func->doesThisDeclarationHaveABody()) + return true; + report( DiagnosticsEngine::Warning, "unused parameter %0 [loplugin]", + var->getLocation()) << var->getDeclName(); + } + else + report( DiagnosticsEngine::Warning, "unused variable %0 [loplugin]", + var->getLocation()) << var->getDeclName(); + } + } + return true; + } + +} // namespace diff --git a/compilerplugins/clang/unusedvariablecheck.hxx b/compilerplugins/clang/unusedvariablecheck.hxx new file mode 100644 index 000000000000..21e0eabd03c0 --- /dev/null +++ b/compilerplugins/clang/unusedvariablecheck.hxx @@ -0,0 +1,31 @@ +/* + * This file is part of the LibreOffice project. + * + * Based on LLVM/Clang. + * + * This file is distributed under the University of Illinois Open Source + * License. See LICENSE.TXT for details. + * + */ + +#ifndef UNUSEDVARIABLECHECK_H +#define UNUSEDVARIABLECHECK_H + +#include "compileplugin.hxx" + +namespace loplugin +{ + +class UnusedVariableCheck + : public RecursiveASTVisitor< UnusedVariableCheck > + , public Plugin + { + public: + explicit UnusedVariableCheck( ASTContext& context ); + void run(); + bool VisitNamedDecl( NamedDecl* declaration ); + }; + +} // namespace + +#endif // UNUSEDVARIABLECHECK_H |