From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- compilerplugins/clang/singlevalfields.cxx | 577 ++++++++++++++++++++++++++++++ 1 file changed, 577 insertions(+) create mode 100644 compilerplugins/clang/singlevalfields.cxx (limited to 'compilerplugins/clang/singlevalfields.cxx') diff --git a/compilerplugins/clang/singlevalfields.cxx b/compilerplugins/clang/singlevalfields.cxx new file mode 100644 index 000000000..df5abcc00 --- /dev/null +++ b/compilerplugins/clang/singlevalfields.cxx @@ -0,0 +1,577 @@ +/* -*- 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 +#include +#include +#include +#include + +#include "config_clang.h" + +#include "compat.hxx" +#include "plugin.hxx" + +#include "clang/AST/ParentMapContext.h" + +/** +Look for fields that are only ever assigned a single constant value. + +We dmp a list of values assigned to fields, and a list of field definitions. +Then we will post-process the 2 lists and find the set of interesting fields. + +Be warned that it produces around 5G of log file. + +The process goes something like this: + $ make check + $ make FORCE_COMPILE=all COMPILER_PLUGIN_TOOL='singlevalfields' check + $ ./compilerplugins/clang/singlevalfields.py + +Note that the actual process may involve a fair amount of undoing, hand editing, and general messing around +to get it to work :-) + +@TODO we don't spot fields that have been zero-initialised via calloc or rtl_allocateZeroMemory or memset +@TODO calls to lambdas where a reference to the field is taken + +*/ + +namespace { + +struct MyFieldInfo +{ + FieldDecl const * fieldDecl; + std::string parentClass; + std::string fieldName; + std::string fieldType; + std::string sourceLocation; +}; +bool operator < (const MyFieldInfo &lhs, const MyFieldInfo &rhs) +{ + return std::tie(lhs.parentClass, lhs.fieldName) + < std::tie(rhs.parentClass, rhs.fieldName); +} + +struct MyFieldAssignmentInfo : public MyFieldInfo +{ + std::string value; +}; + +bool operator < (const MyFieldAssignmentInfo &lhs, const MyFieldAssignmentInfo &rhs) +{ + return std::tie(lhs.parentClass, lhs.fieldName, lhs.value) + < std::tie(rhs.parentClass, rhs.fieldName, rhs.value); +} + + +// try to limit the voluminous output a little +static std::set assignedSet; +static std::set definitionSet; + +/** escape the value string to make it easier to parse the output file in python */ +std::string escape(std::string s) +{ + std::string out; + for (size_t i=0; i= 32) + out += s[i]; + else + out += "\\" + std::to_string((int)s[i]); + return out; +} + +class SingleValFields: + public RecursiveASTVisitor, public loplugin::Plugin +{ +public: + explicit SingleValFields(loplugin::InstantiationData const & data): + Plugin(data) {} + + virtual void run() override + { + 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 MyFieldAssignmentInfo & s : assignedSet) + output += "asgn:\t" + s.parentClass + "\t" + s.fieldName + "\t" + escape(s.value) + "\n"; + for (const MyFieldInfo & s : definitionSet) + output += "defn:\t" + s.parentClass + "\t" + s.fieldName + "\t" + s.fieldType + "\t" + s.sourceLocation + "\n"; + std::ofstream myfile; + myfile.open( WORKDIR "/loplugin.singlevalfields.log", std::ios::app | std::ios::out); + myfile << output; + myfile.close(); + } + else + { + for (const MyFieldAssignmentInfo & s : assignedSet) + if (s.fieldDecl && compiler.getSourceManager().isInMainFile(s.fieldDecl->getBeginLoc())) + report( + DiagnosticsEngine::Warning, + "assign %0", + s.fieldDecl->getBeginLoc()) + << s.value; + } + } + + bool shouldVisitTemplateInstantiations () const { return true; } + // to catch compiler-generated constructors + bool shouldVisitImplicitCode() const { return true; } + + bool VisitFieldDecl( const FieldDecl* ); + bool VisitVarDecl( const VarDecl* ); + bool VisitMemberExpr( const MemberExpr* ); + bool VisitDeclRefExpr( const DeclRefExpr* ); + bool VisitCXXConstructorDecl( const CXXConstructorDecl* ); +// bool VisitUnaryExprOrTypeTraitExpr( const UnaryExprOrTypeTraitExpr* ); +private: + void niceName(const DeclaratorDecl*, MyFieldInfo&); + void walkPotentialAssign( const DeclaratorDecl* fieldOrVarDecl, const Stmt* stmt ); + std::string getExprValue(const Expr*); + const FunctionDecl* get_top_FunctionDecl_from_Stmt(const Stmt&); + void checkCallExpr(const Stmt* child, const CallExpr* callExpr, std::string& assignValue, bool& bPotentiallyAssignedTo); +}; + +void SingleValFields::niceName(const DeclaratorDecl* fieldOrVarDecl, MyFieldInfo& aInfo) +{ + const VarDecl* varDecl = dyn_cast(fieldOrVarDecl); + const FieldDecl* fieldDecl = dyn_cast(fieldOrVarDecl); + aInfo.fieldDecl = fieldDecl; + if (fieldDecl) + aInfo.parentClass = fieldDecl->getParent()->getQualifiedNameAsString(); + else + { + if (auto parentRecordDecl = dyn_cast(varDecl->getDeclContext())) + aInfo.parentClass = parentRecordDecl->getQualifiedNameAsString(); + else if (auto parentMethodDecl = dyn_cast(varDecl->getDeclContext())) + aInfo.parentClass = parentMethodDecl->getQualifiedNameAsString(); + else if (auto parentFunctionDecl = dyn_cast(varDecl->getDeclContext())) + aInfo.parentClass = parentFunctionDecl->getQualifiedNameAsString(); + else if (isa(varDecl->getDeclContext())) + aInfo.parentClass = handler.getMainFileName().str(); + else if (auto parentNamespaceDecl = dyn_cast(varDecl->getDeclContext())) + aInfo.parentClass = parentNamespaceDecl->getQualifiedNameAsString(); + else if (isa(varDecl->getDeclContext())) + aInfo.parentClass = "extern"; // what to do here? + else + { + std::cout << "what is this? " << varDecl->getDeclContext()->getDeclKindName() << std::endl; + exit(1); + } + } + aInfo.fieldName = fieldOrVarDecl->getNameAsString(); + aInfo.fieldType = fieldOrVarDecl->getType().getAsString(); + + SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( fieldOrVarDecl->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); +} + +bool SingleValFields::VisitFieldDecl( const FieldDecl* fieldDecl ) +{ + auto canonicalDecl = fieldDecl->getCanonicalDecl(); + + if( isInUnoIncludeFile( compiler.getSourceManager().getSpellingLoc(canonicalDecl->getLocation())) ) + return true; + + MyFieldInfo aInfo; + niceName(canonicalDecl, aInfo); + definitionSet.insert(aInfo); + + if (fieldDecl->getInClassInitializer()) + { + MyFieldAssignmentInfo aInfo; + niceName(canonicalDecl, aInfo); + aInfo.value = getExprValue(fieldDecl->getInClassInitializer()); + assignedSet.insert(aInfo); + } + + return true; +} + +bool SingleValFields::VisitVarDecl( const VarDecl* varDecl ) +{ + if (isa(varDecl)) + return true; + if (varDecl->getType().isConstQualified()) + return true; + if (!(varDecl->isStaticLocal() || varDecl->isStaticDataMember() || varDecl->hasGlobalStorage())) + return true; + + auto canonicalDecl = varDecl->getCanonicalDecl(); + if (!canonicalDecl->getLocation().isValid()) + return true; + + if( isInUnoIncludeFile( compiler.getSourceManager().getSpellingLoc(canonicalDecl->getLocation())) ) + return true; + + MyFieldInfo aInfo; + niceName(canonicalDecl, aInfo); + definitionSet.insert(aInfo); + + if (varDecl->getInit()) + { + MyFieldAssignmentInfo aInfo; + niceName(canonicalDecl, aInfo); + aInfo.value = getExprValue(varDecl->getInit()); + assignedSet.insert(aInfo); + } + + return true; +} + +bool SingleValFields::VisitCXXConstructorDecl( const CXXConstructorDecl* decl ) +{ + // doesn't count as a write to fields because it's self->self + if (decl->isCopyOrMoveConstructor()) + return true; + + for(auto it = decl->init_begin(); it != decl->init_end(); ++it) + { + const CXXCtorInitializer* init = *it; + const FieldDecl* fieldDecl = init->getMember(); + if( !fieldDecl ) + continue; + MyFieldAssignmentInfo aInfo; + niceName(fieldDecl, aInfo); + const Expr * expr = init->getInit(); + // unwrap any single-arg constructors, this helps to find smart pointers + // that are only assigned nullptr + if (auto cxxConstructExpr = dyn_cast(expr)) + if (cxxConstructExpr->getNumArgs() == 1) + expr = cxxConstructExpr->getArg(0); + aInfo.value = getExprValue(expr); + assignedSet.insert(aInfo); + } + return true; +} + +bool SingleValFields::VisitMemberExpr( const MemberExpr* memberExpr ) +{ + const ValueDecl* decl = memberExpr->getMemberDecl(); + const FieldDecl* fieldDecl = dyn_cast(decl); + if (!fieldDecl) + return true; + walkPotentialAssign(fieldDecl, memberExpr); + return true; +} + +bool SingleValFields::VisitDeclRefExpr( const DeclRefExpr* declRefExpr ) +{ + const VarDecl* varDecl = dyn_cast_or_null(declRefExpr->getDecl()); + if (!varDecl) + return true; + if (isa(varDecl)) + return true; + if (varDecl->getType().isConstQualified()) + return true; + if (!(varDecl->isStaticLocal() || varDecl->isStaticDataMember() || varDecl->hasGlobalStorage())) + return true; + walkPotentialAssign(varDecl, declRefExpr); + return true; +} + +void SingleValFields::walkPotentialAssign( const DeclaratorDecl* fieldOrVarDecl, const Stmt* memberExpr ) +{ + const FunctionDecl* parentFunction = getParentFunctionDecl(memberExpr); + if (parentFunction) + { + auto methodDecl = dyn_cast(parentFunction); + if (methodDecl && (methodDecl->isCopyAssignmentOperator() || methodDecl->isMoveAssignmentOperator())) + return; + if (methodDecl && methodDecl->getIdentifier() + && (methodDecl->getName().startswith("Clone") || methodDecl->getName().startswith("clone"))) + return; + auto cxxConstructorDecl = dyn_cast(parentFunction); + if (cxxConstructorDecl && cxxConstructorDecl->isCopyOrMoveConstructor()) + return; + } + + // walk up the tree until we find something interesting + const Stmt* child = memberExpr; + const Stmt* parent = getParentStmt(memberExpr); + bool bPotentiallyAssignedTo = false; + bool bDump = false; + std::string assignValue = "?"; + + // check for field being returned by non-const ref eg. Foo& getFoo() { return f; } + if (parentFunction && parent && isa(parent)) { + const Stmt* parent2 = getParentStmt(parent); + if (parent2 && isa(parent2)) { + QualType qt = parentFunction->getReturnType().getDesugaredType(compiler.getASTContext()); + if (!qt.isConstQualified() && qt->isReferenceType()) { + bPotentiallyAssignedTo = true; + } + } + } + + while (!bPotentiallyAssignedTo) { + // check for field being accessed by a reference variable e.g. Foo& f = m.foo; + auto parentsList = compiler.getASTContext().getParents(*child); + auto it = parentsList.begin(); + if (it != parentsList.end()) { + const VarDecl *varDecl = it->get(); + if (varDecl) { + QualType qt = varDecl->getType().getDesugaredType(compiler.getASTContext()); + if (!qt.isConstQualified() && qt->isReferenceType()) { + bPotentiallyAssignedTo = true; + break; + } + } + } + + if (!parent) { + return; + } + if (isa(parent) || isa(parent) || isa(parent) || isa(parent) + || isa(parent)) + { + child = parent; + parent = getParentStmt(parent); + } + else if (isa(parent)) + { + const UnaryOperator* unaryOperator = dyn_cast(parent); + int x = unaryOperator->getOpcode(); + if (x == UO_AddrOf || x == UO_PostInc || x == UO_PostDec || x == UO_PreInc || x == UO_PreDec) { + assignValue = "?"; + bPotentiallyAssignedTo = true; + break; + } + // cannot be assigned to anymore + break; + } + else if (auto callExpr = dyn_cast(parent)) + { + checkCallExpr(child, callExpr, assignValue, bPotentiallyAssignedTo); + break; + } + else if (isa(parent)) + { + const CXXConstructExpr* consExpr = dyn_cast(parent); + const CXXConstructorDecl* consDecl = consExpr->getConstructor(); + for (unsigned i = 0; i < consExpr->getNumArgs(); ++i) { + if (i >= consDecl->getNumParams()) // can happen in template code + break; + if (consExpr->getArg(i) == child) { + const ParmVarDecl* parmVarDecl = consDecl->getParamDecl(i); + QualType qt = parmVarDecl->getType().getDesugaredType(compiler.getASTContext()); + if (!qt.isConstQualified() && qt->isReferenceType()) { + bPotentiallyAssignedTo = true; + } + break; + } + } + break; + } + else if (isa(parent)) + { + const BinaryOperator* binaryOp = dyn_cast(parent); + auto op = binaryOp->getOpcode(); + if ( binaryOp->getLHS() != child ) { + // if the expr is on the RHS, do nothing + } + else if ( op == BO_Assign ) { + assignValue = getExprValue(binaryOp->getRHS()); + bPotentiallyAssignedTo = true; + } else if ( 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 ) + { + bPotentiallyAssignedTo = true; + } + break; + } + else if ( isa(parent) + || isa(parent) || isa(parent) || isa(parent) + || isa(parent) || isa(parent) + || isa(parent) + || isa(parent) + || isa(parent) + || isa(parent) + || isa(parent) + || isa(parent) + || isa(parent) + || isa(parent) + || isa(parent) + || isa(parent) + || isa(parent) + || isa(parent) + || isa(parent) //??? + || isa(parent) + || isa(parent) + || isa(parent) + || isa(parent) + || isa(parent) + ) + { + break; + } + else if ( isa(parent) || isa(parent) || isa(parent) || isa(parent)) + { + bPotentiallyAssignedTo = true; + break; + } + else { + bPotentiallyAssignedTo = true; + bDump = true; + break; + } + } + if (bDump) + { + report( + DiagnosticsEngine::Warning, + "oh dear, what can the matter be?", + memberExpr->getBeginLoc()) + << memberExpr->getSourceRange(); + parent->dump(); + } + if (bPotentiallyAssignedTo) + { + MyFieldAssignmentInfo aInfo; + niceName(fieldOrVarDecl, aInfo); + aInfo.value = assignValue; + assignedSet.insert(aInfo); + } +} + +void SingleValFields::checkCallExpr(const Stmt* child, const CallExpr* callExpr, std::string& assignValue, bool& bPotentiallyAssignedTo) +{ + if (callExpr->getCallee() == child) { + return; + } + const FunctionDecl* functionDecl; + if (auto memberCallExpr = dyn_cast(callExpr)) { + functionDecl = memberCallExpr->getMethodDecl(); + } else { + functionDecl = callExpr->getDirectCallee(); + } + if (functionDecl) { + if (auto operatorCallExpr = dyn_cast(callExpr)) { + if (operatorCallExpr->getArg(0) == child) { + const CXXMethodDecl* calleeMethodDecl = dyn_cast_or_null(operatorCallExpr->getDirectCallee()); + if (calleeMethodDecl) { + if (operatorCallExpr->getOperator() == OO_Equal) { + assignValue = getExprValue(operatorCallExpr->getArg(1)); + bPotentiallyAssignedTo = true; + return; + } + } + } + } + for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) { + if (i >= functionDecl->getNumParams()) // can happen in template code + break; + if (callExpr->getArg(i) == child) { + const ParmVarDecl* parmVarDecl = functionDecl->getParamDecl(i); + QualType qt = parmVarDecl->getType().getDesugaredType(compiler.getASTContext()); + if (!qt.isConstQualified() && qt->isReferenceType()) { + assignValue = "?"; + bPotentiallyAssignedTo = true; + } + break; + } + } + return; + } + // check for function pointers + const FieldDecl* calleeFieldDecl = dyn_cast_or_null(callExpr->getCalleeDecl()); + if (!calleeFieldDecl) { + return; + } + QualType qt = calleeFieldDecl->getType().getDesugaredType(compiler.getASTContext()); + if (!qt->isPointerType()) { + return; + } + qt = qt->getPointeeType().getDesugaredType(compiler.getASTContext()); + const FunctionProtoType* proto = qt->getAs(); + if (!proto) { + return; + } + for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) { + if (i >= proto->getNumParams()) // can happen in template code + break; + if (callExpr->getArg(i) == child) { + QualType qt = proto->getParamType(i).getDesugaredType(compiler.getASTContext()); + if (!qt.isConstQualified() && qt->isReferenceType()) { + assignValue = "?"; + bPotentiallyAssignedTo = true; + } + break; + } + } +} + + +std::string SingleValFields::getExprValue(const Expr* arg) +{ + if (!arg) + return "?"; + arg = arg->IgnoreParenCasts(); + arg = arg->IgnoreImplicit(); + // ignore this, it seems to trigger an infinite recursion + if (isa(arg)) + return "?"; + if (arg->isValueDependent()) + return "?"; + // for stuff like: OUString foo = "xxx"; + if (auto stringLiteral = dyn_cast(arg)) + { + if (stringLiteral->getCharByteWidth() == 1) + return stringLiteral->getString().str(); + return "?"; + } + // ParenListExpr containing a CXXNullPtrLiteralExpr and has a NULL type pointer + if (auto parenListExpr = dyn_cast(arg)) + { + if (parenListExpr->getNumExprs() == 1) + return getExprValue(parenListExpr->getExpr(0)); + return "?"; + } + if (auto constructExpr = dyn_cast(arg)) + { + if (constructExpr->getNumArgs() >= 1 + && isa(constructExpr->getArg(0))) + { + auto stringLiteral = dyn_cast(constructExpr->getArg(0)); + if (stringLiteral->getCharByteWidth() == 1) + return stringLiteral->getString().str(); + return "?"; + } + } + if (arg->getType()->isFloatingType()) + { + APFloat x1(0.0f); + if (arg->EvaluateAsFloat(x1, compiler.getASTContext())) + { + std::string s; + llvm::raw_string_ostream os(s); + x1.print(os); + return os.str(); + } + } + APSInt x1; + if (compat::EvaluateAsInt(arg, x1, compiler.getASTContext())) + return compat::toString(x1, 10); + if (isa(arg)) + return "0"; + return "?"; +} + +loplugin::Plugin::Registration< SingleValFields > X("singlevalfields", false); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit v1.2.3