diff --git a/src/checkbase.cpp b/src/checkbase.cpp index ab8421d..cc64cd5 100644 --- a/src/checkbase.cpp +++ b/src/checkbase.cpp @@ -1,255 +1,261 @@ /* 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-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 "checkbase.h" #include "ClazyContext.h" #include "StringUtils.h" #include #include #include #include #include #include using namespace clang; using namespace clang::ast_matchers; using namespace std; ClazyPreprocessorCallbacks::ClazyPreprocessorCallbacks(CheckBase *check) : check(check) { } void ClazyPreprocessorCallbacks::MacroExpands(const Token ¯oNameTok, const MacroDefinition &md, SourceRange range, const MacroArgs *) { check->VisitMacroExpands(macroNameTok, range, md.getMacroInfo()); } void ClazyPreprocessorCallbacks::Defined(const Token ¯oNameTok, const MacroDefinition &, SourceRange range) { check->VisitDefined(macroNameTok, range); } void ClazyPreprocessorCallbacks::Ifdef(SourceLocation loc, const Token ¯oNameTok, const MacroDefinition &) { check->VisitIfdef(loc, macroNameTok); } void ClazyPreprocessorCallbacks::MacroDefined(const Token ¯oNameTok, const MacroDirective *) { check->VisitMacroDefined(macroNameTok); } CheckBase::CheckBase(const string &name, const ClazyContext *context, Options options) : m_sm(context->ci.getSourceManager()) , m_name(name) , m_context(context) , m_astContext(context->astContext) , m_preprocessorCallbacks(new ClazyPreprocessorCallbacks(this)) , m_options(options) , m_tag(" [-Wclazy-" + m_name + ']') { } CheckBase::~CheckBase() { } void CheckBase::VisitStmt(Stmt *) { // Overriden in derived classes } void CheckBase::VisitDecl(Decl *) { // Overriden in derived classes } void CheckBase::VisitMacroExpands(const Token &, const SourceRange &, const clang::MacroInfo *) { // Overriden in derived classes } void CheckBase::VisitMacroDefined(const Token &) { // Overriden in derived classes } void CheckBase::VisitDefined(const Token &, const SourceRange &) { // Overriden in derived classes } void CheckBase::VisitIfdef(clang::SourceLocation, const clang::Token &) { // Overriden in derived classes } void CheckBase::enablePreProcessorCallbacks() { Preprocessor &pi = m_context->ci.getPreprocessor(); pi.addPPCallbacks(std::unique_ptr(m_preprocessorCallbacks)); } bool CheckBase::shouldIgnoreFile(SourceLocation loc) const { if (m_filesToIgnore.empty()) return false; if (!loc.isValid()) return true; string filename = sm().getFilename(loc); return clazy::any_of(m_filesToIgnore, [filename](const std::string &ignored) { return clazy::contains(filename, ignored); }); } void CheckBase::emitWarning(const clang::Decl *d, const std::string &error, bool printWarningTag) { emitWarning(d->getLocStart(), error, printWarningTag); } void CheckBase::emitWarning(const clang::Stmt *s, const std::string &error, bool printWarningTag) { emitWarning(s->getLocStart(), error, printWarningTag); } void CheckBase::emitWarning(clang::SourceLocation loc, const std::string &error, bool printWarningTag) { emitWarning(loc, error, {}, printWarningTag); } void CheckBase::emitWarning(clang::SourceLocation loc, std::string error, const vector &fixits, bool printWarningTag) { if (m_context->suppressionManager.isSuppressed(m_name, loc, sm(), lo())) return; if (m_context->isHeaderFilteredOut(loc)) return; if (loc.isMacroID()) { if (warningAlreadyEmitted(loc)) return; // For warnings in macro arguments we get a warning in each place the argument is used within the expanded macro, so filter all the dups m_emittedWarningsInMacro.push_back(loc.getRawEncoding()); } if (printWarningTag) error += m_tag; reallyEmitWarning(loc, error, fixits); for (const auto& l : m_queuedManualInterventionWarnings) { string msg = string("FixIt failed, requires manual intervention: "); if (!l.second.empty()) msg += ' ' + l.second; reallyEmitWarning(l.first, msg + m_tag, {}); } m_queuedManualInterventionWarnings.clear(); } void CheckBase::emitInternalError(SourceLocation loc, string error) { llvm::errs() << m_tag << ": internal error: " << error << " at " << loc.printToString(sm()) << "\n"; } void CheckBase::reallyEmitWarning(clang::SourceLocation loc, const std::string &error, const vector &fixits) { FullSourceLoc full(loc, sm()); auto &engine = m_context->ci.getDiagnostics(); auto severity = (engine.getWarningsAsErrors() && !m_context->userDisabledWError()) ? DiagnosticIDs::Error : DiagnosticIDs::Warning; unsigned id = engine.getDiagnosticIDs()->getCustomDiagID(severity, error.c_str()); DiagnosticBuilder B = engine.Report(full, id); for (const FixItHint& fixit : fixits) { if (!fixit.isNull()) B.AddFixItHint(fixit); } } -void CheckBase::queueManualFixitWarning(clang::SourceLocation loc, int fixitType, const string &message) +void CheckBase::queueManualFixitWarning(clang::SourceLocation loc, const string &message, int fixitType) { if (isFixitEnabled(fixitType) && !manualFixitAlreadyQueued(loc)) { m_queuedManualInterventionWarnings.push_back({loc, message}); m_emittedManualFixItsWarningsInMacro.push_back(loc.getRawEncoding()); } } bool CheckBase::warningAlreadyEmitted(SourceLocation loc) const { PresumedLoc ploc = sm().getPresumedLoc(loc); for (auto rawLoc : m_emittedWarningsInMacro) { SourceLocation l = SourceLocation::getFromRawEncoding(rawLoc); PresumedLoc p = sm().getPresumedLoc(l); if (Utils::presumedLocationsEqual(p, ploc)) return true; } return false; } bool CheckBase::manualFixitAlreadyQueued(SourceLocation loc) const { PresumedLoc ploc = sm().getPresumedLoc(loc); for (auto loc : m_emittedManualFixItsWarningsInMacro) { SourceLocation l = SourceLocation::getFromRawEncoding(loc); PresumedLoc p = sm().getPresumedLoc(l); if (Utils::presumedLocationsEqual(p, ploc)) return true; } return false; } bool CheckBase::isOptionSet(const std::string &optionName) const { const string qualifiedName = name() + '-' + optionName; return m_context->isOptionSet(qualifiedName); } void CheckBase::setEnabledFixits(int fixits) { m_enabledFixits = fixits; } bool CheckBase::isFixitEnabled(int fixit) const { return (m_enabledFixits & fixit) || (m_context->options & ClazyContext::ClazyOption_AllFixitsEnabled); } +bool CheckBase::isFixitEnabled() const +{ + // Checks with only 1 fixit (which is most of them) don't need to pass fixit id + return isFixitEnabled(1); +} + ClazyAstMatcherCallback::ClazyAstMatcherCallback(CheckBase *check) : MatchFinder::MatchCallback() , m_check(check) { } diff --git a/src/checkbase.h b/src/checkbase.h index e2a895b..92b9306 100644 --- a/src/checkbase.h +++ b/src/checkbase.h @@ -1,166 +1,167 @@ /* 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-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 CHECK_BASE_H #define CHECK_BASE_H #include "clazy_export.h" #include "clazy_stl.h" #include #include #include #include #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include namespace clang { class CXXMethodDecl; class Stmt; class Decl; class TranslationUnitDecl; class FixItHint; class PresumedLoc; class SourceLocation; class PreprocessorOptions; } class CheckBase; class ClazyContext; enum CheckLevel { // See README.md for what each level does CheckLevelUndefined = -1, CheckLevel0 = 0, CheckLevel1, CheckLevel2, CheckLevel3 = 3, ManualCheckLevel, MaxCheckLevel = CheckLevel3, DefaultCheckLevel = CheckLevel1 }; class ClazyPreprocessorCallbacks : public clang::PPCallbacks { public: ClazyPreprocessorCallbacks(const ClazyPreprocessorCallbacks &) = delete; explicit ClazyPreprocessorCallbacks(CheckBase *check); void MacroExpands(const clang::Token &MacroNameTok, const clang::MacroDefinition &, clang::SourceRange, const clang::MacroArgs *) override; void MacroDefined(const clang::Token &MacroNameTok, const clang::MacroDirective*) override; void Defined(const clang::Token &MacroNameTok, const clang::MacroDefinition &, clang::SourceRange Range) override; void Ifdef(clang::SourceLocation, const clang::Token &MacroNameTok, const clang::MacroDefinition &) override; private: CheckBase *const check; }; class ClazyAstMatcherCallback : public clang::ast_matchers::MatchFinder::MatchCallback { public: explicit ClazyAstMatcherCallback(CheckBase *check); protected: CheckBase *const m_check; }; class CLAZYLIB_EXPORT CheckBase { public: enum Option { Option_None = 0, Option_CanIgnoreIncludes = 1 }; typedef int Options; typedef std::vector List; explicit CheckBase(const std::string &name, const ClazyContext *context, Options = Option_None); CheckBase(const CheckBase &other) = delete; virtual ~CheckBase(); std::string name() const { return m_name; } void setEnabledFixits(int); bool isFixitEnabled(int fixit) const; + bool isFixitEnabled() const; void emitWarning(const clang::Decl *, const std::string &error, bool printWarningTag = true); void emitWarning(const clang::Stmt *, const std::string &error, bool printWarningTag = true); void emitWarning(clang::SourceLocation loc, const std::string &error, bool printWarningTag = true); void emitWarning(clang::SourceLocation loc, std::string error, const std::vector &fixits, bool printWarningTag = true); void emitInternalError(clang::SourceLocation loc, std::string error); virtual void registerASTMatchers(clang::ast_matchers::MatchFinder &) {}; bool canIgnoreIncludes() const { return m_options & Option_CanIgnoreIncludes; } virtual void VisitStmt(clang::Stmt *stm); virtual void VisitDecl(clang::Decl *decl); protected: virtual void VisitMacroExpands(const clang::Token ¯oNameTok, const clang::SourceRange &, const clang::MacroInfo *minfo = nullptr); virtual void VisitMacroDefined(const clang::Token ¯oNameTok); virtual void VisitDefined(const clang::Token ¯oNameTok, const clang::SourceRange &); virtual void VisitIfdef(clang::SourceLocation, const clang::Token ¯oNameTok); void enablePreProcessorCallbacks(); bool shouldIgnoreFile(clang::SourceLocation) const; void reallyEmitWarning(clang::SourceLocation loc, const std::string &error, const std::vector &fixits); - void queueManualFixitWarning(clang::SourceLocation loc, int fixitType, const std::string &message = {}); + void queueManualFixitWarning(clang::SourceLocation loc, const std::string &message = {}, int fixitType = 1); bool warningAlreadyEmitted(clang::SourceLocation loc) const; bool manualFixitAlreadyQueued(clang::SourceLocation loc) const; bool isOptionSet(const std::string &optionName) const; // 3 shortcuts for stuff that litter the codebase all over. const clang::SourceManager &sm() const { return m_sm; } const clang::LangOptions &lo() const { return m_astContext.getLangOpts(); } const clang::SourceManager &m_sm; const std::string m_name; const ClazyContext *const m_context; clang::ASTContext &m_astContext; std::vector m_filesToIgnore; private: friend class ClazyPreprocessorCallbacks; friend class ClazyAstMatcherCallback; ClazyPreprocessorCallbacks *const m_preprocessorCallbacks; std::vector m_emittedWarningsInMacro; std::vector m_emittedManualFixItsWarningsInMacro; std::vector> m_queuedManualInterventionWarnings; int m_enabledFixits = 0; const Options m_options; const std::string m_tag; }; #endif diff --git a/src/checks/level0/qdatetime-utc.cpp b/src/checks/level0/qdatetime-utc.cpp index ac8c9c2..d7027af 100644 --- a/src/checks/level0/qdatetime-utc.cpp +++ b/src/checks/level0/qdatetime-utc.cpp @@ -1,84 +1,79 @@ /* 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 "qdatetime-utc.h" #include "Utils.h" #include "StringUtils.h" #include "FixItUtils.h" #include #include using namespace clang; using namespace std; -enum Fixit { - FixitNone = 0, - FixitAll = 0x1 // More granularity isn't needed I guess -}; - QDateTimeUtc::QDateTimeUtc(const std::string &name, ClazyContext *context) : CheckBase(name, context) { } void QDateTimeUtc::VisitStmt(clang::Stmt *stmt) { CXXMemberCallExpr *secondCall = dyn_cast(stmt); if (!secondCall || !secondCall->getMethodDecl()) return; CXXMethodDecl *secondMethod = secondCall->getMethodDecl(); const string secondMethodName = secondMethod->getQualifiedNameAsString(); const bool isTimeT = secondMethodName == "QDateTime::toTime_t"; if (!isTimeT && secondMethodName != "QDateTime::toUTC") return; vector chainedCalls = Utils::callListForChain(secondCall); if (chainedCalls.size() < 2) return; CallExpr *firstCall = chainedCalls[chainedCalls.size() - 1]; FunctionDecl *firstFunc = firstCall->getDirectCallee(); if (!firstFunc) return; CXXMethodDecl *firstMethod = dyn_cast(firstFunc); if (!firstMethod || firstMethod->getQualifiedNameAsString() != "QDateTime::currentDateTime") return; std::string replacement = "::currentDateTimeUtc()"; if (isTimeT) { replacement += ".toTime_t()"; } std::vector fixits; - if (isFixitEnabled(FixitAll)) { + if (isFixitEnabled()) { const bool success = clazy::transformTwoCallsIntoOneV2(&m_astContext, secondCall, replacement, fixits); if (!success) { - queueManualFixitWarning(secondCall->getLocStart(), FixitAll); + queueManualFixitWarning(secondCall->getLocStart()); } } emitWarning(stmt->getLocStart(), "Use QDateTime" + replacement + " instead", fixits); } diff --git a/src/checks/level0/qgetenv.cpp b/src/checks/level0/qgetenv.cpp index 919ba85..f2067fe 100644 --- a/src/checks/level0/qgetenv.cpp +++ b/src/checks/level0/qgetenv.cpp @@ -1,100 +1,95 @@ /* 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 "qgetenv.h" #include "Utils.h" #include "StringUtils.h" #include "FixItUtils.h" #include #include using namespace clang; using namespace std; -enum Fixit { - FixitNone = 0, - FixitAll = 0x1 // More granularity isn't needed I guess -}; - QGetEnv::QGetEnv(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { } void QGetEnv::VisitStmt(clang::Stmt *stmt) { // Lets check only in function calls. Otherwise there are too many false positives, it's common // to implicit cast to bool when checking pointers for validity, like if (ptr) auto *memberCall = dyn_cast(stmt); if (!memberCall) return; CXXMethodDecl *method = memberCall->getMethodDecl(); if (!method) return; CXXRecordDecl *record = method->getParent(); if (!record || clazy::name(record) != "QByteArray") { return; } std::vector calls = Utils::callListForChain(memberCall); if (calls.size() != 2) return; CallExpr *qgetEnvCall = calls.back(); FunctionDecl *func = qgetEnvCall->getDirectCallee(); if (!func || clazy::name(func) != "qgetenv") return; StringRef methodname = clazy::name(method); string errorMsg; std::string replacement; if (methodname == "isEmpty") { errorMsg = "qgetenv().isEmpty() allocates."; replacement = "qEnvironmentVariableIsEmpty"; } else if (methodname == "isNull") { errorMsg = "qgetenv().isNull() allocates."; replacement = "qEnvironmentVariableIsSet"; } else if (methodname == "toInt") { errorMsg = "qgetenv().toInt() is slow."; replacement = "qEnvironmentVariableIntValue"; } if (!errorMsg.empty()) { std::vector fixits; - if (isFixitEnabled(FixitAll)) { + if (isFixitEnabled()) { const bool success = clazy::transformTwoCallsIntoOne(&m_astContext, qgetEnvCall, memberCall, replacement, fixits); if (!success) { - queueManualFixitWarning(memberCall->getLocStart(), FixitAll); + queueManualFixitWarning(memberCall->getLocStart()); } } errorMsg += " Use " + replacement + "() instead"; emitWarning(memberCall->getLocStart(), errorMsg.c_str(), fixits); } } diff --git a/src/checks/level0/qstring-ref.cpp b/src/checks/level0/qstring-ref.cpp index c863c81..68c5314 100644 --- a/src/checks/level0/qstring-ref.cpp +++ b/src/checks/level0/qstring-ref.cpp @@ -1,236 +1,231 @@ /* This file is part of the clazy static checker. 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 "qstring-ref.h" #include "ClazyContext.h" #include "Utils.h" #include "HierarchyUtils.h" #include "StringUtils.h" #include "FixItUtils.h" #include #include #include #include using namespace clang; using namespace std; -enum Fixit { - FixitNone = 0, - FixitUseQStringRef = 0x1, -}; - StringRefCandidates::StringRefCandidates(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { } static bool isInterestingFirstMethod(CXXMethodDecl *method) { if (!method || clazy::name(method->getParent()) != "QString") return false; static const llvm::SmallVector list = {{ "left", "mid", "right" }}; return clazy::contains(list, clazy::name(method)); } static bool isInterestingSecondMethod(CXXMethodDecl *method, const clang::LangOptions &lo) { if (!method || clazy::name(method->getParent()) != "QString") return false; static const std::array list = {{ "compare", "contains", "count", "startsWith", "endsWith", "indexOf", "isEmpty", "isNull", "lastIndexOf", "length", "size", "toDouble", "toFloat", "toInt", "toUInt", "toULong", "toULongLong", "toUShort", "toUcs4" }}; if (!clazy::contains(list, clazy::name(method))) return false; return !clazy::anyArgIsOfAnySimpleType(method, {"QRegExp", "QRegularExpression"}, lo); } static bool isMethodReceivingQStringRef(CXXMethodDecl *method) { if (!method || clazy::name(method->getParent()) != "QString") return false; static const std::array list = {{ "append", "compare", "count", "indexOf", "endsWith", "lastIndexOf", "localAwareCompare", "startsWidth" }}; if (clazy::contains(list, clazy::name(method))) return true; if (method->getOverloadedOperator() == OO_PlusEqual) // operator+= return true; return false; } void StringRefCandidates::VisitStmt(clang::Stmt *stmt) { // Here we look for code like str.firstMethod().secondMethod(), where firstMethod() is for example mid() and secondMethod is for example, toInt() auto call = dyn_cast(stmt); if (!call || processCase1(dyn_cast(call))) return; processCase2(call); } static bool containsChild(Stmt *s, Stmt *target) { if (!s) return false; if (s == target) return true; if (auto mte = dyn_cast(s)) { return containsChild(mte->getTemporary(), target); } else if (auto ice = dyn_cast(s)) { return containsChild(ice->getSubExpr(), target); } else if (auto bte = dyn_cast(s)) { return containsChild(bte->getSubExpr(), target); } return false; } bool StringRefCandidates::isConvertedToSomethingElse(clang::Stmt* s) const { // While passing a QString to the QVariant ctor works fine, passing QStringRef doesn't // So let's not warn when QStrings are cast to something else. if (!s) return false; auto constr = clazy::getFirstParentOfType(m_context->parentMap, s); if (!constr || constr->getNumArgs() == 0) return false; if (containsChild(constr->getArg(0), s)) { CXXConstructorDecl *ctor = constr->getConstructor(); CXXRecordDecl *record = ctor ? ctor->getParent() : nullptr; return record ? record->getQualifiedNameAsString() != "QString" : false; } return false; } // Catches cases like: int i = s.mid(1, 1).toInt() bool StringRefCandidates::processCase1(CXXMemberCallExpr *memberCall) { if (!memberCall) return false; // In the AST secondMethod() is parent of firstMethod() call, and will be visited first (because at runtime firstMethod() is resolved first(). // So check for interesting second method first CXXMethodDecl *method = memberCall->getMethodDecl(); if (!isInterestingSecondMethod(method, lo())) return false; vector callExprs = Utils::callListForChain(memberCall); if (callExprs.size() < 2) return false; // The list now contains {secondMethod(), firstMethod() } auto firstMemberCall = dyn_cast(callExprs.at(1)); if (!firstMemberCall || !isInterestingFirstMethod(firstMemberCall->getMethodDecl())) return false; if (isConvertedToSomethingElse(memberCall)) return false; const string firstMethodName = firstMemberCall->getMethodDecl()->getNameAsString(); std::vector fixits; - if (isFixitEnabled(FixitUseQStringRef)) + if (isFixitEnabled()) fixits = fixit(firstMemberCall); emitWarning(firstMemberCall->getLocEnd(), "Use " + firstMethodName + "Ref() instead", fixits); return true; } // Catches cases like: s.append(s2.mid(1, 1)); bool StringRefCandidates::processCase2(CallExpr *call) { auto memberCall = dyn_cast(call); auto operatorCall = memberCall ? nullptr : dyn_cast(call); CXXMethodDecl *method = nullptr; if (memberCall) { method = memberCall->getMethodDecl(); } else if (operatorCall && operatorCall->getCalleeDecl()) { Decl *decl = operatorCall->getCalleeDecl(); method = dyn_cast(decl); } if (!isMethodReceivingQStringRef(method)) return false; Expr *firstArgument = call->getNumArgs() > 0 ? call->getArg(0) : nullptr; MaterializeTemporaryExpr *temp = firstArgument ? dyn_cast(firstArgument) : nullptr; if (!temp) { Expr *secondArgument = call->getNumArgs() > 1 ? call->getArg(1) : nullptr; temp = secondArgument ? dyn_cast(secondArgument) : nullptr; if (!temp) // For the CXXOperatorCallExpr it's in the second argument return false; } CallExpr *innerCall = clazy::getFirstChildOfType2(temp); auto innerMemberCall = innerCall ? dyn_cast(innerCall) : nullptr; if (!innerMemberCall) return false; CXXMethodDecl *innerMethod = innerMemberCall->getMethodDecl(); if (!isInterestingFirstMethod(innerMethod)) return false; std::vector fixits; - if (isFixitEnabled(FixitUseQStringRef)) { + if (isFixitEnabled()) { fixits = fixit(innerMemberCall); } emitWarning(call->getLocStart(), "Use " + innerMethod->getNameAsString() + "Ref() instead", fixits); return true; } std::vector StringRefCandidates::fixit(CXXMemberCallExpr *call) { MemberExpr *memberExpr = clazy::getFirstChildOfType(call); if (!memberExpr) { - queueManualFixitWarning(call->getLocStart(), FixitUseQStringRef, "Internal error 1"); + queueManualFixitWarning(call->getLocStart(), "Internal error 1"); return {}; } auto insertionLoc = Lexer::getLocForEndOfToken(memberExpr->getLocEnd(), 0, sm(), lo()); // llvm::errs() << insertionLoc.printToString(sm()) << "\n"; if (!insertionLoc.isValid()) { - queueManualFixitWarning(call->getLocStart(), FixitUseQStringRef, "Internal error 2"); + queueManualFixitWarning(call->getLocStart(), "Internal error 2"); return {}; } std::vector fixits; fixits.push_back(clazy::createInsertion(insertionLoc, "Ref")); return fixits; } diff --git a/src/checks/level1/auto-unexpected-qstringbuilder.cpp b/src/checks/level1/auto-unexpected-qstringbuilder.cpp index 9e77d45..7eb90fb 100644 --- a/src/checks/level1/auto-unexpected-qstringbuilder.cpp +++ b/src/checks/level1/auto-unexpected-qstringbuilder.cpp @@ -1,89 +1,83 @@ /* This file is part of the clazy static checker. Copyright (C) 2015 Sergio Martins Copyright (C) 2015 Mathias Hasselmann 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 "auto-unexpected-qstringbuilder.h" #include "Utils.h" #include "StringUtils.h" #include "FixItUtils.h" #include "TypeUtils.h" #include #include using namespace clang; using namespace std; - -enum Fixit { - FixitNone = 0, - FixitUseQString = 0x1, -}; - static bool isQStringBuilder(QualType t) { CXXRecordDecl *record = TypeUtils::typeAsRecord(t); return record && record->getName() == "QStringBuilder"; } AutoUnexpectedQStringBuilder::AutoUnexpectedQStringBuilder(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { } void AutoUnexpectedQStringBuilder::VisitDecl(Decl *decl) { VarDecl *varDecl = dyn_cast(decl); if (!varDecl) return; QualType qualtype = varDecl->getType(); const Type *type = qualtype.getTypePtrOrNull(); if (!type || !type->isRecordType() || !dyn_cast(type) || !isQStringBuilder(qualtype)) return; std::vector fixits; - if (isFixitEnabled(FixitUseQString)) { + if (isFixitEnabled()) { std::string replacement = "QString " + varDecl->getName().str(); if (qualtype.isConstQualified()) replacement = "const " + replacement; SourceLocation start = varDecl->getLocStart(); SourceLocation end = varDecl->getLocation(); fixits.push_back(clazy::createReplacement({ start, end }, replacement)); } emitWarning(decl->getLocStart(), "auto deduced to be QStringBuilder instead of QString. Possible crash.", fixits); } void AutoUnexpectedQStringBuilder::VisitStmt(Stmt *stmt) { auto lambda = dyn_cast(stmt); if (!lambda) return; CXXMethodDecl *method = lambda->getCallOperator(); if (!method || !isQStringBuilder(method->getReturnType())) return; emitWarning(stmt->getLocStart(), "lambda return type deduced to be QStringBuilder instead of QString. Possible crash."); } diff --git a/src/checks/level2/function-args-by-ref.cpp b/src/checks/level2/function-args-by-ref.cpp index 9c7a478..20dc468 100644 --- a/src/checks/level2/function-args-by-ref.cpp +++ b/src/checks/level2/function-args-by-ref.cpp @@ -1,170 +1,165 @@ /* 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 + 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 "FixItUtils.h" #include "TypeUtils.h" #include "ClazyContext.h" #include "StringUtils.h" #include #include using namespace clang; using namespace std; -enum Fixit { - FixitNone = 0, - FixitAll = 0x1 // More granularity isn't needed I guess -}; - 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"); 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(); int i = -1; for (auto param : Utils::functionParameters(func)) { i++; 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); 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 + ')'; } emitWarning(param->getLocStart(), error.c_str(), fixits); } } } void FunctionArgsByRef::VisitDecl(Decl *decl) { processFunction(dyn_cast(decl)); } void FunctionArgsByRef::VisitStmt(Stmt *stmt) { if (auto lambda = dyn_cast(stmt)) { if (!shouldIgnoreFile(stmt->getLocStart())) 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 cd5707b..adc12a9 100644 --- a/src/checks/level2/function-args-by-value.cpp +++ b/src/checks/level2/function-args-by-value.cpp @@ -1,210 +1,205 @@ /* 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 #include using namespace clang; using namespace std; -enum Fixit { - FixitNone = 0, - FixitAll = 0x1 // More granularity isn't needed I guess -}; - // 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 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); 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) && isFixitEnabled(FixitAll)) { // Don't try to fix virtual methods, as build can fail + if ((!isVirtualMethod || warnForOverriddenMethods) && isFixitEnabled()) { // 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(param->getLocStart(), 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(param->getName()); SourceLocation startLoc = param->getLocStart(); SourceLocation endLoc = param->getLocEnd(); 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 = param->getDefaultArg()->getLocStart().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); } diff --git a/src/checks/level2/old-style-connect.cpp b/src/checks/level2/old-style-connect.cpp index 1995296..9633fdc 100644 --- a/src/checks/level2/old-style-connect.cpp +++ b/src/checks/level2/old-style-connect.cpp @@ -1,432 +1,426 @@ /* 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 "old-style-connect.h" #include "Utils.h" #include "StringUtils.h" #include "FixItUtils.h" #include "ContextUtils.h" #include "QtUtils.h" #include "ClazyContext.h" #include "AccessSpecifierManager.h" #include #include #include #include #include #include using namespace clang; using namespace std; - -enum Fixit { - FixitNone = 0, - FixItConnects = 1 -}; - enum ConnectFlag { ConnectFlag_None = 0, // Not a disconnect or connect ConnectFlag_Connect = 1, // It's a connect ConnectFlag_Disconnect = 2, // It's a disconnect ConnectFlag_QTimerSingleShot = 4, ConnectFlag_OldStyle = 8, // Qt4 style ConnectFlag_4ArgsDisconnect = 16 , // disconnect(const char *signal = 0, const QObject *receiver = 0, const char *method = 0) const ConnectFlag_2ArgsDisconnect = 32, //disconnect(const QObject *receiver, const char *method = 0) const ConnectFlag_5ArgsConnect = 64, // connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection) ConnectFlag_4ArgsConnect = 128, // connect(const QObject *sender, const char *signal, const char *method, Qt::ConnectionType type = Qt::AutoConnection) ConnectFlag_OldStyleButNonLiteral = 256, // connect(foo, SIGNAL(bar()), foo, variableWithSlotName); // here the slot name isn't a literal ConnectFlag_QStateAddTransition = 512, ConnectFlag_Bogus = 1024 }; static bool classIsOk(StringRef className) { // List of classes we usually use Qt4 syntax return className != "QDBusInterface"; } OldStyleConnect::OldStyleConnect(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { enablePreProcessorCallbacks(); context->enableAccessSpecifierManager(); } int OldStyleConnect::classifyConnect(FunctionDecl *connectFunc, CallExpr *connectCall) { int classification = ConnectFlag_None; const string methodName = connectFunc->getQualifiedNameAsString(); if (methodName == "QObject::connect") classification |= ConnectFlag_Connect; else if (methodName == "QObject::disconnect") classification |= ConnectFlag_Disconnect; else if (methodName == "QTimer::singleShot") classification |= ConnectFlag_QTimerSingleShot; else if (methodName == "QState::addTransition") classification |= ConnectFlag_QStateAddTransition; if (classification == ConnectFlag_None) return classification; if (clazy::connectHasPMFStyle(connectFunc)) return classification; else classification |= ConnectFlag_OldStyle; const int numParams = connectFunc->getNumParams(); if (classification & ConnectFlag_Connect) { if (numParams == 5) { classification |= ConnectFlag_5ArgsConnect; } else if (numParams == 4) { classification |= ConnectFlag_4ArgsConnect; } else { classification |= ConnectFlag_Bogus; } } else if (classification & ConnectFlag_Disconnect) { if (numParams == 4) { classification |= ConnectFlag_4ArgsDisconnect; } else if (numParams == 2) { classification |= ConnectFlag_2ArgsDisconnect; } else { classification |= ConnectFlag_Bogus; } } if (classification & ConnectFlag_OldStyle) { // It's old style, but check if all macros are literals int numLiterals = 0; for (auto arg : connectCall->arguments()) { auto argLocation = arg->getLocStart(); string dummy; if (isSignalOrSlot(argLocation, dummy)) ++numLiterals; } if ((classification & ConnectFlag_QTimerSingleShot) && numLiterals != 1) { classification |= ConnectFlag_OldStyleButNonLiteral; } else if (((classification & ConnectFlag_Connect) && numLiterals != 2)) { classification |= ConnectFlag_OldStyleButNonLiteral; } else if ((classification & ConnectFlag_4ArgsDisconnect) && numLiterals != 2) { classification |= ConnectFlag_OldStyleButNonLiteral; } else if ((classification & ConnectFlag_QStateAddTransition) && numLiterals != 1) { classification |= ConnectFlag_OldStyleButNonLiteral; } else if ((classification & ConnectFlag_Disconnect) && numLiterals == 0) { classification |= ConnectFlag_OldStyleButNonLiteral; } } return classification; } bool OldStyleConnect::isQPointer(Expr *expr) const { vector memberCalls; clazy::getChilds(expr, memberCalls); for (auto callExpr : memberCalls) { if (!callExpr->getDirectCallee()) continue; auto method = dyn_cast(callExpr->getDirectCallee()); if (!method) continue; // Any better way to detect it's an operator ? if (clazy::startsWith(method->getNameAsString(), "operator ")) return true; } return false; } bool OldStyleConnect::isPrivateSlot(const string &name) const { return clazy::any_of(m_privateSlots, [name](const PrivateSlot &slot) { return slot.name == name; }); } void OldStyleConnect::VisitStmt(Stmt *s) { auto call = dyn_cast(s); if (!call) return; if (m_context->lastMethodDecl && m_context->isQtDeveloper() && m_context->lastMethodDecl->getParent() && clazy::name(m_context->lastMethodDecl->getParent()) == "QObject") // Don't warn of stuff inside qobject.h return; FunctionDecl *function = call->getDirectCallee(); if (!function) return; auto method = dyn_cast(function); if (!method) return; const int classification = classifyConnect(method, call); if (!(classification & ConnectFlag_OldStyle)) return; if ((classification & ConnectFlag_OldStyleButNonLiteral)) return; if (classification & ConnectFlag_Bogus) { emitWarning(s->getLocStart(), "Internal error"); return; } emitWarning(s->getLocStart(), "Old Style Connect", fixits(classification, call)); } void OldStyleConnect::addPrivateSlot(const PrivateSlot &slot) { m_privateSlots.push_back(slot); } void OldStyleConnect::VisitMacroExpands(const Token ¯oNameTok, const SourceRange &range, const MacroInfo *) { IdentifierInfo *ii = macroNameTok.getIdentifierInfo(); if (!ii || ii->getName() != "Q_PRIVATE_SLOT") return; auto charRange = Lexer::getAsCharRange(range, sm(), lo()); const string text = Lexer::getSourceText(charRange, sm(), lo()); static regex rx(R"(Q_PRIVATE_SLOT\s*\((.*)\s*,\s*.*\s+(.*)\(.*)"); smatch match; if (!regex_match(text, match, rx) || match.size() != 3) return; addPrivateSlot({match[1], match[2]}); } // SIGNAL(foo()) -> foo string OldStyleConnect::signalOrSlotNameFromMacro(SourceLocation macroLoc) { if (!macroLoc.isMacroID()) return "error"; auto expansionRange = sm().getImmediateExpansionRange(macroLoc); SourceRange range = SourceRange(expansionRange.first, expansionRange.second); auto charRange = Lexer::getAsCharRange(range, sm(), lo()); const string text = Lexer::getSourceText(charRange, sm(), lo()); static regex rx(R"(\s*(SIGNAL|SLOT)\s*\(\s*(.+)\s*\(.*)"); smatch match; if (regex_match(text, match, rx)) { if (match.size() == 3) { return match[2].str(); } else { return "error2"; } } else { return string("regexp failed for ") + text; } } bool OldStyleConnect::isSignalOrSlot(SourceLocation loc, string ¯oName) const { macroName.clear(); if (!loc.isMacroID() || loc.isInvalid()) return false; macroName = Lexer::getImmediateMacroName(loc, sm(), lo()); return macroName == "SIGNAL" || macroName == "SLOT"; } vector OldStyleConnect::fixits(int classification, CallExpr *call) { - if (!isFixitEnabled(FixItConnects)) + if (!isFixitEnabled()) return {}; if (!call) { llvm::errs() << "Call is invalid\n"; return {}; } if (classification & ConnectFlag_2ArgsDisconnect) { // Not implemented yet string msg = "Fix it not implemented for disconnect with 2 args"; - queueManualFixitWarning(call->getLocStart(), FixItConnects, msg); + queueManualFixitWarning(call->getLocStart(), msg); return {}; } vector fixits; int macroNum = 0; string implicitCallee; string macroName; CXXMethodDecl *senderMethod = nullptr; for (auto arg : call->arguments()) { SourceLocation s = arg->getLocStart(); static const CXXRecordDecl *lastRecordDecl = nullptr; if (isSignalOrSlot(s, macroName)) { macroNum++; if (!lastRecordDecl && (classification & ConnectFlag_4ArgsConnect)) { // This means it's a connect with implicit receiver lastRecordDecl = Utils::recordForMemberCall(dyn_cast(call), implicitCallee); if (macroNum == 1) llvm::errs() << "This first macro shouldn't enter this path"; if (!lastRecordDecl) { string msg = "Failed to get class name for implicit receiver"; - queueManualFixitWarning(s, FixItConnects, msg); + queueManualFixitWarning(s, msg); return {}; } } if (!lastRecordDecl) { string msg = "Failed to get class name for explicit receiver"; - queueManualFixitWarning(s, FixItConnects, msg); + queueManualFixitWarning(s, msg); return {}; } const string methodName = signalOrSlotNameFromMacro(s); auto methods = Utils::methodsFromString(lastRecordDecl, methodName); if (methods.empty()) { string msg; if (isPrivateSlot(methodName)) { msg = "Converting Q_PRIVATE_SLOTS not implemented yet\n"; } else { if (m_context->isQtDeveloper() && classIsOk(clazy::name(lastRecordDecl))) { // This is OK return {}; } else { msg = "No such method " + methodName + " in class " + lastRecordDecl->getNameAsString(); } } - queueManualFixitWarning(s, FixItConnects, msg); + queueManualFixitWarning(s, msg); return {}; } else if (methods.size() != 1) { string msg = string("Too many overloads (") + to_string(methods.size()) + string(") for method ") + methodName + " for record " + lastRecordDecl->getNameAsString(); - queueManualFixitWarning(s, FixItConnects, msg); + queueManualFixitWarning(s, msg); return {}; } else { AccessSpecifierManager *a = m_context->accessSpecifierManager; if (!a) return {}; const bool isSignal = a->qtAccessSpecifierType(methods[0]) == QtAccessSpecifier_Signal; if (isSignal && macroName == "SLOT") { // The method is actually a signal and the user used SLOT() // bail out with the fixing. string msg = string("Can't fix. SLOT macro used but method " + methodName + " is a signal"); - queueManualFixitWarning(s, FixItConnects, msg); + queueManualFixitWarning(s, msg); return {}; } } auto methodDecl = methods[0]; if (methodDecl->isStatic()) return {}; if (macroNum == 1) { // Save the number of parameters of the signal. The slot should not have more arguments. senderMethod = methodDecl; } else if (macroNum == 2) { const unsigned int numReceiverParams = methodDecl->getNumParams(); if (numReceiverParams > senderMethod->getNumParams()) { string msg = string("Receiver has more parameters (") + to_string(methodDecl->getNumParams()) + ") than signal (" + to_string(senderMethod->getNumParams()) + ')'; - queueManualFixitWarning(s, FixItConnects, msg); + queueManualFixitWarning(s, msg); return {}; } for (unsigned int i = 0; i < numReceiverParams; ++i) { ParmVarDecl *receiverParm = methodDecl->getParamDecl(i); ParmVarDecl *senderParm = senderMethod->getParamDecl(i); if (!clazy::isConvertibleTo(senderParm->getType().getTypePtr(), receiverParm->getType().getTypePtrOrNull())) { string msg = string("Sender's parameters are incompatible with the receiver's"); - queueManualFixitWarning(s, FixItConnects, msg); + queueManualFixitWarning(s, msg); return {}; } } } if ((classification & ConnectFlag_QTimerSingleShot) && methodDecl->getNumParams() > 0) { string msg = "(QTimer) Fixit not implemented for slot with arguments, use a lambda"; - queueManualFixitWarning(s, FixItConnects, msg); + queueManualFixitWarning(s, msg); return {}; } DeclContext *context = m_context->lastDecl->getDeclContext(); bool isSpecialProtectedCase = false; if (!clazy::canTakeAddressOf(methodDecl, context, /*by-ref*/isSpecialProtectedCase)) { string msg = "Can't fix " + clazy::accessString(methodDecl->getAccess()) + ' ' + macroName + ' ' + methodDecl->getQualifiedNameAsString(); - queueManualFixitWarning(s, FixItConnects, msg); + queueManualFixitWarning(s, msg); return {}; } string qualifiedName; auto contextRecord = clazy::firstContextOfType(m_context->lastDecl->getDeclContext()); const bool isInInclude = sm().getMainFileID() != sm().getFileID(call->getLocStart()); if (isSpecialProtectedCase && contextRecord) { // We're inside a derived class trying to take address of a protected base member, must use &Derived::method instead of &Base::method. qualifiedName = contextRecord->getNameAsString() + "::" + methodDecl->getNameAsString() ; } else { qualifiedName = clazy::getMostNeededQualifiedName(sm(), methodDecl, context, call->getLocStart(), !isInInclude); // (In includes ignore using directives) } auto expansionRange = sm().getImmediateExpansionRange(s); SourceRange range = SourceRange(expansionRange.first, expansionRange.second); const string functionPointer = '&' + qualifiedName; string replacement = functionPointer; if ((classification & ConnectFlag_4ArgsConnect) && macroNum == 2) replacement = implicitCallee + ", " + replacement; fixits.push_back(FixItHint::CreateReplacement(range, replacement)); lastRecordDecl = nullptr; } else { Expr *expr = arg; const auto record = expr ? expr->getBestDynamicClassType() : nullptr; if (record) { lastRecordDecl = record; if (isQPointer(expr)) { auto endLoc = clazy::locForNextToken(&m_astContext, arg->getLocStart(), tok::comma); if (endLoc.isValid()) { fixits.push_back(FixItHint::CreateInsertion(endLoc, ".data()")); } else { - queueManualFixitWarning(s, FixItConnects, "Can't fix this QPointer case"); + queueManualFixitWarning(s, "Can't fix this QPointer case"); return {}; } } } } } return fixits; } diff --git a/src/checks/level2/qstring-allocations.cpp b/src/checks/level2/qstring-allocations.cpp index 819a7ef..793f007 100644 --- a/src/checks/level2/qstring-allocations.cpp +++ b/src/checks/level2/qstring-allocations.cpp @@ -1,583 +1,583 @@ /* 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 "qstring-allocations.h" #include "ClazyContext.h" #include "Utils.h" #include "clazy_stl.h" #include "StringUtils.h" #include "FixItUtils.h" #include "FunctionUtils.h" #include "QtUtils.h" #include #include #include #include #include #include #include #include /// WARNING /// /// This code is a bit unreadable and unmaintanable due to the fact that there are more corner-cases than normal cases. /// It will be rewritten in a new check, so don't bother. using namespace clang; using namespace std; enum Fixit { FixitNone = 0, QLatin1StringAllocations = 0x1, FromLatin1_FromUtf8Allocations = 0x2, CharPtrAllocations = 0x4, }; struct Latin1Expr { CXXConstructExpr *qlatin1ctorexpr; bool enableFixit; bool isValid() const { return qlatin1ctorexpr != nullptr; } }; QStringAllocations::QStringAllocations(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { } void QStringAllocations::VisitStmt(clang::Stmt *stm) { if (m_context->isQtDeveloper() && clazy::isBootstrapping(m_context->ci.getPreprocessorOpts())) { // During bootstrap many QString::fromLatin1() are used instead of tr(), which causes // much noise return; } VisitCtor(stm); VisitOperatorCall(stm); VisitFromLatin1OrUtf8(stm); VisitAssignOperatorQLatin1String(stm); } static bool betterTakeQLatin1String(CXXMethodDecl *method, StringLiteral *lt) { static const vector methods = {"append", "compare", "endsWith", "startsWith", "insert", "lastIndexOf", "prepend", "replace", "contains", "indexOf" }; if (!clazy::isOfClass(method, "QString")) return false; return (!lt || Utils::isAscii(lt)) && clazy::contains(methods, clazy::name(method)); } // Returns the first occurrence of a QLatin1String(char*) CTOR call Latin1Expr QStringAllocations::qlatin1CtorExpr(Stmt *stm, ConditionalOperator * &ternary) { if (!stm) return {}; auto constructExpr = dyn_cast(stm); if (constructExpr) { CXXConstructorDecl *ctor = constructExpr->getConstructor(); const int numArgs = ctor->getNumParams(); if (clazy::isOfClass(ctor, "QLatin1String")) { if (Utils::containsStringLiteral(constructExpr, /*allowEmpty=*/ false, 2)) return {constructExpr, /*enableFixits=*/ numArgs == 1}; if (Utils::userDefinedLiteral(constructExpr, "QLatin1String", lo())) return {constructExpr, /*enableFixits=*/ false}; } } if (!ternary) ternary = dyn_cast(stm); for (auto child : stm->children()) { auto expr = qlatin1CtorExpr(child, ternary); if (expr.isValid()) return expr; } return {}; } // Returns true if there's a literal in the hierarchy, but aborts if it's parented on CallExpr // so, returns true for: QLatin1String("foo") but false for QLatin1String(indirection("foo")); // static bool containsStringLiteralNoCallExpr(Stmt *stmt) { if (!stmt) return false; StringLiteral *sl = dyn_cast(stmt); if (sl) return true; for (auto child : stmt->children()) { if (!child) continue; CallExpr *callExpr = dyn_cast(child); if (!callExpr && containsStringLiteralNoCallExpr(child)) return true; } return false; } // For QString::fromLatin1("foo") returns "foo" static StringLiteral* stringLiteralForCall(Stmt *call) { if (!call) return nullptr; vector literals; clazy::getChilds(call, literals, 2); return literals.empty() ? nullptr : literals[0]; } void QStringAllocations::VisitCtor(Stmt *stm) { CXXConstructExpr *ctorExpr = dyn_cast(stm); if (!Utils::containsStringLiteral(ctorExpr, /**allowEmpty=*/ true)) return; CXXConstructorDecl *ctorDecl = ctorExpr->getConstructor(); if (!clazy::isOfClass(ctorDecl, "QString")) return; if (Utils::insideCTORCall(m_context->parentMap, stm, { "QRegExp", "QIcon" })) { // https://blogs.kde.org/2015/11/05/qregexp-qstringliteral-crash-exit return; } if (!isOptionSet("no-msvc-compat")) { InitListExpr *initializerList = clazy::getFirstParentOfType(m_context->parentMap, ctorExpr); if (initializerList != nullptr) return; // Nothing to do here, MSVC doesn't like it StringLiteral *lt = stringLiteralForCall(stm); if (lt && lt->getNumConcatenated() > 1) { return; // Nothing to do here, MSVC doesn't like it } } bool isQLatin1String = false; string paramType; if (clazy::hasCharPtrArgument(ctorDecl, 1)) { paramType = "const char*"; } else if (ctorDecl->param_size() == 1 && clazy::hasArgumentOfType(ctorDecl, "QLatin1String", lo())) { paramType = "QLatin1String"; isQLatin1String = true; } else { return; } string msg = string("QString(") + paramType + string(") being called"); if (isQLatin1String) { ConditionalOperator *ternary = nullptr; Latin1Expr qlatin1expr = qlatin1CtorExpr(stm, ternary); if (!qlatin1expr.isValid()) { return; } auto qlatin1Ctor = qlatin1expr.qlatin1ctorexpr; if (qlatin1Ctor->getLocStart().isMacroID()) { auto macroName = Lexer::getImmediateMacroName(qlatin1Ctor->getLocStart(), sm(), lo()); if (macroName == "Q_GLOBAL_STATIC_WITH_ARGS") // bug #391807 return; } vector fixits; if (qlatin1expr.enableFixit && isFixitEnabled(QLatin1StringAllocations)) { if (!qlatin1Ctor->getLocStart().isMacroID()) { if (!ternary) { fixits = fixItReplaceWordWithWord(qlatin1Ctor, "QStringLiteral", "QLatin1String", QLatin1StringAllocations); bool shouldRemoveQString = qlatin1Ctor->getLocStart().getRawEncoding() != stm->getLocStart().getRawEncoding() && dyn_cast_or_null(clazy::parent(m_context->parentMap, ctorExpr)); if (shouldRemoveQString) { // This is the case of QString(QLatin1String("foo")), which we just fixed to be QString(QStringLiteral("foo)), so now remove QString auto removalFixits = clazy::fixItRemoveToken(&m_astContext, ctorExpr, true); if (removalFixits.empty()) { - queueManualFixitWarning(ctorExpr->getLocStart(), QLatin1StringAllocations, "Internal error: invalid start or end location"); + queueManualFixitWarning(ctorExpr->getLocStart(), "Internal error: invalid start or end location", QLatin1StringAllocations); } else { clazy::append(removalFixits, fixits); } } } else { fixits = fixItReplaceWordWithWordInTernary(ternary); } } else { - queueManualFixitWarning(qlatin1Ctor->getLocStart(), QLatin1StringAllocations, "Can't use QStringLiteral in macro"); + queueManualFixitWarning(qlatin1Ctor->getLocStart(), "Can't use QStringLiteral in macro", QLatin1StringAllocations); } } emitWarning(stm->getLocStart(), msg, fixits); } else { vector fixits; if (clazy::hasChildren(ctorExpr)) { auto pointerDecay = dyn_cast(*(ctorExpr->child_begin())); if (clazy::hasChildren(pointerDecay)) { StringLiteral *lt = dyn_cast(*pointerDecay->child_begin()); if (lt && isFixitEnabled(CharPtrAllocations)) { Stmt *grandParent = clazy::parent(m_context->parentMap, lt, 2); Stmt *grandGrandParent = clazy::parent(m_context->parentMap, lt, 3); Stmt *grandGrandGrandParent = clazy::parent(m_context->parentMap, lt, 4); if (grandParent == ctorExpr && grandGrandParent && isa(grandGrandParent) && grandGrandGrandParent && isa(grandGrandGrandParent)) { // This is the case of QString("foo"), replace QString const bool literalIsEmpty = lt->getLength() == 0; if (literalIsEmpty && clazy::getFirstParentOfType(m_context->parentMap, ctorExpr) == nullptr) fixits = fixItReplaceWordWithWord(ctorExpr, "QLatin1String", "QString", CharPtrAllocations); else if (!ctorExpr->getLocStart().isMacroID()) fixits = fixItReplaceWordWithWord(ctorExpr, "QStringLiteral", "QString", CharPtrAllocations); else - queueManualFixitWarning(ctorExpr->getLocStart(), CharPtrAllocations, "Can't use QStringLiteral in macro."); + queueManualFixitWarning(ctorExpr->getLocStart(), "Can't use QStringLiteral in macro.", CharPtrAllocations); } else { auto parentMemberCallExpr = clazy::getFirstParentOfType(m_context->parentMap, lt, /*maxDepth=*/6); // 6 seems like a nice max from the ASTs I've seen string replacement = "QStringLiteral"; if (parentMemberCallExpr) { FunctionDecl *fDecl = parentMemberCallExpr->getDirectCallee(); if (fDecl) { auto method = dyn_cast(fDecl); if (method && betterTakeQLatin1String(method, lt)) { replacement = "QLatin1String"; } } } fixits = fixItRawLiteral(lt, replacement); } } } } emitWarning(stm->getLocStart(), msg, fixits); } } vector QStringAllocations::fixItReplaceWordWithWord(clang::Stmt *begin, const string &replacement, const string &replacee, int fixitType) { StringLiteral *lt = stringLiteralForCall(begin); if (replacee == "QLatin1String") { if (lt && !Utils::isAscii(lt)) { emitWarning(lt->getLocStart(), "Don't use QLatin1String with non-latin1 literals"); return {}; } } if (Utils::literalContainsEscapedBytes(lt, sm(), lo())) return {}; vector fixits; FixItHint fixit = clazy::fixItReplaceWordWithWord(&m_astContext, begin, replacement, replacee); if (fixit.isNull()) { - queueManualFixitWarning(begin->getLocStart(), fixitType); + queueManualFixitWarning(begin->getLocStart(), "", fixitType); } else { fixits.push_back(fixit); } return fixits; } vector QStringAllocations::fixItReplaceWordWithWordInTernary(clang::ConditionalOperator *ternary) { vector constructExprs; clazy::getChilds(ternary, constructExprs, 1); // depth = 1, only the two immediate expressions vector fixits; fixits.reserve(2); if (constructExprs.size() != 2) { llvm::errs() << "Weird ternary operator with " << constructExprs.size() << " at " << ternary->getLocStart().printToString(sm()) << "\n"; assert(false); return fixits; } for (int i = 0; i < 2; ++i) { SourceLocation rangeStart = constructExprs[i]->getLocStart(); SourceLocation rangeEnd = Lexer::getLocForEndOfToken(rangeStart, -1, sm(), lo()); fixits.push_back(FixItHint::CreateReplacement(SourceRange(rangeStart, rangeEnd), "QStringLiteral")); } return fixits; } // true for: QString::fromLatin1().arg() // false for: QString::fromLatin1("") // true for: QString s = QString::fromLatin1("foo") // false for: s += QString::fromLatin1("foo"), etc. static bool isQStringLiteralCandidate(Stmt *s, ParentMap *map, const LangOptions &lo, const SourceManager &sm , int currentCall = 0) { if (!s) return false; MemberExpr *memberExpr = dyn_cast(s); if (memberExpr) return true; auto constructExpr = dyn_cast(s); if (clazy::isOfClass(constructExpr, "QString")) return true; if (Utils::isAssignOperator(dyn_cast(s), "QString", "QLatin1String", lo)) return true; if (Utils::isAssignOperator(dyn_cast(s), "QString", "QString", lo)) return true; CallExpr *callExpr = dyn_cast(s); StringLiteral *literal = stringLiteralForCall(callExpr); auto operatorCall = dyn_cast(s); if (operatorCall && clazy::returnTypeName(operatorCall, lo) != "QTestData") { // QTest::newRow will static_assert when using QLatin1String // Q_STATIC_ASSERT_X(QMetaTypeId2::Defined, "Type is not registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt's meta-object system"); string className = clazy::classNameFor(operatorCall); if (className == "QString") { return false; } else if (className.empty() && clazy::hasArgumentOfType(operatorCall->getDirectCallee(), "QString", lo)) { return false; } } if (currentCall > 0 && callExpr) { auto fDecl = callExpr->getDirectCallee(); if (fDecl && betterTakeQLatin1String(dyn_cast(fDecl), literal)) return false; return true; } if (currentCall == 0 || dyn_cast(s) || dyn_cast(s) || dyn_cast(s)) // skip this cruft return isQStringLiteralCandidate(clazy::parent(map, s), map, lo, sm, currentCall + 1); return false; } std::vector QStringAllocations::fixItReplaceFromLatin1OrFromUtf8(CallExpr *callExpr, FromFunction fromFunction) { vector fixits; std::string replacement = isQStringLiteralCandidate(callExpr, m_context->parentMap, lo(), sm()) ? "QStringLiteral" - : "QLatin1String"; + : "QLatin1String"; if (replacement == "QStringLiteral" && callExpr->getLocStart().isMacroID()) { - queueManualFixitWarning(callExpr->getLocStart(), FromLatin1_FromUtf8Allocations, "Can't use QStringLiteral in macro!"); + queueManualFixitWarning(callExpr->getLocStart(), "Can't use QStringLiteral in macro!", FromLatin1_FromUtf8Allocations); return {}; } StringLiteral *literal = stringLiteralForCall(callExpr); if (literal) { if (Utils::literalContainsEscapedBytes(literal, sm(), lo())) return {}; if (!Utils::isAscii(literal)) { // QString::fromLatin1() to QLatin1String() is fine // QString::fromUtf8() to QStringLiteral() is fine // all other combinations are not if (replacement == "QStringLiteral" && fromFunction == FromLatin1) { return {}; } else if (replacement == "QLatin1String" && fromFunction == FromUtf8) { replacement = "QStringLiteral"; } } auto classNameLoc = Lexer::getLocForEndOfToken(callExpr->getLocStart(), 0, sm(), lo()); auto scopeOperatorLoc = Lexer::getLocForEndOfToken(classNameLoc, 0, sm(), lo()); auto methodNameLoc = Lexer::getLocForEndOfToken(scopeOperatorLoc, -1, sm(), lo()); SourceRange range(callExpr->getLocStart(), methodNameLoc); fixits.push_back(FixItHint::CreateReplacement(range, replacement)); } else { - queueManualFixitWarning(callExpr->getLocStart(), FromLatin1_FromUtf8Allocations, "Internal error: literal is null"); + queueManualFixitWarning(callExpr->getLocStart(), "Internal error: literal is null", FromLatin1_FromUtf8Allocations); } return fixits; } std::vector QStringAllocations::fixItRawLiteral(clang::StringLiteral *lt, const string &replacement) { vector fixits; SourceRange range = clazy::rangeForLiteral(&m_astContext, lt); if (range.isInvalid()) { if (lt) { - queueManualFixitWarning(lt->getLocStart(), CharPtrAllocations, "Internal error: Can't calculate source location"); + queueManualFixitWarning(lt->getLocStart(), "Internal error: Can't calculate source location", CharPtrAllocations); } return {}; } SourceLocation start = lt->getLocStart(); if (start.isMacroID()) { - queueManualFixitWarning(start, CharPtrAllocations, "Can't use QStringLiteral in macro.."); + queueManualFixitWarning(start, "Can't use QStringLiteral in macro", CharPtrAllocations); } else { if (Utils::literalContainsEscapedBytes(lt, sm(), lo())) return {}; string revisedReplacement = lt->getLength() == 0 ? "QLatin1String" : replacement; // QLatin1String("") is better than QStringLiteral("") if (revisedReplacement == "QStringLiteral" && lt->getLocStart().isMacroID()) { - queueManualFixitWarning(lt->getLocStart(), CharPtrAllocations, "Can't use QStringLiteral in macro..."); + queueManualFixitWarning(lt->getLocStart(), "Can't use QStringLiteral in macro...", CharPtrAllocations); return {}; } clazy::insertParentMethodCall(revisedReplacement, range, /**by-ref*/fixits); } return fixits; } void QStringAllocations::VisitOperatorCall(Stmt *stm) { CXXOperatorCallExpr *operatorCall = dyn_cast(stm); if (!operatorCall) return; if (clazy::returnTypeName(operatorCall, lo()) == "QTestData") { // QTest::newRow will static_assert when using QLatin1String // Q_STATIC_ASSERT_X(QMetaTypeId2::Defined, "Type is not registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt's meta-object system"); return; } std::vector stringLiterals; clazy::getChilds(operatorCall, stringLiterals); // We're only after string literals, str.contains(some_method_returning_const_char_is_fine()) if (stringLiterals.empty()) return; FunctionDecl *funcDecl = operatorCall->getDirectCallee(); if (!funcDecl) return; CXXMethodDecl *methodDecl = dyn_cast(funcDecl); if (!clazy::isOfClass(methodDecl, "QString")) return; if (!clazy::hasCharPtrArgument(methodDecl)) return; vector fixits; vector literals; clazy::getChilds(stm, literals, 2); if (!isOptionSet("no-msvc-compat") && !literals.empty()) { if (literals[0]->getNumConcatenated() > 1) { return; // Nothing to do here, MSVC doesn't like it } } if (isFixitEnabled(CharPtrAllocations)) { if (literals.empty()) { - queueManualFixitWarning(stm->getLocStart(), CharPtrAllocations, "Couldn't find literal"); + queueManualFixitWarning(stm->getLocStart(), "Couldn't find literal", CharPtrAllocations); } else { const string replacement = Utils::isAscii(literals[0]) ? "QLatin1String" : "QStringLiteral"; fixits = fixItRawLiteral(literals[0], replacement); } } string msg = string("QString(const char*) being called"); emitWarning(stm->getLocStart(), msg, fixits); } void QStringAllocations::VisitFromLatin1OrUtf8(Stmt *stmt) { CallExpr *callExpr = dyn_cast(stmt); if (!callExpr) return; FunctionDecl *functionDecl = callExpr->getDirectCallee(); if (!clazy::functionIsOneOf(functionDecl, {"fromLatin1", "fromUtf8"})) return; CXXMethodDecl *methodDecl = dyn_cast(functionDecl); if (!clazy::isOfClass(methodDecl, "QString")) return; if (!Utils::callHasDefaultArguments(callExpr) || !clazy::hasCharPtrArgument(functionDecl, 2)) // QString::fromLatin1("foo", 1) is ok return; if (!containsStringLiteralNoCallExpr(callExpr)) return; if (!isOptionSet("no-msvc-compat")) { StringLiteral *lt = stringLiteralForCall(callExpr); if (lt && lt->getNumConcatenated() > 1) { return; // Nothing to do here, MSVC doesn't like it } } vector ternaries; clazy::getChilds(callExpr, ternaries, 2); if (!ternaries.empty()) { auto ternary = ternaries[0]; if (Utils::ternaryOperatorIsOfStringLiteral(ternary)) { emitWarning(stmt->getLocStart(), string("QString::fromLatin1() being passed a literal")); } return; } std::vector fixits; if (isFixitEnabled(FromLatin1_FromUtf8Allocations)) { const FromFunction fromFunction = clazy::name(functionDecl) == "fromLatin1" ? FromLatin1 : FromUtf8; fixits = fixItReplaceFromLatin1OrFromUtf8(callExpr, fromFunction); } if (clazy::name(functionDecl) == "fromLatin1") { emitWarning(stmt->getLocStart(), string("QString::fromLatin1() being passed a literal"), fixits); } else { emitWarning(stmt->getLocStart(), string("QString::fromUtf8() being passed a literal"), fixits); } } void QStringAllocations::VisitAssignOperatorQLatin1String(Stmt *stmt) { CXXOperatorCallExpr *callExpr = dyn_cast(stmt); if (!Utils::isAssignOperator(callExpr, "QString", "QLatin1String", lo())) return; if (!containsStringLiteralNoCallExpr(stmt)) return; ConditionalOperator *ternary = nullptr; Stmt *begin = qlatin1CtorExpr(stmt, ternary).qlatin1ctorexpr; if (!begin) return; vector fixits; if (isFixitEnabled(QLatin1StringAllocations)) { fixits = ternary == nullptr ? fixItReplaceWordWithWord(begin, "QStringLiteral", "QLatin1String", QLatin1StringAllocations) : fixItReplaceWordWithWordInTernary(ternary); } emitWarning(stmt->getLocStart(), string("QString::operator=(QLatin1String(\"literal\")"), fixits); } diff --git a/src/checks/manuallevel/qt-keywords.cpp b/src/checks/manuallevel/qt-keywords.cpp index 2358f8f..1cd9085 100644 --- a/src/checks/manuallevel/qt-keywords.cpp +++ b/src/checks/manuallevel/qt-keywords.cpp @@ -1,80 +1,74 @@ /* This file is part of the clazy static checker. Copyright (C) 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 "qt-keywords.h" #include "Utils.h" #include "HierarchyUtils.h" #include "QtUtils.h" #include "TypeUtils.h" #include "FixItUtils.h" #include "ClazyContext.h" #include "PreProcessorVisitor.h" #include #include using namespace clang; using namespace std; -enum Fixits { - FixitNone = 0, - FixitKeywords = 1 -}; - QtKeywords::QtKeywords(const std::string &name, ClazyContext *context) : CheckBase(name, context) { enablePreProcessorCallbacks(); context->enablePreprocessorVisitor(); } void QtKeywords::VisitMacroExpands(const Token ¯oNameTok, const SourceRange &range, const clang::MacroInfo *minfo) { IdentifierInfo *ii = macroNameTok.getIdentifierInfo(); if (!ii || !minfo) return; if (auto ppvisitor = m_context->preprocessorVisitor) { // Save some CPU cycles. No point in running if QT_NO_KEYWORDS if (ppvisitor->isQT_NO_KEYWORDS()) return; } static const vector keywords = { "foreach", "signals", "slots", "emit" }; std::string name = ii->getName(); if (!clazy::contains(keywords, name)) return; // Make sure the macro is Qt's. It must be defined in Qt's headers, not 3rdparty std::string qtheader = sm().getFilename(sm().getSpellingLoc(minfo->getDefinitionLoc())); if (!clazy::endsWith(qtheader, "qglobal.h") && !clazy::endsWith(qtheader, "qobjectdefs.h")) return; std::vector fixits; - if (isFixitEnabled(FixitKeywords)) { + if (isFixitEnabled()) { std::string replacement = "Q_" + name; std::transform(replacement.begin(), replacement.end(), replacement.begin(), ::toupper); fixits.push_back(clazy::createReplacement(range, replacement)); } emitWarning(range.getBegin(), "Using a Qt keyword (" + string(ii->getName()) + ")", fixits); - } diff --git a/src/checks/manuallevel/qt4-qstring-from-array.cpp b/src/checks/manuallevel/qt4-qstring-from-array.cpp index 050b0d5..9d6e411 100644 --- a/src/checks/manuallevel/qt4-qstring-from-array.cpp +++ b/src/checks/manuallevel/qt4-qstring-from-array.cpp @@ -1,266 +1,261 @@ /* 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 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 "qt4-qstring-from-array.h" #include "ClazyContext.h" #include "Utils.h" #include "StringUtils.h" #include "FixItUtils.h" #include "HierarchyUtils.h" #include #include using namespace clang; using namespace std; -enum FixIt { - FixItNone, - FixItToFromLatin1 -}; - Qt4QStringFromArray::Qt4QStringFromArray(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { } static bool isInterestingParam(ParmVarDecl *param, bool &is_char_array, bool &is_byte_array) { is_char_array = false; is_byte_array = false; const string typeStr = param->getType().getAsString(); if (typeStr == "const class QByteArray &") { is_byte_array = true; } else if (typeStr == "const char *") {// We only want bytearray and const char* is_char_array = true; } return is_char_array || is_byte_array; } static bool isInterestingCtorCall(CXXConstructorDecl *ctor, bool &is_char_array, bool &is_byte_array) { is_char_array = false; is_byte_array = false; if (!ctor || !clazy::isOfClass(ctor, "QString")) return false; for (auto param : Utils::functionParameters(ctor)) { if (isInterestingParam(param, is_char_array, is_byte_array)) break; return false; } return is_char_array || is_byte_array; } static bool isInterestingMethod(const string &methodName) { static const vector methods = { "append", "prepend", "operator=", "operator==", "operator!=", "operator<", "operator<=", "operator>", "operator>=", "operator+=" }; return clazy::contains(methods, methodName); } static bool isInterestingMethodCall(CXXMethodDecl *method, string &methodName, bool &is_char_array, bool &is_byte_array) { is_char_array = false; is_byte_array = false; if (!method) return false; if (clazy::name(method->getParent()) != "QString" || method->getNumParams() != 1) return false; methodName = method->getNameAsString(); if (!isInterestingMethod(methodName)) return false; if (!isInterestingParam(method->getParamDecl(0), is_char_array, is_byte_array)) return false; return true; } static bool isInterestingOperatorCall(CXXOperatorCallExpr *op, string &operatorName, bool &is_char_array, bool &is_byte_array) { is_char_array = false; is_byte_array = false; FunctionDecl *func = op->getDirectCallee(); if (!func) return false; return isInterestingMethodCall(dyn_cast(func), operatorName, is_char_array, is_byte_array); } void Qt4QStringFromArray::VisitStmt(clang::Stmt *stm) { CXXConstructExpr *ctorExpr = dyn_cast(stm); CXXOperatorCallExpr *operatorCall = dyn_cast(stm); CXXMemberCallExpr *memberCall = dyn_cast(stm); if (!ctorExpr && !operatorCall && !memberCall) return; vector fixits; bool is_char_array = false; bool is_byte_array = false; string methodName; string message; if (ctorExpr) { CXXConstructorDecl *ctorDecl = ctorExpr->getConstructor(); if (!isInterestingCtorCall(ctorDecl, is_char_array, is_byte_array)) return; fixits = fixCtorCall(ctorExpr); if (is_char_array) { message = "QString(const char *) ctor being called"; } else { message = "QString(QByteArray) ctor being called"; } } else if (operatorCall) { if (!isInterestingOperatorCall(operatorCall, /*by-ref*/methodName, is_char_array, is_byte_array)) return; fixits = fixOperatorCall(operatorCall); } else if (memberCall) { if (!isInterestingMethodCall(memberCall->getMethodDecl(), /*by-ref*/methodName, is_char_array, is_byte_array)) return; fixits = fixMethodCallCall(memberCall); } else { return; } if (operatorCall || memberCall) { if (is_char_array) { message = "QString::" + methodName + "(const char *) being called"; } else { message = "QString::" + methodName + "(QByteArray) being called"; } } emitWarning(stm->getLocStart(), message, fixits); } std::vector Qt4QStringFromArray::fixCtorCall(CXXConstructExpr *ctorExpr) { Stmt *parent = clazy::parent(m_context->parentMap, ctorExpr); // CXXBindTemporaryExpr Stmt *grandParent = clazy::parent(m_context->parentMap, parent); //CXXFunctionalCastExpr if (parent && grandParent && isa(parent) && isa(grandParent)) { return fixitReplaceWithFromLatin1(ctorExpr); } else { return fixitInsertFromLatin1(ctorExpr); } } std::vector Qt4QStringFromArray::fixOperatorCall(CXXOperatorCallExpr *op) { vector fixits; if (op->getNumArgs() == 2) { Expr *e = op->getArg(1); SourceLocation start = e->getLocStart(); SourceLocation end = Lexer::getLocForEndOfToken(clazy::biggestSourceLocationInStmt(sm(), e), 0, sm(), lo()); SourceRange range = { start, end }; if (range.isInvalid()) { emitWarning(op->getLocStart(), "internal error"); return {}; } clazy::insertParentMethodCall("QString::fromLatin1", {start, end}, /*by-ref*/fixits); } else { emitWarning(op->getLocStart(), "internal error"); } return fixits; } std::vector Qt4QStringFromArray::fixMethodCallCall(clang::CXXMemberCallExpr *memberExpr) { vector fixits; if (memberExpr->getNumArgs() == 1) { Expr *e = *(memberExpr->arg_begin()); SourceLocation start = e->getLocStart(); SourceLocation end = Lexer::getLocForEndOfToken(clazy::biggestSourceLocationInStmt(sm(), e), 0, sm(), lo()); SourceRange range = { start, end }; if (range.isInvalid()) { emitWarning(memberExpr->getLocStart(), "internal error"); return {}; } clazy::insertParentMethodCall("QString::fromLatin1", {start, end}, /*by-ref*/fixits); } else { emitWarning(memberExpr->getLocStart(), "internal error"); } return fixits; } std::vector Qt4QStringFromArray::fixitReplaceWithFromLatin1(CXXConstructExpr *ctorExpr) { const string replacement = "QString::fromLatin1"; const string replacee = "QString"; vector fixits; SourceLocation rangeStart = ctorExpr->getLocStart(); SourceLocation rangeEnd = Lexer::getLocForEndOfToken(rangeStart, -1, sm(), lo()); if (rangeEnd.isInvalid()) { // Fallback. Have seen a case in the wild where the above would fail, it's very rare rangeEnd = rangeStart.getLocWithOffset(replacee.size() - 2); if (rangeEnd.isInvalid()) { clazy::printLocation(sm(), rangeStart); clazy::printLocation(sm(), rangeEnd); clazy::printLocation(sm(), Lexer::getLocForEndOfToken(rangeStart, 0, sm(), lo())); - queueManualFixitWarning(ctorExpr->getLocStart(), FixItToFromLatin1); + queueManualFixitWarning(ctorExpr->getLocStart()); return {}; } } fixits.push_back(FixItHint::CreateReplacement(SourceRange(rangeStart, rangeEnd), replacement)); return fixits; } std::vector Qt4QStringFromArray::fixitInsertFromLatin1(CXXConstructExpr *ctorExpr) { vector fixits; SourceRange range; Expr *arg = *(ctorExpr->arg_begin()); range.setBegin(arg->getLocStart()); range.setEnd(Lexer::getLocForEndOfToken(clazy::biggestSourceLocationInStmt(sm(), ctorExpr), 0, sm(), lo())); if (range.isInvalid()) { emitWarning(ctorExpr->getLocStart(), "Internal error"); return {}; } clazy::insertParentMethodCall("QString::fromLatin1", range, fixits); return fixits; }