diff --git a/src/ClazyContext.cpp b/src/ClazyContext.cpp index 6661bae..b5d9868 100644 --- a/src/ClazyContext.cpp +++ b/src/ClazyContext.cpp @@ -1,142 +1,142 @@ /* This file is part of the clazy static checker. Copyright (C) 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 "AccessSpecifierManager.h" #include "ClazyContext.h" -#include "PreProcessorVisitor.h" #include "FixItExporter.h" +#include "PreProcessorVisitor.h" #include #include #include #include #include #include using namespace std; using namespace clang; class ClazyFixItOptions : public FixItOptions { public: ClazyFixItOptions(const ClazyFixItOptions &other) = delete; ClazyFixItOptions(bool inplace) { if (const char *suffix = getenv("CLAZY_FIXIT_SUFFIX")) m_suffix = suffix; InPlace = inplace && m_suffix.empty(); FixWhatYouCan = true; FixOnlyWarnings = true; Silent = false; } std::string RewriteFilename(const std::string &filename, int &fd) override { fd = -1; return InPlace ? filename : filename + m_suffix; } std::string m_suffix; }; ClazyContext::ClazyContext(const clang::CompilerInstance &compiler, const string &headerFilter, const string &ignoreDirs, ClazyOptions opts) : ci(compiler) , astContext(ci.getASTContext()) , sm(ci.getSourceManager()) , m_noWerror(getenv("CLAZY_NO_WERROR") != nullptr) // Allows user to make clazy ignore -Werror , options(opts) , extraOptions(clazy::splitString(getenv("CLAZY_EXTRA_OPTIONS"), ',')) { if (!headerFilter.empty()) headerFilterRegex = std::unique_ptr(new llvm::Regex(headerFilter)); if (!ignoreDirs.empty()) ignoreDirsRegex = std::unique_ptr(new llvm::Regex(ignoreDirs)); const char *fixitsEnv = getenv("CLAZY_FIXIT"); allFixitsEnabled = (options & ClazyOption_AllFixitsEnabled); if (!allFixitsEnabled && fixitsEnv) { const string fixitsEnvStr = clazy::unquoteString(fixitsEnv); if (fixitsEnvStr == "all_fixits") { allFixitsEnabled = true; } else { requestedFixitName = fixitsEnvStr; } } if (fixitsEnabled() && !(options & ClazyOption_NoFixitsAutoWrite)) rewriter = new FixItRewriter(ci.getDiagnostics(), sm, ci.getLangOpts(), new ClazyFixItOptions(fixitsAreInplace())); exporter = new FixItExporter(ci.getDiagnostics(), sm, ci.getLangOpts(), new ClazyFixItOptions(fixitsAreInplace())); } ClazyContext::~ClazyContext() { //delete preprocessorVisitor; // we don't own it delete accessSpecifierManager; delete parentMap; if (exporter) { exporter->Export(); delete exporter; } if (rewriter) { rewriter->WriteFixedFiles(); delete rewriter; } preprocessorVisitor = nullptr; accessSpecifierManager = nullptr; parentMap = nullptr; } void ClazyContext::enableAccessSpecifierManager() { if (!accessSpecifierManager && !usingPreCompiledHeaders()) accessSpecifierManager = new AccessSpecifierManager(ci); } void ClazyContext::enablePreprocessorVisitor() { if (!preprocessorVisitor && !usingPreCompiledHeaders()) preprocessorVisitor = new PreProcessorVisitor(ci); } bool ClazyContext::isQt() const { static const bool s_isQt = [this] { for (auto s : ci.getPreprocessorOpts().Macros) { if (s.first == "QT_CORE_LIB") return true; } return false; } (); return s_isQt; } diff --git a/src/FixItExporter.cpp b/src/FixItExporter.cpp index cddd456..d16deb9 100644 --- a/src/FixItExporter.cpp +++ b/src/FixItExporter.cpp @@ -1,158 +1,166 @@ /* This file is part of the clazy static checker. - Copyright (C) 2016 Sergio Martins - Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2019 Christian Gagneraud 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 "FixItExporter.h" #include #include #include #include #define DEBUG_FIX_IT_EXPORTER #ifdef DEBUG_FIX_IT_EXPORTER -#include +# include #endif using namespace clang; -FixItExporter::FixItExporter(DiagnosticsEngine &DiagEngine, SourceManager &SourceMgr, const LangOptions &LangOpts, FixItOptions *FixItOpts) - : DiagEngine(DiagEngine), SourceMgr(SourceMgr), LangOpts(LangOpts), - FixItOpts(FixItOpts) { +FixItExporter::FixItExporter(DiagnosticsEngine &DiagEngine, SourceManager &SourceMgr, + const LangOptions &LangOpts, FixItOptions *FixItOpts) + : DiagEngine(DiagEngine), SourceMgr(SourceMgr), LangOpts(LangOpts) + , FixItOpts(FixItOpts) +{ Owner = DiagEngine.takeClient(); Client = DiagEngine.getClient(); DiagEngine.setClient(this, false); } -FixItExporter::~FixItExporter() { +FixItExporter::~FixItExporter() +{ DiagEngine.setClient(Client, Owner.release() != nullptr); } -void FixItExporter::BeginSourceFile(const LangOptions &LangOpts, const Preprocessor *PP) { +void FixItExporter::BeginSourceFile(const LangOptions &LangOpts, const Preprocessor *PP) +{ if (Client) Client->BeginSourceFile(LangOpts, PP); const auto id = SourceMgr.getMainFileID(); const auto entry = SourceMgr.getFileEntryForID(id); TUDiag.MainSourceFile = entry->getName(); } -bool FixItExporter::IncludeInDiagnosticCounts() const { - return Client ? Client->IncludeInDiagnosticCounts() : true; +bool FixItExporter::IncludeInDiagnosticCounts() const +{ + return Client ? Client->IncludeInDiagnosticCounts() : true; } -void FixItExporter::EndSourceFile() { +void FixItExporter::EndSourceFile() +{ if (Client) Client->EndSourceFile(); } tooling::Diagnostic FixItExporter::ConvertDiagnostic(const Diagnostic &Info) { SmallString<100> TmpMessageText; Info.FormatDiagnostic(TmpMessageText); const auto MessageText = TmpMessageText.slice(0, TmpMessageText.find_last_of('[') - 1).str(); const auto CheckName = TmpMessageText.slice(TmpMessageText.find_last_of('[') + 3, - TmpMessageText.find_last_of(']')).str(); + TmpMessageText.find_last_of(']')).str(); llvm::StringRef CurrentBuildDir; // Not needed? tooling::Diagnostic ToolingDiag(CheckName, tooling::Diagnostic::Warning, CurrentBuildDir); ToolingDiag.Message = tooling::DiagnosticMessage(MessageText, SourceMgr, Info.getLocation()); return ToolingDiag; } tooling::Replacement FixItExporter::ConvertFixIt(const FixItHint &Hint) { tooling::Replacement Replacement; if (Hint.CodeToInsert.empty()) { - if (Hint.InsertFromRange.isValid()) { - clang::SourceLocation b(Hint.InsertFromRange.getBegin()), _e(Hint.InsertFromRange.getEnd()); - if (b.isMacroID()) - b = SourceMgr.getSpellingLoc(b); - if (_e.isMacroID()) - _e = SourceMgr.getSpellingLoc(_e); - clang::SourceLocation e(clang::Lexer::getLocForEndOfToken(_e, 0, SourceMgr, LangOpts)); - StringRef Text(SourceMgr.getCharacterData(b), - SourceMgr.getCharacterData(e)-SourceMgr.getCharacterData(b)); - return tooling::Replacement(SourceMgr, Hint.RemoveRange, Text); - } - return tooling::Replacement(SourceMgr, Hint.RemoveRange, ""); + if (Hint.InsertFromRange.isValid()) { + clang::SourceLocation b(Hint.InsertFromRange.getBegin()), _e(Hint.InsertFromRange.getEnd()); + if (b.isMacroID()) + b = SourceMgr.getSpellingLoc(b); + if (_e.isMacroID()) + _e = SourceMgr.getSpellingLoc(_e); + clang::SourceLocation e(clang::Lexer::getLocForEndOfToken(_e, 0, SourceMgr, LangOpts)); + StringRef Text(SourceMgr.getCharacterData(b), + SourceMgr.getCharacterData(e)-SourceMgr.getCharacterData(b)); + return tooling::Replacement(SourceMgr, Hint.RemoveRange, Text); + } + return tooling::Replacement(SourceMgr, Hint.RemoveRange, ""); } return tooling::Replacement(SourceMgr, Hint.RemoveRange, Hint.CodeToInsert); } -void FixItExporter::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) { +void FixItExporter::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) +{ // Default implementation (Warnings/errors count). DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); // Let origianl client do it's handling if (Client) Client->HandleDiagnostic(DiagLevel, Info); // We only deal with warnings if (DiagLevel != DiagnosticsEngine::Warning) return; // Convert and record this diagnostic auto ToolingDiag = ConvertDiagnostic(Info); for (unsigned Idx = 0, Last = Info.getNumFixItHints(); Idx < Last; ++Idx) { const FixItHint &Hint = Info.getFixItHint(Idx); const auto replacement = ConvertFixIt(Hint); #ifdef DEBUG_FIX_IT_EXPORTER const auto FileName = SourceMgr.getFilename(Info.getLocation()); std::cerr << "Handling Fixit #" << Idx << " for " << FileName.str() << std::endl; std::cerr << "F: " << Hint.RemoveRange.getBegin().printToString(SourceMgr) << ":" << Hint.RemoveRange.getEnd().printToString(SourceMgr) << " " << Hint.InsertFromRange.getBegin().printToString(SourceMgr) << ":" << Hint.InsertFromRange.getEnd().printToString(SourceMgr) << " " << Hint.BeforePreviousInsertions << " " << Hint.CodeToInsert << std::endl; std::cerr << "R: " << replacement.toString() << std::endl; #endif auto &Replacements = ToolingDiag.Fix[replacement.getFilePath()]; auto error = Replacements.add(ConvertFixIt(Hint)); if (error) { Diag(Info.getLocation(), diag::note_fixit_failed); } } TUDiag.Diagnostics.push_back(ToolingDiag); } -void FixItExporter::Export() { +void FixItExporter::Export() +{ std::error_code EC; llvm::raw_fd_ostream OS(TUDiag.MainSourceFile + ".yaml", EC, llvm::sys::fs::F_None); llvm::yaml::Output YAML(OS); YAML << TUDiag; } -void FixItExporter::Diag(SourceLocation Loc, unsigned DiagID) { +void FixItExporter::Diag(SourceLocation Loc, unsigned DiagID) +{ // When producing this diagnostic, we temporarily bypass ourselves, // clear out any current diagnostic, and let the downstream client // format the diagnostic. DiagEngine.setClient(Client, false); DiagEngine.Clear(); DiagEngine.Report(Loc, DiagID); DiagEngine.setClient(this, false); } diff --git a/src/FixItExporter.h b/src/FixItExporter.h index 6f71f07..3de100d 100644 --- a/src/FixItExporter.h +++ b/src/FixItExporter.h @@ -1,67 +1,67 @@ /* This file is part of the clazy static checker. - Copyright (C) 2016 Sergio Martins - Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2019 Christian Gagneraud This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef CLAZY_FIX_IT_EXPORTER_H #define CLAZY_FIX_IT_EXPORTER_H #include #include namespace clang { class FixItOptions; } -class FixItExporter : public clang::DiagnosticConsumer { +class FixItExporter : public clang::DiagnosticConsumer +{ public: FixItExporter(clang::DiagnosticsEngine &DiagEngine, clang::SourceManager &SourceMgr, const clang::LangOptions &LangOpts, clang::FixItOptions *FixItOpts); ~FixItExporter() override; bool IncludeInDiagnosticCounts() const override; void BeginSourceFile(const clang::LangOptions &LangOpts, const clang::Preprocessor *PP = nullptr) override; void EndSourceFile() override; void HandleDiagnostic(clang::DiagnosticsEngine::Level DiagLevel, const clang::Diagnostic &Info) override; void Export(); /// Emit a diagnostic via the adapted diagnostic client. void Diag(clang::SourceLocation Loc, unsigned DiagID); private: clang::DiagnosticsEngine &DiagEngine; clang::SourceManager &SourceMgr; const clang::LangOptions &LangOpts; clang::FixItOptions *FixItOpts; DiagnosticConsumer *Client; std::unique_ptr Owner; clang::tooling::TranslationUnitDiagnostics TUDiag; clang::tooling::Diagnostic ConvertDiagnostic(const clang::Diagnostic &Info); clang::tooling::Replacement ConvertFixIt(const clang::FixItHint &Hint); }; #endif // CLAZY_FIX_IT_EXPORTER_H diff --git a/src/checks/level1/connect-3arg-lambda.cpp b/src/checks/level1/connect-3arg-lambda.cpp index f0d32ea..b6d2058 100644 --- a/src/checks/level1/connect-3arg-lambda.cpp +++ b/src/checks/level1/connect-3arg-lambda.cpp @@ -1,157 +1,161 @@ /* This file is part of the clazy static checker. Copyright (C) 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 "connect-3arg-lambda.h" #include "HierarchyUtils.h" #include "QtUtils.h" +#include "ClazyContext.h" #include #include #include #include #include #include #include class ClazyContext; using namespace clang; using namespace std; using uint = unsigned; Connect3ArgLambda::Connect3ArgLambda(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { } void Connect3ArgLambda::VisitStmt(clang::Stmt *stmt) { auto callExpr = dyn_cast(stmt); if (!callExpr) return; FunctionDecl *fdecl = callExpr->getDirectCallee(); if (!fdecl) return; const uint numParams = fdecl->getNumParams(); if (numParams != 2 && numParams != 3) return; + if (m_context->lastFunctionDecl && clazy::name(m_context->lastFunctionDecl) == "main") + return; + string qualifiedName = fdecl->getQualifiedNameAsString(); if (qualifiedName == "QTimer::singleShot") { processQTimer(fdecl, stmt); return; } if (qualifiedName == "QMenu::addAction") { processQMenu(fdecl, stmt); return; } if (numParams != 3 || !clazy::isConnect(fdecl)) return; auto lambda = clazy::getFirstChildOfType2(callExpr->getArg(2)); if (!lambda) return; DeclRefExpr *senderDeclRef = nullptr; MemberExpr *senderMemberExpr = nullptr; Stmt *s = callExpr->getArg(0); while (s) { if ((senderDeclRef = dyn_cast(s))) break; if ((senderMemberExpr = dyn_cast(s))) break; s = clazy::getFirstChild(s); } // The sender can be: this auto senderThis = clazy::unpeal(callExpr->getArg(0), clazy::IgnoreImplicitCasts); // The variables used inside the lambda auto declrefs = clazy::getStatements(lambda->getBody()); ValueDecl *senderDecl = senderDeclRef ? senderDeclRef->getDecl() : nullptr; // We'll only warn if the lambda is dereferencing another QObject (besides the sender) bool found = false; for (auto declref : declrefs) { ValueDecl *decl = declref->getDecl(); if (decl == senderDecl) continue; // It's the sender, continue. if (clazy::isQObject(decl->getType())) { found = true; break; } } if (!found) { auto thisexprs = clazy::getStatements(lambda->getBody()); if (!thisexprs.empty() && !senderThis) found = true; } if (found) emitWarning(stmt, "Pass a context object as 3rd connect parameter"); } void Connect3ArgLambda::processQTimer(FunctionDecl *func, Stmt *stmt) { // Signatures to catch: // QTimer::singleShot(int msec, Functor functor) // QTimer::singleShot(int msec, Qt::TimerType timerType, Functor functor) const uint numParams = func->getNumParams(); if (numParams == 2) { if (func->getParamDecl(0)->getNameAsString() == "interval" && func->getParamDecl(1)->getNameAsString() == "slot") { emitWarning(stmt, "Pass a context object as 2nd singleShot parameter"); } } else if (numParams == 3) { if (func->getParamDecl(0)->getNameAsString() == "interval" && func->getParamDecl(1)->getNameAsString() == "timerType" && func->getParamDecl(2)->getNameAsString() == "slot") { emitWarning(stmt, "Pass a context object as 3rd singleShot parameter"); } } } void Connect3ArgLambda::processQMenu(FunctionDecl *func, Stmt *stmt) { // Signatures to catch: // QMenu::addAction(const QString &text, Func1 slot, const QKeySequence &shortcut = 0) const uint numParams = func->getNumParams(); if (numParams == 3) { if (func->getParamDecl(0)->getNameAsString() == "text" && func->getParamDecl(1)->getNameAsString() == "slot" && func->getParamDecl(2)->getNameAsString() == "shortcut") { emitWarning(stmt, "Pass a context object as 2nd singleShot parameter"); } } }