diff --git a/src/Utils.cpp b/src/Utils.cpp index ef0d268..0472462 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -1,970 +1,991 @@ /* 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-2016 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 "Utils.h" #include "StringUtils.h" #include "HierarchyUtils.h" #include "StmtBodyRange.h" #include "clazy_stl.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace clang { class LangOptions; } // namespace clang using namespace clang; using namespace std; bool Utils::hasConstexprCtor(CXXRecordDecl *decl) { return clazy::any_of(decl->ctors(), [](CXXConstructorDecl *ctor) { return ctor->isConstexpr(); }); } CXXRecordDecl * Utils::namedCastInnerDecl(CXXNamedCastExpr *staticOrDynamicCast) { Expr *e = staticOrDynamicCast->getSubExpr(); if (!e) return nullptr; if (auto implicitCast = dyn_cast(e)) { // Sometimes it's automatically cast to base if (implicitCast->getCastKind() == CK_DerivedToBase) { e = implicitCast->getSubExpr(); } } QualType qt = e->getType(); const Type *t = qt.getTypePtrOrNull(); if (!t) return nullptr; QualType qt2 = t->getPointeeType(); const Type *t2 = qt2.getTypePtrOrNull(); if (!t2) return nullptr; return t2->getAsCXXRecordDecl(); } CXXRecordDecl * Utils::namedCastOuterDecl(CXXNamedCastExpr *staticOrDynamicCast) { QualType qt = staticOrDynamicCast->getTypeAsWritten(); const Type *t = qt.getTypePtrOrNull(); QualType qt2 = t->getPointeeType(); const Type *t2 = qt2.getTypePtrOrNull(); if (!t2) return nullptr; return t2->getAsCXXRecordDecl(); } bool Utils::allChildrenMemberCallsConst(Stmt *stm) { if (!stm) return false; auto expr = dyn_cast(stm); if (expr) { auto methodDecl = dyn_cast(expr->getMemberDecl()); if (methodDecl && !methodDecl->isConst()) return false; } return clazy::all_of(stm->children(), [](Stmt *child) { return allChildrenMemberCallsConst(child); }); } bool Utils::childsHaveSideEffects(Stmt *stm) { if (!stm) return false; auto unary = dyn_cast(stm); if (unary && (unary->isIncrementOp() || unary->isDecrementOp())) return true; auto binary = dyn_cast(stm); if (binary && (binary->isAssignmentOp() || binary->isShiftAssignOp() || binary->isCompoundAssignmentOp())) return true; static const std::vector method_blacklist = { "isDestroyed", "isRecursive", // TODO: Use qualified name instead ? "q_func", "d_func", "begin", "end", "data", "fragment", "glIsRenderbuffer" }; auto memberCall = dyn_cast(stm); if (memberCall) { auto methodDecl = dyn_cast(memberCall->getMemberDecl()); if (methodDecl && !methodDecl->isConst() && !methodDecl->isStatic() && !clazy::contains(method_blacklist, clazy::name(methodDecl))) return true; } /* // too many false positives, qIsFinite() etc for example auto callExpr = dyn_cast(stm); if (callExpr) { FunctionDecl *callee = callExpr->getDirectCallee(); if (callee && callee->isGlobal()) return true; }*/ return clazy::any_of(stm->children(), [](Stmt *s) { return childsHaveSideEffects(s); }); } CXXRecordDecl *Utils::recordFromVarDecl(Decl *decl) { auto varDecl = dyn_cast(decl); if (!varDecl) return nullptr; QualType qt = varDecl->getType(); const Type *t = qt.getTypePtrOrNull(); if (!t) return nullptr; return t->getAsCXXRecordDecl(); } ClassTemplateSpecializationDecl *Utils::templateSpecializationFromVarDecl(Decl *decl) { auto record = recordFromVarDecl(decl); if (record) return dyn_cast(record); return nullptr; } ValueDecl *Utils::valueDeclForMemberCall(CXXMemberCallExpr *memberCall) { if (!memberCall) return nullptr; Expr *implicitObject = memberCall->getImplicitObjectArgument(); if (!implicitObject) return nullptr; auto declRefExpr = dyn_cast(implicitObject); auto memberExpr = dyn_cast(implicitObject); if (declRefExpr) { return declRefExpr->getDecl(); } else if (memberExpr) { return memberExpr->getMemberDecl(); } // Maybe there's an implicit cast in between.. auto memberExprs = clazy::getStatements(implicitObject, nullptr, {}, /**depth=*/ 1, /*includeParent=*/ true); auto declRefs = clazy::getStatements(implicitObject, nullptr, {}, /**depth=*/ 1, /*includeParent=*/ true); if (!memberExprs.empty()) { return memberExprs.at(0)->getMemberDecl(); } if (!declRefs.empty()) { return declRefs.at(0)->getDecl(); } return nullptr; } ValueDecl *Utils::valueDeclForOperatorCall(CXXOperatorCallExpr *operatorCall) { if (!operatorCall) return nullptr; // CXXOperatorCallExpr doesn't have API to access the value decl. // By inspecting several ASTs I noticed it's always in the 2nd child Stmt *child2 = clazy::childAt(operatorCall, 1); if (!child2) return nullptr; if (auto memberExpr = dyn_cast(child2)) { return memberExpr->getMemberDecl(); } else { vector refs; clazy::getChilds(child2, refs); if (refs.size() == 1) { return refs[0]->getDecl(); } } return nullptr; } clang::ValueDecl * Utils::valueDeclForCallExpr(clang::CallExpr *expr) { if (auto memberExpr = dyn_cast(expr)) { return valueDeclForMemberCall(memberExpr); } else if (auto operatorExpr = dyn_cast(expr)) { return valueDeclForOperatorCall(operatorExpr); } return nullptr; } static bool referencesVar(Stmt *s, const VarDecl *varDecl) { // look for a DeclRefExpr that references varDecl while (s) { auto it = s->child_begin(); Stmt *child = it == s->child_end() ? nullptr : *it; if (auto declRef = dyn_cast_or_null(child)) { if (declRef->getDecl() == varDecl) return true; } s = child; } return false; } bool Utils::containsNonConstMemberCall(clang::ParentMap *map, Stmt *body, const VarDecl *varDecl) { if (!varDecl) return false; std::vector memberCallExprs; clazy::getChilds(body, memberCallExprs); for (auto memberCall : memberCallExprs) { CXXMethodDecl *methodDecl = memberCall->getMethodDecl(); if (methodDecl && !methodDecl->isConst()) { ValueDecl *valueDecl = Utils::valueDeclForMemberCall(memberCall); if (valueDecl == varDecl) return true; } } std::vector operatorCalls; clazy::getChilds(body, operatorCalls); for (auto operatorCall : operatorCalls) { FunctionDecl *fDecl = operatorCall->getDirectCallee(); if (fDecl) { auto methodDecl = dyn_cast(fDecl); if (methodDecl && !methodDecl->isConst()) { ValueDecl *valueDecl = Utils::valueDeclForOperatorCall(operatorCall); if (valueDecl == varDecl) return true; } } } std::vector assignmentOperators; clazy::getChilds(body, assignmentOperators); for (auto op : assignmentOperators) { if (!op->isAssignmentOp()) continue; if (referencesVar(op, varDecl)) return true; } return false; } template static bool isArgOfFunc(T expr, FunctionDecl *fDecl, const VarDecl *varDecl, bool byRefOrPtrOnly) { unsigned int param = -1; for (auto arg : expr->arguments()) { ++param; auto refExpr = dyn_cast(arg); if (!refExpr) { if (clazy::hasChildren(arg)) { Stmt* firstChild = *(arg->child_begin()); // Can be null (bug #362236) refExpr = firstChild ? dyn_cast(firstChild) : nullptr; if (!refExpr) continue; } else { continue; } } if (refExpr->getDecl() != varDecl) // It's our variable ? continue; if (!byRefOrPtrOnly) { // We found it return true; } // It is, lets see if the callee takes our variable by const-ref if (param >= fDecl->param_size()) continue; ParmVarDecl *paramDecl = fDecl->getParamDecl(param); if (!paramDecl) continue; QualType qt = paramDecl->getType(); const Type *t = qt.getTypePtrOrNull(); if (!t) continue; if ((t->isReferenceType() || t->isPointerType()) && !t->getPointeeType().isConstQualified()) return true; // function receives non-const ref, so our foreach variable cant be const-ref } return false; } bool Utils::isPassedToFunction(const StmtBodyRange &bodyRange, const VarDecl *varDecl, bool byRefOrPtrOnly) { if (!bodyRange.isValid()) return false; Stmt *body = bodyRange.body; std::vector callExprs; clazy::getChilds(body, callExprs); for (CallExpr *callexpr : callExprs) { if (bodyRange.isOutsideRange(callexpr)) continue; FunctionDecl *fDecl = callexpr->getDirectCallee(); if (!fDecl) continue; if (isArgOfFunc(callexpr, fDecl, varDecl, byRefOrPtrOnly)) return true; } std::vector constructExprs; clazy::getChilds(body, constructExprs); for (CXXConstructExpr *constructExpr : constructExprs) { if (bodyRange.isOutsideRange(constructExpr)) continue; FunctionDecl *fDecl = constructExpr->getConstructor(); if (isArgOfFunc(constructExpr, fDecl, varDecl, byRefOrPtrOnly)) return true; } return false; } bool Utils::addressIsTaken(const clang::CompilerInstance &ci, Stmt *body, const clang::ValueDecl *valDecl) { if (!body || !valDecl) return false; auto unaries = clazy::getStatements(body); return clazy::any_of(unaries, [valDecl](UnaryOperator *op) { if (op->getOpcode() != clang::UO_AddrOf) return false; auto declRef = clazy::getFirstChildOfType(op); return declRef && declRef->getDecl() == valDecl; }); } +bool Utils::isReturned(Stmt *body, const VarDecl *varDecl) +{ + if (!body) + return false; + + std::vector returns; + clazy::getChilds(body, returns); + for (ReturnStmt *returnStmt : returns) { + Expr* retValue = returnStmt->getRetValue(); + if (!retValue) + continue; + auto declRef = clazy::unpeal(retValue, clazy::IgnoreImplicitCasts); + if (!declRef) + continue; + if (declRef->getDecl() == varDecl) + return true; + } + + return false; +} + bool Utils::isAssignedTo(Stmt *body, const VarDecl *varDecl) { if (!body) return false; std::vector operatorCalls; clazy::getChilds(body, operatorCalls); for (BinaryOperator *binaryOperator : operatorCalls) { if (binaryOperator->getOpcode() != clang::BO_Assign) continue; Expr *rhs = binaryOperator->getRHS(); auto declRef = clazy::unpeal(rhs, clazy::IgnoreImplicitCasts); if (!declRef) continue; if (declRef->getDecl() == varDecl) return true; } return false; } bool Utils::isAssignedFrom(Stmt *body, const VarDecl *varDecl) { if (!body) return false; std::vector operatorCalls; clazy::getChilds(body, operatorCalls); for (CXXOperatorCallExpr *operatorExpr : operatorCalls) { FunctionDecl *fDecl = operatorExpr->getDirectCallee(); if (!fDecl) continue; auto methodDecl = dyn_cast(fDecl); if (methodDecl && methodDecl->isCopyAssignmentOperator()) { ValueDecl *valueDecl = Utils::valueDeclForOperatorCall(operatorExpr); if (valueDecl == varDecl) return true; } } return false; } bool Utils::callHasDefaultArguments(clang::CallExpr *expr) { std::vector exprs; clazy::getChilds(expr, exprs, 1); return !exprs.empty(); } bool Utils::containsStringLiteral(Stmt *stm, bool allowEmpty, int depth) { if (!stm) return false; std::vector stringLiterals; clazy::getChilds(stm, stringLiterals, depth); if (allowEmpty) return !stringLiterals.empty(); for (StringLiteral *sl : stringLiterals) { if (sl->getLength() > 0) return true; } return false; } bool Utils::ternaryOperatorIsOfStringLiteral(ConditionalOperator *ternary) { bool skipFirst = true; for (auto child : ternary->children()) { if (skipFirst) { skipFirst = false; continue; } if (isa(child)) continue; auto arrayToPointerDecay = dyn_cast(child); if (!arrayToPointerDecay || !isa(*(arrayToPointerDecay->child_begin()))) return false; } return true; } bool Utils::isAssignOperator(CXXOperatorCallExpr *op, StringRef className, StringRef argumentType, const clang::LangOptions &lo) { if (!op) return false; FunctionDecl *functionDecl = op->getDirectCallee(); if (!functionDecl || functionDecl->param_size() != 1 ) return false; if (!className.empty()) { auto methodDecl = dyn_cast(functionDecl); if (!clazy::isOfClass(methodDecl, className)) return false; } if (functionDecl->getNameAsString() != "operator=") return false; if (!argumentType.empty() && !clazy::hasArgumentOfType(functionDecl, argumentType, lo)) return false; return true; } bool Utils::isImplicitCastTo(Stmt *s, const string &className) { auto expr = dyn_cast(s); if (!expr) return false; auto record = expr->getBestDynamicClassType(); return record && clazy::name(record) == className; } bool Utils::isInsideOperatorCall(ParentMap *map, Stmt *s, const std::vector &anyOf) { if (!s) return false; auto oper = dyn_cast(s); if (oper) { auto func = oper->getDirectCallee(); if (func) { if (anyOf.empty()) return true; auto method = dyn_cast(func); if (method) { auto record = method->getParent(); if (record && clazy::contains(anyOf, clazy::name(record))) return true; } } } return isInsideOperatorCall(map, clazy::parent(map, s), anyOf); } bool Utils::insideCTORCall(ParentMap *map, Stmt *s, const std::vector &anyOf) { if (!s) return false; auto expr = dyn_cast(s); if (expr && expr->getConstructor() && clazy::contains(anyOf, clazy::name(expr->getConstructor()))) { return true; } return insideCTORCall(map, clazy::parent(map, s), anyOf); } bool Utils::presumedLocationsEqual(const clang::PresumedLoc &l1, const clang::PresumedLoc &l2) { return l1.isValid() && l2.isValid() && l1.getColumn() == l2.getColumn() && l1.getLine() == l2.getLine() && StringRef(l1.getFilename()) == StringRef(l2.getFilename()); } CXXRecordDecl *Utils::isMemberVariable(ValueDecl *decl) { return decl ? dyn_cast(decl->getDeclContext()) : nullptr; } std::vector Utils::methodsFromString(const CXXRecordDecl *record, const string &methodName) { if (!record) return {}; vector methods; clazy::append_if(record->methods(), methods, [methodName](CXXMethodDecl *m) { return clazy::name(m) == methodName; }); // Also include the base classes for (auto base : record->bases()) { const Type *t = base.getType().getTypePtrOrNull(); if (t) { auto baseMethods = methodsFromString(t->getAsCXXRecordDecl(), methodName); if (!baseMethods.empty()) clazy::append(baseMethods, methods); } } return methods; } const CXXRecordDecl *Utils::recordForMemberCall(CXXMemberCallExpr *call, string &implicitCallee) { implicitCallee.clear(); Expr *implicitArgument= call->getImplicitObjectArgument(); if (!implicitArgument) { return nullptr; } Stmt *s = implicitArgument; while (s) { if (auto declRef = dyn_cast(s)) { if (declRef->getDecl()) { implicitCallee = declRef->getDecl()->getNameAsString(); QualType qt = declRef->getDecl()->getType(); return qt->getPointeeCXXRecordDecl(); } else { return nullptr; } } else if (auto thisExpr = dyn_cast(s)) { implicitCallee = "this"; return thisExpr->getType()->getPointeeCXXRecordDecl(); } else if (auto memberExpr = dyn_cast(s)) { auto decl = memberExpr->getMemberDecl(); if (decl) { implicitCallee = decl->getNameAsString(); QualType qt = decl->getType(); return qt->getPointeeCXXRecordDecl(); } else { return nullptr; } } s = s->child_begin() == s->child_end() ? nullptr : *(s->child_begin()); } return nullptr; } bool Utils::isAscii(StringLiteral *lt) { // 'é' for some reason has isAscii() == true, so also call containsNonAsciiOrNull return lt && lt->isAscii() && !lt->containsNonAsciiOrNull(); } bool Utils::isInDerefExpression(Stmt *s, ParentMap *map) { if (!s) return false; Stmt *p = s; do { p = clazy::parent(map, p); auto op = p ? dyn_cast(p) : nullptr; if (op && op->getOperator() == OO_Star) { return op; } } while (p); return false; } std::vector Utils::callListForChain(CallExpr *lastCallExpr) { if (!lastCallExpr) return {}; const bool isOperator = isa(lastCallExpr); vector callexprs = { lastCallExpr }; Stmt *s = lastCallExpr; do { const int childCount = std::distance(s->child_begin(), s->child_end()); if (isOperator && childCount > 1 && s == lastCallExpr) { // for operator case, the chained call childs are in the second child s = *(++s->child_begin()); } else { s = childCount > 0 ? *s->child_begin() : nullptr; } if (s) { auto callExpr = dyn_cast(s); if (callExpr && callExpr->getCalleeDecl()) { callexprs.push_back(callExpr); } else if (auto memberExpr = dyn_cast(s)) { if (isa(memberExpr->getMemberDecl())) break; // accessing a public member via . or -> breaks the chain } else if (isa(s)) { // Gets very greasy with conditional operators // This would match: (should() ? container1 : container2).append() // and it would return { append(), should()} break; } } } while (s); return callexprs; } CXXRecordDecl *Utils::rootBaseClass(CXXRecordDecl *derived) { if (!derived || derived->getNumBases() == 0) return derived; CXXBaseSpecifier *base = derived->bases_begin(); CXXRecordDecl *record = base->getType()->getAsCXXRecordDecl(); return record ? rootBaseClass(record) : derived; } CXXConstructorDecl *Utils::copyCtor(CXXRecordDecl *record) { for (auto ctor : record->ctors()) { if (ctor->isCopyConstructor()) return ctor; } return nullptr; } CXXMethodDecl *Utils::copyAssign(CXXRecordDecl *record) { for (auto copyAssign : record->methods()) { if (copyAssign->isCopyAssignmentOperator()) return copyAssign; } return nullptr; } bool Utils::hasMember(CXXRecordDecl *record, const string &memberTypeName) { if (!record) return false; for (auto field : record->fields()) { field->getParent()->getNameAsString(); QualType qt = field->getType(); const Type *t = qt.getTypePtrOrNull(); if (t && t->getAsCXXRecordDecl()) { CXXRecordDecl *rec = t->getAsCXXRecordDecl(); if (clazy::name(rec) == memberTypeName) return true; } } return false; } bool Utils::isSharedPointer(CXXRecordDecl *record) { static const vector names = { "std::shared_ptr", "QSharedPointer", "boost::shared_ptr" }; return record ? clazy::contains(names, record->getQualifiedNameAsString()) : false; } bool Utils::isInitializedExternally(clang::VarDecl *varDecl) { if (!varDecl) return false; DeclContext *context = varDecl->getDeclContext(); auto fDecl = context ? dyn_cast(context) : nullptr; Stmt *body = fDecl ? fDecl->getBody() : nullptr; if (!body) return false; vector declStmts; clazy::getChilds(body, declStmts); for (DeclStmt *declStmt : declStmts) { if (referencesVarDecl(declStmt, varDecl)) { vector declRefs; clazy::getChilds(declStmt, declRefs); if (!declRefs.empty()) return true; vector callExprs; clazy::getChilds(declStmt, callExprs); if (!callExprs.empty()) return true; } } return false; } bool Utils::functionHasEmptyBody(clang::FunctionDecl *func) { Stmt *body = func ? func->getBody() : nullptr; return !clazy::hasChildren(body); } clang::Expr *Utils::isWriteOperator(Stmt *stm) { if (!stm) return nullptr; if (auto up = dyn_cast(stm)) { auto opcode = up->getOpcode(); if (opcode == clang::UO_AddrOf || opcode == clang::UO_Deref) return nullptr; return up->getSubExpr(); } if (auto bp = dyn_cast(stm)) return bp->getLHS(); return nullptr; } bool Utils::referencesVarDecl(clang::DeclStmt *declStmt, clang::VarDecl *varDecl) { if (!declStmt || !varDecl) return false; if (declStmt->isSingleDecl() && declStmt->getSingleDecl() == varDecl) return true; return clazy::any_of(declStmt->getDeclGroup(), [varDecl](Decl *decl) { return varDecl == decl; }); } UserDefinedLiteral *Utils::userDefinedLiteral(Stmt *stm, const std::string &type, const clang::LangOptions &lo) { auto udl = dyn_cast(stm); if (!udl) udl = clazy::getFirstChildOfType(stm); if (udl && clazy::returnTypeName(udl, lo) == type) { return udl; } return nullptr; } clang::ArrayRef Utils::functionParameters(clang::FunctionDecl *func) { return func->parameters(); } vector Utils::ctorInitializer(CXXConstructorDecl *ctor, clang::ParmVarDecl *param) { if (!ctor) return {}; vector result; for (auto it = ctor->init_begin(), end = ctor->init_end(); it != end; ++it) { auto ctorInit = *it; vector declRefs; clazy::getChilds(ctorInit->getInit(), declRefs); for (auto declRef : declRefs) { if (declRef->getDecl() == param) { result.push_back(ctorInit); break; } } } return result; } bool Utils::ctorInitializerContainsMove(CXXCtorInitializer *init) { if (!init) return false; vector calls; clazy::getChilds(init->getInit(), calls); for (auto call : calls) { if (FunctionDecl *funcDecl = call->getDirectCallee()) { auto name = funcDecl->getQualifiedNameAsString(); if (name == "std::move" || name == "std::__1::move") return true; } } return false; } bool Utils::ctorInitializerContainsMove(const vector &ctorInits) { return clazy::any_of(ctorInits, [](CXXCtorInitializer *ctorInit) { return Utils::ctorInitializerContainsMove(ctorInit); }); } string Utils::filenameForLoc(SourceLocation loc, const clang::SourceManager &sm) { if (loc.isMacroID()) loc = sm.getExpansionLoc(loc); const string filename = sm.getFilename(loc); auto splitted = clazy::splitString(filename, '/'); if (splitted.empty()) return {}; return splitted[splitted.size() - 1]; } SourceLocation Utils::locForNextToken(SourceLocation loc, const clang::SourceManager &sm, const clang::LangOptions &lo) { std::pair locInfo = sm.getDecomposedLoc(loc); bool InvalidTemp = false; StringRef File = sm.getBufferData(locInfo.first, &InvalidTemp); if (InvalidTemp) return {}; const char *TokenBegin = File.data() + locInfo.second; Lexer lexer(sm.getLocForStartOfFile(locInfo.first), lo, File.begin(), TokenBegin, File.end()); Token Tok; lexer.LexFromRawLexer(Tok); SourceLocation TokenLoc = Tok.getLocation(); // Calculate how much whitespace needs to be skipped if any. unsigned NumWhitespaceChars = 0; const char *TokenEnd = sm.getCharacterData(TokenLoc) + Tok.getLength(); unsigned char C = *TokenEnd; while (isHorizontalWhitespace(C)) { C = *(++TokenEnd); NumWhitespaceChars++; } // Skip \r, \n, \r\n, or \n\r if (C == '\n' || C == '\r') { char PrevC = C; C = *(++TokenEnd); NumWhitespaceChars++; if ((C == '\n' || C == '\r') && C != PrevC) NumWhitespaceChars++; } return loc.getLocWithOffset(Tok.getLength() + NumWhitespaceChars); } bool Utils::literalContainsEscapedBytes(StringLiteral *lt, const SourceManager &sm, const LangOptions &lo) { if (!lt) return false; // The AST doesn't have the info, we need to ask the Lexer SourceRange sr = lt->getSourceRange(); CharSourceRange cr = Lexer::getAsCharRange(sr, sm, lo); const StringRef str = Lexer::getSourceText(cr, sm, lo); for (int i = 0, size = str.size(); i < size - 1; ++i) { if (str[i] == '\\') { auto next = str[i+1]; if (next == 'U' || next == 'u' || next == 'x' || std::isdigit(next)) return true; } } return false; } diff --git a/src/Utils.h b/src/Utils.h index e7cdef2..034fee1 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -1,317 +1,320 @@ /* 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-2016 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 MOREWARNINGS_UTILS_H #define MOREWARNINGS_UTILS_H #include "SourceCompatibilityHelpers.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // TODO: this is a dumping ground, most of these functions should be moved to the other *Utils classes namespace clang { class CXXNamedCastExpr; class CXXRecordDecl; class CXXMemberCallExpr; class CXXConstructExpr; class CompilerInstance; class ClassTemplateSpecializationDecl; class Decl; class ParentMap; class SourceManager; class Stmt; class SourceLocation; class ExprWithCleanups; class ValueDecl; class ConditionalOperator; class CXXMethodDecl; class BinaryOperator; class CXXOperatorCallExpr; class CallExpr; class DeclStmt; class Expr; class FunctionDecl; class LangOptions; class ParmVarDecl; class StringLiteral; class UserDefinedLiteral; class VarDecl; } struct StmtBodyRange; namespace Utils { /// Returns true if the class has at least one constexpr ctor bool hasConstexprCtor(clang::CXXRecordDecl *decl); /// Returns the type we're casting *from* clang::CXXRecordDecl * namedCastInnerDecl(clang::CXXNamedCastExpr *staticOrDynamicCast); /// Returns the type we're casting *to* clang::CXXRecordDecl * namedCastOuterDecl(clang::CXXNamedCastExpr *staticOrDynamicCast); /// Returns the class declaration from a variable declaration // So, if the var decl is "Foo f"; it returns the declaration of Foo clang::CXXRecordDecl * recordFromVarDecl(clang::Decl *); /// Returns the template specialization from a variable declaration // So, if the var decl is "QList f;", returns the template specialization QList clang::ClassTemplateSpecializationDecl * templateSpecializationFromVarDecl(clang::Decl *); /// Returns true if all the child member function calls are const functions. bool allChildrenMemberCallsConst(clang::Stmt *stm); /// Returns true if at least a UnaryOperator, BinaryOperator or non-const function call is found bool childsHaveSideEffects(clang::Stmt *stm); /// Receives a member call, such as "list.reserve()" and returns the declaration of the variable list // such as "QList list" clang::ValueDecl * valueDeclForMemberCall(clang::CXXMemberCallExpr *); /// Receives an operator call, such as "list << fooo" and returns the declaration of the variable list // such as "QList list" clang::ValueDecl * valueDeclForOperatorCall(clang::CXXOperatorCallExpr *); // overload clang::ValueDecl * valueDeclForCallExpr(clang::CallExpr *); // Returns true of this value decl is a member variable of a class or struct // returns null if not clang::CXXRecordDecl* isMemberVariable(clang::ValueDecl *); // Returns true if a body of statements contains a non const member call on object declared by varDecl // For example: // Foo foo; // this is the varDecl // while (bar) { foo.setValue(); // non-const call } bool containsNonConstMemberCall(clang::ParentMap *map, clang::Stmt *body, const clang::VarDecl *varDecl); // Returns true if there's an assignment to varDecl in body // Example: our_var = something_else bool isAssignedFrom(clang::Stmt *body, const clang::VarDecl *varDecl); // Returns true if varDecl is assigned to something // Example: something_else = our_var bool isAssignedTo(clang::Stmt *body, const clang::VarDecl *varDecl); +// Returns whether the variable is returned in body +bool isReturned(clang::Stmt *body, const clang::VarDecl *varDecl); + // Returns true if a body of statements contains a function call that takes our variable (varDecl) // By ref or pointer bool isPassedToFunction(const StmtBodyRange &bodyRange, const clang::VarDecl *varDecl, bool byRefOrPtrOnly); // Returns true if we take the address of varDecl, such as: &foo bool addressIsTaken(const clang::CompilerInstance &ci, clang::Stmt *body, const clang::ValueDecl *valDecl); // QString::fromLatin1("foo") -> true // QString::fromLatin1("foo", 1) -> false bool callHasDefaultArguments(clang::CallExpr *expr); // If there's a child of type StringLiteral, returns true // if allowEmpty is false, "" will be ignored bool containsStringLiteral(clang::Stmt *, bool allowEmpty = true, int depth = -1); bool isInsideOperatorCall(clang::ParentMap *map, clang::Stmt *s, const std::vector &anyOf); bool insideCTORCall(clang::ParentMap *map, clang::Stmt *s, const std::vector &anyOf); // returns true if the ternary operator has two string literal arguments, such as: // foo ? "bar" : "baz" bool ternaryOperatorIsOfStringLiteral(clang::ConditionalOperator*); bool isAssignOperator(clang::CXXOperatorCallExpr *op, llvm::StringRef className, llvm::StringRef argumentType, const clang::LangOptions &lo); bool isImplicitCastTo(clang::Stmt *, const std::string &); bool presumedLocationsEqual(const clang::PresumedLoc &l1, const clang::PresumedLoc &l2); // Returns the list of methods with name methodName that the class/struct record contains std::vector methodsFromString(const clang::CXXRecordDecl *record, const std::string &methodName); // Returns the most derived class. (CXXMemberCallExpr::getRecordDecl() return the first base class with the method) // The returned callee is the name of the variable on which the member call was made: // o1->foo() => "o1" // foo() => "this" const clang::CXXRecordDecl* recordForMemberCall(clang::CXXMemberCallExpr *call, std::string &implicitCallee); bool isAscii(clang::StringLiteral *lt); // Checks if Statement s inside an operator* call bool isInDerefExpression(clang::Stmt *s, clang::ParentMap *map); // For a a chain called expression like foo().bar().baz() returns a list of calls // {baz(), bar(), foo()} // the parameter lastCallExpr to pass would be baz() // No need to specify the other callexprs, they are children of the first one, since the AST looks like: // - baz // -- bar // --- foo std::vector callListForChain(clang::CallExpr *lastCallExpr); // Returns the first base class clang::CXXRecordDecl * rootBaseClass(clang::CXXRecordDecl *derived); // Returns the copy ctor for this class clang::CXXConstructorDecl *copyCtor(clang::CXXRecordDecl *); // Returns the copy-assignment operator for this class clang::CXXMethodDecl *copyAssign(clang::CXXRecordDecl *); bool hasMember(clang::CXXRecordDecl *record, const std::string &memberTypeName); /** * Returns true if record is a shared pointer (boost, Qt or stl only). */ bool isSharedPointer(clang::CXXRecordDecl *record); /** * Returns true if varDecl is initialized externally. * Example: * QList list = getList(); // true * QList list = list2; // true * QList list = {1, 2, 3}; // false * QList list; // false */ bool isInitializedExternally(clang::VarDecl *varDecl); /** * Returns true if declStmt refers to varDecl */ bool referencesVarDecl(clang::DeclStmt *declStmt, clang::VarDecl *varDecl); /** * Returns true if the body of a function is empty. * Returns false if either function or it's body are null. */ bool functionHasEmptyBody(clang::FunctionDecl *func); /** * If stm is an UnaryOperator or BinaryOperator that writes to the variable it returns the expression * that represents the variable (Usually a MemberExpr or DeclRefExpr for local variables). * * Otherwise returns nullptr. * * The operators that write to the variable are operator=, operator+=, operator++, etc. */ clang::Expr* isWriteOperator(clang::Stmt *stm); /** * Gets the UserDefinedLiteral of type @p type which is somewhere in the ast of @p stm. * Returns nullptr if there's no such UserDefinedLiteral. */ clang::UserDefinedLiteral* userDefinedLiteral(clang::Stmt *stm, const std::string &type, const clang::LangOptions &lo); /** * Returns the function parameters fom @p func * This should be used instead of calling FunctionDecl::params() since it changed signature in * clang 3.9. */ #if LLVM_VERSION_MAJOR == 3 && LLVM_VERSION_MINOR <= 8 clang::FunctionDecl::param_range #else clang::ArrayRef #endif functionParameters(clang::FunctionDecl *func); /** * For the given ctor, and ctor param, returns the ctor member initializers that used that param. * Example: * MyCtor(int a, int b) : c(a), d(b) {} * auto result = Utils::ctorInitializer(MyCtor, b); // Result is the statement "d(b)" */ std::vector ctorInitializer(clang::CXXConstructorDecl *ctor, clang::ParmVarDecl *param); /** * Returns true if a ctor initializer contains a std::move() * Example * MyCtor(Foo a) : c(move(a)) {} // Would return true for this init list */ bool ctorInitializerContainsMove(clang::CXXCtorInitializer*); // Overload that recieves a vector and returns true if any ctor initializer contains a move() bool ctorInitializerContainsMove(const std::vector &); /** * Returns the filename for the source location loc */ std::string filenameForLoc(clang::SourceLocation loc, const clang::SourceManager &sm); /** * Returns the location after the lexer token that is at loc. * For example: * emit sig(); * If loc refers to the location of 'emit', then this function will return the source location if * the sig() call. */ clang::SourceLocation locForNextToken(clang::SourceLocation loc, const clang::SourceManager &sm, const clang::LangOptions &lo); inline bool isMainFile(const clang::SourceManager &sm, clang::SourceLocation loc) { if (loc.isMacroID()) loc = sm.getExpansionLoc(loc); return sm.isInFileID(loc, sm.getMainFileID()); } /** * Returns true if the string literal contains escaped bytes, such as \x12, \123, \u00F6. */ bool literalContainsEscapedBytes(clang::StringLiteral *lt, const clang::SourceManager &sm, const clang::LangOptions &lo); /** * Returns true if this method overrides one from the base class */ inline bool methodOverrides(clang::CXXMethodDecl *method) { return method && method->isVirtual() && method->size_overridden_methods() > 0; } } #endif diff --git a/src/checks/manuallevel/heap-allocated-small-trivial-type.cpp b/src/checks/manuallevel/heap-allocated-small-trivial-type.cpp index 36764a9..a501ba1 100644 --- a/src/checks/manuallevel/heap-allocated-small-trivial-type.cpp +++ b/src/checks/manuallevel/heap-allocated-small-trivial-type.cpp @@ -1,78 +1,79 @@ /* This file is part of the clazy static checker. Copyright (C) 2019 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 "heap-allocated-small-trivial-type.h" #include "Utils.h" #include "StmtBodyRange.h" #include "HierarchyUtils.h" #include "QtUtils.h" #include "TypeUtils.h" #include "ClazyContext.h" #include using namespace clang; using namespace std; HeapAllocatedSmallTrivialType::HeapAllocatedSmallTrivialType(const std::string &name, ClazyContext *context) : CheckBase(name, context) { } void HeapAllocatedSmallTrivialType::VisitDecl(clang::Decl *decl) { auto varDecl = dyn_cast(decl); if (!varDecl) return; Expr *init = varDecl->getInit(); if (!init) return; auto newExpr = dyn_cast(init); if (!newExpr || newExpr->getNumPlacementArgs() > 0) // Placement new, user probably knows what he's doing return; if (newExpr->isArray()) return; DeclContext *context = varDecl->getDeclContext(); FunctionDecl *fDecl = context ? dyn_cast(context) : nullptr; if (!fDecl) return; QualType qualType = newExpr->getType()->getPointeeType(); if (clazy::isSmallTrivial(m_context, qualType)) { if (clazy::contains(qualType.getAsString(), "Private")) { // Possibly a pimpl, forward declared in header return; } auto body = fDecl->getBody(); - if (Utils::isAssignedTo(body, varDecl) || Utils::isPassedToFunction(StmtBodyRange(body), varDecl, false)) + if (Utils::isAssignedTo(body, varDecl) || + Utils::isPassedToFunction(StmtBodyRange(body), varDecl, false) || + Utils::isReturned(body, varDecl)) return; emitWarning(init, "Don't heap-allocate small trivially copyable/destructible types: " + qualType.getAsString()); } - } diff --git a/tests/heap-allocated-small-trivial-type/main.cpp b/tests/heap-allocated-small-trivial-type/main.cpp index 9f5a7ad..89a85bf 100644 --- a/tests/heap-allocated-small-trivial-type/main.cpp +++ b/tests/heap-allocated-small-trivial-type/main.cpp @@ -1,65 +1,71 @@ #include #include struct SmallTrivial { int v; }; struct BigTrivial { int v[10]; }; struct NonTrivial { int v; ~NonTrivial() {} }; struct NonTrivial2 { int v; NonTrivial2() {} NonTrivial2(const NonTrivial2 &) {} }; void test() { auto a = new SmallTrivial(); // Warn auto b = new BigTrivial(); auto c = new NonTrivial(); auto d = new NonTrivial2(); auto e = new(0) SmallTrivial; auto f = new SmallTrivial[100]; } class MyClass { void myMethod() { auto a = new SmallTrivial(); // OK m_smallTrivial = a; m_smallTrivial = new SmallTrivial(); // OK } SmallTrivial *m_smallTrivial = nullptr; }; void receivesSmallTrivial(SmallTrivial *) { } SmallTrivial* test2() { return new SmallTrivial(); // OK } SmallTrivial* test3() { auto a = new SmallTrivial(); // OK receivesSmallTrivial(a); return a; } + +SmallTrivial* test4() +{ + auto a = new SmallTrivial(); // OK + return a; +}