summaryrefslogtreecommitdiffstats
path: root/compilerplugins/clang/sharedvisitor/analyzer.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'compilerplugins/clang/sharedvisitor/analyzer.cxx')
-rw-r--r--compilerplugins/clang/sharedvisitor/analyzer.cxx299
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;
+}