/* -*- 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 #include #include #include #include #include #include #include "config_clang.h" #include "../check.hxx" #include "../check.cxx" #include "../compat.hxx" 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::TraverseNamespaceDecl(decl); } private: ASTContext const* context_ = nullptr; QualType unqualifyPointeeType(QualType type) { assert(context_ != nullptr); if (auto const t = type->getAs()) { 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( compat::starts_with(method->getName(), "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( compat::starts_with(method->getName(), "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( compat::starts_with(method->getName(), "PreTraverse" )) { TraverseFunctionInfo traverseInfo = findOrCreateTraverseFunctionInfo( method->getName().substr( 3 )); traverseInfo.hasPre = true; traverseFunctions.insert( std::move( traverseInfo )); } else if( compat::starts_with(method->getName(), "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( compat::starts_with(method->getName(), "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 CreateASTConsumer( CompilerInstance&, StringRef ) override { return std::unique_ptr( 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(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; }