diff options
Diffstat (limited to 'compilerplugins/clang/sharedvisitor/analyzer.cxx')
-rw-r--r-- | compilerplugins/clang/sharedvisitor/analyzer.cxx | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/compilerplugins/clang/sharedvisitor/analyzer.cxx b/compilerplugins/clang/sharedvisitor/analyzer.cxx new file mode 100644 index 000000000..a000bd249 --- /dev/null +++ b/compilerplugins/clang/sharedvisitor/analyzer.cxx @@ -0,0 +1,299 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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 "clang/AST/ASTConsumer.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/StringExtras.h" + +#include <cassert> +#include <cstddef> +#include <cstring> +#include <iostream> +#include <memory> +#include <fstream> +#include <set> + +#include "config_clang.h" +#include "../check.hxx" +#include "../check.cxx" + +using namespace clang; +using namespace llvm; + +using namespace loplugin; + +// Info about a Traverse* function in a plugin. +struct TraverseFunctionInfo +{ + std::string name; + std::string argument; + bool hasPre = false; + bool hasPost = false; +}; + +struct TraverseFunctionInfoLess +{ + bool operator()( const TraverseFunctionInfo& l, const TraverseFunctionInfo& r ) const + { + return l.name < r.name; + } +}; + +static std::set< TraverseFunctionInfo, TraverseFunctionInfoLess > traverseFunctions; + +class CheckFileVisitor + : public RecursiveASTVisitor< CheckFileVisitor > +{ +public: + void setContext(ASTContext const& context) { context_ = &context; } + + bool VisitCXXRecordDecl(CXXRecordDecl *Declaration); + + bool TraverseNamespaceDecl(NamespaceDecl * decl) + { + // Skip non-LO namespaces the same way FilteringPlugin does. + if( !ContextCheck( decl ).Namespace( "loplugin" ).GlobalNamespace() + && !ContextCheck( decl ).AnonymousNamespace()) + { + return true; + } + return RecursiveASTVisitor<CheckFileVisitor>::TraverseNamespaceDecl(decl); + } + +private: + ASTContext const* context_ = nullptr; + + QualType unqualifyPointeeType(QualType type) + { + assert(context_ != nullptr); + if (auto const t = type->getAs<clang::PointerType>()) + { + return context_->getQualifiedType( + context_->getPointerType(t->getPointeeType().getUnqualifiedType()), + type.getQualifiers()); + } + return type; + } +}; + +static bool inheritsPluginClassCheck( const Decl* decl ) +{ + return bool( DeclCheck( decl ).Class( "FilteringPlugin" ).Namespace( "loplugin" ).GlobalNamespace()) + || bool( DeclCheck( decl ).Class( "FilteringRewritePlugin" ).Namespace( "loplugin" ).GlobalNamespace()); +} + +static TraverseFunctionInfo findOrCreateTraverseFunctionInfo( StringRef name ) +{ + TraverseFunctionInfo info; + info.name = name.str(); + auto foundInfo = traverseFunctions.find( info ); + if( foundInfo != traverseFunctions.end()) + { + info = std::move( *foundInfo ); + traverseFunctions.erase( foundInfo ); + } + return info; +} + +static bool foundSomething; + +bool CheckFileVisitor::VisitCXXRecordDecl( CXXRecordDecl* decl ) +{ + if( !isDerivedFrom( decl, inheritsPluginClassCheck )) + return true; + + if( decl->getName() == "FilteringPlugin" || decl->getName() == "FilteringRewritePlugin" ) + return true; + + std::cout << "# This file is autogenerated. Do not modify." << std::endl; + std::cout << "# Generated by compilerplugins/clang/sharedvisitor/analyzer.cxx ." << std::endl; + std::cout << "InfoVersion:1" << std::endl; + std::cout << "ClassName:" << decl->getName().str() << std::endl; + traverseFunctions.clear(); + for( const CXXMethodDecl* method : decl->methods()) + { + if( !method->getDeclName().isIdentifier()) + continue; + if( method->isStatic() || method->getAccess() != AS_public ) + continue; + if( method->getName().startswith( "Visit" )) + { + if( method->getNumParams() == 1 ) + { + std::cout << "VisitFunctionStart" << std::endl; + std::cout << "VisitFunctionName:" << method->getName().str() << std::endl; + std::cout << "VisitFunctionArgument:" + << unqualifyPointeeType( + method->getParamDecl( 0 )->getTypeSourceInfo()->getType()).getAsString() + << std::endl; + std::cout << "VisitFunctionEnd" << std::endl; + } + else + { + std::cerr << "Unhandled Visit* function: " << decl->getName().str() + << "::" << method->getName().str() << std::endl; + abort(); + } + } + else if( method->getName().startswith( "Traverse" )) + { + if( method->getNumParams() == 1 ) + { + TraverseFunctionInfo traverseInfo = findOrCreateTraverseFunctionInfo( method->getName()); + traverseInfo.argument = method->getParamDecl( 0 )->getTypeSourceInfo()->getType().getAsString(); + traverseFunctions.insert( std::move( traverseInfo )); + } + else + { + std::cerr << "Unhandled Traverse* function: " << decl->getName().str() + << "::" << method->getName().str() << std::endl; + abort(); + } + } + else if( method->getName().startswith( "PreTraverse" )) + { + TraverseFunctionInfo traverseInfo = findOrCreateTraverseFunctionInfo( method->getName().substr( 3 )); + traverseInfo.hasPre = true; + traverseFunctions.insert( std::move( traverseInfo )); + } + else if( method->getName().startswith( "PostTraverse" )) + { + TraverseFunctionInfo traverseInfo = findOrCreateTraverseFunctionInfo( method->getName().substr( 4 )); + traverseInfo.hasPost = true; + traverseFunctions.insert( std::move( traverseInfo )); + } + else if( method->getName() == "shouldVisitTemplateInstantiations" ) + std::cout << "ShouldVisitTemplateInstantiations:1" << std::endl; + else if (method->getName() == "shouldVisitImplicitCode") + std::cout << "ShouldVisitImplicitCode:1" << std::endl; + else if( method->getName().startswith( "WalkUp" )) + { + std::cerr << "WalkUp function not supported for shared visitor: " << decl->getName().str() + << "::" << method->getName().str() << std::endl; + abort(); + } + } + + for( const auto& traverseFunction : traverseFunctions ) + { + std::cout << "TraverseFunctionStart" << std::endl; + std::cout << "TraverseFunctionName:" << traverseFunction.name << std::endl; + std::cout << "TraverseFunctionArgument:" << traverseFunction.argument << std::endl; + std::cout << "TraverseFunctionHasPre:" << traverseFunction.hasPre << std::endl; + std::cout << "TraverseFunctionHasPost:" << traverseFunction.hasPost << std::endl; + std::cout << "TraverseFunctionEnd" << std::endl; + } + + std::cout << "InfoEnd" << std::endl; + foundSomething = true; + return true; +} + +class FindNamedClassConsumer + : public ASTConsumer +{ +public: + void Initialize(ASTContext& context) override + { + visitor.setContext(context); + } + virtual void HandleTranslationUnit(ASTContext& context) override + { + visitor.TraverseDecl( context.getTranslationUnitDecl()); + } +private: + CheckFileVisitor visitor; +}; + +class FindNamedClassAction + : public ASTFrontendAction + { +public: + virtual std::unique_ptr<ASTConsumer> CreateASTConsumer( CompilerInstance&, StringRef ) override + { + return std::unique_ptr<ASTConsumer>( new FindNamedClassConsumer ); + } +}; + + +std::string readSourceFile( const char* filename ) +{ + std::string contents; + std::ifstream stream( filename ); + if( !stream ) + { + std::cerr << "Failed to open: " << filename << std::endl; + exit( 1 ); + } + std::string line; + bool hasIfdef = false; + while( getline( stream, line )) + { + // TODO add checks that it's e.g. not "#ifdef" ? + if( line.find( "#ifndef LO_CLANG_SHARED_PLUGINS" ) == 0 ) + hasIfdef = true; + contents += line; + contents += '\n'; + } + if( stream.eof() && hasIfdef ) + return contents; + return ""; +} + +int main(int argc, char** argv) +{ + std::vector< std::string > args; + int i = 1; + for( ; i < argc; ++ i ) + { + constexpr std::size_t prefixlen = 5; // strlen("-arg="); + if (std::strncmp(argv[i], "-arg=", prefixlen) != 0) + { + break; + } + args.push_back(argv[i] + prefixlen); + } + SmallVector< StringRef, 20 > clangflags; + SplitString( CLANGFLAGS, clangflags ); + for (auto const & i: clangflags) { + args.push_back(i.str()); + } + args.insert( + args.end(), + { // These must match LO_CLANG_ANALYZER_PCH_CXXFLAGS in Makefile-clang.mk . + "-I" BUILDDIR "/config_host" // plugin sources use e.g. config_global.h +#if LO_CLANG_USE_ANALYZER_PCH + , + "-include-pch", // use PCH with Clang headers to speed up parsing/analysing + BUILDDIR "/compilerplugins/clang/sharedvisitor/clang.pch" +#endif + }); + for( ; i < argc; ++ i ) + { + std::string contents = readSourceFile(argv[i]); + if( contents.empty()) + continue; + foundSomething = false; + if( !tooling::runToolOnCodeWithArgs( std::unique_ptr<FindNamedClassAction>(new FindNamedClassAction), contents, args, argv[ i ] )) + { + std::cerr << "Failed to analyze: " << argv[ i ] << std::endl; + return 2; + } + if( !foundSomething ) + { + // there's #ifndef LO_CLANG_SHARED_PLUGINS in the source, but no class matched + std::cerr << "Failed to find code: " << argv[ i ] << std::endl; + return 2; + } + } + return 0; +} |