summaryrefslogtreecommitdiffstats
path: root/compilerplugins/clang/constfields.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'compilerplugins/clang/constfields.cxx')
-rw-r--r--compilerplugins/clang/constfields.cxx597
1 files changed, 0 insertions, 597 deletions
diff --git a/compilerplugins/clang/constfields.cxx b/compilerplugins/clang/constfields.cxx
deleted file mode 100644
index 1833fd056d6c..000000000000
--- a/compilerplugins/clang/constfields.cxx
+++ /dev/null
@@ -1,597 +0,0 @@
-/* -*- 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/.
- */
-
-#if !defined _WIN32 //TODO, #include <sys/file.h>
-
-#include <cassert>
-#include <string>
-#include <iostream>
-#include <fstream>
-#include <unordered_set>
-#include <vector>
-#include <algorithm>
-#include <sys/file.h>
-#include <unistd.h>
-
-#include "config_clang.h"
-
-#include "plugin.hxx"
-#include "compat.hxx"
-#include "check.hxx"
-
-#if CLANG_VERSION >= 110000
-#include "clang/AST/ParentMapContext.h"
-#endif
-
-/**
-Look for fields that are only assigned to in the constructor using field-init, and can therefore be const.
-
-The process goes something like this:
- $ make check
- $ make FORCE_COMPILE=all COMPILER_PLUGIN_TOOL='constfields' check
- $ ./compilerplugins/clang/constfields.py
-
-and then
- $ for dir in *; do make $dir FORCE_COMPILE=all UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='constfieldsrewrite' $dir; done
-to auto-remove the method declarations
-
-*/
-
-namespace
-{
-struct MyFieldInfo
-{
- const RecordDecl* parentRecord;
- std::string parentClass;
- std::string fieldName;
- std::string fieldType;
- std::string sourceLocation;
- std::string access;
-};
-bool operator<(const MyFieldInfo& lhs, const MyFieldInfo& rhs)
-{
- return std::tie(lhs.parentClass, lhs.fieldName) < std::tie(rhs.parentClass, rhs.fieldName);
-}
-
-// try to limit the voluminous output a little
-static std::set<MyFieldInfo> cannotBeConstSet;
-static std::set<MyFieldInfo> definitionSet;
-
-/**
- * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
- */
-class CallerWrapper
-{
- const CallExpr* m_callExpr;
- const CXXConstructExpr* m_cxxConstructExpr;
-
-public:
- CallerWrapper(const CallExpr* callExpr)
- : m_callExpr(callExpr)
- , m_cxxConstructExpr(nullptr)
- {
- }
- CallerWrapper(const CXXConstructExpr* cxxConstructExpr)
- : m_callExpr(nullptr)
- , m_cxxConstructExpr(cxxConstructExpr)
- {
- }
- unsigned getNumArgs() const
- {
- return m_callExpr ? m_callExpr->getNumArgs() : m_cxxConstructExpr->getNumArgs();
- }
- const Expr* getArg(unsigned i) const
- {
- return m_callExpr ? m_callExpr->getArg(i) : m_cxxConstructExpr->getArg(i);
- }
-};
-class CalleeWrapper
-{
- const FunctionDecl* m_calleeFunctionDecl = nullptr;
- const CXXConstructorDecl* m_cxxConstructorDecl = nullptr;
- const FunctionProtoType* m_functionPrototype = nullptr;
-
-public:
- explicit CalleeWrapper(const FunctionDecl* calleeFunctionDecl)
- : m_calleeFunctionDecl(calleeFunctionDecl)
- {
- }
- explicit CalleeWrapper(const CXXConstructExpr* cxxConstructExpr)
- : m_cxxConstructorDecl(cxxConstructExpr->getConstructor())
- {
- }
- explicit CalleeWrapper(const FunctionProtoType* functionPrototype)
- : m_functionPrototype(functionPrototype)
- {
- }
- unsigned getNumParams() const
- {
- if (m_calleeFunctionDecl)
- return m_calleeFunctionDecl->getNumParams();
- else if (m_cxxConstructorDecl)
- return m_cxxConstructorDecl->getNumParams();
- else if (m_functionPrototype->param_type_begin() == m_functionPrototype->param_type_end())
- // FunctionProtoType will assert if we call getParamTypes() and it has no params
- return 0;
- else
- return m_functionPrototype->getParamTypes().size();
- }
- const QualType getParamType(unsigned i) const
- {
- if (m_calleeFunctionDecl)
- return m_calleeFunctionDecl->getParamDecl(i)->getType();
- else if (m_cxxConstructorDecl)
- return m_cxxConstructorDecl->getParamDecl(i)->getType();
- else
- return m_functionPrototype->getParamTypes()[i];
- }
- std::string getNameAsString() const
- {
- if (m_calleeFunctionDecl)
- return m_calleeFunctionDecl->getNameAsString();
- else if (m_cxxConstructorDecl)
- return m_cxxConstructorDecl->getNameAsString();
- else
- return "";
- }
- CXXMethodDecl const* getAsCXXMethodDecl() const
- {
- if (m_calleeFunctionDecl)
- return dyn_cast<CXXMethodDecl>(m_calleeFunctionDecl);
- return nullptr;
- }
-};
-
-class ConstFields : public RecursiveASTVisitor<ConstFields>, public loplugin::Plugin
-{
-public:
- explicit ConstFields(loplugin::InstantiationData const& data)
- : Plugin(data)
- {
- }
-
- virtual void run() override;
-
- bool shouldVisitTemplateInstantiations() const { return true; }
- bool shouldVisitImplicitCode() const { return true; }
-
- bool VisitFieldDecl(const FieldDecl*);
- bool VisitMemberExpr(const MemberExpr*);
- bool TraverseCXXConstructorDecl(CXXConstructorDecl*);
- bool TraverseCXXMethodDecl(CXXMethodDecl*);
- bool TraverseFunctionDecl(FunctionDecl*);
- bool TraverseIfStmt(IfStmt*);
-
-private:
- MyFieldInfo niceName(const FieldDecl*);
- void check(const FieldDecl* fieldDecl, const Expr* memberExpr);
- bool isSomeKindOfZero(const Expr* arg);
- bool IsPassedByNonConst(const FieldDecl* fieldDecl, const Stmt* child, CallerWrapper callExpr,
- CalleeWrapper calleeFunctionDecl);
- llvm::Optional<CalleeWrapper> getCallee(CallExpr const*);
-
- RecordDecl* insideMoveOrCopyDeclParent = nullptr;
- // For reasons I do not understand, parentFunctionDecl() is not reliable, so
- // we store the parent function on the way down the AST.
- FunctionDecl* insideFunctionDecl = nullptr;
- std::vector<FieldDecl const*> insideConditionalCheckOfMemberSet;
-};
-
-void ConstFields::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
- // writing to the same logfile
- std::string output;
- for (const MyFieldInfo& s : cannotBeConstSet)
- output += "write-outside-constructor:\t" + s.parentClass + "\t" + s.fieldName + "\n";
- for (const MyFieldInfo& s : definitionSet)
- output += "definition:\t" + s.access + "\t" + s.parentClass + "\t" + s.fieldName + "\t"
- + s.fieldType + "\t" + s.sourceLocation + "\n";
- std::ofstream myfile;
- myfile.open(WORKDIR "/loplugin.constfields.log", std::ios::app | std::ios::out);
- myfile << output;
- myfile.close();
- }
- else
- {
- for (const MyFieldInfo& s : cannotBeConstSet)
- report(DiagnosticsEngine::Warning, "notconst %0", compat::getBeginLoc(s.parentRecord))
- << s.fieldName;
- }
-}
-
-MyFieldInfo ConstFields::niceName(const FieldDecl* fieldDecl)
-{
- MyFieldInfo aInfo;
-
- const RecordDecl* recordDecl = fieldDecl->getParent();
-
- if (const CXXRecordDecl* cxxRecordDecl = dyn_cast<CXXRecordDecl>(recordDecl))
- {
- if (cxxRecordDecl->getTemplateInstantiationPattern())
- cxxRecordDecl = cxxRecordDecl->getTemplateInstantiationPattern();
- aInfo.parentRecord = cxxRecordDecl;
- aInfo.parentClass = cxxRecordDecl->getQualifiedNameAsString();
- }
- else
- {
- aInfo.parentRecord = recordDecl;
- aInfo.parentClass = recordDecl->getQualifiedNameAsString();
- }
-
- aInfo.fieldName = fieldDecl->getNameAsString();
- // sometimes the name (if it's an anonymous thing) contains the full path of the build folder, which we don't need
- size_t idx = aInfo.fieldName.find(SRCDIR);
- if (idx != std::string::npos)
- {
- aInfo.fieldName = aInfo.fieldName.replace(idx, strlen(SRCDIR), "");
- }
- aInfo.fieldType = fieldDecl->getType().getAsString();
-
- SourceLocation expansionLoc
- = compiler.getSourceManager().getExpansionLoc(fieldDecl->getLocation());
- StringRef name = getFilenameOfLocation(expansionLoc);
- aInfo.sourceLocation
- = std::string(name.substr(strlen(SRCDIR) + 1)) + ":"
- + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
- loplugin::normalizeDotDotInFilePath(aInfo.sourceLocation);
-
- switch (fieldDecl->getAccess())
- {
- case AS_public:
- aInfo.access = "public";
- break;
- case AS_private:
- aInfo.access = "private";
- break;
- case AS_protected:
- aInfo.access = "protected";
- break;
- default:
- aInfo.access = "unknown";
- break;
- }
-
- return aInfo;
-}
-
-bool ConstFields::VisitFieldDecl(const FieldDecl* fieldDecl)
-{
- fieldDecl = fieldDecl->getCanonicalDecl();
- if (ignoreLocation(fieldDecl))
- {
- return true;
- }
- // ignore stuff that forms part of the stable URE interface
- if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation())))
- {
- return true;
- }
- definitionSet.insert(niceName(fieldDecl));
- return true;
-}
-
-bool ConstFields::TraverseCXXConstructorDecl(CXXConstructorDecl* cxxConstructorDecl)
-{
- auto copy = insideMoveOrCopyDeclParent;
- if (!ignoreLocation(cxxConstructorDecl) && cxxConstructorDecl->isThisDeclarationADefinition())
- {
- if (cxxConstructorDecl->isCopyOrMoveConstructor())
- insideMoveOrCopyDeclParent = cxxConstructorDecl->getParent();
- }
- bool ret = RecursiveASTVisitor::TraverseCXXConstructorDecl(cxxConstructorDecl);
- insideMoveOrCopyDeclParent = copy;
- return ret;
-}
-
-bool ConstFields::TraverseCXXMethodDecl(CXXMethodDecl* cxxMethodDecl)
-{
- auto copy1 = insideMoveOrCopyDeclParent;
- auto copy2 = insideFunctionDecl;
- if (!ignoreLocation(cxxMethodDecl) && cxxMethodDecl->isThisDeclarationADefinition())
- {
- if (cxxMethodDecl->isCopyAssignmentOperator() || cxxMethodDecl->isMoveAssignmentOperator())
- insideMoveOrCopyDeclParent = cxxMethodDecl->getParent();
- }
- insideFunctionDecl = cxxMethodDecl;
- bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl);
- insideMoveOrCopyDeclParent = copy1;
- insideFunctionDecl = copy2;
- return ret;
-}
-
-bool ConstFields::TraverseFunctionDecl(FunctionDecl* functionDecl)
-{
- auto copy2 = insideFunctionDecl;
- insideFunctionDecl = functionDecl;
- bool ret = RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
- insideFunctionDecl = copy2;
- return ret;
-}
-
-bool ConstFields::TraverseIfStmt(IfStmt* ifStmt)
-{
- FieldDecl const* memberFieldDecl = nullptr;
- Expr const* cond = ifStmt->getCond()->IgnoreParenImpCasts();
- if (auto memberExpr = dyn_cast<MemberExpr>(cond))
- {
- if ((memberFieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl())))
- insideConditionalCheckOfMemberSet.push_back(memberFieldDecl);
- }
- bool ret = RecursiveASTVisitor::TraverseIfStmt(ifStmt);
- if (memberFieldDecl)
- insideConditionalCheckOfMemberSet.pop_back();
- return ret;
-}
-
-bool ConstFields::VisitMemberExpr(const MemberExpr* memberExpr)
-{
- const ValueDecl* decl = memberExpr->getMemberDecl();
- const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl);
- if (!fieldDecl)
- {
- return true;
- }
- fieldDecl = fieldDecl->getCanonicalDecl();
- if (ignoreLocation(fieldDecl))
- {
- return true;
- }
- // ignore stuff that forms part of the stable URE interface
- if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation())))
- {
- return true;
- }
-
- check(fieldDecl, memberExpr);
-
- return true;
-}
-
-void ConstFields::check(const FieldDecl* fieldDecl, const Expr* memberExpr)
-{
- auto parentsRange = compiler.getASTContext().getParents(*memberExpr);
- const Stmt* child = memberExpr;
- const Stmt* parent
- = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
- // walk up the tree until we find something interesting
- bool bCannotBeConst = false;
- bool bDump = false;
- auto walkUp = [&]() {
- child = parent;
- auto parentsRange = compiler.getASTContext().getParents(*parent);
- parent = parentsRange.begin() == parentsRange.end() ? nullptr
- : parentsRange.begin()->get<Stmt>();
- };
- do
- {
- if (!parent)
- {
- // check if we have an expression like
- // int& r = m_field;
- auto parentsRange = compiler.getASTContext().getParents(*child);
- if (parentsRange.begin() != parentsRange.end())
- {
- auto varDecl = dyn_cast_or_null<VarDecl>(parentsRange.begin()->get<Decl>());
- // The isImplicit() call is to avoid triggering when we see the vardecl which is part of a for-range statement,
- // which is of type 'T&&' and also an l-value-ref ?
- if (varDecl && !varDecl->isImplicit()
- && loplugin::TypeCheck(varDecl->getType()).LvalueReference().NonConst())
- {
- bCannotBeConst = true;
- }
- }
- break;
- }
- if (isa<CXXReinterpretCastExpr>(parent))
- {
- // once we see one of these, there is not much useful we can know
- bCannotBeConst = true;
- break;
- }
- else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent)
- || isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent)
- || isa<ExprWithCleanups>(parent))
- {
- walkUp();
- }
- else if (auto unaryOperator = dyn_cast<UnaryOperator>(parent))
- {
- UnaryOperator::Opcode op = unaryOperator->getOpcode();
- if (op == UO_AddrOf || op == UO_PostInc || op == UO_PostDec || op == UO_PreInc
- || op == UO_PreDec)
- {
- bCannotBeConst = true;
- }
- walkUp();
- }
- else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
- {
- auto callee = getCallee(operatorCallExpr);
- if (callee)
- {
- // if calling a non-const operator on the field
- auto calleeMethodDecl = callee->getAsCXXMethodDecl();
- if (calleeMethodDecl && operatorCallExpr->getArg(0) == child
- && !calleeMethodDecl->isConst())
- {
- bCannotBeConst = true;
- }
- else if (IsPassedByNonConst(fieldDecl, child, operatorCallExpr, *callee))
- {
- bCannotBeConst = true;
- }
- }
- else
- bCannotBeConst = true; // conservative, could improve
- walkUp();
- }
- else if (auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(parent))
- {
- const CXXMethodDecl* calleeMethodDecl = cxxMemberCallExpr->getMethodDecl();
- if (calleeMethodDecl)
- {
- // if calling a non-const method on the field
- const Expr* tmp = dyn_cast<Expr>(child);
- if (tmp->isBoundMemberFunction(compiler.getASTContext()))
- {
- tmp = dyn_cast<MemberExpr>(tmp)->getBase();
- }
- if (cxxMemberCallExpr->getImplicitObjectArgument() == tmp
- && !calleeMethodDecl->isConst())
- {
- bCannotBeConst = true;
- break;
- }
- if (IsPassedByNonConst(fieldDecl, child, cxxMemberCallExpr,
- CalleeWrapper(calleeMethodDecl)))
- bCannotBeConst = true;
- }
- else
- bCannotBeConst = true; // can happen in templates
- walkUp();
- }
- else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
- {
- if (IsPassedByNonConst(fieldDecl, child, cxxConstructExpr,
- CalleeWrapper(cxxConstructExpr)))
- bCannotBeConst = true;
- walkUp();
- }
- else if (auto callExpr = dyn_cast<CallExpr>(parent))
- {
- auto callee = getCallee(callExpr);
- if (callee)
- {
- if (IsPassedByNonConst(fieldDecl, child, callExpr, *callee))
- bCannotBeConst = true;
- }
- else
- bCannotBeConst = true; // conservative, could improve
- walkUp();
- }
- else if (auto binaryOp = dyn_cast<BinaryOperator>(parent))
- {
- BinaryOperator::Opcode op = binaryOp->getOpcode();
- const bool assignmentOp = op == BO_Assign || op == BO_MulAssign || op == BO_DivAssign
- || op == BO_RemAssign || op == BO_AddAssign
- || op == BO_SubAssign || op == BO_ShlAssign
- || op == BO_ShrAssign || op == BO_AndAssign
- || op == BO_XorAssign || op == BO_OrAssign;
- if (assignmentOp)
- {
- if (binaryOp->getLHS() == child)
- bCannotBeConst = true;
- else if (loplugin::TypeCheck(binaryOp->getLHS()->getType())
- .LvalueReference()
- .NonConst())
- // if the LHS is a non-const reference, we could write to the field later on
- bCannotBeConst = true;
- }
- walkUp();
- }
- else if (isa<ReturnStmt>(parent))
- {
- if (insideFunctionDecl)
- {
- auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType());
- if (tc.LvalueReference().NonConst())
- bCannotBeConst = true;
- }
- break;
- }
- else if (isa<SwitchStmt>(parent) || isa<WhileStmt>(parent) || isa<ForStmt>(parent)
- || isa<IfStmt>(parent) || isa<DoStmt>(parent) || isa<CXXForRangeStmt>(parent)
- || isa<DefaultStmt>(parent))
- {
- break;
- }
- else
- {
- walkUp();
- }
- } while (true);
-
- if (bDump)
- {
- report(DiagnosticsEngine::Warning, "oh dear, what can the matter be? writtenTo=%0",
- compat::getBeginLoc(memberExpr))
- << bCannotBeConst << memberExpr->getSourceRange();
- if (parent)
- {
- report(DiagnosticsEngine::Note, "parent over here", compat::getBeginLoc(parent))
- << parent->getSourceRange();
- parent->dump();
- }
- memberExpr->dump();
- fieldDecl->getType()->dump();
- }
-
- if (bCannotBeConst)
- {
- cannotBeConstSet.insert(niceName(fieldDecl));
- }
-}
-
-bool ConstFields::IsPassedByNonConst(const FieldDecl* fieldDecl, const Stmt* child,
- CallerWrapper callExpr, CalleeWrapper calleeFunctionDecl)
-{
- unsigned len = std::min(callExpr.getNumArgs(), calleeFunctionDecl.getNumParams());
- // if it's an array, passing it by value to a method typically means the
- // callee takes a pointer and can modify the array
- if (fieldDecl->getType()->isConstantArrayType())
- {
- for (unsigned i = 0; i < len; ++i)
- if (callExpr.getArg(i) == child)
- if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i)).Pointer().NonConst())
- return true;
- }
- else
- {
- for (unsigned i = 0; i < len; ++i)
- if (callExpr.getArg(i) == child)
- if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i))
- .LvalueReference()
- .NonConst())
- return true;
- }
- return false;
-}
-
-llvm::Optional<CalleeWrapper> ConstFields::getCallee(CallExpr const* callExpr)
-{
- FunctionDecl const* functionDecl = callExpr->getDirectCallee();
- if (functionDecl)
- return CalleeWrapper(functionDecl);
-
- // Extract the functionprototype from a type
- clang::Type const* calleeType = callExpr->getCallee()->getType().getTypePtr();
- if (auto pointerType = calleeType->getUnqualifiedDesugaredType()->getAs<clang::PointerType>())
- {
- if (auto prototype = pointerType->getPointeeType()
- ->getUnqualifiedDesugaredType()
- ->getAs<FunctionProtoType>())
- {
- return CalleeWrapper(prototype);
- }
- }
-
- return llvm::Optional<CalleeWrapper>();
-}
-
-loplugin::Plugin::Registration<ConstFields> X("constfields", false);
-}
-
-#endif
-
-/* vim:set shiftwidth=4 softtabstop=4 expandtab: */