diff --git a/src/TypeUtils.cpp b/src/TypeUtils.cpp index 03a699e..7898779 100644 --- a/src/TypeUtils.cpp +++ b/src/TypeUtils.cpp @@ -1,167 +1,166 @@ /* This file is part of the clazy static checker. Copyright (C) 2016-2017 Sergio Martins This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "TypeUtils.h" #include "HierarchyUtils.h" #include "StringUtils.h" #include "Utils.h" #include "StmtBodyRange.h" #include "ClazyContext.h" #include #include #include #include #include #include #include using namespace clang; -bool TypeUtils::classifyQualType(const ClazyContext *context, const VarDecl *varDecl, QualTypeClassification &classif, clang::Stmt *body) +bool TypeUtils::classifyQualType(const ClazyContext *context, clang::QualType qualType, + const VarDecl *varDecl, QualTypeClassification &classif, + clang::Stmt *body) { - if (!varDecl) - return false; - - QualType qualType = TypeUtils::unrefQualType(varDecl->getType()); - const Type *paramType = qualType.getTypePtrOrNull(); + QualType unrefQualType = TypeUtils::unrefQualType(qualType); + const Type *paramType = unrefQualType.getTypePtrOrNull(); if (!paramType || paramType->isIncompleteType()) return false; if (isUndeducibleAuto(paramType)) return false; - classif.size_of_T = context->astContext.getTypeSize(qualType) / 8; + classif.size_of_T = context->astContext.getTypeSize(unrefQualType) / 8; classif.isBig = classif.size_of_T > 16; CXXRecordDecl *recordDecl = paramType->getAsCXXRecordDecl(); CXXMethodDecl *copyCtor = recordDecl ? Utils::copyCtor(recordDecl) : nullptr; classif.isNonTriviallyCopyable = recordDecl && (recordDecl->hasNonTrivialCopyConstructor() || recordDecl->hasNonTrivialDestructor() || (copyCtor && copyCtor->isDeleted())); - classif.isReference = varDecl->getType()->isLValueReferenceType(); - classif.isConst = qualType.isConstQualified(); + classif.isReference = qualType->isLValueReferenceType(); + classif.isConst = unrefQualType.isConstQualified(); - if (varDecl->getType()->isRValueReferenceType()) // && ref, nothing to do here + if (qualType->isRValueReferenceType()) // && ref, nothing to do here return true; if (classif.isConst && !classif.isReference) { classif.passNonTriviallyCopyableByConstRef = classif.isNonTriviallyCopyable; if (classif.isBig) { classif.passBigTypeByConstRef = true; } } else if (classif.isConst && classif.isReference && !classif.isNonTriviallyCopyable && !classif.isBig) { classif.passSmallTrivialByValue = true; - } else if (!classif.isConst && !classif.isReference && (classif.isBig || classif.isNonTriviallyCopyable)) { + } else if (varDecl && !classif.isConst && !classif.isReference && (classif.isBig || classif.isNonTriviallyCopyable)) { if (body && (Utils::containsNonConstMemberCall(context->parentMap, body, varDecl) || Utils::isPassedToFunction(StmtBodyRange(body), varDecl, /*byrefonly=*/ true))) return true; classif.passNonTriviallyCopyableByConstRef = classif.isNonTriviallyCopyable; if (classif.isBig) { classif.passBigTypeByConstRef = true; } } return true; } void TypeUtils::heapOrStackAllocated(Expr *arg, const std::string &type, const clang::LangOptions &lo, bool &isStack, bool &isHeap) { isStack = false; isHeap = false; if (isa(arg)) { isHeap = true; return; } std::vector declrefs; clazy::getChilds(arg, declrefs, 3); std::vector interestingDeclRefs; for (auto declref : declrefs) { auto t = declref->getType().getTypePtrOrNull(); if (!t) continue; // Remove the '*' if it's a pointer QualType qt = t->isPointerType() ? t->getPointeeType() : declref->getType(); if (t && type == clazy::simpleTypeName(qt, lo)) { interestingDeclRefs.push_back(declref); } } if (interestingDeclRefs.size() > 1) { // Too complex return; } if (!interestingDeclRefs.empty()) { auto declref = interestingDeclRefs[0]; isStack = !declref->getType().getTypePtr()->isPointerType(); isHeap = !isStack; } } bool TypeUtils::derivesFrom(const CXXRecordDecl *derived, const CXXRecordDecl *possibleBase, std::vector *baseClasses) { if (!derived || !possibleBase || derived == possibleBase) return false; for (auto base : derived->bases()) { const Type *type = base.getType().getTypePtrOrNull(); if (!type) continue; CXXRecordDecl *baseDecl = type->getAsCXXRecordDecl(); baseDecl = baseDecl ? baseDecl->getCanonicalDecl() : nullptr; if (possibleBase == baseDecl || derivesFrom(baseDecl, possibleBase, baseClasses)) { if (baseClasses) baseClasses->push_back(baseDecl); return true; } } return false; } bool TypeUtils::derivesFrom(const clang::CXXRecordDecl *derived, const std::string &possibleBase) { if (!derived || !derived->hasDefinition()) return false; if (derived->getQualifiedNameAsString() == possibleBase) return true; for (auto base : derived->bases()) { if (derivesFrom(recordFromBaseSpecifier(base), possibleBase)) return true; } return false; } bool TypeUtils::derivesFrom(QualType derivedQT, const std::string &possibleBase) { derivedQT = pointeeQualType(derivedQT); const auto t = derivedQT.getTypePtrOrNull(); return t ? derivesFrom(t->getAsCXXRecordDecl(), possibleBase) : false; } diff --git a/src/TypeUtils.h b/src/TypeUtils.h index 9c98aaf..f1f7d6b 100644 --- a/src/TypeUtils.h +++ b/src/TypeUtils.h @@ -1,232 +1,232 @@ /* This file is part of the clazy static checker. Copyright (C) 2016-2017 Sergio Martins This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef CLAZY_TYPE_UTILS_H #define CLAZY_TYPE_UTILS_H #include #include #include #include #include #include #include #include #include namespace clang { class CompilerInstance; class Expr; class LangOptions; class QualType; class Stmt; class VarDecl; class Type; class CXXRecordDecl; class CXXBaseSpecifier; } class ClazyContext; namespace TypeUtils { /** * Returns the sizeof(void*) for the platform we're compiling for, in bits. */ inline int sizeOfPointer(const clang::ASTContext *context, clang::QualType qt) { if (!qt.getTypePtrOrNull()) return -1; // HACK: What's a better way of getting the size of a pointer ? return context->getTypeSize(context->getPointerType(qt)); } struct QualTypeClassification { bool isConst = false; bool isReference = false; bool isBig = false; bool isNonTriviallyCopyable = false; bool passBigTypeByConstRef = false; bool passNonTriviallyCopyableByConstRef = false; bool passSmallTrivialByValue = false; int size_of_T = 0; }; /** * Classifies a QualType, for example: * * This function is useful to know if a type should be passed by value or const-ref. * The optional parameter body is in order to advise non-const-ref -> value, since the body * needs to be inspected to see if we that would compile. */ -bool classifyQualType(const ClazyContext *context, const clang::VarDecl *varDecl, +bool classifyQualType(const ClazyContext *context, clang::QualType qualType, const clang::VarDecl *varDecl, QualTypeClassification &classification, clang::Stmt *body = nullptr); /** * If qt is a reference, return it without a reference. * If qt is not a reference, return qt. * * This is useful because sometimes you have an argument like "const QString &", but qualType.isConstQualified() * returns false. Must go through qualType->getPointeeType().isConstQualified(). */ inline clang::QualType unrefQualType(clang::QualType qualType) { const clang::Type *t = qualType.getTypePtrOrNull(); return (t && t->isReferenceType()) ? t->getPointeeType() : qualType; } /** * If qt is a pointer or ref, return it without * or &. * Otherwise return qt unchanged. */ inline clang::QualType pointeeQualType(clang::QualType qualType) { // TODO: Make this recursive when we need to remove more than one level of * const clang::Type *t = qualType.getTypePtrOrNull(); return (t && (t->isReferenceType() || t->isPointerType())) ? t->getPointeeType() : qualType; } /** * Returns if @p arg is stack or heap allocated. * true means it is. false means it either isn't or the situation was too complex to judge. */ void heapOrStackAllocated(clang::Expr *arg, const std::string &type, const clang::LangOptions &lo, bool &isStack, bool &isHeap); /** * Returns true if t is an AutoType that can't be deduced. */ inline bool isUndeducibleAuto(const clang::Type *t) { if (!t) return false; auto at = llvm::dyn_cast(t); return at && at->getDeducedType().isNull(); } inline const clang::Type * unpealAuto(clang::QualType q) { if (q.isNull()) return nullptr; if (auto t = llvm::dyn_cast(q.getTypePtr())) return t->getDeducedType().getTypePtrOrNull(); return q.getTypePtr(); } /** * Returns true if childDecl is a descent from parentDecl **/ bool derivesFrom(const clang::CXXRecordDecl *derived, const clang::CXXRecordDecl *possibleBase, std::vector *baseClasses = nullptr); // Overload bool derivesFrom(const clang::CXXRecordDecl *derived, const std::string &possibleBase); // Overload bool derivesFrom(clang::QualType derived, const std::string &possibleBase); /** * Returns the CXXRecordDecl represented by the CXXBaseSpecifier */ inline clang::CXXRecordDecl * recordFromBaseSpecifier(const clang::CXXBaseSpecifier &base) { const clang::Type *t = base.getType().getTypePtrOrNull(); return t ? t->getAsCXXRecordDecl() : nullptr; } /** * Returns true if the value is const. This is usually equivalent to qt.isConstQualified() but * takes care of the special case where qt represents a pointer. Many times you don't care if the * pointer is const or not and just care about the pointee. * * A a; => false * const A a; => true * A* a; => false * const A* a; => true * A *const a; => false */ inline bool valueIsConst(clang::QualType qt) { return pointeeQualType(qt).isConstQualified(); } inline clang::CXXRecordDecl* typeAsRecord(clang::QualType qt) { if (qt.isNull()) return nullptr; return qt->getAsCXXRecordDecl(); } inline clang::CXXRecordDecl* typeAsRecord(clang::Expr *expr) { if (!expr) return nullptr; return typeAsRecord(pointeeQualType(expr->getType())); } inline clang::CXXRecordDecl* typeAsRecord(clang::ValueDecl *value) { if (!value) return nullptr; return typeAsRecord(pointeeQualType(value->getType())); } /** * Returns the class that the typedef refered by qt is in. * * class Foo { * typedef A B; * }; * * For the above example Foo would be returned. */ inline clang::CXXRecordDecl* parentRecordForTypedef(clang::QualType qt) { auto t = qt.getTypePtrOrNull(); if (!t) return nullptr; if (t->getTypeClass() == clang::Type::Typedef) { auto tdt = static_cast(t); clang::TypedefNameDecl *tdnd = tdt->getDecl(); return llvm::dyn_cast_or_null(tdnd->getDeclContext()); } return nullptr; } // Example: const QString & inline bool isConstRef(const clang::Type *t) { return t && t->isReferenceType() && t->getPointeeType().isConstQualified(); } } #endif diff --git a/src/checks/level1/foreach.cpp b/src/checks/level1/foreach.cpp index 7e00f05..702ba2f 100644 --- a/src/checks/level1/foreach.cpp +++ b/src/checks/level1/foreach.cpp @@ -1,223 +1,223 @@ /* This file is part of the clazy static checker. Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Sérgio Martins Copyright (C) 2015 Sergio Martins This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "foreach.h" #include "ClazyContext.h" #include "Utils.h" #include "HierarchyUtils.h" #include "QtUtils.h" #include "TypeUtils.h" #include "PreProcessorVisitor.h" #include "StringUtils.h" #include "SourceCompatibilityHelpers.h" #include "clazy_stl.h" #include #include #include #include #include #include #include #include #include #include #include namespace clang { class Decl; class DeclContext; } // namespace clang using namespace clang; using namespace std; Foreach::Foreach(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { context->enablePreprocessorVisitor(); } void Foreach::VisitStmt(clang::Stmt *stmt) { PreProcessorVisitor *preProcessorVisitor = m_context->preprocessorVisitor; if (!preProcessorVisitor || preProcessorVisitor->qtVersion() >= 50900) { // Disabled since 5.9 because the Q_FOREACH internals changed. // Not worth fixing it because range-loop is recommended return; } auto forStm = dyn_cast(stmt); if (forStm) { m_lastForStmt = forStm; return; } if (!m_lastForStmt) return; auto constructExpr = dyn_cast(stmt); if (!constructExpr || constructExpr->getNumArgs() < 1) return; CXXConstructorDecl *constructorDecl = constructExpr->getConstructor(); if (!constructorDecl || clazy::name(constructorDecl) != "QForeachContainer") return; vector declRefExprs; clazy::getChilds(constructExpr, declRefExprs); if (declRefExprs.empty()) return; // Get the container value declaration DeclRefExpr *declRefExpr = declRefExprs.front(); auto valueDecl = dyn_cast(declRefExpr->getDecl()); if (!valueDecl) return; QualType containerQualType = constructExpr->getArg(0)->getType(); const Type *containerType = containerQualType.getTypePtrOrNull(); CXXRecordDecl *const containerRecord = containerType ? containerType->getAsCXXRecordDecl() : nullptr; if (!containerRecord) return; auto rootBaseClass = Utils::rootBaseClass(containerRecord); StringRef containerClassName = clazy::name(rootBaseClass); const bool isQtContainer = clazy::isQtIterableClass(containerClassName); if (containerClassName.empty()) { emitWarning(clazy::getLocStart(stmt), "internal error, couldn't get class name of foreach container, please report a bug"); return; } else { if (!isQtContainer) { emitWarning(clazy::getLocStart(stmt), "foreach with STL container causes deep-copy (" + rootBaseClass->getQualifiedNameAsString() + ')'); return; } else if (containerClassName == "QVarLengthArray") { emitWarning(clazy::getLocStart(stmt), "foreach with QVarLengthArray causes deep-copy"); return; } } checkBigTypeMissingRef(); if (isa(constructExpr->getArg(0))) // Nothing else to check return; // const containers are fine if (valueDecl->getType().isConstQualified()) return; // Now look inside the for statement for detachments if (containsDetachments(m_lastForStmt, valueDecl)) { emitWarning(clazy::getLocStart(stmt), "foreach container detached"); } } void Foreach::checkBigTypeMissingRef() { // Get the inner forstm vector forStatements; clazy::getChilds(m_lastForStmt->getBody(), forStatements); if (forStatements.empty()) return; // Get the variable declaration (lhs of foreach) vector varDecls; clazy::getChilds(forStatements.at(0), varDecls); if (varDecls.empty()) return; Decl *decl = varDecls.at(0)->getSingleDecl(); VarDecl *varDecl = decl ? dyn_cast(decl) : nullptr; if (!varDecl) return; TypeUtils::QualTypeClassification classif; - bool success = TypeUtils::classifyQualType(m_context, varDecl, /*by-ref*/ classif, forStatements.at(0)); + bool success = TypeUtils::classifyQualType(m_context, varDecl->getType(), varDecl, /*by-ref*/ classif, forStatements.at(0)); if (!success) return; if (classif.passBigTypeByConstRef || classif.passNonTriviallyCopyableByConstRef || classif.passSmallTrivialByValue) { string error; const string paramStr = varDecl->getType().getAsString(); if (classif.passBigTypeByConstRef) { error = "Missing reference in foreach with sizeof(T) = "; error += std::to_string(classif.size_of_T) + " bytes (" + paramStr + ')'; } else if (classif.passNonTriviallyCopyableByConstRef) { error = "Missing reference in foreach with non trivial type (" + paramStr + ')'; } else if (classif.passSmallTrivialByValue) { //error = "Pass small and trivially-copyable type by value (" + paramStr + ')'; // Don't warn. The compiler can (and most do) optimize this and generate the same code return; } emitWarning(clazy::getLocStart(varDecl), error.c_str()); } } bool Foreach::containsDetachments(Stmt *stm, clang::ValueDecl *containerValueDecl) { if (!stm) return false; auto memberExpr = dyn_cast(stm); if (memberExpr) { ValueDecl *valDecl = memberExpr->getMemberDecl(); if (valDecl && valDecl->isCXXClassMember()) { DeclContext *declContext = valDecl->getDeclContext(); auto recordDecl = dyn_cast(declContext); if (recordDecl) { const std::string className = Utils::rootBaseClass(recordDecl)->getQualifiedNameAsString(); const std::unordered_map> &detachingMethodsMap = clazy::detachingMethods(); if (detachingMethodsMap.find(className) != detachingMethodsMap.end()) { const std::string functionName = valDecl->getNameAsString(); const auto &allowedFunctions = detachingMethodsMap.at(className); if (clazy::contains(allowedFunctions, functionName)) { Expr *expr = memberExpr->getBase(); if (expr) { DeclRefExpr *refExpr = dyn_cast(expr); if (!refExpr) { auto s = clazy::getFirstChildAtDepth(expr, 1); refExpr = dyn_cast(s); if (refExpr) { if (refExpr->getDecl() == containerValueDecl) { // Finally, check if this non-const member call is on the same container we're iterating return true; } } } } } } } } } return clazy::any_of(stm->children(), [this, containerValueDecl](Stmt *child) { return this->containsDetachments(child, containerValueDecl); }); } diff --git a/src/checks/level1/range-loop.cpp b/src/checks/level1/range-loop.cpp index a72fefb..1a9247e 100644 --- a/src/checks/level1/range-loop.cpp +++ b/src/checks/level1/range-loop.cpp @@ -1,164 +1,164 @@ /* This file is part of the clazy static checker. Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Sérgio Martins Copyright (C) 2015 Sergio Martins This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "range-loop.h" #include "Utils.h" #include "QtUtils.h" #include "TypeUtils.h" #include "StringUtils.h" #include "LoopUtils.h" #include "StmtBodyRange.h" #include "SourceCompatibilityHelpers.h" #include "FixItUtils.h" #include "ClazyContext.h" #include "PreProcessorVisitor.h" #include #include #include #include #include #include #include #include class ClazyContext; using namespace clang; using namespace std; enum Fixit { Fixit_AddRef = 1, Fixit_AddqAsConst = 2 }; RangeLoop::RangeLoop(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { if (fixitsEnabled()) { context->enablePreprocessorVisitor(); } } void RangeLoop::VisitStmt(clang::Stmt *stmt) { if (auto rangeLoop = dyn_cast(stmt)) { processForRangeLoop(rangeLoop); } } bool RangeLoop::islvalue(Expr *exp, SourceLocation &endLoc) { if (isa(exp)) { endLoc = clazy::locForEndOfToken(&m_astContext, clazy::getLocStart(exp)); return true; } if (auto me = dyn_cast(exp)) { auto decl = me->getMemberDecl(); if (!decl || isa(decl)) return false; endLoc = clazy::locForEndOfToken(&m_astContext, me->getMemberLoc()); return true; } return false; } void RangeLoop::processForRangeLoop(CXXForRangeStmt *rangeLoop) { Expr *containerExpr = rangeLoop->getRangeInit(); if (!containerExpr) return; QualType qt = containerExpr->getType(); const Type *t = qt.getTypePtrOrNull(); if (!t || !t->isRecordType()) return; checkPassByConstRefCorrectness(rangeLoop); if (qt.isConstQualified()) // const won't detach return; auto loopVariableType = rangeLoop->getLoopVariable()->getType(); if (!TypeUtils::unrefQualType(loopVariableType).isConstQualified() && loopVariableType->isReferenceType()) return; CXXRecordDecl *record = t->getAsCXXRecordDecl(); if (!clazy::isQtCOWIterableClass(Utils::rootBaseClass(record))) return; StmtBodyRange bodyRange(nullptr, &sm(), clazy::getLocStart(rangeLoop)); if (clazy::containerNeverDetaches(clazy::containerDeclForLoop(rangeLoop), bodyRange)) return; std::vector fixits; SourceLocation end; if (fixitsEnabled() && islvalue(containerExpr, /*by-ref*/end)) { PreProcessorVisitor *preProcessorVisitor = m_context->preprocessorVisitor; if (!preProcessorVisitor || preProcessorVisitor->qtVersion() >= 50700) { // qAsConst() was added to 5.7 SourceLocation start = clazy::getLocStart(containerExpr); fixits.push_back(clazy::createInsertion(start, "qAsConst(")); //SourceLocation end = getLocEnd(containerExpr); fixits.push_back(clazy::createInsertion(end, ")")); } } emitWarning(clazy::getLocStart(rangeLoop), "c++11 range-loop might detach Qt container (" + record->getQualifiedNameAsString() + ')', fixits); } void RangeLoop::checkPassByConstRefCorrectness(CXXForRangeStmt *rangeLoop) { TypeUtils::QualTypeClassification classif; auto varDecl = rangeLoop->getLoopVariable(); - bool success = TypeUtils::classifyQualType(m_context, varDecl, /*by-ref*/ classif, rangeLoop); + bool success = varDecl && TypeUtils::classifyQualType(m_context, varDecl->getType(), varDecl, /*by-ref*/ classif, rangeLoop); if (!success) return; if (classif.passNonTriviallyCopyableByConstRef) { string msg; const string paramStr = clazy::simpleTypeName(varDecl->getType(), lo()); msg = "Missing reference in range-for with non trivial type (" + paramStr + ')'; std::vector fixits; if (fixitsEnabled()) { const bool isConst = varDecl->getType().isConstQualified(); if (!isConst) { SourceLocation start = clazy::getLocStart(varDecl); fixits.push_back(clazy::createInsertion(start, "const ")); } SourceLocation end = varDecl->getLocation(); fixits.push_back(clazy::createInsertion(end, "&")); } // We ignore classif.passSmallTrivialByValue because it doesn't matter, the compiler is able // to optimize it, generating the same assembly, regardless of pass by value. emitWarning(clazy::getLocStart(varDecl), msg.c_str(), fixits); } } diff --git a/src/checks/level2/function-args-by-ref.cpp b/src/checks/level2/function-args-by-ref.cpp index 6b7b5cb..fdb88b1 100644 --- a/src/checks/level2/function-args-by-ref.cpp +++ b/src/checks/level2/function-args-by-ref.cpp @@ -1,202 +1,202 @@ /* This file is part of the clazy static checker. Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Sérgio Martins Copyright (C) 2015,2018 Sergio Martins This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "function-args-by-ref.h" #include "Utils.h" #include "TypeUtils.h" #include "ClazyContext.h" #include "StringUtils.h" #include "SourceCompatibilityHelpers.h" #include "clazy_stl.h" #include "FixItUtils.h" #include #include #include #include #include #include #include #include #include #include #include using namespace clang; using namespace std; static bool shouldIgnoreClass(CXXRecordDecl *record) { if (!record) return false; if (Utils::isSharedPointer(record)) return true; static const vector ignoreList = {"QDebug", // Too many warnings "QGenericReturnArgument", "QColor", // TODO: Remove in Qt6 "QStringRef", // TODO: Remove in Qt6 "QList::const_iterator", // TODO: Remove in Qt6 "QJsonArray::const_iterator", // TODO: Remove in Qt6 "QList::const_iterator", // TODO: Remove in Qt6 "QtMetaTypePrivate::QSequentialIterableImpl", "QtMetaTypePrivate::QAssociativeIterableImpl", "QVariantComparisonHelper", "QHashDummyValue", "QCharRef", "QString::Null" }; return clazy::contains(ignoreList, record->getQualifiedNameAsString()); } static bool shouldIgnoreOperator(FunctionDecl *function) { // Too many warnings in operator<< static const vector ignoreList = { "operator<<" }; return clazy::contains(ignoreList, clazy::name(function)); } static bool shouldIgnoreFunction(clang::FunctionDecl *function) { static const vector qualifiedIgnoreList = {"QDBusMessage::createErrorReply", // Fixed in Qt6 "QMenu::exec", // Fixed in Qt6 "QTextFrame::iterator", // Fixed in Qt6 "QGraphicsWidget::addActions", // Fixed in Qt6 "QListWidget::mimeData", // Fixed in Qt6 "QTableWidget::mimeData", // Fixed in Qt6 "QTreeWidget::mimeData", // Fixed in Qt6 "QWidget::addActions", // Fixed in Qt6 "QSslCertificate::verify", // Fixed in Qt6 "QSslConfiguration::setAllowedNextProtocols" // Fixed in Qt6 }; return clazy::contains(qualifiedIgnoreList, function->getQualifiedNameAsString()); } FunctionArgsByRef::FunctionArgsByRef(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { } static std::string warningMsgForSmallType(int sizeOf, const std::string &typeName) { std::string sizeStr = std::to_string(sizeOf); return "Missing reference on large type (sizeof " + typeName + " is " + sizeStr + " bytes)"; } void FunctionArgsByRef::processFunction(FunctionDecl *func) { if (!func || !func->isThisDeclarationADefinition() || func->isDeleted() || shouldIgnoreOperator(func)) return; if (m_context->isQtDeveloper() && shouldIgnoreFunction(func)) return; const bool warnForOverriddenMethods = isOptionSet("warn-for-overridden-methods") || fixitsEnabled(); if (!warnForOverriddenMethods && Utils::methodOverrides(dyn_cast(func))) { // When overriding you can't change the signature. You should fix the base classes first return; } Stmt *body = func->getBody(); auto funcParams = Utils::functionParameters(func); for (unsigned int i = 0; i < funcParams.size(); ++i) { ParmVarDecl* param = funcParams[i]; - QualType paramQt = TypeUtils::unrefQualType(param->getType()); + const QualType paramQt = TypeUtils::unrefQualType(param->getType()); const Type *paramType = paramQt.getTypePtrOrNull(); if (!paramType || paramType->isIncompleteType() || paramType->isDependentType()) continue; if (shouldIgnoreClass(paramType->getAsCXXRecordDecl())) continue; TypeUtils::QualTypeClassification classif; - bool success = TypeUtils::classifyQualType(m_context, param, classif, body); + bool success = TypeUtils::classifyQualType(m_context, param->getType(), param, classif, body); if (!success) continue; vector ctorInits = Utils::ctorInitializer(dyn_cast(func), param); if (Utils::ctorInitializerContainsMove(ctorInits)) continue; if (classif.passBigTypeByConstRef || classif.passNonTriviallyCopyableByConstRef) { string error; std::vector fixits; const string paramStr = param->getType().getAsString(); if (classif.passBigTypeByConstRef) { error = warningMsgForSmallType(classif.size_of_T, paramStr); } else if (classif.passNonTriviallyCopyableByConstRef) { error = "Missing reference on non-trivial type (" + paramStr + ')'; } addFixits(fixits, func, i); emitWarning(clazy::getLocStart(param), error.c_str(), fixits); } } } void FunctionArgsByRef::addFixits(std::vector &fixits, FunctionDecl *func, unsigned int parmIndex) { if (fixitsEnabled()) { for (auto funcRedecl : func->redecls()) { auto funcParams = Utils::functionParameters(funcRedecl); if (funcParams.size() <= parmIndex) return; ParmVarDecl *param = funcParams[parmIndex]; QualType paramQt = TypeUtils::unrefQualType(param->getType()); const bool isConst = paramQt.isConstQualified(); if (!isConst) { SourceLocation start = clazy::getLocStart(param); fixits.push_back(clazy::createInsertion(start, "const ")); } SourceLocation end = param->getLocation(); fixits.push_back(clazy::createInsertion(end, "&")); } } } void FunctionArgsByRef::VisitDecl(Decl *decl) { processFunction(dyn_cast(decl)); } void FunctionArgsByRef::VisitStmt(Stmt *stmt) { if (auto lambda = dyn_cast(stmt)) { if (!shouldIgnoreFile(clazy::getLocStart(stmt))) processFunction(lambda->getCallOperator()); } } clang::FixItHint FunctionArgsByRef::fixit(const ParmVarDecl *, TypeUtils::QualTypeClassification) { FixItHint fixit; return fixit; } diff --git a/src/checks/level2/function-args-by-value.cpp b/src/checks/level2/function-args-by-value.cpp index 0bd270a..b302f87 100644 --- a/src/checks/level2/function-args-by-value.cpp +++ b/src/checks/level2/function-args-by-value.cpp @@ -1,227 +1,227 @@ /* This file is part of the clazy static checker. Copyright (C) 2016-2018 Sergio Martins This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "function-args-by-value.h" #include "Utils.h" #include "StringUtils.h" #include "TypeUtils.h" #include "FixItUtils.h" #include "ClazyContext.h" #include "SourceCompatibilityHelpers.h" #include "clazy_stl.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace clang { class Decl; } // namespace clang using namespace clang; using namespace std; // TODO, go over all these static bool shouldIgnoreClass(CXXRecordDecl *record) { if (!record) return false; if (Utils::isSharedPointer(record)) return true; static const vector ignoreList = {"QDebug", // Too many warnings "QGenericReturnArgument", "QColor", // TODO: Remove in Qt6 "QStringRef", // TODO: Remove in Qt6 "QList::const_iterator", // TODO: Remove in Qt6 "QJsonArray::const_iterator", // TODO: Remove in Qt6 "QList::const_iterator", // TODO: Remove in Qt6 "QtMetaTypePrivate::QSequentialIterableImpl", "QtMetaTypePrivate::QAssociativeIterableImpl", "QVariantComparisonHelper", "QHashDummyValue", "QCharRef", "QString::Null" }; return clazy::contains(ignoreList, record->getQualifiedNameAsString()); } static bool shouldIgnoreOperator(FunctionDecl *function) { // Too many warnings in operator<< static const vector ignoreList = { "operator<<" }; return clazy::contains(ignoreList, clazy::name(function)); } static bool shouldIgnoreFunction(clang::FunctionDecl *function) { static const vector qualifiedIgnoreList = {"QDBusMessage::createErrorReply", // Fixed in Qt6 "QMenu::exec", // Fixed in Qt6 "QTextFrame::iterator", // Fixed in Qt6 "QGraphicsWidget::addActions", // Fixed in Qt6 "QListWidget::mimeData", // Fixed in Qt6 "QTableWidget::mimeData", // Fixed in Qt6 "QTreeWidget::mimeData", // Fixed in Qt6 "QWidget::addActions", // Fixed in Qt6 "QSslCertificate::verify", // Fixed in Qt6 "QSslConfiguration::setAllowedNextProtocols" // Fixed in Qt6 }; return clazy::contains(qualifiedIgnoreList, function->getQualifiedNameAsString()); } FunctionArgsByValue::FunctionArgsByValue(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { } void FunctionArgsByValue::VisitDecl(Decl *decl) { processFunction(dyn_cast(decl)); } void FunctionArgsByValue::VisitStmt(Stmt *stmt) { if (auto lambda = dyn_cast(stmt)) processFunction(lambda->getCallOperator()); } void FunctionArgsByValue::processFunction(FunctionDecl *func) { if (!func || !func->isThisDeclarationADefinition() || func->isDeleted()) return; auto ctor = dyn_cast(func); if (ctor && ctor->isCopyConstructor()) return; // copy-ctor must take by ref const bool warnForOverriddenMethods = isOptionSet("warn-for-overridden-methods"); if (!warnForOverriddenMethods && Utils::methodOverrides(dyn_cast(func))) { // When overriding you can't change the signature. You should fix the base classes first return; } if (shouldIgnoreOperator(func)) return; if (m_context->isQtDeveloper() && shouldIgnoreFunction(func)) return; Stmt *body = func->getBody(); int i = -1; for (auto param : Utils::functionParameters(func)) { i++; - QualType paramQt = TypeUtils::unrefQualType(param->getType()); + const QualType paramQt = TypeUtils::unrefQualType(param->getType()); const Type *paramType = paramQt.getTypePtrOrNull(); if (!paramType || paramType->isIncompleteType() || paramType->isDependentType()) continue; if (shouldIgnoreClass(paramType->getAsCXXRecordDecl())) continue; TypeUtils::QualTypeClassification classif; - bool success = TypeUtils::classifyQualType(m_context, param, classif, body); + bool success = TypeUtils::classifyQualType(m_context, param->getType(), param, classif, body); if (!success) continue; if (classif.passSmallTrivialByValue) { if (ctor) { // Implements fix for Bug #379342 vector initializers = Utils::ctorInitializer(ctor, param); bool found_by_ref_member_init = false; for (auto initializer : initializers) { if (!initializer->isMemberInitializer()) continue; // skip base class initializer FieldDecl *field = initializer->getMember(); if (!field) continue; QualType type = field->getType(); if (type.isNull() || type->isReferenceType()) { found_by_ref_member_init = true; break; } } if (found_by_ref_member_init) continue; } std::vector fixits; auto method = dyn_cast(func); const bool isVirtualMethod = method && method->isVirtual(); if ((!isVirtualMethod || warnForOverriddenMethods) && fixitsEnabled()) { // Don't try to fix virtual methods, as build can fail for (auto redecl : func->redecls()) { // Fix in both header and .cpp auto fdecl = dyn_cast(redecl); const ParmVarDecl *param = fdecl->getParamDecl(i); fixits.push_back(fixit(fdecl, param, classif)); } } const string paramStr = param->getType().getAsString(); string error = "Pass small and trivially-copyable type by value (" + paramStr + ')'; emitWarning(clazy::getLocStart(param), error.c_str(), fixits); } } } FixItHint FunctionArgsByValue::fixit(FunctionDecl *func, const ParmVarDecl *param, TypeUtils::QualTypeClassification) { QualType qt = TypeUtils::unrefQualType(param->getType()); qt.removeLocalConst(); const string typeName = qt.getAsString(PrintingPolicy(lo())); string replacement = typeName + ' ' + string(clazy::name(param)); SourceLocation startLoc = clazy::getLocStart(param); SourceLocation endLoc = clazy::getLocEnd(param); const int numRedeclarations = std::distance(func->redecls_begin(), func->redecls_end()); const bool definitionIsAlsoDeclaration = numRedeclarations == 1; const bool isDeclarationButNotDefinition = !func->doesThisDeclarationHaveABody(); if (param->hasDefaultArg() && (isDeclarationButNotDefinition || definitionIsAlsoDeclaration)) { endLoc = clazy::getLocStart(param->getDefaultArg()).getLocWithOffset(-1); replacement += " ="; } if (!startLoc.isValid() || !endLoc.isValid()) { llvm::errs() << "Internal error could not apply fixit " << startLoc.printToString(sm()) << ';' << endLoc.printToString(sm()) << "\n"; return {}; } return clazy::createReplacement({ startLoc, endLoc }, replacement); }