From: Sylvestre Ledru Date: Thu, 23 Jun 2016 06:49:29 +0000 (+0000) Subject: Import llvm-toolchain-3.8_3.8.1.orig-clang-tools-extra.tar.bz2 X-Git-Tag: archive/raspbian/1%3.8.1-17+rpi1~59^2 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=8a12cc647869b8ee717ca03eb55069d55e4a35eb;p=llvm-toolchain-3.8.git Import llvm-toolchain-3.8_3.8.1.orig-clang-tools-extra.tar.bz2 [dgit import orig llvm-toolchain-3.8_3.8.1.orig-clang-tools-extra.tar.bz2] --- 8a12cc647869b8ee717ca03eb55069d55e4a35eb diff --git a/.arcconfig b/.arcconfig new file mode 100644 index 00000000..03e10ba3 --- /dev/null +++ b/.arcconfig @@ -0,0 +1,4 @@ +{ + "project_id" : "clang-tools-extra", + "conduit_uri" : "http://reviews.llvm.org/" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ac573c4a --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +#==============================================================================# +# This file specifies intentionally untracked files that git should ignore. +# See: http://www.kernel.org/pub/software/scm/git/docs/gitignore.html +# +# This file is intentionally different from the output of `git svn show-ignore`, +# as most of those are useless. +#==============================================================================# + +#==============================================================================# +# File extensions to be ignored anywhere in the tree. +#==============================================================================# +# Temp files created by most text editors. +*~ +# Merge files created by git. +*.orig +# Byte compiled python modules. +*.pyc +# vim swap files +.*.swp +.sw? + +#==============================================================================# +# Explicit files to ignore (only matches one). +#==============================================================================# +cscope.files +cscope.out +.clang_complete + +#==============================================================================# +# Directories to ignore (do not add trailing '/'s, they skip symlinks). +#==============================================================================# +docs/_build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..24e7b786 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,24 @@ +add_subdirectory(clang-apply-replacements) +add_subdirectory(clang-rename) +add_subdirectory(modularize) +if(CLANG_ENABLE_STATIC_ANALYZER) +add_subdirectory(clang-tidy) +endif() + +add_subdirectory(clang-query) +add_subdirectory(pp-trace) +add_subdirectory(tool-template) + +# Add the common testsuite after all the tools. +# TODO: Support tests with more granularity when features are off? +if(CLANG_ENABLE_STATIC_ANALYZER AND CLANG_INCLUDE_TESTS) +add_subdirectory(test) +add_subdirectory(unittests) +endif() + +option(CLANG_TOOLS_EXTRA_INCLUDE_DOCS "Generate build targets for the Clang Extra Tools docs." + ${LLVM_INCLUDE_DOCS}) +if( CLANG_TOOLS_EXTRA_INCLUDE_DOCS ) + add_subdirectory(docs) +endif() + diff --git a/CODE_OWNERS.TXT b/CODE_OWNERS.TXT new file mode 100644 index 00000000..af8beb4a --- /dev/null +++ b/CODE_OWNERS.TXT @@ -0,0 +1,21 @@ +This file is a list of the people responsible for ensuring that patches for a +particular tool are reviewed, either by themself or by someone else. They are +also the gatekeepers for their part of Clang, with the final word on what goes +in or not. + +The list is sorted by surname and formatted to allow easy grepping and +beautification by scripts. The fields are: name (N), email (E), web-address +(W), PGP key ID and fingerprint (P), description (D), and snail-mail address +(S). + +N: Peter Collingbourne +E: peter@pcc.me.uk +D: clang-query + +N: Manuel Klimek +E: klimek@google.com +D: clang-rename, all parts of clang-tools-extra not covered by someone else + +N: Alexander Kornienko +E: alexfh@google.com +D: clang-tidy diff --git a/LICENSE.TXT b/LICENSE.TXT new file mode 100644 index 00000000..1b70ad2d --- /dev/null +++ b/LICENSE.TXT @@ -0,0 +1,62 @@ +============================================================================== +LLVM Release License +============================================================================== +University of Illinois/NCSA +Open Source License + +Copyright (c) 2007-2015 University of Illinois at Urbana-Champaign. +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +============================================================================== +The LLVM software contains code written by third parties. Such software will +have its own individual LICENSE.TXT file in the directory in which it appears. +This file will describe the copyrights, license, and restrictions which apply +to that code. + +The disclaimer of warranty in the University of Illinois Open Source License +applies to all code in the LLVM Distribution, and nothing in any of the +other licenses gives permission to use the names of the LLVM Team or the +University of Illinois to endorse or promote products derived from this +Software. + +The following pieces of software have additional or alternate copyrights, +licenses, and/or restrictions: + +Program Directory +------- --------- +clang-tidy clang-tidy/cert diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..cfb284d1 --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +##===- tools/extra/Makefile --------------------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL := ../.. + +include $(CLANG_LEVEL)/../../Makefile.config + +PARALLEL_DIRS := tool-template modularize pp-trace +DIRS := clang-apply-replacements clang-rename clang-tidy clang-query unittests + +include $(CLANG_LEVEL)/Makefile + +### +# Handle the nested test suite. + +ifneq ($(PROJ_SRC_ROOT),$(PROJ_OBJ_ROOT)) +$(RecursiveTargets):: + $(Verb) for dir in test; do \ + if [ -f $(PROJ_SRC_DIR)/$${dir}/Makefile ] && [ ! -f $${dir}/Makefile ]; then \ + $(MKDIR) $${dir}; \ + $(CP) $(PROJ_SRC_DIR)/$${dir}/Makefile $${dir}/Makefile; \ + fi \ + done +endif + +test:: + @ $(MAKE) -C test + +report:: + @ $(MAKE) -C test report + +clean:: + @ $(MAKE) -C test clean + +.PHONY: test report clean diff --git a/README.txt b/README.txt new file mode 100644 index 00000000..9809cc38 --- /dev/null +++ b/README.txt @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// Clang Tools repository +//===----------------------------------------------------------------------===// + +Welcome to the repository of extra Clang Tools. This repository holds tools +that are developed as part of the LLVM compiler infrastructure project and the +Clang frontend. These tools are kept in a separate "extra" repository to +allow lighter weight checkouts of the core Clang codebase. + +This repository is only intended to be checked out inside of a full LLVM+Clang +tree, and in the 'tools/extra' subdirectory of the Clang checkout. + +All discussion regarding Clang, Clang-based tools, and code in this repository +should be held using the standard Clang mailing lists: + http://lists.llvm.org/mailman/listinfo/cfe-dev + +Code review for this tree should take place on the standard Clang patch and +commit lists: + http://lists.llvm.org/mailman/listinfo/cfe-commits + +If you find a bug in these tools, please file it in the LLVM bug tracker: + http://llvm.org/bugs/ diff --git a/clang-apply-replacements/CMakeLists.txt b/clang-apply-replacements/CMakeLists.txt new file mode 100644 index 00000000..5366e02d --- /dev/null +++ b/clang-apply-replacements/CMakeLists.txt @@ -0,0 +1,19 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_library(clangApplyReplacements + lib/Tooling/ApplyReplacements.cpp + + LINK_LIBS + clangAST + clangBasic + clangRewrite + clangToolingCore + ) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + include + ) +add_subdirectory(tool) diff --git a/clang-apply-replacements/Makefile b/clang-apply-replacements/Makefile new file mode 100644 index 00000000..7fd932b4 --- /dev/null +++ b/clang-apply-replacements/Makefile @@ -0,0 +1,15 @@ +##===- clang-apply-replacements/Makefile -------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL := ../../.. +include $(CLANG_LEVEL)/../../Makefile.config + +DIRS = lib/Tooling tool + +include $(CLANG_LEVEL)/Makefile diff --git a/clang-apply-replacements/include/clang-apply-replacements/Tooling/ApplyReplacements.h b/clang-apply-replacements/include/clang-apply-replacements/Tooling/ApplyReplacements.h new file mode 100644 index 00000000..bab19270 --- /dev/null +++ b/clang-apply-replacements/include/clang-apply-replacements/Tooling/ApplyReplacements.h @@ -0,0 +1,140 @@ +//===-- ApplyReplacements.h - Deduplicate and apply replacements -- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief This file provides the interface for deduplicating, detecting +/// conflicts in, and applying collections of Replacements. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_APPLYREPLACEMENTS_H +#define LLVM_CLANG_APPLYREPLACEMENTS_H + +#include "clang/Tooling/Refactoring.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include +#include +#include + +namespace clang { + +class DiagnosticsEngine; +class Rewriter; + +namespace format { +struct FormatStyle; +} // end namespace format + +namespace replace { + +/// \brief Collection of source ranges. +typedef std::vector RangeVector; + +/// \brief Collection of TranslationUnitReplacements. +typedef std::vector +TUReplacements; + +/// \brief Collection of TranslationUnitReplacement files. +typedef std::vector TUReplacementFiles; + +/// \brief Map mapping file name to Replacements targeting that file. +typedef llvm::DenseMap> + FileToReplacementsMap; + +/// \brief Recursively descends through a directory structure rooted at \p +/// Directory and attempts to deserialize *.yaml files as +/// TranslationUnitReplacements. All docs that successfully deserialize are +/// added to \p TUs. +/// +/// Directories starting with '.' are ignored during traversal. +/// +/// \param[in] Directory Directory to begin search for serialized +/// TranslationUnitReplacements. +/// \param[out] TUs Collection of all found and deserialized +/// TranslationUnitReplacements. +/// \param[out] TURFiles Collection of all TranslationUnitReplacement files +/// found in \c Directory. +/// \param[in] Diagnostics DiagnosticsEngine used for error output. +/// +/// \returns An error_code indicating success or failure in navigating the +/// directory structure. +std::error_code +collectReplacementsFromDirectory(const llvm::StringRef Directory, + TUReplacements &TUs, + TUReplacementFiles &TURFiles, + clang::DiagnosticsEngine &Diagnostics); + +/// \brief Deduplicate, check for conflicts, and apply all Replacements stored +/// in \c TUs. If conflicts occur, no Replacements are applied. +/// +/// \post For all (key,value) in GroupedReplacements, value[i].getOffset() <= +/// value[i+1].getOffset(). +/// +/// \param[in] TUs Collection of TranslationUnitReplacements to merge, +/// deduplicate, and test for conflicts. +/// \param[out] GroupedReplacements Container grouping all Replacements by the +/// file they target. +/// \param[in] SM SourceManager required for conflict reporting. +/// +/// \returns \parblock +/// \li true If all changes were applied successfully. +/// \li false If there were conflicts. +bool mergeAndDeduplicate(const TUReplacements &TUs, + FileToReplacementsMap &GroupedReplacements, + clang::SourceManager &SM); + +/// \brief Apply all replacements in \c GroupedReplacements. +/// +/// \param[in] GroupedReplacements Deduplicated and conflict free Replacements +/// to apply. +/// \param[out] Rewrites The results of applying replacements will be applied +/// to this Rewriter. +/// +/// \returns \parblock +/// \li true If all changes were applied successfully. +/// \li false If a replacement failed to apply. +bool applyReplacements(const FileToReplacementsMap &GroupedReplacements, + clang::Rewriter &Rewrites); + +/// \brief Given a collection of Replacements for a single file, produces a list +/// of source ranges that enclose those Replacements. +/// +/// \pre Replacements[i].getOffset() <= Replacements[i+1].getOffset(). +/// +/// \param[in] Replacements Replacements from a single file. +/// +/// \returns Collection of source ranges that enclose all given Replacements. +/// One range is created for each replacement. +RangeVector calculateChangedRanges( + const std::vector &Replacements); + +/// \brief Write the contents of \c FileContents to disk. Keys of the map are +/// filenames and values are the new contents for those files. +/// +/// \param[in] Rewrites Rewriter containing written files to write to disk. +bool writeFiles(const clang::Rewriter &Rewrites); + +/// \brief Delete the replacement files. +/// +/// \param[in] Files Replacement files to delete. +/// \param[in] Diagnostics DiagnosticsEngine used for error output. +/// +/// \returns \parblock +/// \li true If all files have been deleted successfully. +/// \li false If at least one or more failures occur when deleting +/// files. +bool deleteReplacementFiles(const TUReplacementFiles &Files, + clang::DiagnosticsEngine &Diagnostics); + +} // end namespace replace +} // end namespace clang + +#endif // LLVM_CLANG_APPLYREPLACEMENTS_H diff --git a/clang-apply-replacements/lib/Tooling/ApplyReplacements.cpp b/clang-apply-replacements/lib/Tooling/ApplyReplacements.cpp new file mode 100644 index 00000000..4603212b --- /dev/null +++ b/clang-apply-replacements/lib/Tooling/ApplyReplacements.cpp @@ -0,0 +1,260 @@ +//===-- ApplyReplacements.cpp - Apply and deduplicate replacements --------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief This file provides the implementation for deduplicating, detecting +/// conflicts in, and applying collections of Replacements. +/// +/// FIXME: Use Diagnostics for output instead of llvm::errs(). +/// +//===----------------------------------------------------------------------===// +#include "clang-apply-replacements/Tooling/ApplyReplacements.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Format/Format.h" +#include "clang/Lex/Lexer.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/ReplacementsYaml.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; +using namespace clang; + + +static void eatDiagnostics(const SMDiagnostic &, void *) {} + +namespace clang { +namespace replace { + +std::error_code +collectReplacementsFromDirectory(const llvm::StringRef Directory, + TUReplacements &TUs, + TUReplacementFiles & TURFiles, + clang::DiagnosticsEngine &Diagnostics) { + using namespace llvm::sys::fs; + using namespace llvm::sys::path; + + std::error_code ErrorCode; + + for (recursive_directory_iterator I(Directory, ErrorCode), E; + I != E && !ErrorCode; I.increment(ErrorCode)) { + if (filename(I->path())[0] == '.') { + // Indicate not to descend into directories beginning with '.' + I.no_push(); + continue; + } + + if (extension(I->path()) != ".yaml") + continue; + + TURFiles.push_back(I->path()); + + ErrorOr> Out = + MemoryBuffer::getFile(I->path()); + if (std::error_code BufferError = Out.getError()) { + errs() << "Error reading " << I->path() << ": " << BufferError.message() + << "\n"; + continue; + } + + yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics); + tooling::TranslationUnitReplacements TU; + YIn >> TU; + if (YIn.error()) { + // File doesn't appear to be a header change description. Ignore it. + continue; + } + + // Only keep files that properly parse. + TUs.push_back(TU); + } + + return ErrorCode; +} + +/// \brief Dumps information for a sequence of conflicting Replacements. +/// +/// \param[in] File FileEntry for the file the conflicting Replacements are +/// for. +/// \param[in] ConflictingReplacements List of conflicting Replacements. +/// \param[in] SM SourceManager used for reporting. +static void reportConflict( + const FileEntry *File, + const llvm::ArrayRef ConflictingReplacements, + SourceManager &SM) { + FileID FID = SM.translateFile(File); + if (FID.isInvalid()) + FID = SM.createFileID(File, SourceLocation(), SrcMgr::C_User); + + // FIXME: Output something a little more user-friendly (e.g. unified diff?) + errs() << "The following changes conflict:\n"; + for (const tooling::Replacement &R : ConflictingReplacements) { + if (R.getLength() == 0) { + errs() << " Insert at " << SM.getLineNumber(FID, R.getOffset()) << ":" + << SM.getColumnNumber(FID, R.getOffset()) << " " + << R.getReplacementText() << "\n"; + } else { + if (R.getReplacementText().empty()) + errs() << " Remove "; + else + errs() << " Replace "; + + errs() << SM.getLineNumber(FID, R.getOffset()) << ":" + << SM.getColumnNumber(FID, R.getOffset()) << "-" + << SM.getLineNumber(FID, R.getOffset() + R.getLength() - 1) << ":" + << SM.getColumnNumber(FID, R.getOffset() + R.getLength() - 1); + + if (R.getReplacementText().empty()) + errs() << "\n"; + else + errs() << " with \"" << R.getReplacementText() << "\"\n"; + } + } +} + +/// \brief Deduplicates and tests for conflicts among the replacements for each +/// file in \c Replacements. Any conflicts found are reported. +/// +/// \post Replacements[i].getOffset() <= Replacements[i+1].getOffset(). +/// +/// \param[in,out] Replacements Container of all replacements grouped by file +/// to be deduplicated and checked for conflicts. +/// \param[in] SM SourceManager required for conflict reporting. +/// +/// \returns \parblock +/// \li true if conflicts were detected +/// \li false if no conflicts were detected +static bool deduplicateAndDetectConflicts(FileToReplacementsMap &Replacements, + SourceManager &SM) { + bool conflictsFound = false; + + for (auto &FileAndReplacements : Replacements) { + const FileEntry *Entry = FileAndReplacements.first; + auto &Replacements = FileAndReplacements.second; + assert(Entry != nullptr && "No file entry!"); + + std::vector Conflicts; + tooling::deduplicate(FileAndReplacements.second, Conflicts); + + if (Conflicts.empty()) + continue; + + conflictsFound = true; + + errs() << "There are conflicting changes to " << Entry->getName() << ":\n"; + + for (const tooling::Range &Conflict : Conflicts) { + auto ConflictingReplacements = llvm::makeArrayRef( + &Replacements[Conflict.getOffset()], Conflict.getLength()); + reportConflict(Entry, ConflictingReplacements, SM); + } + } + + return conflictsFound; +} + +bool mergeAndDeduplicate(const TUReplacements &TUs, + FileToReplacementsMap &GroupedReplacements, + clang::SourceManager &SM) { + + // Group all replacements by target file. + std::set Warned; + for (const auto &TU : TUs) { + for (const tooling::Replacement &R : TU.Replacements) { + // Use the file manager to deduplicate paths. FileEntries are + // automatically canonicalized. + const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath()); + if (!Entry && Warned.insert(R.getFilePath()).second) { + errs() << "Described file '" << R.getFilePath() + << "' doesn't exist. Ignoring...\n"; + continue; + } + GroupedReplacements[Entry].push_back(R); + } + } + + // Ask clang to deduplicate and report conflicts. + return !deduplicateAndDetectConflicts(GroupedReplacements, SM); +} + +bool applyReplacements(const FileToReplacementsMap &GroupedReplacements, + clang::Rewriter &Rewrites) { + + // Apply all changes + // + // FIXME: No longer certain GroupedReplacements is really the best kind of + // data structure for applying replacements. Rewriter certainly doesn't care. + // However, until we nail down the design of ReplacementGroups, might as well + // leave this as is. + for (const auto &FileAndReplacements : GroupedReplacements) { + if (!tooling::applyAllReplacements(FileAndReplacements.second, Rewrites)) + return false; + } + + return true; +} + +RangeVector calculateChangedRanges( + const std::vector &Replaces) { + RangeVector ChangedRanges; + + // Generate the new ranges from the replacements. + int Shift = 0; + for (const tooling::Replacement &R : Replaces) { + unsigned Offset = R.getOffset() + Shift; + unsigned Length = R.getReplacementText().size(); + Shift += Length - R.getLength(); + ChangedRanges.push_back(tooling::Range(Offset, Length)); + } + + return ChangedRanges; +} + +bool writeFiles(const clang::Rewriter &Rewrites) { + + for (Rewriter::const_buffer_iterator BufferI = Rewrites.buffer_begin(), + BufferE = Rewrites.buffer_end(); + BufferI != BufferE; ++BufferI) { + const char *FileName = + Rewrites.getSourceMgr().getFileEntryForID(BufferI->first)->getName(); + + std::error_code EC; + llvm::raw_fd_ostream FileStream(FileName, EC, llvm::sys::fs::F_Text); + if (EC) { + errs() << "Warning: Could not write to " << EC.message() << "\n"; + continue; + } + BufferI->second.write(FileStream); + } + + return true; +} + +bool deleteReplacementFiles(const TUReplacementFiles &Files, + clang::DiagnosticsEngine &Diagnostics) { + bool Success = true; + for (const auto &Filename : Files) { + std::error_code Error = llvm::sys::fs::remove(Filename); + if (Error) { + Success = false; + // FIXME: Use Diagnostics for outputting errors. + errs() << "Error deleting file: " << Filename << "\n"; + errs() << Error.message() << "\n"; + errs() << "Please delete the file manually\n"; + } + } + return Success; +} + +} // end namespace replace +} // end namespace clang diff --git a/clang-apply-replacements/lib/Tooling/Makefile b/clang-apply-replacements/lib/Tooling/Makefile new file mode 100644 index 00000000..c341ff39 --- /dev/null +++ b/clang-apply-replacements/lib/Tooling/Makefile @@ -0,0 +1,14 @@ +##===- clang-apply-replacements/lib/Tooling/Makefile -------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL := ../../../../.. +LIBRARYNAME := clangApplyReplacements +include $(CLANG_LEVEL)/../../Makefile.config +include $(CLANG_LEVEL)/Makefile +CPP.Flags += -I$(PROJ_SRC_DIR)/../../include diff --git a/clang-apply-replacements/tool/CMakeLists.txt b/clang-apply-replacements/tool/CMakeLists.txt new file mode 100644 index 00000000..b5c159da --- /dev/null +++ b/clang-apply-replacements/tool/CMakeLists.txt @@ -0,0 +1,17 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_executable(clang-apply-replacements + ClangApplyReplacementsMain.cpp + ) +target_link_libraries(clang-apply-replacements + clangApplyReplacements + clangBasic + clangFormat + clangRewrite + clangToolingCore + ) + +install(TARGETS clang-apply-replacements + RUNTIME DESTINATION bin) diff --git a/clang-apply-replacements/tool/ClangApplyReplacementsMain.cpp b/clang-apply-replacements/tool/ClangApplyReplacementsMain.cpp new file mode 100644 index 00000000..5bc06bb1 --- /dev/null +++ b/clang-apply-replacements/tool/ClangApplyReplacementsMain.cpp @@ -0,0 +1,279 @@ +//===-- ClangApplyReplacementsMain.cpp - Main file for the tool -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief This file provides the main function for the +/// clang-apply-replacements tool. +/// +//===----------------------------------------------------------------------===// + +#include "clang-apply-replacements/Tooling/ApplyReplacements.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/Version.h" +#include "clang/Format/Format.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/Support/CommandLine.h" + +using namespace llvm; +using namespace clang; +using namespace clang::replace; + +static cl::opt Directory(cl::Positional, cl::Required, + cl::desc("")); + +static cl::OptionCategory ReplacementCategory("Replacement Options"); +static cl::OptionCategory FormattingCategory("Formatting Options"); + +const cl::OptionCategory *VisibleCategories[] = {&ReplacementCategory, + &FormattingCategory}; + +static cl::opt RemoveTUReplacementFiles( + "remove-change-desc-files", + cl::desc("Remove the change description files regardless of successful\n" + "merging/replacing."), + cl::init(false), cl::cat(ReplacementCategory)); + + +static cl::opt DoFormat( + "format", + cl::desc("Enable formatting of code changed by applying replacements.\n" + "Use -style to choose formatting style.\n"), + cl::cat(FormattingCategory)); + +// FIXME: Consider making the default behaviour for finding a style +// configuration file to start the search anew for every file being changed to +// handle situations where the style is different for different parts of a +// project. + +static cl::opt FormatStyleConfig( + "style-config", + cl::desc("Path to a directory containing a .clang-format file\n" + "describing a formatting style to use for formatting\n" + "code when -style=file.\n"), + cl::init(""), cl::cat(FormattingCategory)); + +static cl::opt +FormatStyleOpt("style", cl::desc(format::StyleOptionHelpDescription), + cl::init("LLVM"), cl::cat(FormattingCategory)); + +namespace { +// Helper object to remove the TUReplacement files (triggered by +// "remove-change-desc-files" command line option) when exiting current scope. +class ScopedFileRemover { +public: + ScopedFileRemover(const TUReplacementFiles &Files, + clang::DiagnosticsEngine &Diagnostics) + : TURFiles(Files), Diag(Diagnostics) {} + + ~ScopedFileRemover() { + deleteReplacementFiles(TURFiles, Diag); + } + +private: + const TUReplacementFiles &TURFiles; + clang::DiagnosticsEngine &Diag; +}; +} // namespace + +static void printVersion() { + outs() << "clang-apply-replacements version " CLANG_VERSION_STRING << "\n"; +} + +/// \brief Convenience function to get rewritten content for \c Filename from +/// \c Rewrites. +/// +/// \pre Replacements[i].getFilePath() == Replacements[i+1].getFilePath(). +/// \post Replacements.empty() -> Result.empty() +/// +/// \param[in] Replacements Replacements to apply +/// \param[in] Rewrites Rewriter to use to apply replacements. +/// \param[out] Result Contents of the file after applying replacements if +/// replacements were provided. +/// +/// \returns \parblock +/// \li true if all replacements were applied successfully. +/// \li false if at least one replacement failed to apply. +static bool +getRewrittenData(const std::vector &Replacements, + Rewriter &Rewrites, std::string &Result) { + if (Replacements.empty()) return true; + + if (!tooling::applyAllReplacements(Replacements, Rewrites)) + return false; + + SourceManager &SM = Rewrites.getSourceMgr(); + FileManager &Files = SM.getFileManager(); + + StringRef FileName = Replacements.begin()->getFilePath(); + const clang::FileEntry *Entry = Files.getFile(FileName); + assert(Entry && "Expected an existing file"); + FileID ID = SM.translateFile(Entry); + assert(ID.isValid() && "Expected a valid FileID"); + const RewriteBuffer *Buffer = Rewrites.getRewriteBufferFor(ID); + Result = std::string(Buffer->begin(), Buffer->end()); + + return true; +} + +/// \brief Apply \c Replacements and return the new file contents. +/// +/// \pre Replacements[i].getFilePath() == Replacements[i+1].getFilePath(). +/// \post Replacements.empty() -> Result.empty() +/// +/// \param[in] Replacements Replacements to apply. +/// \param[out] Result Contents of the file after applying replacements if +/// replacements were provided. +/// \param[in] Diagnostics For diagnostic output. +/// +/// \returns \parblock +/// \li true if all replacements applied successfully. +/// \li false if at least one replacement failed to apply. +static bool +applyReplacements(const std::vector &Replacements, + std::string &Result, DiagnosticsEngine &Diagnostics) { + FileManager Files((FileSystemOptions())); + SourceManager SM(Diagnostics, Files); + Rewriter Rewrites(SM, LangOptions()); + + return getRewrittenData(Replacements, Rewrites, Result); +} + +/// \brief Apply code formatting to all places where replacements were made. +/// +/// \pre !Replacements.empty(). +/// \pre Replacements[i].getFilePath() == Replacements[i+1].getFilePath(). +/// \pre Replacements[i].getOffset() <= Replacements[i+1].getOffset(). +/// +/// \param[in] Replacements Replacements that were made to the file. Provided +/// to indicate where changes were made. +/// \param[in] FileData The contents of the file \b after \c Replacements have +/// been applied. +/// \param[out] FormattedFileData The contents of the file after reformatting. +/// \param[in] FormatStyle Style to apply. +/// \param[in] Diagnostics For diagnostic output. +/// +/// \returns \parblock +/// \li true if reformatting replacements were all successfully +/// applied. +/// \li false if at least one reformatting replacement failed to apply. +static bool +applyFormatting(const std::vector &Replacements, + const StringRef FileData, std::string &FormattedFileData, + const format::FormatStyle &FormatStyle, + DiagnosticsEngine &Diagnostics) { + assert(!Replacements.empty() && "Need at least one replacement"); + + RangeVector Ranges = calculateChangedRanges(Replacements); + + StringRef FileName = Replacements.begin()->getFilePath(); + tooling::Replacements R = + format::reformat(FormatStyle, FileData, Ranges, FileName); + + // FIXME: Remove this copy when tooling::Replacements is implemented as a + // vector instead of a set. + std::vector FormattingReplacements; + std::copy(R.begin(), R.end(), back_inserter(FormattingReplacements)); + + if (FormattingReplacements.empty()) { + FormattedFileData = FileData; + return true; + } + + FileManager Files((FileSystemOptions())); + SourceManager SM(Diagnostics, Files); + SM.overrideFileContents(Files.getFile(FileName), + llvm::MemoryBuffer::getMemBufferCopy(FileData)); + Rewriter Rewrites(SM, LangOptions()); + + return getRewrittenData(FormattingReplacements, Rewrites, FormattedFileData); +} + +int main(int argc, char **argv) { + cl::HideUnrelatedOptions(makeArrayRef(VisibleCategories)); + + cl::SetVersionPrinter(&printVersion); + cl::ParseCommandLineOptions(argc, argv); + + IntrusiveRefCntPtr DiagOpts(new DiagnosticOptions()); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(new DiagnosticIDs()), + DiagOpts.get()); + + // Determine a formatting style from options. + format::FormatStyle FormatStyle; + if (DoFormat) + FormatStyle = format::getStyle(FormatStyleOpt, FormatStyleConfig, "LLVM"); + + TUReplacements TUs; + TUReplacementFiles TURFiles; + + std::error_code ErrorCode = + collectReplacementsFromDirectory(Directory, TUs, TURFiles, Diagnostics); + + if (ErrorCode) { + errs() << "Trouble iterating over directory '" << Directory + << "': " << ErrorCode.message() << "\n"; + return 1; + } + + // Remove the TUReplacementFiles (triggered by "remove-change-desc-files" + // command line option) when exiting main(). + std::unique_ptr Remover; + if (RemoveTUReplacementFiles) + Remover.reset(new ScopedFileRemover(TURFiles, Diagnostics)); + + FileManager Files((FileSystemOptions())); + SourceManager SM(Diagnostics, Files); + + FileToReplacementsMap GroupedReplacements; + if (!mergeAndDeduplicate(TUs, GroupedReplacements, SM)) + return 1; + + Rewriter ReplacementsRewriter(SM, LangOptions()); + + for (const auto &FileAndReplacements : GroupedReplacements) { + // This shouldn't happen but if a file somehow has no replacements skip to + // next file. + if (FileAndReplacements.second.empty()) + continue; + + std::string NewFileData; + const char *FileName = FileAndReplacements.first->getName(); + if (!applyReplacements(FileAndReplacements.second, NewFileData, + Diagnostics)) { + errs() << "Failed to apply replacements to " << FileName << "\n"; + continue; + } + + // Apply formatting if requested. + if (DoFormat && + !applyFormatting(FileAndReplacements.second, NewFileData, NewFileData, + FormatStyle, Diagnostics)) { + errs() << "Failed to apply reformatting replacements for " << FileName + << "\n"; + continue; + } + + // Write new file to disk + std::error_code EC; + llvm::raw_fd_ostream FileStream(FileName, EC, llvm::sys::fs::F_None); + if (EC) { + llvm::errs() << "Could not open " << FileName << " for writing\n"; + continue; + } + + FileStream << NewFileData; + } + + return 0; +} diff --git a/clang-apply-replacements/tool/Makefile b/clang-apply-replacements/tool/Makefile new file mode 100644 index 00000000..8c911720 --- /dev/null +++ b/clang-apply-replacements/tool/Makefile @@ -0,0 +1,29 @@ +##===- clang-apply-replacements/tool/Makefile --------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL := ../../../.. +include $(CLANG_LEVEL)/../../Makefile.config + +TOOLNAME = clang-apply-replacements + +# No plugins, optimize startup time. +TOOL_NO_EXPORTS = 1 + +SOURCES = ClangApplyReplacementsMain.cpp + +LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader support mc mcparser option +USEDLIBS = clangApplyReplacements.a clangFormat.a \ + clangTooling.a clangToolingCore.a clangFrontend.a \ + clangSerialization.a clangDriver.a clangRewriteFrontend.a \ + clangRewrite.a clangParse.a clangSema.a clangAnalysis.a \ + clangAST.a clangASTMatchers.a clangEdit.a clangLex.a clangBasic.a + +include $(CLANG_LEVEL)/Makefile + +CPP.Flags += -I$(PROJ_SRC_DIR)/../include diff --git a/clang-query/CMakeLists.txt b/clang-query/CMakeLists.txt new file mode 100644 index 00000000..46febd60 --- /dev/null +++ b/clang-query/CMakeLists.txt @@ -0,0 +1,18 @@ +set(LLVM_LINK_COMPONENTS + lineeditor + support + ) + +add_clang_library(clangQuery + Query.cpp + QueryParser.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangDynamicASTMatchers + clangFrontend + ) + +add_subdirectory(tool) diff --git a/clang-query/Makefile b/clang-query/Makefile new file mode 100644 index 00000000..13903b98 --- /dev/null +++ b/clang-query/Makefile @@ -0,0 +1,16 @@ +##===- tools/extra/clang-query/Makefile --------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL := ../../.. +LIBRARYNAME := clangQuery +include $(CLANG_LEVEL)/../../Makefile.config + +DIRS = tool + +include $(CLANG_LEVEL)/Makefile diff --git a/clang-query/Query.cpp b/clang-query/Query.cpp new file mode 100644 index 00000000..74eb6ea4 --- /dev/null +++ b/clang-query/Query.cpp @@ -0,0 +1,150 @@ +//===---- Query.cpp - clang-query query -----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Query.h" +#include "QuerySession.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Frontend/TextDiagnostic.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang::ast_matchers; +using namespace clang::ast_matchers::dynamic; + +namespace clang { +namespace query { + +Query::~Query() {} + +bool InvalidQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { + OS << ErrStr << "\n"; + return false; +} + +bool NoOpQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { + return true; +} + +bool HelpQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { + OS << "Available commands:\n\n" + " match MATCHER, m MATCHER " + "Match the loaded ASTs against the given matcher.\n" + " let NAME MATCHER, l NAME MATCHER " + "Give a matcher expression a name, to be used later\n" + " " + "as part of other expressions.\n" + " set bind-root (true|false) " + "Set whether to bind the root matcher to \"root\".\n" + " set output (diag|print|dump) " + "Set whether to print bindings as diagnostics,\n" + " " + "AST pretty prints or AST dumps.\n" + " quit " + "Terminates the query session.\n\n"; + return true; +} + +bool QuitQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { + QS.Terminate = true; + return true; +} + +namespace { + +struct CollectBoundNodes : MatchFinder::MatchCallback { + std::vector &Bindings; + CollectBoundNodes(std::vector &Bindings) : Bindings(Bindings) {} + void run(const MatchFinder::MatchResult &Result) override { + Bindings.push_back(Result.Nodes); + } +}; + +} // namespace + +bool MatchQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { + unsigned MatchCount = 0; + + for (auto &AST : QS.ASTs) { + MatchFinder Finder; + std::vector Matches; + DynTypedMatcher MaybeBoundMatcher = Matcher; + if (QS.BindRoot) { + llvm::Optional M = Matcher.tryBind("root"); + if (M) + MaybeBoundMatcher = *M; + } + CollectBoundNodes Collect(Matches); + if (!Finder.addDynamicMatcher(MaybeBoundMatcher, &Collect)) { + OS << "Not a valid top-level matcher.\n"; + return false; + } + Finder.matchAST(AST->getASTContext()); + + for (std::vector::iterator MI = Matches.begin(), + ME = Matches.end(); + MI != ME; ++MI) { + OS << "\nMatch #" << ++MatchCount << ":\n\n"; + + for (BoundNodes::IDToNodeMap::const_iterator BI = MI->getMap().begin(), + BE = MI->getMap().end(); + BI != BE; ++BI) { + switch (QS.OutKind) { + case OK_Diag: { + clang::SourceRange R = BI->second.getSourceRange(); + if (R.isValid()) { + TextDiagnostic TD(OS, AST->getASTContext().getLangOpts(), + &AST->getDiagnostics().getDiagnosticOptions()); + TD.emitDiagnostic( + R.getBegin(), DiagnosticsEngine::Note, + "\"" + BI->first + "\" binds here", + CharSourceRange::getTokenRange(R), + None, &AST->getSourceManager()); + } + break; + } + case OK_Print: { + OS << "Binding for \"" << BI->first << "\":\n"; + BI->second.print(OS, AST->getASTContext().getPrintingPolicy()); + OS << "\n"; + break; + } + case OK_Dump: { + OS << "Binding for \"" << BI->first << "\":\n"; + BI->second.dump(OS, AST->getSourceManager()); + OS << "\n"; + break; + } + } + } + + if (MI->getMap().empty()) + OS << "No bindings.\n"; + } + } + + OS << MatchCount << (MatchCount == 1 ? " match.\n" : " matches.\n"); + return true; +} + +bool LetQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { + if (Value) { + QS.NamedValues[Name] = Value; + } else { + QS.NamedValues.erase(Name); + } + return true; +} + +#ifndef _MSC_VER +const QueryKind SetQueryKind::value; +const QueryKind SetQueryKind::value; +#endif + +} // namespace query +} // namespace clang diff --git a/clang-query/Query.h b/clang-query/Query.h new file mode 100644 index 00000000..109336a9 --- /dev/null +++ b/clang-query/Query.h @@ -0,0 +1,140 @@ +//===--- Query.h - clang-query ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_H + +#include "clang/ASTMatchers/Dynamic/VariantValue.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/Optional.h" +#include + +namespace clang { +namespace query { + +enum OutputKind { + OK_Diag, + OK_Print, + OK_Dump +}; + +enum QueryKind { + QK_Invalid, + QK_NoOp, + QK_Help, + QK_Let, + QK_Match, + QK_SetBool, + QK_SetOutputKind, + QK_Quit +}; + +class QuerySession; + +struct Query : llvm::RefCountedBase { + Query(QueryKind Kind) : Kind(Kind) {} + virtual ~Query(); + + /// Perform the query on \p QS and print output to \p OS. + /// + /// \return false if an error occurs, otherwise return true. + virtual bool run(llvm::raw_ostream &OS, QuerySession &QS) const = 0; + + const QueryKind Kind; +}; + +typedef llvm::IntrusiveRefCntPtr QueryRef; + +/// Any query which resulted in a parse error. The error message is in ErrStr. +struct InvalidQuery : Query { + InvalidQuery(const Twine &ErrStr) : Query(QK_Invalid), ErrStr(ErrStr.str()) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + std::string ErrStr; + + static bool classof(const Query *Q) { return Q->Kind == QK_Invalid; } +}; + +/// No-op query (i.e. a blank line). +struct NoOpQuery : Query { + NoOpQuery() : Query(QK_NoOp) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + static bool classof(const Query *Q) { return Q->Kind == QK_NoOp; } +}; + +/// Query for "help". +struct HelpQuery : Query { + HelpQuery() : Query(QK_Help) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + static bool classof(const Query *Q) { return Q->Kind == QK_Help; } +}; + +/// Query for "quit". +struct QuitQuery : Query { + QuitQuery() : Query(QK_Quit) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + static bool classof(const Query *Q) { return Q->Kind == QK_Quit; } +}; + +/// Query for "match MATCHER". +struct MatchQuery : Query { + MatchQuery(const ast_matchers::dynamic::DynTypedMatcher &Matcher) + : Query(QK_Match), Matcher(Matcher) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + ast_matchers::dynamic::DynTypedMatcher Matcher; + + static bool classof(const Query *Q) { return Q->Kind == QK_Match; } +}; + +struct LetQuery : Query { + LetQuery(StringRef Name, const ast_matchers::dynamic::VariantValue &Value) + : Query(QK_Let), Name(Name), Value(Value) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + std::string Name; + ast_matchers::dynamic::VariantValue Value; + + static bool classof(const Query *Q) { return Q->Kind == QK_Let; } +}; + +template struct SetQueryKind {}; + +template <> struct SetQueryKind { + static const QueryKind value = QK_SetBool; +}; + +template <> struct SetQueryKind { + static const QueryKind value = QK_SetOutputKind; +}; + +/// Query for "set VAR VALUE". +template struct SetQuery : Query { + SetQuery(T QuerySession::*Var, T Value) + : Query(SetQueryKind::value), Var(Var), Value(Value) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override { + QS.*Var = Value; + return true; + } + + static bool classof(const Query *Q) { + return Q->Kind == SetQueryKind::value; + } + + T QuerySession::*Var; + T Value; +}; + +} // namespace query +} // namespace clang + +#endif diff --git a/clang-query/QueryParser.cpp b/clang-query/QueryParser.cpp new file mode 100644 index 00000000..e25b6970 --- /dev/null +++ b/clang-query/QueryParser.cpp @@ -0,0 +1,286 @@ +//===---- QueryParser.cpp - clang-query command parser --------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "QueryParser.h" +#include "Query.h" +#include "QuerySession.h" +#include "clang/ASTMatchers/Dynamic/Parser.h" +#include "clang/Basic/CharInfo.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSwitch.h" +#include + +using namespace llvm; +using namespace clang::ast_matchers::dynamic; + +namespace clang { +namespace query { + +// Lex any amount of whitespace followed by a "word" (any sequence of +// non-whitespace characters) from the start of region [Begin,End). If no word +// is found before End, return StringRef(). Begin is adjusted to exclude the +// lexed region. +StringRef QueryParser::lexWord() { + while (true) { + if (Begin == End) + return StringRef(Begin, 0); + + if (!isWhitespace(*Begin)) + break; + + ++Begin; + } + + const char *WordBegin = Begin; + + while (true) { + ++Begin; + + if (Begin == End || isWhitespace(*Begin)) + return StringRef(WordBegin, Begin - WordBegin); + } +} + +// This is the StringSwitch-alike used by lexOrCompleteWord below. See that +// function for details. +template struct QueryParser::LexOrCompleteWord { + StringSwitch Switch; + + QueryParser *P; + StringRef Word; + // Set to the completion point offset in Word, or StringRef::npos if + // completion point not in Word. + size_t WordCompletionPos; + + LexOrCompleteWord(QueryParser *P, StringRef Word, size_t WCP) + : Switch(Word), P(P), Word(Word), WordCompletionPos(WCP) {} + + template + LexOrCompleteWord &Case(const char (&S)[N], const T &Value, + bool IsCompletion = true) { + StringRef CaseStr(S, N - 1); + + if (WordCompletionPos == StringRef::npos) + Switch.Case(S, Value); + else if (N != 1 && IsCompletion && WordCompletionPos <= CaseStr.size() && + CaseStr.substr(0, WordCompletionPos) == + Word.substr(0, WordCompletionPos)) + P->Completions.push_back(LineEditor::Completion( + (CaseStr.substr(WordCompletionPos) + " ").str(), CaseStr)); + return *this; + } + + T Default(const T& Value) const { + return Switch.Default(Value); + } +}; + +// Lexes a word and stores it in Word. Returns a LexOrCompleteWord object +// that can be used like a llvm::StringSwitch, but adds cases as possible +// completions if the lexed word contains the completion point. +template +QueryParser::LexOrCompleteWord +QueryParser::lexOrCompleteWord(StringRef &Word) { + Word = lexWord(); + size_t WordCompletionPos = StringRef::npos; + if (CompletionPos && CompletionPos <= Word.data() + Word.size()) { + if (CompletionPos < Word.data()) + WordCompletionPos = 0; + else + WordCompletionPos = CompletionPos - Word.data(); + } + return LexOrCompleteWord(this, Word, WordCompletionPos); +} + +QueryRef QueryParser::parseSetBool(bool QuerySession::*Var) { + StringRef ValStr; + unsigned Value = lexOrCompleteWord(ValStr) + .Case("false", 0) + .Case("true", 1) + .Default(~0u); + if (Value == ~0u) { + return new InvalidQuery("expected 'true' or 'false', got '" + ValStr + "'"); + } + return new SetQuery(Var, Value); +} + +QueryRef QueryParser::parseSetOutputKind() { + StringRef ValStr; + unsigned OutKind = lexOrCompleteWord(ValStr) + .Case("diag", OK_Diag) + .Case("print", OK_Print) + .Case("dump", OK_Dump) + .Default(~0u); + if (OutKind == ~0u) { + return new InvalidQuery("expected 'diag', 'print' or 'dump', got '" + + ValStr + "'"); + } + return new SetQuery(&QuerySession::OutKind, OutputKind(OutKind)); +} + +QueryRef QueryParser::endQuery(QueryRef Q) { + const char *Extra = Begin; + if (!lexWord().empty()) + return new InvalidQuery("unexpected extra input: '" + + StringRef(Extra, End - Extra) + "'"); + return Q; +} + +namespace { + +enum ParsedQueryKind { + PQK_Invalid, + PQK_NoOp, + PQK_Help, + PQK_Let, + PQK_Match, + PQK_Set, + PQK_Unlet, + PQK_Quit +}; + +enum ParsedQueryVariable { + PQV_Invalid, + PQV_Output, + PQV_BindRoot +}; + +QueryRef makeInvalidQueryFromDiagnostics(const Diagnostics &Diag) { + std::string ErrStr; + llvm::raw_string_ostream OS(ErrStr); + Diag.printToStreamFull(OS); + return new InvalidQuery(OS.str()); +} + +} // namespace + +QueryRef QueryParser::completeMatcherExpression() { + std::vector Comps = Parser::completeExpression( + StringRef(Begin, End - Begin), CompletionPos - Begin, nullptr, + &QS.NamedValues); + for (std::vector::iterator I = Comps.begin(), + E = Comps.end(); + I != E; ++I) { + Completions.push_back(LineEditor::Completion(I->TypedText, I->MatcherDecl)); + } + return QueryRef(); +} + +QueryRef QueryParser::doParse() { + StringRef CommandStr; + ParsedQueryKind QKind = lexOrCompleteWord(CommandStr) + .Case("", PQK_NoOp) + .Case("help", PQK_Help) + .Case("m", PQK_Match, /*IsCompletion=*/false) + .Case("let", PQK_Let) + .Case("match", PQK_Match) + .Case("set", PQK_Set) + .Case("unlet", PQK_Unlet) + .Case("quit", PQK_Quit) + .Default(PQK_Invalid); + + switch (QKind) { + case PQK_NoOp: + return new NoOpQuery; + + case PQK_Help: + return endQuery(new HelpQuery); + + case PQK_Quit: + return endQuery(new QuitQuery); + + case PQK_Let: { + StringRef Name = lexWord(); + + if (Name.empty()) + return new InvalidQuery("expected variable name"); + + if (CompletionPos) + return completeMatcherExpression(); + + Diagnostics Diag; + ast_matchers::dynamic::VariantValue Value; + if (!Parser::parseExpression(StringRef(Begin, End - Begin), nullptr, + &QS.NamedValues, &Value, &Diag)) { + return makeInvalidQueryFromDiagnostics(Diag); + } + + return new LetQuery(Name, Value); + } + + case PQK_Match: { + if (CompletionPos) + return completeMatcherExpression(); + + Diagnostics Diag; + Optional Matcher = Parser::parseMatcherExpression( + StringRef(Begin, End - Begin), nullptr, &QS.NamedValues, &Diag); + if (!Matcher) { + return makeInvalidQueryFromDiagnostics(Diag); + } + return new MatchQuery(*Matcher); + } + + case PQK_Set: { + StringRef VarStr; + ParsedQueryVariable Var = lexOrCompleteWord(VarStr) + .Case("output", PQV_Output) + .Case("bind-root", PQV_BindRoot) + .Default(PQV_Invalid); + if (VarStr.empty()) + return new InvalidQuery("expected variable name"); + if (Var == PQV_Invalid) + return new InvalidQuery("unknown variable: '" + VarStr + "'"); + + QueryRef Q; + switch (Var) { + case PQV_Output: + Q = parseSetOutputKind(); + break; + case PQV_BindRoot: + Q = parseSetBool(&QuerySession::BindRoot); + break; + case PQV_Invalid: + llvm_unreachable("Invalid query kind"); + } + + return endQuery(Q); + } + + case PQK_Unlet: { + StringRef Name = lexWord(); + + if (Name.empty()) + return new InvalidQuery("expected variable name"); + + return endQuery(new LetQuery(Name, VariantValue())); + } + + case PQK_Invalid: + return new InvalidQuery("unknown command: " + CommandStr); + } + + llvm_unreachable("Invalid query kind"); +} + +QueryRef QueryParser::parse(StringRef Line, const QuerySession &QS) { + return QueryParser(Line, QS).doParse(); +} + +std::vector +QueryParser::complete(StringRef Line, size_t Pos, const QuerySession &QS) { + QueryParser P(Line, QS); + P.CompletionPos = Line.data() + Pos; + + P.doParse(); + return P.Completions; +} + +} // namespace query +} // namespace clang diff --git a/clang-query/QueryParser.h b/clang-query/QueryParser.h new file mode 100644 index 00000000..97d89a2b --- /dev/null +++ b/clang-query/QueryParser.h @@ -0,0 +1,72 @@ +//===--- QueryParser.h - clang-query ----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_PARSER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_PARSER_H + +#include "Query.h" +#include "QuerySession.h" +#include "llvm/LineEditor/LineEditor.h" +#include + +namespace clang { +namespace query { + +class QuerySession; + +class QueryParser { +public: + /// Parse \a Line as a query. + /// + /// \return A QueryRef representing the query, which may be an InvalidQuery. + static QueryRef parse(StringRef Line, const QuerySession &QS); + + /// Compute a list of completions for \a Line assuming a cursor at + /// \param Pos characters past the start of \a Line, ordered from most + /// likely to least likely. + /// + /// \return A vector of completions for \a Line. + static std::vector + complete(StringRef Line, size_t Pos, const QuerySession &QS); + +private: + QueryParser(StringRef Line, const QuerySession &QS) + : Begin(Line.begin()), End(Line.end()), + CompletionPos(nullptr), QS(QS) {} + + StringRef lexWord(); + + template struct LexOrCompleteWord; + template LexOrCompleteWord lexOrCompleteWord(StringRef &Str); + + QueryRef parseSetBool(bool QuerySession::*Var); + QueryRef parseSetOutputKind(); + QueryRef completeMatcherExpression(); + + QueryRef endQuery(QueryRef Q); + + /// \brief Parse [\p Begin,\p End). + /// + /// \return A reference to the parsed query object, which may be an + /// \c InvalidQuery if a parse error occurs. + QueryRef doParse(); + + const char *Begin; + const char *End; + + const char *CompletionPos; + std::vector Completions; + + const QuerySession &QS; +}; + +} // namespace query +} // namespace clang + +#endif diff --git a/clang-query/QuerySession.h b/clang-query/QuerySession.h new file mode 100644 index 00000000..162289f8 --- /dev/null +++ b/clang-query/QuerySession.h @@ -0,0 +1,40 @@ +//===--- QuerySession.h - clang-query ---------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_SESSION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_SESSION_H + +#include "Query.h" +#include "clang/ASTMatchers/Dynamic/VariantValue.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringMap.h" + +namespace clang { + +class ASTUnit; + +namespace query { + +/// Represents the state for a particular clang-query session. +class QuerySession { +public: + QuerySession(llvm::ArrayRef> ASTs) + : ASTs(ASTs), OutKind(OK_Diag), BindRoot(true), Terminate(false) {} + + llvm::ArrayRef> ASTs; + OutputKind OutKind; + bool BindRoot; + bool Terminate; + llvm::StringMap NamedValues; +}; + +} // namespace query +} // namespace clang + +#endif diff --git a/clang-query/tool/CMakeLists.txt b/clang-query/tool/CMakeLists.txt new file mode 100644 index 00000000..52af5c87 --- /dev/null +++ b/clang-query/tool/CMakeLists.txt @@ -0,0 +1,14 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_executable(clang-query ClangQuery.cpp) +target_link_libraries(clang-query + clangAST + clangASTMatchers + clangBasic + clangDynamicASTMatchers + clangFrontend + clangQuery + clangTooling + ) + +install(TARGETS clang-query RUNTIME DESTINATION bin) diff --git a/clang-query/tool/ClangQuery.cpp b/clang-query/tool/ClangQuery.cpp new file mode 100644 index 00000000..f1c97ce4 --- /dev/null +++ b/clang-query/tool/ClangQuery.cpp @@ -0,0 +1,120 @@ +//===---- ClangQuery.cpp - clang-query tool -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This tool is for interactive exploration of the Clang AST using AST matchers. +// It currently allows the user to enter a matcher at an interactive prompt and +// view the resulting bindings as diagnostics, AST pretty prints or AST dumps. +// Example session: +// +// $ cat foo.c +// void foo(void) {} +// $ clang-query foo.c -- +// clang-query> match functionDecl() +// +// Match #1: +// +// foo.c:1:1: note: "root" binds here +// void foo(void) {} +// ^~~~~~~~~~~~~~~~~ +// 1 match. +// +//===----------------------------------------------------------------------===// + +#include "Query.h" +#include "QueryParser.h" +#include "QuerySession.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/LineEditor/LineEditor.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Signals.h" +#include +#include + +using namespace clang; +using namespace clang::ast_matchers; +using namespace clang::ast_matchers::dynamic; +using namespace clang::query; +using namespace clang::tooling; +using namespace llvm; + +static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); +static cl::OptionCategory ClangQueryCategory("clang-query options"); + +static cl::list Commands("c", cl::desc("Specify command to run"), + cl::value_desc("command"), + cl::cat(ClangQueryCategory)); + +static cl::list CommandFiles("f", + cl::desc("Read commands from file"), + cl::value_desc("file"), + cl::cat(ClangQueryCategory)); + +int main(int argc, const char **argv) { + llvm::sys::PrintStackTraceOnErrorSignal(); + + CommonOptionsParser OptionsParser(argc, argv, ClangQueryCategory); + + if (!Commands.empty() && !CommandFiles.empty()) { + llvm::errs() << argv[0] << ": cannot specify both -c and -f\n"; + return 1; + } + + ClangTool Tool(OptionsParser.getCompilations(), + OptionsParser.getSourcePathList()); + std::vector> ASTs; + if (Tool.buildASTs(ASTs) != 0) + return 1; + + QuerySession QS(ASTs); + + if (!Commands.empty()) { + for (cl::list::iterator I = Commands.begin(), + E = Commands.end(); + I != E; ++I) { + QueryRef Q = QueryParser::parse(I->c_str(), QS); + if (!Q->run(llvm::outs(), QS)) + return 1; + } + } else if (!CommandFiles.empty()) { + for (cl::list::iterator I = CommandFiles.begin(), + E = CommandFiles.end(); + I != E; ++I) { + std::ifstream Input(I->c_str()); + if (!Input.is_open()) { + llvm::errs() << argv[0] << ": cannot open " << *I << "\n"; + return 1; + } + while (Input.good()) { + std::string Line; + std::getline(Input, Line); + + QueryRef Q = QueryParser::parse(Line.c_str(), QS); + if (!Q->run(llvm::outs(), QS)) + return 1; + } + } + } else { + LineEditor LE("clang-query"); + LE.setListCompleter([&QS](StringRef Line, size_t Pos) { + return QueryParser::complete(Line, Pos, QS); + }); + while (llvm::Optional Line = LE.readLine()) { + QueryRef Q = QueryParser::parse(*Line, QS); + Q->run(llvm::outs(), QS); + llvm::outs().flush(); + if (QS.Terminate) + break; + } + } + + return 0; +} diff --git a/clang-query/tool/Makefile b/clang-query/tool/Makefile new file mode 100644 index 00000000..f62b0b54 --- /dev/null +++ b/clang-query/tool/Makefile @@ -0,0 +1,39 @@ +##===- tools/extra/clang-query/tool/Makefile ---------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL := ../../../.. +include $(CLANG_LEVEL)/../../Makefile.config + +TOOLNAME = clang-query + +# No plugins, optimize startup time. +TOOL_NO_EXPORTS = 1 + +SOURCES = ClangQuery.cpp + +LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader support mc mcparser option +USEDLIBS = clangQuery.a clangDynamicASTMatchers.a clangFormat.a clangTooling.a \ + clangFrontend.a clangSerialization.a clangDriver.a clangRewriteFrontend.a \ + LLVMLineEditor.a clangRewrite.a clangParse.a clangSema.a clangAnalysis.a \ + clangAST.a clangASTMatchers.a clangEdit.a clangLex.a clangBasic.a + +include $(CLANG_LEVEL)/Makefile + +CPP.Flags += -I$(PROJ_SRC_DIR)/.. + +# BUILT_SOURCES gets used as a prereq for many top-level targets. However, at +# the point those targets are defined, $(ObjDir) hasn't been defined and so the +# directory to create becomes // which is not what we want. So instead, +# this .objdir recipe is defined at at point where $(ObjDir) is defined and +# it's specialized to $(ObjDir) to ensure it only works on targets we want it +# to. +$(ObjDir)/%.objdir: + $(Verb) $(MKDIR) $(ObjDir)/$* > /dev/null + $(Verb) $(DOTDIR_TIMESTAMP_COMMAND) > $@ + diff --git a/clang-rename/CMakeLists.txt b/clang-rename/CMakeLists.txt new file mode 100644 index 00000000..559826ce --- /dev/null +++ b/clang-rename/CMakeLists.txt @@ -0,0 +1,16 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangRename + USRFinder.cpp + USRFindingAction.cpp + USRLocFinder.cpp + RenamingAction.cpp + + LINK_LIBS + clangAST + clangBasic + clangIndex + clangToolingCore + ) + +add_subdirectory(tool) diff --git a/clang-rename/Makefile b/clang-rename/Makefile new file mode 100644 index 00000000..d534a78f --- /dev/null +++ b/clang-rename/Makefile @@ -0,0 +1,16 @@ +##===- tools/extra/clang-rename/Makefile -------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL := ../../.. +LIBRARYNAME = clangRename +include $(CLANG_LEVEL)/../../Makefile.config + +DIRS = tool + +include $(CLANG_LEVEL)/Makefile diff --git a/clang-rename/RenamingAction.cpp b/clang-rename/RenamingAction.cpp new file mode 100644 index 00000000..0c2307b9 --- /dev/null +++ b/clang-rename/RenamingAction.cpp @@ -0,0 +1,90 @@ +//===--- tools/extra/clang-rename/RenamingAction.cpp - Clang rename tool --===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Provides an action to rename every symbol at a point. +/// +//===----------------------------------------------------------------------===// + +#include "RenamingAction.h" +#include "USRLocFinder.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/Basic/FileManager.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include +#include +#include +#include +#include + +using namespace llvm; + +namespace clang { +namespace rename { + +class RenamingASTConsumer : public ASTConsumer { +public: + RenamingASTConsumer(const std::string &NewName, + const std::string &PrevName, + const std::vector &USRs, + tooling::Replacements &Replaces, + bool PrintLocations) + : NewName(NewName), PrevName(PrevName), USRs(USRs), Replaces(Replaces), + PrintLocations(PrintLocations) { + } + + void HandleTranslationUnit(ASTContext &Context) override { + const auto &SourceMgr = Context.getSourceManager(); + std::vector RenamingCandidates; + std::vector NewCandidates; + + for (const auto &USR : USRs) { + NewCandidates = getLocationsOfUSR(USR, Context.getTranslationUnitDecl()); + RenamingCandidates.insert(RenamingCandidates.end(), NewCandidates.begin(), + NewCandidates.end()); + NewCandidates.clear(); + } + + auto PrevNameLen = PrevName.length(); + if (PrintLocations) + for (const auto &Loc : RenamingCandidates) { + FullSourceLoc FullLoc(Loc, SourceMgr); + errs() << "clang-rename: renamed at: " << SourceMgr.getFilename(Loc) + << ":" << FullLoc.getSpellingLineNumber() << ":" + << FullLoc.getSpellingColumnNumber() << "\n"; + Replaces.insert(tooling::Replacement(SourceMgr, Loc, PrevNameLen, + NewName)); + } + else + for (const auto &Loc : RenamingCandidates) + Replaces.insert(tooling::Replacement(SourceMgr, Loc, PrevNameLen, + NewName)); + } + +private: + const std::string &NewName, &PrevName; + const std::vector &USRs; + tooling::Replacements &Replaces; + bool PrintLocations; +}; + +std::unique_ptr RenamingAction::newASTConsumer() { + return llvm::make_unique(NewName, PrevName, USRs, + Replaces, PrintLocations); +} + +} +} diff --git a/clang-rename/RenamingAction.h b/clang-rename/RenamingAction.h new file mode 100644 index 00000000..d52f21d7 --- /dev/null +++ b/clang-rename/RenamingAction.h @@ -0,0 +1,47 @@ +//===--- tools/extra/clang-rename/RenamingAction.h - Clang rename tool ----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Provides an action to rename every symbol at a point. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_RENAMING_ACTION_H_ +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_RENAMING_ACTION_H_ + +#include "clang/Tooling/Refactoring.h" + +namespace clang { +class ASTConsumer; +class CompilerInstance; + +namespace rename { + +class RenamingAction { +public: + RenamingAction(const std::string &NewName, const std::string &PrevName, + const std::vector &USRs, + tooling::Replacements &Replaces, bool PrintLocations = false) + : NewName(NewName), PrevName(PrevName), USRs(USRs), Replaces(Replaces), + PrintLocations(PrintLocations) { + } + + std::unique_ptr newASTConsumer(); + +private: + const std::string &NewName, &PrevName; + const std::vector &USRs; + tooling::Replacements &Replaces; + bool PrintLocations; +}; + +} +} + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_RENAMING_ACTION_H_ diff --git a/clang-rename/USRFinder.cpp b/clang-rename/USRFinder.cpp new file mode 100644 index 00000000..2c73c879 --- /dev/null +++ b/clang-rename/USRFinder.cpp @@ -0,0 +1,162 @@ +//===--- tools/extra/clang-rename/USRFinder.cpp - Clang rename tool -------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file Implements a recursive AST visitor that finds the USR of a symbol at a +/// point. +/// +//===----------------------------------------------------------------------===// + +#include "USRFinder.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Index/USRGeneration.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallVector.h" + +using namespace llvm; + +namespace clang { +namespace rename { + +// NamedDeclFindingASTVisitor recursively visits each AST node to find the +// symbol underneath the cursor. +// FIXME: move to seperate .h/.cc file if this gets too large. +namespace { +class NamedDeclFindingASTVisitor + : public clang::RecursiveASTVisitor { +public: + // \brief Finds the NamedDecl at a point in the source. + // \param Point the location in the source to search for the NamedDecl. + explicit NamedDeclFindingASTVisitor(const SourceManager &SourceMgr, + const SourceLocation Point) + : Result(nullptr), SourceMgr(SourceMgr), + Point(Point) { + } + + // Declaration visitors: + + // \brief Checks if the point falls within the NameDecl. This covers every + // declaration of a named entity that we may come across. Usually, just + // checking if the point lies within the length of the name of the declaration + // and the start location is sufficient. + bool VisitNamedDecl(const NamedDecl *Decl) { + return setResult(Decl, Decl->getLocation(), + Decl->getNameAsString().length()); + } + + // Expression visitors: + + bool VisitDeclRefExpr(const DeclRefExpr *Expr) { + // Check the namespace specifier first. + if (!checkNestedNameSpecifierLoc(Expr->getQualifierLoc())) + return false; + + const auto *Decl = Expr->getFoundDecl(); + return setResult(Decl, Expr->getLocation(), + Decl->getNameAsString().length()); + } + + bool VisitMemberExpr(const MemberExpr *Expr) { + const auto *Decl = Expr->getFoundDecl().getDecl(); + return setResult(Decl, Expr->getMemberLoc(), + Decl->getNameAsString().length()); + } + + // Other: + + const NamedDecl *getNamedDecl() { + return Result; + } + +private: + // \brief Determines if a namespace qualifier contains the point. + // \returns false on success and sets Result. + bool checkNestedNameSpecifierLoc(NestedNameSpecifierLoc NameLoc) { + while (NameLoc) { + const auto *Decl = NameLoc.getNestedNameSpecifier()->getAsNamespace(); + if (Decl && !setResult(Decl, NameLoc.getLocalBeginLoc(), + Decl->getNameAsString().length())) + return false; + NameLoc = NameLoc.getPrefix(); + } + return true; + } + + // \brief Sets Result to Decl if the Point is within Start and End. + // \returns false on success. + bool setResult(const NamedDecl *Decl, SourceLocation Start, + SourceLocation End) { + if (!Start.isValid() || !Start.isFileID() || !End.isValid() || + !End.isFileID() || !isPointWithin(Start, End)) { + return true; + } + Result = Decl; + return false; + } + + // \brief Sets Result to Decl if Point is within Loc and Loc + Offset. + // \returns false on success. + bool setResult(const NamedDecl *Decl, SourceLocation Loc, + unsigned Offset) { + // FIXME: Add test for Offset == 0. Add test for Offset - 1 (vs -2 etc). + return Offset == 0 || + setResult(Decl, Loc, Loc.getLocWithOffset(Offset - 1)); + } + + // \brief Determines if the Point is within Start and End. + bool isPointWithin(const SourceLocation Start, const SourceLocation End) { + // FIXME: Add tests for Point == End. + return Point == Start || Point == End || + (SourceMgr.isBeforeInTranslationUnit(Start, Point) && + SourceMgr.isBeforeInTranslationUnit(Point, End)); + } + + const NamedDecl *Result; + const SourceManager &SourceMgr; + const SourceLocation Point; // The location to find the NamedDecl. +}; +} + +const NamedDecl *getNamedDeclAt(const ASTContext &Context, + const SourceLocation Point) { + const auto &SourceMgr = Context.getSourceManager(); + const auto SearchFile = SourceMgr.getFilename(Point); + + NamedDeclFindingASTVisitor Visitor(SourceMgr, Point); + + // We only want to search the decls that exist in the same file as the point. + auto Decls = Context.getTranslationUnitDecl()->decls(); + for (auto &CurrDecl : Decls) { + const auto FileLoc = CurrDecl->getLocStart(); + const auto FileName = SourceMgr.getFilename(FileLoc); + // FIXME: Add test. + if (FileName == SearchFile) { + Visitor.TraverseDecl(CurrDecl); + if (const NamedDecl *Result = Visitor.getNamedDecl()) { + return Result; + } + } + } + + return nullptr; +} + +std::string getUSRForDecl(const Decl *Decl) { + llvm::SmallVector Buff; + + // FIXME: Add test for the nullptr case. + if (Decl == nullptr || index::generateUSRForDecl(Decl, Buff)) + return ""; + + return std::string(Buff.data(), Buff.size()); +} + +} // namespace clang +} // namespace rename diff --git a/clang-rename/USRFinder.h b/clang-rename/USRFinder.h new file mode 100644 index 00000000..2de6f427 --- /dev/null +++ b/clang-rename/USRFinder.h @@ -0,0 +1,39 @@ +//===--- tools/extra/clang-rename/USRFinder.h - Clang rename tool ---------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Methods for determining the USR of a symbol at a location in source +/// code. +/// +//===----------------------------------------------------------------------===// +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDER_H + +#include + +namespace clang { +class ASTContext; +class Decl; +class SourceLocation; +class NamedDecl; + +namespace rename { + +// Given an AST context and a point, returns a NamedDecl identifying the symbol +// at the point. Returns null if nothing is found at the point. +const NamedDecl *getNamedDeclAt(const ASTContext &Context, + const SourceLocation Point); + +// Converts a Decl into a USR. +std::string getUSRForDecl(const Decl *Decl); + +} +} + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDER_H diff --git a/clang-rename/USRFindingAction.cpp b/clang-rename/USRFindingAction.cpp new file mode 100644 index 00000000..da925628 --- /dev/null +++ b/clang-rename/USRFindingAction.cpp @@ -0,0 +1,118 @@ +//===--- tools/extra/clang-rename/USRFindingAction.cpp - Clang rename tool ===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Provides an action to rename every symbol at a point. +/// +//===----------------------------------------------------------------------===// + +#include "USRFindingAction.h" +#include "USRFinder.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/Basic/FileManager.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include +#include +#include +#include +#include + +using namespace llvm; + +namespace clang { +namespace rename { + +// Get the USRs for the constructors of the class. +static std::vector getAllConstructorUSRs( + const CXXRecordDecl *Decl) { + std::vector USRs; + + // We need to get the definition of the record (as opposed to any forward + // declarations) in order to find the constructor and destructor. + const auto *RecordDecl = Decl->getDefinition(); + + // Iterate over all the constructors and add their USRs. + for (const auto *CtorDecl : RecordDecl->ctors()) + USRs.push_back(getUSRForDecl(CtorDecl)); + + // Ignore destructors. GetLocationsOfUSR will find the declaration of and + // explicit calls to a destructor through TagTypeLoc (and it is better for the + // purpose of renaming). + // + // For example, in the following code segment, + // 1 class C { + // 2 ~C(); + // 3 }; + // At line 3, there is a NamedDecl starting from '~' and a TagTypeLoc starting + // from 'C'. + + return USRs; +} + +struct NamedDeclFindingConsumer : public ASTConsumer { + void HandleTranslationUnit(ASTContext &Context) override { + const auto &SourceMgr = Context.getSourceManager(); + // The file we look for the USR in will always be the main source file. + const auto Point = SourceMgr.getLocForStartOfFile( + SourceMgr.getMainFileID()).getLocWithOffset(SymbolOffset); + if (!Point.isValid()) + return; + const NamedDecl *FoundDecl = getNamedDeclAt(Context, Point); + if (FoundDecl == nullptr) { + FullSourceLoc FullLoc(Point, SourceMgr); + errs() << "clang-rename: could not find symbol at " + << SourceMgr.getFilename(Point) << ":" + << FullLoc.getSpellingLineNumber() << ":" + << FullLoc.getSpellingColumnNumber() << " (offset " << SymbolOffset + << ").\n"; + return; + } + + // If the decl is a constructor or destructor, we want to instead take the + // decl of the parent record. + if (const auto *CtorDecl = dyn_cast(FoundDecl)) + FoundDecl = CtorDecl->getParent(); + else if (const auto *DtorDecl = dyn_cast(FoundDecl)) + FoundDecl = DtorDecl->getParent(); + + // If the decl is in any way relatedpp to a class, we want to make sure we + // search for the constructor and destructor as well as everything else. + if (const auto *Record = dyn_cast(FoundDecl)) + *USRs = getAllConstructorUSRs(Record); + + USRs->push_back(getUSRForDecl(FoundDecl)); + *SpellingName = FoundDecl->getNameAsString(); + } + + unsigned SymbolOffset; + std::string *SpellingName; + std::vector *USRs; +}; + +std::unique_ptr +USRFindingAction::newASTConsumer() { + std::unique_ptr Consumer( + new NamedDeclFindingConsumer); + SpellingName = ""; + Consumer->SymbolOffset = SymbolOffset; + Consumer->USRs = &USRs; + Consumer->SpellingName = &SpellingName; + return std::move(Consumer); +} + +} // namespace rename +} // namespace clang diff --git a/clang-rename/USRFindingAction.h b/clang-rename/USRFindingAction.h new file mode 100644 index 00000000..4c059a56 --- /dev/null +++ b/clang-rename/USRFindingAction.h @@ -0,0 +1,50 @@ +//===--- tools/extra/clang-rename/USRFindingAction.h - Clang rename tool --===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Provides an action to find all relevent USRs at a point. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDING_ACTION_H_ +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDING_ACTION_H_ + +#include "clang/Frontend/FrontendAction.h" + +namespace clang { +class ASTConsumer; +class CompilerInstance; +class NamedDecl; + +namespace rename { + +struct USRFindingAction { + USRFindingAction(unsigned Offset) : SymbolOffset(Offset) { + } + std::unique_ptr newASTConsumer(); + + // \brief get the spelling of the USR(s) as it would appear in source files. + const std::string &getUSRSpelling() { + return SpellingName; + } + + const std::vector &getUSRs() { + return USRs; + } + +private: + unsigned SymbolOffset; + std::string SpellingName; + std::vector USRs; +}; + +} +} + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDING_ACTION_H_ diff --git a/clang-rename/USRLocFinder.cpp b/clang-rename/USRLocFinder.cpp new file mode 100644 index 00000000..fc8dfca3 --- /dev/null +++ b/clang-rename/USRLocFinder.cpp @@ -0,0 +1,103 @@ +//===--- tools/extra/clang-rename/USRLocFinder.cpp - Clang rename tool ----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Mehtods for finding all instances of a USR. Our strategy is very +/// simple; we just compare the USR at every relevant AST node with the one +/// provided. +/// +//===----------------------------------------------------------------------===// + +#include "USRLocFinder.h" +#include "USRFinder.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Index/USRGeneration.h" +#include "llvm/ADT/SmallVector.h" + +using namespace llvm; + +namespace clang { +namespace rename { + +namespace { +// \brief This visitor recursively searches for all instances of a USR in a +// translation unit and stores them for later usage. +class USRLocFindingASTVisitor + : public clang::RecursiveASTVisitor { +public: + explicit USRLocFindingASTVisitor(const std::string USR) : USR(USR) { + } + + // Declaration visitors: + + bool VisitNamedDecl(const NamedDecl *Decl) { + if (getUSRForDecl(Decl) == USR) { + LocationsFound.push_back(Decl->getLocation()); + } + return true; + } + + // Expression visitors: + + bool VisitDeclRefExpr(const DeclRefExpr *Expr) { + const auto *Decl = Expr->getFoundDecl(); + + checkNestedNameSpecifierLoc(Expr->getQualifierLoc()); + if (getUSRForDecl(Decl) == USR) { + LocationsFound.push_back(Expr->getLocation()); + } + + return true; + } + + bool VisitMemberExpr(const MemberExpr *Expr) { + const auto *Decl = Expr->getFoundDecl().getDecl(); + if (getUSRForDecl(Decl) == USR) { + LocationsFound.push_back(Expr->getMemberLoc()); + } + return true; + } + + // Non-visitors: + + // \brief Returns a list of unique locations. Duplicate or overlapping + // locations are erroneous and should be reported! + const std::vector &getLocationsFound() const { + return LocationsFound; + } + +private: + // Namespace traversal: + void checkNestedNameSpecifierLoc(NestedNameSpecifierLoc NameLoc) { + while (NameLoc) { + const auto *Decl = NameLoc.getNestedNameSpecifier()->getAsNamespace(); + if (Decl && getUSRForDecl(Decl) == USR) + LocationsFound.push_back(NameLoc.getLocalBeginLoc()); + NameLoc = NameLoc.getPrefix(); + } + } + + // All the locations of the USR were found. + const std::string USR; + std::vector LocationsFound; +}; +} // namespace + +std::vector getLocationsOfUSR(const std::string USR, + Decl *Decl) { + USRLocFindingASTVisitor visitor(USR); + + visitor.TraverseDecl(Decl); + return visitor.getLocationsFound(); +} + +} // namespace rename +} // namespace clang diff --git a/clang-rename/USRLocFinder.h b/clang-rename/USRLocFinder.h new file mode 100644 index 00000000..7ed0e0f8 --- /dev/null +++ b/clang-rename/USRLocFinder.h @@ -0,0 +1,35 @@ +//===--- tools/extra/clang-rename/USRLocFinder.h - Clang rename tool ------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Provides functionality for finding all instances of a USR in a given +/// AST. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_LOC_FINDER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_LOC_FINDER_H + +#include +#include + +namespace clang { + +class Decl; +class SourceLocation; + +namespace rename { + +// FIXME: make this an AST matcher. Wouldn't that be awesome??? I agree! +std::vector getLocationsOfUSR(const std::string usr, + Decl *decl); +} +} + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_LOC_FINDER_H diff --git a/clang-rename/tool/CMakeLists.txt b/clang-rename/tool/CMakeLists.txt new file mode 100644 index 00000000..a393b0a0 --- /dev/null +++ b/clang-rename/tool/CMakeLists.txt @@ -0,0 +1,11 @@ +add_clang_executable(clang-rename ClangRename.cpp) + +target_link_libraries(clang-rename + clangBasic + clangFrontend + clangRename + clangRewrite + clangTooling + ) + +install(TARGETS clang-rename RUNTIME DESTINATION bin) diff --git a/clang-rename/tool/ClangRename.cpp b/clang-rename/tool/ClangRename.cpp new file mode 100644 index 00000000..793e471e --- /dev/null +++ b/clang-rename/tool/ClangRename.cpp @@ -0,0 +1,151 @@ +//===--- tools/extra/clang-rename/ClangRename.cpp - Clang rename tool -----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief This file implements a clang-rename tool that automatically finds and +/// renames symbols in C++ code. +/// +//===----------------------------------------------------------------------===// + +#include "../USRFindingAction.h" +#include "../RenamingAction.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/Basic/TargetOptions.h" +#include "clang/Frontend/CommandLineSourceLoc.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Parse/ParseAST.h" +#include "clang/Parse/Parser.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/Support/Host.h" +#include +#include +#include +#include +#include +#include + +using namespace llvm; + +cl::OptionCategory ClangRenameCategory("Clang-rename options"); + +static cl::opt +NewName( + "new-name", + cl::desc("The new name to change the symbol to."), + cl::cat(ClangRenameCategory)); +static cl::opt +SymbolOffset( + "offset", + cl::desc("Locates the symbol by offset as opposed to :."), + cl::cat(ClangRenameCategory)); +static cl::opt +Inplace( + "i", + cl::desc("Overwrite edited s."), + cl::cat(ClangRenameCategory)); +static cl::opt +PrintName( + "pn", + cl::desc("Print the found symbol's name prior to renaming to stderr."), + cl::cat(ClangRenameCategory)); +static cl::opt +PrintLocations( + "pl", + cl::desc("Print the locations affected by renaming to stderr."), + cl::cat(ClangRenameCategory)); + +#define CLANG_RENAME_VERSION "0.0.1" + +static void PrintVersion() { + outs() << "clang-rename version " << CLANG_RENAME_VERSION << "\n"; +} + +using namespace clang; + +const char RenameUsage[] = "A tool to rename symbols in C/C++ code.\n\ +clang-rename renames every occurrence of a symbol found at in\n\ +. If -i is specified, the edited files are overwritten to disk.\n\ +Otherwise, the results are written to stdout.\n"; + +int main(int argc, const char **argv) { + cl::SetVersionPrinter(PrintVersion); + tooling::CommonOptionsParser OP(argc, argv, ClangRenameCategory, RenameUsage); + + // Check the arguments for correctness. + + if (NewName.empty()) { + errs() << "clang-rename: no new name provided.\n\n"; + cl::PrintHelpMessage(); + exit(1); + } + + // Get the USRs. + auto Files = OP.getSourcePathList(); + tooling::RefactoringTool Tool(OP.getCompilations(), Files); + rename::USRFindingAction USRAction(SymbolOffset); + + // Find the USRs. + Tool.run(tooling::newFrontendActionFactory(&USRAction).get()); + const auto &USRs = USRAction.getUSRs(); + const auto &PrevName = USRAction.getUSRSpelling(); + + if (PrevName.empty()) + // An error should have already been printed. + exit(1); + + if (PrintName) + errs() << "clang-rename: found name: " << PrevName; + + // Perform the renaming. + rename::RenamingAction RenameAction(NewName, PrevName, USRs, + Tool.getReplacements(), PrintLocations); + auto Factory = tooling::newFrontendActionFactory(&RenameAction); + int res; + + if (Inplace) { + res = Tool.runAndSave(Factory.get()); + } else { + res = Tool.run(Factory.get()); + + // Write every file to stdout. Right now we just barf the files without any + // indication of which files start where, other than that we print the files + // in the same order we see them. + LangOptions DefaultLangOptions; + IntrusiveRefCntPtr DiagOpts = + new DiagnosticOptions(); + TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(new DiagnosticIDs()), + &*DiagOpts, &DiagnosticPrinter, false); + auto &FileMgr = Tool.getFiles(); + SourceManager Sources(Diagnostics, FileMgr); + Rewriter Rewrite(Sources, DefaultLangOptions); + + Tool.applyAllReplacements(Rewrite); + for (const auto &File : Files) { + const auto *Entry = FileMgr.getFile(File); + auto ID = Sources.translateFile(Entry); + Rewrite.getEditBuffer(ID).write(outs()); + } + } + + exit(res); +} diff --git a/clang-rename/tool/Makefile b/clang-rename/tool/Makefile new file mode 100644 index 00000000..b3d9555f --- /dev/null +++ b/clang-rename/tool/Makefile @@ -0,0 +1,13 @@ +CLANG_LEVEL := ../../../.. +TOOLNAME = clang-rename +include $(CLANG_LEVEL)/../../Makefile.config +LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader support mc option +USEDLIBS = clangRename.a clangFrontend.a clangSerialization.a clangDriver.a \ + clangTooling.a clangToolingCore.a \ + clangParse.a clangSema.a clangIndex.a \ + clangStaticAnalyzerFrontend.a clangStaticAnalyzerCheckers.a \ + clangStaticAnalyzerCore.a clangAnalysis.a clangRewriteFrontend.a \ + clangRewrite.a clangEdit.a clangAST.a clangLex.a clangBasic.a + +include $(CLANG_LEVEL)/Makefile + diff --git a/clang-tidy/CMakeLists.txt b/clang-tidy/CMakeLists.txt new file mode 100644 index 00000000..a870eb5d --- /dev/null +++ b/clang-tidy/CMakeLists.txt @@ -0,0 +1,37 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_library(clangTidy + ClangTidy.cpp + ClangTidyModule.cpp + ClangTidyDiagnosticConsumer.cpp + ClangTidyOptions.cpp + + DEPENDS + ClangSACheckers + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangFrontend + clangLex + clangRewrite + clangSema + clangStaticAnalyzerCore + clangStaticAnalyzerFrontend + clangTooling + clangToolingCore + ) + +add_subdirectory(tool) +add_subdirectory(cert) +add_subdirectory(llvm) +add_subdirectory(cppcoreguidelines) +add_subdirectory(google) +add_subdirectory(misc) +add_subdirectory(modernize) +add_subdirectory(performance) +add_subdirectory(readability) +add_subdirectory(utils) diff --git a/clang-tidy/ClangTidy.cpp b/clang-tidy/ClangTidy.cpp new file mode 100644 index 00000000..0b9895bc --- /dev/null +++ b/clang-tidy/ClangTidy.cpp @@ -0,0 +1,443 @@ +//===--- tools/extra/clang-tidy/ClangTidy.cpp - Clang tidy tool -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file This file implements a clang-tidy tool. +/// +/// This tool uses the Clang Tooling infrastructure, see +/// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +/// for details on setting it up with LLVM source tree. +/// +//===----------------------------------------------------------------------===// + +#include "ClangTidy.h" +#include "ClangTidyDiagnosticConsumer.h" +#include "ClangTidyModuleRegistry.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/ASTConsumers.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/Frontend/MultiplexConsumer.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Rewrite/Frontend/FixItRewriter.h" +#include "clang/Rewrite/Frontend/FrontendActions.h" +#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/ReplacementsYaml.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/Signals.h" +#include +#include + +using namespace clang::ast_matchers; +using namespace clang::driver; +using namespace clang::tooling; +using namespace llvm; + +template class llvm::Registry; + +namespace clang { +namespace tidy { + +namespace { +static const char *AnalyzerCheckNamePrefix = "clang-analyzer-"; + +static const StringRef StaticAnalyzerChecks[] = { +#define GET_CHECKERS +#define CHECKER(FULLNAME, CLASS, DESCFILE, HELPTEXT, GROUPINDEX, HIDDEN) \ + FULLNAME, +#include "../../../lib/StaticAnalyzer/Checkers/Checkers.inc" +#undef CHECKER +#undef GET_CHECKERS +}; + +class AnalyzerDiagnosticConsumer : public ento::PathDiagnosticConsumer { +public: + AnalyzerDiagnosticConsumer(ClangTidyContext &Context) : Context(Context) {} + + void FlushDiagnosticsImpl(std::vector &Diags, + FilesMade *filesMade) override { + for (const ento::PathDiagnostic *PD : Diags) { + SmallString<64> CheckName(AnalyzerCheckNamePrefix); + CheckName += PD->getCheckName(); + Context.diag(CheckName, PD->getLocation().asLocation(), + PD->getShortDescription()) + << PD->path.back()->getRanges(); + + for (const auto &DiagPiece : + PD->path.flatten(/*ShouldFlattenMacros=*/true)) { + Context.diag(CheckName, DiagPiece->getLocation().asLocation(), + DiagPiece->getString(), DiagnosticIDs::Note) + << DiagPiece->getRanges(); + } + } + } + + StringRef getName() const override { return "ClangTidyDiags"; } + bool supportsLogicalOpControlFlow() const override { return true; } + bool supportsCrossFileDiagnostics() const override { return true; } + +private: + ClangTidyContext &Context; +}; + +class ErrorReporter { +public: + ErrorReporter(bool ApplyFixes) + : Files(FileSystemOptions()), DiagOpts(new DiagnosticOptions()), + DiagPrinter(new TextDiagnosticPrinter(llvm::outs(), &*DiagOpts)), + Diags(IntrusiveRefCntPtr(new DiagnosticIDs), &*DiagOpts, + DiagPrinter), + SourceMgr(Diags, Files), Rewrite(SourceMgr, LangOpts), + ApplyFixes(ApplyFixes), TotalFixes(0), AppliedFixes(0) { + DiagOpts->ShowColors = llvm::sys::Process::StandardOutHasColors(); + DiagPrinter->BeginSourceFile(LangOpts); + } + + void reportDiagnostic(const ClangTidyError &Error) { + const ClangTidyMessage &Message = Error.Message; + SourceLocation Loc = getLocation(Message.FilePath, Message.FileOffset); + // Contains a pair for each attempted fix: location and whether the fix was + // applied successfully. + SmallVector, 4> FixLocations; + { + auto Level = static_cast(Error.DiagLevel); + auto Diag = Diags.Report(Loc, Diags.getCustomDiagID(Level, "%0 [%1]")) + << Message.Message << Error.CheckName; + for (const tooling::Replacement &Fix : Error.Fix) { + SourceLocation FixLoc = getLocation(Fix.getFilePath(), Fix.getOffset()); + SourceLocation FixEndLoc = FixLoc.getLocWithOffset(Fix.getLength()); + Diag << FixItHint::CreateReplacement(SourceRange(FixLoc, FixEndLoc), + Fix.getReplacementText()); + ++TotalFixes; + if (ApplyFixes) { + bool Success = Fix.isApplicable() && Fix.apply(Rewrite); + if (Success) + ++AppliedFixes; + FixLocations.push_back(std::make_pair(FixLoc, Success)); + } + } + } + for (auto Fix : FixLocations) { + Diags.Report(Fix.first, Fix.second ? diag::note_fixit_applied + : diag::note_fixit_failed); + } + for (const ClangTidyMessage &Note : Error.Notes) + reportNote(Note); + } + + void Finish() { + // FIXME: Run clang-format on changes. + if (ApplyFixes && TotalFixes > 0) { + llvm::errs() << "clang-tidy applied " << AppliedFixes << " of " + << TotalFixes << " suggested fixes.\n"; + Rewrite.overwriteChangedFiles(); + } + } + +private: + SourceLocation getLocation(StringRef FilePath, unsigned Offset) { + if (FilePath.empty()) + return SourceLocation(); + + const FileEntry *File = SourceMgr.getFileManager().getFile(FilePath); + FileID ID = SourceMgr.createFileID(File, SourceLocation(), SrcMgr::C_User); + return SourceMgr.getLocForStartOfFile(ID).getLocWithOffset(Offset); + } + + void reportNote(const ClangTidyMessage &Message) { + SourceLocation Loc = getLocation(Message.FilePath, Message.FileOffset); + DiagnosticBuilder Diag = + Diags.Report(Loc, Diags.getCustomDiagID(DiagnosticsEngine::Note, "%0")) + << Message.Message; + } + + FileManager Files; + LangOptions LangOpts; // FIXME: use langopts from each original file + IntrusiveRefCntPtr DiagOpts; + DiagnosticConsumer *DiagPrinter; + DiagnosticsEngine Diags; + SourceManager SourceMgr; + Rewriter Rewrite; + bool ApplyFixes; + unsigned TotalFixes; + unsigned AppliedFixes; +}; + +class ClangTidyASTConsumer : public MultiplexConsumer { +public: + ClangTidyASTConsumer(std::vector> Consumers, + std::unique_ptr Finder, + std::vector> Checks) + : MultiplexConsumer(std::move(Consumers)), Finder(std::move(Finder)), + Checks(std::move(Checks)) {} + +private: + std::unique_ptr Finder; + std::vector> Checks; +}; + +} // namespace + +ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory( + ClangTidyContext &Context) + : Context(Context), CheckFactories(new ClangTidyCheckFactories) { + for (ClangTidyModuleRegistry::iterator I = ClangTidyModuleRegistry::begin(), + E = ClangTidyModuleRegistry::end(); + I != E; ++I) { + std::unique_ptr Module(I->instantiate()); + Module->addCheckFactories(*CheckFactories); + } +} + +static void setStaticAnalyzerCheckerOpts(const ClangTidyOptions &Opts, + AnalyzerOptionsRef AnalyzerOptions) { + StringRef AnalyzerPrefix(AnalyzerCheckNamePrefix); + for (const auto &Opt : Opts.CheckOptions) { + StringRef OptName(Opt.first); + if (!OptName.startswith(AnalyzerPrefix)) + continue; + AnalyzerOptions->Config[OptName.substr(AnalyzerPrefix.size())] = Opt.second; + } +} + +std::unique_ptr +ClangTidyASTConsumerFactory::CreateASTConsumer( + clang::CompilerInstance &Compiler, StringRef File) { + // FIXME: Move this to a separate method, so that CreateASTConsumer doesn't + // modify Compiler. + Context.setSourceManager(&Compiler.getSourceManager()); + Context.setCurrentFile(File); + Context.setASTContext(&Compiler.getASTContext()); + + std::vector> Checks; + CheckFactories->createChecks(&Context, Checks); + + ast_matchers::MatchFinder::MatchFinderOptions FinderOptions; + if (auto *P = Context.getCheckProfileData()) + FinderOptions.CheckProfiling.emplace(P->Records); + + std::unique_ptr Finder( + new ast_matchers::MatchFinder(std::move(FinderOptions))); + + for (auto &Check : Checks) { + Check->registerMatchers(&*Finder); + Check->registerPPCallbacks(Compiler); + } + + std::vector> Consumers; + if (!Checks.empty()) + Consumers.push_back(Finder->newASTConsumer()); + + AnalyzerOptionsRef AnalyzerOptions = Compiler.getAnalyzerOpts(); + // FIXME: Remove this option once clang's cfg-temporary-dtors option defaults + // to true. + AnalyzerOptions->Config["cfg-temporary-dtors"] = + Context.getOptions().AnalyzeTemporaryDtors ? "true" : "false"; + + GlobList &Filter = Context.getChecksFilter(); + AnalyzerOptions->CheckersControlList = getCheckersControlList(Filter); + if (!AnalyzerOptions->CheckersControlList.empty()) { + setStaticAnalyzerCheckerOpts(Context.getOptions(), AnalyzerOptions); + AnalyzerOptions->AnalysisStoreOpt = RegionStoreModel; + AnalyzerOptions->AnalysisDiagOpt = PD_NONE; + AnalyzerOptions->AnalyzeNestedBlocks = true; + AnalyzerOptions->eagerlyAssumeBinOpBifurcation = true; + std::unique_ptr AnalysisConsumer = + ento::CreateAnalysisConsumer(Compiler); + AnalysisConsumer->AddDiagnosticConsumer( + new AnalyzerDiagnosticConsumer(Context)); + Consumers.push_back(std::move(AnalysisConsumer)); + } + return llvm::make_unique( + std::move(Consumers), std::move(Finder), std::move(Checks)); +} + +std::vector ClangTidyASTConsumerFactory::getCheckNames() { + std::vector CheckNames; + GlobList &Filter = Context.getChecksFilter(); + for (const auto &CheckFactory : *CheckFactories) { + if (Filter.contains(CheckFactory.first)) + CheckNames.push_back(CheckFactory.first); + } + + for (const auto &AnalyzerCheck : getCheckersControlList(Filter)) + CheckNames.push_back(AnalyzerCheckNamePrefix + AnalyzerCheck.first); + + std::sort(CheckNames.begin(), CheckNames.end()); + return CheckNames; +} + +ClangTidyOptions::OptionMap ClangTidyASTConsumerFactory::getCheckOptions() { + ClangTidyOptions::OptionMap Options; + std::vector> Checks; + CheckFactories->createChecks(&Context, Checks); + for (const auto &Check : Checks) + Check->storeOptions(Options); + return Options; +} + +ClangTidyASTConsumerFactory::CheckersList +ClangTidyASTConsumerFactory::getCheckersControlList(GlobList &Filter) { + CheckersList List; + + bool AnalyzerChecksEnabled = false; + for (StringRef CheckName : StaticAnalyzerChecks) { + std::string Checker((AnalyzerCheckNamePrefix + CheckName).str()); + AnalyzerChecksEnabled = + AnalyzerChecksEnabled || + (!CheckName.startswith("debug") && Filter.contains(Checker)); + } + + if (AnalyzerChecksEnabled) { + // Run our regex against all possible static analyzer checkers. Note that + // debug checkers print values / run programs to visualize the CFG and are + // thus not applicable to clang-tidy in general. + // + // Always add all core checkers if any other static analyzer checks are + // enabled. This is currently necessary, as other path sensitive checks + // rely on the core checkers. + for (StringRef CheckName : StaticAnalyzerChecks) { + std::string Checker((AnalyzerCheckNamePrefix + CheckName).str()); + + if (CheckName.startswith("core") || + (!CheckName.startswith("debug") && Filter.contains(Checker))) + List.push_back(std::make_pair(CheckName, true)); + } + } + return List; +} + +DiagnosticBuilder ClangTidyCheck::diag(SourceLocation Loc, StringRef Message, + DiagnosticIDs::Level Level) { + return Context->diag(CheckName, Loc, Message, Level); +} + +void ClangTidyCheck::run(const ast_matchers::MatchFinder::MatchResult &Result) { + Context->setSourceManager(Result.SourceManager); + check(Result); +} + +OptionsView::OptionsView(StringRef CheckName, + const ClangTidyOptions::OptionMap &CheckOptions) + : NamePrefix(CheckName.str() + "."), CheckOptions(CheckOptions) {} + +std::string OptionsView::get(StringRef LocalName, std::string Default) const { + const auto &Iter = CheckOptions.find(NamePrefix + LocalName.str()); + if (Iter != CheckOptions.end()) + return Iter->second; + return Default; +} + +void OptionsView::store(ClangTidyOptions::OptionMap &Options, + StringRef LocalName, StringRef Value) const { + Options[NamePrefix + LocalName.str()] = Value; +} + +void OptionsView::store(ClangTidyOptions::OptionMap &Options, + StringRef LocalName, int64_t Value) const { + store(Options, LocalName, llvm::itostr(Value)); +} + +std::vector getCheckNames(const ClangTidyOptions &Options) { + clang::tidy::ClangTidyContext Context( + llvm::make_unique(ClangTidyGlobalOptions(), + Options)); + ClangTidyASTConsumerFactory Factory(Context); + return Factory.getCheckNames(); +} + +ClangTidyOptions::OptionMap getCheckOptions(const ClangTidyOptions &Options) { + clang::tidy::ClangTidyContext Context( + llvm::make_unique(ClangTidyGlobalOptions(), + Options)); + ClangTidyASTConsumerFactory Factory(Context); + return Factory.getCheckOptions(); +} + +ClangTidyStats +runClangTidy(std::unique_ptr OptionsProvider, + const tooling::CompilationDatabase &Compilations, + ArrayRef InputFiles, + std::vector *Errors, ProfileData *Profile) { + ClangTool Tool(Compilations, InputFiles); + clang::tidy::ClangTidyContext Context(std::move(OptionsProvider)); + ArgumentsAdjuster PerFileExtraArgumentsInserter = [&Context]( + const CommandLineArguments &Args, StringRef Filename) { + ClangTidyOptions Opts = Context.getOptionsForFile(Filename); + CommandLineArguments AdjustedArgs; + if (Opts.ExtraArgsBefore) + AdjustedArgs = *Opts.ExtraArgsBefore; + AdjustedArgs.insert(AdjustedArgs.begin(), Args.begin(), Args.end()); + if (Opts.ExtraArgs) + AdjustedArgs.insert(AdjustedArgs.end(), Opts.ExtraArgs->begin(), + Opts.ExtraArgs->end()); + return AdjustedArgs; + }; + Tool.appendArgumentsAdjuster(PerFileExtraArgumentsInserter); + if (Profile) + Context.setCheckProfileData(Profile); + + ClangTidyDiagnosticConsumer DiagConsumer(Context); + + Tool.setDiagnosticConsumer(&DiagConsumer); + + class ActionFactory : public FrontendActionFactory { + public: + ActionFactory(ClangTidyContext &Context) : ConsumerFactory(Context) {} + FrontendAction *create() override { return new Action(&ConsumerFactory); } + + private: + class Action : public ASTFrontendAction { + public: + Action(ClangTidyASTConsumerFactory *Factory) : Factory(Factory) {} + std::unique_ptr CreateASTConsumer(CompilerInstance &Compiler, + StringRef File) override { + return Factory->CreateASTConsumer(Compiler, File); + } + + private: + ClangTidyASTConsumerFactory *Factory; + }; + + ClangTidyASTConsumerFactory ConsumerFactory; + }; + + ActionFactory Factory(Context); + Tool.run(&Factory); + *Errors = Context.getErrors(); + return Context.getStats(); +} + +void handleErrors(const std::vector &Errors, bool Fix) { + ErrorReporter Reporter(Fix); + for (const ClangTidyError &Error : Errors) + Reporter.reportDiagnostic(Error); + Reporter.Finish(); +} + +void exportReplacements(const std::vector &Errors, + raw_ostream &OS) { + tooling::TranslationUnitReplacements TUR; + for (const ClangTidyError &Error : Errors) + TUR.Replacements.insert(TUR.Replacements.end(), Error.Fix.begin(), + Error.Fix.end()); + + yaml::Output YAML(OS); + YAML << TUR; +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/ClangTidy.h b/clang-tidy/ClangTidy.h new file mode 100644 index 00000000..a2dbcbc5 --- /dev/null +++ b/clang-tidy/ClangTidy.h @@ -0,0 +1,227 @@ +//===--- ClangTidy.h - clang-tidy -------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDY_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDY_H + +#include "ClangTidyDiagnosticConsumer.h" +#include "ClangTidyOptions.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Tooling/Refactoring.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include + +namespace clang { + +class CompilerInstance; +namespace tooling { +class CompilationDatabase; +} + +namespace tidy { + +/// \brief Provides access to the \c ClangTidyCheck options via check-local +/// names. +/// +/// Methods of this class prepend CheckName + "." to translate +/// check-local option names to global option names. +class OptionsView { +public: + /// \brief Initializes the instance using \p CheckName + "." as a prefix. + OptionsView(StringRef CheckName, + const ClangTidyOptions::OptionMap &CheckOptions); + + /// \brief Read a named option from the \c Context. + /// + /// Reads the option with the check-local name \p LocalName from the + /// \c CheckOptions. If the corresponding key is not present, returns + /// \p Default. + std::string get(StringRef LocalName, std::string Default) const; + + /// \brief Read a named option from the \c Context and parse it as an integral + /// type \c T. + /// + /// Reads the option with the check-local name \p LocalName from the + /// \c CheckOptions. If the corresponding key is not present, returns + /// \p Default. + template + typename std::enable_if::value, T>::type + get(StringRef LocalName, T Default) const { + std::string Value = get(LocalName, ""); + T Result = Default; + if (!Value.empty()) + StringRef(Value).getAsInteger(10, Result); + return Result; + } + + /// \brief Stores an option with the check-local name \p LocalName with string + /// value \p Value to \p Options. + void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, + StringRef Value) const; + + /// \brief Stores an option with the check-local name \p LocalName with + /// \c int64_t value \p Value to \p Options. + void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, + int64_t Value) const; + +private: + std::string NamePrefix; + const ClangTidyOptions::OptionMap &CheckOptions; +}; + +/// \brief Base class for all clang-tidy checks. +/// +/// To implement a \c ClangTidyCheck, write a subclass and override some of the +/// base class's methods. E.g. to implement a check that validates namespace +/// declarations, override \c registerMatchers: +/// +/// \code +/// registerMatchers(ast_matchers::MatchFinder *Finder) { +/// Finder->addMatcher(namespaceDecl().bind("namespace"), this); +/// } +/// \endcode +/// +/// and then override \c check(const MatchResult &Result) to do the actual +/// check for each match. +/// +/// A new \c ClangTidyCheck instance is created per translation unit. +/// +/// FIXME: Figure out whether carrying information from one TU to another is +/// useful/necessary. +class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback { +public: + /// \brief Initializes the check with \p CheckName and \p Context. + /// + /// Derived classes must implement the constructor with this signature or + /// delegate it. If a check needs to read options, it can do this in the + /// constructor using the Options.get() methods below. + ClangTidyCheck(StringRef CheckName, ClangTidyContext *Context) + : CheckName(CheckName), Context(Context), + Options(CheckName, Context->getOptions().CheckOptions) { + assert(Context != nullptr); + assert(!CheckName.empty()); + } + + /// \brief Override this to register \c PPCallbacks with \c Compiler. + /// + /// This should be used for clang-tidy checks that analyze preprocessor- + /// dependent properties, e.g. the order of include directives. + virtual void registerPPCallbacks(CompilerInstance &Compiler) {} + + /// \brief Override this to register ASTMatchers with \p Finder. + /// + /// This should be used by clang-tidy checks that analyze code properties that + /// dependent on AST knowledge. + /// + /// You can register as many matchers as necessary with \p Finder. Usually, + /// "this" will be used as callback, but you can also specify other callback + /// classes. Thereby, different matchers can trigger different callbacks. + /// + /// If you need to merge information between the different matchers, you can + /// store these as members of the derived class. However, note that all + /// matches occur in the order of the AST traversal. + virtual void registerMatchers(ast_matchers::MatchFinder *Finder) {} + + /// \brief \c ClangTidyChecks that register ASTMatchers should do the actual + /// work in here. + virtual void check(const ast_matchers::MatchFinder::MatchResult &Result) {} + + /// \brief Add a diagnostic with the check's name. + DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, + DiagnosticIDs::Level Level = DiagnosticIDs::Warning); + + /// \brief Should store all options supported by this check with their + /// current values or default values for options that haven't been overridden. + /// + /// The check should use \c Options.store() to store each option it supports + /// whether it has the default value or it has been overridden. + virtual void storeOptions(ClangTidyOptions::OptionMap &Options) {} + +private: + void run(const ast_matchers::MatchFinder::MatchResult &Result) override; + StringRef getID() const override { return CheckName; } + std::string CheckName; + ClangTidyContext *Context; + +protected: + OptionsView Options; + /// \brief Returns the main file name of the current translation unit. + StringRef getCurrentMainFile() const { return Context->getCurrentFile(); } + /// \brief Returns the language options from the context. + LangOptions getLangOpts() const { return Context->getLangOpts(); } +}; + +class ClangTidyCheckFactories; + +class ClangTidyASTConsumerFactory { +public: + ClangTidyASTConsumerFactory(ClangTidyContext &Context); + + /// \brief Returns an ASTConsumer that runs the specified clang-tidy checks. + std::unique_ptr + CreateASTConsumer(clang::CompilerInstance &Compiler, StringRef File); + + /// \brief Get the list of enabled checks. + std::vector getCheckNames(); + + /// \brief Get the union of options from all checks. + ClangTidyOptions::OptionMap getCheckOptions(); + +private: + typedef std::vector> CheckersList; + CheckersList getCheckersControlList(GlobList &Filter); + + ClangTidyContext &Context; + std::unique_ptr CheckFactories; +}; + +/// \brief Fills the list of check names that are enabled when the provided +/// filters are applied. +std::vector getCheckNames(const ClangTidyOptions &Options); + +/// \brief Returns the effective check-specific options. +/// +/// The method configures ClangTidy with the specified \p Options and collects +/// effective options from all created checks. The returned set of options +/// includes default check-specific options for all keys not overridden by \p +/// Options. +ClangTidyOptions::OptionMap getCheckOptions(const ClangTidyOptions &Options); + +/// \brief Run a set of clang-tidy checks on a set of files. +/// +/// \param Profile if provided, it enables check profile collection in +/// MatchFinder, and will contain the result of the profile. +ClangTidyStats +runClangTidy(std::unique_ptr OptionsProvider, + const tooling::CompilationDatabase &Compilations, + ArrayRef InputFiles, + std::vector *Errors, + ProfileData *Profile = nullptr); + +// FIXME: This interface will need to be significantly extended to be useful. +// FIXME: Implement confidence levels for displaying/fixing errors. +// +/// \brief Displays the found \p Errors to the users. If \p Fix is true, \p +/// Errors containing fixes are automatically applied. +void handleErrors(const std::vector &Errors, bool Fix); + +/// \brief Serializes replacements into YAML and writes them to the specified +/// output stream. +void exportReplacements(const std::vector &Errors, + raw_ostream &OS); + +} // end namespace tidy +} // end namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDY_H diff --git a/clang-tidy/ClangTidyDiagnosticConsumer.cpp b/clang-tidy/ClangTidyDiagnosticConsumer.cpp new file mode 100644 index 00000000..ac216753 --- /dev/null +++ b/clang-tidy/ClangTidyDiagnosticConsumer.cpp @@ -0,0 +1,556 @@ +//===--- tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp ----------=== // +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file This file implements ClangTidyDiagnosticConsumer, ClangTidyMessage, +/// ClangTidyContext and ClangTidyError classes. +/// +/// This tool uses the Clang Tooling infrastructure, see +/// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +/// for details on setting it up with LLVM source tree. +/// +//===----------------------------------------------------------------------===// + +#include "ClangTidyDiagnosticConsumer.h" +#include "ClangTidyOptions.h" +#include "clang/AST/ASTDiagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Frontend/DiagnosticRenderer.h" +#include "llvm/ADT/SmallString.h" +#include +#include +using namespace clang; +using namespace tidy; + +namespace { +class ClangTidyDiagnosticRenderer : public DiagnosticRenderer { +public: + ClangTidyDiagnosticRenderer(const LangOptions &LangOpts, + DiagnosticOptions *DiagOpts, + ClangTidyError &Error) + : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {} + +protected: + void emitDiagnosticMessage(SourceLocation Loc, PresumedLoc PLoc, + DiagnosticsEngine::Level Level, StringRef Message, + ArrayRef Ranges, + const SourceManager *SM, + DiagOrStoredDiag Info) override { + // Remove check name from the message. + // FIXME: Remove this once there's a better way to pass check names than + // appending the check name to the message in ClangTidyContext::diag and + // using getCustomDiagID. + std::string CheckNameInMessage = " [" + Error.CheckName + "]"; + if (Message.endswith(CheckNameInMessage)) + Message = Message.substr(0, Message.size() - CheckNameInMessage.size()); + + ClangTidyMessage TidyMessage = Loc.isValid() + ? ClangTidyMessage(Message, *SM, Loc) + : ClangTidyMessage(Message); + if (Level == DiagnosticsEngine::Note) { + Error.Notes.push_back(TidyMessage); + return; + } + assert(Error.Message.Message.empty() && "Overwriting a diagnostic message"); + Error.Message = TidyMessage; + } + + void emitDiagnosticLoc(SourceLocation Loc, PresumedLoc PLoc, + DiagnosticsEngine::Level Level, + ArrayRef Ranges, + const SourceManager &SM) override {} + + void emitCodeContext(SourceLocation Loc, DiagnosticsEngine::Level Level, + SmallVectorImpl &Ranges, + ArrayRef Hints, + const SourceManager &SM) override { + assert(Loc.isValid()); + for (const auto &FixIt : Hints) { + CharSourceRange Range = FixIt.RemoveRange; + assert(Range.getBegin().isValid() && Range.getEnd().isValid() && + "Invalid range in the fix-it hint."); + assert(Range.getBegin().isFileID() && Range.getEnd().isFileID() && + "Only file locations supported in fix-it hints."); + + Error.Fix.insert(tooling::Replacement(SM, Range, FixIt.CodeToInsert)); + } + } + + void emitIncludeLocation(SourceLocation Loc, PresumedLoc PLoc, + const SourceManager &SM) override {} + + void emitImportLocation(SourceLocation Loc, PresumedLoc PLoc, + StringRef ModuleName, + const SourceManager &SM) override {} + + void emitBuildingModuleLocation(SourceLocation Loc, PresumedLoc PLoc, + StringRef ModuleName, + const SourceManager &SM) override {} + + void endDiagnostic(DiagOrStoredDiag D, + DiagnosticsEngine::Level Level) override { + assert(!Error.Message.Message.empty() && "Message has not been set"); + } + +private: + ClangTidyError &Error; +}; +} // end anonymous namespace + +ClangTidyMessage::ClangTidyMessage(StringRef Message) + : Message(Message), FileOffset(0) {} + +ClangTidyMessage::ClangTidyMessage(StringRef Message, + const SourceManager &Sources, + SourceLocation Loc) + : Message(Message) { + assert(Loc.isValid() && Loc.isFileID()); + FilePath = Sources.getFilename(Loc); + FileOffset = Sources.getFileOffset(Loc); +} + +ClangTidyError::ClangTidyError(StringRef CheckName, + ClangTidyError::Level DiagLevel) + : CheckName(CheckName), DiagLevel(DiagLevel) {} + +// Returns true if GlobList starts with the negative indicator ('-'), removes it +// from the GlobList. +static bool ConsumeNegativeIndicator(StringRef &GlobList) { + if (GlobList.startswith("-")) { + GlobList = GlobList.substr(1); + return true; + } + return false; +} +// Converts first glob from the comma-separated list of globs to Regex and +// removes it and the trailing comma from the GlobList. +static llvm::Regex ConsumeGlob(StringRef &GlobList) { + StringRef Glob = GlobList.substr(0, GlobList.find(',')).trim(); + GlobList = GlobList.substr(Glob.size() + 1); + SmallString<128> RegexText("^"); + StringRef MetaChars("()^$|*+?.[]\\{}"); + for (char C : Glob) { + if (C == '*') + RegexText.push_back('.'); + else if (MetaChars.find(C) != StringRef::npos) + RegexText.push_back('\\'); + RegexText.push_back(C); + } + RegexText.push_back('$'); + return llvm::Regex(RegexText); +} + +GlobList::GlobList(StringRef Globs) + : Positive(!ConsumeNegativeIndicator(Globs)), Regex(ConsumeGlob(Globs)), + NextGlob(Globs.empty() ? nullptr : new GlobList(Globs)) {} + +bool GlobList::contains(StringRef S, bool Contains) { + if (Regex.match(S)) + Contains = Positive; + + if (NextGlob) + Contains = NextGlob->contains(S, Contains); + return Contains; +} + +ClangTidyContext::ClangTidyContext( + std::unique_ptr OptionsProvider) + : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)), + Profile(nullptr) { + // Before the first translation unit we can get errors related to command-line + // parsing, use empty string for the file name in this case. + setCurrentFile(""); +} + +DiagnosticBuilder ClangTidyContext::diag( + StringRef CheckName, SourceLocation Loc, StringRef Description, + DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) { + assert(Loc.isValid()); + bool Invalid; + const char *CharacterData = + DiagEngine->getSourceManager().getCharacterData(Loc, &Invalid); + if (!Invalid) { + const char *P = CharacterData; + while (*P != '\0' && *P != '\r' && *P != '\n') + ++P; + StringRef RestOfLine(CharacterData, P - CharacterData + 1); + // FIXME: Handle /\bNOLINT\b(\([^)]*\))?/ as cpplint.py does. + if (RestOfLine.find("NOLINT") != StringRef::npos) { + Level = DiagnosticIDs::Ignored; + ++Stats.ErrorsIgnoredNOLINT; + } + } + unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID( + Level, (Description + " [" + CheckName + "]").str()); + if (CheckNamesByDiagnosticID.count(ID) == 0) + CheckNamesByDiagnosticID.insert(std::make_pair(ID, CheckName.str())); + return DiagEngine->Report(Loc, ID); +} + +void ClangTidyContext::setDiagnosticsEngine(DiagnosticsEngine *Engine) { + DiagEngine = Engine; +} + +void ClangTidyContext::setSourceManager(SourceManager *SourceMgr) { + DiagEngine->setSourceManager(SourceMgr); +} + +void ClangTidyContext::setCurrentFile(StringRef File) { + CurrentFile = File; + CurrentOptions = getOptionsForFile(CurrentFile); + CheckFilter.reset(new GlobList(*getOptions().Checks)); +} + +void ClangTidyContext::setASTContext(ASTContext *Context) { + DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context); + LangOpts = Context->getLangOpts(); +} + +const ClangTidyGlobalOptions &ClangTidyContext::getGlobalOptions() const { + return OptionsProvider->getGlobalOptions(); +} + +const ClangTidyOptions &ClangTidyContext::getOptions() const { + return CurrentOptions; +} + +ClangTidyOptions ClangTidyContext::getOptionsForFile(StringRef File) const { + // Merge options on top of getDefaults() as a safeguard against options with + // unset values. + return ClangTidyOptions::getDefaults().mergeWith( + OptionsProvider->getOptions(CurrentFile)); +} + +void ClangTidyContext::setCheckProfileData(ProfileData *P) { Profile = P; } + +GlobList &ClangTidyContext::getChecksFilter() { + assert(CheckFilter != nullptr); + return *CheckFilter; +} + +bool ClangTidyContext::isCheckEnabled(StringRef CheckName) const { + return CheckFilter->contains(CheckName); +} + +/// \brief Store a \c ClangTidyError. +void ClangTidyContext::storeError(const ClangTidyError &Error) { + Errors.push_back(Error); +} + +StringRef ClangTidyContext::getCheckName(unsigned DiagnosticID) const { + llvm::DenseMap::const_iterator I = + CheckNamesByDiagnosticID.find(DiagnosticID); + if (I != CheckNamesByDiagnosticID.end()) + return I->second; + return ""; +} + +ClangTidyDiagnosticConsumer::ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx) + : Context(Ctx), LastErrorRelatesToUserCode(false), + LastErrorPassesLineFilter(false) { + IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); + Diags.reset(new DiagnosticsEngine( + IntrusiveRefCntPtr(new DiagnosticIDs), &*DiagOpts, this, + /*ShouldOwnClient=*/false)); + Context.setDiagnosticsEngine(Diags.get()); +} + +void ClangTidyDiagnosticConsumer::finalizeLastError() { + if (!Errors.empty()) { + ClangTidyError &Error = Errors.back(); + if (!Context.getChecksFilter().contains(Error.CheckName) && + Error.DiagLevel != ClangTidyError::Error) { + ++Context.Stats.ErrorsIgnoredCheckFilter; + Errors.pop_back(); + } else if (!LastErrorRelatesToUserCode) { + ++Context.Stats.ErrorsIgnoredNonUserCode; + Errors.pop_back(); + } else if (!LastErrorPassesLineFilter) { + ++Context.Stats.ErrorsIgnoredLineFilter; + Errors.pop_back(); + } else { + ++Context.Stats.ErrorsDisplayed; + } + } + LastErrorRelatesToUserCode = false; + LastErrorPassesLineFilter = false; +} + +void ClangTidyDiagnosticConsumer::HandleDiagnostic( + DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) { + // Count warnings/errors. + DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); + + if (DiagLevel == DiagnosticsEngine::Note) { + assert(!Errors.empty() && + "A diagnostic note can only be appended to a message."); + } else { + finalizeLastError(); + StringRef WarningOption = + Context.DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag( + Info.getID()); + std::string CheckName = !WarningOption.empty() + ? ("clang-diagnostic-" + WarningOption).str() + : Context.getCheckName(Info.getID()).str(); + + if (CheckName.empty()) { + // This is a compiler diagnostic without a warning option. Assign check + // name based on its level. + switch (DiagLevel) { + case DiagnosticsEngine::Error: + case DiagnosticsEngine::Fatal: + CheckName = "clang-diagnostic-error"; + break; + case DiagnosticsEngine::Warning: + CheckName = "clang-diagnostic-warning"; + break; + default: + CheckName = "clang-diagnostic-unknown"; + break; + } + } + + ClangTidyError::Level Level = ClangTidyError::Warning; + if (DiagLevel == DiagnosticsEngine::Error || + DiagLevel == DiagnosticsEngine::Fatal) { + // Force reporting of Clang errors regardless of filters and non-user + // code. + Level = ClangTidyError::Error; + LastErrorRelatesToUserCode = true; + LastErrorPassesLineFilter = true; + } + Errors.push_back(ClangTidyError(CheckName, Level)); + } + + // FIXME: Provide correct LangOptions for each file. + LangOptions LangOpts; + ClangTidyDiagnosticRenderer Converter( + LangOpts, &Context.DiagEngine->getDiagnosticOptions(), Errors.back()); + SmallString<100> Message; + Info.FormatDiagnostic(Message); + SourceManager *Sources = nullptr; + if (Info.hasSourceManager()) + Sources = &Info.getSourceManager(); + Converter.emitDiagnostic(Info.getLocation(), DiagLevel, Message, + Info.getRanges(), Info.getFixItHints(), Sources); + + checkFilters(Info.getLocation()); +} + +bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName, + unsigned LineNumber) const { + if (Context.getGlobalOptions().LineFilter.empty()) + return true; + for (const FileFilter &Filter : Context.getGlobalOptions().LineFilter) { + if (FileName.endswith(Filter.Name)) { + if (Filter.LineRanges.empty()) + return true; + for (const FileFilter::LineRange &Range : Filter.LineRanges) { + if (Range.first <= LineNumber && LineNumber <= Range.second) + return true; + } + return false; + } + } + return false; +} + +void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location) { + // Invalid location may mean a diagnostic in a command line, don't skip these. + if (!Location.isValid()) { + LastErrorRelatesToUserCode = true; + LastErrorPassesLineFilter = true; + return; + } + + const SourceManager &Sources = Diags->getSourceManager(); + if (!*Context.getOptions().SystemHeaders && + Sources.isInSystemHeader(Location)) + return; + + // FIXME: We start with a conservative approach here, but the actual type of + // location needed depends on the check (in particular, where this check wants + // to apply fixes). + FileID FID = Sources.getDecomposedExpansionLoc(Location).first; + const FileEntry *File = Sources.getFileEntryForID(FID); + + // -DMACRO definitions on the command line have locations in a virtual buffer + // that doesn't have a FileEntry. Don't skip these as well. + if (!File) { + LastErrorRelatesToUserCode = true; + LastErrorPassesLineFilter = true; + return; + } + + StringRef FileName(File->getName()); + LastErrorRelatesToUserCode = LastErrorRelatesToUserCode || + Sources.isInMainFile(Location) || + getHeaderFilter()->match(FileName); + + unsigned LineNumber = Sources.getExpansionLineNumber(Location); + LastErrorPassesLineFilter = + LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber); +} + +llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() { + if (!HeaderFilter) + HeaderFilter.reset( + new llvm::Regex(*Context.getOptions().HeaderFilterRegex)); + return HeaderFilter.get(); +} + +void ClangTidyDiagnosticConsumer::removeIncompatibleErrors( + SmallVectorImpl &Errors) const { + // Each error is modelled as the set of intervals in which it applies + // replacements. To detect overlapping replacements, we use a sweep line + // algorithm over these sets of intervals. + // An event here consists of the opening or closing of an interval. During the + // proccess, we maintain a counter with the amount of open intervals. If we + // find an endpoint of an interval and this counter is different from 0, it + // means that this interval overlaps with another one, so we set it as + // inapplicable. + struct Event { + // An event can be either the begin or the end of an interval. + enum EventType { + ET_Begin = 1, + ET_End = -1, + }; + + Event(unsigned Begin, unsigned End, EventType Type, unsigned ErrorId, + unsigned ErrorSize) + : Type(Type), ErrorId(ErrorId) { + // The events are going to be sorted by their position. In case of draw: + // + // * If an interval ends at the same position at which other interval + // begins, this is not an overlapping, so we want to remove the ending + // interval before adding the starting one: end events have higher + // priority than begin events. + // + // * If we have several begin points at the same position, we will mark as + // inapplicable the ones that we proccess later, so the first one has to + // be the one with the latest end point, because this one will contain + // all the other intervals. For the same reason, if we have several end + // points in the same position, the last one has to be the one with the + // earliest begin point. In both cases, we sort non-increasingly by the + // position of the complementary. + // + // * In case of two equal intervals, the one whose error is bigger can + // potentially contain the other one, so we want to proccess its begin + // points before and its end points later. + // + // * Finally, if we have two equal intervals whose errors have the same + // size, none of them will be strictly contained inside the other. + // Sorting by ErrorId will guarantee that the begin point of the first + // one will be proccessed before, disallowing the second one, and the + // end point of the first one will also be proccessed before, + // disallowing the first one. + if (Type == ET_Begin) + Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId); + else + Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId); + } + + bool operator<(const Event &Other) const { + return Priority < Other.Priority; + } + + // Determines if this event is the begin or the end of an interval. + EventType Type; + // The index of the error to which the interval that generated this event + // belongs. + unsigned ErrorId; + // The events will be sorted based on this field. + std::tuple Priority; + }; + + // Compute error sizes. + std::vector Sizes; + for (const auto &Error : Errors) { + int Size = 0; + for (const auto &Replace : Error.Fix) + Size += Replace.getLength(); + Sizes.push_back(Size); + } + + // Build events from error intervals. + std::map> FileEvents; + for (unsigned I = 0; I < Errors.size(); ++I) { + for (const auto &Replace : Errors[I].Fix) { + unsigned Begin = Replace.getOffset(); + unsigned End = Begin + Replace.getLength(); + const std::string &FilePath = Replace.getFilePath(); + // FIXME: Handle empty intervals, such as those from insertions. + if (Begin == End) + continue; + FileEvents[FilePath].push_back( + Event(Begin, End, Event::ET_Begin, I, Sizes[I])); + FileEvents[FilePath].push_back( + Event(Begin, End, Event::ET_End, I, Sizes[I])); + } + } + + std::vector Apply(Errors.size(), true); + for (auto &FileAndEvents : FileEvents) { + std::vector &Events = FileAndEvents.second; + // Sweep. + std::sort(Events.begin(), Events.end()); + int OpenIntervals = 0; + for (const auto &Event : Events) { + if (Event.Type == Event::ET_End) + --OpenIntervals; + // This has to be checked after removing the interval from the count if it + // is an end event, or before adding it if it is a begin event. + if (OpenIntervals != 0) + Apply[Event.ErrorId] = false; + if (Event.Type == Event::ET_Begin) + ++OpenIntervals; + } + assert(OpenIntervals == 0 && "Amount of begin/end points doesn't match"); + } + + for (unsigned I = 0; I < Errors.size(); ++I) { + if (!Apply[I]) { + Errors[I].Fix.clear(); + Errors[I].Notes.push_back( + ClangTidyMessage("this fix will not be applied because" + " it overlaps with another fix")); + } + } +} + +namespace { +struct LessClangTidyError { + bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const { + const ClangTidyMessage &M1 = LHS.Message; + const ClangTidyMessage &M2 = RHS.Message; + + return std::tie(M1.FilePath, M1.FileOffset, M1.Message) < + std::tie(M2.FilePath, M2.FileOffset, M2.Message); + } +}; +struct EqualClangTidyError { + bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const { + LessClangTidyError Less; + return !Less(LHS, RHS) && !Less(RHS, LHS); + } +}; +} // end anonymous namespace + +// Flushes the internal diagnostics buffer to the ClangTidyContext. +void ClangTidyDiagnosticConsumer::finish() { + finalizeLastError(); + + std::sort(Errors.begin(), Errors.end(), LessClangTidyError()); + Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()), + Errors.end()); + removeIncompatibleErrors(Errors); + + for (const ClangTidyError &Error : Errors) + Context.storeError(Error); + Errors.clear(); +} diff --git a/clang-tidy/ClangTidyDiagnosticConsumer.h b/clang-tidy/ClangTidyDiagnosticConsumer.h new file mode 100644 index 00000000..780f8a32 --- /dev/null +++ b/clang-tidy/ClangTidyDiagnosticConsumer.h @@ -0,0 +1,267 @@ +//===--- ClangTidyDiagnosticConsumer.h - clang-tidy -------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H + +#include "ClangTidyOptions.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Tooling/Refactoring.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Regex.h" +#include "llvm/Support/Timer.h" + +namespace clang { + +class ASTContext; +class CompilerInstance; +namespace ast_matchers { +class MatchFinder; +} +namespace tooling { +class CompilationDatabase; +} + +namespace tidy { + +/// \brief A message from a clang-tidy check. +/// +/// Note that this is independent of a \c SourceManager. +struct ClangTidyMessage { + ClangTidyMessage(StringRef Message = ""); + ClangTidyMessage(StringRef Message, const SourceManager &Sources, + SourceLocation Loc); + std::string Message; + std::string FilePath; + unsigned FileOffset; +}; + +/// \brief A detected error complete with information to display diagnostic and +/// automatic fix. +/// +/// This is used as an intermediate format to transport Diagnostics without a +/// dependency on a SourceManager. +/// +/// FIXME: Make Diagnostics flexible enough to support this directly. +struct ClangTidyError { + enum Level { + Warning = DiagnosticsEngine::Warning, + Error = DiagnosticsEngine::Error + }; + + ClangTidyError(StringRef CheckName, Level DiagLevel); + + std::string CheckName; + ClangTidyMessage Message; + tooling::Replacements Fix; + SmallVector Notes; + + Level DiagLevel; +}; + +/// \brief Read-only set of strings represented as a list of positive and +/// negative globs. Positive globs add all matched strings to the set, negative +/// globs remove them in the order of appearance in the list. +class GlobList { +public: + /// \brief \p GlobList is a comma-separated list of globs (only '*' + /// metacharacter is supported) with optional '-' prefix to denote exclusion. + GlobList(StringRef Globs); + + /// \brief Returns \c true if the pattern matches \p S. The result is the last + /// matching glob's Positive flag. + bool contains(StringRef S) { return contains(S, false); } + +private: + bool contains(StringRef S, bool Contains); + + bool Positive; + llvm::Regex Regex; + std::unique_ptr NextGlob; +}; + +/// \brief Contains displayed and ignored diagnostic counters for a ClangTidy +/// run. +struct ClangTidyStats { + ClangTidyStats() + : ErrorsDisplayed(0), ErrorsIgnoredCheckFilter(0), ErrorsIgnoredNOLINT(0), + ErrorsIgnoredNonUserCode(0), ErrorsIgnoredLineFilter(0) {} + + unsigned ErrorsDisplayed; + unsigned ErrorsIgnoredCheckFilter; + unsigned ErrorsIgnoredNOLINT; + unsigned ErrorsIgnoredNonUserCode; + unsigned ErrorsIgnoredLineFilter; + + unsigned errorsIgnored() const { + return ErrorsIgnoredNOLINT + ErrorsIgnoredCheckFilter + + ErrorsIgnoredNonUserCode + ErrorsIgnoredLineFilter; + } +}; + +/// \brief Container for clang-tidy profiling data. +struct ProfileData { + llvm::StringMap Records; +}; + +/// \brief Every \c ClangTidyCheck reports errors through a \c DiagnosticsEngine +/// provided by this context. +/// +/// A \c ClangTidyCheck always has access to the active context to report +/// warnings like: +/// \code +/// Context->Diag(Loc, "Single-argument constructors must be explicit") +/// << FixItHint::CreateInsertion(Loc, "explicit "); +/// \endcode +class ClangTidyContext { +public: + /// \brief Initializes \c ClangTidyContext instance. + ClangTidyContext(std::unique_ptr OptionsProvider); + + /// \brief Report any errors detected using this method. + /// + /// This is still under heavy development and will likely change towards using + /// tablegen'd diagnostic IDs. + /// FIXME: Figure out a way to manage ID spaces. + DiagnosticBuilder diag(StringRef CheckName, SourceLocation Loc, + StringRef Message, + DiagnosticIDs::Level Level = DiagnosticIDs::Warning); + + /// \brief Sets the \c SourceManager of the used \c DiagnosticsEngine. + /// + /// This is called from the \c ClangTidyCheck base class. + void setSourceManager(SourceManager *SourceMgr); + + /// \brief Should be called when starting to process new translation unit. + void setCurrentFile(StringRef File); + + /// \brief Returns the main file name of the current translation unit. + StringRef getCurrentFile() const { return CurrentFile; } + + /// \brief Sets ASTContext for the current translation unit. + void setASTContext(ASTContext *Context); + + /// \brief Gets the language options from the AST context. + LangOptions getLangOpts() const { return LangOpts; } + + /// \brief Returns the name of the clang-tidy check which produced this + /// diagnostic ID. + StringRef getCheckName(unsigned DiagnosticID) const; + + /// \brief Returns check filter for the \c CurrentFile. + /// + /// The \c CurrentFile can be changed using \c setCurrentFile. + GlobList &getChecksFilter(); + + /// \brief Returns true if the check name is enabled for the \c CurrentFile. + bool isCheckEnabled(StringRef CheckName) const; + + /// \brief Returns global options. + const ClangTidyGlobalOptions &getGlobalOptions() const; + + /// \brief Returns options for \c CurrentFile. + /// + /// The \c CurrentFile can be changed using \c setCurrentFile. + const ClangTidyOptions &getOptions() const; + + /// \brief Returns options for \c File. Does not change or depend on + /// \c CurrentFile. + ClangTidyOptions getOptionsForFile(StringRef File) const; + + /// \brief Returns \c ClangTidyStats containing issued and ignored diagnostic + /// counters. + const ClangTidyStats &getStats() const { return Stats; } + + /// \brief Returns all collected errors. + const std::vector &getErrors() const { return Errors; } + + /// \brief Clears collected errors. + void clearErrors() { Errors.clear(); } + + /// \brief Set the output struct for profile data. + /// + /// Setting a non-null pointer here will enable profile collection in + /// clang-tidy. + void setCheckProfileData(ProfileData *Profile); + ProfileData *getCheckProfileData() const { return Profile; } + +private: + // Calls setDiagnosticsEngine() and storeError(). + friend class ClangTidyDiagnosticConsumer; + + /// \brief Sets the \c DiagnosticsEngine so that Diagnostics can be generated + /// correctly. + void setDiagnosticsEngine(DiagnosticsEngine *Engine); + + /// \brief Store an \p Error. + void storeError(const ClangTidyError &Error); + + std::vector Errors; + DiagnosticsEngine *DiagEngine; + std::unique_ptr OptionsProvider; + + std::string CurrentFile; + ClangTidyOptions CurrentOptions; + std::unique_ptr CheckFilter; + + LangOptions LangOpts; + + ClangTidyStats Stats; + + llvm::DenseMap CheckNamesByDiagnosticID; + + ProfileData *Profile; +}; + +/// \brief A diagnostic consumer that turns each \c Diagnostic into a +/// \c SourceManager-independent \c ClangTidyError. +// +// FIXME: If we move away from unit-tests, this can be moved to a private +// implementation file. +class ClangTidyDiagnosticConsumer : public DiagnosticConsumer { +public: + ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx); + + // FIXME: The concept of converting between FixItHints and Replacements is + // more generic and should be pulled out into a more useful Diagnostics + // library. + void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, + const Diagnostic &Info) override; + + /// \brief Flushes the internal diagnostics buffer to the ClangTidyContext. + void finish() override; + +private: + void finalizeLastError(); + + void removeIncompatibleErrors(SmallVectorImpl &Errors) const; + + /// \brief Returns the \c HeaderFilter constructed for the options set in the + /// context. + llvm::Regex *getHeaderFilter(); + + /// \brief Updates \c LastErrorRelatesToUserCode and LastErrorPassesLineFilter + /// according to the diagnostic \p Location. + void checkFilters(SourceLocation Location); + bool passesLineFilter(StringRef FileName, unsigned LineNumber) const; + + ClangTidyContext &Context; + std::unique_ptr Diags; + SmallVector Errors; + std::unique_ptr HeaderFilter; + bool LastErrorRelatesToUserCode; + bool LastErrorPassesLineFilter; +}; + +} // end namespace tidy +} // end namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H diff --git a/clang-tidy/ClangTidyModule.cpp b/clang-tidy/ClangTidyModule.cpp new file mode 100644 index 00000000..e1e798dd --- /dev/null +++ b/clang-tidy/ClangTidyModule.cpp @@ -0,0 +1,39 @@ +//===--- tools/extra/clang-tidy/ClangTidyModule.cpp - Clang tidy tool -----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file Implements classes required to build clang-tidy modules. +/// +//===----------------------------------------------------------------------===// + +#include "ClangTidyModule.h" + +namespace clang { +namespace tidy { + +void ClangTidyCheckFactories::registerCheckFactory(StringRef Name, + CheckFactory Factory) { + Factories[Name] = Factory; +} + +void ClangTidyCheckFactories::createChecks( + ClangTidyContext *Context, + std::vector> &Checks) { + GlobList &Filter = Context->getChecksFilter(); + for (const auto &Factory : Factories) { + if (Filter.contains(Factory.first)) + Checks.emplace_back(Factory.second(Factory.first, Context)); + } +} + +ClangTidyOptions ClangTidyModule::getModuleOptions() { + return ClangTidyOptions(); +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/ClangTidyModule.h b/clang-tidy/ClangTidyModule.h new file mode 100644 index 00000000..58e833c0 --- /dev/null +++ b/clang-tidy/ClangTidyModule.h @@ -0,0 +1,98 @@ +//===--- ClangTidyModule.h - clang-tidy -------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYMODULE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYMODULE_H + +#include "ClangTidy.h" +#include "llvm/ADT/StringRef.h" +#include +#include +#include +#include + +namespace clang { +namespace tidy { + +/// \brief A collection of \c ClangTidyCheckFactory instances. +/// +/// All clang-tidy modules register their check factories with an instance of +/// this object. +class ClangTidyCheckFactories { +public: + typedef std::function CheckFactory; + + /// \brief Registers check \p Factory with name \p Name. + /// + /// For all checks that have default constructors, use \c registerCheck. + void registerCheckFactory(StringRef Name, CheckFactory Factory); + + /// \brief Registers the \c CheckType with the name \p Name. + /// + /// This method should be used for all \c ClangTidyChecks that don't require + /// constructor parameters. + /// + /// For example, if have a clang-tidy check like: + /// \code + /// class MyTidyCheck : public ClangTidyCheck { + /// void registerMatchers(ast_matchers::MatchFinder *Finder) override { + /// .. + /// } + /// }; + /// \endcode + /// you can register it with: + /// \code + /// class MyModule : public ClangTidyModule { + /// void addCheckFactories(ClangTidyCheckFactories &Factories) override { + /// Factories.registerCheck("myproject-my-check"); + /// } + /// }; + /// \endcode + template void registerCheck(StringRef CheckName) { + registerCheckFactory(CheckName, + [](StringRef Name, ClangTidyContext *Context) { + return new CheckType(Name, Context); + }); + } + + /// \brief Create instances of all checks matching \p CheckRegexString and + /// store them in \p Checks. + /// + /// The caller takes ownership of the return \c ClangTidyChecks. + void createChecks(ClangTidyContext *Context, + std::vector> &Checks); + + typedef std::map FactoryMap; + FactoryMap::const_iterator begin() const { return Factories.begin(); } + FactoryMap::const_iterator end() const { return Factories.end(); } + bool empty() const { return Factories.empty(); } + +private: + FactoryMap Factories; +}; + +/// \brief A clang-tidy module groups a number of \c ClangTidyChecks and gives +/// them a prefixed name. +class ClangTidyModule { +public: + virtual ~ClangTidyModule() {} + + /// \brief Implement this function in order to register all \c CheckFactories + /// belonging to this module. + virtual void addCheckFactories(ClangTidyCheckFactories &CheckFactories) = 0; + + /// \brief Gets default options for checks defined in this module. + virtual ClangTidyOptions getModuleOptions(); +}; + +} // end namespace tidy +} // end namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYMODULE_H diff --git a/clang-tidy/ClangTidyModuleRegistry.h b/clang-tidy/ClangTidyModuleRegistry.h new file mode 100644 index 00000000..de2a3d72 --- /dev/null +++ b/clang-tidy/ClangTidyModuleRegistry.h @@ -0,0 +1,26 @@ +//===--- ClangTidyModuleRegistry.h - clang-tidy -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYMODULEREGISTRY_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYMODULEREGISTRY_H + +#include "ClangTidyModule.h" +#include "llvm/Support/Registry.h" + +extern template class llvm::Registry; + +namespace clang { +namespace tidy { + +typedef llvm::Registry ClangTidyModuleRegistry; + +} // end namespace tidy +} // end namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYMODULEREGISTRY_H diff --git a/clang-tidy/ClangTidyOptions.cpp b/clang-tidy/ClangTidyOptions.cpp new file mode 100644 index 00000000..6baca370 --- /dev/null +++ b/clang-tidy/ClangTidyOptions.cpp @@ -0,0 +1,287 @@ +//===--- ClangTidyOptions.cpp - clang-tidy ----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ClangTidyOptions.h" +#include "ClangTidyModuleRegistry.h" +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" +#include + +#define DEBUG_TYPE "clang-tidy-options" + +using clang::tidy::ClangTidyOptions; +using clang::tidy::FileFilter; + +LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter) +LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter::LineRange) +LLVM_YAML_IS_SEQUENCE_VECTOR(ClangTidyOptions::StringPair) +LLVM_YAML_IS_SEQUENCE_VECTOR(std::string) + +namespace llvm { +namespace yaml { + +// Map std::pair to a JSON array of size 2. +template <> struct SequenceTraits { + static size_t size(IO &IO, FileFilter::LineRange &Range) { + return Range.first == 0 ? 0 : Range.second == 0 ? 1 : 2; + } + static unsigned &element(IO &IO, FileFilter::LineRange &Range, size_t Index) { + if (Index > 1) + IO.setError("Too many elements in line range."); + return Index == 0 ? Range.first : Range.second; + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, FileFilter &File) { + IO.mapRequired("name", File.Name); + IO.mapOptional("lines", File.LineRanges); + } + static StringRef validate(IO &io, FileFilter &File) { + if (File.Name.empty()) + return "No file name specified"; + for (const FileFilter::LineRange &Range : File.LineRanges) { + if (Range.first <= 0 || Range.second <= 0) + return "Invalid line range"; + } + return StringRef(); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, ClangTidyOptions::StringPair &KeyValue) { + IO.mapRequired("key", KeyValue.first); + IO.mapRequired("value", KeyValue.second); + } +}; + +struct NOptionMap { + NOptionMap(IO &) {} + NOptionMap(IO &, const ClangTidyOptions::OptionMap &OptionMap) + : Options(OptionMap.begin(), OptionMap.end()) {} + ClangTidyOptions::OptionMap denormalize(IO &) { + ClangTidyOptions::OptionMap Map; + for (const auto &KeyValue : Options) + Map[KeyValue.first] = KeyValue.second; + return Map; + } + std::vector Options; +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, ClangTidyOptions &Options) { + MappingNormalization NOpts( + IO, Options.CheckOptions); + IO.mapOptional("Checks", Options.Checks); + IO.mapOptional("HeaderFilterRegex", Options.HeaderFilterRegex); + IO.mapOptional("AnalyzeTemporaryDtors", Options.AnalyzeTemporaryDtors); + IO.mapOptional("User", Options.User); + IO.mapOptional("CheckOptions", NOpts->Options); + IO.mapOptional("ExtraArgs", Options.ExtraArgs); + IO.mapOptional("ExtraArgsBefore", Options.ExtraArgsBefore); + } +}; + +} // namespace yaml +} // namespace llvm + +namespace clang { +namespace tidy { + +ClangTidyOptions ClangTidyOptions::getDefaults() { + ClangTidyOptions Options; + Options.Checks = ""; + Options.HeaderFilterRegex = ""; + Options.SystemHeaders = false; + Options.AnalyzeTemporaryDtors = false; + Options.User = llvm::None; + for (ClangTidyModuleRegistry::iterator I = ClangTidyModuleRegistry::begin(), + E = ClangTidyModuleRegistry::end(); + I != E; ++I) + Options = Options.mergeWith(I->instantiate()->getModuleOptions()); + return Options; +} + +ClangTidyOptions +ClangTidyOptions::mergeWith(const ClangTidyOptions &Other) const { + ClangTidyOptions Result = *this; + + // Merge comma-separated glob lists by appending the new value after a comma. + if (Other.Checks) + Result.Checks = + (Result.Checks && !Result.Checks->empty() ? *Result.Checks + "," : "") + + *Other.Checks; + + if (Other.HeaderFilterRegex) + Result.HeaderFilterRegex = Other.HeaderFilterRegex; + if (Other.SystemHeaders) + Result.SystemHeaders = Other.SystemHeaders; + if (Other.AnalyzeTemporaryDtors) + Result.AnalyzeTemporaryDtors = Other.AnalyzeTemporaryDtors; + if (Other.User) + Result.User = Other.User; + if (Other.ExtraArgs) + Result.ExtraArgs = Other.ExtraArgs; + if (Other.ExtraArgsBefore) + Result.ExtraArgsBefore = Other.ExtraArgsBefore; + + for (const auto &KeyValue : Other.CheckOptions) + Result.CheckOptions[KeyValue.first] = KeyValue.second; + + return Result; +} + +FileOptionsProvider::FileOptionsProvider( + const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &OverrideOptions) + : DefaultOptionsProvider(GlobalOptions, DefaultOptions), + OverrideOptions(OverrideOptions) { + ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration); + CachedOptions[""] = DefaultOptions.mergeWith(OverrideOptions); +} + +FileOptionsProvider::FileOptionsProvider( + const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &OverrideOptions, + const FileOptionsProvider::ConfigFileHandlers &ConfigHandlers) + : DefaultOptionsProvider(GlobalOptions, DefaultOptions), + OverrideOptions(OverrideOptions), ConfigHandlers(ConfigHandlers) { + CachedOptions[""] = DefaultOptions.mergeWith(OverrideOptions); +} + +// FIXME: This method has some common logic with clang::format::getStyle(). +// Consider pulling out common bits to a findParentFileWithName function or +// similar. +ClangTidyOptions FileOptionsProvider::getOptions(StringRef FileName) { + DEBUG(llvm::dbgs() << "Getting options for file " << FileName << "...\n"); + SmallString<256> FilePath(FileName); + + if (std::error_code EC = llvm::sys::fs::make_absolute(FilePath)) { + llvm::errs() << "Can't make absolute path from " << FileName << ": " + << EC.message() << "\n"; + // FIXME: Figure out what to do. + } else { + FileName = FilePath; + } + + // Look for a suitable configuration file in all parent directories of the + // file. Start with the immediate parent directory and move up. + StringRef Path = llvm::sys::path::parent_path(FileName); + for (StringRef CurrentPath = Path;; + CurrentPath = llvm::sys::path::parent_path(CurrentPath)) { + llvm::Optional Result; + + auto Iter = CachedOptions.find(CurrentPath); + if (Iter != CachedOptions.end()) + Result = Iter->second; + + if (!Result) + Result = TryReadConfigFile(CurrentPath); + + if (Result) { + // Store cached value for all intermediate directories. + while (Path != CurrentPath) { + DEBUG(llvm::dbgs() << "Caching configuration for path " << Path + << ".\n"); + CachedOptions[Path] = *Result; + Path = llvm::sys::path::parent_path(Path); + } + return CachedOptions[Path] = *Result; + } + } +} + +llvm::Optional +FileOptionsProvider::TryReadConfigFile(StringRef Directory) { + assert(!Directory.empty()); + + if (!llvm::sys::fs::is_directory(Directory)) { + llvm::errs() << "Error reading configuration from " << Directory + << ": directory doesn't exist.\n"; + return llvm::None; + } + + for (const ConfigFileHandler &ConfigHandler : ConfigHandlers) { + SmallString<128> ConfigFile(Directory); + llvm::sys::path::append(ConfigFile, ConfigHandler.first); + DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n"); + + bool IsFile = false; + // Ignore errors from is_regular_file: we only need to know if we can read + // the file or not. + llvm::sys::fs::is_regular_file(Twine(ConfigFile), IsFile); + if (!IsFile) + continue; + + llvm::ErrorOr> Text = + llvm::MemoryBuffer::getFile(ConfigFile.c_str()); + if (std::error_code EC = Text.getError()) { + llvm::errs() << "Can't read " << ConfigFile << ": " << EC.message() + << "\n"; + continue; + } + + // Skip empty files, e.g. files opened for writing via shell output + // redirection. + if ((*Text)->getBuffer().empty()) + continue; + llvm::ErrorOr ParsedOptions = + ConfigHandler.second((*Text)->getBuffer()); + if (!ParsedOptions) { + if (ParsedOptions.getError()) + llvm::errs() << "Error parsing " << ConfigFile << ": " + << ParsedOptions.getError().message() << "\n"; + continue; + } + + return DefaultOptionsProvider::getOptions(Directory) + .mergeWith(*ParsedOptions) + .mergeWith(OverrideOptions); + } + return llvm::None; +} + +/// \brief Parses -line-filter option and stores it to the \c Options. +std::error_code parseLineFilter(StringRef LineFilter, + clang::tidy::ClangTidyGlobalOptions &Options) { + llvm::yaml::Input Input(LineFilter); + Input >> Options.LineFilter; + return Input.error(); +} + +llvm::ErrorOr parseConfiguration(StringRef Config) { + llvm::yaml::Input Input(Config); + ClangTidyOptions Options; + Input >> Options; + if (Input.error()) + return Input.error(); + return Options; +} + +std::string configurationAsText(const ClangTidyOptions &Options) { + std::string Text; + llvm::raw_string_ostream Stream(Text); + llvm::yaml::Output Output(Stream); + // We use the same mapping method for input and output, so we need a non-const + // reference here. + ClangTidyOptions NonConstValue = Options; + Output << NonConstValue; + return Stream.str(); +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/ClangTidyOptions.h b/clang-tidy/ClangTidyOptions.h new file mode 100644 index 00000000..b3d956f8 --- /dev/null +++ b/clang-tidy/ClangTidyOptions.h @@ -0,0 +1,224 @@ +//===--- ClangTidyOptions.h - clang-tidy ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H + +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/ErrorOr.h" +#include +#include +#include +#include +#include +#include + +namespace clang { +namespace tidy { + +/// \brief Contains a list of line ranges in a single file. +struct FileFilter { + /// \brief File name. + std::string Name; + + /// \brief LineRange is a pair (inclusive). + typedef std::pair LineRange; + + /// \brief A list of line ranges in this file, for which we show warnings. + std::vector LineRanges; +}; + +/// \brief Global options. These options are neither stored nor read from +/// configuration files. +struct ClangTidyGlobalOptions { + /// \brief Output warnings from certain line ranges of certain files only. + /// If empty, no warnings will be filtered. + std::vector LineFilter; +}; + +/// \brief Contains options for clang-tidy. These options may be read from +/// configuration files, and may be different for different translation units. +struct ClangTidyOptions { + /// \brief These options are used for all settings that haven't been + /// overridden by the \c OptionsProvider. + /// + /// Allow no checks and no headers by default. This method initializes + /// check-specific options by calling \c ClangTidyModule::getModuleOptions() + /// of each registered \c ClangTidyModule. + static ClangTidyOptions getDefaults(); + + /// \brief Creates a new \c ClangTidyOptions instance combined from all fields + /// of this instance overridden by the fields of \p Other that have a value. + ClangTidyOptions mergeWith(const ClangTidyOptions &Other) const; + + /// \brief Checks filter. + llvm::Optional Checks; + + /// \brief Output warnings from headers matching this filter. Warnings from + /// main files will always be displayed. + llvm::Optional HeaderFilterRegex; + + /// \brief Output warnings from system headers matching \c HeaderFilterRegex. + llvm::Optional SystemHeaders; + + /// \brief Turns on temporary destructor-based analysis. + llvm::Optional AnalyzeTemporaryDtors; + + /// \brief Specifies the name or e-mail of the user running clang-tidy. + /// + /// This option is used, for example, to place the correct user name in TODO() + /// comments in the relevant check. + llvm::Optional User; + + typedef std::pair StringPair; + typedef std::map OptionMap; + + /// \brief Key-value mapping used to store check-specific options. + OptionMap CheckOptions; + + typedef std::vector ArgList; + + /// \brief Add extra compilation arguments to the end of the list. + llvm::Optional ExtraArgs; + + /// \brief Add extra compilation arguments to the start of the list. + llvm::Optional ExtraArgsBefore; +}; + +/// \brief Abstract interface for retrieving various ClangTidy options. +class ClangTidyOptionsProvider { +public: + virtual ~ClangTidyOptionsProvider() {} + + /// \brief Returns global options, which are independent of the file. + virtual const ClangTidyGlobalOptions &getGlobalOptions() = 0; + + /// \brief Returns options applying to a specific translation unit with the + /// specified \p FileName. + virtual ClangTidyOptions getOptions(llvm::StringRef FileName) = 0; +}; + +/// \brief Implementation of the \c ClangTidyOptionsProvider interface, which +/// returns the same options for all files. +class DefaultOptionsProvider : public ClangTidyOptionsProvider { +public: + DefaultOptionsProvider(const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &Options) + : GlobalOptions(GlobalOptions), DefaultOptions(Options) {} + const ClangTidyGlobalOptions &getGlobalOptions() override { + return GlobalOptions; + } + ClangTidyOptions getOptions(llvm::StringRef /*FileName*/) override { + return DefaultOptions; + } + +private: + ClangTidyGlobalOptions GlobalOptions; + ClangTidyOptions DefaultOptions; +}; + +/// \brief Implementation of the \c ClangTidyOptionsProvider interface, which +/// tries to find a configuration file in the closest parent directory of each +/// source file. +/// +/// By default, files named ".clang-tidy" will be considered, and the +/// \c clang::tidy::parseConfiguration function will be used for parsing, but a +/// custom set of configuration file names and parsing functions can be +/// specified using the appropriate constructor. +class FileOptionsProvider : public DefaultOptionsProvider { +public: + // \brief A pair of configuration file base name and a function parsing + // configuration from text in the corresponding format. + typedef std::pair( + llvm::StringRef)>> ConfigFileHandler; + + /// \brief Configuration file handlers listed in the order of priority. + /// + /// Custom configuration file formats can be supported by constructing the + /// list of handlers and passing it to the appropriate \c FileOptionsProvider + /// constructor. E.g. initialization of a \c FileOptionsProvider with support + /// of a custom configuration file format for files named ".my-tidy-config" + /// could look similar to this: + /// \code + /// FileOptionsProvider::ConfigFileHandlers ConfigHandlers; + /// ConfigHandlers.emplace_back(".my-tidy-config", parseMyConfigFormat); + /// ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration); + /// return llvm::make_unique( + /// GlobalOptions, DefaultOptions, OverrideOptions, ConfigHandlers); + /// \endcode + /// + /// With the order of handlers shown above, the ".my-tidy-config" file would + /// take precedence over ".clang-tidy" if both reside in the same directory. + typedef std::vector ConfigFileHandlers; + + /// \brief Initializes the \c FileOptionsProvider instance. + /// + /// \param GlobalOptions are just stored and returned to the caller of + /// \c getGlobalOptions. + /// + /// \param DefaultOptions are used for all settings not specified in a + /// configuration file. + /// + /// If any of the \param OverrideOptions fields are set, they will override + /// whatever options are read from the configuration file. + FileOptionsProvider(const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &OverrideOptions); + + /// \brief Initializes the \c FileOptionsProvider instance with a custom set + /// of configuration file handlers. + /// + /// \param GlobalOptions are just stored and returned to the caller of + /// \c getGlobalOptions. + /// + /// \param DefaultOptions are used for all settings not specified in a + /// configuration file. + /// + /// If any of the \param OverrideOptions fields are set, they will override + /// whatever options are read from the configuration file. + /// + /// \param ConfigHandlers specifies a custom set of configuration file + /// handlers. Each handler is a pair of configuration file name and a function + /// that can parse configuration from this file type. The configuration files + /// in each directory are searched for in the order of appearance in + /// \p ConfigHandlers. + FileOptionsProvider(const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &OverrideOptions, + const ConfigFileHandlers &ConfigHandlers); + + ClangTidyOptions getOptions(llvm::StringRef FileName) override; + +protected: + /// \brief Try to read configuration files from \p Directory using registered + /// \c ConfigHandlers. + llvm::Optional TryReadConfigFile(llvm::StringRef Directory); + + llvm::StringMap CachedOptions; + ClangTidyOptions OverrideOptions; + ConfigFileHandlers ConfigHandlers; +}; + +/// \brief Parses LineFilter from JSON and stores it to the \p Options. +std::error_code parseLineFilter(llvm::StringRef LineFilter, + ClangTidyGlobalOptions &Options); + +/// \brief Parses configuration from JSON and returns \c ClangTidyOptions or an +/// error. +llvm::ErrorOr parseConfiguration(llvm::StringRef Config); + +/// \brief Serializes configuration to a YAML-encoded string. +std::string configurationAsText(const ClangTidyOptions &Options); + +} // end namespace tidy +} // end namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H diff --git a/clang-tidy/Makefile b/clang-tidy/Makefile new file mode 100644 index 00000000..c6509ac0 --- /dev/null +++ b/clang-tidy/Makefile @@ -0,0 +1,16 @@ +##===- tools/extra/clang-tidy/Makefile ---------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL := ../../.. +LIBRARYNAME := clangTidy +include $(CLANG_LEVEL)/../../Makefile.config + +DIRS = utils cert cppcoreguidelines readability llvm google misc modernize performance tool + +include $(CLANG_LEVEL)/Makefile diff --git a/clang-tidy/add_new_check.py b/clang-tidy/add_new_check.py new file mode 100755 index 00000000..6000616d --- /dev/null +++ b/clang-tidy/add_new_check.py @@ -0,0 +1,288 @@ +#!/usr/bin/env python +# +#===- add_new_check.py - clang-tidy check generator ----------*- python -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +import os +import re +import sys + + +# Adapts the module's CMakelist file. Returns 'True' if it could add a new entry +# and 'False' if the entry already existed. +def adapt_cmake(module_path, check_name_camel): + filename = os.path.join(module_path, 'CMakeLists.txt') + with open(filename, 'r') as f: + lines = f.readlines() + + cpp_file = check_name_camel + '.cpp' + + # Figure out whether this check already exists. + for line in lines: + if line.strip() == cpp_file: + return False + + print('Updating %s...' % filename) + with open(filename, 'wb') as f: + cpp_found = False + file_added = False + for line in lines: + cpp_line = line.strip().endswith('.cpp') + if (not file_added) and (cpp_line or cpp_found): + cpp_found = True + if (line.strip() > cpp_file) or (not cpp_line): + f.write(' ' + cpp_file + '\n') + file_added = True + f.write(line) + + return True + + +# Adds a header for the new check. +def write_header(module_path, module, check_name, check_name_camel): + check_name_dashes = module + '-' + check_name + filename = os.path.join(module_path, check_name_camel) + '.h' + print('Creating %s...' % filename) + with open(filename, 'wb') as f: + header_guard = ('LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_' + module.upper() + + '_' + check_name.upper().replace('-', '_') + '_H') + f.write('//===--- ') + f.write(os.path.basename(filename)) + f.write(' - clang-tidy') + f.write('-' * max(0, 43 - len(os.path.basename(filename)))) + f.write('*- C++ -*-===//') + f.write(""" +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef %(header_guard)s +#define %(header_guard)s + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace %(module)s { + +/// FIXME: Write a short description. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/%(check_name_dashes)s.html +class %(check_name)s : public ClangTidyCheck { +public: + %(check_name)s(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace %(module)s +} // namespace tidy +} // namespace clang + +#endif // %(header_guard)s +""" % {'header_guard': header_guard, + 'check_name': check_name_camel, + 'check_name_dashes': check_name_dashes, + 'module': module}) + + +# Adds the implementation of the new check. +def write_implementation(module_path, module, check_name_camel): + filename = os.path.join(module_path, check_name_camel) + '.cpp' + print('Creating %s...' % filename) + with open(filename, 'wb') as f: + f.write('//===--- ') + f.write(os.path.basename(filename)) + f.write(' - clang-tidy') + f.write('-' * max(0, 52 - len(os.path.basename(filename)))) + f.write('-===//') + f.write(""" +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "%(check_name)s.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace %(module)s { + +void %(check_name)s::registerMatchers(MatchFinder *Finder) { + // FIXME: Add matchers. + Finder->addMatcher(functionDecl().bind("x"), this); +} + +void %(check_name)s::check(const MatchFinder::MatchResult &Result) { + // FIXME: Add callback implementation. + const auto *MatchedDecl = Result.Nodes.getNodeAs("x"); + if (MatchedDecl->getName().startswith("awesome_")) + return; + diag(MatchedDecl->getLocation(), "function '%%0' is insufficiently awesome") + << MatchedDecl->getName() + << FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_"); +} + +} // namespace %(module)s +} // namespace tidy +} // namespace clang +""" % {'check_name': check_name_camel, + 'module': module}) + + +# Modifies the module to include the new check. +def adapt_module(module_path, module, check_name, check_name_camel): + modulecpp = filter(lambda p: p.lower() == module.lower() + "tidymodule.cpp", os.listdir(module_path))[0] + filename = os.path.join(module_path, modulecpp) + with open(filename, 'r') as f: + lines = f.readlines() + + print('Updating %s...' % filename) + with open(filename, 'wb') as f: + header_added = False + header_found = False + check_added = False + check_decl = (' CheckFactories.registerCheck<' + check_name_camel + + '>(\n "' + module + '-' + check_name + '");\n') + + for line in lines: + if not header_added: + match = re.search('#include "(.*)"', line) + if match: + header_found = True + if match.group(1) > check_name_camel: + header_added = True + f.write('#include "' + check_name_camel + '.h"\n') + elif header_found: + header_added = True + f.write('#include "' + check_name_camel + '.h"\n') + + if not check_added: + if line.strip() == '}': + check_added = True + f.write(check_decl) + else: + match = re.search('registerCheck<(.*)>', line) + if match and match.group(1) > check_name_camel: + check_added = True + f.write(check_decl) + f.write(line) + + +# Adds a test for the check. +def write_test(module_path, module, check_name): + check_name_dashes = module + '-' + check_name + filename = os.path.normpath( + os.path.join(module_path, '../../test/clang-tidy', + check_name_dashes + '.cpp')) + print('Creating %s...' % filename) + with open(filename, 'wb') as f: + f.write( +"""// RUN: %%check_clang_tidy %%s %(check_name_dashes)s %%t + +// FIXME: Add something that triggers the check here. +void f(); +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently awesome [%(check_name_dashes)s] + +// FIXME: Verify the applied fix. +// * Make the CHECK patterns specific enough and try to make verified lines +// unique to avoid incorrect matches. +// * Use {{}} for regular expressions. +// CHECK-FIXES: {{^}}void awesome_f();{{$}} + +// FIXME: Add something that doesn't trigger the check here. +void awesome_f2(); +""" % {"check_name_dashes" : check_name_dashes}) + +# Recreates the list of checks in the docs/clang-tidy/checks directory. +def update_checks_list(module_path): + docs_dir = os.path.join(module_path, '../../docs/clang-tidy/checks') + filename = os.path.normpath(os.path.join(docs_dir, 'list.rst')) + with open(filename, 'r') as f: + lines = f.readlines() + doc_files = filter( + lambda s: s.endswith('.rst') and s != 'list.rst', + os.listdir(docs_dir)) + doc_files.sort() + + def format_link(doc_file): + check_name = doc_file.replace('.rst', '') + with open(os.path.join(docs_dir, doc_file), 'r') as doc: + match = re.search('.*:http-equiv=refresh: \d+;URL=(.*).html.*', doc.read()) + if match: + return ' %(check)s (redirects to %(target)s) <%(check)s>\n' % { + 'check' : check_name, 'target' : match.group(1) } + return ' %s\n' % check_name + + checks = map(format_link, doc_files) + + print('Updating %s...' % filename) + with open(filename, 'wb') as f: + for line in lines: + f.write(line) + if line.startswith('.. toctree::'): + f.writelines(checks) + break + +# Adds a documentation for the check. +def write_docs(module_path, module, check_name): + check_name_dashes = module + '-' + check_name + filename = os.path.normpath( + os.path.join(module_path, '../../docs/clang-tidy/checks/', + check_name_dashes + '.rst')) + print('Creating %s...' % filename) + with open(filename, 'wb') as f: + f.write( +""".. title:: clang-tidy - %(check_name_dashes)s + +%(check_name_dashes)s +%(underline)s + +FIXME: Describe what patterns does the check detect and why. Give examples. +""" % {"check_name_dashes" : check_name_dashes, + "underline" : "=" * len(check_name_dashes)}) + +def main(): + if len(sys.argv) != 3: + print 'Usage: add_new_check.py , e.g.\n' + print 'add_new_check.py misc awesome-functions\n' + return + + module = sys.argv[1] + check_name = sys.argv[2] + check_name_camel = ''.join(map(lambda elem: elem.capitalize(), + check_name.split('-'))) + 'Check' + clang_tidy_path = os.path.dirname(sys.argv[0]) + module_path = os.path.join(clang_tidy_path, module) + + if not adapt_cmake(module_path, check_name_camel): + return + write_header(module_path, module, check_name, check_name_camel) + write_implementation(module_path, module, check_name_camel) + adapt_module(module_path, module, check_name, check_name_camel) + write_test(module_path, module, check_name) + write_docs(module_path, module, check_name) + update_checks_list(module_path) + print('Done. Now it\'s your turn!') + +if __name__ == '__main__': + main() diff --git a/clang-tidy/cert/CERTTidyModule.cpp b/clang-tidy/cert/CERTTidyModule.cpp new file mode 100644 index 00000000..4e0a6ed2 --- /dev/null +++ b/clang-tidy/cert/CERTTidyModule.cpp @@ -0,0 +1,75 @@ +//===--- CERTTidyModule.cpp - clang-tidy ----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "../google/UnnamedNamespaceInHeaderCheck.h" +#include "../misc/MoveConstructorInitCheck.h" +#include "../misc/NewDeleteOverloadsCheck.h" +#include "../misc/NonCopyableObjects.h" +#include "../misc/StaticAssertCheck.h" +#include "../misc/ThrowByValueCatchByReferenceCheck.h" +#include "SetLongJmpCheck.h" +#include "StaticObjectExceptionCheck.h" +#include "ThrownExceptionTypeCheck.h" +#include "VariadicFunctionDefCheck.h" + +namespace clang { +namespace tidy { +namespace cert { + +class CERTModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + // C++ checkers + // DCL + CheckFactories.registerCheck( + "cert-dcl50-cpp"); + CheckFactories.registerCheck( + "cert-dcl54-cpp"); + CheckFactories.registerCheck( + "cert-dcl59-cpp"); + // OOP + CheckFactories.registerCheck( + "cert-oop11-cpp"); + // ERR + CheckFactories.registerCheck( + "cert-err52-cpp"); + CheckFactories.registerCheck( + "cert-err58-cpp"); + CheckFactories.registerCheck( + "cert-err60-cpp"); + CheckFactories.registerCheck( + "cert-err61-cpp"); + + // C checkers + // DCL + CheckFactories.registerCheck( + "cert-dcl03-c"); + + // FIO + CheckFactories.registerCheck( + "cert-fio38-c"); + } +}; + +} // namespace cert + +// Register the MiscTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add +X("cert-module", + "Adds lint checks corresponding to CERT secure coding guidelines."); + +// This anchor is used to force the linker to link in the generated object file +// and thus register the CERTModule. +volatile int CERTModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/CMakeLists.txt b/clang-tidy/cert/CMakeLists.txt new file mode 100644 index 00000000..da4eddd2 --- /dev/null +++ b/clang-tidy/cert/CMakeLists.txt @@ -0,0 +1,19 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyCERTModule + CERTTidyModule.cpp + SetLongJmpCheck.cpp + StaticObjectExceptionCheck.cpp + ThrownExceptionTypeCheck.cpp + VariadicFunctionDefCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyGoogleModule + clangTidyMiscModule + clangTidyUtils + ) diff --git a/clang-tidy/cert/LICENSE.TXT b/clang-tidy/cert/LICENSE.TXT new file mode 100644 index 00000000..b332152c --- /dev/null +++ b/clang-tidy/cert/LICENSE.TXT @@ -0,0 +1,22 @@ +------------------------------------------------------------------------------ +clang-tidy CERT Files +------------------------------------------------------------------------------ +All clang-tidy files are licensed under the LLVM license with the following +additions: + +Any file referencing a CERT Secure Coding guideline: +Please allow this letter to serve as confirmation that open source projects on +http://llvm.org are permitted to link via hypertext to the CERT(R) secure coding +guidelines available at https://www.securecoding.cert.org. + +The foregoing is permitted by the Terms of Use as follows: +"Linking to the Service +Because we update many of our Web documents regularly, we would prefer that you +link to our Web pages whenever possible rather than reproduce them. It is not +necessary to request permission to make referential hypertext links to The +Service." +http://www.sei.cmu.edu/legal/ip/index.cfm. + +Please allow this letter to also confirm that no formal permission is required +to reproduce the title of the content being linked to, nor to reproduce any +de Minimis description of such content. diff --git a/clang-tidy/cert/Makefile b/clang-tidy/cert/Makefile new file mode 100644 index 00000000..0c0f5270 --- /dev/null +++ b/clang-tidy/cert/Makefile @@ -0,0 +1,12 @@ +##===- clang-tidy/cert/Makefile ----------------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## +CLANG_LEVEL := ../../../.. +LIBRARYNAME := clangTidyCERTModule + +include $(CLANG_LEVEL)/Makefile diff --git a/clang-tidy/cert/SetLongJmpCheck.cpp b/clang-tidy/cert/SetLongJmpCheck.cpp new file mode 100644 index 00000000..89ba5e77 --- /dev/null +++ b/clang-tidy/cert/SetLongJmpCheck.cpp @@ -0,0 +1,79 @@ +//===--- SetLongJmpCheck.cpp - clang-tidy----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SetLongJmpCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +const char SetLongJmpCheck::DiagWording[] = + "do not call %0; consider using exception handling instead"; + +namespace { +class SetJmpMacroCallbacks : public PPCallbacks { + SetLongJmpCheck &Check; + +public: + explicit SetJmpMacroCallbacks(SetLongJmpCheck &Check) : Check(Check) {} + + void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD, + SourceRange Range, const MacroArgs *Args) override { + const auto *II = MacroNameTok.getIdentifierInfo(); + if (!II) + return; + + if (II->getName() == "setjmp") + Check.diag(Range.getBegin(), Check.DiagWording) << II; + } +}; +} // namespace + +void SetLongJmpCheck::registerPPCallbacks(CompilerInstance &Compiler) { + // This checker only applies to C++, where exception handling is a superior + // solution to setjmp/longjmp calls. + if (!getLangOpts().CPlusPlus) + return; + + // Per [headers]p5, setjmp must be exposed as a macro instead of a function, + // despite the allowance in C for setjmp to also be an extern function. + Compiler.getPreprocessor().addPPCallbacks( + llvm::make_unique(*this)); +} + +void SetLongJmpCheck::registerMatchers(MatchFinder *Finder) { + // This checker only applies to C++, where exception handling is a superior + // solution to setjmp/longjmp calls. + if (!getLangOpts().CPlusPlus) + return; + + // In case there is an implementation that happens to define setjmp as a + // function instead of a macro, this will also catch use of it. However, we + // are primarily searching for uses of longjmp. + Finder->addMatcher(callExpr(callee(functionDecl(anyOf(hasName("setjmp"), + hasName("longjmp"))))) + .bind("expr"), + this); +} + +void SetLongJmpCheck::check(const MatchFinder::MatchResult &Result) { + const auto *E = Result.Nodes.getNodeAs("expr"); + diag(E->getExprLoc(), DiagWording) << cast(E->getCalleeDecl()); +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/SetLongJmpCheck.h b/clang-tidy/cert/SetLongJmpCheck.h new file mode 100644 index 00000000..1d6c0981 --- /dev/null +++ b/clang-tidy/cert/SetLongJmpCheck.h @@ -0,0 +1,38 @@ +//===--- SetLongJmpCheck.h - clang-tidy--------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_SETLONGJMPCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_SETLONGJMPCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Guards against use of setjmp/longjmp in C++ code +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-err52-cpp.html +class SetLongJmpCheck : public ClangTidyCheck { +public: + SetLongJmpCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void registerPPCallbacks(CompilerInstance &Compiler) override; + + static const char DiagWording[]; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_SETLONGJMPCHECK_H diff --git a/clang-tidy/cert/StaticObjectExceptionCheck.cpp b/clang-tidy/cert/StaticObjectExceptionCheck.cpp new file mode 100644 index 00000000..53597fae --- /dev/null +++ b/clang-tidy/cert/StaticObjectExceptionCheck.cpp @@ -0,0 +1,50 @@ +//===--- StaticObjectExceptionCheck.cpp - clang-tidy-----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StaticObjectExceptionCheck.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +void StaticObjectExceptionCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // Match any static or thread_local variable declaration that is initialized + // with a constructor that can throw. + Finder->addMatcher( + varDecl(anyOf(hasThreadStorageDuration(), hasStaticStorageDuration()), + hasInitializer(cxxConstructExpr(hasDeclaration( + cxxConstructorDecl(unless(isNoThrow())) + .bind("ctor"))))) + .bind("var"), + this); +} + +void StaticObjectExceptionCheck::check(const MatchFinder::MatchResult &Result) { + const auto *VD = Result.Nodes.getNodeAs("var"); + const auto *Ctor = Result.Nodes.getNodeAs("ctor"); + + diag(VD->getLocation(), + "construction of %0 with %select{static|thread_local}1 storage " + "duration may throw an exception that cannot be caught") + << VD << (VD->getStorageDuration() == SD_Static ? 0 : 1); + diag(Ctor->getLocation(), "possibly throwing constructor declared here", + DiagnosticIDs::Note); +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/StaticObjectExceptionCheck.h b/clang-tidy/cert/StaticObjectExceptionCheck.h new file mode 100644 index 00000000..463f4336 --- /dev/null +++ b/clang-tidy/cert/StaticObjectExceptionCheck.h @@ -0,0 +1,36 @@ +//===--- StaticObjectExceptionCheck.h - clang-tidy---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_ERR58_CPP_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_ERR58_CPP_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Checks whether the constructor for a static or thread_local object will +/// throw. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-err58-cpp.html +class StaticObjectExceptionCheck : public ClangTidyCheck { +public: + StaticObjectExceptionCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_ERR58_CPP_H diff --git a/clang-tidy/cert/ThrownExceptionTypeCheck.cpp b/clang-tidy/cert/ThrownExceptionTypeCheck.cpp new file mode 100644 index 00000000..c9c5f3ee --- /dev/null +++ b/clang-tidy/cert/ThrownExceptionTypeCheck.cpp @@ -0,0 +1,41 @@ +//===--- ThrownExceptionTypeCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ThrownExceptionTypeCheck.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +void ThrownExceptionTypeCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + cxxThrowExpr( + has(cxxConstructExpr(hasDeclaration(cxxConstructorDecl( + isCopyConstructor(), unless(isNoThrow())))) + .bind("expr"))), + this); +} + +void ThrownExceptionTypeCheck::check(const MatchFinder::MatchResult &Result) { + const auto *E = Result.Nodes.getNodeAs("expr"); + diag(E->getExprLoc(), + "thrown exception type is not nothrow copy constructible"); +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/ThrownExceptionTypeCheck.h b/clang-tidy/cert/ThrownExceptionTypeCheck.h new file mode 100644 index 00000000..e05539ef --- /dev/null +++ b/clang-tidy/cert/ThrownExceptionTypeCheck.h @@ -0,0 +1,35 @@ +//===--- ThrownExceptionTypeCheck.h - clang-tidy-----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_THROWNEXCEPTIONTYPECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_THROWNEXCEPTIONTYPECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Checks whether a thrown object is nothrow copy constructible. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-err60-cpp.html +class ThrownExceptionTypeCheck : public ClangTidyCheck { +public: + ThrownExceptionTypeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_THROWNEXCEPTIONTYPECHECK_H diff --git a/clang-tidy/cert/VariadicFunctionDefCheck.cpp b/clang-tidy/cert/VariadicFunctionDefCheck.cpp new file mode 100644 index 00000000..ea6112a6 --- /dev/null +++ b/clang-tidy/cert/VariadicFunctionDefCheck.cpp @@ -0,0 +1,42 @@ +//===--- VariadicfunctiondefCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "VariadicFunctionDefCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +void VariadicFunctionDefCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // We only care about function *definitions* that are variadic, and do not + // have extern "C" language linkage. + Finder->addMatcher( + functionDecl(isDefinition(), isVariadic(), unless(isExternC())) + .bind("func"), + this); +} + +void VariadicFunctionDefCheck::check(const MatchFinder::MatchResult &Result) { + const auto *FD = Result.Nodes.getNodeAs("func"); + + diag(FD->getLocation(), + "do not define a C-style variadic function; consider using a function " + "parameter pack or currying instead"); +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/VariadicFunctionDefCheck.h b/clang-tidy/cert/VariadicFunctionDefCheck.h new file mode 100644 index 00000000..e215e8df --- /dev/null +++ b/clang-tidy/cert/VariadicFunctionDefCheck.h @@ -0,0 +1,35 @@ +//===--- VariadicFunctionDefCheck.h - clang-tidy-----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_VARIADICFUNCTIONDEF_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_VARIADICFUNCTIONDEF_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Guards against any C-style variadic function definitions (not declarations). +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-dcl50-cpp.html +class VariadicFunctionDefCheck : public ClangTidyCheck { +public: + VariadicFunctionDefCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_VARIADICFUNCTIONDEF_H diff --git a/clang-tidy/cppcoreguidelines/CMakeLists.txt b/clang-tidy/cppcoreguidelines/CMakeLists.txt new file mode 100644 index 00000000..d466ca83 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/CMakeLists.txt @@ -0,0 +1,24 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyCppCoreGuidelinesModule + CppCoreGuidelinesTidyModule.cpp + ProBoundsArrayToPointerDecayCheck.cpp + ProBoundsConstantArrayIndexCheck.cpp + ProBoundsPointerArithmeticCheck.cpp + ProTypeConstCastCheck.cpp + ProTypeCstyleCastCheck.cpp + ProTypeReinterpretCastCheck.cpp + ProTypeStaticCastDowncastCheck.cpp + ProTypeUnionAccessCheck.cpp + ProTypeVarargCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyMiscModule + clangTidyUtils + clangTooling + ) diff --git a/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp b/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp new file mode 100644 index 00000000..db783a39 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp @@ -0,0 +1,66 @@ +//===--- CppCoreGuidelinesModule.cpp - clang-tidy -------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "../misc/AssignOperatorSignatureCheck.h" +#include "ProBoundsArrayToPointerDecayCheck.h" +#include "ProBoundsConstantArrayIndexCheck.h" +#include "ProBoundsPointerArithmeticCheck.h" +#include "ProTypeConstCastCheck.h" +#include "ProTypeCstyleCastCheck.h" +#include "ProTypeReinterpretCastCheck.h" +#include "ProTypeStaticCastDowncastCheck.h" +#include "ProTypeUnionAccessCheck.h" +#include "ProTypeVarargCheck.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// A module containing checks of the C++ Core Guidelines +class CppCoreGuidelinesModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "cppcoreguidelines-pro-bounds-array-to-pointer-decay"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-bounds-constant-array-index"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-bounds-pointer-arithmetic"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-const-cast"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-cstyle-cast"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-reinterpret-cast"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-static-cast-downcast"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-union-access"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-vararg"); + CheckFactories.registerCheck( + "cppcoreguidelines-c-copy-assignment-signature"); + } +}; + +// Register the LLVMTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("cppcoreguidelines-module", "Adds checks for the C++ Core Guidelines."); + +} // namespace cppcoreguidelines + +// This anchor is used to force the linker to link in the generated object file +// and thus register the CppCoreGuidelinesModule. +volatile int CppCoreGuidelinesModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/Makefile b/clang-tidy/cppcoreguidelines/Makefile new file mode 100644 index 00000000..a30b3c0f --- /dev/null +++ b/clang-tidy/cppcoreguidelines/Makefile @@ -0,0 +1,12 @@ +##===- clang-tidy/cppcoreguidelines/Makefile ----------------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## +CLANG_LEVEL := ../../../.. +LIBRARYNAME := clangTidyCppCoreGuidelinesModule + +include $(CLANG_LEVEL)/Makefile diff --git a/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp b/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp new file mode 100644 index 00000000..75093a0d --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp @@ -0,0 +1,76 @@ +//===--- ProBoundsArrayToPointerDecayCheck.cpp - clang-tidy----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProBoundsArrayToPointerDecayCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +AST_MATCHER_P(CXXForRangeStmt, hasRangeBeginEndStmt, + ast_matchers::internal::Matcher, InnerMatcher) { + const DeclStmt *const Stmt = Node.getBeginEndStmt(); + return (Stmt != nullptr && InnerMatcher.matches(*Stmt, Finder, Builder)); +} + +AST_MATCHER(Stmt, isInsideOfRangeBeginEndStmt) { + return stmt(hasAncestor(cxxForRangeStmt( + hasRangeBeginEndStmt(hasDescendant(equalsNode(&Node)))))) + .matches(Node, Finder, Builder); +} + +AST_MATCHER_P(Expr, hasParentIgnoringImpCasts, + ast_matchers::internal::Matcher, InnerMatcher) { + const Expr *E = &Node; + do { + ASTContext::DynTypedNodeList Parents = + Finder->getASTContext().getParents(*E); + if (Parents.size() != 1) + return false; + E = Parents[0].get(); + if (!E) + return false; + } while (isa(E)); + + return InnerMatcher.matches(*E, Finder, Builder); +} + +void ProBoundsArrayToPointerDecayCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // The only allowed array to pointer decay + // 1) just before array subscription + // 2) inside a range-for over an array + // 3) if it converts a string literal to a pointer + Finder->addMatcher( + implicitCastExpr(unless(hasParent(arraySubscriptExpr())), + unless(hasParentIgnoringImpCasts(explicitCastExpr())), + unless(isInsideOfRangeBeginEndStmt()), + unless(hasSourceExpression(stringLiteral()))) + .bind("cast"), + this); +} + +void ProBoundsArrayToPointerDecayCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *MatchedCast = Result.Nodes.getNodeAs("cast"); + if (MatchedCast->getCastKind() != CK_ArrayToPointerDecay) + return; + + diag(MatchedCast->getExprLoc(), "do not implicitly decay an array into a " + "pointer; consider using gsl::array_view or " + "an explicit cast instead"); +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.h b/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.h new file mode 100644 index 00000000..ec6979a5 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.h @@ -0,0 +1,33 @@ +//===--- ProBoundsArrayToPointerDecayCheck.h - clang-tidy--------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_ARRAY_TO_POINTER_DECAY_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_ARRAY_TO_POINTER_DECAY_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// This check flags all array to pointer decays +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-bounds-array-to-pointer-decay.html +class ProBoundsArrayToPointerDecayCheck : public ClangTidyCheck { +public: + ProBoundsArrayToPointerDecayCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_ARRAY_TO_POINTER_DECAY_H diff --git a/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp b/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp new file mode 100644 index 00000000..a8dc1d87 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp @@ -0,0 +1,132 @@ +//===--- ProBoundsConstantArrayIndexCheck.cpp - clang-tidy-----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProBoundsConstantArrayIndexCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Preprocessor.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +ProBoundsConstantArrayIndexCheck::ProBoundsConstantArrayIndexCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), GslHeader(Options.get("GslHeader", "")), + IncludeStyle(IncludeSorter::parseIncludeStyle( + Options.get("IncludeStyle", "llvm"))) {} + +void ProBoundsConstantArrayIndexCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "GslHeader", GslHeader); + Options.store(Opts, "IncludeStyle", IncludeStyle); +} + +void ProBoundsConstantArrayIndexCheck::registerPPCallbacks( + CompilerInstance &Compiler) { + if (!getLangOpts().CPlusPlus) + return; + + Inserter.reset(new IncludeInserter(Compiler.getSourceManager(), + Compiler.getLangOpts(), IncludeStyle)); + Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks()); +} + +void ProBoundsConstantArrayIndexCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(arraySubscriptExpr(hasBase(ignoringImpCasts(hasType( + constantArrayType().bind("type")))), + hasIndex(expr().bind("index"))) + .bind("expr"), + this); + + Finder->addMatcher( + cxxOperatorCallExpr( + hasOverloadedOperatorName("[]"), + hasArgument( + 0, hasType(cxxRecordDecl(hasName("::std::array")).bind("type"))), + hasArgument(1, expr().bind("index"))) + .bind("expr"), + this); +} + +void ProBoundsConstantArrayIndexCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Matched = Result.Nodes.getNodeAs("expr"); + const auto *IndexExpr = Result.Nodes.getNodeAs("index"); + llvm::APSInt Index; + if (!IndexExpr->isIntegerConstantExpr(Index, *Result.Context, nullptr, + /*isEvaluated=*/true)) { + SourceRange BaseRange; + if (const auto *ArraySubscriptE = dyn_cast(Matched)) + BaseRange = ArraySubscriptE->getBase()->getSourceRange(); + else + BaseRange = + dyn_cast(Matched)->getArg(0)->getSourceRange(); + SourceRange IndexRange = IndexExpr->getSourceRange(); + + auto Diag = diag(Matched->getExprLoc(), + "do not use array subscript when the index is " + "not an integer constant expression; use gsl::at() " + "instead"); + if (!GslHeader.empty()) { + Diag << FixItHint::CreateInsertion(BaseRange.getBegin(), "gsl::at(") + << FixItHint::CreateReplacement( + SourceRange(BaseRange.getEnd().getLocWithOffset(1), + IndexRange.getBegin().getLocWithOffset(-1)), + ", ") + << FixItHint::CreateReplacement(Matched->getLocEnd(), ")"); + + Optional Insertion = Inserter->CreateIncludeInsertion( + Result.SourceManager->getMainFileID(), GslHeader, + /*IsAngled=*/false); + if (Insertion) + Diag << Insertion.getValue(); + } + return; + } + + const auto *StdArrayDecl = + Result.Nodes.getNodeAs("type"); + + // For static arrays, this is handled in clang-diagnostic-array-bounds. + if (!StdArrayDecl) + return; + + if (Index.isSigned() && Index.isNegative()) { + diag(Matched->getExprLoc(), + "std::array<> index %0 is negative") + << Index.toString(10); + return; + } + + const TemplateArgumentList &TemplateArgs = StdArrayDecl->getTemplateArgs(); + if (TemplateArgs.size() < 2) + return; + // First template arg of std::array is the type, second arg is the size. + const auto &SizeArg = TemplateArgs[1]; + if (SizeArg.getKind() != TemplateArgument::Integral) + return; + llvm::APInt ArraySize = SizeArg.getAsIntegral(); + + // Get uint64_t values, because different bitwidths would lead to an assertion + // in APInt::uge. + if (Index.getZExtValue() >= ArraySize.getZExtValue()) { + diag(Matched->getExprLoc(), "std::array<> index %0 is past the end of the array " + "(which contains %1 elements)") + << Index.toString(10) << ArraySize.toString(10, false); + } +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.h b/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.h new file mode 100644 index 00000000..1caf28cc --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.h @@ -0,0 +1,40 @@ +//===--- ProBoundsConstantArrayIndexCheck.h - clang-tidy---------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_CONSTANT_ARRAY_INDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_CONSTANT_ARRAY_INDEX_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" + +namespace clang { +namespace tidy { + +/// This checks that all array subscriptions on static arrays and std::arrays +/// have a constant index and are within bounds +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-bounds-constant-array-index.html +class ProBoundsConstantArrayIndexCheck : public ClangTidyCheck { + const std::string GslHeader; + const IncludeSorter::IncludeStyle IncludeStyle; + std::unique_ptr Inserter; + +public: + ProBoundsConstantArrayIndexCheck(StringRef Name, ClangTidyContext *Context); + void registerPPCallbacks(CompilerInstance &Compiler) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_CONSTANT_ARRAY_INDEX_H diff --git a/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp b/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp new file mode 100644 index 00000000..9dcd7c4a --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp @@ -0,0 +1,57 @@ +//===--- ProBoundsPointerArithmeticCheck.cpp - clang-tidy------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProBoundsPointerArithmeticCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +void ProBoundsPointerArithmeticCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // Flag all operators +, -, +=, -=, ++, -- that result in a pointer + Finder->addMatcher( + binaryOperator( + anyOf(hasOperatorName("+"), hasOperatorName("-"), + hasOperatorName("+="), hasOperatorName("-=")), + hasType(pointerType()), + unless(hasLHS(ignoringImpCasts(declRefExpr(to(isImplicit())))))) + .bind("expr"), + this); + + Finder->addMatcher( + unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--")), + hasType(pointerType())) + .bind("expr"), + this); + + // Array subscript on a pointer (not an array) is also pointer arithmetic + Finder->addMatcher( + arraySubscriptExpr( + hasBase(ignoringImpCasts( + anyOf(hasType(pointerType()), + hasType(decayedType(hasDecayedType(pointerType()))))))) + .bind("expr"), + this); +} + +void +ProBoundsPointerArithmeticCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedExpr = Result.Nodes.getNodeAs("expr"); + + diag(MatchedExpr->getExprLoc(), "do not use pointer arithmetic"); +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.h b/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.h new file mode 100644 index 00000000..f3a3fb18 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.h @@ -0,0 +1,34 @@ +//===--- ProBoundsPointerArithmeticCheck.h - clang-tidy----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_POINTER_ARITHMETIC_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_POINTER_ARITHMETIC_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// Flags all kinds of pointer arithmetic that have result of pointer type, i.e. +/// +, -, +=, -=, ++, --. In addition, the [] operator on pointers (not on arrays) is flagged. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-bounds-pointer-arithmetic.html +class ProBoundsPointerArithmeticCheck : public ClangTidyCheck { +public: + ProBoundsPointerArithmeticCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_POINTER_ARITHMETIC_H diff --git a/clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.cpp b/clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.cpp new file mode 100644 index 00000000..6b32ad04 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.cpp @@ -0,0 +1,32 @@ +//===--- ProTypeConstCastCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProTypeConstCastCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +void ProTypeConstCastCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(cxxConstCastExpr().bind("cast"), this); +} + +void ProTypeConstCastCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedCast = Result.Nodes.getNodeAs("cast"); + diag(MatchedCast->getOperatorLoc(), "do not use const_cast"); +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.h b/clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.h new file mode 100644 index 00000000..af7c5547 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.h @@ -0,0 +1,33 @@ +//===--- ProTypeConstCastCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_CONST_CAST_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_CONST_CAST_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// This check flags all instances of const_cast +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-const-cast.html +class ProTypeConstCastCheck : public ClangTidyCheck { +public: + ProTypeConstCastCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_CONST_CAST_H diff --git a/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp b/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp new file mode 100644 index 00000000..542dd854 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp @@ -0,0 +1,107 @@ +//===--- ProTypeCstyleCastCheck.cpp - clang-tidy---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProTypeCstyleCastCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +static bool needsConstCast(QualType SourceType, QualType DestType) { + SourceType = SourceType.getNonReferenceType(); + DestType = DestType.getNonReferenceType(); + while (SourceType->isPointerType() && DestType->isPointerType()) { + SourceType = SourceType->getPointeeType(); + DestType = DestType->getPointeeType(); + if (SourceType.isConstQualified() && !DestType.isConstQualified()) + return true; + } + return false; +} + +void ProTypeCstyleCastCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + cStyleCastExpr(unless(isInTemplateInstantiation())).bind("cast"), this); +} + +void ProTypeCstyleCastCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedCast = Result.Nodes.getNodeAs("cast"); + + if (MatchedCast->getCastKind() == CK_BitCast || + MatchedCast->getCastKind() == CK_LValueBitCast || + MatchedCast->getCastKind() == CK_IntegralToPointer || + MatchedCast->getCastKind() == CK_PointerToIntegral || + MatchedCast->getCastKind() == CK_ReinterpretMemberPointer) { + diag(MatchedCast->getLocStart(), + "do not use C-style cast to convert between unrelated types"); + return; + } + + QualType SourceType = MatchedCast->getSubExpr()->getType(); + + if (MatchedCast->getCastKind() == CK_BaseToDerived) { + const auto *SourceDecl = SourceType->getPointeeCXXRecordDecl(); + if (!SourceDecl) // The cast is from object to reference. + SourceDecl = SourceType->getAsCXXRecordDecl(); + if (!SourceDecl) + return; + + if (SourceDecl->isPolymorphic()) { + // Leave type spelling exactly as it was (unlike + // getTypeAsWritten().getAsString() which would spell enum types 'enum + // X'). + StringRef DestTypeString = Lexer::getSourceText( + CharSourceRange::getTokenRange( + MatchedCast->getLParenLoc().getLocWithOffset(1), + MatchedCast->getRParenLoc().getLocWithOffset(-1)), + *Result.SourceManager, Result.Context->getLangOpts()); + + auto diag_builder = diag( + MatchedCast->getLocStart(), + "do not use C-style cast to downcast from a base to a derived class; " + "use dynamic_cast instead"); + + const Expr *SubExpr = + MatchedCast->getSubExprAsWritten()->IgnoreImpCasts(); + std::string CastText = ("dynamic_cast<" + DestTypeString + ">").str(); + if (!isa(SubExpr)) { + CastText.push_back('('); + diag_builder << FixItHint::CreateInsertion( + Lexer::getLocForEndOfToken(SubExpr->getLocEnd(), 0, + *Result.SourceManager, + Result.Context->getLangOpts()), + ")"); + } + auto ParenRange = CharSourceRange::getTokenRange( + MatchedCast->getLParenLoc(), MatchedCast->getRParenLoc()); + diag_builder << FixItHint::CreateReplacement(ParenRange, CastText); + } else { + diag( + MatchedCast->getLocStart(), + "do not use C-style cast to downcast from a base to a derived class"); + } + return; + } + + if (MatchedCast->getCastKind() == CK_NoOp && + needsConstCast(SourceType, MatchedCast->getType())) { + diag(MatchedCast->getLocStart(), + "do not use C-style cast to cast away constness"); + } +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.h b/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.h new file mode 100644 index 00000000..f85be87d --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.h @@ -0,0 +1,34 @@ +//===--- ProTypeCstyleCastCheck.h - clang-tidy-------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_CSTYLE_CAST_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_CSTYLE_CAST_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// This check flags all use of C-style casts that perform a static_cast +/// downcast, const_cast, or reinterpret_cast. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-cstyle-cast.html +class ProTypeCstyleCastCheck : public ClangTidyCheck { +public: + ProTypeCstyleCastCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_CSTYLE_CAST_H diff --git a/clang-tidy/cppcoreguidelines/ProTypeReinterpretCastCheck.cpp b/clang-tidy/cppcoreguidelines/ProTypeReinterpretCastCheck.cpp new file mode 100644 index 00000000..57c4a59b --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeReinterpretCastCheck.cpp @@ -0,0 +1,34 @@ +//===--- ProTypeReinterpretCastCheck.cpp - clang-tidy----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProTypeReinterpretCastCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +void ProTypeReinterpretCastCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(cxxReinterpretCastExpr().bind("cast"), this); +} + +void ProTypeReinterpretCastCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *MatchedCast = + Result.Nodes.getNodeAs("cast"); + diag(MatchedCast->getOperatorLoc(), "do not use reinterpret_cast"); +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProTypeReinterpretCastCheck.h b/clang-tidy/cppcoreguidelines/ProTypeReinterpretCastCheck.h new file mode 100644 index 00000000..0ca16e54 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeReinterpretCastCheck.h @@ -0,0 +1,33 @@ +//===--- ProTypeReinterpretCast.h - clang-tidy------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_REINTERPRETCAST_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_REINTERPRETCAST_CHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// Flags all occurrences of reinterpret_cast +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-reinterpret-cast.html +class ProTypeReinterpretCastCheck : public ClangTidyCheck { +public: + ProTypeReinterpretCastCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_REINTERPRETCAST_CHECK_H diff --git a/clang-tidy/cppcoreguidelines/ProTypeStaticCastDowncastCheck.cpp b/clang-tidy/cppcoreguidelines/ProTypeStaticCastDowncastCheck.cpp new file mode 100644 index 00000000..0033e4ed --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeStaticCastDowncastCheck.cpp @@ -0,0 +1,52 @@ +//===--- ProTypeStaticCastDowncastCheck.cpp - clang-tidy-------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProTypeStaticCastDowncastCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +void ProTypeStaticCastDowncastCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + cxxStaticCastExpr(unless(isInTemplateInstantiation())).bind("cast"), + this); +} + +void ProTypeStaticCastDowncastCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedCast = Result.Nodes.getNodeAs("cast"); + if (MatchedCast->getCastKind() != CK_BaseToDerived) + return; + + QualType SourceType = MatchedCast->getSubExpr()->getType(); + const auto *SourceDecl = SourceType->getPointeeCXXRecordDecl(); + if (!SourceDecl) // The cast is from object to reference + SourceDecl = SourceType->getAsCXXRecordDecl(); + if (!SourceDecl) + return; + + if (SourceDecl->isPolymorphic()) + diag(MatchedCast->getOperatorLoc(), + "do not use static_cast to downcast from a base to a derived class; " + "use dynamic_cast instead") + << FixItHint::CreateReplacement(MatchedCast->getOperatorLoc(), + "dynamic_cast"); + else + diag(MatchedCast->getOperatorLoc(), + "do not use static_cast to downcast from a base to a derived class"); +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProTypeStaticCastDowncastCheck.h b/clang-tidy/cppcoreguidelines/ProTypeStaticCastDowncastCheck.h new file mode 100644 index 00000000..02de48ae --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeStaticCastDowncastCheck.h @@ -0,0 +1,33 @@ +//===--- ProTypeStaticCastDowncastCheck.h - clang-tidy-----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_STATIC_CAST_DOWNCAST_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_STATIC_CAST_DOWNCAST_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// Checks for usages of static_cast, where a base class is downcasted to a derived class. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-static-cast-downcast.html +class ProTypeStaticCastDowncastCheck : public ClangTidyCheck { +public: + ProTypeStaticCastDowncastCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_STATIC_CAST_DOWNCAST_H diff --git a/clang-tidy/cppcoreguidelines/ProTypeUnionAccessCheck.cpp b/clang-tidy/cppcoreguidelines/ProTypeUnionAccessCheck.cpp new file mode 100644 index 00000000..64f51369 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeUnionAccessCheck.cpp @@ -0,0 +1,33 @@ +//===--- ProTypeUnionAccessCheck.cpp - clang-tidy--------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProTypeUnionAccessCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +void ProTypeUnionAccessCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(memberExpr(hasObjectExpression(hasType(recordDecl(isUnion())))).bind("expr"), this); +} + +void ProTypeUnionAccessCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Matched = Result.Nodes.getNodeAs("expr"); + diag(Matched->getMemberLoc(), "do not access members of unions; use (boost::)variant instead"); +} + +} // namespace tidy +} // namespace clang + diff --git a/clang-tidy/cppcoreguidelines/ProTypeUnionAccessCheck.h b/clang-tidy/cppcoreguidelines/ProTypeUnionAccessCheck.h new file mode 100644 index 00000000..67ce6165 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeUnionAccessCheck.h @@ -0,0 +1,35 @@ +//===--- ProTypeUnionAccessCheck.h - clang-tidy------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_UNION_ACCESS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_UNION_ACCESS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// This check flags all access to members of unions. +/// Access to a union as a whole (e.g. passing to a function) is not flagged. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-union-access.html +class ProTypeUnionAccessCheck : public ClangTidyCheck { +public: + ProTypeUnionAccessCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_UNION_ACCESS_H + diff --git a/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.cpp b/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.cpp new file mode 100644 index 00000000..526016ea --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.cpp @@ -0,0 +1,76 @@ +//===--- ProTypeVarargCheck.cpp - clang-tidy-------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProTypeVarargCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +const internal::VariadicDynCastAllOfMatcher vAArgExpr; + +void ProTypeVarargCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(vAArgExpr().bind("va_use"), this); + + Finder->addMatcher( + callExpr(callee(functionDecl(isVariadic()))) + .bind("callvararg"), + this); +} + +static bool hasSingleVariadicArgumentWithValue(const CallExpr *C, uint64_t I) { + const auto *FDecl = dyn_cast(C->getCalleeDecl()); + if (!FDecl) + return false; + + auto N = FDecl->getNumParams(); // Number of parameters without '...' + if (C->getNumArgs() != N + 1) + return false; // more/less than one argument passed to '...' + + const auto *IntLit = + dyn_cast(C->getArg(N)->IgnoreParenImpCasts()); + if (!IntLit) + return false; + + if (IntLit->getValue() != I) + return false; + + return true; +} + +void ProTypeVarargCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Matched = Result.Nodes.getNodeAs("callvararg")) { + if (hasSingleVariadicArgumentWithValue(Matched, 0)) + return; + diag(Matched->getExprLoc(), "do not call c-style vararg functions"); + } + + if (const auto *Matched = Result.Nodes.getNodeAs("va_use")) { + diag(Matched->getExprLoc(), + "do not use va_start/va_arg to define c-style vararg functions; " + "use variadic templates instead"); + } + + if (const auto *Matched = Result.Nodes.getNodeAs("va_list")) { + auto SR = Matched->getSourceRange(); + if (SR.isInvalid()) + return; // some implicitly generated builtins take va_list + diag(SR.getBegin(), "do not declare variables of type va_list; " + "use variadic templates instead"); + } +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.h b/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.h new file mode 100644 index 00000000..fd1f9bfe --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.h @@ -0,0 +1,34 @@ +//===--- ProTypeVarargCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_VARARG_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_VARARG_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// This check flags all calls to c-style variadic functions and all use +/// of va_arg. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-vararg.html +class ProTypeVarargCheck : public ClangTidyCheck { +public: + ProTypeVarargCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_VARARG_H diff --git a/clang-tidy/google/AvoidCStyleCastsCheck.cpp b/clang-tidy/google/AvoidCStyleCastsCheck.cpp new file mode 100644 index 00000000..34353b35 --- /dev/null +++ b/clang-tidy/google/AvoidCStyleCastsCheck.cpp @@ -0,0 +1,175 @@ +//===--- AvoidCStyleCastsCheck.cpp - clang-tidy -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "AvoidCStyleCastsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace readability { + +void +AvoidCStyleCastsCheck::registerMatchers(ast_matchers::MatchFinder *Finder) { + Finder->addMatcher( + cStyleCastExpr( + // Filter out (EnumType)IntegerLiteral construct, which is generated + // for non-type template arguments of enum types. + // FIXME: Remove this once this is fixed in the AST. + unless(hasParent(substNonTypeTemplateParmExpr())), + // Avoid matches in template instantiations. + unless(isInTemplateInstantiation())).bind("cast"), + this); +} + +static bool needsConstCast(QualType SourceType, QualType DestType) { + SourceType = SourceType.getNonReferenceType(); + DestType = DestType.getNonReferenceType(); + while (SourceType->isPointerType() && DestType->isPointerType()) { + SourceType = SourceType->getPointeeType(); + DestType = DestType->getPointeeType(); + if (SourceType.isConstQualified() && !DestType.isConstQualified()) + return true; + } + return false; +} + +static bool pointedTypesAreEqual(QualType SourceType, QualType DestType) { + SourceType = SourceType.getNonReferenceType(); + DestType = DestType.getNonReferenceType(); + while (SourceType->isPointerType() && DestType->isPointerType()) { + SourceType = SourceType->getPointeeType(); + DestType = DestType->getPointeeType(); + } + return SourceType.getUnqualifiedType() == DestType.getUnqualifiedType(); +} + +void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) { + const auto *CastExpr = Result.Nodes.getNodeAs("cast"); + + auto ParenRange = CharSourceRange::getTokenRange(CastExpr->getLParenLoc(), + CastExpr->getRParenLoc()); + // Ignore casts in macros. + if (ParenRange.getBegin().isMacroID() || ParenRange.getEnd().isMacroID()) + return; + + // Casting to void is an idiomatic way to mute "unused variable" and similar + // warnings. + if (CastExpr->getTypeAsWritten()->isVoidType()) + return; + + QualType SourceType = CastExpr->getSubExprAsWritten()->getType(); + QualType DestType = CastExpr->getTypeAsWritten(); + + if (SourceType == DestType) { + diag(CastExpr->getLocStart(), "redundant cast to the same type") + << FixItHint::CreateRemoval(ParenRange); + return; + } + SourceType = SourceType.getCanonicalType(); + DestType = DestType.getCanonicalType(); + if (SourceType == DestType) { + diag(CastExpr->getLocStart(), + "possibly redundant cast between typedefs of the same type"); + return; + } + + + // The rest of this check is only relevant to C++. + if (!Result.Context->getLangOpts().CPlusPlus) + return; + // Ignore code inside extern "C" {} blocks. + if (!match(expr(hasAncestor(linkageSpecDecl())), *CastExpr, *Result.Context) + .empty()) + return; + // Ignore code in .c files and headers included from them, even if they are + // compiled as C++. + if (getCurrentMainFile().endswith(".c")) + return; + // Ignore code in .c files #included in other files (which shouldn't be done, + // but people still do this for test and other purposes). + SourceManager &SM = *Result.SourceManager; + if (SM.getFilename(SM.getSpellingLoc(CastExpr->getLocStart())).endswith(".c")) + return; + + // Leave type spelling exactly as it was (unlike + // getTypeAsWritten().getAsString() which would spell enum types 'enum X'). + StringRef DestTypeString = + Lexer::getSourceText(CharSourceRange::getTokenRange( + CastExpr->getLParenLoc().getLocWithOffset(1), + CastExpr->getRParenLoc().getLocWithOffset(-1)), + SM, Result.Context->getLangOpts()); + + auto diag_builder = + diag(CastExpr->getLocStart(), "C-style casts are discouraged; use %0"); + + auto ReplaceWithCast = [&](StringRef CastType) { + diag_builder << CastType; + + const Expr *SubExpr = CastExpr->getSubExprAsWritten()->IgnoreImpCasts(); + std::string CastText = (CastType + "<" + DestTypeString + ">").str(); + if (!isa(SubExpr)) { + CastText.push_back('('); + diag_builder << FixItHint::CreateInsertion( + Lexer::getLocForEndOfToken(SubExpr->getLocEnd(), 0, SM, + Result.Context->getLangOpts()), + ")"); + } + diag_builder << FixItHint::CreateReplacement(ParenRange, CastText); + }; + + // Suggest appropriate C++ cast. See [expr.cast] for cast notation semantics. + switch (CastExpr->getCastKind()) { + case CK_NoOp: + if (needsConstCast(SourceType, DestType) && + pointedTypesAreEqual(SourceType, DestType)) { + ReplaceWithCast("const_cast"); + return; + } + if (DestType->isReferenceType() && + (SourceType.getNonReferenceType() == + DestType.getNonReferenceType().withConst() || + SourceType.getNonReferenceType() == DestType.getNonReferenceType())) { + ReplaceWithCast("const_cast"); + return; + } + // FALLTHROUGH + case clang::CK_IntegralCast: + // Convert integral and no-op casts between builtin types and enums to + // static_cast. A cast from enum to integer may be unnecessary, but it's + // still retained. + if ((SourceType->isBuiltinType() || SourceType->isEnumeralType()) && + (DestType->isBuiltinType() || DestType->isEnumeralType())) { + ReplaceWithCast("static_cast"); + return; + } + break; + case CK_BitCast: + // FIXME: Suggest const_cast<...>(reinterpret_cast<...>(...)) replacement. + if (!needsConstCast(SourceType, DestType)) { + ReplaceWithCast("reinterpret_cast"); + return; + } + break; + default: + break; + } + + diag_builder << "static_cast/const_cast/reinterpret_cast"; +} + +} // namespace readability +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/AvoidCStyleCastsCheck.h b/clang-tidy/google/AvoidCStyleCastsCheck.h new file mode 100644 index 00000000..aa98ad8c --- /dev/null +++ b/clang-tidy/google/AvoidCStyleCastsCheck.h @@ -0,0 +1,42 @@ +//===--- AvoidCStyleCastsCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_AVOIDCSTYLECASTSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_AVOIDCSTYLECASTSCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace readability { + +/// Finds usages of C-style casts. +/// +/// http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Casting#Casting +/// +/// Corresponding cpplint.py check name: 'readability/casting'. +/// +/// This check is similar to `-Wold-style-cast`, but it suggests automated fixes +/// in some cases. The reported locations should not be different from the +/// ones generated by `-Wold-style-cast`. +class AvoidCStyleCastsCheck : public ClangTidyCheck { +public: + AvoidCStyleCastsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_AVOIDCSTYLECASTSCHECK_H diff --git a/clang-tidy/google/CMakeLists.txt b/clang-tidy/google/CMakeLists.txt new file mode 100644 index 00000000..3597613f --- /dev/null +++ b/clang-tidy/google/CMakeLists.txt @@ -0,0 +1,24 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyGoogleModule + AvoidCStyleCastsCheck.cpp + ExplicitConstructorCheck.cpp + ExplicitMakePairCheck.cpp + GlobalNamesInHeadersCheck.cpp + GoogleTidyModule.cpp + IntegerTypesCheck.cpp + MemsetZeroLengthCheck.cpp + OverloadedUnaryAndCheck.cpp + StringReferenceMemberCheck.cpp + TodoCommentCheck.cpp + UnnamedNamespaceInHeaderCheck.cpp + UsingNamespaceDirectiveCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyReadabilityModule + ) diff --git a/clang-tidy/google/ExplicitConstructorCheck.cpp b/clang-tidy/google/ExplicitConstructorCheck.cpp new file mode 100644 index 00000000..e3095fe5 --- /dev/null +++ b/clang-tidy/google/ExplicitConstructorCheck.cpp @@ -0,0 +1,132 @@ +//===--- ExplicitConstructorCheck.cpp - clang-tidy ------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ExplicitConstructorCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { + +void ExplicitConstructorCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (getLangOpts().CPlusPlus) + Finder->addMatcher( + cxxConstructorDecl(unless(isInstantiated())).bind("ctor"), this); +} + +// Looks for the token matching the predicate and returns the range of the found +// token including trailing whitespace. +static SourceRange FindToken(const SourceManager &Sources, LangOptions LangOpts, + SourceLocation StartLoc, SourceLocation EndLoc, + bool (*Pred)(const Token &)) { + if (StartLoc.isMacroID() || EndLoc.isMacroID()) + return SourceRange(); + FileID File = Sources.getFileID(Sources.getSpellingLoc(StartLoc)); + StringRef Buf = Sources.getBufferData(File); + const char *StartChar = Sources.getCharacterData(StartLoc); + Lexer Lex(StartLoc, LangOpts, StartChar, StartChar, Buf.end()); + Lex.SetCommentRetentionState(true); + Token Tok; + do { + Lex.LexFromRawLexer(Tok); + if (Pred(Tok)) { + Token NextTok; + Lex.LexFromRawLexer(NextTok); + return SourceRange(Tok.getLocation(), NextTok.getLocation()); + } + } while (Tok.isNot(tok::eof) && Tok.getLocation() < EndLoc); + + return SourceRange(); +} + +static bool declIsStdInitializerList(const NamedDecl *D) { + // First use the fast getName() method to avoid unnecessary calls to the + // slow getQualifiedNameAsString(). + return D->getName() == "initializer_list" && + D->getQualifiedNameAsString() == "std::initializer_list"; +} + +static bool isStdInitializerList(QualType Type) { + Type = Type.getCanonicalType(); + if (const auto *TS = Type->getAs()) { + if (const TemplateDecl *TD = TS->getTemplateName().getAsTemplateDecl()) + return declIsStdInitializerList(TD); + } + if (const auto *RT = Type->getAs()) { + if (const auto *Specialization = + dyn_cast(RT->getDecl())) + return declIsStdInitializerList(Specialization->getSpecializedTemplate()); + } + return false; +} + +void ExplicitConstructorCheck::check(const MatchFinder::MatchResult &Result) { + const CXXConstructorDecl *Ctor = + Result.Nodes.getNodeAs("ctor"); + // Do not be confused: isExplicit means 'explicit' keyword is present, + // isImplicit means that it's a compiler-generated constructor. + if (Ctor->isOutOfLine() || Ctor->isImplicit() || Ctor->isDeleted() || + Ctor->getNumParams() == 0 || Ctor->getMinRequiredArguments() > 1) + return; + + bool takesInitializerList = isStdInitializerList( + Ctor->getParamDecl(0)->getType().getNonReferenceType()); + if (Ctor->isExplicit() && + (Ctor->isCopyOrMoveConstructor() || takesInitializerList)) { + auto isKWExplicit = [](const Token &Tok) { + return Tok.is(tok::raw_identifier) && + Tok.getRawIdentifier() == "explicit"; + }; + SourceRange ExplicitTokenRange = + FindToken(*Result.SourceManager, Result.Context->getLangOpts(), + Ctor->getOuterLocStart(), Ctor->getLocEnd(), isKWExplicit); + StringRef ConstructorDescription; + if (Ctor->isMoveConstructor()) + ConstructorDescription = "move"; + else if (Ctor->isCopyConstructor()) + ConstructorDescription = "copy"; + else + ConstructorDescription = "initializer-list"; + + DiagnosticBuilder Diag = + diag(Ctor->getLocation(), + "%0 constructor should not be declared explicit") + << ConstructorDescription; + if (ExplicitTokenRange.isValid()) { + Diag << FixItHint::CreateRemoval( + CharSourceRange::getCharRange(ExplicitTokenRange)); + } + return; + } + + if (Ctor->isExplicit() || Ctor->isCopyOrMoveConstructor() || + takesInitializerList) + return; + + bool SingleArgument = + Ctor->getNumParams() == 1 && !Ctor->getParamDecl(0)->isParameterPack(); + SourceLocation Loc = Ctor->getLocation(); + diag(Loc, + "%0 must be marked explicit to avoid unintentional implicit conversions") + << (SingleArgument + ? "single-argument constructors" + : "constructors that are callable with a single argument") + << FixItHint::CreateInsertion(Loc, "explicit "); +} + +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/ExplicitConstructorCheck.h b/clang-tidy/google/ExplicitConstructorCheck.h new file mode 100644 index 00000000..2c074f1c --- /dev/null +++ b/clang-tidy/google/ExplicitConstructorCheck.h @@ -0,0 +1,34 @@ +//===--- ExplicitConstructorCheck.h - clang-tidy ----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_EXPLICITCONSTRUCTORCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_EXPLICITCONSTRUCTORCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { + +/// Checks that all single-argument constructors are explicit. +/// +/// See http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Explicit_Constructors +class ExplicitConstructorCheck : public ClangTidyCheck { +public: + ExplicitConstructorCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_EXPLICITCONSTRUCTORCHECK_H diff --git a/clang-tidy/google/ExplicitMakePairCheck.cpp b/clang-tidy/google/ExplicitMakePairCheck.cpp new file mode 100644 index 00000000..a07e0fff --- /dev/null +++ b/clang-tidy/google/ExplicitMakePairCheck.cpp @@ -0,0 +1,77 @@ +//===--- ExplicitMakePairCheck.cpp - clang-tidy -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ExplicitMakePairCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace { +AST_MATCHER(DeclRefExpr, hasExplicitTemplateArgs) { + return Node.hasExplicitTemplateArgs(); +} +} // namespace + +namespace tidy { +namespace google { +namespace build { + +void +ExplicitMakePairCheck::registerMatchers(ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + // Look for std::make_pair with explicit template args. Ignore calls in + // templates. + Finder->addMatcher( + callExpr(unless(isInTemplateInstantiation()), + callee(expr(ignoringParenImpCasts( + declRefExpr(hasExplicitTemplateArgs(), + to(functionDecl(hasName("::std::make_pair")))) + .bind("declref"))))).bind("call"), + this); +} + +void ExplicitMakePairCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("call"); + const auto *DeclRef = Result.Nodes.getNodeAs("declref"); + + // Sanity check: The use might have overriden ::std::make_pair. + if (Call->getNumArgs() != 2) + return; + + const Expr *Arg0 = Call->getArg(0)->IgnoreParenImpCasts(); + const Expr *Arg1 = Call->getArg(1)->IgnoreParenImpCasts(); + + // If types don't match, we suggest replacing with std::pair and explicit + // template arguments. Otherwise just remove the template arguments from + // make_pair. + if (Arg0->getType() != Call->getArg(0)->getType() || + Arg1->getType() != Call->getArg(1)->getType()) { + diag(Call->getLocStart(), "for C++11-compatibility, use pair directly") + << FixItHint::CreateReplacement( + SourceRange(DeclRef->getLocStart(), DeclRef->getLAngleLoc()), + "std::pair<"); + } else { + diag(Call->getLocStart(), + "for C++11-compatibility, omit template arguments from make_pair") + << FixItHint::CreateRemoval( + SourceRange(DeclRef->getLAngleLoc(), DeclRef->getRAngleLoc())); + } +} + +} // namespace build +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/ExplicitMakePairCheck.h b/clang-tidy/google/ExplicitMakePairCheck.h new file mode 100644 index 00000000..a29825f3 --- /dev/null +++ b/clang-tidy/google/ExplicitMakePairCheck.h @@ -0,0 +1,39 @@ +//===--- ExplicitMakePairCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_EXPLICITMAKEPAIRCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_EXPLICITMAKEPAIRCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace build { + +/// Check that `make_pair`'s template arguments are deduced. +/// +/// G++ 4.6 in C++11 mode fails badly if `make_pair`'s template arguments are +/// specified explicitly, and such use isn't intended in any case. +/// +/// Corresponding cpplint.py check name: 'build/explicit_make_pair'. +class ExplicitMakePairCheck : public ClangTidyCheck { +public: + ExplicitMakePairCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace build +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_EXPLICITMAKEPAIRCHECK_H diff --git a/clang-tidy/google/GlobalNamesInHeadersCheck.cpp b/clang-tidy/google/GlobalNamesInHeadersCheck.cpp new file mode 100644 index 00000000..5f7c1cd4 --- /dev/null +++ b/clang-tidy/google/GlobalNamesInHeadersCheck.cpp @@ -0,0 +1,65 @@ +//===--- GlobalNamesInHeadersCheck.cpp - clang-tidy -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "GlobalNamesInHeadersCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace readability { + +void +GlobalNamesInHeadersCheck::registerMatchers(ast_matchers::MatchFinder *Finder) { + Finder->addMatcher( + decl(anyOf(usingDecl(), usingDirectiveDecl()), + hasDeclContext(translationUnitDecl())).bind("using_decl"), + this); +} + +void GlobalNamesInHeadersCheck::check(const MatchFinder::MatchResult &Result) { + const auto *D = Result.Nodes.getNodeAs("using_decl"); + // If it comes from a macro, we'll assume it is fine. + if (D->getLocStart().isMacroID()) + return; + + // Ignore if it comes from the "main" file ... + if (Result.SourceManager->isInMainFile( + Result.SourceManager->getExpansionLoc(D->getLocStart()))) { + // unless that file is a header. + StringRef Filename = Result.SourceManager->getFilename( + Result.SourceManager->getSpellingLoc(D->getLocStart())); + + if (!Filename.endswith(".h")) + return; + } + + if (const auto* UsingDirective = dyn_cast(D)) { + if (UsingDirective->getNominatedNamespace()->isAnonymousNamespace()) { + // Anynoumous namespaces inject a using directive into the AST to import + // the names into the containing namespace. + // We should not have them in headers, but there is another warning for + // that. + return; + } + } + + diag(D->getLocStart(), + "using declarations in the global namespace in headers are prohibited"); +} + +} // namespace readability +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/GlobalNamesInHeadersCheck.h b/clang-tidy/google/GlobalNamesInHeadersCheck.h new file mode 100644 index 00000000..07a380ff --- /dev/null +++ b/clang-tidy/google/GlobalNamesInHeadersCheck.h @@ -0,0 +1,35 @@ +//===--- GlobalNamesInHeadersCheck.h - clang-tidy ---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_GLOBALNAMESINHEADERSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_GLOBALNAMESINHEADERSCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace readability { + +// Flag global namespace pollution in header files. +// Right now it only triggers on using declarations and directives. +class GlobalNamesInHeadersCheck : public ClangTidyCheck { +public: + GlobalNamesInHeadersCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_GLOBALNAMESINHEADERSCHECK_H diff --git a/clang-tidy/google/GoogleTidyModule.cpp b/clang-tidy/google/GoogleTidyModule.cpp new file mode 100644 index 00000000..395de361 --- /dev/null +++ b/clang-tidy/google/GoogleTidyModule.cpp @@ -0,0 +1,96 @@ +//===--- GoogleTidyModule.cpp - clang-tidy --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "../readability/BracesAroundStatementsCheck.h" +#include "../readability/FunctionSizeCheck.h" +#include "../readability/NamespaceCommentCheck.h" +#include "../readability/RedundantSmartptrGetCheck.h" +#include "AvoidCStyleCastsCheck.h" +#include "ExplicitConstructorCheck.h" +#include "ExplicitMakePairCheck.h" +#include "GlobalNamesInHeadersCheck.h" +#include "IntegerTypesCheck.h" +#include "MemsetZeroLengthCheck.h" +#include "OverloadedUnaryAndCheck.h" +#include "StringReferenceMemberCheck.h" +#include "TodoCommentCheck.h" +#include "UnnamedNamespaceInHeaderCheck.h" +#include "UsingNamespaceDirectiveCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { + +class GoogleModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "google-build-explicit-make-pair"); + CheckFactories.registerCheck( + "google-build-namespaces"); + CheckFactories.registerCheck( + "google-build-using-namespace"); + CheckFactories.registerCheck( + "google-explicit-constructor"); + CheckFactories.registerCheck( + "google-runtime-int"); + CheckFactories.registerCheck( + "google-runtime-operator"); + CheckFactories.registerCheck( + "google-runtime-member-string-references"); + CheckFactories.registerCheck( + "google-runtime-memset"); + CheckFactories.registerCheck( + "google-readability-casting"); + CheckFactories.registerCheck( + "google-readability-todo"); + CheckFactories + .registerCheck( + "google-readability-braces-around-statements"); + CheckFactories.registerCheck( + "google-global-names-in-headers"); + CheckFactories.registerCheck( + "google-readability-function-size"); + CheckFactories + .registerCheck( + "google-readability-namespace-comments"); + CheckFactories + .registerCheck( + "google-readability-redundant-smartptr-get"); + } + + ClangTidyOptions getModuleOptions() override { + ClangTidyOptions Options; + auto &Opts = Options.CheckOptions; + Opts["google-readability-braces-around-statements.ShortStatementLines"] = + "1"; + Opts["google-readability-function-size.StatementThreshold"] = "800"; + Opts["google-readability-namespace-comments.ShortNamespaceLines"] = "10"; + Opts["google-readability-namespace-comments.SpacesBeforeComments"] = "2"; + return Options; + } +}; + +// Register the GoogleTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add X("google-module", + "Adds Google lint checks."); + +} // namespace google + +// This anchor is used to force the linker to link in the generated object file +// and thus register the GoogleModule. +volatile int GoogleModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/IntegerTypesCheck.cpp b/clang-tidy/google/IntegerTypesCheck.cpp new file mode 100644 index 00000000..0ac31240 --- /dev/null +++ b/clang-tidy/google/IntegerTypesCheck.cpp @@ -0,0 +1,114 @@ +//===--- IntegerTypesCheck.cpp - clang-tidy -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "IntegerTypesCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/CharInfo.h" +#include "clang/Basic/TargetInfo.h" + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +using namespace ast_matchers; + +IntegerTypesCheck::IntegerTypesCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + UnsignedTypePrefix(Options.get("UnsignedTypePrefix", "uint")), + SignedTypePrefix(Options.get("SignedTypePrefix", "int")), + TypeSuffix(Options.get("TypeSuffix", "")) {} + +void IntegerTypesCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "UnsignedTypePrefix", UnsignedTypePrefix); + Options.store(Opts, "SignedTypePrefix", SignedTypePrefix); + Options.store(Opts, "TypeSuffix", TypeSuffix); +} + +void IntegerTypesCheck::registerMatchers(MatchFinder *Finder) { + // Find all TypeLocs. The relevant Style Guide rule only applies to C++. + if (getLangOpts().CPlusPlus) + Finder->addMatcher(typeLoc(loc(isInteger())).bind("tl"), this); +} + +void IntegerTypesCheck::check(const MatchFinder::MatchResult &Result) { + auto TL = *Result.Nodes.getNodeAs("tl"); + SourceLocation Loc = TL.getLocStart(); + + if (Loc.isInvalid() || Loc.isMacroID()) + return; + + // Look through qualification. + if (auto QualLoc = TL.getAs()) + TL = QualLoc.getUnqualifiedLoc(); + + auto BuiltinLoc = TL.getAs(); + if (!BuiltinLoc) + return; + + bool IsSigned; + unsigned Width; + const TargetInfo &TargetInfo = Result.Context->getTargetInfo(); + + // Look for uses of short, long, long long and their unsigned versions. + switch (BuiltinLoc.getTypePtr()->getKind()) { + case BuiltinType::Short: + Width = TargetInfo.getShortWidth(); + IsSigned = true; + break; + case BuiltinType::Long: + Width = TargetInfo.getLongWidth(); + IsSigned = true; + break; + case BuiltinType::LongLong: + Width = TargetInfo.getLongLongWidth(); + IsSigned = true; + break; + case BuiltinType::UShort: + Width = TargetInfo.getShortWidth(); + IsSigned = false; + break; + case BuiltinType::ULong: + Width = TargetInfo.getLongWidth(); + IsSigned = false; + break; + case BuiltinType::ULongLong: + Width = TargetInfo.getLongLongWidth(); + IsSigned = false; + break; + default: + return; + } + + // We allow "unsigned short port" as that's reasonably common and required by + // the sockets API. + const StringRef Port = "unsigned short port"; + const char *Data = Result.SourceManager->getCharacterData(Loc); + if (!std::strncmp(Data, Port.data(), Port.size()) && + !isIdentifierBody(Data[Port.size()])) + return; + + std::string Replacement = + ((IsSigned ? SignedTypePrefix : UnsignedTypePrefix) + Twine(Width) + + TypeSuffix) + .str(); + + // We don't add a fix-it as changing the type can easily break code, + // e.g. when a function requires a 'long' argument on all platforms. + // QualTypes are printed with implicit quotes. + diag(Loc, "consider replacing %0 with '%1'") << BuiltinLoc.getType() + << Replacement; +} + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/IntegerTypesCheck.h b/clang-tidy/google/IntegerTypesCheck.h new file mode 100644 index 00000000..c7193c0e --- /dev/null +++ b/clang-tidy/google/IntegerTypesCheck.h @@ -0,0 +1,42 @@ +//===--- IntegerTypesCheck.h - clang-tidy -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_INTEGERTYPESCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_INTEGERTYPESCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +/// Finds uses of `short`, `long` and `long long` and suggest replacing them +/// with `u?intXX(_t)?`. +/// +/// Correspondig cpplint.py check: 'runtime/int'. +class IntegerTypesCheck : public ClangTidyCheck { +public: + IntegerTypesCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Options) override; + +private: + const std::string UnsignedTypePrefix; + const std::string SignedTypePrefix; + const std::string TypeSuffix; +}; + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_INTEGERTYPESCHECK_H diff --git a/clang-tidy/google/Makefile b/clang-tidy/google/Makefile new file mode 100644 index 00000000..4a39fddf --- /dev/null +++ b/clang-tidy/google/Makefile @@ -0,0 +1,12 @@ +##===- clang-tidy/google/Makefile --------------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## +CLANG_LEVEL := ../../../.. +LIBRARYNAME := clangTidyGoogleModule + +include $(CLANG_LEVEL)/Makefile diff --git a/clang-tidy/google/MemsetZeroLengthCheck.cpp b/clang-tidy/google/MemsetZeroLengthCheck.cpp new file mode 100644 index 00000000..bcf4b5b3 --- /dev/null +++ b/clang-tidy/google/MemsetZeroLengthCheck.cpp @@ -0,0 +1,95 @@ +//===--- MemsetZeroLengthCheck.cpp - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MemsetZeroLengthCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +void +MemsetZeroLengthCheck::registerMatchers(ast_matchers::MatchFinder *Finder) { + // Look for memset(x, y, 0) as those is most likely an argument swap. + // TODO: Also handle other standard functions that suffer from the same + // problem, e.g. memchr. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::memset"))), argumentCountIs(3), + unless(isInTemplateInstantiation())).bind("decl"), + this); +} + +/// \brief Get a StringRef representing a SourceRange. +static StringRef getAsString(const MatchFinder::MatchResult &Result, + SourceRange R) { + const SourceManager &SM = *Result.SourceManager; + // Don't even try to resolve macro or include contraptions. Not worth emitting + // a fixit for. + if (R.getBegin().isMacroID() || + !SM.isWrittenInSameFile(R.getBegin(), R.getEnd())) + return StringRef(); + + const char *Begin = SM.getCharacterData(R.getBegin()); + const char *End = SM.getCharacterData(Lexer::getLocForEndOfToken( + R.getEnd(), 0, SM, Result.Context->getLangOpts())); + + return StringRef(Begin, End - Begin); +} + +void MemsetZeroLengthCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("decl"); + + // Note, this is: + // void *memset(void *buffer, int fill_char, size_t byte_count); + // Arg1 is fill_char, Arg2 is byte_count. + const Expr *Arg1 = Call->getArg(1); + const Expr *Arg2 = Call->getArg(2); + + // Return if `byte_count` is not zero at compile time. + llvm::APSInt Value1, Value2; + if (Arg2->isValueDependent() || + !Arg2->EvaluateAsInt(Value2, *Result.Context) || Value2 != 0) + return; + + // Return if `fill_char` is known to be zero or negative at compile + // time. In these cases, swapping the args would be a nop, or + // introduce a definite bug. The code is likely correct. + if (!Arg1->isValueDependent() && + Arg1->EvaluateAsInt(Value1, *Result.Context) && + (Value1 == 0 || Value1.isNegative())) + return; + + // `byte_count` is known to be zero at compile time, and `fill_char` is + // either not known or known to be a positive integer. Emit a warning + // and fix-its to swap the arguments. + auto D = diag(Call->getLocStart(), + "memset of size zero, potentially swapped arguments"); + SourceRange LHSRange = Arg1->getSourceRange(); + SourceRange RHSRange = Arg2->getSourceRange(); + StringRef RHSString = getAsString(Result, RHSRange); + StringRef LHSString = getAsString(Result, LHSRange); + if (LHSString.empty() || RHSString.empty()) + return; + + D << FixItHint::CreateReplacement(CharSourceRange::getTokenRange(LHSRange), + RHSString) + << FixItHint::CreateReplacement(CharSourceRange::getTokenRange(RHSRange), + LHSString); +} + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/MemsetZeroLengthCheck.h b/clang-tidy/google/MemsetZeroLengthCheck.h new file mode 100644 index 00000000..57c7aec7 --- /dev/null +++ b/clang-tidy/google/MemsetZeroLengthCheck.h @@ -0,0 +1,39 @@ +//===--- MemsetZeroLengthCheck.h - clang-tidy ---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_MEMSETZEROLENGTHCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_MEMSETZEROLENGTHCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +/// Finds calls to memset with a literal zero in the length argument. +/// +/// This is most likely unintended and the length and value arguments are +/// swapped. +/// +/// Corresponding cpplint.py check name: 'runtime/memset'. +class MemsetZeroLengthCheck : public ClangTidyCheck { +public: + MemsetZeroLengthCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_MEMSETZEROLENGTHCHECK_H diff --git a/clang-tidy/google/OverloadedUnaryAndCheck.cpp b/clang-tidy/google/OverloadedUnaryAndCheck.cpp new file mode 100644 index 00000000..6fb1ca9d --- /dev/null +++ b/clang-tidy/google/OverloadedUnaryAndCheck.cpp @@ -0,0 +1,53 @@ +//===--- OverloadedUnaryAndCheck.cpp - clang-tidy ---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "OverloadedUnaryAndCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +void +OverloadedUnaryAndCheck::registerMatchers(ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + // Match unary methods that overload operator&. + Finder->addMatcher( + cxxMethodDecl(parameterCountIs(0), hasOverloadedOperatorName("&")) + .bind("overload"), + this); + // Also match freestanding unary operator& overloads. Be careful not to match + // binary methods. + Finder->addMatcher( + functionDecl( + allOf(unless(cxxMethodDecl()), + functionDecl(parameterCountIs(1), + hasOverloadedOperatorName("&")).bind("overload"))), + this); +} + +void OverloadedUnaryAndCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Decl = Result.Nodes.getNodeAs("overload"); + diag(Decl->getLocStart(), + "do not overload unary operator&, it is dangerous."); +} + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/OverloadedUnaryAndCheck.h b/clang-tidy/google/OverloadedUnaryAndCheck.h new file mode 100644 index 00000000..894fc719 --- /dev/null +++ b/clang-tidy/google/OverloadedUnaryAndCheck.h @@ -0,0 +1,38 @@ +//===--- OverloadedUnaryAndCheck.h - clang-tidy -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OVERLOADEDUNARYANDCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OVERLOADEDUNARYANDCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +/// Finds overloads of unary `operator &`. +/// +/// http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Operator_Overloading#Operator_Overloading +/// +/// Corresponding cpplint.py check name: 'runtime/operator'. +class OverloadedUnaryAndCheck : public ClangTidyCheck { +public: + OverloadedUnaryAndCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OVERLOADEDUNARYANDCHECK_H diff --git a/clang-tidy/google/StringReferenceMemberCheck.cpp b/clang-tidy/google/StringReferenceMemberCheck.cpp new file mode 100644 index 00000000..c6324573 --- /dev/null +++ b/clang-tidy/google/StringReferenceMemberCheck.cpp @@ -0,0 +1,51 @@ +//===--- StringReferenceMemberCheck.cpp - clang-tidy ------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StringReferenceMemberCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +void StringReferenceMemberCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + // Look for const references to std::string or ::string. + auto String = anyOf(recordDecl(hasName("::std::basic_string")), + recordDecl(hasName("::string"))); + auto ConstString = qualType(isConstQualified(), hasDeclaration(String)); + + // Ignore members in template instantiations. + Finder->addMatcher(fieldDecl(hasType(references(ConstString)), + unless(isInstantiated())).bind("member"), + this); +} + +void +StringReferenceMemberCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Member = Result.Nodes.getNodeAs("member"); + diag(Member->getLocStart(), "const string& members are dangerous; it is much " + "better to use alternatives, such as pointers or " + "simple constants"); +} + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/StringReferenceMemberCheck.h b/clang-tidy/google/StringReferenceMemberCheck.h new file mode 100644 index 00000000..5a39fd9b --- /dev/null +++ b/clang-tidy/google/StringReferenceMemberCheck.h @@ -0,0 +1,54 @@ +//===--- StringReferenceMemberCheck.h - clang-tidy ----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_STRINGREFERENCEMEMBERCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_STRINGREFERENCEMEMBERCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +/// Finds members of type `const string&`. +/// +/// const string reference members are generally considered unsafe as they can +/// be created from a temporary quite easily. +/// +/// \code +/// struct S { +/// S(const string &Str) : Str(Str) {} +/// const string &Str; +/// }; +/// S instance("string"); +/// \endcode +/// +/// In the constructor call a string temporary is created from `const char *` +/// and destroyed immediately after the call. This leaves around a dangling +/// reference. +/// +/// This check emit warnings for both `std::string` and `::string` const +/// reference members. +/// +/// Corresponding cpplint.py check name: 'runtime/member_string_reference'. +class StringReferenceMemberCheck : public ClangTidyCheck { +public: + StringReferenceMemberCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_STRINGREFERENCEMEMBERCHECK_H diff --git a/clang-tidy/google/TodoCommentCheck.cpp b/clang-tidy/google/TodoCommentCheck.cpp new file mode 100644 index 00000000..f1c79ce6 --- /dev/null +++ b/clang-tidy/google/TodoCommentCheck.cpp @@ -0,0 +1,66 @@ +//===--- TodoCommentCheck.cpp - clang-tidy --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TodoCommentCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Preprocessor.h" + +namespace clang { +namespace tidy { +namespace google { +namespace readability { + +class TodoCommentCheck::TodoCommentHandler : public CommentHandler { +public: + TodoCommentHandler(TodoCommentCheck &Check, llvm::Optional User) + : Check(Check), User(User ? *User : "unknown"), + TodoMatch("^// *TODO *(\\(.*\\))?:?( )?(.*)$") {} + + bool HandleComment(Preprocessor &PP, SourceRange Range) override { + StringRef Text = + Lexer::getSourceText(CharSourceRange::getCharRange(Range), + PP.getSourceManager(), PP.getLangOpts()); + + SmallVector Matches; + if (!TodoMatch.match(Text, &Matches)) + return false; + + StringRef Username = Matches[1]; + StringRef Comment = Matches[3]; + + if (!Username.empty()) + return false; + + std::string NewText = ("// TODO(" + Twine(User) + "): " + Comment).str(); + + Check.diag(Range.getBegin(), "missing username/bug in TODO") + << FixItHint::CreateReplacement(CharSourceRange::getCharRange(Range), + NewText); + return false; + } + +private: + TodoCommentCheck &Check; + std::string User; + llvm::Regex TodoMatch; +}; + +TodoCommentCheck::TodoCommentCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + Handler(llvm::make_unique( + *this, Context->getOptions().User)) {} + +void TodoCommentCheck::registerPPCallbacks(CompilerInstance &Compiler) { + Compiler.getPreprocessor().addCommentHandler(Handler.get()); +} + +} // namespace readability +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/TodoCommentCheck.h b/clang-tidy/google/TodoCommentCheck.h new file mode 100644 index 00000000..dbdc3668 --- /dev/null +++ b/clang-tidy/google/TodoCommentCheck.h @@ -0,0 +1,38 @@ +//===--- TodoCommentCheck.h - clang-tidy ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_TODOCOMMENTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_TODOCOMMENTCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace readability { + +/// Finds TODO comments without a username or bug number. +/// +/// Corresponding cpplint.py check: 'readability/todo' +class TodoCommentCheck : public ClangTidyCheck { +public: + TodoCommentCheck(StringRef Name, ClangTidyContext *Context); + void registerPPCallbacks(CompilerInstance &Compiler) override; + +private: + class TodoCommentHandler; + std::unique_ptr Handler; +}; + +} // namespace readability +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_TODOCOMMENTCHECK_H diff --git a/clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp b/clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp new file mode 100644 index 00000000..7ce245e0 --- /dev/null +++ b/clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp @@ -0,0 +1,50 @@ +//===--- UnnamedNamespaceInHeaderCheck.cpp - clang-tidy ---------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnnamedNamespaceInHeaderCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace build { + +void UnnamedNamespaceInHeaderCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (getLangOpts().CPlusPlus) + Finder->addMatcher(namespaceDecl(isAnonymous()).bind("anonymousNamespace"), + this); +} + +void +UnnamedNamespaceInHeaderCheck::check(const MatchFinder::MatchResult &Result) { + SourceManager *SM = Result.SourceManager; + const auto *N = Result.Nodes.getNodeAs("anonymousNamespace"); + SourceLocation Loc = N->getLocStart(); + if (!Loc.isValid()) + return; + + // Look if we're inside a header, check for common suffixes only. + // TODO: Allow configuring the set of file extensions. + StringRef FileName = SM->getPresumedLoc(Loc).getFilename(); + if (FileName.endswith(".h") || FileName.endswith(".hh") || + FileName.endswith(".hpp") || FileName.endswith(".hxx")) + diag(Loc, "do not use unnamed namespaces in header files"); +} + +} // namespace build +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/UnnamedNamespaceInHeaderCheck.h b/clang-tidy/google/UnnamedNamespaceInHeaderCheck.h new file mode 100644 index 00000000..1d249431 --- /dev/null +++ b/clang-tidy/google/UnnamedNamespaceInHeaderCheck.h @@ -0,0 +1,38 @@ +//===--- UnnamedNamespaceInHeaderCheck.h - clang-tidy -----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_UNNAMEDNAMESPACEINHEADERCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_UNNAMEDNAMESPACEINHEADERCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace build { + +/// Finds anonymous namespaces in headers. +/// +/// http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Namespaces#Namespaces +/// +/// Corresponding cpplint.py check name: 'build/namespaces'. +class UnnamedNamespaceInHeaderCheck : public ClangTidyCheck { +public: + UnnamedNamespaceInHeaderCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace build +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_UNNAMEDNAMESPACEINHEADERCHECK_H diff --git a/clang-tidy/google/UsingNamespaceDirectiveCheck.cpp b/clang-tidy/google/UsingNamespaceDirectiveCheck.cpp new file mode 100644 index 00000000..c4ac5a4b --- /dev/null +++ b/clang-tidy/google/UsingNamespaceDirectiveCheck.cpp @@ -0,0 +1,46 @@ +//===--- UsingNamespaceDirectiveCheck.cpp - clang-tidy ----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UsingNamespaceDirectiveCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace build { + +void UsingNamespaceDirectiveCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (getLangOpts().CPlusPlus) + Finder->addMatcher(usingDirectiveDecl().bind("usingNamespace"), this); +} + +void +UsingNamespaceDirectiveCheck::check(const MatchFinder::MatchResult &Result) { + const auto *U = Result.Nodes.getNodeAs("usingNamespace"); + SourceLocation Loc = U->getLocStart(); + if (U->isImplicit() || !Loc.isValid()) + return; + + diag(Loc, "do not use namespace using-directives; " + "use using-declarations instead"); + // TODO: We could suggest a list of using directives replacing the using + // namespace directive. +} + +} // namespace build +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/UsingNamespaceDirectiveCheck.h b/clang-tidy/google/UsingNamespaceDirectiveCheck.h new file mode 100644 index 00000000..b88a2d3c --- /dev/null +++ b/clang-tidy/google/UsingNamespaceDirectiveCheck.h @@ -0,0 +1,48 @@ +//===--- UsingNamespaceDirectiveCheck.h - clang-tidy ------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_USINGNAMESPACEDIRECTIVECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_USINGNAMESPACEDIRECTIVECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace build { + +/// Finds using namespace directives. +/// +/// http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Namespaces#Namespaces +/// +/// The check implements the following rule of the Google C++ Style Guide: +/// +/// You may not use a using-directive to make all names from a namespace +/// available. +/// +/// \code +/// // Forbidden -- This pollutes the namespace. +/// using namespace foo; +/// \endcode +/// +/// Corresponding cpplint.py check name: `build/namespaces`. +class UsingNamespaceDirectiveCheck : public ClangTidyCheck { +public: + UsingNamespaceDirectiveCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace build +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_USINGNAMESPACEDIRECTIVECHECK_H diff --git a/clang-tidy/llvm/CMakeLists.txt b/clang-tidy/llvm/CMakeLists.txt new file mode 100644 index 00000000..ce69c05f --- /dev/null +++ b/clang-tidy/llvm/CMakeLists.txt @@ -0,0 +1,18 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyLLVMModule + HeaderGuardCheck.cpp + IncludeOrderCheck.cpp + LLVMTidyModule.cpp + TwineLocalCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyReadabilityModule + clangTidyUtils + clangTooling + ) diff --git a/clang-tidy/llvm/HeaderGuardCheck.cpp b/clang-tidy/llvm/HeaderGuardCheck.cpp new file mode 100644 index 00000000..c44abbd9 --- /dev/null +++ b/clang-tidy/llvm/HeaderGuardCheck.cpp @@ -0,0 +1,55 @@ +//===--- HeaderGuardCheck.cpp - clang-tidy --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "HeaderGuardCheck.h" + +namespace clang { +namespace tidy { +namespace llvm { + +bool LLVMHeaderGuardCheck::shouldFixHeaderGuard(StringRef Filename) { + return Filename.endswith(".h"); +} + +std::string LLVMHeaderGuardCheck::getHeaderGuard(StringRef Filename, + StringRef OldGuard) { + std::string Guard = tooling::getAbsolutePath(Filename); + + // Sanitize the path. There are some rules for compatibility with the historic + // style in include/llvm and include/clang which we want to preserve. + + // We don't want _INCLUDE_ in our guards. + size_t PosInclude = Guard.rfind("include/"); + if (PosInclude != StringRef::npos) + Guard = Guard.substr(PosInclude + std::strlen("include/")); + + // For clang we drop the _TOOLS_. + size_t PosToolsClang = Guard.rfind("tools/clang/"); + if (PosToolsClang != StringRef::npos) + Guard = Guard.substr(PosToolsClang + std::strlen("tools/")); + + // The remainder is LLVM_FULL_PATH_TO_HEADER_H + size_t PosLLVM = Guard.rfind("llvm/"); + if (PosLLVM != StringRef::npos) + Guard = Guard.substr(PosLLVM); + + std::replace(Guard.begin(), Guard.end(), '/', '_'); + std::replace(Guard.begin(), Guard.end(), '.', '_'); + std::replace(Guard.begin(), Guard.end(), '-', '_'); + + // The prevalent style in clang is LLVM_CLANG_FOO_BAR_H + if (StringRef(Guard).startswith("clang")) + Guard = "LLVM_" + Guard; + + return StringRef(Guard).upper(); +} + +} // namespace llvm +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/llvm/HeaderGuardCheck.h b/clang-tidy/llvm/HeaderGuardCheck.h new file mode 100644 index 00000000..f5c509a8 --- /dev/null +++ b/clang-tidy/llvm/HeaderGuardCheck.h @@ -0,0 +1,33 @@ +//===--- HeaderGuardCheck.h - clang-tidy ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADER_GUARD_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADER_GUARD_CHECK_H + +#include "../utils/HeaderGuard.h" + +namespace clang { +namespace tidy { +namespace llvm { + +/// Finds and fixes header guards that do not adhere to LLVM style. +class LLVMHeaderGuardCheck : public HeaderGuardCheck { +public: + LLVMHeaderGuardCheck(StringRef Name, ClangTidyContext *Context) + : HeaderGuardCheck(Name, Context) {} + bool shouldSuggestEndifComment(StringRef Filename) override { return false; } + bool shouldFixHeaderGuard(StringRef Filename) override; + std::string getHeaderGuard(StringRef Filename, StringRef OldGuard) override; +}; + +} // namespace llvm +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADER_GUARD_CHECK_H diff --git a/clang-tidy/llvm/IncludeOrderCheck.cpp b/clang-tidy/llvm/IncludeOrderCheck.cpp new file mode 100644 index 00000000..e6d5bc3f --- /dev/null +++ b/clang-tidy/llvm/IncludeOrderCheck.cpp @@ -0,0 +1,169 @@ +//===--- IncludeOrderCheck.cpp - clang-tidy -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "IncludeOrderCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" + +namespace clang { +namespace tidy { +namespace llvm { + +namespace { +class IncludeOrderPPCallbacks : public PPCallbacks { +public: + explicit IncludeOrderPPCallbacks(ClangTidyCheck &Check, SourceManager &SM) + : LookForMainModule(true), Check(Check), SM(SM) {} + + void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, + StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, + const Module *Imported) override; + void EndOfMainFile() override; + +private: + struct IncludeDirective { + SourceLocation Loc; ///< '#' location in the include directive + CharSourceRange Range; ///< SourceRange for the file name + std::string Filename; ///< Filename as a string + bool IsAngled; ///< true if this was an include with angle brackets + bool IsMainModule; ///< true if this was the first include in a file + }; + std::vector IncludeDirectives; + bool LookForMainModule; + + ClangTidyCheck &Check; + SourceManager &SM; +}; +} // namespace + +void IncludeOrderCheck::registerPPCallbacks(CompilerInstance &Compiler) { + Compiler.getPreprocessor().addPPCallbacks( + ::llvm::make_unique( + *this, Compiler.getSourceManager())); +} + +static int getPriority(StringRef Filename, bool IsAngled, bool IsMainModule) { + // We leave the main module header at the top. + if (IsMainModule) + return 0; + + // LLVM and clang headers are in the penultimate position. + if (Filename.startswith("llvm/") || Filename.startswith("llvm-c/") || + Filename.startswith("clang/") || Filename.startswith("clang-c/")) + return 2; + + // System headers are sorted to the end. + if (IsAngled || Filename.startswith("gtest/")) + return 3; + + // Other headers are inserted between the main module header and LLVM headers. + return 1; +} + +void IncludeOrderPPCallbacks::InclusionDirective( + SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName, + bool IsAngled, CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, const Module *Imported) { + // We recognize the first include as a special main module header and want + // to leave it in the top position. + IncludeDirective ID = {HashLoc, FilenameRange, FileName, IsAngled, false}; + if (LookForMainModule && !IsAngled) { + ID.IsMainModule = true; + LookForMainModule = false; + } + IncludeDirectives.push_back(std::move(ID)); +} + +void IncludeOrderPPCallbacks::EndOfMainFile() { + LookForMainModule = true; + if (IncludeDirectives.empty()) + return; + + // TODO: find duplicated includes. + + // Form blocks of includes. We don't want to sort across blocks. This also + // implicitly makes us never reorder over #defines or #if directives. + // FIXME: We should be more careful about sorting below comments as we don't + // know if the comment refers to the next include or the whole block that + // follows. + std::vector Blocks(1, 0); + for (unsigned I = 1, E = IncludeDirectives.size(); I != E; ++I) + if (SM.getExpansionLineNumber(IncludeDirectives[I].Loc) != + SM.getExpansionLineNumber(IncludeDirectives[I - 1].Loc) + 1) + Blocks.push_back(I); + Blocks.push_back(IncludeDirectives.size()); // Sentinel value. + + // Get a vector of indices. + std::vector IncludeIndices; + for (unsigned I = 0, E = IncludeDirectives.size(); I != E; ++I) + IncludeIndices.push_back(I); + + // Sort the includes. We first sort by priority, then lexicographically. + for (unsigned BI = 0, BE = Blocks.size() - 1; BI != BE; ++BI) + std::sort(IncludeIndices.begin() + Blocks[BI], + IncludeIndices.begin() + Blocks[BI + 1], + [this](unsigned LHSI, unsigned RHSI) { + IncludeDirective &LHS = IncludeDirectives[LHSI]; + IncludeDirective &RHS = IncludeDirectives[RHSI]; + + int PriorityLHS = + getPriority(LHS.Filename, LHS.IsAngled, LHS.IsMainModule); + int PriorityRHS = + getPriority(RHS.Filename, RHS.IsAngled, RHS.IsMainModule); + + return std::tie(PriorityLHS, LHS.Filename) < + std::tie(PriorityRHS, RHS.Filename); + }); + + // Emit a warning for each block and fixits for all changes within that block. + for (unsigned BI = 0, BE = Blocks.size() - 1; BI != BE; ++BI) { + // Find the first include that's not in the right position. + unsigned I, E; + for (I = Blocks[BI], E = Blocks[BI + 1]; I != E; ++I) + if (IncludeIndices[I] != I) + break; + + if (I == E) + continue; + + // Emit a warning. + auto D = Check.diag(IncludeDirectives[I].Loc, + "#includes are not sorted properly"); + + // Emit fix-its for all following includes in this block. + for (; I != E; ++I) { + if (IncludeIndices[I] == I) + continue; + const IncludeDirective &CopyFrom = IncludeDirectives[IncludeIndices[I]]; + + SourceLocation FromLoc = CopyFrom.Range.getBegin(); + const char *FromData = SM.getCharacterData(FromLoc); + unsigned FromLen = std::strcspn(FromData, "\n"); + + StringRef FixedName(FromData, FromLen); + + SourceLocation ToLoc = IncludeDirectives[I].Range.getBegin(); + const char *ToData = SM.getCharacterData(ToLoc); + unsigned ToLen = std::strcspn(ToData, "\n"); + auto ToRange = + CharSourceRange::getCharRange(ToLoc, ToLoc.getLocWithOffset(ToLen)); + + D << FixItHint::CreateReplacement(ToRange, FixedName); + } + } + + IncludeDirectives.clear(); +} + +} // namespace llvm +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/llvm/IncludeOrderCheck.h b/clang-tidy/llvm/IncludeOrderCheck.h new file mode 100644 index 00000000..ad876b95 --- /dev/null +++ b/clang-tidy/llvm/IncludeOrderCheck.h @@ -0,0 +1,33 @@ +//===--- IncludeOrderCheck.h - clang-tidy -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_INCLUDE_ORDER_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_INCLUDE_ORDER_CHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace llvm { + +/// Checks the correct order of `#includes`. +/// +/// See http://llvm.org/docs/CodingStandards.html#include-style +class IncludeOrderCheck : public ClangTidyCheck { +public: + IncludeOrderCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerPPCallbacks(CompilerInstance &Compiler) override; +}; + +} // namespace llvm +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_INCLUDE_ORDER_CHECK_H diff --git a/clang-tidy/llvm/LLVMTidyModule.cpp b/clang-tidy/llvm/LLVMTidyModule.cpp new file mode 100644 index 00000000..ea46ca93 --- /dev/null +++ b/clang-tidy/llvm/LLVMTidyModule.cpp @@ -0,0 +1,44 @@ +//===--- LLVMTidyModule.cpp - clang-tidy ----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "../readability/NamespaceCommentCheck.h" +#include "HeaderGuardCheck.h" +#include "IncludeOrderCheck.h" +#include "TwineLocalCheck.h" + +namespace clang { +namespace tidy { +namespace llvm { + +class LLVMModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("llvm-header-guard"); + CheckFactories.registerCheck("llvm-include-order"); + CheckFactories.registerCheck( + "llvm-namespace-comment"); + CheckFactories.registerCheck("llvm-twine-local"); + } +}; + +// Register the LLVMTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add X("llvm-module", + "Adds LLVM lint checks."); + +} // namespace llvm + +// This anchor is used to force the linker to link in the generated object file +// and thus register the LLVMModule. +volatile int LLVMModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/llvm/Makefile b/clang-tidy/llvm/Makefile new file mode 100644 index 00000000..15ece2e2 --- /dev/null +++ b/clang-tidy/llvm/Makefile @@ -0,0 +1,12 @@ +##===- clang-tidy/llvm/Makefile ----------------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## +CLANG_LEVEL := ../../../.. +LIBRARYNAME := clangTidyLLVMModule + +include $(CLANG_LEVEL)/Makefile diff --git a/clang-tidy/llvm/TwineLocalCheck.cpp b/clang-tidy/llvm/TwineLocalCheck.cpp new file mode 100644 index 00000000..0d9c5ecd --- /dev/null +++ b/clang-tidy/llvm/TwineLocalCheck.cpp @@ -0,0 +1,63 @@ +//===--- TwineLocalCheck.cpp - clang-tidy ---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TwineLocalCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace llvm { + +void TwineLocalCheck::registerMatchers(MatchFinder *Finder) { + auto TwineType = + qualType(hasDeclaration(recordDecl(hasName("::llvm::Twine")))); + Finder->addMatcher(varDecl(hasType(TwineType)).bind("variable"), this); +} + +void TwineLocalCheck::check(const MatchFinder::MatchResult &Result) { + const VarDecl *VD = Result.Nodes.getNodeAs("variable"); + auto Diag = diag(VD->getLocation(), + "twine variables are prone to use-after-free bugs"); + + // If this VarDecl has an initializer try to fix it. + if (VD->hasInit()) { + // Peel away implicit constructors and casts so we can see the actual type + // of the initializer. + const Expr *C = VD->getInit(); + while (isa(C)) + C = cast(C)->getArg(0)->IgnoreParenImpCasts(); + + SourceRange TypeRange = + VD->getTypeSourceInfo()->getTypeLoc().getSourceRange(); + + // A real Twine, turn it into a std::string. + if (VD->getType()->getCanonicalTypeUnqualified() == + C->getType()->getCanonicalTypeUnqualified()) { + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + VD->getInit()->getLocEnd(), 0, *Result.SourceManager, + Result.Context->getLangOpts()); + Diag << FixItHint::CreateReplacement(TypeRange, "std::string") + << FixItHint::CreateInsertion(VD->getInit()->getLocStart(), "(") + << FixItHint::CreateInsertion(EndLoc, ").str()"); + } else { + // Just an implicit conversion. Insert the real type. + Diag << FixItHint::CreateReplacement( + TypeRange, + C->getType().getAsString(Result.Context->getPrintingPolicy())); + } + } +} + +} // namespace llvm +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/llvm/TwineLocalCheck.h b/clang-tidy/llvm/TwineLocalCheck.h new file mode 100644 index 00000000..9f7979b4 --- /dev/null +++ b/clang-tidy/llvm/TwineLocalCheck.h @@ -0,0 +1,33 @@ +//===--- TwineLocalCheck.h - clang-tidy -------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_TWINE_LOCAL_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_TWINE_LOCAL_CHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace llvm { + +/// Looks for local `Twine` variables which are prone to use after frees and +/// should be generally avoided. +class TwineLocalCheck : public ClangTidyCheck { +public: + TwineLocalCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace llvm +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_TWINE_LOCAL_CHECK_H diff --git a/clang-tidy/misc/ArgumentCommentCheck.cpp b/clang-tidy/misc/ArgumentCommentCheck.cpp new file mode 100644 index 00000000..a69aaecc --- /dev/null +++ b/clang-tidy/misc/ArgumentCommentCheck.cpp @@ -0,0 +1,187 @@ +//===--- ArgumentCommentCheck.cpp - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ArgumentCommentCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Token.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +ArgumentCommentCheck::ArgumentCommentCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IdentRE("^(/\\* *)([_A-Za-z][_A-Za-z0-9]*)( *= *\\*/)$") {} + +void ArgumentCommentCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + callExpr(unless(cxxOperatorCallExpr()), + // NewCallback's arguments relate to the pointed function, don't + // check them against NewCallback's parameter names. + // FIXME: Make this configurable. + unless(hasDeclaration(functionDecl(anyOf( + hasName("NewCallback"), hasName("NewPermanentCallback")))))) + .bind("expr"), + this); + Finder->addMatcher(cxxConstructExpr().bind("expr"), this); +} + +std::vector> +ArgumentCommentCheck::getCommentsInRange(ASTContext *Ctx, SourceRange Range) { + std::vector> Comments; + auto &SM = Ctx->getSourceManager(); + std::pair BeginLoc = SM.getDecomposedLoc(Range.getBegin()), + EndLoc = SM.getDecomposedLoc(Range.getEnd()); + + if (BeginLoc.first != EndLoc.first) + return Comments; + + bool Invalid = false; + StringRef Buffer = SM.getBufferData(BeginLoc.first, &Invalid); + if (Invalid) + return Comments; + + const char *StrData = Buffer.data() + BeginLoc.second; + + Lexer TheLexer(SM.getLocForStartOfFile(BeginLoc.first), Ctx->getLangOpts(), + Buffer.begin(), StrData, Buffer.end()); + TheLexer.SetCommentRetentionState(true); + + while (true) { + Token Tok; + if (TheLexer.LexFromRawLexer(Tok)) + break; + if (Tok.getLocation() == Range.getEnd() || Tok.getKind() == tok::eof) + break; + + if (Tok.getKind() == tok::comment) { + std::pair CommentLoc = + SM.getDecomposedLoc(Tok.getLocation()); + assert(CommentLoc.first == BeginLoc.first); + Comments.emplace_back( + Tok.getLocation(), + StringRef(Buffer.begin() + CommentLoc.second, Tok.getLength())); + } + } + + return Comments; +} + +bool +ArgumentCommentCheck::isLikelyTypo(llvm::ArrayRef Params, + StringRef ArgName, unsigned ArgIndex) { + std::string ArgNameLower = ArgName.lower(); + unsigned UpperBound = (ArgName.size() + 2) / 3 + 1; + unsigned ThisED = StringRef(ArgNameLower).edit_distance( + Params[ArgIndex]->getIdentifier()->getName().lower(), + /*AllowReplacements=*/true, UpperBound); + if (ThisED >= UpperBound) + return false; + + for (unsigned I = 0, E = Params.size(); I != E; ++I) { + if (I == ArgIndex) + continue; + IdentifierInfo *II = Params[I]->getIdentifier(); + if (!II) + continue; + + const unsigned Threshold = 2; + // Other parameters must be an edit distance at least Threshold more away + // from this parameter. This gives us greater confidence that this is a typo + // of this parameter and not one with a similar name. + unsigned OtherED = StringRef(ArgNameLower).edit_distance( + II->getName().lower(), + /*AllowReplacements=*/true, ThisED + Threshold); + if (OtherED < ThisED + Threshold) + return false; + } + + return true; +} + +void ArgumentCommentCheck::checkCallArgs(ASTContext *Ctx, + const FunctionDecl *Callee, + SourceLocation ArgBeginLoc, + llvm::ArrayRef Args) { + Callee = Callee->getFirstDecl(); + for (unsigned i = 0, + e = std::min(Args.size(), Callee->getNumParams()); + i != e; ++i) { + const ParmVarDecl *PVD = Callee->getParamDecl(i); + IdentifierInfo *II = PVD->getIdentifier(); + if (!II) + continue; + if (auto Template = Callee->getTemplateInstantiationPattern()) { + // Don't warn on arguments for parameters instantiated from template + // parameter packs. If we find more arguments than the template definition + // has, it also means that they correspond to a parameter pack. + if (Template->getNumParams() <= i || + Template->getParamDecl(i)->isParameterPack()) { + continue; + } + } + + SourceLocation BeginSLoc, EndSLoc = Args[i]->getLocStart(); + if (i == 0) + BeginSLoc = ArgBeginLoc; + else + BeginSLoc = Args[i - 1]->getLocEnd(); + if (BeginSLoc.isMacroID() || EndSLoc.isMacroID()) + continue; + + for (auto Comment : + getCommentsInRange(Ctx, SourceRange(BeginSLoc, EndSLoc))) { + llvm::SmallVector Matches; + if (IdentRE.match(Comment.second, &Matches)) { + if (Matches[2] != II->getName()) { + { + DiagnosticBuilder Diag = + diag(Comment.first, "argument name '%0' in comment does not " + "match parameter name %1") + << Matches[2] << II; + if (isLikelyTypo(Callee->parameters(), Matches[2], i)) { + Diag << FixItHint::CreateReplacement( + Comment.first, + (Matches[1] + II->getName() + Matches[3]).str()); + } + } + diag(PVD->getLocation(), "%0 declared here", DiagnosticIDs::Note) + << II; + } + } + } + } +} + +void ArgumentCommentCheck::check(const MatchFinder::MatchResult &Result) { + const Expr *E = Result.Nodes.getNodeAs("expr"); + if (auto Call = dyn_cast(E)) { + const FunctionDecl *Callee = Call->getDirectCallee(); + if (!Callee) + return; + + checkCallArgs(Result.Context, Callee, Call->getCallee()->getLocEnd(), + llvm::makeArrayRef(Call->getArgs(), Call->getNumArgs())); + } else { + auto Construct = cast(E); + checkCallArgs( + Result.Context, Construct->getConstructor(), + Construct->getParenOrBraceRange().getBegin(), + llvm::makeArrayRef(Construct->getArgs(), Construct->getNumArgs())); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/ArgumentCommentCheck.h b/clang-tidy/misc/ArgumentCommentCheck.h new file mode 100644 index 00000000..f37e408d --- /dev/null +++ b/clang-tidy/misc/ArgumentCommentCheck.h @@ -0,0 +1,57 @@ +//===--- ArgumentCommentCheck.h - clang-tidy --------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ARGUMENTCOMMENTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ARGUMENTCOMMENTCHECK_H + +#include "../ClangTidy.h" +#include "llvm/Support/Regex.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Checks that argument comments match parameter names. +/// +/// The check understands argument comments in the form `/*parameter_name=*/` +/// that are placed right before the argument. +/// +/// \code +/// void f(bool foo); +/// +/// ... +/// f(/*bar=*/true); +/// // warning: argument name 'bar' in comment does not match parameter name 'foo' +/// \endcode +/// +/// The check tries to detect typos and suggest automated fixes for them. +class ArgumentCommentCheck : public ClangTidyCheck { +public: + ArgumentCommentCheck(StringRef Name, ClangTidyContext *Context); + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + llvm::Regex IdentRE; + + bool isLikelyTypo(llvm::ArrayRef Params, StringRef ArgName, + unsigned ArgIndex); + std::vector> + getCommentsInRange(ASTContext *Ctx, SourceRange Range); + void checkCallArgs(ASTContext *Ctx, const FunctionDecl *Callee, + SourceLocation ArgBeginLoc, + llvm::ArrayRef Args); +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ARGUMENTCOMMENTCHECK_H diff --git a/clang-tidy/misc/AssertSideEffectCheck.cpp b/clang-tidy/misc/AssertSideEffectCheck.cpp new file mode 100644 index 00000000..6b93d9c9 --- /dev/null +++ b/clang-tidy/misc/AssertSideEffectCheck.cpp @@ -0,0 +1,118 @@ +//===--- AssertSideEffectCheck.cpp - clang-tidy ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "AssertSideEffectCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace { + +AST_MATCHER_P(Expr, hasSideEffect, bool, CheckFunctionCalls) { + const Expr *E = &Node; + + if (const auto *Op = dyn_cast(E)) { + UnaryOperator::Opcode OC = Op->getOpcode(); + return OC == UO_PostInc || OC == UO_PostDec || OC == UO_PreInc || + OC == UO_PreDec; + } + + if (const auto *Op = dyn_cast(E)) { + return Op->isAssignmentOp(); + } + + if (const auto *OpCallExpr = dyn_cast(E)) { + OverloadedOperatorKind OpKind = OpCallExpr->getOperator(); + return OpKind == OO_Equal || OpKind == OO_PlusEqual || + OpKind == OO_MinusEqual || OpKind == OO_StarEqual || + OpKind == OO_SlashEqual || OpKind == OO_AmpEqual || + OpKind == OO_PipeEqual || OpKind == OO_CaretEqual || + OpKind == OO_LessLessEqual || OpKind == OO_GreaterGreaterEqual || + OpKind == OO_PlusPlus || OpKind == OO_MinusMinus || + OpKind == OO_PercentEqual || OpKind == OO_New || + OpKind == OO_Delete || OpKind == OO_Array_New || + OpKind == OO_Array_Delete; + } + + if (const auto *CExpr = dyn_cast(E)) { + bool Result = CheckFunctionCalls; + if (const auto *FuncDecl = CExpr->getDirectCallee()) { + if (FuncDecl->getDeclName().isIdentifier() && + FuncDecl->getName() == "__builtin_expect") // exceptions come here + Result = false; + else if (const auto *MethodDecl = dyn_cast(FuncDecl)) + Result &= !MethodDecl->isConst(); + } + return Result; + } + + return isa(E) || isa(E) || isa(E); +} + +} // namespace + +namespace tidy { + +AssertSideEffectCheck::AssertSideEffectCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + CheckFunctionCalls(Options.get("CheckFunctionCalls", false)), + RawAssertList(Options.get("AssertMacros", "assert")) { + StringRef(RawAssertList).split(AssertMacros, ",", -1, false); +} + +// The options are explained in AssertSideEffectCheck.h. +void AssertSideEffectCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "CheckFunctionCalls", CheckFunctionCalls); + Options.store(Opts, "AssertMacros", RawAssertList); +} + +void AssertSideEffectCheck::registerMatchers(MatchFinder *Finder) { + auto ConditionWithSideEffect = + hasCondition(hasDescendant(expr(hasSideEffect(CheckFunctionCalls)))); + Finder->addMatcher( + stmt(anyOf(conditionalOperator(ConditionWithSideEffect), + ifStmt(ConditionWithSideEffect))).bind("condStmt"), + this); +} + +void AssertSideEffectCheck::check(const MatchFinder::MatchResult &Result) { + const SourceManager &SM = *Result.SourceManager; + const LangOptions LangOpts = Result.Context->getLangOpts(); + SourceLocation Loc = Result.Nodes.getNodeAs("condStmt")->getLocStart(); + + StringRef AssertMacroName; + while (Loc.isValid() && Loc.isMacroID()) { + StringRef MacroName = Lexer::getImmediateMacroName(Loc, SM, LangOpts); + + // Check if this macro is an assert. + if (std::find(AssertMacros.begin(), AssertMacros.end(), MacroName) != + AssertMacros.end()) { + AssertMacroName = MacroName; + break; + } + Loc = SM.getImmediateMacroCallerLoc(Loc); + } + if (AssertMacroName.empty()) + return; + + diag(Loc, "found %0() with side effect") << AssertMacroName; +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/AssertSideEffectCheck.h b/clang-tidy/misc/AssertSideEffectCheck.h new file mode 100644 index 00000000..1ca278dc --- /dev/null +++ b/clang-tidy/misc/AssertSideEffectCheck.h @@ -0,0 +1,50 @@ +//===--- AssertSideEffectCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSERTSIDEEFFECTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSERTSIDEEFFECTCHECK_H + +#include "../ClangTidy.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace tidy { + +/// Finds `assert()` with side effect. +/// +/// The condition of `assert()` is evaluated only in debug builds so a +/// condition with side effect can cause different behavior in debug / release +/// builds. +/// +/// There are two options: +/// +/// - `AssertMacros`: A comma-separated list of the names of assert macros to +/// be checked. +/// - `CheckFunctionCalls`: Whether to treat non-const member and non-member +/// functions as they produce side effects. Disabled by default because it +/// can increase the number of false positive warnings. +class AssertSideEffectCheck : public ClangTidyCheck { +public: + AssertSideEffectCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool CheckFunctionCalls; + const std::string RawAssertList; + SmallVector AssertMacros; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSERTSIDEEFFECTCHECK_H diff --git a/clang-tidy/misc/AssignOperatorSignatureCheck.cpp b/clang-tidy/misc/AssignOperatorSignatureCheck.cpp new file mode 100644 index 00000000..fe3942fc --- /dev/null +++ b/clang-tidy/misc/AssignOperatorSignatureCheck.cpp @@ -0,0 +1,79 @@ +//===--- AssignOperatorSignatureCheck.cpp - clang-tidy ----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "AssignOperatorSignatureCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void AssignOperatorSignatureCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + const auto HasGoodReturnType = cxxMethodDecl(returns( + lValueReferenceType(pointee(unless(isConstQualified()), + hasDeclaration(equalsBoundNode("class")))))); + + const auto IsSelf = qualType( + anyOf(hasDeclaration(equalsBoundNode("class")), + referenceType(pointee(hasDeclaration(equalsBoundNode("class")))))); + const auto IsSelfAssign = + cxxMethodDecl(unless(anyOf(isDeleted(), isPrivate(), isImplicit())), + hasName("operator="), ofClass(recordDecl().bind("class")), + hasParameter(0, parmVarDecl(hasType(IsSelf)))) + .bind("method"); + + Finder->addMatcher( + cxxMethodDecl(IsSelfAssign, unless(HasGoodReturnType)).bind("ReturnType"), + this); + + const auto BadSelf = referenceType( + anyOf(lValueReferenceType(pointee(unless(isConstQualified()))), + rValueReferenceType(pointee(isConstQualified())))); + + Finder->addMatcher( + cxxMethodDecl(IsSelfAssign, + hasParameter(0, parmVarDecl(hasType(BadSelf)))) + .bind("ArgumentType"), + this); + + Finder->addMatcher( + cxxMethodDecl(IsSelfAssign, anyOf(isConst(), isVirtual())).bind("cv"), + this); +} + +void AssignOperatorSignatureCheck::check( + const MatchFinder::MatchResult &Result) { + const auto* Method = Result.Nodes.getNodeAs("method"); + std::string Name = Method->getParent()->getName(); + + static const char *const Messages[][2] = { + {"ReturnType", "operator=() should return '%0&'"}, + {"ArgumentType", "operator=() should take '%0 const&', '%0&&' or '%0'"}, + {"cv", "operator=() should not be marked '%1'"} + }; + + for (const auto &Message : Messages) { + if (Result.Nodes.getNodeAs(Message[0])) + diag(Method->getLocStart(), Message[1]) + << Name << (Method->isConst() ? "const" : "virtual"); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/AssignOperatorSignatureCheck.h b/clang-tidy/misc/AssignOperatorSignatureCheck.h new file mode 100644 index 00000000..a2530f93 --- /dev/null +++ b/clang-tidy/misc/AssignOperatorSignatureCheck.h @@ -0,0 +1,37 @@ +//===--- AssignOperatorSignatureCheck.h - clang-tidy ------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSIGNOPERATORSIGNATURECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSIGNOPERATORSIGNATURECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds declarations of assign operators with the wrong return and/or argument +/// types. +/// +/// * The return type must be `Class&`. +/// * Works with move-assign and assign by value. +/// * Private and deleted operators are ignored. +class AssignOperatorSignatureCheck : public ClangTidyCheck { +public: + AssignOperatorSignatureCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSIGNOPERATORSIGNATURECHECK_H diff --git a/clang-tidy/misc/BoolPointerImplicitConversionCheck.cpp b/clang-tidy/misc/BoolPointerImplicitConversionCheck.cpp new file mode 100644 index 00000000..ec13e63b --- /dev/null +++ b/clang-tidy/misc/BoolPointerImplicitConversionCheck.cpp @@ -0,0 +1,77 @@ +//===--- BoolPointerImplicitConversionCheck.cpp - clang-tidy --------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "BoolPointerImplicitConversionCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace { + +AST_MATCHER(CastExpr, isPointerToBoolean) { + return Node.getCastKind() == CK_PointerToBoolean; +} +AST_MATCHER(QualType, isBoolean) { return Node->isBooleanType(); } + +} // namespace + +namespace tidy { +namespace misc { + +void BoolPointerImplicitConversionCheck::registerMatchers(MatchFinder *Finder) { + // Look for ifs that have an implicit bool* to bool conversion in the + // condition. Filter negations. + Finder->addMatcher( + ifStmt(hasCondition(findAll(implicitCastExpr( + allOf(unless(hasParent(unaryOperator(hasOperatorName("!")))), + hasSourceExpression(expr( + hasType(pointerType(pointee(isBoolean()))), + ignoringParenImpCasts(declRefExpr().bind("expr")))), + isPointerToBoolean())))), + unless(isInTemplateInstantiation())).bind("if"), + this); +} + +void BoolPointerImplicitConversionCheck::check( + const MatchFinder::MatchResult &Result) { + auto *If = Result.Nodes.getStmtAs("if"); + auto *Var = Result.Nodes.getStmtAs("expr"); + + // Ignore macros. + if (Var->getLocStart().isMacroID()) + return; + + // Only allow variable accesses for now, no function calls or member exprs. + // Check that we don't dereference the variable anywhere within the if. This + // avoids false positives for checks of the pointer for nullptr before it is + // dereferenced. If there is a dereferencing operator on this variable don't + // emit a diagnostic. Also ignore array subscripts. + const Decl *D = Var->getDecl(); + auto DeclRef = ignoringParenImpCasts(declRefExpr(to(equalsNode(D)))); + if (!match(findAll( + unaryOperator(hasOperatorName("*"), hasUnaryOperand(DeclRef))), + *If, *Result.Context).empty() || + !match(findAll(arraySubscriptExpr(hasBase(DeclRef))), *If, + *Result.Context).empty() || + // FIXME: We should still warn if the paremater is implicitly converted to + // bool. + !match(findAll(callExpr(hasAnyArgument(DeclRef))), *If, *Result.Context) + .empty() || + !match(findAll(cxxDeleteExpr(has(expr(DeclRef)))), *If, *Result.Context) + .empty()) + return; + + diag(Var->getLocStart(), "dubious check of 'bool *' against 'nullptr', did " + "you mean to dereference it?") + << FixItHint::CreateInsertion(Var->getLocStart(), "*"); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/BoolPointerImplicitConversionCheck.h b/clang-tidy/misc/BoolPointerImplicitConversionCheck.h new file mode 100644 index 00000000..80dcdc78 --- /dev/null +++ b/clang-tidy/misc/BoolPointerImplicitConversionCheck.h @@ -0,0 +1,43 @@ +//===--- BoolPointerImplicitConversionCheck.h - clang-tidy ------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_BOOLPOINTERIMPLICITCONVERSIONCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_BOOLPOINTERIMPLICITCONVERSIONCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Checks for conditions based on implicit conversion from a bool pointer to +/// bool. +/// +/// Example: +/// +/// \code +/// bool *p; +/// if (p) { +/// // Never used in a pointer-specific way. +/// } +/// \endcode +class BoolPointerImplicitConversionCheck : public ClangTidyCheck { +public: + BoolPointerImplicitConversionCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_BOOLPOINTERIMPLICITCONVERSIONCHECK_H + diff --git a/clang-tidy/misc/CMakeLists.txt b/clang-tidy/misc/CMakeLists.txt new file mode 100644 index 00000000..6835c549 --- /dev/null +++ b/clang-tidy/misc/CMakeLists.txt @@ -0,0 +1,38 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyMiscModule + ArgumentCommentCheck.cpp + AssertSideEffectCheck.cpp + AssignOperatorSignatureCheck.cpp + BoolPointerImplicitConversionCheck.cpp + DefinitionsInHeadersCheck.cpp + InaccurateEraseCheck.cpp + InefficientAlgorithmCheck.cpp + MacroParenthesesCheck.cpp + MacroRepeatedSideEffectsCheck.cpp + MiscTidyModule.cpp + MoveConstantArgumentCheck.cpp + MoveConstructorInitCheck.cpp + NewDeleteOverloadsCheck.cpp + NoexceptMoveConstructorCheck.cpp + NonCopyableObjects.cpp + SizeofContainerCheck.cpp + StaticAssertCheck.cpp + StringIntegerAssignmentCheck.cpp + SwappedArgumentsCheck.cpp + ThrowByValueCatchByReferenceCheck.cpp + UndelegatedConstructor.cpp + UnusedAliasDeclsCheck.cpp + UnusedParametersCheck.cpp + UnusedRAIICheck.cpp + UniqueptrResetReleaseCheck.cpp + VirtualNearMissCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + ) diff --git a/clang-tidy/misc/DefinitionsInHeadersCheck.cpp b/clang-tidy/misc/DefinitionsInHeadersCheck.cpp new file mode 100644 index 00000000..29fa0f6d --- /dev/null +++ b/clang-tidy/misc/DefinitionsInHeadersCheck.cpp @@ -0,0 +1,126 @@ +//===--- DefinitionsInHeadersCheck.cpp - clang-tidy------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DefinitionsInHeadersCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { + +AST_MATCHER(NamedDecl, isHeaderFileExtension) { + SourceManager& SM = Finder->getASTContext().getSourceManager(); + SourceLocation ExpansionLoc = SM.getExpansionLoc(Node.getLocStart()); + StringRef Filename = SM.getFilename(ExpansionLoc); + return Filename.endswith(".h") || Filename.endswith(".hh") || + Filename.endswith(".hpp") || Filename.endswith(".hxx") || + llvm::sys::path::extension(Filename).empty(); +} + +} // namespace + +DefinitionsInHeadersCheck::DefinitionsInHeadersCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + UseHeaderFileExtension(Options.get("UseHeaderFileExtension", true)) {} + +void DefinitionsInHeadersCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "UseHeaderFileExtension", UseHeaderFileExtension); +} + +void DefinitionsInHeadersCheck::registerMatchers(MatchFinder *Finder) { + if (UseHeaderFileExtension) { + Finder->addMatcher( + namedDecl(anyOf(functionDecl(isDefinition()), varDecl(isDefinition())), + isHeaderFileExtension()).bind("name-decl"), + this); + } else { + Finder->addMatcher( + namedDecl(anyOf(functionDecl(isDefinition()), varDecl(isDefinition())), + anyOf(isHeaderFileExtension(), + unless(isExpansionInMainFile()))).bind("name-decl"), + this); + } +} + +void DefinitionsInHeadersCheck::check(const MatchFinder::MatchResult &Result) { + // C++ [basic.def.odr] p6: + // There can be more than one definition of a class type, enumeration type, + // inline function with external linkage, class template, non-static function + // template, static data member of a class template, member function of a + // class template, or template specialization for which some template + // parameters are not specifiedin a program provided that each definition + // appears in a different translation unit, and provided the definitions + // satisfy the following requirements. + const auto *ND = Result.Nodes.getNodeAs("name-decl"); + assert(ND); + + // Internal linkage variable definitions are ignored for now: + // const int a = 1; + // static int b = 1; + // + // Although these might also cause ODR violations, we can be less certain and + // should try to keep the false-positive rate down. + if (ND->getLinkageInternal() == InternalLinkage) + return; + + if (const auto *FD = dyn_cast(ND)) { + // Inline functions are allowed. + if (FD->isInlined()) + return; + // Function templates are allowed. + if (FD->getTemplatedKind() == FunctionDecl::TK_FunctionTemplate) + return; + // Function template full specialization is prohibited in header file. + if (FD->getTemplateSpecializationKind() == TSK_ImplicitInstantiation) + return; + // Member function of a class template and member function of a nested class + // in a class template are allowed. + if (const auto *MD = dyn_cast(FD)) { + const auto *DC = MD->getDeclContext(); + while (DC->isRecord()) { + if (const auto *RD = dyn_cast(DC)) + if (RD->getDescribedClassTemplate()) + return; + DC = DC->getParent(); + } + } + + diag(FD->getLocation(), + "function '%0' defined in a header file; " + "function definitions in header files can lead to ODR violations") + << FD->getNameInfo().getName().getAsString() + << FixItHint::CreateInsertion(FD->getSourceRange().getBegin(), + "inline "); + } else if (const auto *VD = dyn_cast(ND)) { + // Static data members of a class template are allowed. + if (VD->getDeclContext()->isDependentContext() && VD->isStaticDataMember()) + return; + if (VD->getTemplateSpecializationKind() == TSK_ImplicitInstantiation) + return; + // Ignore variable definition within function scope. + if (VD->hasLocalStorage() || VD->isStaticLocal()) + return; + + diag(VD->getLocation(), + "variable '%0' defined in a header file; " + "variable definitions in header files can lead to ODR violations") + << VD->getName(); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/DefinitionsInHeadersCheck.h b/clang-tidy/misc/DefinitionsInHeadersCheck.h new file mode 100644 index 00000000..93956099 --- /dev/null +++ b/clang-tidy/misc/DefinitionsInHeadersCheck.h @@ -0,0 +1,43 @@ +//===--- DefinitionsInHeadersCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DEFINITIONS_IN_HEADERS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DEFINITIONS_IN_HEADERS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +// Finds non-extern non-inline function and variable definitions in header +// files, which can lead to potential ODR violations. +// +// There is one option: +// - `UseHeaderFileExtension`: Whether to use file extension (h, hh, hpp, hxx) +// to distinguish header files. True by default. +// +// For the user-facing documentation see: +// http://clang.llvm.org/extra/clang-tidy/checks/misc-definitions-in-headers.html +class DefinitionsInHeadersCheck : public ClangTidyCheck { +public: + DefinitionsInHeadersCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool UseHeaderFileExtension; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DEFINITIONS_IN_HEADERS_H diff --git a/clang-tidy/misc/InaccurateEraseCheck.cpp b/clang-tidy/misc/InaccurateEraseCheck.cpp new file mode 100644 index 00000000..6e69f2a4 --- /dev/null +++ b/clang-tidy/misc/InaccurateEraseCheck.cpp @@ -0,0 +1,73 @@ +//===--- InaccurateEraseCheck.cpp - clang-tidy-----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "InaccurateEraseCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void InaccurateEraseCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + const auto CheckForEndCall = hasArgument( + 1, anyOf(cxxConstructExpr( + has(cxxMemberCallExpr(callee(cxxMethodDecl(hasName("end")))) + .bind("InaccEndCall"))), + anything())); + + Finder->addMatcher( + cxxMemberCallExpr( + on(hasType(namedDecl(matchesName("^::std::")))), + callee(cxxMethodDecl(hasName("erase"))), argumentCountIs(1), + hasArgument(0, has(callExpr(callee(functionDecl(matchesName( + "^::std::(remove(_if)?|unique)$"))), + CheckForEndCall) + .bind("InaccAlgCall"))), + unless(isInTemplateInstantiation())) + .bind("InaccErase"), + this); +} + +void InaccurateEraseCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MemberCall = + Result.Nodes.getNodeAs("InaccErase"); + const auto *EndExpr = + Result.Nodes.getNodeAs("InaccEndCall"); + const SourceLocation Loc = MemberCall->getLocStart(); + + FixItHint Hint; + + if (!Loc.isMacroID() && EndExpr) { + const auto *AlgCall = Result.Nodes.getNodeAs("InaccAlgCall"); + std::string ReplacementText = Lexer::getSourceText( + CharSourceRange::getTokenRange(EndExpr->getSourceRange()), + *Result.SourceManager, Result.Context->getLangOpts()); + const SourceLocation EndLoc = Lexer::getLocForEndOfToken( + AlgCall->getLocEnd(), 0, *Result.SourceManager, + Result.Context->getLangOpts()); + Hint = FixItHint::CreateInsertion(EndLoc, ", " + ReplacementText); + } + + diag(Loc, "this call will remove at most one item even when multiple items " + "should be removed") + << Hint; +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/InaccurateEraseCheck.h b/clang-tidy/misc/InaccurateEraseCheck.h new file mode 100644 index 00000000..623e1c23 --- /dev/null +++ b/clang-tidy/misc/InaccurateEraseCheck.h @@ -0,0 +1,38 @@ +//===--- InaccurateEraseCheck.h - clang-tidy---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INACCURATEERASECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INACCURATEERASECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Checks for inaccurate use of the `erase()` method. +/// +/// Algorithms like `remove()` do not actually remove any element from the +/// container but return an iterator to the first redundant element at the end +/// of the container. These redundant elements must be removed using the +/// `erase()` method. This check warns when not all of the elements will be +/// removed due to using an inappropriate overload. +class InaccurateEraseCheck : public ClangTidyCheck { +public: + InaccurateEraseCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INACCURATEERASECHECK_H diff --git a/clang-tidy/misc/InefficientAlgorithmCheck.cpp b/clang-tidy/misc/InefficientAlgorithmCheck.cpp new file mode 100644 index 00000000..94e37859 --- /dev/null +++ b/clang-tidy/misc/InefficientAlgorithmCheck.cpp @@ -0,0 +1,158 @@ +//===--- InefficientAlgorithmCheck.cpp - clang-tidy------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "InefficientAlgorithmCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +static bool areTypesCompatible(QualType Left, QualType Right) { + if (const auto *LeftRefType = Left->getAs()) + Left = LeftRefType->getPointeeType(); + if (const auto *RightRefType = Right->getAs()) + Right = RightRefType->getPointeeType(); + return Left->getCanonicalTypeUnqualified() == + Right->getCanonicalTypeUnqualified(); +} + +void InefficientAlgorithmCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + const std::string Algorithms = + "^::std::(find|count|equal_range|lower_bound|upper_bound)$"; + const auto ContainerMatcher = classTemplateSpecializationDecl( + matchesName("^::std::(unordered_)?(multi)?(set|map)$")); + const auto Matcher = + callExpr( + callee(functionDecl(matchesName(Algorithms))), + hasArgument( + 0, cxxConstructExpr(has(cxxMemberCallExpr( + callee(cxxMethodDecl(hasName("begin"))), + on(declRefExpr( + hasDeclaration(decl().bind("IneffContObj")), + anyOf(hasType(ContainerMatcher.bind("IneffCont")), + hasType(pointsTo( + ContainerMatcher.bind("IneffContPtr"))))) + .bind("IneffContExpr")))))), + hasArgument(1, cxxConstructExpr(has(cxxMemberCallExpr( + callee(cxxMethodDecl(hasName("end"))), + on(declRefExpr(hasDeclaration( + equalsBoundNode("IneffContObj")))))))), + hasArgument(2, expr().bind("AlgParam")), + unless(isInTemplateInstantiation())) + .bind("IneffAlg"); + + Finder->addMatcher(Matcher, this); +} + +void InefficientAlgorithmCheck::check(const MatchFinder::MatchResult &Result) { + const auto *AlgCall = Result.Nodes.getNodeAs("IneffAlg"); + const auto *IneffCont = + Result.Nodes.getNodeAs("IneffCont"); + bool PtrToContainer = false; + if (!IneffCont) { + IneffCont = + Result.Nodes.getNodeAs("IneffContPtr"); + PtrToContainer = true; + } + const llvm::StringRef IneffContName = IneffCont->getName(); + const bool Unordered = + IneffContName.find("unordered") != llvm::StringRef::npos; + const bool Maplike = IneffContName.find("map") != llvm::StringRef::npos; + + // Store if the key type of the container is compatible with the value + // that is searched for. + QualType ValueType = AlgCall->getArg(2)->getType(); + QualType KeyType = + IneffCont->getTemplateArgs()[0].getAsType().getCanonicalType(); + const bool CompatibleTypes = areTypesCompatible(KeyType, ValueType); + + // Check if the comparison type for the algorithm and the container matches. + if (AlgCall->getNumArgs() == 4 && !Unordered) { + const Expr *Arg = AlgCall->getArg(3); + const QualType AlgCmp = + Arg->getType().getUnqualifiedType().getCanonicalType(); + const unsigned CmpPosition = + (IneffContName.find("map") == llvm::StringRef::npos) ? 1 : 2; + const QualType ContainerCmp = IneffCont->getTemplateArgs()[CmpPosition] + .getAsType() + .getUnqualifiedType() + .getCanonicalType(); + if (AlgCmp != ContainerCmp) { + diag(Arg->getLocStart(), + "different comparers used in the algorithm and the container"); + return; + } + } + + const auto *AlgDecl = AlgCall->getDirectCallee(); + if (!AlgDecl) + return; + + if (Unordered && AlgDecl->getName().find("bound") != llvm::StringRef::npos) + return; + + const auto *AlgParam = Result.Nodes.getNodeAs("AlgParam"); + const auto *IneffContExpr = Result.Nodes.getNodeAs("IneffContExpr"); + FixItHint Hint; + + SourceManager &SM = *Result.SourceManager; + LangOptions LangOpts = Result.Context->getLangOpts(); + + CharSourceRange CallRange = + CharSourceRange::getTokenRange(AlgCall->getSourceRange()); + + // FIXME: Create a common utility to extract a file range that the given token + // sequence is exactly spelled at (without macro argument expansions etc.). + // We can't use Lexer::makeFileCharRange here, because for + // + // #define F(x) x + // x(a b c); + // + // it will return "x(a b c)", when given the range "a"-"c". It makes sense for + // removals, but not for replacements. + // + // This code is over-simplified, but works for many real cases. + if (SM.isMacroArgExpansion(CallRange.getBegin()) && + SM.isMacroArgExpansion(CallRange.getEnd())) { + CallRange.setBegin(SM.getSpellingLoc(CallRange.getBegin())); + CallRange.setEnd(SM.getSpellingLoc(CallRange.getEnd())); + } + + if (!CallRange.getBegin().isMacroID() && !Maplike && CompatibleTypes) { + StringRef ContainerText = Lexer::getSourceText( + CharSourceRange::getTokenRange(IneffContExpr->getSourceRange()), SM, + LangOpts); + StringRef ParamText = Lexer::getSourceText( + CharSourceRange::getTokenRange(AlgParam->getSourceRange()), SM, + LangOpts); + std::string ReplacementText = + (llvm::Twine(ContainerText) + (PtrToContainer ? "->" : ".") + + AlgDecl->getName() + "(" + ParamText + ")") + .str(); + Hint = FixItHint::CreateReplacement(CallRange, ReplacementText); + } + + diag(AlgCall->getLocStart(), + "this STL algorithm call should be replaced with a container method") + << Hint; +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/InefficientAlgorithmCheck.h b/clang-tidy/misc/InefficientAlgorithmCheck.h new file mode 100644 index 00000000..6935b455 --- /dev/null +++ b/clang-tidy/misc/InefficientAlgorithmCheck.h @@ -0,0 +1,36 @@ +//===--- InefficientAlgorithmCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INEFFICIENTALGORITHMCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INEFFICIENTALGORITHMCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Warns on inefficient use of STL algorithms on associative containers. +/// +/// Associative containers implements some of the algorithms as methods which +/// should be preferred to the algorithms in the algorithm header. The methods +/// can take advanatage of the order of the elements. +class InefficientAlgorithmCheck : public ClangTidyCheck { +public: + InefficientAlgorithmCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INEFFICIENTALGORITHMCHECK_H diff --git a/clang-tidy/misc/MacroParenthesesCheck.cpp b/clang-tidy/misc/MacroParenthesesCheck.cpp new file mode 100644 index 00000000..e1955dec --- /dev/null +++ b/clang-tidy/misc/MacroParenthesesCheck.cpp @@ -0,0 +1,202 @@ +//===--- MacroParenthesesCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MacroParenthesesCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" + +namespace clang { +namespace tidy { + +namespace { +class MacroParenthesesPPCallbacks : public PPCallbacks { +public: + explicit MacroParenthesesPPCallbacks(Preprocessor *PP, + MacroParenthesesCheck *Check) + : PP(PP), Check(Check) {} + + void MacroDefined(const Token &MacroNameTok, + const MacroDirective *MD) override { + replacementList(MacroNameTok, MD->getMacroInfo()); + argument(MacroNameTok, MD->getMacroInfo()); + } + +private: + /// Replacement list with calculations should be enclosed in parentheses. + void replacementList(const Token &MacroNameTok, const MacroInfo *MI); + + /// Arguments should be enclosed in parentheses. + void argument(const Token &MacroNameTok, const MacroInfo *MI); + + Preprocessor *PP; + MacroParenthesesCheck *Check; +}; +} // namespace + +/// Is argument surrounded properly with parentheses/braces/squares/commas? +static bool isSurroundedLeft(const Token &T) { + return T.isOneOf(tok::l_paren, tok::l_brace, tok::l_square, tok::comma, + tok::semi); +} + +/// Is argument surrounded properly with parentheses/braces/squares/commas? +static bool isSurroundedRight(const Token &T) { + return T.isOneOf(tok::r_paren, tok::r_brace, tok::r_square, tok::comma, + tok::semi); +} + +/// Is given TokenKind a keyword? +static bool isKeyword(const Token &T) { + // FIXME: better matching of keywords to avoid false positives. + return T.isOneOf(tok::kw_case, tok::kw_const, tok::kw_struct); +} + +/// Warning is written when one of these operators are not within parentheses. +static bool isWarnOp(const Token &T) { + // FIXME: This is an initial list of operators. It can be tweaked later to + // get more positives or perhaps avoid some false positive. + return T.isOneOf(tok::plus, tok::minus, tok::star, tok::slash, tok::percent, + tok::amp, tok::pipe, tok::caret); +} + +void MacroParenthesesPPCallbacks::replacementList(const Token &MacroNameTok, + const MacroInfo *MI) { + // Count how deep we are in parentheses/braces/squares. + int Count = 0; + + // SourceLocation for error + SourceLocation Loc; + + for (auto TI = MI->tokens_begin(), TE = MI->tokens_end(); TI != TE; ++TI) { + const Token &Tok = *TI; + // Replacement list contains keywords, don't warn about it. + if (isKeyword(Tok)) + return; + // When replacement list contains comma/semi don't warn about it. + if (Count == 0 && Tok.isOneOf(tok::comma, tok::semi)) + return; + if (Tok.isOneOf(tok::l_paren, tok::l_brace, tok::l_square)) { + ++Count; + } else if (Tok.isOneOf(tok::r_paren, tok::r_brace, tok::r_square)) { + --Count; + // If there are unbalanced parentheses don't write any warning + if (Count < 0) + return; + } else if (Count == 0 && isWarnOp(Tok)) { + // Heuristic for macros that are clearly not intended to be enclosed in + // parentheses, macro starts with operator. For example: + // #define X *10 + if (TI == MI->tokens_begin() && (TI + 1) != TE && + !Tok.isOneOf(tok::plus, tok::minus)) + return; + // Don't warn about this macro if the last token is a star. For example: + // #define X void * + if ((TE - 1)->is(tok::star)) + return; + + Loc = Tok.getLocation(); + } + } + if (Loc.isValid()) { + const Token &Last = *(MI->tokens_end() - 1); + Check->diag(Loc, "macro replacement list should be enclosed in parentheses") + << FixItHint::CreateInsertion(MI->tokens_begin()->getLocation(), "(") + << FixItHint::CreateInsertion(Last.getLocation().getLocWithOffset( + PP->getSpelling(Last).length()), + ")"); + } +} + +void MacroParenthesesPPCallbacks::argument(const Token &MacroNameTok, + const MacroInfo *MI) { + + for (auto TI = MI->tokens_begin(), TE = MI->tokens_end(); TI != TE; ++TI) { + // First token. + if (TI == MI->tokens_begin()) + continue; + + // Last token. + if ((TI + 1) == MI->tokens_end()) + continue; + + const Token &Prev = *(TI - 1); + const Token &Next = *(TI + 1); + + const Token &Tok = *TI; + + // Only interested in identifiers. + if (!Tok.isOneOf(tok::identifier, tok::raw_identifier)) + continue; + + // Only interested in macro arguments. + if (MI->getArgumentNum(Tok.getIdentifierInfo()) < 0) + continue; + + // Argument is surrounded with parentheses/squares/braces/commas. + if (isSurroundedLeft(Prev) && isSurroundedRight(Next)) + continue; + + // Don't warn after hash/hashhash or before hashhash. + if (Prev.isOneOf(tok::hash, tok::hashhash) || Next.is(tok::hashhash)) + continue; + + // Argument is a struct member. + if (Prev.isOneOf(tok::period, tok::arrow, tok::coloncolon, tok::arrowstar, + tok::periodstar)) + continue; + + // Argument is a namespace or class. + if (Next.is(tok::coloncolon)) + continue; + + // String concatenation. + if (isStringLiteral(Prev.getKind()) || isStringLiteral(Next.getKind())) + continue; + + // Type/Var. + if (isAnyIdentifier(Prev.getKind()) || isKeyword(Prev) || + isAnyIdentifier(Next.getKind()) || isKeyword(Next)) + continue; + + // Initialization. + if (Next.is(tok::l_paren)) + continue; + + // Cast. + if (Prev.is(tok::l_paren) && Next.is(tok::star) && + TI + 2 != MI->tokens_end() && (TI + 2)->is(tok::r_paren)) + continue; + + // Assignment/return, i.e. '=x;' or 'return x;'. + if (Prev.isOneOf(tok::equal, tok::kw_return) && Next.is(tok::semi)) + continue; + + // C++ template parameters. + if (PP->getLangOpts().CPlusPlus && Prev.isOneOf(tok::comma, tok::less) && + Next.isOneOf(tok::comma, tok::greater)) + continue; + + Check->diag(Tok.getLocation(), "macro argument should be enclosed in " + "parentheses") + << FixItHint::CreateInsertion(Tok.getLocation(), "(") + << FixItHint::CreateInsertion(Tok.getLocation().getLocWithOffset( + PP->getSpelling(Tok).length()), + ")"); + } +} + +void MacroParenthesesCheck::registerPPCallbacks(CompilerInstance &Compiler) { + Compiler.getPreprocessor().addPPCallbacks( + llvm::make_unique( + &Compiler.getPreprocessor(), this)); +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MacroParenthesesCheck.h b/clang-tidy/misc/MacroParenthesesCheck.h new file mode 100644 index 00000000..f4a79c01 --- /dev/null +++ b/clang-tidy/misc/MacroParenthesesCheck.h @@ -0,0 +1,41 @@ +//===--- MacroParenthesesCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_PARENTHESES_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_PARENTHESES_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// Finds macros that can have unexpected behaviour due to missing parentheses. +/// +/// Macros are expanded by the preprocessor as-is. As a result, there can be +/// unexpected behaviour; operators may be evaluated in unexpected order and +/// unary operators may become binary operators, etc. +/// +/// When the replacement list has an expression, it is recommended to surround +/// it with parentheses. This ensures that the macro result is evaluated +/// completely before it is used. +/// +/// It is also recommended to surround macro arguments in the replacement list +/// with parentheses. This ensures that the argument value is calculated +/// properly. +class MacroParenthesesCheck : public ClangTidyCheck { +public: + MacroParenthesesCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerPPCallbacks(CompilerInstance &Compiler) override; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_PARENTHESES_H diff --git a/clang-tidy/misc/MacroRepeatedSideEffectsCheck.cpp b/clang-tidy/misc/MacroRepeatedSideEffectsCheck.cpp new file mode 100644 index 00000000..24593cba --- /dev/null +++ b/clang-tidy/misc/MacroRepeatedSideEffectsCheck.cpp @@ -0,0 +1,173 @@ +//===--- MacroRepeatedSideEffectsCheck.cpp - clang-tidy--------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MacroRepeatedSideEffectsCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Lex/MacroArgs.h" + +namespace clang { +namespace tidy { +namespace misc { + +namespace { +class MacroRepeatedPPCallbacks : public PPCallbacks { +public: + MacroRepeatedPPCallbacks(ClangTidyCheck &Check, Preprocessor &PP) + : Check(Check), PP(PP) {} + + void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD, + SourceRange Range, const MacroArgs *Args) override; + +private: + ClangTidyCheck &Check; + Preprocessor &PP; + + unsigned countArgumentExpansions(const MacroInfo *MI, + const IdentifierInfo *Arg) const; + + bool hasSideEffects(const Token *ResultArgToks) const; +}; +} // End of anonymous namespace. + +void MacroRepeatedPPCallbacks::MacroExpands(const Token &MacroNameTok, + const MacroDefinition &MD, + SourceRange Range, + const MacroArgs *Args) { + // Ignore macro argument expansions. + if (!Range.getBegin().isFileID()) + return; + + const MacroInfo *MI = MD.getMacroInfo(); + + // Bail out if the contents of the macro are containing keywords that are + // making the macro too complex. + if (std::find_if( + MI->tokens().begin(), MI->tokens().end(), [](const Token &T) { + return T.isOneOf(tok::kw_if, tok::kw_else, tok::kw_switch, + tok::kw_case, tok::kw_break, tok::kw_while, + tok::kw_do, tok::kw_for, tok::kw_continue, + tok::kw_goto, tok::kw_return); + }) != MI->tokens().end()) + return; + + for (unsigned ArgNo = 0U; ArgNo < MI->getNumArgs(); ++ArgNo) { + const IdentifierInfo *Arg = *(MI->arg_begin() + ArgNo); + const Token *ResultArgToks = Args->getUnexpArgument(ArgNo); + + if (hasSideEffects(ResultArgToks) && + countArgumentExpansions(MI, Arg) >= 2) { + Check.diag(ResultArgToks->getLocation(), + "side effects in the %ordinal0 macro argument '%1' are " + "repeated in macro expansion") + << (ArgNo + 1) << Arg->getName(); + Check.diag(MI->getDefinitionLoc(), "macro %0 defined here", + DiagnosticIDs::Note) + << MacroNameTok.getIdentifierInfo(); + } + } +} + +unsigned MacroRepeatedPPCallbacks::countArgumentExpansions( + const MacroInfo *MI, const IdentifierInfo *Arg) const { + // Current argument count. When moving forward to a different control-flow + // path this can decrease. + unsigned Current = 0; + // Max argument count. + unsigned Max = 0; + bool SkipParen = false; + int SkipParenCount = 0; + // Has a __builtin_constant_p been found? + bool FoundBuiltin = false; + // Count when "?" is reached. The "Current" will get this value when the ":" + // is reached. + std::stack> CountAtQuestion; + for (const auto &T : MI->tokens()) { + // The result of __builtin_constant_p(x) is 0 if x is a macro argument + // with side effects. If we see a __builtin_constant_p(x) followed by a + // "?" "&&" or "||", then we need to reason about control flow to report + // warnings correctly. Until such reasoning is added, bail out when this + // happens. + if (FoundBuiltin && T.isOneOf(tok::question, tok::ampamp, tok::pipepipe)) + return Max; + + // Handling of ? and :. + if (T.is(tok::question)) { + CountAtQuestion.push(Current); + } else if (T.is(tok::colon)) { + if (CountAtQuestion.empty()) + return 0; + Current = CountAtQuestion.top(); + CountAtQuestion.pop(); + } + + // If current token is a parenthesis, skip it. + if (SkipParen) { + if (T.is(tok::l_paren)) + SkipParenCount++; + else if (T.is(tok::r_paren)) + SkipParenCount--; + SkipParen = (SkipParenCount != 0); + if (SkipParen) + continue; + } + + IdentifierInfo *TII = T.getIdentifierInfo(); + // If not existent, skip it. + if (TII == nullptr) + continue; + + // If a __builtin_constant_p is found within the macro definition, don't + // count arguments inside the parentheses and remember that it has been + // seen in case there are "?", "&&" or "||" operators later. + if (TII->getBuiltinID() == Builtin::BI__builtin_constant_p) { + FoundBuiltin = true; + SkipParen = true; + continue; + } + + // If another macro is found within the macro definition, skip the macro + // and the eventual arguments. + if (TII->hasMacroDefinition()) { + const MacroInfo *M = PP.getMacroDefinition(TII).getMacroInfo(); + if (M != nullptr && M->isFunctionLike()) + SkipParen = true; + continue; + } + + // Count argument. + if (TII == Arg) { + Current++; + if (Current > Max) + Max = Current; + } + } + return Max; +} + +bool MacroRepeatedPPCallbacks::hasSideEffects( + const Token *ResultArgToks) const { + for (; ResultArgToks->isNot(tok::eof); ++ResultArgToks) { + if (ResultArgToks->isOneOf(tok::plusplus, tok::minusminus)) + return true; + } + return false; +} + +void MacroRepeatedSideEffectsCheck::registerPPCallbacks( + CompilerInstance &Compiler) { + Compiler.getPreprocessor().addPPCallbacks( + ::llvm::make_unique( + *this, Compiler.getPreprocessor())); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MacroRepeatedSideEffectsCheck.h b/clang-tidy/misc/MacroRepeatedSideEffectsCheck.h new file mode 100644 index 00000000..10ff8427 --- /dev/null +++ b/clang-tidy/misc/MacroRepeatedSideEffectsCheck.h @@ -0,0 +1,31 @@ +//===--- MacroRepeatedSideEffectsCheck.h - clang-tidy -----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_REPEATED_SIDE_EFFECTS_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_REPEATED_SIDE_EFFECTS_CHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Checks for repeated argument with side effects in macros. +class MacroRepeatedSideEffectsCheck : public ClangTidyCheck { +public: + MacroRepeatedSideEffectsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerPPCallbacks(CompilerInstance &Compiler) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_REPEATED_SIDE_EFFECTS_CHECK_H diff --git a/clang-tidy/misc/Makefile b/clang-tidy/misc/Makefile new file mode 100644 index 00000000..57e1f1a3 --- /dev/null +++ b/clang-tidy/misc/Makefile @@ -0,0 +1,12 @@ +##===- clang-tidy/misc/Makefile ----------------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## +CLANG_LEVEL := ../../../.. +LIBRARYNAME := clangTidyMiscModule + +include $(CLANG_LEVEL)/Makefile diff --git a/clang-tidy/misc/MiscTidyModule.cpp b/clang-tidy/misc/MiscTidyModule.cpp new file mode 100644 index 00000000..62f6b052 --- /dev/null +++ b/clang-tidy/misc/MiscTidyModule.cpp @@ -0,0 +1,107 @@ +//===--- MiscTidyModule.cpp - clang-tidy ----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "ArgumentCommentCheck.h" +#include "AssertSideEffectCheck.h" +#include "AssignOperatorSignatureCheck.h" +#include "BoolPointerImplicitConversionCheck.h" +#include "DefinitionsInHeadersCheck.h" +#include "InaccurateEraseCheck.h" +#include "InefficientAlgorithmCheck.h" +#include "MacroParenthesesCheck.h" +#include "MacroRepeatedSideEffectsCheck.h" +#include "MoveConstantArgumentCheck.h" +#include "MoveConstructorInitCheck.h" +#include "NewDeleteOverloadsCheck.h" +#include "NoexceptMoveConstructorCheck.h" +#include "NonCopyableObjects.h" +#include "SizeofContainerCheck.h" +#include "StaticAssertCheck.h" +#include "StringIntegerAssignmentCheck.h" +#include "SwappedArgumentsCheck.h" +#include "ThrowByValueCatchByReferenceCheck.h" +#include "UndelegatedConstructor.h" +#include "UniqueptrResetReleaseCheck.h" +#include "UnusedAliasDeclsCheck.h" +#include "UnusedParametersCheck.h" +#include "UnusedRAIICheck.h" +#include "VirtualNearMissCheck.h" + +namespace clang { +namespace tidy { +namespace misc { + +class MiscModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("misc-argument-comment"); + CheckFactories.registerCheck( + "misc-assert-side-effect"); + CheckFactories.registerCheck( + "misc-assign-operator-signature"); + CheckFactories.registerCheck( + "misc-bool-pointer-implicit-conversion"); + CheckFactories.registerCheck( + "misc-definitions-in-headers"); + CheckFactories.registerCheck( + "misc-inaccurate-erase"); + CheckFactories.registerCheck( + "misc-inefficient-algorithm"); + CheckFactories.registerCheck( + "misc-macro-parentheses"); + CheckFactories.registerCheck( + "misc-macro-repeated-side-effects"); + CheckFactories.registerCheck( + "misc-move-const-arg"); + CheckFactories.registerCheck( + "misc-move-constructor-init"); + CheckFactories.registerCheck( + "misc-new-delete-overloads"); + CheckFactories.registerCheck( + "misc-noexcept-move-constructor"); + CheckFactories.registerCheck( + "misc-non-copyable-objects"); + CheckFactories.registerCheck("misc-sizeof-container"); + CheckFactories.registerCheck( + "misc-static-assert"); + CheckFactories.registerCheck( + "misc-string-integer-assignment"); + CheckFactories.registerCheck( + "misc-swapped-arguments"); + CheckFactories.registerCheck( + "misc-throw-by-value-catch-by-reference"); + CheckFactories.registerCheck( + "misc-undelegated-constructor"); + CheckFactories.registerCheck( + "misc-uniqueptr-reset-release"); + CheckFactories.registerCheck( + "misc-unused-alias-decls"); + CheckFactories.registerCheck( + "misc-unused-parameters"); + CheckFactories.registerCheck("misc-unused-raii"); + CheckFactories.registerCheck( + "misc-virtual-near-miss"); + } +}; + +} // namespace misc + +// Register the MiscTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add +X("misc-module", "Adds miscellaneous lint checks."); + +// This anchor is used to force the linker to link in the generated object file +// and thus register the MiscModule. +volatile int MiscModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MoveConstantArgumentCheck.cpp b/clang-tidy/misc/MoveConstantArgumentCheck.cpp new file mode 100644 index 00000000..f38b49be --- /dev/null +++ b/clang-tidy/misc/MoveConstantArgumentCheck.cpp @@ -0,0 +1,71 @@ +//===--- MoveConstandArgumentCheck.cpp - clang-tidy -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MoveConstantArgumentCheck.h" + +#include + +namespace clang { +namespace tidy { +namespace misc { + +using namespace ast_matchers; + +void MoveConstantArgumentCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + Finder->addMatcher(callExpr(unless(isInTemplateInstantiation()), + callee(functionDecl(hasName("::std::move")))) + .bind("call-move"), + this); +} + +void MoveConstantArgumentCheck::check(const MatchFinder::MatchResult &Result) { + const auto *CallMove = Result.Nodes.getNodeAs("call-move"); + if (CallMove->getNumArgs() != 1) + return; + const Expr *Arg = CallMove->getArg(0); + SourceManager &SM = Result.Context->getSourceManager(); + + bool IsConstArg = Arg->getType().isConstQualified(); + bool IsTriviallyCopyable = + Arg->getType().isTriviallyCopyableType(*Result.Context); + + if (IsConstArg || IsTriviallyCopyable) { + auto MoveRange = CharSourceRange::getCharRange(CallMove->getSourceRange()); + auto FileMoveRange = Lexer::makeFileCharRange(MoveRange, SM, getLangOpts()); + if (!FileMoveRange.isValid()) + return; + bool IsVariable = isa(Arg); + auto Diag = + diag(FileMoveRange.getBegin(), "std::move of the %select{|const }0" + "%select{expression|variable}1 " + "%select{|of trivially-copyable type }2" + "has no effect; remove std::move()") + << IsConstArg << IsVariable << IsTriviallyCopyable; + + auto BeforeArgumentsRange = Lexer::makeFileCharRange( + CharSourceRange::getCharRange(CallMove->getLocStart(), + Arg->getLocStart()), + SM, getLangOpts()); + auto AfterArgumentsRange = Lexer::makeFileCharRange( + CharSourceRange::getCharRange( + CallMove->getLocEnd(), CallMove->getLocEnd().getLocWithOffset(1)), + SM, getLangOpts()); + + if (BeforeArgumentsRange.isValid() && AfterArgumentsRange.isValid()) { + Diag << FixItHint::CreateRemoval(BeforeArgumentsRange) + << FixItHint::CreateRemoval(AfterArgumentsRange); + } + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MoveConstantArgumentCheck.h b/clang-tidy/misc/MoveConstantArgumentCheck.h new file mode 100644 index 00000000..dbdd75ef --- /dev/null +++ b/clang-tidy/misc/MoveConstantArgumentCheck.h @@ -0,0 +1,31 @@ +//===--- MoveConstandArgumentCheck.h - clang-tidy -------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONTANTARGUMENTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONTANTARGUMENTCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +class MoveConstantArgumentCheck : public ClangTidyCheck { +public: + MoveConstantArgumentCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONTANTARGUMENTCHECK_H diff --git a/clang-tidy/misc/MoveConstructorInitCheck.cpp b/clang-tidy/misc/MoveConstructorInitCheck.cpp new file mode 100644 index 00000000..eeebcf69 --- /dev/null +++ b/clang-tidy/misc/MoveConstructorInitCheck.cpp @@ -0,0 +1,179 @@ +//===--- MoveConstructorInitCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MoveConstructorInitCheck.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +namespace { + +unsigned int +parmVarDeclRefExprOccurences(const ParmVarDecl &MovableParam, + const CXXConstructorDecl &ConstructorDecl, + ASTContext &Context) { + unsigned int Occurrences = 0; + auto AllDeclRefs = + findAll(declRefExpr(to(parmVarDecl(equalsNode(&MovableParam))))); + Occurrences += match(AllDeclRefs, *ConstructorDecl.getBody(), Context).size(); + for (const auto *Initializer : ConstructorDecl.inits()) { + Occurrences += match(AllDeclRefs, *Initializer->getInit(), Context).size(); + } + return Occurrences; +} + +} // namespace + +MoveConstructorInitCheck::MoveConstructorInitCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IncludeStyle(IncludeSorter::parseIncludeStyle( + Options.get("IncludeStyle", "llvm"))), + UseCERTSemantics(Context->isCheckEnabled("cert-oop11-cpp")) {} + +void MoveConstructorInitCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++11; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus11) + return; + + Finder->addMatcher( + cxxConstructorDecl( + unless(isImplicit()), + allOf(isMoveConstructor(), + hasAnyConstructorInitializer( + cxxCtorInitializer( + withInitializer(cxxConstructExpr(hasDeclaration( + cxxConstructorDecl(isCopyConstructor()) + .bind("ctor"))))) + .bind("move-init")))), + this); + + auto NonConstValueMovableAndExpensiveToCopy = + qualType(allOf(unless(pointerType()), unless(isConstQualified()), + hasDeclaration(cxxRecordDecl(hasMethod(cxxConstructorDecl( + isMoveConstructor(), unless(isDeleted()))))), + matchers::isExpensiveToCopy())); + + // This checker is also used to implement cert-oop11-cpp, but when using that + // form of the checker, we do not want to diagnose movable parameters. + if (!UseCERTSemantics) + Finder->addMatcher( + cxxConstructorDecl( + allOf( + unless(isMoveConstructor()), + hasAnyConstructorInitializer(withInitializer(cxxConstructExpr( + hasDeclaration(cxxConstructorDecl(isCopyConstructor())), + hasArgument( + 0, + declRefExpr( + to(parmVarDecl( + hasType( + NonConstValueMovableAndExpensiveToCopy)) + .bind("movable-param"))) + .bind("init-arg"))))))) + .bind("ctor-decl"), + this); +} + +void MoveConstructorInitCheck::check(const MatchFinder::MatchResult &Result) { + if (Result.Nodes.getNodeAs("move-init") != nullptr) + handleMoveConstructor(Result); + if (Result.Nodes.getNodeAs("movable-param") != nullptr) + handleParamNotMoved(Result); +} + +void MoveConstructorInitCheck::handleParamNotMoved( + const MatchFinder::MatchResult &Result) { + const auto *MovableParam = + Result.Nodes.getNodeAs("movable-param"); + const auto *ConstructorDecl = + Result.Nodes.getNodeAs("ctor-decl"); + const auto *InitArg = Result.Nodes.getNodeAs("init-arg"); + // If the parameter is referenced more than once it is not safe to move it. + if (parmVarDeclRefExprOccurences(*MovableParam, *ConstructorDecl, + *Result.Context) > 1) + return; + auto DiagOut = + diag(InitArg->getLocStart(), "value argument can be moved to avoid copy"); + DiagOut << FixItHint::CreateReplacement( + InitArg->getSourceRange(), + (Twine("std::move(") + MovableParam->getName() + ")").str()); + if (auto IncludeFixit = Inserter->CreateIncludeInsertion( + Result.SourceManager->getFileID(InitArg->getLocStart()), "utility", + /*IsAngled=*/true)) { + DiagOut << *IncludeFixit; + } +} + +void MoveConstructorInitCheck::handleMoveConstructor( + const MatchFinder::MatchResult &Result) { + const auto *CopyCtor = Result.Nodes.getNodeAs("ctor"); + const auto *Initializer = Result.Nodes.getNodeAs("move-init"); + + // Do not diagnose if the expression used to perform the initialization is a + // trivially-copyable type. + QualType QT = Initializer->getInit()->getType(); + if (QT.isTriviallyCopyableType(*Result.Context)) + return; + + const auto *RD = QT->getAsCXXRecordDecl(); + if (RD && RD->isTriviallyCopyable()) + return; + + // Diagnose when the class type has a move constructor available, but the + // ctor-initializer uses the copy constructor instead. + const CXXConstructorDecl *Candidate = nullptr; + for (const auto *Ctor : CopyCtor->getParent()->ctors()) { + if (Ctor->isMoveConstructor() && Ctor->getAccess() <= AS_protected && + !Ctor->isDeleted()) { + // The type has a move constructor that is at least accessible to the + // initializer. + // + // FIXME: Determine whether the move constructor is a viable candidate + // for the ctor-initializer, perhaps provide a fixit that suggests + // using std::move(). + Candidate = Ctor; + break; + } + } + + if (Candidate) { + // There's a move constructor candidate that the caller probably intended + // to call instead. + diag(Initializer->getSourceLocation(), + "move constructor initializes %0 by calling a copy constructor") + << (Initializer->isBaseInitializer() ? "base class" : "class member"); + diag(CopyCtor->getLocation(), "copy constructor being called", + DiagnosticIDs::Note); + diag(Candidate->getLocation(), "candidate move constructor here", + DiagnosticIDs::Note); + } +} + +void MoveConstructorInitCheck::registerPPCallbacks(CompilerInstance &Compiler) { + Inserter.reset(new IncludeInserter(Compiler.getSourceManager(), + Compiler.getLangOpts(), IncludeStyle)); + Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks()); +} + +void MoveConstructorInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", IncludeSorter::toString(IncludeStyle)); +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MoveConstructorInitCheck.h b/clang-tidy/misc/MoveConstructorInitCheck.h new file mode 100644 index 00000000..c9bac19e --- /dev/null +++ b/clang-tidy/misc/MoveConstructorInitCheck.h @@ -0,0 +1,48 @@ +//===--- MoveConstructorInitCheck.h - clang-tidy-----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONSTRUCTORINITCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONSTRUCTORINITCHECK_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" + +#include + +namespace clang { +namespace tidy { + +/// The check flags user-defined move constructors that have a ctor-initializer +/// initializing a member or base class through a copy constructor instead of a +/// move constructor. +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-move-constructor-init.html +class MoveConstructorInitCheck : public ClangTidyCheck { +public: + MoveConstructorInitCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void registerPPCallbacks(clang::CompilerInstance &Compiler) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + void + handleMoveConstructor(const ast_matchers::MatchFinder::MatchResult &Result); + void + handleParamNotMoved(const ast_matchers::MatchFinder::MatchResult &Result); + + std::unique_ptr Inserter; + const IncludeSorter::IncludeStyle IncludeStyle; + const bool UseCERTSemantics; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONSTRUCTORINITCHECK_H diff --git a/clang-tidy/misc/NewDeleteOverloadsCheck.cpp b/clang-tidy/misc/NewDeleteOverloadsCheck.cpp new file mode 100644 index 00000000..8f40dc67 --- /dev/null +++ b/clang-tidy/misc/NewDeleteOverloadsCheck.cpp @@ -0,0 +1,212 @@ +//===--- NewDeleteOverloadsCheck.cpp - clang-tidy--------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NewDeleteOverloadsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace { +AST_MATCHER(FunctionDecl, isPlacementOverload) { + bool New; + switch (Node.getOverloadedOperator()) { + default: + return false; + case OO_New: + case OO_Array_New: + New = true; + break; + case OO_Delete: + case OO_Array_Delete: + New = false; + break; + } + + // Variadic functions are always placement functions. + if (Node.isVariadic()) + return true; + + // Placement new is easy: it always has more than one parameter (the first + // parameter is always the size). If it's an overload of delete or delete[] + // that has only one parameter, it's never a placement delete. + if (New) + return Node.getNumParams() > 1; + if (Node.getNumParams() == 1) + return false; + + // Placement delete is a little more challenging. They always have more than + // one parameter with the first parameter being a pointer. However, the + // second parameter can be a size_t for sized deallocation, and that is never + // a placement delete operator. + if (Node.getNumParams() <= 1 || Node.getNumParams() > 2) + return true; + + const auto *FPT = Node.getType()->castAs(); + ASTContext &Ctx = Node.getASTContext(); + if (Ctx.getLangOpts().SizedDeallocation && + Ctx.hasSameType(FPT->getParamType(1), Ctx.getSizeType())) + return false; + + return true; +} +} // namespace + +namespace tidy { +namespace misc { + +namespace { +OverloadedOperatorKind getCorrespondingOverload(const FunctionDecl *FD) { + switch (FD->getOverloadedOperator()) { + default: break; + case OO_New: + return OO_Delete; + case OO_Delete: + return OO_New; + case OO_Array_New: + return OO_Array_Delete; + case OO_Array_Delete: + return OO_Array_New; + } + llvm_unreachable("Not an overloaded allocation operator"); +} + +const char *getOperatorName(OverloadedOperatorKind K) { + switch (K) { + default: break; + case OO_New: + return "operator new"; + case OO_Delete: + return "operator delete"; + case OO_Array_New: + return "operator new[]"; + case OO_Array_Delete: + return "operator delete[]"; + } + llvm_unreachable("Not an overloaded allocation operator"); +} + +bool areCorrespondingOverloads(const FunctionDecl *LHS, + const FunctionDecl *RHS) { + return RHS->getOverloadedOperator() == getCorrespondingOverload(LHS); +} + +bool hasCorrespondingOverloadInBaseClass(const CXXMethodDecl *MD, + const CXXRecordDecl *RD = nullptr) { + if (RD) { + // Check the methods in the given class and accessible to derived classes. + for (const auto *BMD : RD->methods()) + if (BMD->isOverloadedOperator() && BMD->getAccess() != AS_private && + areCorrespondingOverloads(MD, BMD)) + return true; + } else { + // Get the parent class of the method; we do not need to care about checking + // the methods in this class as the caller has already done that by looking + // at the declaration contexts. + RD = MD->getParent(); + } + + for (const auto &BS : RD->bases()) { + // We can't say much about a dependent base class, but to avoid false + // positives assume it can have a corresponding overload. + if (BS.getType()->isDependentType()) + return true; + if (const auto *BaseRD = BS.getType()->getAsCXXRecordDecl()) + if (hasCorrespondingOverloadInBaseClass(MD, BaseRD)) + return true; + } + + return false; +} +} // anonymous namespace + +void NewDeleteOverloadsCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // Match all operator new and operator delete overloads (including the array + // forms). Do not match implicit operators, placement operators, or + // deleted/private operators. + // + // Technically, trivially-defined operator delete seems like a reasonable + // thing to also skip. e.g., void operator delete(void *) {} + // However, I think it's more reasonable to warn in this case as the user + // should really be writing that as a deleted function. + Finder->addMatcher( + functionDecl( + unless(anyOf(isImplicit(), isPlacementOverload(), isDeleted(), + cxxMethodDecl(isPrivate()))), + anyOf(hasOverloadedOperatorName("new"), + hasOverloadedOperatorName("new[]"), + hasOverloadedOperatorName("delete"), + hasOverloadedOperatorName("delete[]"))) + .bind("func"), + this); +} + +void NewDeleteOverloadsCheck::check(const MatchFinder::MatchResult &Result) { + // Add any matches we locate to the list of things to be checked at the + // end of the translation unit. + const auto *FD = Result.Nodes.getNodeAs("func"); + const CXXRecordDecl *RD = nullptr; + if (const auto *MD = dyn_cast(FD)) + RD = MD->getParent(); + Overloads[RD].push_back(FD); +} + +void NewDeleteOverloadsCheck::onEndOfTranslationUnit() { + // Walk over the list of declarations we've found to see if there is a + // corresponding overload at the same declaration context or within a base + // class. If there is not, add the element to the list of declarations to + // diagnose. + SmallVector Diagnose; + for (const auto &RP : Overloads) { + // We don't care about the CXXRecordDecl key in the map; we use it as a way + // to shard the overloads by declaration context to reduce the algorithmic + // complexity when searching for corresponding free store functions. + for (const auto *Overload : RP.second) { + const auto *Match = + std::find_if(RP.second.begin(), RP.second.end(), + [&Overload](const FunctionDecl *FD) { + if (FD == Overload) + return false; + // If the declaration contexts don't match, we don't + // need to check any further. + if (FD->getDeclContext() != Overload->getDeclContext()) + return false; + + // Since the declaration contexts match, see whether + // the current element is the corresponding operator. + if (!areCorrespondingOverloads(Overload, FD)) + return false; + + return true; + }); + + if (Match == RP.second.end()) { + // Check to see if there is a corresponding overload in a base class + // context. If there isn't, or if the overload is not a class member + // function, then we should diagnose. + const auto *MD = dyn_cast(Overload); + if (!MD || !hasCorrespondingOverloadInBaseClass(MD)) + Diagnose.push_back(Overload); + } + } + } + + for (const auto *FD : Diagnose) + diag(FD->getLocation(), "declaration of %0 has no matching declaration " + "of '%1' at the same scope") + << FD << getOperatorName(getCorrespondingOverload(FD)); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/NewDeleteOverloadsCheck.h b/clang-tidy/misc/NewDeleteOverloadsCheck.h new file mode 100644 index 00000000..582e659d --- /dev/null +++ b/clang-tidy/misc/NewDeleteOverloadsCheck.h @@ -0,0 +1,37 @@ +//===--- NewDeleteOverloadsCheck.h - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NEWDELETEOVERLOADS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NEWDELETEOVERLOADS_H + +#include "../ClangTidy.h" +#include "llvm/ADT/SmallVector.h" +#include + +namespace clang { +namespace tidy { +namespace misc { + +class NewDeleteOverloadsCheck : public ClangTidyCheck { + std::map> Overloads; + +public: + NewDeleteOverloadsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NEWDELETEOVERLOADS_H diff --git a/clang-tidy/misc/NoexceptMoveConstructorCheck.cpp b/clang-tidy/misc/NoexceptMoveConstructorCheck.cpp new file mode 100644 index 00000000..1a22e174 --- /dev/null +++ b/clang-tidy/misc/NoexceptMoveConstructorCheck.cpp @@ -0,0 +1,72 @@ +//===--- NoexceptMoveConstructorCheck.cpp - clang-tidy---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NoexceptMoveConstructorCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +void NoexceptMoveConstructorCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++11; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus11) + return; + + Finder->addMatcher( + cxxMethodDecl(anyOf(cxxConstructorDecl(), hasOverloadedOperatorName("=")), + unless(isImplicit()), unless(isDeleted())) + .bind("decl"), + this); +} + +void NoexceptMoveConstructorCheck::check( + const MatchFinder::MatchResult &Result) { + if (const auto *Decl = Result.Nodes.getNodeAs("decl")) { + StringRef MethodType = "assignment operator"; + if (const auto *Ctor = dyn_cast(Decl)) { + if (!Ctor->isMoveConstructor()) + return; + MethodType = "constructor"; + } else if (!Decl->isMoveAssignmentOperator()) { + return; + } + + const auto *ProtoType = Decl->getType()->getAs(); + switch(ProtoType->getNoexceptSpec(*Result.Context)) { + case FunctionProtoType::NR_NoNoexcept: + diag(Decl->getLocation(), "move %0s should be marked noexcept") + << MethodType; + // FIXME: Add a fixit. + break; + case FunctionProtoType::NR_Throw: + // Don't complain about nothrow(false), but complain on nothrow(expr) + // where expr evaluates to false. + if (const Expr *E = ProtoType->getNoexceptExpr()) { + if (isa(E)) + break; + diag(E->getExprLoc(), + "noexcept specifier on the move %0 evaluates to 'false'") + << MethodType; + } + break; + case FunctionProtoType::NR_Nothrow: + case FunctionProtoType::NR_Dependent: + case FunctionProtoType::NR_BadNoexcept: + break; + } + } +} + +} // namespace tidy +} // namespace clang + diff --git a/clang-tidy/misc/NoexceptMoveConstructorCheck.h b/clang-tidy/misc/NoexceptMoveConstructorCheck.h new file mode 100644 index 00000000..db0f297f --- /dev/null +++ b/clang-tidy/misc/NoexceptMoveConstructorCheck.h @@ -0,0 +1,37 @@ +//===--- NoexceptMoveConstructorCheck.h - clang-tidy-------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NOEXCEPTMOVECONSTRUCTORCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NOEXCEPTMOVECONSTRUCTORCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// The check flags user-defined move constructors and assignment operators not +/// marked with `noexcept` or marked with `noexcept(expr)` where `expr` +/// evaluates to `false` (but is not a `false` literal itself). +/// +/// Move constructors of all the types used with STL containers, for example, +/// need to be declared `noexcept`. Otherwise STL will choose copy constructors +/// instead. The same is valid for move assignment operations. +class NoexceptMoveConstructorCheck : public ClangTidyCheck { +public: + NoexceptMoveConstructorCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NOEXCEPTMOVECONSTRUCTORCHECK_H + diff --git a/clang-tidy/misc/NonCopyableObjects.cpp b/clang-tidy/misc/NonCopyableObjects.cpp new file mode 100644 index 00000000..64c45082 --- /dev/null +++ b/clang-tidy/misc/NonCopyableObjects.cpp @@ -0,0 +1,96 @@ +//===--- NonCopyableObjects.cpp - clang-tidy-------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NonCopyableObjects.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace { +// FIXME: it would be good to make a list that is also user-configurable so that +// users can add their own elements to the list. However, it may require some +// extra thought since POSIX types and FILE types are usable in different ways. +bool isPOSIXTypeName(StringRef ClassName) { + static const char *const TypeNames[] = { + "::pthread_cond_t", + "::pthread_mutex_t", + "pthread_cond_t", + "pthread_mutex_t" + }; + return std::binary_search(std::begin(TypeNames), std::end(TypeNames), + ClassName); +} + +bool isFILETypeName(StringRef ClassName) { + static const char *const TypeNames[] = { + "::FILE", + "FILE", + "std::FILE" + }; + return std::binary_search(std::begin(TypeNames), std::end(TypeNames), + ClassName); +} + +AST_MATCHER(NamedDecl, isFILEType) { + return isFILETypeName(Node.getQualifiedNameAsString()); +} + +AST_MATCHER(NamedDecl, isPOSIXType) { + return isPOSIXTypeName(Node.getQualifiedNameAsString()); +} +} // namespace + +namespace tidy { +void NonCopyableObjectsCheck::registerMatchers(MatchFinder *Finder) { + // There are two ways to get into trouble with objects like FILE *: + // dereferencing the pointer type to be a non-pointer type, and declaring + // the type as a non-pointer type in the first place. While the declaration + // itself could technically be well-formed in the case where the type is not + // an opaque type, it's highly suspicious behavior. + // + // POSIX types are a bit different in that it's reasonable to declare a + // non-pointer variable or data member of the type, but it is not reasonable + // to dereference a pointer to the type, or declare a parameter of non-pointer + // type. + auto BadFILEType = hasType(namedDecl(isFILEType()).bind("type_decl")); + auto BadPOSIXType = hasType(namedDecl(isPOSIXType()).bind("type_decl")); + auto BadEitherType = anyOf(BadFILEType, BadPOSIXType); + + Finder->addMatcher( + namedDecl(anyOf(varDecl(BadFILEType), fieldDecl(BadFILEType))) + .bind("decl"), + this); + Finder->addMatcher(parmVarDecl(BadPOSIXType).bind("decl"), this); + Finder->addMatcher( + expr(unaryOperator(hasOperatorName("*"), BadEitherType)).bind("expr"), + this); +} + +void NonCopyableObjectsCheck::check(const MatchFinder::MatchResult &Result) { + const auto *D = Result.Nodes.getNodeAs("decl"); + const auto *BD = Result.Nodes.getNodeAs("type_decl"); + const auto *E = Result.Nodes.getNodeAs("expr"); + + if (D && BD) + diag(D->getLocation(), "'%0' declared as type '%1', which is unsafe to copy" + "; did you mean '%1 *'?") + << D->getName() << BD->getName(); + else if (E) + diag(E->getExprLoc(), + "expression has opaque data structure type '%0'; type should only be " + "used as a pointer and not dereferenced") + << BD->getName(); +} + +} // namespace tidy +} // namespace clang + diff --git a/clang-tidy/misc/NonCopyableObjects.h b/clang-tidy/misc/NonCopyableObjects.h new file mode 100644 index 00000000..c812e500 --- /dev/null +++ b/clang-tidy/misc/NonCopyableObjects.h @@ -0,0 +1,31 @@ +//===--- NonCopyableObjects.h - clang-tidy-----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NONCOPYABLEOBJECTS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NONCOPYABLEOBJECTS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// The check flags dereferences and non-pointer declarations of objects that +/// are not meant to be passed by value, such as C FILE objects. +class NonCopyableObjectsCheck : public ClangTidyCheck { +public: + NonCopyableObjectsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NONCOPYABLEOBJECTS_H diff --git a/clang-tidy/misc/SizeofContainerCheck.cpp b/clang-tidy/misc/SizeofContainerCheck.cpp new file mode 100644 index 00000000..cd264268 --- /dev/null +++ b/clang-tidy/misc/SizeofContainerCheck.cpp @@ -0,0 +1,48 @@ +//===--- SizeofContainerCheck.cpp - clang-tidy-----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SizeofContainerCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +void SizeofContainerCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + expr(unless(isInTemplateInstantiation()), + expr(sizeOfExpr(has( + expr(hasType(hasCanonicalType(hasDeclaration(cxxRecordDecl( + matchesName("^(::std::|::string)"), + unless(matchesName("^::std::(bitset|array)$")), + hasMethod(cxxMethodDecl(hasName("size"), isPublic(), + isConst())))))))))) + .bind("sizeof"), + // Ignore ARRAYSIZE() pattern. + unless(hasAncestor(binaryOperator( + anyOf(hasOperatorName("/"), hasOperatorName("%")), + hasLHS(ignoringParenCasts(sizeOfExpr(expr()))), + hasRHS(ignoringParenCasts(equalsBoundNode("sizeof"))))))), + this); +} + +void SizeofContainerCheck::check(const MatchFinder::MatchResult &Result) { + const auto *SizeOf = + Result.Nodes.getNodeAs("sizeof"); + + auto Diag = + diag(SizeOf->getLocStart(), "sizeof() doesn't return the size of the " + "container; did you mean .size()?"); +} + +} // namespace tidy +} // namespace clang + diff --git a/clang-tidy/misc/SizeofContainerCheck.h b/clang-tidy/misc/SizeofContainerCheck.h new file mode 100644 index 00000000..eae5a063 --- /dev/null +++ b/clang-tidy/misc/SizeofContainerCheck.h @@ -0,0 +1,35 @@ +//===--- SizeofContainerCheck.h - clang-tidy---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_CONTAINER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_CONTAINER_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// Find usages of sizeof on expressions of STL container types. Most likely the +/// user wanted to use `.size()` instead. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-sizeof-container.html +class SizeofContainerCheck : public ClangTidyCheck { +public: + SizeofContainerCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_CONTAINER_H + diff --git a/clang-tidy/misc/StaticAssertCheck.cpp b/clang-tidy/misc/StaticAssertCheck.cpp new file mode 100644 index 00000000..c9793bc3 --- /dev/null +++ b/clang-tidy/misc/StaticAssertCheck.cpp @@ -0,0 +1,168 @@ +//===--- StaticAssertCheck.cpp - clang-tidy -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StaticAssertCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +StaticAssertCheck::StaticAssertCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + +void StaticAssertCheck::registerMatchers(MatchFinder *Finder) { + // This checker only makes sense for languages that have static assertion + // capabilities: C++11 and C11. + if (!(getLangOpts().CPlusPlus11 || getLangOpts().C11)) + return; + + auto IsAlwaysFalse = expr(ignoringParenImpCasts( + expr(anyOf(cxxBoolLiteral(equals(false)), integerLiteral(equals(0)), + cxxNullPtrLiteralExpr(), gnuNullExpr())) + .bind("isAlwaysFalse"))); + auto IsAlwaysFalseWithCast = ignoringParenImpCasts(anyOf( + IsAlwaysFalse, cStyleCastExpr(has(IsAlwaysFalse)).bind("castExpr"))); + auto AssertExprRoot = anyOf( + binaryOperator( + anyOf(hasOperatorName("&&"), hasOperatorName("==")), + hasEitherOperand(ignoringImpCasts(stringLiteral().bind("assertMSG"))), + anyOf(binaryOperator(hasEitherOperand(IsAlwaysFalseWithCast)), + anything())) + .bind("assertExprRoot"), + IsAlwaysFalse); + auto NonConstexprFunctionCall = + callExpr(hasDeclaration(functionDecl(unless(isConstexpr())))); + auto AssertCondition = + expr( + anyOf(expr(ignoringParenCasts(anyOf( + AssertExprRoot, unaryOperator(hasUnaryOperand( + ignoringParenCasts(AssertExprRoot)))))), + anything()), + unless(findAll(NonConstexprFunctionCall))) + .bind("condition"); + auto Condition = + anyOf(ignoringParenImpCasts(callExpr( + hasDeclaration(functionDecl(hasName("__builtin_expect"))), + hasArgument(0, AssertCondition))), + AssertCondition); + + Finder->addMatcher(stmt(anyOf(conditionalOperator(hasCondition(Condition)), + ifStmt(hasCondition(Condition))), + unless(isInTemplateInstantiation())) + .bind("condStmt"), + this); +} + +void StaticAssertCheck::check(const MatchFinder::MatchResult &Result) { + const ASTContext *ASTCtx = Result.Context; + const LangOptions &Opts = ASTCtx->getLangOpts(); + const SourceManager &SM = ASTCtx->getSourceManager(); + const auto *CondStmt = Result.Nodes.getNodeAs("condStmt"); + const auto *Condition = Result.Nodes.getNodeAs("condition"); + const auto *IsAlwaysFalse = Result.Nodes.getNodeAs("isAlwaysFalse"); + const auto *AssertMSG = Result.Nodes.getNodeAs("assertMSG"); + const auto *AssertExprRoot = + Result.Nodes.getNodeAs("assertExprRoot"); + const auto *CastExpr = Result.Nodes.getNodeAs("castExpr"); + SourceLocation AssertExpansionLoc = CondStmt->getLocStart(); + + if (!AssertExpansionLoc.isValid() || !AssertExpansionLoc.isMacroID()) + return; + + StringRef MacroName = + Lexer::getImmediateMacroName(AssertExpansionLoc, SM, Opts); + + if (MacroName != "assert" || Condition->isValueDependent() || + Condition->isTypeDependent() || Condition->isInstantiationDependent() || + !Condition->isEvaluatable(*ASTCtx)) + return; + + // False literal is not the result of macro expansion. + if (IsAlwaysFalse && (!CastExpr || CastExpr->getType()->isPointerType())) { + SourceLocation FalseLiteralLoc = + SM.getImmediateSpellingLoc(IsAlwaysFalse->getExprLoc()); + if (!FalseLiteralLoc.isMacroID()) + return; + + StringRef FalseMacroName = + Lexer::getImmediateMacroName(FalseLiteralLoc, SM, Opts); + if (FalseMacroName.compare_lower("false") == 0 || + FalseMacroName.compare_lower("null") == 0) + return; + } + + SourceLocation AssertLoc = SM.getImmediateMacroCallerLoc(AssertExpansionLoc); + + SmallVector FixItHints; + SourceLocation LastParenLoc; + if (AssertLoc.isValid() && !AssertLoc.isMacroID() && + (LastParenLoc = getLastParenLoc(ASTCtx, AssertLoc)).isValid()) { + FixItHints.push_back( + FixItHint::CreateReplacement(SourceRange(AssertLoc), "static_assert")); + + std::string StaticAssertMSG = ", \"\""; + if (AssertExprRoot) { + FixItHints.push_back(FixItHint::CreateRemoval( + SourceRange(AssertExprRoot->getOperatorLoc()))); + FixItHints.push_back(FixItHint::CreateRemoval( + SourceRange(AssertMSG->getLocStart(), AssertMSG->getLocEnd()))); + StaticAssertMSG = (Twine(", \"") + AssertMSG->getString() + "\"").str(); + } + + FixItHints.push_back( + FixItHint::CreateInsertion(LastParenLoc, StaticAssertMSG)); + } + + diag(AssertLoc, "found assert() that could be replaced by static_assert()") + << FixItHints; +} + +SourceLocation StaticAssertCheck::getLastParenLoc(const ASTContext *ASTCtx, + SourceLocation AssertLoc) { + const LangOptions &Opts = ASTCtx->getLangOpts(); + const SourceManager &SM = ASTCtx->getSourceManager(); + + llvm::MemoryBuffer *Buffer = SM.getBuffer(SM.getFileID(AssertLoc)); + if (!Buffer) + return SourceLocation(); + + const char *BufferPos = SM.getCharacterData(AssertLoc); + + Token Token; + Lexer Lexer(SM.getLocForStartOfFile(SM.getFileID(AssertLoc)), Opts, + Buffer->getBufferStart(), BufferPos, Buffer->getBufferEnd()); + + // assert first left parenthesis + if (Lexer.LexFromRawLexer(Token) || Lexer.LexFromRawLexer(Token) || + !Token.is(tok::l_paren)) + return SourceLocation(); + + unsigned int ParenCount = 1; + while (ParenCount && !Lexer.LexFromRawLexer(Token)) { + if (Token.is(tok::l_paren)) + ++ParenCount; + else if (Token.is(tok::r_paren)) + --ParenCount; + } + + return Token.getLocation(); +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/StaticAssertCheck.h b/clang-tidy/misc/StaticAssertCheck.h new file mode 100644 index 00000000..b3b1cb5f --- /dev/null +++ b/clang-tidy/misc/StaticAssertCheck.h @@ -0,0 +1,39 @@ +//===--- StaticAssertCheck.h - clang-tidy -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STATICASSERTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STATICASSERTCHECK_H + +#include "../ClangTidy.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace tidy { + +/// Replaces `assert()` with `static_assert()` if the condition is evaluatable +/// at compile time. +/// +/// The condition of `static_assert()` is evaluated at compile time which is +/// safer and more efficient. +class StaticAssertCheck : public ClangTidyCheck { +public: + StaticAssertCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + SourceLocation getLastParenLoc(const ASTContext *ASTCtx, + SourceLocation AssertLoc); +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STATICASSERTCHECK_H diff --git a/clang-tidy/misc/StringIntegerAssignmentCheck.cpp b/clang-tidy/misc/StringIntegerAssignmentCheck.cpp new file mode 100644 index 00000000..c9ed466c --- /dev/null +++ b/clang-tidy/misc/StringIntegerAssignmentCheck.cpp @@ -0,0 +1,85 @@ +//===--- StringIntegerAssignmentCheck.cpp - clang-tidy---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StringIntegerAssignmentCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +void StringIntegerAssignmentCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + Finder->addMatcher( + cxxOperatorCallExpr( + anyOf(hasOverloadedOperatorName("="), + hasOverloadedOperatorName("+=")), + callee(cxxMethodDecl(ofClass(classTemplateSpecializationDecl( + hasName("::std::basic_string"), + hasTemplateArgument(0, refersToType(qualType().bind("type"))))))), + hasArgument(1, + ignoringImpCasts(expr(hasType(isInteger()), + unless(hasType(isAnyCharacter()))) + .bind("expr"))), + unless(isInTemplateInstantiation())), + this); +} + +void StringIntegerAssignmentCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Argument = Result.Nodes.getNodeAs("expr"); + SourceLocation Loc = Argument->getLocStart(); + + auto Diag = + diag(Loc, "an integer is interpreted as a character code when assigning " + "it to a string; if this is intended, cast the integer to the " + "appropriate character type; if you want a string " + "representation, use the appropriate conversion facility"); + + if (Loc.isMacroID()) + return; + + auto CharType = *Result.Nodes.getNodeAs("type"); + bool IsWideCharType = CharType->isWideCharType(); + if (!CharType->isCharType() && !IsWideCharType) + return; + bool IsOneDigit = false; + bool IsLiteral = false; + if (const auto *Literal = dyn_cast(Argument)) { + IsOneDigit = Literal->getValue().getLimitedValue() < 10; + IsLiteral = true; + } + + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + Argument->getLocEnd(), 0, *Result.SourceManager, + Result.Context->getLangOpts()); + if (IsOneDigit) { + Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "L'" : "'") + << FixItHint::CreateInsertion(EndLoc, "'"); + return; + } + if (IsLiteral) { + Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "L\"" : "\"") + << FixItHint::CreateInsertion(EndLoc, "\""); + return; + } + + if (getLangOpts().CPlusPlus11) { + Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "std::to_wstring(" + : "std::to_string(") + << FixItHint::CreateInsertion(EndLoc, ")"); + } +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/StringIntegerAssignmentCheck.h b/clang-tidy/misc/StringIntegerAssignmentCheck.h new file mode 100644 index 00000000..072b96aa --- /dev/null +++ b/clang-tidy/misc/StringIntegerAssignmentCheck.h @@ -0,0 +1,34 @@ +//===--- StringIntegerAssignmentCheck.h - clang-tidy-------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_INTEGER_ASSIGNMENT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_INTEGER_ASSIGNMENT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// Finds instances where an integer is assigned to a string. +/// +/// For more details see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-string-assignment.html +class StringIntegerAssignmentCheck : public ClangTidyCheck { +public: + StringIntegerAssignmentCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_INTEGER_ASSIGNMENT_H + diff --git a/clang-tidy/misc/SwappedArgumentsCheck.cpp b/clang-tidy/misc/SwappedArgumentsCheck.cpp new file mode 100644 index 00000000..f6f52edc --- /dev/null +++ b/clang-tidy/misc/SwappedArgumentsCheck.cpp @@ -0,0 +1,127 @@ +//===--- SwappedArgumentsCheck.cpp - clang-tidy ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SwappedArgumentsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallPtrSet.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void SwappedArgumentsCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(callExpr().bind("call"), this); +} + +/// \brief Look through lvalue to rvalue and nop casts. This filters out +/// implicit conversions that have no effect on the input but block our view for +/// other implicit casts. +static const Expr *ignoreNoOpCasts(const Expr *E) { + if (auto *Cast = dyn_cast(E)) + if (Cast->getCastKind() == CK_LValueToRValue || + Cast->getCastKind() == CK_NoOp) + return ignoreNoOpCasts(Cast->getSubExpr()); + return E; +} + +/// \brief Restrict the warning to implicit casts that are most likely +/// accidental. User defined or integral conversions fit in this category, +/// lvalue to rvalue or derived to base does not. +static bool isImplicitCastCandidate(const CastExpr *Cast) { + return Cast->getCastKind() == CK_UserDefinedConversion || + Cast->getCastKind() == CK_FloatingToBoolean || + Cast->getCastKind() == CK_FloatingToIntegral || + Cast->getCastKind() == CK_IntegralToBoolean || + Cast->getCastKind() == CK_IntegralToFloating || + Cast->getCastKind() == CK_MemberPointerToBoolean || + Cast->getCastKind() == CK_PointerToBoolean; +} + +/// \brief Get a StringRef representing a SourceRange. +static StringRef getAsString(const MatchFinder::MatchResult &Result, + SourceRange R) { + const SourceManager &SM = *Result.SourceManager; + // Don't even try to resolve macro or include contraptions. Not worth emitting + // a fixit for. + if (R.getBegin().isMacroID() || + !SM.isWrittenInSameFile(R.getBegin(), R.getEnd())) + return StringRef(); + + const char *Begin = SM.getCharacterData(R.getBegin()); + const char *End = SM.getCharacterData(Lexer::getLocForEndOfToken( + R.getEnd(), 0, SM, Result.Context->getLangOpts())); + + return StringRef(Begin, End - Begin); +} + +void SwappedArgumentsCheck::check(const MatchFinder::MatchResult &Result) { + auto *Call = Result.Nodes.getStmtAs("call"); + + llvm::SmallPtrSet UsedArgs; + for (unsigned I = 1, E = Call->getNumArgs(); I < E; ++I) { + const Expr *LHS = Call->getArg(I - 1); + const Expr *RHS = Call->getArg(I); + + // Only need to check RHS, as LHS has already been covered. We don't want to + // emit two warnings for a single argument. + if (UsedArgs.count(RHS)) + continue; + + const auto *LHSCast = dyn_cast(ignoreNoOpCasts(LHS)); + const auto *RHSCast = dyn_cast(ignoreNoOpCasts(RHS)); + + // Look if this is a potentially swapped argument pair. First look for + // implicit casts. + if (!LHSCast || !RHSCast || !isImplicitCastCandidate(LHSCast) || + !isImplicitCastCandidate(RHSCast)) + continue; + + // If the types that go into the implicit casts match the types of the other + // argument in the declaration there is a high probability that the + // arguments were swapped. + // TODO: We could make use of the edit distance between the argument name + // and the name of the passed variable in addition to this type based + // heuristic. + const Expr *LHSFrom = ignoreNoOpCasts(LHSCast->getSubExpr()); + const Expr *RHSFrom = ignoreNoOpCasts(RHSCast->getSubExpr()); + if (LHS->getType() == RHS->getType() || + LHS->getType() != RHSFrom->getType() || + RHS->getType() != LHSFrom->getType()) + continue; + + // Emit a warning and fix-its that swap the arguments. + SourceRange LHSRange = LHS->getSourceRange(), + RHSRange = RHS->getSourceRange(); + auto D = + diag(Call->getLocStart(), "argument with implicit conversion from %0 " + "to %1 followed by argument converted from " + "%2 to %3, potentially swapped arguments.") + << LHS->getType() << LHSFrom->getType() << RHS->getType() + << RHSFrom->getType() << LHSRange << RHSRange; + + StringRef RHSString = getAsString(Result, RHSRange); + StringRef LHSString = getAsString(Result, LHSRange); + if (!LHSString.empty() && !RHSString.empty()) { + D << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(LHSRange), RHSString) + << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(RHSRange), LHSString); + } + + // Remember that we emitted a warning for this argument. + UsedArgs.insert(RHSCast); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/SwappedArgumentsCheck.h b/clang-tidy/misc/SwappedArgumentsCheck.h new file mode 100644 index 00000000..ed326619 --- /dev/null +++ b/clang-tidy/misc/SwappedArgumentsCheck.h @@ -0,0 +1,32 @@ +//===--- SwappedArgumentsCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SWAPPEDARGUMENTSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SWAPPEDARGUMENTSCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds potentially swapped arguments by looking at implicit conversions. +class SwappedArgumentsCheck : public ClangTidyCheck { +public: + SwappedArgumentsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SWAPPEDARGUMENTSCHECK_H diff --git a/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp b/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp new file mode 100644 index 00000000..3fbbe8ea --- /dev/null +++ b/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp @@ -0,0 +1,159 @@ +//===--- ThrowByValueCatchByReferenceCheck.cpp - clang-tidy----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ThrowByValueCatchByReferenceCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/AST/OperationKinds.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +ThrowByValueCatchByReferenceCheck::ThrowByValueCatchByReferenceCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + CheckAnonymousTemporaries(Options.get("CheckThrowTemporaries", true)) {} + +void ThrowByValueCatchByReferenceCheck::registerMatchers(MatchFinder *Finder) { + // This is a C++ only check thus we register the matchers only for C++ + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(cxxThrowExpr().bind("throw"), this); + Finder->addMatcher(cxxCatchStmt().bind("catch"), this); +} + +void ThrowByValueCatchByReferenceCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "CheckThrowTemporaries", true); +} + +void ThrowByValueCatchByReferenceCheck::check( + const MatchFinder::MatchResult &Result) { + diagnoseThrowLocations(Result.Nodes.getNodeAs("throw")); + diagnoseCatchLocations(Result.Nodes.getNodeAs("catch"), + *Result.Context); +} + +bool ThrowByValueCatchByReferenceCheck::isFunctionParameter( + const DeclRefExpr *declRefExpr) { + return isa(declRefExpr->getDecl()); +} + +bool ThrowByValueCatchByReferenceCheck::isCatchVariable( + const DeclRefExpr *declRefExpr) { + auto *valueDecl = declRefExpr->getDecl(); + if (auto *varDecl = dyn_cast(valueDecl)) + return varDecl->isExceptionVariable(); + return false; +} + +bool ThrowByValueCatchByReferenceCheck::isFunctionOrCatchVar( + const DeclRefExpr *declRefExpr) { + return isFunctionParameter(declRefExpr) || isCatchVariable(declRefExpr); +} + +void ThrowByValueCatchByReferenceCheck::diagnoseThrowLocations( + const CXXThrowExpr *throwExpr) { + if (!throwExpr) + return; + auto *subExpr = throwExpr->getSubExpr(); + if (!subExpr) + return; + auto qualType = subExpr->getType(); + if (qualType->isPointerType()) { + // The code is throwing a pointer. + // In case it is strng literal, it is safe and we return. + auto *inner = subExpr->IgnoreParenImpCasts(); + if (isa(inner)) + return; + // If it's a variable from a catch statement, we return as well. + auto *declRef = dyn_cast(inner); + if (declRef && isCatchVariable(declRef)) { + return; + } + diag(subExpr->getLocStart(), "throw expression throws a pointer; it should " + "throw a non-pointer value instead"); + } + // If the throw statement does not throw by pointer then it throws by value + // which is ok. + // There are addition checks that emit diagnosis messages if the thrown value + // is not an RValue. See: + // https://www.securecoding.cert.org/confluence/display/cplusplus/ERR09-CPP.+Throw+anonymous+temporaries + // This behavior can be influenced by an option. + + // If we encounter a CXXThrowExpr, we move through all casts until you either + // encounter a DeclRefExpr or a CXXConstructExpr. + // If it's a DeclRefExpr, we emit a message if the referenced variable is not + // a catch variable or function parameter. + // When encountering a CopyOrMoveConstructor: emit message if after casts, + // the expression is a LValue + if (CheckAnonymousTemporaries) { + bool emit = false; + auto *currentSubExpr = subExpr->IgnoreImpCasts(); + const DeclRefExpr *variableReference = + dyn_cast(currentSubExpr); + const CXXConstructExpr *constructorCall = + dyn_cast(currentSubExpr); + // If we have a DeclRefExpr, we flag for emitting a diagnosis message in + // case the referenced variable is neither a function parameter nor a + // variable declared in the catch statement. + if (variableReference) + emit = !isFunctionOrCatchVar(variableReference); + else if (constructorCall && + constructorCall->getConstructor()->isCopyOrMoveConstructor()) { + // If we have a copy / move construction, we emit a diagnosis message if + // the object that we copy construct from is neither a function parameter + // nor a variable declared in a catch statement + auto argIter = + constructorCall + ->arg_begin(); // there's only one for copy constructors + auto *currentSubExpr = (*argIter)->IgnoreImpCasts(); + if (currentSubExpr->isLValue()) { + if (auto *tmp = dyn_cast(currentSubExpr)) + emit = !isFunctionOrCatchVar(tmp); + else if (isa(currentSubExpr)) + emit = true; + } + } + if (emit) + diag(subExpr->getLocStart(), + "throw expression should throw anonymous temporary values instead"); + } +} + +void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations( + const CXXCatchStmt *catchStmt, ASTContext &context) { + const char *diagMsgCatchReference = "catch handler catches a pointer value; " + "should throw a non-pointer value and " + "catch by reference instead"; + if (!catchStmt) + return; + auto caughtType = catchStmt->getCaughtType(); + if (caughtType.isNull()) + return; + auto *varDecl = catchStmt->getExceptionDecl(); + if (const auto *PT = caughtType.getCanonicalType()->getAs()) { + // We do not diagnose when catching pointer to strings since we also allow + // throwing string literals. + if (!PT->getPointeeType()->isAnyCharacterType()) + diag(varDecl->getLocStart(), diagMsgCatchReference); + } else if (!caughtType->isReferenceType()) { + // If it's not a pointer and not a reference then it must be thrown "by + // value". In this case we should emit a diagnosis message unless the type + // is trivial. + if (!caughtType.isTrivialType(context)) + diag(varDecl->getLocStart(), diagMsgCatchReference); + } +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.h b/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.h new file mode 100644 index 00000000..70300318 --- /dev/null +++ b/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.h @@ -0,0 +1,49 @@ +//===--- ThrowByValueCatchByReferenceCheck.h - clang-tidy--------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_THROW_BY_VALUE_CATCH_BY_REFERENCE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_THROW_BY_VALUE_CATCH_BY_REFERENCE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +///\brief checks for locations that do not throw by value +// or catch by reference. +// The check is C++ only. It checks that all throw locations +// throw by value and not by pointer. Additionally it +// contains an option ("CheckThrowTemporaries", default value "true") that +// checks that thrown objects are anonymous temporaries. It is also +// acceptable for this check to throw string literals. +// This test checks that exceptions are caught by reference +// and not by value or pointer. It will not warn when catching +// pointer to char, wchar_t, char16_t or char32_t. This is +// due to not warning on throwing string literals. +class ThrowByValueCatchByReferenceCheck : public ClangTidyCheck { +public: + ThrowByValueCatchByReferenceCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void diagnoseThrowLocations(const CXXThrowExpr *throwExpr); + void diagnoseCatchLocations(const CXXCatchStmt *catchStmt, + ASTContext &context); + bool isFunctionParameter(const DeclRefExpr *declRefExpr); + bool isCatchVariable(const DeclRefExpr *declRefExpr); + bool isFunctionOrCatchVar(const DeclRefExpr *declRefExpr); + const bool CheckAnonymousTemporaries; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_THROW_BY_VALUE_CATCH_BY_REFERENCE_H diff --git a/clang-tidy/misc/UndelegatedConstructor.cpp b/clang-tidy/misc/UndelegatedConstructor.cpp new file mode 100644 index 00000000..90135e35 --- /dev/null +++ b/clang-tidy/misc/UndelegatedConstructor.cpp @@ -0,0 +1,84 @@ +//===--- UndelegatedConstructor.cpp - clang-tidy --------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UndelegatedConstructor.h" +#include "clang/AST/ASTContext.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { + +namespace { +AST_MATCHER_P(Stmt, ignoringTemporaryExpr, + ast_matchers::internal::Matcher, InnerMatcher) { + const Stmt *E = &Node; + for (;;) { + // Temporaries with non-trivial dtors. + if (const auto *EWC = dyn_cast(E)) + E = EWC->getSubExpr(); + // Temporaries with zero or more than two ctor arguments. + else if (const auto *BTE = dyn_cast(E)) + E = BTE->getSubExpr(); + // Temporaries with exactly one ctor argument. + else if (const auto *FCE = dyn_cast(E)) + E = FCE->getSubExpr(); + else + break; + } + + return InnerMatcher.matches(*E, Finder, Builder); +} + +// Finds a node if it's a base of an already bound node. +AST_MATCHER_P(CXXRecordDecl, baseOfBoundNode, std::string, ID) { + return Builder->removeBindings( + [&](const ast_matchers::internal::BoundNodesMap &Nodes) { + const auto *Derived = Nodes.getNodeAs(ID); + return Derived != &Node && !Derived->isDerivedFrom(&Node); + }); +} +} // namespace + +namespace tidy { +namespace misc { + +void UndelegatedConstructorCheck::registerMatchers(MatchFinder *Finder) { + // We look for calls to constructors of the same type in constructors. To do + // this we have to look through a variety of nodes that occur in the path, + // depending on the type's destructor and the number of arguments on the + // constructor call, this is handled by ignoringTemporaryExpr. Ignore template + // instantiations to reduce the number of duplicated warnings. + // + // Only register the matchers for C++11; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus11) + return; + + Finder->addMatcher( + compoundStmt( + hasParent( + cxxConstructorDecl(ofClass(cxxRecordDecl().bind("parent")))), + forEach(ignoringTemporaryExpr( + cxxConstructExpr(hasDeclaration(cxxConstructorDecl(ofClass( + cxxRecordDecl(baseOfBoundNode("parent")))))) + .bind("construct"))), + unless(isInTemplateInstantiation())), + this); +} + +void UndelegatedConstructorCheck::check(const MatchFinder::MatchResult &Result) { + const auto *E = Result.Nodes.getStmtAs("construct"); + diag(E->getLocStart(), "did you intend to call a delegated constructor? " + "A temporary object is created here instead"); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UndelegatedConstructor.h b/clang-tidy/misc/UndelegatedConstructor.h new file mode 100644 index 00000000..bba36ed4 --- /dev/null +++ b/clang-tidy/misc/UndelegatedConstructor.h @@ -0,0 +1,36 @@ +//===--- UndelegatedConstructor.h - clang-tidy ------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNDELEGATEDCONSTRUCTOR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNDELEGATEDCONSTRUCTOR_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds creation of temporary objects in constructors that look like a +/// function call to another constructor of the same class. +/// +/// The user most likely meant to use a delegating constructor or base class +/// initializer. +class UndelegatedConstructorCheck : public ClangTidyCheck { +public: + UndelegatedConstructorCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNDELEGATEDCONSTRUCTOR_H diff --git a/clang-tidy/misc/UniqueptrResetReleaseCheck.cpp b/clang-tidy/misc/UniqueptrResetReleaseCheck.cpp new file mode 100644 index 00000000..32916d13 --- /dev/null +++ b/clang-tidy/misc/UniqueptrResetReleaseCheck.cpp @@ -0,0 +1,137 @@ +//===--- UniqueptrResetReleaseCheck.cpp - clang-tidy ----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UniqueptrResetReleaseCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void UniqueptrResetReleaseCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++11; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus11) + return; + + Finder->addMatcher( + cxxMemberCallExpr( + on(expr().bind("left")), callee(memberExpr().bind("reset_member")), + callee( + cxxMethodDecl(hasName("reset"), + ofClass(cxxRecordDecl(hasName("::std::unique_ptr"), + decl().bind("left_class"))))), + has(cxxMemberCallExpr( + on(expr().bind("right")), + callee(memberExpr().bind("release_member")), + callee(cxxMethodDecl( + hasName("release"), + ofClass(cxxRecordDecl(hasName("::std::unique_ptr"), + decl().bind("right_class")))))))) + .bind("reset_call"), + this); +} + +namespace { +const Type *getDeleterForUniquePtr(const MatchFinder::MatchResult &Result, + StringRef ID) { + const auto *Class = + Result.Nodes.getNodeAs(ID); + if (!Class) + return nullptr; + auto DeleterArgument = Class->getTemplateArgs()[1]; + if (DeleterArgument.getKind() != TemplateArgument::Type) + return nullptr; + return DeleterArgument.getAsType().getTypePtr(); +} + +bool areDeletersCompatible(const MatchFinder::MatchResult &Result) { + const Type *LeftDeleterType = getDeleterForUniquePtr(Result, "left_class"); + const Type *RightDeleterType = getDeleterForUniquePtr(Result, "right_class"); + + if (LeftDeleterType->getUnqualifiedDesugaredType() == + RightDeleterType->getUnqualifiedDesugaredType()) { + // Same type. We assume they are compatible. + // This check handles the case where the deleters are function pointers. + return true; + } + + const CXXRecordDecl *LeftDeleter = LeftDeleterType->getAsCXXRecordDecl(); + const CXXRecordDecl *RightDeleter = RightDeleterType->getAsCXXRecordDecl(); + if (!LeftDeleter || !RightDeleter) + return false; + + if (LeftDeleter->getCanonicalDecl() == RightDeleter->getCanonicalDecl()) { + // Same class. We assume they are compatible. + return true; + } + + const auto *LeftAsTemplate = + dyn_cast(LeftDeleter); + const auto *RightAsTemplate = + dyn_cast(RightDeleter); + if (LeftAsTemplate && RightAsTemplate && + LeftAsTemplate->getSpecializedTemplate() == + RightAsTemplate->getSpecializedTemplate()) { + // They are different instantiations of the same template. We assume they + // are compatible. + // This handles things like std::default_delete vs. + // std::default_delete. + return true; + } + return false; +} + +} // namespace + +void UniqueptrResetReleaseCheck::check(const MatchFinder::MatchResult &Result) { + if (!areDeletersCompatible(Result)) + return; + + const auto *ResetMember = Result.Nodes.getNodeAs("reset_member"); + const auto *ReleaseMember = + Result.Nodes.getNodeAs("release_member"); + const auto *Right = Result.Nodes.getNodeAs("right"); + const auto *Left = Result.Nodes.getNodeAs("left"); + const auto *ResetCall = + Result.Nodes.getNodeAs("reset_call"); + + std::string LeftText = clang::Lexer::getSourceText( + CharSourceRange::getTokenRange(Left->getSourceRange()), + *Result.SourceManager, Result.Context->getLangOpts()); + std::string RightText = clang::Lexer::getSourceText( + CharSourceRange::getTokenRange(Right->getSourceRange()), + *Result.SourceManager, Result.Context->getLangOpts()); + + if (ResetMember->isArrow()) + LeftText = "*" + LeftText; + if (ReleaseMember->isArrow()) + RightText = "*" + RightText; + std::string DiagText; + // Even if x was rvalue, *x is not rvalue anymore. + if (!Right->isRValue() || ReleaseMember->isArrow()) { + RightText = "std::move(" + RightText + ")"; + DiagText = "prefer ptr1 = std::move(ptr2) over ptr1.reset(ptr2.release())"; + } else { + DiagText = + "prefer ptr = ReturnUnique() over ptr.reset(ReturnUnique().release())"; + } + std::string NewText = LeftText + " = " + RightText; + + diag(ResetMember->getExprLoc(), DiagText) + << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(ResetCall->getSourceRange()), NewText); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UniqueptrResetReleaseCheck.h b/clang-tidy/misc/UniqueptrResetReleaseCheck.h new file mode 100644 index 00000000..cf18a5a5 --- /dev/null +++ b/clang-tidy/misc/UniqueptrResetReleaseCheck.h @@ -0,0 +1,43 @@ +//===--- UniqueptrResetReleaseCheck.h - clang-tidy --------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNIQUEPTRRESETRELEASECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNIQUEPTRRESETRELEASECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Find and replace `unique_ptr::reset(release())` with `std::move()`. +/// +/// Example: +/// +/// \code +/// std::unique_ptr x, y; +/// x.reset(y.release()); -> x = std::move(y); +/// \endcode +/// +/// If `y` is already rvalue, `std::move()` is not added. `x` and `y` can also +/// be `std::unique_ptr*`. +class UniqueptrResetReleaseCheck : public ClangTidyCheck { +public: + UniqueptrResetReleaseCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNIQUEPTRRESETRELEASECHECK_H diff --git a/clang-tidy/misc/UnusedAliasDeclsCheck.cpp b/clang-tidy/misc/UnusedAliasDeclsCheck.cpp new file mode 100644 index 00000000..3af988a6 --- /dev/null +++ b/clang-tidy/misc/UnusedAliasDeclsCheck.cpp @@ -0,0 +1,63 @@ +//===--- UnusedAliasDeclsCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnusedAliasDeclsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +void UnusedAliasDeclsCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++11; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus11) + return; + + // We cannot do anything about headers (yet), as the alias declarations + // used in one header could be used by some other translation unit. + Finder->addMatcher(namespaceAliasDecl(isExpansionInMainFile()).bind("alias"), + this); + Finder->addMatcher(nestedNameSpecifier().bind("nns"), this); +} + +void UnusedAliasDeclsCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *AliasDecl = Result.Nodes.getNodeAs("alias")) { + FoundDecls[AliasDecl] = CharSourceRange::getCharRange( + AliasDecl->getLocStart(), + Lexer::findLocationAfterToken( + AliasDecl->getLocEnd(), tok::semi, *Result.SourceManager, + Result.Context->getLangOpts(), + /*SkipTrailingWhitespaceAndNewLine=*/true)); + return; + } + + if (const auto *NestedName = + Result.Nodes.getNodeAs("nns")) { + if (const auto *AliasDecl = NestedName->getAsNamespaceAlias()) { + FoundDecls[AliasDecl] = CharSourceRange(); + } + } +} + +void UnusedAliasDeclsCheck::onEndOfTranslationUnit() { + for (const auto &FoundDecl : FoundDecls) { + if (!FoundDecl.second.isValid()) + continue; + diag(FoundDecl.first->getLocation(), "namespace alias decl '%0' is unused") + << FoundDecl.first->getName() + << FixItHint::CreateRemoval(FoundDecl.second); + } +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UnusedAliasDeclsCheck.h b/clang-tidy/misc/UnusedAliasDeclsCheck.h new file mode 100644 index 00000000..346fb691 --- /dev/null +++ b/clang-tidy/misc/UnusedAliasDeclsCheck.h @@ -0,0 +1,35 @@ +//===--- UnusedAliasDeclsCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_ALIAS_DECLS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_ALIAS_DECLS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// Finds unused namespace alias declarations. +class UnusedAliasDeclsCheck : public ClangTidyCheck { +public: + UnusedAliasDeclsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; + +private: + llvm::DenseMap FoundDecls; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_ALIAS_DECLS_H + diff --git a/clang-tidy/misc/UnusedParametersCheck.cpp b/clang-tidy/misc/UnusedParametersCheck.cpp new file mode 100644 index 00000000..8be8767b --- /dev/null +++ b/clang-tidy/misc/UnusedParametersCheck.cpp @@ -0,0 +1,122 @@ +//===--- UnusedParametersCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnusedParametersCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +void UnusedParametersCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(functionDecl().bind("function"), this); +} + +template +static CharSourceRange removeNode(const MatchFinder::MatchResult &Result, + const T *PrevNode, const T *Node, + const T *NextNode) { + if (NextNode) + return CharSourceRange::getCharRange(Node->getLocStart(), + NextNode->getLocStart()); + + if (PrevNode) + return CharSourceRange::getTokenRange( + Lexer::getLocForEndOfToken(PrevNode->getLocEnd(), 0, + *Result.SourceManager, + Result.Context->getLangOpts()), + Node->getLocEnd()); + + return CharSourceRange::getTokenRange(Node->getSourceRange()); +} + +static FixItHint removeParameter(const MatchFinder::MatchResult &Result, + const FunctionDecl *Function, unsigned Index) { + return FixItHint::CreateRemoval(removeNode( + Result, Index > 0 ? Function->getParamDecl(Index - 1) : nullptr, + Function->getParamDecl(Index), + Index + 1 < Function->getNumParams() ? Function->getParamDecl(Index + 1) + : nullptr)); +} + +static FixItHint removeArgument(const MatchFinder::MatchResult &Result, + const CallExpr *Call, unsigned Index) { + return FixItHint::CreateRemoval(removeNode( + Result, Index > 0 ? Call->getArg(Index - 1) : nullptr, + Call->getArg(Index), + Index + 1 < Call->getNumArgs() ? Call->getArg(Index + 1) : nullptr)); +} + +void UnusedParametersCheck::warnOnUnusedParameter( + const MatchFinder::MatchResult &Result, const FunctionDecl *Function, + unsigned ParamIndex) { + const auto *Param = Function->getParamDecl(ParamIndex); + auto MyDiag = diag(Param->getLocation(), "parameter '%0' is unused") + << Param->getName(); + + auto UsedByRef = [&] { + return !ast_matchers::match( + decl(hasDescendant( + declRefExpr(to(equalsNode(Function)), + unless(hasAncestor( + callExpr(callee(equalsNode(Function)))))))), + *Result.Context->getTranslationUnitDecl(), *Result.Context) + .empty(); + }; + + // Comment out parameter name for non-local functions. + if (Function->isExternallyVisible() || + !Result.SourceManager->isInMainFile(Function->getLocation()) || + UsedByRef()) { + SourceRange RemovalRange(Param->getLocation(), Param->getLocEnd()); + // Note: We always add a space before the '/*' to not accidentally create a + // '*/*' for pointer types, which doesn't start a comment. clang-format will + // clean this up afterwards. + MyDiag << FixItHint::CreateReplacement( + RemovalRange, (Twine(" /*") + Param->getName() + "*/").str()); + return; + } + + // Fix all redeclarations. + for (const FunctionDecl *FD : Function->redecls()) + if (FD->param_size()) + MyDiag << removeParameter(Result, FD, ParamIndex); + + // Fix all call sites. + auto CallMatches = ast_matchers::match( + decl(forEachDescendant( + callExpr(callee(functionDecl(equalsNode(Function)))).bind("x"))), + *Result.Context->getTranslationUnitDecl(), *Result.Context); + for (const auto &Match : CallMatches) + MyDiag << removeArgument(Result, Match.getNodeAs("x"), + ParamIndex); +} + +void UnusedParametersCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Function = Result.Nodes.getNodeAs("function"); + if (!Function->doesThisDeclarationHaveABody() || + !Function->hasWrittenPrototype()) + return; + if (const auto *Method = dyn_cast(Function)) + if (Method->isLambdaStaticInvoker()) + return; + for (unsigned i = 0, e = Function->getNumParams(); i != e; ++i) { + const auto *Param = Function->getParamDecl(i); + if (Param->isUsed() || Param->isReferenced() || !Param->getDeclName() || + Param->hasAttr()) + continue; + warnOnUnusedParameter(Result, Function, i); + } +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UnusedParametersCheck.h b/clang-tidy/misc/UnusedParametersCheck.h new file mode 100644 index 00000000..595f3a71 --- /dev/null +++ b/clang-tidy/misc/UnusedParametersCheck.h @@ -0,0 +1,37 @@ +//===--- UnusedParametersCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_PARAMETERS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_PARAMETERS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// Finds unused parameters and fixes them, so that `-Wunused-parameter` can be +/// turned on. +class UnusedParametersCheck : public ClangTidyCheck { +public: + UnusedParametersCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void + warnOnUnusedParameter(const ast_matchers::MatchFinder::MatchResult &Result, + const FunctionDecl *Function, unsigned ParamIndex); +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_PARAMETERS_H + diff --git a/clang-tidy/misc/UnusedRAIICheck.cpp b/clang-tidy/misc/UnusedRAIICheck.cpp new file mode 100644 index 00000000..e62b31da --- /dev/null +++ b/clang-tidy/misc/UnusedRAIICheck.cpp @@ -0,0 +1,91 @@ +//===--- UnusedRAIICheck.cpp - clang-tidy ---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnusedRAIICheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace { +AST_MATCHER(CXXRecordDecl, hasNonTrivialDestructor) { + // TODO: If the dtor is there but empty we don't want to warn either. + return Node.hasDefinition() && Node.hasNonTrivialDestructor(); +} +} // namespace + +namespace tidy { +namespace misc { + +void UnusedRAIICheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + // Look for temporaries that are constructed in-place and immediately + // destroyed. Look for temporaries created by a functional cast but not for + // those returned from a call. + auto BindTemp = cxxBindTemporaryExpr(unless(has(callExpr()))).bind("temp"); + Finder->addMatcher( + exprWithCleanups( + unless(isInTemplateInstantiation()), + hasParent(compoundStmt().bind("compound")), + hasType(cxxRecordDecl(hasNonTrivialDestructor())), + anyOf(has(BindTemp), has(cxxFunctionalCastExpr(has(BindTemp))))) + .bind("expr"), + this); +} + +void UnusedRAIICheck::check(const MatchFinder::MatchResult &Result) { + const auto *E = Result.Nodes.getStmtAs("expr"); + + // We ignore code expanded from macros to reduce the number of false + // positives. + if (E->getLocStart().isMacroID()) + return; + + // Don't emit a warning for the last statement in the surrounding compund + // statement. + const auto *CS = Result.Nodes.getStmtAs("compound"); + if (E == CS->body_back()) + return; + + // Emit a warning. + auto D = diag(E->getLocStart(), "object destroyed immediately after " + "creation; did you mean to name the object?"); + const char *Replacement = " give_me_a_name"; + + // If this is a default ctor we have to remove the parens or we'll introduce a + // most vexing parse. + const auto *BTE = Result.Nodes.getStmtAs("temp"); + if (const auto *TOE = dyn_cast(BTE->getSubExpr())) + if (TOE->getNumArgs() == 0) { + D << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(TOE->getParenOrBraceRange()), + Replacement); + return; + } + + // Otherwise just suggest adding a name. To find the place to insert the name + // find the first TypeLoc in the children of E, which always points to the + // written type. + auto Matches = + match(expr(hasDescendant(typeLoc().bind("t"))), *E, *Result.Context); + const auto *TL = selectFirst("t", Matches); + D << FixItHint::CreateInsertion( + Lexer::getLocForEndOfToken(TL->getLocEnd(), 0, *Result.SourceManager, + Result.Context->getLangOpts()), + Replacement); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UnusedRAIICheck.h b/clang-tidy/misc/UnusedRAIICheck.h new file mode 100644 index 00000000..40f44e3d --- /dev/null +++ b/clang-tidy/misc/UnusedRAIICheck.h @@ -0,0 +1,35 @@ +//===--- UnusedRAIICheck.h - clang-tidy -------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSEDRAIICHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSEDRAIICHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds temporaries that look like RAII objects. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-unused-raii.html +class UnusedRAIICheck : public ClangTidyCheck { +public: + UnusedRAIICheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSEDRAIICHECK_H diff --git a/clang-tidy/misc/VirtualNearMissCheck.cpp b/clang-tidy/misc/VirtualNearMissCheck.cpp new file mode 100644 index 00000000..def63ce1 --- /dev/null +++ b/clang-tidy/misc/VirtualNearMissCheck.cpp @@ -0,0 +1,266 @@ +//===--- VirtualNearMissCheck.cpp - clang-tidy-----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "VirtualNearMissCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds out if the given method overrides some method. +static bool isOverrideMethod(const CXXMethodDecl *MD) { + return MD->size_overridden_methods() > 0 || MD->hasAttr(); +} + +/// Checks whether the return types are covariant, according to +/// C++[class.virtual]p7. +/// +/// Similar with clang::Sema::CheckOverridingFunctionReturnType. +/// \returns true if the return types of BaseMD and DerivedMD are covariant. +static bool checkOverridingFunctionReturnType(const ASTContext *Context, + const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + QualType BaseReturnTy = + BaseMD->getType()->getAs()->getReturnType(); + QualType DerivedReturnTy = + DerivedMD->getType()->getAs()->getReturnType(); + + if (DerivedReturnTy->isDependentType() || BaseReturnTy->isDependentType()) + return false; + + // Check if return types are identical. + if (Context->hasSameType(DerivedReturnTy, BaseReturnTy)) + return true; + + /// Check if the return types are covariant. + /// BTy is the class type in return type of BaseMD. For example, + /// B* Base::md() + /// While BRD is the declaration of B. + QualType BTy, DTy; + const CXXRecordDecl *BRD, *DRD; + + // Both types must be pointers or references to classes. + if (const auto *DerivedPT = DerivedReturnTy->getAs()) { + if (const auto *BasePT = BaseReturnTy->getAs()) { + DTy = DerivedPT->getPointeeType(); + BTy = BasePT->getPointeeType(); + } + } else if (const auto *DerivedRT = DerivedReturnTy->getAs()) { + if (const auto *BaseRT = BaseReturnTy->getAs()) { + DTy = DerivedRT->getPointeeType(); + BTy = BaseRT->getPointeeType(); + } + } + + // The return types aren't either both pointers or references to a class type. + if (DTy.isNull()) + return false; + + DRD = DTy->getAsCXXRecordDecl(); + BRD = BTy->getAsCXXRecordDecl(); + if (DRD == nullptr || BRD == nullptr) + return false; + + if (DRD == BRD) + return true; + + if (!Context->hasSameUnqualifiedType(DTy, BTy)) { + // Begin checking whether the conversion from D to B is valid. + CXXBasePaths Paths(/*FindAmbiguities=*/true, /*RecordPaths=*/true, + /*DetectVirtual=*/false); + + // Check whether D is derived from B, and fill in a CXXBasePaths object. + if (!DRD->isDerivedFrom(BRD, Paths)) + return false; + + // Check ambiguity. + if (Paths.isAmbiguous(Context->getCanonicalType(BTy).getUnqualifiedType())) + return false; + + // Check accessibility. + // FIXME: We currently only support checking if B is accessible base class + // of D, or D is the same class which DerivedMD is in. + bool IsItself = DRD == DerivedMD->getParent(); + bool HasPublicAccess = false; + for (const auto &Path : Paths) { + if (Path.Access == AS_public) + HasPublicAccess = true; + } + if (!HasPublicAccess && !IsItself) + return false; + // End checking conversion from D to B. + } + + // Both pointers or references should have the same cv-qualification. + if (DerivedReturnTy.getLocalCVRQualifiers() != + BaseReturnTy.getLocalCVRQualifiers()) + return false; + + // The class type D should have the same cv-qualification as or less + // cv-qualification than the class type B. + if (DTy.isMoreQualifiedThan(BTy)) + return false; + + return true; +} + +/// \returns true if the param types are the same. +static bool checkParamTypes(const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + unsigned NumParamA = BaseMD->getNumParams(); + unsigned NumParamB = DerivedMD->getNumParams(); + if (NumParamA != NumParamB) + return false; + + for (unsigned I = 0; I < NumParamA; I++) { + if (BaseMD->getParamDecl(I)->getType() != + DerivedMD->getParamDecl(I)->getType()) + return false; + } + return true; +} + +/// \returns true if derived method can override base method except for the +/// name. +static bool checkOverrideWithoutName(const ASTContext *Context, + const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + if (BaseMD->getTypeQualifiers() != DerivedMD->getTypeQualifiers()) + return false; + + if (BaseMD->isStatic() != DerivedMD->isStatic()) + return false; + + if (BaseMD->getAccess() != DerivedMD->getAccess()) + return false; + + if (BaseMD->getType() == DerivedMD->getType()) + return true; + + // Now the function types are not identical. Then check if the return types + // are covariant and if the param types are the same. + if (!checkOverridingFunctionReturnType(Context, BaseMD, DerivedMD)) + return false; + return checkParamTypes(BaseMD, DerivedMD); +} + +/// Check whether BaseMD overrides DerivedMD. +/// +/// Prerequisite: the class which BaseMD is in should be a base class of that +/// DerivedMD is in. +static bool checkOverrideByDerivedMethod(const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + if (BaseMD->getNameAsString() != DerivedMD->getNameAsString()) + return false; + + if (!checkParamTypes(BaseMD, DerivedMD)) + return false; + + return true; +} + +/// Generate unique ID for given MethodDecl. +/// +/// The Id is used as key for 'PossibleMap'. +/// Typical Id: "Base::func void (void)" +static std::string generateMethodId(const CXXMethodDecl *MD) { + return MD->getQualifiedNameAsString() + " " + MD->getType().getAsString(); +} + +bool VirtualNearMissCheck::isPossibleToBeOverridden( + const CXXMethodDecl *BaseMD) { + std::string Id = generateMethodId(BaseMD); + auto Iter = PossibleMap.find(Id); + if (Iter != PossibleMap.end()) + return Iter->second; + + bool IsPossible = !BaseMD->isImplicit() && !isa(BaseMD) && + BaseMD->isVirtual(); + PossibleMap[Id] = IsPossible; + return IsPossible; +} + +bool VirtualNearMissCheck::isOverriddenByDerivedClass( + const CXXMethodDecl *BaseMD, const CXXRecordDecl *DerivedRD) { + auto Key = std::make_pair(generateMethodId(BaseMD), + DerivedRD->getQualifiedNameAsString()); + auto Iter = OverriddenMap.find(Key); + if (Iter != OverriddenMap.end()) + return Iter->second; + + bool IsOverridden = false; + for (const CXXMethodDecl *DerivedMD : DerivedRD->methods()) { + if (!isOverrideMethod(DerivedMD)) + continue; + + if (checkOverrideByDerivedMethod(BaseMD, DerivedMD)) { + IsOverridden = true; + break; + } + } + OverriddenMap[Key] = IsOverridden; + return IsOverridden; +} + +void VirtualNearMissCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(cxxMethodDecl(unless(anyOf(isOverride(), isImplicit(), + cxxConstructorDecl()))) + .bind("method"), + this); +} + +void VirtualNearMissCheck::check(const MatchFinder::MatchResult &Result) { + const auto *DerivedMD = Result.Nodes.getNodeAs("method"); + assert(DerivedMD != nullptr); + + if (DerivedMD->isStatic()) + return; + + const ASTContext *Context = Result.Context; + + const auto *DerivedRD = DerivedMD->getParent(); + + for (const auto &BaseSpec : DerivedRD->bases()) { + if (const auto *BaseRD = BaseSpec.getType()->getAsCXXRecordDecl()) { + for (const auto *BaseMD : BaseRD->methods()) { + if (!isPossibleToBeOverridden(BaseMD)) + continue; + + if (isOverriddenByDerivedClass(BaseMD, DerivedRD)) + continue; + + unsigned EditDistance = + BaseMD->getName().edit_distance(DerivedMD->getName()); + if (EditDistance > 0 && EditDistance <= EditDistanceThreshold) { + if (checkOverrideWithoutName(Context, BaseMD, DerivedMD)) { + // A "virtual near miss" is found. + diag(DerivedMD->getLocStart(), + "method '%0' has a similar name and the same signature as " + "virtual method '%1'; did you mean to override it?") + << DerivedMD->getQualifiedNameAsString() + << BaseMD->getQualifiedNameAsString(); + } + } + } + } + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/VirtualNearMissCheck.h b/clang-tidy/misc/VirtualNearMissCheck.h new file mode 100644 index 00000000..97cb3eaa --- /dev/null +++ b/clang-tidy/misc/VirtualNearMissCheck.h @@ -0,0 +1,65 @@ +//===--- VirtualNearMissCheck.h - clang-tidy---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_VIRTUAL_NEAR_MISS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_VIRTUAL_NEAR_MISS_H + +#include "../ClangTidy.h" +#include +#include + +namespace clang { +namespace tidy { +namespace misc { + +/// \brief Checks for near miss of virtual methods. +/// +/// For a method in a derived class, this check looks for virtual method with a +/// very similar name and an identical signature defined in a base class. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-virtual-near-miss.html +class VirtualNearMissCheck : public ClangTidyCheck { +public: + VirtualNearMissCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + /// Check if the given method is possible to be overridden by some other + /// method. + /// + /// Results are memoized in PossibleMap. + bool isPossibleToBeOverridden(const CXXMethodDecl *BaseMD); + + /// Check if the given base method is overridden by some methods in the given + /// derived class. + /// + /// Results are memoized in OverriddenMap. + bool isOverriddenByDerivedClass(const CXXMethodDecl *BaseMD, + const CXXRecordDecl *DerivedRD); + + /// key: the unique ID of a method; + /// value: whether the method is possible to be overridden. + std::map PossibleMap; + + /// key: + /// value: whether the base method is overridden by some method in the derived + /// class. + std::map, bool> OverriddenMap; + + const unsigned EditDistanceThreshold = 1; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_VIRTUAL_NEAR_MISS_H diff --git a/clang-tidy/modernize/CMakeLists.txt b/clang-tidy/modernize/CMakeLists.txt new file mode 100644 index 00000000..e14faaf8 --- /dev/null +++ b/clang-tidy/modernize/CMakeLists.txt @@ -0,0 +1,25 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyModernizeModule + LoopConvertCheck.cpp + LoopConvertUtils.cpp + MakeUniqueCheck.cpp + ModernizeTidyModule.cpp + PassByValueCheck.cpp + RedundantVoidArgCheck.cpp + ReplaceAutoPtrCheck.cpp + ShrinkToFitCheck.cpp + UseAutoCheck.cpp + UseDefaultCheck.cpp + UseNullptrCheck.cpp + UseOverrideCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyReadabilityModule + clangTidyUtils + ) diff --git a/clang-tidy/modernize/LoopConvertCheck.cpp b/clang-tidy/modernize/LoopConvertCheck.cpp new file mode 100644 index 00000000..bd476dd5 --- /dev/null +++ b/clang-tidy/modernize/LoopConvertCheck.cpp @@ -0,0 +1,897 @@ +//===--- LoopConvertCheck.cpp - clang-tidy---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "LoopConvertCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang; +using namespace clang::ast_matchers; +using namespace llvm; + +namespace clang { +namespace tidy { +namespace modernize { + +static const char LoopNameArray[] = "forLoopArray"; +static const char LoopNameIterator[] = "forLoopIterator"; +static const char LoopNamePseudoArray[] = "forLoopPseudoArray"; +static const char ConditionBoundName[] = "conditionBound"; +static const char ConditionVarName[] = "conditionVar"; +static const char IncrementVarName[] = "incrementVar"; +static const char InitVarName[] = "initVar"; +static const char BeginCallName[] = "beginCall"; +static const char EndCallName[] = "endCall"; +static const char ConditionEndVarName[] = "conditionEndVar"; +static const char EndVarName[] = "endVar"; +static const char DerefByValueResultName[] = "derefByValueResult"; +static const char DerefByRefResultName[] = "derefByRefResult"; + +// shared matchers +static const TypeMatcher AnyType = anything(); + +static const StatementMatcher IntegerComparisonMatcher = + expr(ignoringParenImpCasts( + declRefExpr(to(varDecl(hasType(isInteger())).bind(ConditionVarName))))); + +static const DeclarationMatcher InitToZeroMatcher = + varDecl(hasInitializer(ignoringParenImpCasts(integerLiteral(equals(0))))) + .bind(InitVarName); + +static const StatementMatcher IncrementVarMatcher = + declRefExpr(to(varDecl(hasType(isInteger())).bind(IncrementVarName))); + +/// \brief The matcher for loops over arrays. +/// +/// In this general example, assuming 'j' and 'k' are of integral type: +/// \code +/// for (int i = 0; j < 3 + 2; ++k) { ... } +/// \endcode +/// The following string identifiers are bound to these parts of the AST: +/// ConditionVarName: 'j' (as a VarDecl) +/// ConditionBoundName: '3 + 2' (as an Expr) +/// InitVarName: 'i' (as a VarDecl) +/// IncrementVarName: 'k' (as a VarDecl) +/// LoopName: The entire for loop (as a ForStmt) +/// +/// Client code will need to make sure that: +/// - The three index variables identified by the matcher are the same +/// VarDecl. +/// - The index variable is only used as an array index. +/// - All arrays indexed by the loop are the same. +StatementMatcher makeArrayLoopMatcher() { + StatementMatcher ArrayBoundMatcher = + expr(hasType(isInteger())).bind(ConditionBoundName); + + return forStmt( + unless(isInTemplateInstantiation()), + hasLoopInit(declStmt(hasSingleDecl(InitToZeroMatcher))), + hasCondition(anyOf( + binaryOperator(hasOperatorName("<"), + hasLHS(IntegerComparisonMatcher), + hasRHS(ArrayBoundMatcher)), + binaryOperator(hasOperatorName(">"), hasLHS(ArrayBoundMatcher), + hasRHS(IntegerComparisonMatcher)))), + hasIncrement(unaryOperator(hasOperatorName("++"), + hasUnaryOperand(IncrementVarMatcher)))) + .bind(LoopNameArray); +} + +/// \brief The matcher used for iterator-based for loops. +/// +/// This matcher is more flexible than array-based loops. It will match +/// catch loops of the following textual forms (regardless of whether the +/// iterator type is actually a pointer type or a class type): +/// +/// Assuming f, g, and h are of type containerType::iterator, +/// \code +/// for (containerType::iterator it = container.begin(), +/// e = createIterator(); f != g; ++h) { ... } +/// for (containerType::iterator it = container.begin(); +/// f != anotherContainer.end(); ++h) { ... } +/// \endcode +/// The following string identifiers are bound to the parts of the AST: +/// InitVarName: 'it' (as a VarDecl) +/// ConditionVarName: 'f' (as a VarDecl) +/// LoopName: The entire for loop (as a ForStmt) +/// In the first example only: +/// EndVarName: 'e' (as a VarDecl) +/// ConditionEndVarName: 'g' (as a VarDecl) +/// In the second example only: +/// EndCallName: 'container.end()' (as a CXXMemberCallExpr) +/// +/// Client code will need to make sure that: +/// - The iterator variables 'it', 'f', and 'h' are the same. +/// - The two containers on which 'begin' and 'end' are called are the same. +/// - If the end iterator variable 'g' is defined, it is the same as 'f'. +StatementMatcher makeIteratorLoopMatcher() { + StatementMatcher BeginCallMatcher = + cxxMemberCallExpr( + argumentCountIs(0), + callee(cxxMethodDecl(anyOf(hasName("begin"), hasName("cbegin"))))) + .bind(BeginCallName); + + DeclarationMatcher InitDeclMatcher = + varDecl(hasInitializer(anyOf(ignoringParenImpCasts(BeginCallMatcher), + materializeTemporaryExpr( + ignoringParenImpCasts(BeginCallMatcher)), + hasDescendant(BeginCallMatcher)))) + .bind(InitVarName); + + DeclarationMatcher EndDeclMatcher = + varDecl(hasInitializer(anything())).bind(EndVarName); + + StatementMatcher EndCallMatcher = cxxMemberCallExpr( + argumentCountIs(0), + callee(cxxMethodDecl(anyOf(hasName("end"), hasName("cend"))))); + + StatementMatcher IteratorBoundMatcher = + expr(anyOf(ignoringParenImpCasts( + declRefExpr(to(varDecl().bind(ConditionEndVarName)))), + ignoringParenImpCasts(expr(EndCallMatcher).bind(EndCallName)), + materializeTemporaryExpr(ignoringParenImpCasts( + expr(EndCallMatcher).bind(EndCallName))))); + + StatementMatcher IteratorComparisonMatcher = expr( + ignoringParenImpCasts(declRefExpr(to(varDecl().bind(ConditionVarName))))); + + StatementMatcher OverloadedNEQMatcher = + cxxOperatorCallExpr(hasOverloadedOperatorName("!="), argumentCountIs(2), + hasArgument(0, IteratorComparisonMatcher), + hasArgument(1, IteratorBoundMatcher)); + + // This matcher tests that a declaration is a CXXRecordDecl that has an + // overloaded operator*(). If the operator*() returns by value instead of by + // reference then the return type is tagged with DerefByValueResultName. + internal::Matcher TestDerefReturnsByValue = + hasType(cxxRecordDecl(hasMethod(allOf( + hasOverloadedOperatorName("*"), + anyOf( + // Tag the return type if it's by value. + returns(qualType(unless(hasCanonicalType(referenceType()))) + .bind(DerefByValueResultName)), + returns( + // Skip loops where the iterator's operator* returns an + // rvalue reference. This is just weird. + qualType(unless(hasCanonicalType(rValueReferenceType()))) + .bind(DerefByRefResultName))))))); + + return forStmt( + unless(isInTemplateInstantiation()), + hasLoopInit(anyOf(declStmt(declCountIs(2), + containsDeclaration(0, InitDeclMatcher), + containsDeclaration(1, EndDeclMatcher)), + declStmt(hasSingleDecl(InitDeclMatcher)))), + hasCondition( + anyOf(binaryOperator(hasOperatorName("!="), + hasLHS(IteratorComparisonMatcher), + hasRHS(IteratorBoundMatcher)), + binaryOperator(hasOperatorName("!="), + hasLHS(IteratorBoundMatcher), + hasRHS(IteratorComparisonMatcher)), + OverloadedNEQMatcher)), + hasIncrement(anyOf( + unaryOperator(hasOperatorName("++"), + hasUnaryOperand(declRefExpr( + to(varDecl(hasType(pointsTo(AnyType))) + .bind(IncrementVarName))))), + cxxOperatorCallExpr( + hasOverloadedOperatorName("++"), + hasArgument( + 0, declRefExpr(to(varDecl(TestDerefReturnsByValue) + .bind(IncrementVarName)))))))) + .bind(LoopNameIterator); +} + +/// \brief The matcher used for array-like containers (pseudoarrays). +/// +/// This matcher is more flexible than array-based loops. It will match +/// loops of the following textual forms (regardless of whether the +/// iterator type is actually a pointer type or a class type): +/// +/// Assuming f, g, and h are of type containerType::iterator, +/// \code +/// for (int i = 0, j = container.size(); f < g; ++h) { ... } +/// for (int i = 0; f < container.size(); ++h) { ... } +/// \endcode +/// The following string identifiers are bound to the parts of the AST: +/// InitVarName: 'i' (as a VarDecl) +/// ConditionVarName: 'f' (as a VarDecl) +/// LoopName: The entire for loop (as a ForStmt) +/// In the first example only: +/// EndVarName: 'j' (as a VarDecl) +/// ConditionEndVarName: 'g' (as a VarDecl) +/// In the second example only: +/// EndCallName: 'container.size()' (as a CXXMemberCallExpr) +/// +/// Client code will need to make sure that: +/// - The index variables 'i', 'f', and 'h' are the same. +/// - The containers on which 'size()' is called is the container indexed. +/// - The index variable is only used in overloaded operator[] or +/// container.at(). +/// - If the end iterator variable 'g' is defined, it is the same as 'j'. +/// - The container's iterators would not be invalidated during the loop. +StatementMatcher makePseudoArrayLoopMatcher() { + // Test that the incoming type has a record declaration that has methods + // called 'begin' and 'end'. If the incoming type is const, then make sure + // these methods are also marked const. + // + // FIXME: To be completely thorough this matcher should also ensure the + // return type of begin/end is an iterator that dereferences to the same as + // what operator[] or at() returns. Such a test isn't likely to fail except + // for pathological cases. + // + // FIXME: Also, a record doesn't necessarily need begin() and end(). Free + // functions called begin() and end() taking the container as an argument + // are also allowed. + TypeMatcher RecordWithBeginEnd = qualType(anyOf( + qualType(isConstQualified(), + hasDeclaration(cxxRecordDecl( + hasMethod(cxxMethodDecl(hasName("begin"), isConst())), + hasMethod(cxxMethodDecl(hasName("end"), + isConst())))) // hasDeclaration + ), // qualType + qualType( + unless(isConstQualified()), + hasDeclaration(cxxRecordDecl(hasMethod(hasName("begin")), + hasMethod(hasName("end"))))) // qualType + )); + + StatementMatcher SizeCallMatcher = cxxMemberCallExpr( + argumentCountIs(0), + callee(cxxMethodDecl(anyOf(hasName("size"), hasName("length")))), + on(anyOf(hasType(pointsTo(RecordWithBeginEnd)), + hasType(RecordWithBeginEnd)))); + + StatementMatcher EndInitMatcher = + expr(anyOf(ignoringParenImpCasts(expr(SizeCallMatcher).bind(EndCallName)), + explicitCastExpr(hasSourceExpression(ignoringParenImpCasts( + expr(SizeCallMatcher).bind(EndCallName)))))); + + DeclarationMatcher EndDeclMatcher = + varDecl(hasInitializer(EndInitMatcher)).bind(EndVarName); + + StatementMatcher IndexBoundMatcher = + expr(anyOf(ignoringParenImpCasts(declRefExpr(to( + varDecl(hasType(isInteger())).bind(ConditionEndVarName)))), + EndInitMatcher)); + + return forStmt( + unless(isInTemplateInstantiation()), + hasLoopInit( + anyOf(declStmt(declCountIs(2), + containsDeclaration(0, InitToZeroMatcher), + containsDeclaration(1, EndDeclMatcher)), + declStmt(hasSingleDecl(InitToZeroMatcher)))), + hasCondition(anyOf( + binaryOperator(hasOperatorName("<"), + hasLHS(IntegerComparisonMatcher), + hasRHS(IndexBoundMatcher)), + binaryOperator(hasOperatorName(">"), hasLHS(IndexBoundMatcher), + hasRHS(IntegerComparisonMatcher)))), + hasIncrement(unaryOperator(hasOperatorName("++"), + hasUnaryOperand(IncrementVarMatcher)))) + .bind(LoopNamePseudoArray); +} + +/// \brief Determine whether Init appears to be an initializing an iterator. +/// +/// If it is, returns the object whose begin() or end() method is called, and +/// the output parameter isArrow is set to indicate whether the initialization +/// is called via . or ->. +static const Expr *getContainerFromBeginEndCall(const Expr *Init, bool IsBegin, + bool *IsArrow) { + // FIXME: Maybe allow declaration/initialization outside of the for loop. + const auto *TheCall = + dyn_cast_or_null(digThroughConstructors(Init)); + if (!TheCall || TheCall->getNumArgs() != 0) + return nullptr; + + const auto *Member = dyn_cast(TheCall->getCallee()); + if (!Member) + return nullptr; + StringRef Name = Member->getMemberDecl()->getName(); + StringRef TargetName = IsBegin ? "begin" : "end"; + StringRef ConstTargetName = IsBegin ? "cbegin" : "cend"; + if (Name != TargetName && Name != ConstTargetName) + return nullptr; + + const Expr *SourceExpr = Member->getBase(); + if (!SourceExpr) + return nullptr; + + *IsArrow = Member->isArrow(); + return SourceExpr; +} + +/// \brief Determines the container whose begin() and end() functions are called +/// for an iterator-based loop. +/// +/// BeginExpr must be a member call to a function named "begin()", and EndExpr +/// must be a member. +static const Expr *findContainer(ASTContext *Context, const Expr *BeginExpr, + const Expr *EndExpr, + bool *ContainerNeedsDereference) { + // Now that we know the loop variable and test expression, make sure they are + // valid. + bool BeginIsArrow = false; + bool EndIsArrow = false; + const Expr *BeginContainerExpr = + getContainerFromBeginEndCall(BeginExpr, /*IsBegin=*/true, &BeginIsArrow); + if (!BeginContainerExpr) + return nullptr; + + const Expr *EndContainerExpr = + getContainerFromBeginEndCall(EndExpr, /*IsBegin=*/false, &EndIsArrow); + // Disallow loops that try evil things like this (note the dot and arrow): + // for (IteratorType It = Obj.begin(), E = Obj->end(); It != E; ++It) { } + if (!EndContainerExpr || BeginIsArrow != EndIsArrow || + !areSameExpr(Context, EndContainerExpr, BeginContainerExpr)) + return nullptr; + + *ContainerNeedsDereference = BeginIsArrow; + return BeginContainerExpr; +} + +/// \brief Obtain the original source code text from a SourceRange. +static StringRef getStringFromRange(SourceManager &SourceMgr, + const LangOptions &LangOpts, + SourceRange Range) { + if (SourceMgr.getFileID(Range.getBegin()) != + SourceMgr.getFileID(Range.getEnd())) { + return StringRef(); // Empty string. + } + + return Lexer::getSourceText(CharSourceRange(Range, true), SourceMgr, + LangOpts); +} + +/// \brief If the given expression is actually a DeclRefExpr or a MemberExpr, +/// find and return the underlying ValueDecl; otherwise, return NULL. +static const ValueDecl *getReferencedVariable(const Expr *E) { + if (const DeclRefExpr *DRE = getDeclRef(E)) + return dyn_cast(DRE->getDecl()); + if (const auto *Mem = dyn_cast(E->IgnoreParenImpCasts())) + return dyn_cast(Mem->getMemberDecl()); + return nullptr; +} + +/// \brief Returns true when the given expression is a member expression +/// whose base is `this` (implicitly or not). +static bool isDirectMemberExpr(const Expr *E) { + if (const auto *Member = dyn_cast(E->IgnoreParenImpCasts())) + return isa(Member->getBase()->IgnoreParenImpCasts()); + return false; +} + +/// \brief Given an expression that represents an usage of an element from the +/// containter that we are iterating over, returns false when it can be +/// guaranteed this element cannot be modified as a result of this usage. +static bool canBeModified(ASTContext *Context, const Expr *E) { + if (E->getType().isConstQualified()) + return false; + auto Parents = Context->getParents(*E); + if (Parents.size() != 1) + return true; + if (const auto *Cast = Parents[0].get()) { + if ((Cast->getCastKind() == CK_NoOp && + Cast->getType() == E->getType().withConst()) || + (Cast->getCastKind() == CK_LValueToRValue && + !Cast->getType().isNull() && Cast->getType()->isFundamentalType())) + return false; + } + // FIXME: Make this function more generic. + return true; +} + +/// \brief Returns true when it can be guaranteed that the elements of the +/// container are not being modified. +static bool usagesAreConst(ASTContext *Context, const UsageResult &Usages) { + for (const Usage &U : Usages) { + // Lambda captures are just redeclarations (VarDecl) of the same variable, + // not expressions. If we want to know if a variable that is captured by + // reference can be modified in an usage inside the lambda's body, we need + // to find the expression corresponding to that particular usage, later in + // this loop. + if (U.Kind != Usage::UK_CaptureByCopy && U.Kind != Usage::UK_CaptureByRef && + canBeModified(Context, U.Expression)) + return false; + } + return true; +} + +/// \brief Returns true if the elements of the container are never accessed +/// by reference. +static bool usagesReturnRValues(const UsageResult &Usages) { + for (const auto &U : Usages) { + if (U.Expression && !U.Expression->isRValue()) + return false; + } + return true; +} + +/// \brief Returns true if the container is const-qualified. +static bool containerIsConst(const Expr *ContainerExpr, bool Dereference) { + if (const auto *VDec = getReferencedVariable(ContainerExpr)) { + QualType CType = VDec->getType(); + if (Dereference) { + if (!CType->isPointerType()) + return false; + CType = CType->getPointeeType(); + } + // If VDec is a reference to a container, Dereference is false, + // but we still need to check the const-ness of the underlying container + // type. + CType = CType.getNonReferenceType(); + return CType.isConstQualified(); + } + return false; +} + +LoopConvertCheck::RangeDescriptor::RangeDescriptor() + : ContainerNeedsDereference(false), DerefByConstRef(false), + DerefByValue(false) {} + +LoopConvertCheck::LoopConvertCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), TUInfo(new TUTrackingInfo), + MaxCopySize(std::stoull(Options.get("MaxCopySize", "16"))), + MinConfidence(StringSwitch( + Options.get("MinConfidence", "reasonable")) + .Case("safe", Confidence::CL_Safe) + .Case("risky", Confidence::CL_Risky) + .Default(Confidence::CL_Reasonable)), + NamingStyle(StringSwitch( + Options.get("NamingStyle", "CamelCase")) + .Case("camelBack", VariableNamer::NS_CamelBack) + .Case("lower_case", VariableNamer::NS_LowerCase) + .Case("UPPER_CASE", VariableNamer::NS_UpperCase) + .Default(VariableNamer::NS_CamelCase)) {} + +void LoopConvertCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "MaxCopySize", std::to_string(MaxCopySize)); + SmallVector Confs{"risky", "reasonable", "safe"}; + Options.store(Opts, "MinConfidence", Confs[static_cast(MinConfidence)]); + + SmallVector Styles{"camelBack", "CamelCase", "lower_case", + "UPPER_CASE"}; + Options.store(Opts, "NamingStyle", Styles[static_cast(NamingStyle)]); +} + +void LoopConvertCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++. Because this checker is used for + // modernization, it is reasonable to run it on any C++ standard with the + // assumption the user is trying to modernize their codebase. + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(makeArrayLoopMatcher(), this); + Finder->addMatcher(makeIteratorLoopMatcher(), this); + Finder->addMatcher(makePseudoArrayLoopMatcher(), this); +} + +/// \brief Given the range of a single declaration, such as: +/// \code +/// unsigned &ThisIsADeclarationThatCanSpanSeveralLinesOfCode = +/// InitializationValues[I]; +/// next_instruction; +/// \endcode +/// Finds the range that has to be erased to remove this declaration without +/// leaving empty lines, by extending the range until the beginning of the +/// next instruction. +/// +/// We need to delete a potential newline after the deleted alias, as +/// clang-format will leave empty lines untouched. For all other formatting we +/// rely on clang-format to fix it. +void LoopConvertCheck::getAliasRange(SourceManager &SM, SourceRange &Range) { + bool Invalid = false; + const char *TextAfter = + SM.getCharacterData(Range.getEnd().getLocWithOffset(1), &Invalid); + if (Invalid) + return; + unsigned Offset = std::strspn(TextAfter, " \t\r\n"); + Range = + SourceRange(Range.getBegin(), Range.getEnd().getLocWithOffset(Offset)); +} + +/// \brief Computes the changes needed to convert a given for loop, and +/// applies them. +void LoopConvertCheck::doConversion( + ASTContext *Context, const VarDecl *IndexVar, + const ValueDecl *MaybeContainer, const UsageResult &Usages, + const DeclStmt *AliasDecl, bool AliasUseRequired, bool AliasFromForInit, + const ForStmt *Loop, RangeDescriptor Descriptor) { + auto Diag = diag(Loop->getForLoc(), "use range-based for loop instead"); + + std::string VarName; + bool VarNameFromAlias = (Usages.size() == 1) && AliasDecl; + bool AliasVarIsRef = false; + bool CanCopy = true; + + if (VarNameFromAlias) { + const auto *AliasVar = cast(AliasDecl->getSingleDecl()); + VarName = AliasVar->getName().str(); + AliasVarIsRef = AliasVar->getType()->isReferenceType(); + + // We keep along the entire DeclStmt to keep the correct range here. + SourceRange ReplaceRange = AliasDecl->getSourceRange(); + + std::string ReplacementText; + if (AliasUseRequired) { + ReplacementText = VarName; + } else if (AliasFromForInit) { + // FIXME: Clang includes the location of the ';' but only for DeclStmt's + // in a for loop's init clause. Need to put this ';' back while removing + // the declaration of the alias variable. This is probably a bug. + ReplacementText = ";"; + } else { + // Avoid leaving empty lines or trailing whitespaces. + getAliasRange(Context->getSourceManager(), ReplaceRange); + } + + Diag << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(ReplaceRange), ReplacementText); + // No further replacements are made to the loop, since the iterator or index + // was used exactly once - in the initialization of AliasVar. + } else { + VariableNamer Namer(&TUInfo->getGeneratedDecls(), + &TUInfo->getParentFinder().getStmtToParentStmtMap(), + Loop, IndexVar, MaybeContainer, Context, NamingStyle); + VarName = Namer.createIndexName(); + // First, replace all usages of the array subscript expression with our new + // variable. + for (const auto &Usage : Usages) { + std::string ReplaceText; + SourceRange Range = Usage.Range; + if (Usage.Expression) { + // If this is an access to a member through the arrow operator, after + // the replacement it must be accessed through the '.' operator. + ReplaceText = Usage.Kind == Usage::UK_MemberThroughArrow ? VarName + "." + : VarName; + auto Parents = Context->getParents(*Usage.Expression); + if (Parents.size() == 1) { + if (const auto *Paren = Parents[0].get()) { + // Usage.Expression will be replaced with the new index variable, + // and parenthesis around a simple DeclRefExpr can always be + // removed. + Range = Paren->getSourceRange(); + } else if (const auto *UOP = Parents[0].get()) { + // If we are taking the address of the loop variable, then we must + // not use a copy, as it would mean taking the address of the loop's + // local index instead. + // FIXME: This won't catch cases where the address is taken outside + // of the loop's body (for instance, in a function that got the + // loop's index as a const reference parameter), or where we take + // the address of a member (like "&Arr[i].A.B.C"). + if (UOP->getOpcode() == UO_AddrOf) + CanCopy = false; + } + } + } else { + // The Usage expression is only null in case of lambda captures (which + // are VarDecl). If the index is captured by value, add '&' to capture + // by reference instead. + ReplaceText = + Usage.Kind == Usage::UK_CaptureByCopy ? "&" + VarName : VarName; + } + TUInfo->getReplacedVars().insert(std::make_pair(Loop, IndexVar)); + Diag << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(Range), ReplaceText); + } + } + + // Now, we need to construct the new range expression. + SourceRange ParenRange(Loop->getLParenLoc(), Loop->getRParenLoc()); + + QualType Type = Context->getAutoDeductType(); + if (!Descriptor.ElemType.isNull() && Descriptor.ElemType->isFundamentalType()) + Type = Descriptor.ElemType.getUnqualifiedType(); + + // If the new variable name is from the aliased variable, then the reference + // type for the new variable should only be used if the aliased variable was + // declared as a reference. + bool IsCheapToCopy = + !Descriptor.ElemType.isNull() && + Descriptor.ElemType.isTriviallyCopyableType(*Context) && + // TypeInfo::Width is in bits. + Context->getTypeInfo(Descriptor.ElemType).Width <= 8 * MaxCopySize; + bool UseCopy = CanCopy && ((VarNameFromAlias && !AliasVarIsRef) || + (Descriptor.DerefByConstRef && IsCheapToCopy)); + + if (!UseCopy) { + if (Descriptor.DerefByConstRef) { + Type = Context->getLValueReferenceType(Context->getConstType(Type)); + } else if (Descriptor.DerefByValue) { + if (!IsCheapToCopy) + Type = Context->getRValueReferenceType(Type); + } else { + Type = Context->getLValueReferenceType(Type); + } + } + + StringRef MaybeDereference = Descriptor.ContainerNeedsDereference ? "*" : ""; + std::string TypeString = Type.getAsString(getLangOpts()); + std::string Range = ("(" + TypeString + " " + VarName + " : " + + MaybeDereference + Descriptor.ContainerString + ")") + .str(); + Diag << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(ParenRange), Range); + TUInfo->getGeneratedDecls().insert(make_pair(Loop, VarName)); +} + +/// \brief Returns a string which refers to the container iterated over. +StringRef LoopConvertCheck::getContainerString(ASTContext *Context, + const ForStmt *Loop, + const Expr *ContainerExpr) { + StringRef ContainerString; + if (isa(ContainerExpr->IgnoreParenImpCasts())) { + ContainerString = "this"; + } else { + ContainerString = + getStringFromRange(Context->getSourceManager(), Context->getLangOpts(), + ContainerExpr->getSourceRange()); + } + + return ContainerString; +} + +/// \brief Determines what kind of 'auto' must be used after converting a for +/// loop that iterates over an array or pseudoarray. +void LoopConvertCheck::getArrayLoopQualifiers(ASTContext *Context, + const BoundNodes &Nodes, + const Expr *ContainerExpr, + const UsageResult &Usages, + RangeDescriptor &Descriptor) { + // On arrays and pseudoarrays, we must figure out the qualifiers from the + // usages. + if (usagesAreConst(Context, Usages) || + containerIsConst(ContainerExpr, Descriptor.ContainerNeedsDereference)) { + Descriptor.DerefByConstRef = true; + } + if (usagesReturnRValues(Usages)) { + // If the index usages (dereference, subscript, at, ...) return rvalues, + // then we should not use a reference, because we need to keep the code + // correct if it mutates the returned objects. + Descriptor.DerefByValue = true; + } + // Try to find the type of the elements on the container, to check if + // they are trivially copyable. + for (const Usage &U : Usages) { + if (!U.Expression || U.Expression->getType().isNull()) + continue; + QualType Type = U.Expression->getType().getCanonicalType(); + if (U.Kind == Usage::UK_MemberThroughArrow) { + if (!Type->isPointerType()) { + continue; + } + Type = Type->getPointeeType(); + } + Descriptor.ElemType = Type; + } +} + +/// \brief Determines what kind of 'auto' must be used after converting an +/// iterator based for loop. +void LoopConvertCheck::getIteratorLoopQualifiers(ASTContext *Context, + const BoundNodes &Nodes, + RangeDescriptor &Descriptor) { + // The matchers for iterator loops provide bound nodes to obtain this + // information. + const auto *InitVar = Nodes.getDeclAs(InitVarName); + QualType CanonicalInitVarType = InitVar->getType().getCanonicalType(); + const auto *DerefByValueType = + Nodes.getNodeAs(DerefByValueResultName); + Descriptor.DerefByValue = DerefByValueType; + + if (Descriptor.DerefByValue) { + // If the dereference operator returns by value then test for the + // canonical const qualification of the init variable type. + Descriptor.DerefByConstRef = CanonicalInitVarType.isConstQualified(); + Descriptor.ElemType = *DerefByValueType; + } else { + if (const auto *DerefType = + Nodes.getNodeAs(DerefByRefResultName)) { + // A node will only be bound with DerefByRefResultName if we're dealing + // with a user-defined iterator type. Test the const qualification of + // the reference type. + auto ValueType = DerefType->getNonReferenceType(); + + Descriptor.DerefByConstRef = ValueType.isConstQualified(); + Descriptor.ElemType = ValueType; + } else { + // By nature of the matcher this case is triggered only for built-in + // iterator types (i.e. pointers). + assert(isa(CanonicalInitVarType) && + "Non-class iterator type is not a pointer type"); + + // We test for const qualification of the pointed-at type. + Descriptor.DerefByConstRef = + CanonicalInitVarType->getPointeeType().isConstQualified(); + Descriptor.ElemType = CanonicalInitVarType->getPointeeType(); + } + } +} + +/// \brief Determines the parameters needed to build the range replacement. +void LoopConvertCheck::determineRangeDescriptor( + ASTContext *Context, const BoundNodes &Nodes, const ForStmt *Loop, + LoopFixerKind FixerKind, const Expr *ContainerExpr, + const UsageResult &Usages, RangeDescriptor &Descriptor) { + Descriptor.ContainerString = getContainerString(Context, Loop, ContainerExpr); + + if (FixerKind == LFK_Iterator) + getIteratorLoopQualifiers(Context, Nodes, Descriptor); + else + getArrayLoopQualifiers(Context, Nodes, ContainerExpr, Usages, Descriptor); +} + +/// \brief Check some of the conditions that must be met for the loop to be +/// convertible. +bool LoopConvertCheck::isConvertible(ASTContext *Context, + const ast_matchers::BoundNodes &Nodes, + const ForStmt *Loop, + LoopFixerKind FixerKind) { + // If we already modified the range of this for loop, don't do any further + // updates on this iteration. + if (TUInfo->getReplacedVars().count(Loop)) + return false; + + // Check that we have exactly one index variable and at most one end variable. + const auto *LoopVar = Nodes.getDeclAs(IncrementVarName); + const auto *CondVar = Nodes.getDeclAs(ConditionVarName); + const auto *InitVar = Nodes.getDeclAs(InitVarName); + if (!areSameVariable(LoopVar, CondVar) || !areSameVariable(LoopVar, InitVar)) + return false; + const auto *EndVar = Nodes.getDeclAs(EndVarName); + const auto *ConditionEndVar = Nodes.getDeclAs(ConditionEndVarName); + if (EndVar && !areSameVariable(EndVar, ConditionEndVar)) + return false; + + // FIXME: Try to put most of this logic inside a matcher. + if (FixerKind == LFK_Iterator) { + QualType InitVarType = InitVar->getType(); + QualType CanonicalInitVarType = InitVarType.getCanonicalType(); + + const auto *BeginCall = Nodes.getNodeAs(BeginCallName); + assert(BeginCall && "Bad Callback. No begin call expression"); + QualType CanonicalBeginType = + BeginCall->getMethodDecl()->getReturnType().getCanonicalType(); + if (CanonicalBeginType->isPointerType() && + CanonicalInitVarType->isPointerType()) { + // If the initializer and the variable are both pointers check if the + // un-qualified pointee types match, otherwise we don't use auto. + if (!Context->hasSameUnqualifiedType( + CanonicalBeginType->getPointeeType(), + CanonicalInitVarType->getPointeeType())) + return false; + } else if (!Context->hasSameType(CanonicalInitVarType, + CanonicalBeginType)) { + // Check for qualified types to avoid conversions from non-const to const + // iterator types. + return false; + } + } else if (FixerKind == LFK_PseudoArray) { + // This call is required to obtain the container. + const auto *EndCall = Nodes.getStmtAs(EndCallName); + if (!EndCall || !dyn_cast(EndCall->getCallee())) + return false; + } + return true; +} + +void LoopConvertCheck::check(const MatchFinder::MatchResult &Result) { + const BoundNodes &Nodes = Result.Nodes; + Confidence ConfidenceLevel(Confidence::CL_Safe); + ASTContext *Context = Result.Context; + + const ForStmt *Loop; + LoopFixerKind FixerKind; + RangeDescriptor Descriptor; + + if ((Loop = Nodes.getStmtAs(LoopNameArray))) { + FixerKind = LFK_Array; + } else if ((Loop = Nodes.getStmtAs(LoopNameIterator))) { + FixerKind = LFK_Iterator; + } else { + Loop = Nodes.getStmtAs(LoopNamePseudoArray); + assert(Loop && "Bad Callback. No for statement"); + FixerKind = LFK_PseudoArray; + } + + if (!isConvertible(Context, Nodes, Loop, FixerKind)) + return; + + const auto *LoopVar = Nodes.getDeclAs(IncrementVarName); + const auto *EndVar = Nodes.getDeclAs(EndVarName); + + // If the loop calls end()/size() after each iteration, lower our confidence + // level. + if (FixerKind != LFK_Array && !EndVar) + ConfidenceLevel.lowerTo(Confidence::CL_Reasonable); + + // If the end comparison isn't a variable, we can try to work with the + // expression the loop variable is being tested against instead. + const auto *EndCall = Nodes.getStmtAs(EndCallName); + const auto *BoundExpr = Nodes.getStmtAs(ConditionBoundName); + + // Find container expression of iterators and pseudoarrays, and determine if + // this expression needs to be dereferenced to obtain the container. + // With array loops, the container is often discovered during the + // ForLoopIndexUseVisitor traversal. + const Expr *ContainerExpr = nullptr; + if (FixerKind == LFK_Iterator) { + ContainerExpr = findContainer(Context, LoopVar->getInit(), + EndVar ? EndVar->getInit() : EndCall, + &Descriptor.ContainerNeedsDereference); + } else if (FixerKind == LFK_PseudoArray) { + ContainerExpr = EndCall->getImplicitObjectArgument(); + Descriptor.ContainerNeedsDereference = + dyn_cast(EndCall->getCallee())->isArrow(); + } + + // We must know the container or an array length bound. + if (!ContainerExpr && !BoundExpr) + return; + + ForLoopIndexUseVisitor Finder(Context, LoopVar, EndVar, ContainerExpr, + BoundExpr, + Descriptor.ContainerNeedsDereference); + + // Find expressions and variables on which the container depends. + if (ContainerExpr) { + ComponentFinderASTVisitor ComponentFinder; + ComponentFinder.findExprComponents(ContainerExpr->IgnoreParenImpCasts()); + Finder.addComponents(ComponentFinder.getComponents()); + } + + // Find usages of the loop index. If they are not used in a convertible way, + // stop here. + if (!Finder.findAndVerifyUsages(Loop->getBody())) + return; + ConfidenceLevel.lowerTo(Finder.getConfidenceLevel()); + + // Obtain the container expression, if we don't have it yet. + if (FixerKind == LFK_Array) { + ContainerExpr = Finder.getContainerIndexed()->IgnoreParenImpCasts(); + + // Very few loops are over expressions that generate arrays rather than + // array variables. Consider loops over arrays that aren't just represented + // by a variable to be risky conversions. + if (!getReferencedVariable(ContainerExpr) && + !isDirectMemberExpr(ContainerExpr)) + ConfidenceLevel.lowerTo(Confidence::CL_Risky); + } + + // Find out which qualifiers we have to use in the loop range. + const UsageResult &Usages = Finder.getUsages(); + determineRangeDescriptor(Context, Nodes, Loop, FixerKind, ContainerExpr, + Usages, Descriptor); + + // Ensure that we do not try to move an expression dependent on a local + // variable declared inside the loop outside of it. + // FIXME: Determine when the external dependency isn't an expression converted + // by another loop. + TUInfo->getParentFinder().gatherAncestors(Context->getTranslationUnitDecl()); + DependencyFinderASTVisitor DependencyFinder( + &TUInfo->getParentFinder().getStmtToParentStmtMap(), + &TUInfo->getParentFinder().getDeclToParentStmtMap(), + &TUInfo->getReplacedVars(), Loop); + + if (DependencyFinder.dependsOnInsideVariable(ContainerExpr) || + Descriptor.ContainerString.empty() || Usages.empty() || + ConfidenceLevel.getLevel() < MinConfidence) + return; + + doConversion(Context, LoopVar, getReferencedVariable(ContainerExpr), Usages, + Finder.getAliasDecl(), Finder.aliasUseRequired(), + Finder.aliasFromForInit(), Loop, Descriptor); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/LoopConvertCheck.h b/clang-tidy/modernize/LoopConvertCheck.h new file mode 100644 index 00000000..75ab25aa --- /dev/null +++ b/clang-tidy/modernize/LoopConvertCheck.h @@ -0,0 +1,78 @@ +//===--- LoopConvertCheck.h - clang-tidy-------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_H + +#include "../ClangTidy.h" +#include "LoopConvertUtils.h" + +namespace clang { +namespace tidy { +namespace modernize { + +class LoopConvertCheck : public ClangTidyCheck { +public: + LoopConvertCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + struct RangeDescriptor { + RangeDescriptor(); + bool ContainerNeedsDereference; + bool DerefByConstRef; + bool DerefByValue; + std::string ContainerString; + QualType ElemType; + }; + + void getAliasRange(SourceManager &SM, SourceRange &DeclRange); + + void doConversion(ASTContext *Context, const VarDecl *IndexVar, + const ValueDecl *MaybeContainer, const UsageResult &Usages, + const DeclStmt *AliasDecl, bool AliasUseRequired, + bool AliasFromForInit, const ForStmt *Loop, + RangeDescriptor Descriptor); + + StringRef getContainerString(ASTContext *Context, const ForStmt *Loop, + const Expr *ContainerExpr); + + void getArrayLoopQualifiers(ASTContext *Context, + const ast_matchers::BoundNodes &Nodes, + const Expr *ContainerExpr, + const UsageResult &Usages, + RangeDescriptor &Descriptor); + + void getIteratorLoopQualifiers(ASTContext *Context, + const ast_matchers::BoundNodes &Nodes, + RangeDescriptor &Descriptor); + + void determineRangeDescriptor(ASTContext *Context, + const ast_matchers::BoundNodes &Nodes, + const ForStmt *Loop, LoopFixerKind FixerKind, + const Expr *ContainerExpr, + const UsageResult &Usages, + RangeDescriptor &Descriptor); + + bool isConvertible(ASTContext *Context, const ast_matchers::BoundNodes &Nodes, + const ForStmt *Loop, LoopFixerKind FixerKind); + + std::unique_ptr TUInfo; + const unsigned long long MaxCopySize; + const Confidence::Level MinConfidence; + const VariableNamer::NamingStyle NamingStyle; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_H diff --git a/clang-tidy/modernize/LoopConvertUtils.cpp b/clang-tidy/modernize/LoopConvertUtils.cpp new file mode 100644 index 00000000..c50e9238 --- /dev/null +++ b/clang-tidy/modernize/LoopConvertUtils.cpp @@ -0,0 +1,895 @@ +//===--- LoopConvertUtils.cpp - clang-tidy --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "LoopConvertUtils.h" + +using namespace clang::ast_matchers; +using namespace clang::tooling; +using namespace clang; +using namespace llvm; + +namespace clang { +namespace tidy { +namespace modernize { + +/// \brief Tracks a stack of parent statements during traversal. +/// +/// All this really does is inject push_back() before running +/// RecursiveASTVisitor::TraverseStmt() and pop_back() afterwards. The Stmt atop +/// the stack is the parent of the current statement (NULL for the topmost +/// statement). +bool StmtAncestorASTVisitor::TraverseStmt(Stmt *Statement) { + StmtAncestors.insert(std::make_pair(Statement, StmtStack.back())); + StmtStack.push_back(Statement); + RecursiveASTVisitor::TraverseStmt(Statement); + StmtStack.pop_back(); + return true; +} + +/// \brief Keep track of the DeclStmt associated with each VarDecl. +/// +/// Combined with StmtAncestors, this provides roughly the same information as +/// Scope, as we can map a VarDecl to its DeclStmt, then walk up the parent tree +/// using StmtAncestors. +bool StmtAncestorASTVisitor::VisitDeclStmt(DeclStmt *Decls) { + for (const auto *decl : Decls->decls()) { + if (const auto *V = dyn_cast(decl)) + DeclParents.insert(std::make_pair(V, Decls)); + } + return true; +} + +/// \brief record the DeclRefExpr as part of the parent expression. +bool ComponentFinderASTVisitor::VisitDeclRefExpr(DeclRefExpr *E) { + Components.push_back(E); + return true; +} + +/// \brief record the MemberExpr as part of the parent expression. +bool ComponentFinderASTVisitor::VisitMemberExpr(MemberExpr *Member) { + Components.push_back(Member); + return true; +} + +/// \brief Forward any DeclRefExprs to a check on the referenced variable +/// declaration. +bool DependencyFinderASTVisitor::VisitDeclRefExpr(DeclRefExpr *DeclRef) { + if (auto *V = dyn_cast_or_null(DeclRef->getDecl())) + return VisitVarDecl(V); + return true; +} + +/// \brief Determine if any this variable is declared inside the ContainingStmt. +bool DependencyFinderASTVisitor::VisitVarDecl(VarDecl *V) { + const Stmt *Curr = DeclParents->lookup(V); + // First, see if the variable was declared within an inner scope of the loop. + while (Curr != nullptr) { + if (Curr == ContainingStmt) { + DependsOnInsideVariable = true; + return false; + } + Curr = StmtParents->lookup(Curr); + } + + // Next, check if the variable was removed from existence by an earlier + // iteration. + for (const auto &I : *ReplacedVars) { + if (I.second == V) { + DependsOnInsideVariable = true; + return false; + } + } + return true; +} + +/// \brief If we already created a variable for TheLoop, check to make sure +/// that the name was not already taken. +bool DeclFinderASTVisitor::VisitForStmt(ForStmt *TheLoop) { + StmtGeneratedVarNameMap::const_iterator I = GeneratedDecls->find(TheLoop); + if (I != GeneratedDecls->end() && I->second == Name) { + Found = true; + return false; + } + return true; +} + +/// \brief If any named declaration within the AST subtree has the same name, +/// then consider Name already taken. +bool DeclFinderASTVisitor::VisitNamedDecl(NamedDecl *D) { + const IdentifierInfo *Ident = D->getIdentifier(); + if (Ident && Ident->getName() == Name) { + Found = true; + return false; + } + return true; +} + +/// \brief Forward any declaration references to the actual check on the +/// referenced declaration. +bool DeclFinderASTVisitor::VisitDeclRefExpr(DeclRefExpr *DeclRef) { + if (auto *D = dyn_cast(DeclRef->getDecl())) + return VisitNamedDecl(D); + return true; +} + +/// \brief If the new variable name conflicts with any type used in the loop, +/// then we mark that variable name as taken. +bool DeclFinderASTVisitor::VisitTypeLoc(TypeLoc TL) { + QualType QType = TL.getType(); + + // Check if our name conflicts with a type, to handle for typedefs. + if (QType.getAsString() == Name) { + Found = true; + return false; + } + // Check for base type conflicts. For example, when a struct is being + // referenced in the body of the loop, the above getAsString() will return the + // whole type (ex. "struct s"), but will be caught here. + if (const IdentifierInfo *Ident = QType.getBaseTypeIdentifier()) { + if (Ident->getName() == Name) { + Found = true; + return false; + } + } + return true; +} + +/// \brief Look through conversion/copy constructors to find the explicit +/// initialization expression, returning it is found. +/// +/// The main idea is that given +/// vector v; +/// we consider either of these initializations +/// vector::iterator it = v.begin(); +/// vector::iterator it(v.begin()); +/// and retrieve `v.begin()` as the expression used to initialize `it` but do +/// not include +/// vector::iterator it; +/// vector::iterator it(v.begin(), 0); // if this constructor existed +/// as being initialized from `v.begin()` +const Expr *digThroughConstructors(const Expr *E) { + if (!E) + return nullptr; + E = E->IgnoreParenImpCasts(); + if (const auto *ConstructExpr = dyn_cast(E)) { + // The initial constructor must take exactly one parameter, but base class + // and deferred constructors can take more. + if (ConstructExpr->getNumArgs() != 1 || + ConstructExpr->getConstructionKind() != CXXConstructExpr::CK_Complete) + return nullptr; + E = ConstructExpr->getArg(0); + if (const auto *Temp = dyn_cast(E)) + E = Temp->GetTemporaryExpr(); + return digThroughConstructors(E); + } + return E; +} + +/// \brief Returns true when two Exprs are equivalent. +bool areSameExpr(ASTContext *Context, const Expr *First, const Expr *Second) { + if (!First || !Second) + return false; + + llvm::FoldingSetNodeID FirstID, SecondID; + First->Profile(FirstID, *Context, true); + Second->Profile(SecondID, *Context, true); + return FirstID == SecondID; +} + +/// \brief Returns the DeclRefExpr represented by E, or NULL if there isn't one. +const DeclRefExpr *getDeclRef(const Expr *E) { + return dyn_cast(E->IgnoreParenImpCasts()); +} + +/// \brief Returns true when two ValueDecls are the same variable. +bool areSameVariable(const ValueDecl *First, const ValueDecl *Second) { + return First && Second && + First->getCanonicalDecl() == Second->getCanonicalDecl(); +} + +/// \brief Determines if an expression is a declaration reference to a +/// particular variable. +static bool exprReferencesVariable(const ValueDecl *Target, const Expr *E) { + if (!Target || !E) + return false; + const DeclRefExpr *Decl = getDeclRef(E); + return Decl && areSameVariable(Target, Decl->getDecl()); +} + +/// \brief If the expression is a dereference or call to operator*(), return the +/// operand. Otherwise, return NULL. +static const Expr *getDereferenceOperand(const Expr *E) { + if (const auto *Uop = dyn_cast(E)) + return Uop->getOpcode() == UO_Deref ? Uop->getSubExpr() : nullptr; + + if (const auto *OpCall = dyn_cast(E)) { + return OpCall->getOperator() == OO_Star && OpCall->getNumArgs() == 1 + ? OpCall->getArg(0) + : nullptr; + } + + return nullptr; +} + +/// \brief Returns true when the Container contains an Expr equivalent to E. +template +static bool containsExpr(ASTContext *Context, const ContainerT *Container, + const Expr *E) { + llvm::FoldingSetNodeID ID; + E->Profile(ID, *Context, true); + for (const auto &I : *Container) { + if (ID == I.second) + return true; + } + return false; +} + +/// \brief Returns true when the index expression is a declaration reference to +/// IndexVar. +/// +/// If the index variable is `index`, this function returns true on +/// arrayExpression[index]; +/// containerExpression[index]; +/// but not +/// containerExpression[notIndex]; +static bool isIndexInSubscriptExpr(const Expr *IndexExpr, + const VarDecl *IndexVar) { + const DeclRefExpr *Idx = getDeclRef(IndexExpr); + return Idx && Idx->getType()->isIntegerType() && + areSameVariable(IndexVar, Idx->getDecl()); +} + +/// \brief Returns true when the index expression is a declaration reference to +/// IndexVar, Obj is the same expression as SourceExpr after all parens and +/// implicit casts are stripped off. +/// +/// If PermitDeref is true, IndexExpression may +/// be a dereference (overloaded or builtin operator*). +/// +/// This function is intended for array-like containers, as it makes sure that +/// both the container and the index match. +/// If the loop has index variable `index` and iterates over `container`, then +/// isIndexInSubscriptExpr returns true for +/// \code +/// container[index] +/// container.at(index) +/// container->at(index) +/// \endcode +/// but not for +/// \code +/// container[notIndex] +/// notContainer[index] +/// \endcode +/// If PermitDeref is true, then isIndexInSubscriptExpr additionally returns +/// true on these expressions: +/// \code +/// (*container)[index] +/// (*container).at(index) +/// \endcode +static bool isIndexInSubscriptExpr(ASTContext *Context, const Expr *IndexExpr, + const VarDecl *IndexVar, const Expr *Obj, + const Expr *SourceExpr, bool PermitDeref) { + if (!SourceExpr || !Obj || !isIndexInSubscriptExpr(IndexExpr, IndexVar)) + return false; + + if (areSameExpr(Context, SourceExpr->IgnoreParenImpCasts(), + Obj->IgnoreParenImpCasts())) + return true; + + if (const Expr *InnerObj = getDereferenceOperand(Obj->IgnoreParenImpCasts())) + if (PermitDeref && areSameExpr(Context, SourceExpr->IgnoreParenImpCasts(), + InnerObj->IgnoreParenImpCasts())) + return true; + + return false; +} + +/// \brief Returns true when Opcall is a call a one-parameter dereference of +/// IndexVar. +/// +/// For example, if the index variable is `index`, returns true for +/// *index +/// but not +/// index +/// *notIndex +static bool isDereferenceOfOpCall(const CXXOperatorCallExpr *OpCall, + const VarDecl *IndexVar) { + return OpCall->getOperator() == OO_Star && OpCall->getNumArgs() == 1 && + exprReferencesVariable(IndexVar, OpCall->getArg(0)); +} + +/// \brief Returns true when Uop is a dereference of IndexVar. +/// +/// For example, if the index variable is `index`, returns true for +/// *index +/// but not +/// index +/// *notIndex +static bool isDereferenceOfUop(const UnaryOperator *Uop, + const VarDecl *IndexVar) { + return Uop->getOpcode() == UO_Deref && + exprReferencesVariable(IndexVar, Uop->getSubExpr()); +} + +/// \brief Determines whether the given Decl defines a variable initialized to +/// the loop object. +/// +/// This is intended to find cases such as +/// \code +/// for (int i = 0; i < arraySize(arr); ++i) { +/// T t = arr[i]; +/// // use t, do not use i +/// } +/// \endcode +/// and +/// \code +/// for (iterator i = container.begin(), e = container.end(); i != e; ++i) { +/// T t = *i; +/// // use t, do not use i +/// } +/// \endcode +static bool isAliasDecl(ASTContext *Context, const Decl *TheDecl, + const VarDecl *IndexVar) { + const auto *VDecl = dyn_cast(TheDecl); + if (!VDecl) + return false; + if (!VDecl->hasInit()) + return false; + + bool OnlyCasts = true; + const Expr *Init = VDecl->getInit()->IgnoreParenImpCasts(); + if (Init && isa(Init)) { + Init = digThroughConstructors(Init); + OnlyCasts = false; + } + if (!Init) + return false; + + // Check that the declared type is the same as (or a reference to) the + // container type. + if (!OnlyCasts) { + QualType InitType = Init->getType(); + QualType DeclarationType = VDecl->getType(); + if (!DeclarationType.isNull() && DeclarationType->isReferenceType()) + DeclarationType = DeclarationType.getNonReferenceType(); + + if (InitType.isNull() || DeclarationType.isNull() || + !Context->hasSameUnqualifiedType(DeclarationType, InitType)) + return false; + } + + switch (Init->getStmtClass()) { + case Stmt::ArraySubscriptExprClass: { + const auto *E = cast(Init); + // We don't really care which array is used here. We check to make sure + // it was the correct one later, since the AST will traverse it next. + return isIndexInSubscriptExpr(E->getIdx(), IndexVar); + } + + case Stmt::UnaryOperatorClass: + return isDereferenceOfUop(cast(Init), IndexVar); + + case Stmt::CXXOperatorCallExprClass: { + const auto *OpCall = cast(Init); + if (OpCall->getOperator() == OO_Star) + return isDereferenceOfOpCall(OpCall, IndexVar); + if (OpCall->getOperator() == OO_Subscript) { + assert(OpCall->getNumArgs() == 2); + return isIndexInSubscriptExpr(OpCall->getArg(1), IndexVar); + } + break; + } + + case Stmt::CXXMemberCallExprClass: { + const auto *MemCall = cast(Init); + // This check is needed because getMethodDecl can return nullptr if the + // callee is a member function pointer. + const auto *MDecl = MemCall->getMethodDecl(); + if (MDecl && !isa(MDecl) && MDecl->getName() == "at") { + assert(MemCall->getNumArgs() == 1); + return isIndexInSubscriptExpr(MemCall->getArg(0), IndexVar); + } + return false; + } + + default: + break; + } + return false; +} + +/// \brief Determines whether the bound of a for loop condition expression is +/// the same as the statically computable size of ArrayType. +/// +/// Given +/// \code +/// const int N = 5; +/// int arr[N]; +/// \endcode +/// This is intended to permit +/// \code +/// for (int i = 0; i < N; ++i) { /* use arr[i] */ } +/// for (int i = 0; i < arraysize(arr); ++i) { /* use arr[i] */ } +/// \endcode +static bool arrayMatchesBoundExpr(ASTContext *Context, + const QualType &ArrayType, + const Expr *ConditionExpr) { + if (!ConditionExpr || ConditionExpr->isValueDependent()) + return false; + const ConstantArrayType *ConstType = + Context->getAsConstantArrayType(ArrayType); + if (!ConstType) + return false; + llvm::APSInt ConditionSize; + if (!ConditionExpr->isIntegerConstantExpr(ConditionSize, *Context)) + return false; + llvm::APSInt ArraySize(ConstType->getSize()); + return llvm::APSInt::isSameValue(ConditionSize, ArraySize); +} + +ForLoopIndexUseVisitor::ForLoopIndexUseVisitor(ASTContext *Context, + const VarDecl *IndexVar, + const VarDecl *EndVar, + const Expr *ContainerExpr, + const Expr *ArrayBoundExpr, + bool ContainerNeedsDereference) + : Context(Context), IndexVar(IndexVar), EndVar(EndVar), + ContainerExpr(ContainerExpr), ArrayBoundExpr(ArrayBoundExpr), + ContainerNeedsDereference(ContainerNeedsDereference), + OnlyUsedAsIndex(true), AliasDecl(nullptr), + ConfidenceLevel(Confidence::CL_Safe), NextStmtParent(nullptr), + CurrStmtParent(nullptr), ReplaceWithAliasUse(false), + AliasFromForInit(false) { + if (ContainerExpr) + addComponent(ContainerExpr); +} + +bool ForLoopIndexUseVisitor::findAndVerifyUsages(const Stmt *Body) { + TraverseStmt(const_cast(Body)); + return OnlyUsedAsIndex && ContainerExpr; +} + +void ForLoopIndexUseVisitor::addComponents(const ComponentVector &Components) { + // FIXME: add sort(on ID)+unique to avoid extra work. + for (const auto &I : Components) + addComponent(I); +} + +void ForLoopIndexUseVisitor::addComponent(const Expr *E) { + FoldingSetNodeID ID; + const Expr *Node = E->IgnoreParenImpCasts(); + Node->Profile(ID, *Context, true); + DependentExprs.push_back(std::make_pair(Node, ID)); +} + +void ForLoopIndexUseVisitor::addUsage(const Usage &U) { + SourceLocation Begin = U.Range.getBegin(); + if (Begin.isMacroID()) + Begin = Context->getSourceManager().getSpellingLoc(Begin); + + if (UsageLocations.insert(Begin).second) + Usages.push_back(U); +} + +/// \brief If the unary operator is a dereference of IndexVar, include it +/// as a valid usage and prune the traversal. +/// +/// For example, if container.begin() and container.end() both return pointers +/// to int, this makes sure that the initialization for `k` is not counted as an +/// unconvertible use of the iterator `i`. +/// \code +/// for (int *i = container.begin(), *e = container.end(); i != e; ++i) { +/// int k = *i + 2; +/// } +/// \endcode +bool ForLoopIndexUseVisitor::TraverseUnaryDeref(UnaryOperator *Uop) { + // If we dereference an iterator that's actually a pointer, count the + // occurrence. + if (isDereferenceOfUop(Uop, IndexVar)) { + addUsage(Usage(Uop)); + return true; + } + + return VisitorBase::TraverseUnaryOperator(Uop); +} + +/// \brief If the member expression is operator-> (overloaded or not) on +/// IndexVar, include it as a valid usage and prune the traversal. +/// +/// For example, given +/// \code +/// struct Foo { int bar(); int x; }; +/// vector v; +/// \endcode +/// the following uses will be considered convertible: +/// \code +/// for (vector::iterator i = v.begin(), e = v.end(); i != e; ++i) { +/// int b = i->bar(); +/// int k = i->x + 1; +/// } +/// \endcode +/// though +/// \code +/// for (vector::iterator i = v.begin(), e = v.end(); i != e; ++i) { +/// int k = i.insert(1); +/// } +/// for (vector::iterator i = v.begin(), e = v.end(); i != e; ++i) { +/// int b = e->bar(); +/// } +/// \endcode +/// will not. +bool ForLoopIndexUseVisitor::TraverseMemberExpr(MemberExpr *Member) { + const Expr *Base = Member->getBase(); + const DeclRefExpr *Obj = getDeclRef(Base); + const Expr *ResultExpr = Member; + QualType ExprType; + if (const auto *Call = + dyn_cast(Base->IgnoreParenImpCasts())) { + // If operator->() is a MemberExpr containing a CXXOperatorCallExpr, then + // the MemberExpr does not have the expression we want. We therefore catch + // that instance here. + // For example, if vector::iterator defines operator->(), then the + // example `i->bar()` at the top of this function is a CXXMemberCallExpr + // referring to `i->` as the member function called. We want just `i`, so + // we take the argument to operator->() as the base object. + if (Call->getOperator() == OO_Arrow) { + assert(Call->getNumArgs() == 1 && + "Operator-> takes more than one argument"); + Obj = getDeclRef(Call->getArg(0)); + ResultExpr = Obj; + ExprType = Call->getCallReturnType(*Context); + } + } + + if (Obj && exprReferencesVariable(IndexVar, Obj)) { + // Member calls on the iterator with '.' are not allowed. + if (!Member->isArrow()) { + OnlyUsedAsIndex = false; + return true; + } + + if (ExprType.isNull()) + ExprType = Obj->getType(); + + assert(ExprType->isPointerType() && "Operator-> returned non-pointer type"); + // FIXME: This works around not having the location of the arrow operator. + // Consider adding OperatorLoc to MemberExpr? + SourceLocation ArrowLoc = Lexer::getLocForEndOfToken( + Base->getExprLoc(), 0, Context->getSourceManager(), + Context->getLangOpts()); + // If something complicated is happening (i.e. the next token isn't an + // arrow), give up on making this work. + if (ArrowLoc.isValid()) { + addUsage(Usage(ResultExpr, Usage::UK_MemberThroughArrow, + SourceRange(Base->getExprLoc(), ArrowLoc))); + return true; + } + } + return VisitorBase::TraverseMemberExpr(Member); +} + +/// \brief If a member function call is the at() accessor on the container with +/// IndexVar as the single argument, include it as a valid usage and prune +/// the traversal. +/// +/// Member calls on other objects will not be permitted. +/// Calls on the iterator object are not permitted, unless done through +/// operator->(). The one exception is allowing vector::at() for pseudoarrays. +bool ForLoopIndexUseVisitor::TraverseCXXMemberCallExpr( + CXXMemberCallExpr *MemberCall) { + auto *Member = + dyn_cast(MemberCall->getCallee()->IgnoreParenImpCasts()); + if (!Member) + return VisitorBase::TraverseCXXMemberCallExpr(MemberCall); + + // We specifically allow an accessor named "at" to let STL in, though + // this is restricted to pseudo-arrays by requiring a single, integer + // argument. + const IdentifierInfo *Ident = Member->getMemberDecl()->getIdentifier(); + if (Ident && Ident->isStr("at") && MemberCall->getNumArgs() == 1) { + if (isIndexInSubscriptExpr(Context, MemberCall->getArg(0), IndexVar, + Member->getBase(), ContainerExpr, + ContainerNeedsDereference)) { + addUsage(Usage(MemberCall)); + return true; + } + } + + if (containsExpr(Context, &DependentExprs, Member->getBase())) + ConfidenceLevel.lowerTo(Confidence::CL_Risky); + + return VisitorBase::TraverseCXXMemberCallExpr(MemberCall); +} + +/// \brief If an overloaded operator call is a dereference of IndexVar or +/// a subscript of the container with IndexVar as the single argument, +/// include it as a valid usage and prune the traversal. +/// +/// For example, given +/// \code +/// struct Foo { int bar(); int x; }; +/// vector v; +/// void f(Foo); +/// \endcode +/// the following uses will be considered convertible: +/// \code +/// for (vector::iterator i = v.begin(), e = v.end(); i != e; ++i) { +/// f(*i); +/// } +/// for (int i = 0; i < v.size(); ++i) { +/// int i = v[i] + 1; +/// } +/// \endcode +bool ForLoopIndexUseVisitor::TraverseCXXOperatorCallExpr( + CXXOperatorCallExpr *OpCall) { + switch (OpCall->getOperator()) { + case OO_Star: + if (isDereferenceOfOpCall(OpCall, IndexVar)) { + addUsage(Usage(OpCall)); + return true; + } + break; + + case OO_Subscript: + if (OpCall->getNumArgs() != 2) + break; + if (isIndexInSubscriptExpr(Context, OpCall->getArg(1), IndexVar, + OpCall->getArg(0), ContainerExpr, + ContainerNeedsDereference)) { + addUsage(Usage(OpCall)); + return true; + } + break; + + default: + break; + } + return VisitorBase::TraverseCXXOperatorCallExpr(OpCall); +} + +/// \brief If we encounter an array with IndexVar as the index of an +/// ArraySubsriptExpression, note it as a consistent usage and prune the +/// AST traversal. +/// +/// For example, given +/// \code +/// const int N = 5; +/// int arr[N]; +/// \endcode +/// This is intended to permit +/// \code +/// for (int i = 0; i < N; ++i) { /* use arr[i] */ } +/// \endcode +/// but not +/// \code +/// for (int i = 0; i < N; ++i) { /* use notArr[i] */ } +/// \endcode +/// and further checking needs to be done later to ensure that exactly one array +/// is referenced. +bool ForLoopIndexUseVisitor::TraverseArraySubscriptExpr(ArraySubscriptExpr *E) { + Expr *Arr = E->getBase(); + if (!isIndexInSubscriptExpr(E->getIdx(), IndexVar)) + return VisitorBase::TraverseArraySubscriptExpr(E); + + if ((ContainerExpr && + !areSameExpr(Context, Arr->IgnoreParenImpCasts(), + ContainerExpr->IgnoreParenImpCasts())) || + !arrayMatchesBoundExpr(Context, Arr->IgnoreImpCasts()->getType(), + ArrayBoundExpr)) { + // If we have already discovered the array being indexed and this isn't it + // or this array doesn't match, mark this loop as unconvertible. + OnlyUsedAsIndex = false; + return VisitorBase::TraverseArraySubscriptExpr(E); + } + + if (!ContainerExpr) + ContainerExpr = Arr; + + addUsage(Usage(E)); + return true; +} + +/// \brief If we encounter a reference to IndexVar in an unpruned branch of the +/// traversal, mark this loop as unconvertible. +/// +/// This implements the whitelist for convertible loops: any usages of IndexVar +/// not explicitly considered convertible by this traversal will be caught by +/// this function. +/// +/// Additionally, if the container expression is more complex than just a +/// DeclRefExpr, and some part of it is appears elsewhere in the loop, lower +/// our confidence in the transformation. +/// +/// For example, these are not permitted: +/// \code +/// for (int i = 0; i < N; ++i) { printf("arr[%d] = %d", i, arr[i]); } +/// for (vector::iterator i = container.begin(), e = container.end(); +/// i != e; ++i) +/// i.insert(0); +/// for (vector::iterator i = container.begin(), e = container.end(); +/// i != e; ++i) +/// if (i + 1 != e) +/// printf("%d", *i); +/// \endcode +/// +/// And these will raise the risk level: +/// \code +/// int arr[10][20]; +/// int l = 5; +/// for (int j = 0; j < 20; ++j) +/// int k = arr[l][j] + l; // using l outside arr[l] is considered risky +/// for (int i = 0; i < obj.getVector().size(); ++i) +/// obj.foo(10); // using `obj` is considered risky +/// \endcode +bool ForLoopIndexUseVisitor::VisitDeclRefExpr(DeclRefExpr *E) { + const ValueDecl *TheDecl = E->getDecl(); + if (areSameVariable(IndexVar, TheDecl) || + exprReferencesVariable(IndexVar, E) || areSameVariable(EndVar, TheDecl) || + exprReferencesVariable(EndVar, E)) + OnlyUsedAsIndex = false; + if (containsExpr(Context, &DependentExprs, E)) + ConfidenceLevel.lowerTo(Confidence::CL_Risky); + return true; +} + +/// \brief If the loop index is captured by a lambda, replace this capture +/// by the range-for loop variable. +/// +/// For example: +/// \code +/// for (int i = 0; i < N; ++i) { +/// auto f = [v, i](int k) { +/// printf("%d\n", v[i] + k); +/// }; +/// f(v[i]); +/// } +/// \endcode +/// +/// Will be replaced by: +/// \code +/// for (auto & elem : v) { +/// auto f = [v, elem](int k) { +/// printf("%d\n", elem + k); +/// }; +/// f(elem); +/// } +/// \endcode +bool ForLoopIndexUseVisitor::TraverseLambdaCapture(LambdaExpr *LE, + const LambdaCapture *C) { + if (C->capturesVariable()) { + const VarDecl *VDecl = C->getCapturedVar(); + if (areSameVariable(IndexVar, cast(VDecl))) { + // FIXME: if the index is captured, it will count as an usage and the + // alias (if any) won't work, because it is only used in case of having + // exactly one usage. + addUsage(Usage(nullptr, + C->getCaptureKind() == LCK_ByCopy ? Usage::UK_CaptureByCopy + : Usage::UK_CaptureByRef, + C->getLocation())); + } + } + return VisitorBase::TraverseLambdaCapture(LE, C); +} + +/// \brief If we find that another variable is created just to refer to the loop +/// element, note it for reuse as the loop variable. +/// +/// See the comments for isAliasDecl. +bool ForLoopIndexUseVisitor::VisitDeclStmt(DeclStmt *S) { + if (!AliasDecl && S->isSingleDecl() && + isAliasDecl(Context, S->getSingleDecl(), IndexVar)) { + AliasDecl = S; + if (CurrStmtParent) { + if (isa(CurrStmtParent) || isa(CurrStmtParent) || + isa(CurrStmtParent)) + ReplaceWithAliasUse = true; + else if (isa(CurrStmtParent)) { + if (cast(CurrStmtParent)->getConditionVariableDeclStmt() == S) + ReplaceWithAliasUse = true; + else + // It's assumed S came the for loop's init clause. + AliasFromForInit = true; + } + } + } + + return true; +} + +bool ForLoopIndexUseVisitor::TraverseStmt(Stmt *S) { + // All this pointer swapping is a mechanism for tracking immediate parentage + // of Stmts. + const Stmt *OldNextParent = NextStmtParent; + CurrStmtParent = NextStmtParent; + NextStmtParent = S; + bool Result = VisitorBase::TraverseStmt(S); + NextStmtParent = OldNextParent; + return Result; +} + +std::string VariableNamer::createIndexName() { + // FIXME: Add in naming conventions to handle: + // - How to handle conflicts. + // - An interactive process for naming. + std::string IteratorName; + StringRef ContainerName; + if (TheContainer) + ContainerName = TheContainer->getName(); + + size_t Len = ContainerName.size(); + if (Len > 1 && ContainerName.endswith(Style == NS_UpperCase ? "S" : "s")) { + IteratorName = ContainerName.substr(0, Len - 1); + // E.g.: (auto thing : things) + if (!declarationExists(IteratorName) || IteratorName == OldIndex->getName()) + return IteratorName; + } + + if (Len > 2 && ContainerName.endswith(Style == NS_UpperCase ? "S_" : "s_")) { + IteratorName = ContainerName.substr(0, Len - 2); + // E.g.: (auto thing : things_) + if (!declarationExists(IteratorName) || IteratorName == OldIndex->getName()) + return IteratorName; + } + + return OldIndex->getName(); +} + +/// \brief Determines whether or not the the name \a Symbol conflicts with +/// language keywords or defined macros. Also checks if the name exists in +/// LoopContext, any of its parent contexts, or any of its child statements. +/// +/// We also check to see if the same identifier was generated by this loop +/// converter in a loop nested within SourceStmt. +bool VariableNamer::declarationExists(StringRef Symbol) { + assert(Context != nullptr && "Expected an ASTContext"); + IdentifierInfo &Ident = Context->Idents.get(Symbol); + + // Check if the symbol is not an identifier (ie. is a keyword or alias). + if (!isAnyIdentifier(Ident.getTokenID())) + return true; + + // Check for conflicting macro definitions. + if (Ident.hasMacroDefinition()) + return true; + + // Determine if the symbol was generated in a parent context. + for (const Stmt *S = SourceStmt; S != nullptr; S = ReverseAST->lookup(S)) { + StmtGeneratedVarNameMap::const_iterator I = GeneratedDecls->find(S); + if (I != GeneratedDecls->end() && I->second == Symbol) + return true; + } + + // FIXME: Rather than detecting conflicts at their usages, we should check the + // parent context. + // For some reason, lookup() always returns the pair (NULL, NULL) because its + // StoredDeclsMap is not initialized (i.e. LookupPtr.getInt() is false inside + // of DeclContext::lookup()). Why is this? + + // Finally, determine if the symbol was used in the loop or a child context. + DeclFinderASTVisitor DeclFinder(Symbol, GeneratedDecls); + return DeclFinder.findUsages(SourceStmt); +} + +std::string VariableNamer::AppendWithStyle(StringRef Str, + StringRef Suffix) const { + std::string Name = Str; + if (!Suffix.empty()) { + if (Style == NS_LowerCase || Style == NS_UpperCase) + Name += "_"; + int SuffixStart = Name.size(); + Name += Suffix; + if (Style == NS_CamelBack) + Name[SuffixStart] = toupper(Name[SuffixStart]); + } + return Name; +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/LoopConvertUtils.h b/clang-tidy/modernize/LoopConvertUtils.h new file mode 100644 index 00000000..4a6df187 --- /dev/null +++ b/clang-tidy/modernize/LoopConvertUtils.h @@ -0,0 +1,462 @@ +//===--- LoopConvertUtils.h - clang-tidy ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_UTILS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_UTILS_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/Refactoring.h" + +namespace clang { +namespace tidy { +namespace modernize { + +enum LoopFixerKind { LFK_Array, LFK_Iterator, LFK_PseudoArray }; + +/// A map used to walk the AST in reverse: maps child Stmt to parent Stmt. +typedef llvm::DenseMap StmtParentMap; + +/// A map used to walk the AST in reverse: +/// maps VarDecl to the to parent DeclStmt. +typedef llvm::DenseMap + DeclParentMap; + +/// A map used to track which variables have been removed by a refactoring pass. +/// It maps the parent ForStmt to the removed index variable's VarDecl. +typedef llvm::DenseMap + ReplacedVarsMap; + +/// A map used to remember the variable names generated in a Stmt +typedef llvm::DenseMap + StmtGeneratedVarNameMap; + +/// A vector used to store the AST subtrees of an Expr. +typedef llvm::SmallVector ComponentVector; + +/// \brief Class used build the reverse AST properties needed to detect +/// name conflicts and free variables. +class StmtAncestorASTVisitor + : public clang::RecursiveASTVisitor { +public: + StmtAncestorASTVisitor() { StmtStack.push_back(nullptr); } + + /// \brief Run the analysis on the TranslationUnitDecl. + /// + /// In case we're running this analysis multiple times, don't repeat the work. + void gatherAncestors(const clang::TranslationUnitDecl *T) { + if (StmtAncestors.empty()) + TraverseDecl(const_cast(T)); + } + + /// Accessor for StmtAncestors. + const StmtParentMap &getStmtToParentStmtMap() { return StmtAncestors; } + + /// Accessor for DeclParents. + const DeclParentMap &getDeclToParentStmtMap() { return DeclParents; } + + friend class clang::RecursiveASTVisitor; + +private: + StmtParentMap StmtAncestors; + DeclParentMap DeclParents; + llvm::SmallVector StmtStack; + + bool TraverseStmt(clang::Stmt *Statement); + bool VisitDeclStmt(clang::DeclStmt *Statement); +}; + +/// Class used to find the variables and member expressions on which an +/// arbitrary expression depends. +class ComponentFinderASTVisitor + : public clang::RecursiveASTVisitor { +public: + ComponentFinderASTVisitor() {} + + /// Find the components of an expression and place them in a ComponentVector. + void findExprComponents(const clang::Expr *SourceExpr) { + TraverseStmt(const_cast(SourceExpr)); + } + + /// Accessor for Components. + const ComponentVector &getComponents() { return Components; } + + friend class clang::RecursiveASTVisitor; + +private: + ComponentVector Components; + + bool VisitDeclRefExpr(clang::DeclRefExpr *E); + bool VisitMemberExpr(clang::MemberExpr *Member); +}; + +/// Class used to determine if an expression is dependent on a variable declared +/// inside of the loop where it would be used. +class DependencyFinderASTVisitor + : public clang::RecursiveASTVisitor { +public: + DependencyFinderASTVisitor(const StmtParentMap *StmtParents, + const DeclParentMap *DeclParents, + const ReplacedVarsMap *ReplacedVars, + const clang::Stmt *ContainingStmt) + : StmtParents(StmtParents), DeclParents(DeclParents), + ContainingStmt(ContainingStmt), ReplacedVars(ReplacedVars) {} + + /// \brief Run the analysis on Body, and return true iff the expression + /// depends on some variable declared within ContainingStmt. + /// + /// This is intended to protect against hoisting the container expression + /// outside of an inner context if part of that expression is declared in that + /// inner context. + /// + /// For example, + /// \code + /// const int N = 10, M = 20; + /// int arr[N][M]; + /// int getRow(); + /// + /// for (int i = 0; i < M; ++i) { + /// int k = getRow(); + /// printf("%d:", arr[k][i]); + /// } + /// \endcode + /// At first glance, this loop looks like it could be changed to + /// \code + /// for (int elem : arr[k]) { + /// int k = getIndex(); + /// printf("%d:", elem); + /// } + /// \endcode + /// But this is malformed, since `k` is used before it is defined! + /// + /// In order to avoid this, this class looks at the container expression + /// `arr[k]` and decides whether or not it contains a sub-expression declared + /// within the the loop body. + bool dependsOnInsideVariable(const clang::Stmt *Body) { + DependsOnInsideVariable = false; + TraverseStmt(const_cast(Body)); + return DependsOnInsideVariable; + } + + friend class clang::RecursiveASTVisitor; + +private: + const StmtParentMap *StmtParents; + const DeclParentMap *DeclParents; + const clang::Stmt *ContainingStmt; + const ReplacedVarsMap *ReplacedVars; + bool DependsOnInsideVariable; + + bool VisitVarDecl(clang::VarDecl *V); + bool VisitDeclRefExpr(clang::DeclRefExpr *D); +}; + +/// Class used to determine if any declarations used in a Stmt would conflict +/// with a particular identifier. This search includes the names that don't +/// actually appear in the AST (i.e. created by a refactoring tool) by including +/// a map from Stmts to generated names associated with those stmts. +class DeclFinderASTVisitor + : public clang::RecursiveASTVisitor { +public: + DeclFinderASTVisitor(const std::string &Name, + const StmtGeneratedVarNameMap *GeneratedDecls) + : Name(Name), GeneratedDecls(GeneratedDecls), Found(false) {} + + /// Attempts to find any usages of variables name Name in Body, returning + /// true when it is used in Body. This includes the generated loop variables + /// of ForStmts which have already been transformed. + bool findUsages(const clang::Stmt *Body) { + Found = false; + TraverseStmt(const_cast(Body)); + return Found; + } + + friend class clang::RecursiveASTVisitor; + +private: + std::string Name; + /// GeneratedDecls keeps track of ForStmts which have been transformed, + /// mapping each modified ForStmt to the variable generated in the loop. + const StmtGeneratedVarNameMap *GeneratedDecls; + bool Found; + + bool VisitForStmt(clang::ForStmt *F); + bool VisitNamedDecl(clang::NamedDecl *D); + bool VisitDeclRefExpr(clang::DeclRefExpr *D); + bool VisitTypeLoc(clang::TypeLoc TL); +}; + +/// \brief The information needed to describe a valid convertible usage +/// of an array index or iterator. +struct Usage { + enum UsageKind { + // Regular usages of the loop index (the ones not specified below). Some + // examples: + // \code + // int X = 8 * Arr[i]; + // ^~~~~~ + // f(param1, param2, *It); + // ^~~ + // if (Vec[i].SomeBool) {} + // ^~~~~~ + // \endcode + UK_Default, + // Indicates whether this is an access to a member through the arrow + // operator on pointers or iterators. + UK_MemberThroughArrow, + // If the variable is being captured by a lambda, indicates whether the + // capture was done by value or by reference. + UK_CaptureByCopy, + UK_CaptureByRef + }; + // The expression that is going to be converted. Null in case of lambda + // captures. + const Expr *Expression; + + UsageKind Kind; + + // Range that covers this usage. + SourceRange Range; + + explicit Usage(const Expr *E) + : Expression(E), Kind(UK_Default), Range(Expression->getSourceRange()) {} + Usage(const Expr *E, UsageKind Kind, SourceRange Range) + : Expression(E), Kind(Kind), Range(std::move(Range)) {} +}; + +/// \brief A class to encapsulate lowering of the tool's confidence level. +class Confidence { +public: + enum Level { + // Transformations that are likely to change semantics. + CL_Risky, + + // Transformations that might change semantics. + CL_Reasonable, + + // Transformations that will not change semantics. + CL_Safe + }; + /// \brief Initialize confidence level. + explicit Confidence(Confidence::Level Level) : CurrentLevel(Level) {} + + /// \brief Lower the internal confidence level to Level, but do not raise it. + void lowerTo(Confidence::Level Level) { + CurrentLevel = std::min(Level, CurrentLevel); + } + + /// \brief Return the internal confidence level. + Level getLevel() const { return CurrentLevel; } + +private: + Level CurrentLevel; +}; + +// The main computational result of ForLoopIndexVisitor. +typedef llvm::SmallVector UsageResult; + +// General functions used by ForLoopIndexUseVisitor and LoopConvertCheck. +const Expr *digThroughConstructors(const Expr *E); +bool areSameExpr(ASTContext *Context, const Expr *First, const Expr *Second); +const DeclRefExpr *getDeclRef(const Expr *E); +bool areSameVariable(const ValueDecl *First, const ValueDecl *Second); + +/// \brief Discover usages of expressions consisting of index or iterator +/// access. +/// +/// Given an index variable, recursively crawls a for loop to discover if the +/// index variable is used in a way consistent with range-based for loop access. +class ForLoopIndexUseVisitor + : public RecursiveASTVisitor { +public: + ForLoopIndexUseVisitor(ASTContext *Context, const VarDecl *IndexVar, + const VarDecl *EndVar, const Expr *ContainerExpr, + const Expr *ArrayBoundExpr, + bool ContainerNeedsDereference); + + /// \brief Finds all uses of IndexVar in Body, placing all usages in Usages, + /// and returns true if IndexVar was only used in a way consistent with a + /// range-based for loop. + /// + /// The general strategy is to reject any DeclRefExprs referencing IndexVar, + /// with the exception of certain acceptable patterns. + /// For arrays, the DeclRefExpr for IndexVar must appear as the index of an + /// ArraySubscriptExpression. Iterator-based loops may dereference + /// IndexVar or call methods through operator-> (builtin or overloaded). + /// Array-like containers may use IndexVar as a parameter to the at() member + /// function and in overloaded operator[]. + bool findAndVerifyUsages(const Stmt *Body); + + /// \brief Add a set of components that we should consider relevant to the + /// container. + void addComponents(const ComponentVector &Components); + + /// \brief Accessor for Usages. + const UsageResult &getUsages() const { return Usages; } + + /// \brief Adds the Usage if it was not added before. + void addUsage(const Usage &U); + + /// \brief Get the container indexed by IndexVar, if any. + const Expr *getContainerIndexed() const { return ContainerExpr; } + + /// \brief Returns the statement declaring the variable created as an alias + /// for the loop element, if any. + const DeclStmt *getAliasDecl() const { return AliasDecl; } + + /// \brief Accessor for ConfidenceLevel. + Confidence::Level getConfidenceLevel() const { + return ConfidenceLevel.getLevel(); + } + + /// \brief Indicates if the alias declaration was in a place where it cannot + /// simply be removed but rather replaced with a use of the alias variable. + /// For example, variables declared in the condition of an if, switch, or for + /// stmt. + bool aliasUseRequired() const { return ReplaceWithAliasUse; } + + /// \brief Indicates if the alias declaration came from the init clause of a + /// nested for loop. SourceRanges provided by Clang for DeclStmts in this + /// case need to be adjusted. + bool aliasFromForInit() const { return AliasFromForInit; } + +private: + /// Typedef used in CRTP functions. + typedef RecursiveASTVisitor VisitorBase; + friend class RecursiveASTVisitor; + + /// Overriden methods for RecursiveASTVisitor's traversal. + bool TraverseArraySubscriptExpr(ArraySubscriptExpr *E); + bool TraverseCXXMemberCallExpr(CXXMemberCallExpr *MemberCall); + bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr *OpCall); + bool TraverseLambdaCapture(LambdaExpr *LE, const LambdaCapture *C); + bool TraverseMemberExpr(MemberExpr *Member); + bool TraverseUnaryDeref(UnaryOperator *Uop); + bool VisitDeclRefExpr(DeclRefExpr *E); + bool VisitDeclStmt(DeclStmt *S); + bool TraverseStmt(Stmt *S); + + /// \brief Add an expression to the list of expressions on which the container + /// expression depends. + void addComponent(const Expr *E); + + // Input member variables: + ASTContext *Context; + /// The index variable's VarDecl. + const VarDecl *IndexVar; + /// The loop's 'end' variable, which cannot be mentioned at all. + const VarDecl *EndVar; + /// The Expr which refers to the container. + const Expr *ContainerExpr; + /// The Expr which refers to the terminating condition for array-based loops. + const Expr *ArrayBoundExpr; + bool ContainerNeedsDereference; + + // Output member variables: + /// A container which holds all usages of IndexVar as the index of + /// ArraySubscriptExpressions. + UsageResult Usages; + llvm::SmallSet UsageLocations; + bool OnlyUsedAsIndex; + /// The DeclStmt for an alias to the container element. + const DeclStmt *AliasDecl; + Confidence ConfidenceLevel; + /// \brief A list of expressions on which ContainerExpr depends. + /// + /// If any of these expressions are encountered outside of an acceptable usage + /// of the loop element, lower our confidence level. + llvm::SmallVector, 16> + DependentExprs; + + /// The parent-in-waiting. Will become the real parent once we traverse down + /// one level in the AST. + const Stmt *NextStmtParent; + /// The actual parent of a node when Visit*() calls are made. Only the + /// parentage of DeclStmt's to possible iteration/selection statements is of + /// importance. + const Stmt *CurrStmtParent; + + /// \see aliasUseRequired(). + bool ReplaceWithAliasUse; + /// \see aliasFromForInit(). + bool AliasFromForInit; +}; + +struct TUTrackingInfo { + /// \brief Reset and initialize per-TU tracking information. + /// + /// Must be called before using container accessors. + TUTrackingInfo() : ParentFinder(new StmtAncestorASTVisitor) {} + + StmtAncestorASTVisitor &getParentFinder() { return *ParentFinder; } + StmtGeneratedVarNameMap &getGeneratedDecls() { return GeneratedDecls; } + ReplacedVarsMap &getReplacedVars() { return ReplacedVars; } + +private: + std::unique_ptr ParentFinder; + StmtGeneratedVarNameMap GeneratedDecls; + ReplacedVarsMap ReplacedVars; +}; + +/// \brief Create names for generated variables within a particular statement. +/// +/// VariableNamer uses a DeclContext as a reference point, checking for any +/// conflicting declarations higher up in the context or within SourceStmt. +/// It creates a variable name using hints from a source container and the old +/// index, if they exist. +class VariableNamer { +public: + // Supported naming styles. + enum NamingStyle { + NS_CamelBack, + NS_CamelCase, + NS_LowerCase, + NS_UpperCase, + }; + + VariableNamer(StmtGeneratedVarNameMap *GeneratedDecls, + const StmtParentMap *ReverseAST, const clang::Stmt *SourceStmt, + const clang::VarDecl *OldIndex, + const clang::ValueDecl *TheContainer, + const clang::ASTContext *Context, NamingStyle Style) + : GeneratedDecls(GeneratedDecls), ReverseAST(ReverseAST), + SourceStmt(SourceStmt), OldIndex(OldIndex), TheContainer(TheContainer), + Context(Context), Style(Style) {} + + /// \brief Generate a new index name. + /// + /// Generates the name to be used for an inserted iterator. It relies on + /// declarationExists() to determine that there are no naming conflicts, and + /// tries to use some hints from the container name and the old index name. + std::string createIndexName(); + +private: + StmtGeneratedVarNameMap *GeneratedDecls; + const StmtParentMap *ReverseAST; + const clang::Stmt *SourceStmt; + const clang::VarDecl *OldIndex; + const clang::ValueDecl *TheContainer; + const clang::ASTContext *Context; + const NamingStyle Style; + + // Determine whether or not a declaration that would conflict with Symbol + // exists in an outer context or in any statement contained in SourceStmt. + bool declarationExists(llvm::StringRef Symbol); + + // Concatenates two identifiers following the current naming style. + std::string AppendWithStyle(StringRef Str, StringRef Suffix) const; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_UTILS_H diff --git a/clang-tidy/modernize/MakeUniqueCheck.cpp b/clang-tidy/modernize/MakeUniqueCheck.cpp new file mode 100644 index 00000000..a585fe3e --- /dev/null +++ b/clang-tidy/modernize/MakeUniqueCheck.cpp @@ -0,0 +1,155 @@ +//===--- MakeUniqueCheck.cpp - clang-tidy----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MakeUniqueCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +static const char PointerType[] = "pointerType"; +static const char ConstructorCall[] = "constructorCall"; +static const char NewExpression[] = "newExpression"; + +void MakeUniqueCheck::registerMatchers(MatchFinder *Finder) { + if (getLangOpts().CPlusPlus11) { + Finder->addMatcher( + cxxBindTemporaryExpr(has( + cxxConstructExpr( + hasType(qualType(hasDeclaration(classTemplateSpecializationDecl( + matchesName("::std::unique_ptr"), + templateArgumentCountIs(2), + hasTemplateArgument(0, templateArgument(refersToType( + qualType().bind(PointerType)))), + hasTemplateArgument( + 1, templateArgument(refersToType(qualType( + hasDeclaration(classTemplateSpecializationDecl( + matchesName("::std::default_delete"), + templateArgumentCountIs(1), + hasTemplateArgument( + 0, templateArgument(refersToType( + qualType(equalsBoundNode( + PointerType))))))))))))))), + argumentCountIs(1), + hasArgument( + 0, cxxNewExpr(hasType(pointsTo(qualType(hasCanonicalType( + equalsBoundNode(PointerType)))))) + .bind(NewExpression))) + .bind(ConstructorCall))), + this); + } +} + +void MakeUniqueCheck::check(const MatchFinder::MatchResult &Result) { + SourceManager &SM = *Result.SourceManager; + const auto *Construct = + Result.Nodes.getNodeAs(ConstructorCall); + const auto *Type = Result.Nodes.getNodeAs(PointerType); + const auto *New = Result.Nodes.getNodeAs(NewExpression); + + if (New->getNumPlacementArgs() != 0) + return; + + SourceLocation ConstructCallStart = Construct->getExprLoc(); + + bool Invalid = false; + StringRef ExprStr = Lexer::getSourceText( + CharSourceRange::getCharRange( + ConstructCallStart, Construct->getParenOrBraceRange().getBegin()), + SM, LangOptions(), &Invalid); + if (Invalid) + return; + + auto Diag = diag(ConstructCallStart, "use std::make_unique instead"); + + // Find the location of the template's left angle. + size_t LAngle = ExprStr.find("<"); + SourceLocation ConstructCallEnd; + if (LAngle == StringRef::npos) { + // If the template argument is missing (because it is part of the alias) + // we have to add it back. + ConstructCallEnd = ConstructCallStart.getLocWithOffset(ExprStr.size()); + Diag << FixItHint::CreateInsertion( + ConstructCallEnd, "<" + Type->getAsString(getLangOpts()) + ">"); + } else { + ConstructCallEnd = ConstructCallStart.getLocWithOffset(LAngle); + } + + Diag << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(ConstructCallStart, ConstructCallEnd), + "std::make_unique"); + + // If the unique_ptr is built with brace enclosed direct initialization, use + // parenthesis instead. + if (Construct->isListInitialization()) { + SourceRange BraceRange = Construct->getParenOrBraceRange(); + Diag << FixItHint::CreateReplacement( + CharSourceRange::getCharRange( + BraceRange.getBegin(), BraceRange.getBegin().getLocWithOffset(1)), + "("); + Diag << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(BraceRange.getEnd(), + BraceRange.getEnd().getLocWithOffset(1)), + ")"); + } + + SourceLocation NewStart = New->getSourceRange().getBegin(); + SourceLocation NewEnd = New->getSourceRange().getEnd(); + switch (New->getInitializationStyle()) { + case CXXNewExpr::NoInit: { + Diag << FixItHint::CreateRemoval(SourceRange(NewStart, NewEnd)); + break; + } + case CXXNewExpr::CallInit: { + SourceRange InitRange = New->getDirectInitRange(); + Diag << FixItHint::CreateRemoval( + SourceRange(NewStart, InitRange.getBegin())); + Diag << FixItHint::CreateRemoval(SourceRange(InitRange.getEnd(), NewEnd)); + break; + } + case CXXNewExpr::ListInit: { + // Range of the substring that we do not want to remove. + SourceRange InitRange; + if (const auto *NewConstruct = New->getConstructExpr()) { + // Direct initialization with initialization list. + // struct S { S(int x) {} }; + // std::unique_ptr(new S{5}); + // The arguments in the initialization list are going to be forwarded to + // the constructor, so this has to be replaced with: + // struct S { S(int x) {} }; + // std::make_unique(5); + InitRange = SourceRange( + NewConstruct->getParenOrBraceRange().getBegin().getLocWithOffset(1), + NewConstruct->getParenOrBraceRange().getEnd().getLocWithOffset(-1)); + } else { + // Aggregate initialization. + // std::unique_ptr(new Pair{first, second}); + // Has to be replaced with: + // std::make_unique(Pair{first, second}); + InitRange = SourceRange( + New->getAllocatedTypeSourceInfo()->getTypeLoc().getLocStart(), + New->getInitializer()->getSourceRange().getEnd()); + } + Diag << FixItHint::CreateRemoval( + CharSourceRange::getCharRange(NewStart, InitRange.getBegin())); + Diag << FixItHint::CreateRemoval( + SourceRange(InitRange.getEnd().getLocWithOffset(1), NewEnd)); + break; + } + } +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/MakeUniqueCheck.h b/clang-tidy/modernize/MakeUniqueCheck.h new file mode 100644 index 00000000..824204e2 --- /dev/null +++ b/clang-tidy/modernize/MakeUniqueCheck.h @@ -0,0 +1,41 @@ +//===--- MakeUniqueCheck.h - clang-tidy--------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_MAKE_UNIQUE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_MAKE_UNIQUE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Replace the pattern: +/// \code +/// std::unique_ptr(new type(args...)) +/// \endcode +/// +/// With the C++14 version: +/// \code +/// std::make_unique(args...) +/// \endcode +class MakeUniqueCheck : public ClangTidyCheck { +public: + MakeUniqueCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_MAKE_UNIQUE_H + diff --git a/clang-tidy/modernize/Makefile b/clang-tidy/modernize/Makefile new file mode 100644 index 00000000..28cf9ef4 --- /dev/null +++ b/clang-tidy/modernize/Makefile @@ -0,0 +1,12 @@ +##===- clang-tidy/modernize/Makefile -----------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## +CLANG_LEVEL := ../../../.. +LIBRARYNAME := clangTidyModernizeModule + +include $(CLANG_LEVEL)/Makefile diff --git a/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tidy/modernize/ModernizeTidyModule.cpp new file mode 100644 index 00000000..01c111ec --- /dev/null +++ b/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -0,0 +1,76 @@ +//===--- ModernizeTidyModule.cpp - clang-tidy -----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "LoopConvertCheck.h" +#include "MakeUniqueCheck.h" +#include "PassByValueCheck.h" +#include "RedundantVoidArgCheck.h" +#include "ReplaceAutoPtrCheck.h" +#include "ShrinkToFitCheck.h" +#include "UseAutoCheck.h" +#include "UseDefaultCheck.h" +#include "UseNullptrCheck.h" +#include "UseOverrideCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +class ModernizeModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("modernize-loop-convert"); + CheckFactories.registerCheck("modernize-make-unique"); + CheckFactories.registerCheck("modernize-pass-by-value"); + CheckFactories.registerCheck( + "modernize-redundant-void-arg"); + CheckFactories.registerCheck( + "modernize-replace-auto-ptr"); + CheckFactories.registerCheck("modernize-shrink-to-fit"); + CheckFactories.registerCheck("modernize-use-auto"); + CheckFactories.registerCheck("modernize-use-default"); + CheckFactories.registerCheck("modernize-use-nullptr"); + CheckFactories.registerCheck("modernize-use-override"); + } + + ClangTidyOptions getModuleOptions() override { + ClangTidyOptions Options; + auto &Opts = Options.CheckOptions; + // For types whose size in bytes is above this threshold, we prefer taking a + // const-reference than making a copy. + Opts["modernize-loop-convert.MaxCopySize"] = "16"; + + Opts["modernize-loop-convert.MinConfidence"] = "reasonable"; + Opts["modernize-loop-convert.NamingStyle"] = "CamelCase"; + Opts["modernize-pass-by-value.IncludeStyle"] = "llvm"; // Also: "google". + Opts["modernize-replace-auto-ptr.IncludeStyle"] = "llvm"; // Also: "google". + + // Comma-separated list of macros that behave like NULL. + Opts["modernize-use-nullptr.NullMacros"] = "NULL"; + return Options; + } +}; + +// Register the ModernizeTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add X("modernize-module", + "Add modernize checks."); + +} // namespace modernize + +// This anchor is used to force the linker to link in the generated object file +// and thus register the ModernizeModule. +volatile int ModernizeModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/PassByValueCheck.cpp b/clang-tidy/modernize/PassByValueCheck.cpp new file mode 100644 index 00000000..02ec4b2d --- /dev/null +++ b/clang-tidy/modernize/PassByValueCheck.cpp @@ -0,0 +1,220 @@ +//===--- PassByValueCheck.cpp - clang-tidy---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "PassByValueCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" + +using namespace clang::ast_matchers; +using namespace llvm; + +namespace clang { +namespace tidy { +namespace modernize { + +/// \brief Matches move-constructible classes. +/// +/// Given +/// \code +/// // POD types are trivially move constructible. +/// struct Foo { int a; }; +/// +/// struct Bar { +/// Bar(Bar &&) = deleted; +/// int a; +/// }; +/// \endcode +/// recordDecl(isMoveConstructible()) +/// matches "Foo". +AST_MATCHER(CXXRecordDecl, isMoveConstructible) { + for (const CXXConstructorDecl *Ctor : Node.ctors()) { + if (Ctor->isMoveConstructor() && !Ctor->isDeleted()) + return true; + } + return false; +} + +static TypeMatcher constRefType() { + return lValueReferenceType(pointee(isConstQualified())); +} + +static TypeMatcher nonConstValueType() { + return qualType(unless(anyOf(referenceType(), isConstQualified()))); +} + +/// \brief Whether or not \p ParamDecl is used exactly one time in \p Ctor. +/// +/// Checks both in the init-list and the body of the constructor. +static bool paramReferredExactlyOnce(const CXXConstructorDecl *Ctor, + const ParmVarDecl *ParamDecl) { + /// \brief \c clang::RecursiveASTVisitor that checks that the given + /// \c ParmVarDecl is used exactly one time. + /// + /// \see ExactlyOneUsageVisitor::hasExactlyOneUsageIn() + class ExactlyOneUsageVisitor + : public RecursiveASTVisitor { + friend class RecursiveASTVisitor; + + public: + ExactlyOneUsageVisitor(const ParmVarDecl *ParamDecl) + : ParamDecl(ParamDecl) {} + + /// \brief Whether or not the parameter variable is referred only once in + /// the + /// given constructor. + bool hasExactlyOneUsageIn(const CXXConstructorDecl *Ctor) { + Count = 0; + TraverseDecl(const_cast(Ctor)); + return Count == 1; + } + + private: + /// \brief Counts the number of references to a variable. + /// + /// Stops the AST traversal if more than one usage is found. + bool VisitDeclRefExpr(DeclRefExpr *D) { + if (const ParmVarDecl *To = dyn_cast(D->getDecl())) { + if (To == ParamDecl) { + ++Count; + if (Count > 1) { + // No need to look further, used more than once. + return false; + } + } + } + return true; + } + + const ParmVarDecl *ParamDecl; + unsigned Count; + }; + + return ExactlyOneUsageVisitor(ParamDecl).hasExactlyOneUsageIn(Ctor); +} + +/// \brief Find all references to \p ParamDecl across all of the +/// redeclarations of \p Ctor. +static SmallVector +collectParamDecls(const CXXConstructorDecl *Ctor, + const ParmVarDecl *ParamDecl) { + SmallVector Results; + unsigned ParamIdx = ParamDecl->getFunctionScopeIndex(); + + for (const FunctionDecl *Redecl : Ctor->redecls()) + Results.push_back(Redecl->getParamDecl(ParamIdx)); + return Results; +} + +PassByValueCheck::PassByValueCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IncludeStyle(IncludeSorter::parseIncludeStyle( + Options.get("IncludeStyle", "llvm"))) {} + +void PassByValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", IncludeSorter::toString(IncludeStyle)); +} + +void PassByValueCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (getLangOpts().CPlusPlus) { + Finder->addMatcher( + cxxConstructorDecl( + forEachConstructorInitializer( + cxxCtorInitializer( + // Clang builds a CXXConstructExpr only whin it knows which + // constructor will be called. In dependent contexts a + // ParenListExpr is generated instead of a CXXConstructExpr, + // filtering out templates automatically for us. + withInitializer(cxxConstructExpr( + has(declRefExpr(to( + parmVarDecl( + hasType(qualType( + // Match only const-ref or a non-const value + // parameters. Rvalues and const-values + // shouldn't be modified. + anyOf(constRefType(), + nonConstValueType())))) + .bind("Param")))), + hasDeclaration(cxxConstructorDecl( + isCopyConstructor(), unless(isDeleted()), + hasDeclContext( + cxxRecordDecl(isMoveConstructible()))))))) + .bind("Initializer"))) + .bind("Ctor"), + this); + } +} + +void PassByValueCheck::registerPPCallbacks(CompilerInstance &Compiler) { + // Only register the preprocessor callbacks for C++; the functionality + // currently does not provide any benefit to other languages, despite being + // benign. + if (getLangOpts().CPlusPlus) { + Inserter.reset(new IncludeInserter(Compiler.getSourceManager(), + Compiler.getLangOpts(), IncludeStyle)); + Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks()); + } +} + +void PassByValueCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Ctor = Result.Nodes.getNodeAs("Ctor"); + const auto *ParamDecl = Result.Nodes.getNodeAs("Param"); + const auto *Initializer = + Result.Nodes.getNodeAs("Initializer"); + SourceManager &SM = *Result.SourceManager; + + // If the parameter is used or anything other than the copy, do not apply + // the changes. + if (!paramReferredExactlyOnce(Ctor, ParamDecl)) + return; + + auto Diag = diag(ParamDecl->getLocStart(), "pass by value and use std::move"); + + // Iterate over all declarations of the constructor. + for (const ParmVarDecl *ParmDecl : collectParamDecls(Ctor, ParamDecl)) { + auto ParamTL = ParmDecl->getTypeSourceInfo()->getTypeLoc(); + auto RefTL = ParamTL.getAs(); + + // Do not replace if it is already a value, skip. + if (RefTL.isNull()) + continue; + + TypeLoc ValueTL = RefTL.getPointeeLoc(); + auto TypeRange = CharSourceRange::getTokenRange(ParmDecl->getLocStart(), + ParamTL.getLocEnd()); + std::string ValueStr = + Lexer::getSourceText( + CharSourceRange::getTokenRange(ValueTL.getSourceRange()), SM, + Result.Context->getLangOpts()) + .str(); + ValueStr += ' '; + Diag << FixItHint::CreateReplacement(TypeRange, ValueStr); + } + + // Use std::move in the initialization list. + Diag << FixItHint::CreateInsertion(Initializer->getRParenLoc(), ")") + << FixItHint::CreateInsertion( + Initializer->getLParenLoc().getLocWithOffset(1), "std::move("); + + auto Insertion = + Inserter->CreateIncludeInsertion(SM.getMainFileID(), "utility", + /*IsAngled=*/true); + if (Insertion.hasValue()) + Diag << Insertion.getValue(); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/PassByValueCheck.h b/clang-tidy/modernize/PassByValueCheck.h new file mode 100644 index 00000000..42e53c7e --- /dev/null +++ b/clang-tidy/modernize/PassByValueCheck.h @@ -0,0 +1,39 @@ +//===--- PassByValueCheck.h - clang-tidy-------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_PASS_BY_VALUE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_PASS_BY_VALUE_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" + +#include + +namespace clang { +namespace tidy { +namespace modernize { + +class PassByValueCheck : public ClangTidyCheck { +public: + PassByValueCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerPPCallbacks(clang::CompilerInstance &Compiler) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + std::unique_ptr Inserter; + const IncludeSorter::IncludeStyle IncludeStyle; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_PASS_BY_VALUE_H diff --git a/clang-tidy/modernize/RedundantVoidArgCheck.cpp b/clang-tidy/modernize/RedundantVoidArgCheck.cpp new file mode 100644 index 00000000..2c5611b7 --- /dev/null +++ b/clang-tidy/modernize/RedundantVoidArgCheck.cpp @@ -0,0 +1,261 @@ +//===- RedundantVoidArgCheck.cpp - clang-tidy -----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RedundantVoidArgCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { + +namespace { + +// Determine if the given QualType is a nullary function or pointer to same. +bool protoTypeHasNoParms(QualType QT) { + if (auto PT = QT->getAs()) { + QT = PT->getPointeeType(); + } + if (auto *MPT = QT->getAs()) { + QT = MPT->getPointeeType(); + } + if (auto FP = QT->getAs()) { + return FP->getNumParams() == 0; + } + return false; +} + +const char FunctionId[] = "function"; +const char TypedefId[] = "typedef"; +const char FieldId[] = "field"; +const char VarId[] = "var"; +const char NamedCastId[] = "named-cast"; +const char CStyleCastId[] = "c-style-cast"; +const char ExplicitCastId[] = "explicit-cast"; +const char LambdaId[] = "lambda"; + +} // namespace + +namespace tidy { +namespace modernize { + +void RedundantVoidArgCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(functionDecl(isExpansionInMainFile(), parameterCountIs(0), + unless(isImplicit()), unless(isExternC())) + .bind(FunctionId), + this); + Finder->addMatcher(typedefDecl(isExpansionInMainFile()).bind(TypedefId), + this); + auto ParenFunctionType = parenType(innerType(functionType())); + auto PointerToFunctionType = pointee(ParenFunctionType); + auto FunctionOrMemberPointer = + anyOf(hasType(pointerType(PointerToFunctionType)), + hasType(memberPointerType(PointerToFunctionType))); + Finder->addMatcher( + fieldDecl(isExpansionInMainFile(), FunctionOrMemberPointer).bind(FieldId), + this); + Finder->addMatcher( + varDecl(isExpansionInMainFile(), FunctionOrMemberPointer).bind(VarId), + this); + auto CastDestinationIsFunction = + hasDestinationType(pointsTo(ParenFunctionType)); + Finder->addMatcher( + cStyleCastExpr(isExpansionInMainFile(), CastDestinationIsFunction) + .bind(CStyleCastId), + this); + Finder->addMatcher( + cxxStaticCastExpr(isExpansionInMainFile(), CastDestinationIsFunction) + .bind(NamedCastId), + this); + Finder->addMatcher( + cxxReinterpretCastExpr(isExpansionInMainFile(), CastDestinationIsFunction) + .bind(NamedCastId), + this); + Finder->addMatcher( + cxxConstCastExpr(isExpansionInMainFile(), CastDestinationIsFunction) + .bind(NamedCastId), + this); + Finder->addMatcher(lambdaExpr(isExpansionInMainFile()).bind(LambdaId), this); +} + +void RedundantVoidArgCheck::check(const MatchFinder::MatchResult &Result) { + if (!Result.Context->getLangOpts().CPlusPlus) { + return; + } + + const BoundNodes &Nodes = Result.Nodes; + if (const auto *Function = Nodes.getNodeAs(FunctionId)) { + processFunctionDecl(Result, Function); + } else if (const auto *Typedef = Nodes.getNodeAs(TypedefId)) { + processTypedefDecl(Result, Typedef); + } else if (const auto *Member = Nodes.getNodeAs(FieldId)) { + processFieldDecl(Result, Member); + } else if (const auto *Var = Nodes.getNodeAs(VarId)) { + processVarDecl(Result, Var); + } else if (const auto *NamedCast = + Nodes.getNodeAs(NamedCastId)) { + processNamedCastExpr(Result, NamedCast); + } else if (const auto *CStyleCast = + Nodes.getNodeAs(CStyleCastId)) { + processExplicitCastExpr(Result, CStyleCast); + } else if (const auto *ExplicitCast = + Nodes.getNodeAs(ExplicitCastId)) { + processExplicitCastExpr(Result, ExplicitCast); + } else if (const auto *Lambda = Nodes.getNodeAs(LambdaId)) { + processLambdaExpr(Result, Lambda); + } +} + +void RedundantVoidArgCheck::processFunctionDecl( + const MatchFinder::MatchResult &Result, const FunctionDecl *Function) { + SourceLocation Start = Function->getLocStart(); + if (Function->isThisDeclarationADefinition()) { + SourceLocation End; + if (Function->hasBody()) + End = Function->getBody()->getLocStart().getLocWithOffset(-1); + else + End = Function->getLocEnd(); + removeVoidArgumentTokens(Result, SourceRange(Start, End), + "function definition"); + } else { + removeVoidArgumentTokens(Result, Function->getSourceRange(), + "function declaration"); + } +} + +void RedundantVoidArgCheck::removeVoidArgumentTokens( + const ast_matchers::MatchFinder::MatchResult &Result, SourceRange Range, + StringRef GrammarLocation) { + CharSourceRange CharRange = Lexer::makeFileCharRange( + CharSourceRange::getTokenRange(Range), *Result.SourceManager, + Result.Context->getLangOpts()); + + std::string DeclText = Lexer::getSourceText(CharRange, *Result.SourceManager, + Result.Context->getLangOpts()) + .str(); + Lexer PrototypeLexer(CharRange.getBegin(), Result.Context->getLangOpts(), + DeclText.data(), DeclText.data(), + DeclText.data() + DeclText.size()); + enum TokenState { + NothingYet, + SawLeftParen, + SawVoid, + }; + TokenState State = NothingYet; + Token VoidToken; + Token ProtoToken; + std::string Diagnostic = + ("redundant void argument list in " + GrammarLocation).str(); + + while (!PrototypeLexer.LexFromRawLexer(ProtoToken)) { + switch (State) { + case NothingYet: + if (ProtoToken.is(tok::TokenKind::l_paren)) { + State = SawLeftParen; + } + break; + case SawLeftParen: + if (ProtoToken.is(tok::TokenKind::raw_identifier) && + ProtoToken.getRawIdentifier() == "void") { + State = SawVoid; + VoidToken = ProtoToken; + } else { + State = NothingYet; + } + break; + case SawVoid: + State = NothingYet; + if (ProtoToken.is(tok::TokenKind::r_paren)) { + removeVoidToken(VoidToken, Diagnostic); + } else if (ProtoToken.is(tok::TokenKind::l_paren)) { + State = SawLeftParen; + } + break; + } + } + + if (State == SawVoid && ProtoToken.is(tok::TokenKind::r_paren)) { + removeVoidToken(VoidToken, Diagnostic); + } +} + +void RedundantVoidArgCheck::removeVoidToken(Token VoidToken, + StringRef Diagnostic) { + SourceLocation VoidLoc(VoidToken.getLocation()); + auto VoidRange = + CharSourceRange::getTokenRange(VoidLoc, VoidLoc.getLocWithOffset(3)); + diag(VoidLoc, Diagnostic) << FixItHint::CreateRemoval(VoidRange); +} + +void RedundantVoidArgCheck::processTypedefDecl( + const MatchFinder::MatchResult &Result, const TypedefDecl *Typedef) { + if (protoTypeHasNoParms(Typedef->getUnderlyingType())) { + removeVoidArgumentTokens(Result, Typedef->getSourceRange(), "typedef"); + } +} + +void RedundantVoidArgCheck::processFieldDecl( + const MatchFinder::MatchResult &Result, const FieldDecl *Member) { + if (protoTypeHasNoParms(Member->getType())) { + removeVoidArgumentTokens(Result, Member->getSourceRange(), + "field declaration"); + } +} + +void RedundantVoidArgCheck::processVarDecl( + const MatchFinder::MatchResult &Result, const VarDecl *Var) { + if (protoTypeHasNoParms(Var->getType())) { + SourceLocation Begin = Var->getLocStart(); + if (Var->hasInit()) { + SourceLocation InitStart = + Result.SourceManager->getExpansionLoc(Var->getInit()->getLocStart()) + .getLocWithOffset(-1); + removeVoidArgumentTokens(Result, SourceRange(Begin, InitStart), + "variable declaration with initializer"); + } else { + removeVoidArgumentTokens(Result, Var->getSourceRange(), + "variable declaration"); + } + } +} + +void RedundantVoidArgCheck::processNamedCastExpr( + const MatchFinder::MatchResult &Result, const CXXNamedCastExpr *NamedCast) { + if (protoTypeHasNoParms(NamedCast->getTypeAsWritten())) { + removeVoidArgumentTokens( + Result, + NamedCast->getTypeInfoAsWritten()->getTypeLoc().getSourceRange(), + "named cast"); + } +} + +void RedundantVoidArgCheck::processExplicitCastExpr( + const MatchFinder::MatchResult &Result, + const ExplicitCastExpr *ExplicitCast) { + if (protoTypeHasNoParms(ExplicitCast->getTypeAsWritten())) { + removeVoidArgumentTokens(Result, ExplicitCast->getSourceRange(), + "cast expression"); + } +} + +void RedundantVoidArgCheck::processLambdaExpr( + const MatchFinder::MatchResult &Result, const LambdaExpr *Lambda) { + if (Lambda->getLambdaClass()->getLambdaCallOperator()->getNumParams() == 0 && + Lambda->hasExplicitParameters()) { + SourceLocation Begin = + Lambda->getIntroducerRange().getEnd().getLocWithOffset(1); + SourceLocation End = Lambda->getBody()->getLocStart().getLocWithOffset(-1); + removeVoidArgumentTokens(Result, SourceRange(Begin, End), + "lambda expression"); + } +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/RedundantVoidArgCheck.h b/clang-tidy/modernize/RedundantVoidArgCheck.h new file mode 100644 index 00000000..8feb6e50 --- /dev/null +++ b/clang-tidy/modernize/RedundantVoidArgCheck.h @@ -0,0 +1,76 @@ +//===--- RedundantVoidArgCheck.h - clang-tidy --------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_REDUNDANT_VOID_ARG_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_REDUNDANT_VOID_ARG_CHECK_H + +#include "../ClangTidy.h" +#include "clang/Lex/Token.h" + +#include + +namespace clang { +namespace tidy { +namespace modernize { + +/// \brief Find and remove redundant void argument lists. +/// +/// Examples: +/// `int f(void);` becomes `int f();` +/// `int (*f(void))(void);` becomes `int (*f())();` +/// `typedef int (*f_t(void))(void);` becomes `typedef int (*f_t())();` +/// `void (C::*p)(void);` becomes `void (C::*p)();` +/// `C::C(void) {}` becomes `C::C() {}` +/// `C::~C(void) {}` becomes `C::~C() {}` +/// +class RedundantVoidArgCheck : public ClangTidyCheck { +public: + RedundantVoidArgCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void processFunctionDecl(const ast_matchers::MatchFinder::MatchResult &Result, + const FunctionDecl *Function); + + void processTypedefDecl(const ast_matchers::MatchFinder::MatchResult &Result, + const TypedefDecl *Typedef); + + void processFieldDecl(const ast_matchers::MatchFinder::MatchResult &Result, + const FieldDecl *Member); + + void processVarDecl(const ast_matchers::MatchFinder::MatchResult &Result, + const VarDecl *Var); + + void + processNamedCastExpr(const ast_matchers::MatchFinder::MatchResult &Result, + const CXXNamedCastExpr *NamedCast); + + void + processExplicitCastExpr(const ast_matchers::MatchFinder::MatchResult &Result, + const ExplicitCastExpr *ExplicitCast); + + void processLambdaExpr(const ast_matchers::MatchFinder::MatchResult &Result, + const LambdaExpr *Lambda); + + void + removeVoidArgumentTokens(const ast_matchers::MatchFinder::MatchResult &Result, + SourceRange Range, StringRef GrammarLocation); + + void removeVoidToken(Token VoidToken, StringRef Diagnostic); +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_REDUNDANT_VOID_ARG_CHECK_H diff --git a/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp b/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp new file mode 100644 index 00000000..8924f84c --- /dev/null +++ b/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp @@ -0,0 +1,270 @@ +//===--- ReplaceAutoPtrCheck.cpp - clang-tidy------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ReplaceAutoPtrCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" + +using namespace clang; +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +static const char AutoPtrTokenId[] = "AutoPrTokenId"; +static const char AutoPtrOwnershipTransferId[] = "AutoPtrOwnershipTransferId"; + +/// \brief Matches expressions that are lvalues. +/// +/// In the following example, a[0] matches expr(isLValue()): +/// \code +/// std::string a[2]; +/// std::string b; +/// b = a[0]; +/// b = "this string won't match"; +/// \endcode +AST_MATCHER(Expr, isLValue) { return Node.getValueKind() == VK_LValue; } + +/// Matches declarations whose declaration context is the C++ standard library +/// namespace std. +/// +/// Note that inline namespaces are silently ignored during the lookup since +/// both libstdc++ and libc++ are known to use them for versioning purposes. +/// +/// Given: +/// \code +/// namespace ns { +/// struct my_type {}; +/// using namespace std; +/// } +/// +/// using std::vector; +/// using ns:my_type; +/// using ns::list; +/// \code +/// +/// usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(isFromStdNamespace()))) +/// matches "using std::vector" and "using ns::list". +AST_MATCHER(Decl, isFromStdNamespace) { + const DeclContext *D = Node.getDeclContext(); + + while (D->isInlineNamespace()) + D = D->getParent(); + + if (!D->isNamespace() || !D->getParent()->isTranslationUnit()) + return false; + + const IdentifierInfo *Info = cast(D)->getIdentifier(); + + return (Info && Info->isStr("std")); +} + +/// \brief Matcher that finds auto_ptr declarations. +static DeclarationMatcher AutoPtrDecl = + recordDecl(hasName("auto_ptr"), isFromStdNamespace()); + +/// \brief Matches types declared as auto_ptr. +static TypeMatcher AutoPtrType = qualType(hasDeclaration(AutoPtrDecl)); + +/// \brief Matcher that finds expressions that are candidates to be wrapped with +/// 'std::move'. +/// +/// Binds the id \c AutoPtrOwnershipTransferId to the expression. +static StatementMatcher MovableArgumentMatcher = + expr(allOf(isLValue(), hasType(AutoPtrType))) + .bind(AutoPtrOwnershipTransferId); + +/// \brief Creates a matcher that finds the locations of types referring to the +/// \c std::auto_ptr() type. +/// +/// \code +/// std::auto_ptr a; +/// ^~~~~~~~~~~~~ +/// +/// typedef std::auto_ptr int_ptr_t; +/// ^~~~~~~~~~~~~ +/// +/// std::auto_ptr fn(std::auto_ptr); +/// ^~~~~~~~~~~~~ ^~~~~~~~~~~~~ +/// +/// +/// \endcode +TypeLocMatcher makeAutoPtrTypeLocMatcher() { + // Skip elaboratedType() as the named type will match soon thereafter. + return typeLoc(loc(qualType(AutoPtrType, unless(elaboratedType())))) + .bind(AutoPtrTokenId); +} + +/// \brief Creates a matcher that finds the using declarations referring to +/// \c std::auto_ptr. +/// +/// \code +/// using std::auto_ptr; +/// ^~~~~~~~~~~~~~~~~~~ +/// \endcode +DeclarationMatcher makeAutoPtrUsingDeclMatcher() { + return usingDecl(hasAnyUsingShadowDecl(hasTargetDecl( + allOf(hasName("auto_ptr"), isFromStdNamespace())))) + .bind(AutoPtrTokenId); +} + +/// \brief Creates a matcher that finds the \c std::auto_ptr copy-ctor and +/// assign-operator expressions. +/// +/// \c AutoPtrOwnershipTransferId is assigned to the argument of the expression, +/// this is the part that has to be wrapped by \c std::move(). +/// +/// \code +/// std::auto_ptr i, j; +/// i = j; +/// ~~~~^ +/// \endcode +StatementMatcher makeTransferOwnershipExprMatcher() { + return anyOf( + cxxOperatorCallExpr(allOf(hasOverloadedOperatorName("="), + callee(cxxMethodDecl(ofClass(AutoPtrDecl))), + hasArgument(1, MovableArgumentMatcher))), + cxxConstructExpr(allOf(hasType(AutoPtrType), argumentCountIs(1), + hasArgument(0, MovableArgumentMatcher)))); +} + +/// \brief Locates the \c auto_ptr token when it is referred by a \c TypeLoc. +/// +/// \code +/// std::auto_ptr i; +/// ^~~~~~~~~~~~~ +/// \endcode +/// +/// The caret represents the location returned and the tildes cover the +/// parameter \p AutoPtrTypeLoc. +/// +/// \return An invalid \c SourceLocation if not found, otherwise the location +/// of the beginning of the \c auto_ptr token. +static SourceLocation locateFromTypeLoc(const TypeLoc *AutoPtrTypeLoc, + const SourceManager &SM) { + auto TL = AutoPtrTypeLoc->getAs(); + if (TL.isNull()) + return SourceLocation(); + + return TL.getTemplateNameLoc(); +} + +/// \brief Locates the \c auto_ptr token in using declarations. +/// +/// \code +/// using std::auto_ptr; +/// ^ +/// \endcode +/// +/// The caret represents the location returned. +/// +/// \return An invalid \c SourceLocation if not found, otherwise the location +/// of the beginning of the \c auto_ptr token. +static SourceLocation locateFromUsingDecl(const UsingDecl *UsingAutoPtrDecl, + const SourceManager &SM) { + return UsingAutoPtrDecl->getNameInfo().getBeginLoc(); +} + +/// \brief Verifies that the token at \p TokenStart is 'auto_ptr'. +static bool checkTokenIsAutoPtr(SourceLocation TokenStart, + const SourceManager &SM, + const LangOptions &LO) { + SmallVector Buffer; + bool Invalid = false; + StringRef Res = Lexer::getSpelling(TokenStart, Buffer, SM, LO, &Invalid); + + return (!Invalid && Res == "auto_ptr"); +} + +ReplaceAutoPtrCheck::ReplaceAutoPtrCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IncludeStyle(IncludeSorter::parseIncludeStyle( + Options.get("IncludeStyle", "llvm"))) {} + +void ReplaceAutoPtrCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", IncludeSorter::toString(IncludeStyle)); +} + +void ReplaceAutoPtrCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (getLangOpts().CPlusPlus) { + Finder->addMatcher(makeAutoPtrTypeLocMatcher(), this); + Finder->addMatcher(makeAutoPtrUsingDeclMatcher(), this); + Finder->addMatcher(makeTransferOwnershipExprMatcher(), this); + } +} + +void ReplaceAutoPtrCheck::registerPPCallbacks(CompilerInstance &Compiler) { + // Only register the preprocessor callbacks for C++; the functionality + // currently does not provide any benefit to other languages, despite being + // benign. + if (getLangOpts().CPlusPlus) { + Inserter.reset(new IncludeInserter(Compiler.getSourceManager(), + Compiler.getLangOpts(), IncludeStyle)); + Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks()); + } +} + +void ReplaceAutoPtrCheck::check(const MatchFinder::MatchResult &Result) { + SourceManager &SM = *Result.SourceManager; + if (const auto *E = + Result.Nodes.getNodeAs(AutoPtrOwnershipTransferId)) { + CharSourceRange Range = Lexer::makeFileCharRange( + CharSourceRange::getTokenRange(E->getSourceRange()), SM, LangOptions()); + + if (Range.isInvalid()) + return; + + auto Diag = diag(Range.getBegin(), "use std::move to transfer ownership") + << FixItHint::CreateInsertion(Range.getBegin(), "std::move(") + << FixItHint::CreateInsertion(Range.getEnd(), ")"); + + auto Insertion = + Inserter->CreateIncludeInsertion(SM.getMainFileID(), "utility", + /*IsAngled=*/true); + if (Insertion.hasValue()) + Diag << Insertion.getValue(); + + return; + } + + SourceLocation IdentifierLoc; + if (const auto *TL = Result.Nodes.getNodeAs(AutoPtrTokenId)) { + IdentifierLoc = locateFromTypeLoc(TL, SM); + } else if (const auto *D = + Result.Nodes.getNodeAs(AutoPtrTokenId)) { + IdentifierLoc = locateFromUsingDecl(D, SM); + } else { + llvm_unreachable("Bad Callback. No node provided."); + } + + if (IdentifierLoc.isMacroID()) + IdentifierLoc = SM.getSpellingLoc(IdentifierLoc); + + // Ensure that only the 'auto_ptr' token is replaced and not the template + // aliases. + if (!checkTokenIsAutoPtr(IdentifierLoc, SM, LangOptions())) + return; + + SourceLocation EndLoc = + IdentifierLoc.getLocWithOffset(strlen("auto_ptr") - 1); + diag(IdentifierLoc, "auto_ptr is deprecated, use unique_ptr instead") + << FixItHint::CreateReplacement(SourceRange(IdentifierLoc, EndLoc), + "unique_ptr"); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/ReplaceAutoPtrCheck.h b/clang-tidy/modernize/ReplaceAutoPtrCheck.h new file mode 100644 index 00000000..f8cc034b --- /dev/null +++ b/clang-tidy/modernize/ReplaceAutoPtrCheck.h @@ -0,0 +1,61 @@ +//===--- ReplaceAutoPtrCheck.h - clang-tidy----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_REPLACE_AUTO_PTR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_REPLACE_AUTO_PTR_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Transforms the deprecated `std::auto_ptr` into the C++11 `std::unique_ptr`. +/// +/// Note that both the `std::auto_ptr` type and the transfer of ownership are +/// transformed. `std::auto_ptr` provides two ways to transfer the ownership, +/// the copy-constructor and the assignment operator. Unlike most classes these +/// operations do not 'copy' the resource but they 'steal' it. +/// `std::unique_ptr` uses move semantics instead, which makes the intent of +/// transferring the resource explicit. This difference between the two smart +/// pointers requeres to wrap the copy-ctor and assign-operator with +/// `std::move()`. +/// +/// For example, given: +/// +/// \code +/// std::auto_ptr i, j; +/// i = j; +/// \endcode +/// +/// This code is transformed to: +/// +/// \code +/// std::unique_ptr i, j; +/// i = std::move(j); +/// \endcode +class ReplaceAutoPtrCheck : public ClangTidyCheck { +public: + ReplaceAutoPtrCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void registerPPCallbacks(CompilerInstance &Compiler) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + std::unique_ptr Inserter; + const IncludeSorter::IncludeStyle IncludeStyle; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_REPLACE_AUTO_PTR_H diff --git a/clang-tidy/modernize/ShrinkToFitCheck.cpp b/clang-tidy/modernize/ShrinkToFitCheck.cpp new file mode 100644 index 00000000..0bcb2a01 --- /dev/null +++ b/clang-tidy/modernize/ShrinkToFitCheck.cpp @@ -0,0 +1,104 @@ +//===--- ShrinkToFitCheck.cpp - clang-tidy---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ShrinkToFitCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/StringRef.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace { +bool isShrinkableContainer(llvm::StringRef ClassName) { + static const char *const Shrinkables[] = { + "std::basic_string", + "std::deque", + "std::vector" + }; + return std::binary_search(std::begin(Shrinkables), std::end(Shrinkables), + ClassName); +} + +AST_MATCHER(NamedDecl, stlShrinkableContainer) { + return isShrinkableContainer(Node.getQualifiedNameAsString()); +} +} // namespace + +namespace tidy { +namespace modernize { + +void ShrinkToFitCheck::registerMatchers(MatchFinder *Finder) { + // Swap as a function need not to be considered, because rvalue can not + // be bound to a non-const reference. + const auto ShrinkableAsMember = + memberExpr(member(valueDecl().bind("ContainerDecl"))); + const auto ShrinkableAsDecl = + declRefExpr(hasDeclaration(valueDecl().bind("ContainerDecl"))); + const auto CopyCtorCall = cxxConstructExpr( + hasArgument(0, anyOf(ShrinkableAsMember, ShrinkableAsDecl, + unaryOperator(has(ShrinkableAsMember)), + unaryOperator(has(ShrinkableAsDecl))))); + const auto SwapParam = expr(anyOf( + memberExpr(member(equalsBoundNode("ContainerDecl"))), + declRefExpr(hasDeclaration(equalsBoundNode("ContainerDecl"))), + unaryOperator(has(memberExpr(member(equalsBoundNode("ContainerDecl"))))), + unaryOperator( + has(declRefExpr(hasDeclaration(equalsBoundNode("ContainerDecl"))))))); + + Finder->addMatcher( + cxxMemberCallExpr(on(hasType(namedDecl(stlShrinkableContainer()))), + callee(cxxMethodDecl(hasName("swap"))), + has(memberExpr(hasDescendant(CopyCtorCall))), + hasArgument(0, SwapParam.bind("ContainerToShrink")), + unless(isInTemplateInstantiation())) + .bind("CopyAndSwapTrick"), + this); +} + +void ShrinkToFitCheck::check(const MatchFinder::MatchResult &Result) { + const LangOptions &Opts = Result.Context->getLangOpts(); + + if (!Opts.CPlusPlus11) + return; + + const auto *MemberCall = + Result.Nodes.getNodeAs("CopyAndSwapTrick"); + const auto *Container = Result.Nodes.getNodeAs("ContainerToShrink"); + FixItHint Hint; + + if (!MemberCall->getLocStart().isMacroID()) { + std::string ReplacementText; + if (const auto *UnaryOp = llvm::dyn_cast(Container)) { + ReplacementText = + Lexer::getSourceText(CharSourceRange::getTokenRange( + UnaryOp->getSubExpr()->getSourceRange()), + *Result.SourceManager, Opts); + ReplacementText += "->shrink_to_fit()"; + } else { + ReplacementText = Lexer::getSourceText( + CharSourceRange::getTokenRange(Container->getSourceRange()), + *Result.SourceManager, Opts); + ReplacementText += ".shrink_to_fit()"; + } + + Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(), + ReplacementText); + } + + diag(MemberCall->getLocStart(), "the shrink_to_fit method should be used " + "to reduce the capacity of a shrinkable " + "container") + << Hint; +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/ShrinkToFitCheck.h b/clang-tidy/modernize/ShrinkToFitCheck.h new file mode 100644 index 00000000..1e3745cd --- /dev/null +++ b/clang-tidy/modernize/ShrinkToFitCheck.h @@ -0,0 +1,37 @@ +//===--- ShrinkToFitCheck.h - clang-tidy-------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_SHRINKTOFITCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_SHRINKTOFITCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Replace copy and swap tricks on shrinkable containers with the +/// `shrink_to_fit()` method call. +/// +/// The `shrink_to_fit()` method is more readable and more effective than +/// the copy and swap trick to reduce the capacity of a shrinkable container. +/// Note that, the `shrink_to_fit()` method is only available in C++11 and up. +class ShrinkToFitCheck : public ClangTidyCheck { +public: + ShrinkToFitCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_SHRINKTOFITCHECK_H diff --git a/clang-tidy/modernize/UseAutoCheck.cpp b/clang-tidy/modernize/UseAutoCheck.cpp new file mode 100644 index 00000000..415ecdd8 --- /dev/null +++ b/clang-tidy/modernize/UseAutoCheck.cpp @@ -0,0 +1,378 @@ +//===--- UseAutoCheck.cpp - clang-tidy-------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseAutoCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang; +using namespace clang::ast_matchers; +using namespace clang::ast_matchers::internal; + +namespace clang { +namespace tidy { +namespace modernize { +namespace { + +const char IteratorDeclStmtId[] = "iterator_decl"; +const char DeclWithNewId[] = "decl_new"; + +/// \brief Matches variable declarations that have explicit initializers that +/// are not initializer lists. +/// +/// Given +/// \code +/// iterator I = Container.begin(); +/// MyType A(42); +/// MyType B{2}; +/// MyType C; +/// \endcode +/// +/// varDecl(hasWrittenNonListInitializer()) maches \c I and \c A but not \c B +/// or \c C. +AST_MATCHER(VarDecl, hasWrittenNonListInitializer) { + const Expr *Init = Node.getAnyInitializer(); + if (!Init) + return false; + + // The following test is based on DeclPrinter::VisitVarDecl() to find if an + // initializer is implicit or not. + if (const auto *Construct = dyn_cast(Init)) { + return !Construct->isListInitialization() && Construct->getNumArgs() > 0 && + !Construct->getArg(0)->isDefaultArgument(); + } + return Node.getInitStyle() != VarDecl::ListInit; +} + +/// \brief Matches QualTypes that are type sugar for QualTypes that match \c +/// SugarMatcher. +/// +/// Given +/// \code +/// class C {}; +/// typedef C my_type; +/// typedef my_type my_other_type; +/// \endcode +/// +/// qualType(isSugarFor(recordType(hasDeclaration(namedDecl(hasName("C")))))) +/// matches \c my_type and \c my_other_type. +AST_MATCHER_P(QualType, isSugarFor, Matcher, SugarMatcher) { + QualType QT = Node; + while (true) { + if (SugarMatcher.matches(QT, Finder, Builder)) + return true; + + QualType NewQT = QT.getSingleStepDesugaredType(Finder->getASTContext()); + if (NewQT == QT) + return false; + QT = NewQT; + } +} + +/// \brief Matches named declarations that have one of the standard iterator +/// names: iterator, reverse_iterator, const_iterator, const_reverse_iterator. +/// +/// Given +/// \code +/// iterator I; +/// const_iterator CI; +/// \endcode +/// +/// namedDecl(hasStdIteratorName()) matches \c I and \c CI. +AST_MATCHER(NamedDecl, hasStdIteratorName) { + static const char *const IteratorNames[] = {"iterator", "reverse_iterator", + "const_iterator", + "const_reverse_iterator"}; + + for (const char *Name : IteratorNames) { + if (hasName(Name).matches(Node, Finder, Builder)) + return true; + } + return false; +} + +/// \brief Matches named declarations that have one of the standard container +/// names. +/// +/// Given +/// \code +/// class vector {}; +/// class forward_list {}; +/// class my_ver{}; +/// \endcode +/// +/// recordDecl(hasStdContainerName()) matches \c vector and \c forward_list +/// but not \c my_vec. +AST_MATCHER(NamedDecl, hasStdContainerName) { + static const char *const ContainerNames[] = {"array", "deque", + "forward_list", "list", + "vector", + + "map", "multimap", + "set", "multiset", + + "unordered_map", + "unordered_multimap", + "unordered_set", + "unordered_multiset", + + "queue", "priority_queue", + "stack"}; + + for (const char *Name : ContainerNames) { + if (hasName(Name).matches(Node, Finder, Builder)) + return true; + } + return false; +} + +/// Matches declarations whose declaration context is the C++ standard library +/// namespace std. +/// +/// Note that inline namespaces are silently ignored during the lookup since +/// both libstdc++ and libc++ are known to use them for versioning purposes. +/// +/// Given: +/// \code +/// namespace ns { +/// struct my_type {}; +/// using namespace std; +/// } +/// +/// using std::vector; +/// using ns:my_type; +/// using ns::list; +/// \code +/// +/// usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(isFromStdNamespace()))) +/// matches "using std::vector" and "using ns::list". +AST_MATCHER(Decl, isFromStdNamespace) { + const DeclContext *D = Node.getDeclContext(); + + while (D->isInlineNamespace()) + D = D->getParent(); + + if (!D->isNamespace() || !D->getParent()->isTranslationUnit()) + return false; + + const IdentifierInfo *Info = cast(D)->getIdentifier(); + + return (Info && Info->isStr("std")); +} + +/// \brief Returns a DeclarationMatcher that matches standard iterators nested +/// inside records with a standard container name. +DeclarationMatcher standardIterator() { + return allOf( + namedDecl(hasStdIteratorName()), + hasDeclContext(recordDecl(hasStdContainerName(), isFromStdNamespace()))); +} + +/// \brief Returns a TypeMatcher that matches typedefs for standard iterators +/// inside records with a standard container name. +TypeMatcher typedefIterator() { + return typedefType(hasDeclaration(standardIterator())); +} + +/// \brief Returns a TypeMatcher that matches records named for standard +/// iterators nested inside records named for standard containers. +TypeMatcher nestedIterator() { + return recordType(hasDeclaration(standardIterator())); +} + +/// \brief Returns a TypeMatcher that matches types declared with using +/// declarations and which name standard iterators for standard containers. +TypeMatcher iteratorFromUsingDeclaration() { + auto HasIteratorDecl = hasDeclaration(namedDecl(hasStdIteratorName())); + // Types resulting from using declarations are represented by elaboratedType. + return elaboratedType(allOf( + // Unwrap the nested name specifier to test for one of the standard + // containers. + hasQualifier(specifiesType(templateSpecializationType(hasDeclaration( + namedDecl(hasStdContainerName(), isFromStdNamespace()))))), + // the named type is what comes after the final '::' in the type. It + // should name one of the standard iterator names. + namesType( + anyOf(typedefType(HasIteratorDecl), recordType(HasIteratorDecl))))); +} + +/// \brief This matcher returns declaration statements that contain variable +/// declarations with written non-list initializer for standard iterators. +StatementMatcher makeIteratorDeclMatcher() { + return declStmt( + // At least one varDecl should be a child of the declStmt to ensure + // it's a declaration list and avoid matching other declarations, + // e.g. using directives. + has(varDecl()), + unless(has(varDecl(anyOf( + unless(hasWrittenNonListInitializer()), hasType(autoType()), + unless(hasType( + isSugarFor(anyOf(typedefIterator(), nestedIterator(), + iteratorFromUsingDeclaration()))))))))) + .bind(IteratorDeclStmtId); +} + +StatementMatcher makeDeclWithNewMatcher() { + return declStmt( + has(varDecl()), + unless(has(varDecl(anyOf( + unless(hasInitializer(ignoringParenImpCasts(cxxNewExpr()))), + // Skip declarations that are already using auto. + anyOf(hasType(autoType()), + hasType(pointerType(pointee(autoType())))), + // FIXME: TypeLoc information is not reliable where CV + // qualifiers are concerned so these types can't be + // handled for now. + hasType(pointerType( + pointee(hasCanonicalType(hasLocalQualifiers())))), + + // FIXME: Handle function pointers. For now we ignore them + // because the replacement replaces the entire type + // specifier source range which includes the identifier. + hasType(pointsTo( + pointsTo(parenType(innerType(functionType())))))))))) + .bind(DeclWithNewId); +} + +} // namespace + +void UseAutoCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (getLangOpts().CPlusPlus) { + Finder->addMatcher(makeIteratorDeclMatcher(), this); + Finder->addMatcher(makeDeclWithNewMatcher(), this); + } +} + +void UseAutoCheck::replaceIterators(const DeclStmt *D, ASTContext *Context) { + for (const auto *Dec : D->decls()) { + const auto *V = cast(Dec); + const Expr *ExprInit = V->getInit(); + + // Skip expressions with cleanups from the intializer expression. + if (const auto *E = dyn_cast(ExprInit)) + ExprInit = E->getSubExpr(); + + const auto *Construct = dyn_cast(ExprInit); + if (!Construct) + continue; + + // Ensure that the constructor receives a single argument. + if (Construct->getNumArgs() != 1) + return; + + // Drill down to the as-written initializer. + const Expr *E = (*Construct->arg_begin())->IgnoreParenImpCasts(); + if (E != E->IgnoreConversionOperator()) { + // We hit a conversion operator. Early-out now as they imply an implicit + // conversion from a different type. Could also mean an explicit + // conversion from the same type but that's pretty rare. + return; + } + + if (const auto *NestedConstruct = dyn_cast(E)) { + // If we ran into an implicit conversion contructor, can't convert. + // + // FIXME: The following only checks if the constructor can be used + // implicitly, not if it actually was. Cases where the converting + // constructor was used explicitly won't get converted. + if (NestedConstruct->getConstructor()->isConvertingConstructor(false)) + return; + } + if (!Context->hasSameType(V->getType(), E->getType())) + return; + } + + // Get the type location using the first declaration. + const auto *V = cast(*D->decl_begin()); + + // WARNING: TypeLoc::getSourceRange() will include the identifier for things + // like function pointers. Not a concern since this action only works with + // iterators but something to keep in mind in the future. + + SourceRange Range(V->getTypeSourceInfo()->getTypeLoc().getSourceRange()); + diag(Range.getBegin(), "use auto when declaring iterators") + << FixItHint::CreateReplacement(Range, "auto"); +} + +void UseAutoCheck::replaceNew(const DeclStmt *D, ASTContext *Context) { + const auto *FirstDecl = dyn_cast(*D->decl_begin()); + // Ensure that there is at least one VarDecl within the DeclStmt. + if (!FirstDecl) + return; + + const QualType FirstDeclType = FirstDecl->getType().getCanonicalType(); + + std::vector StarLocations; + for (const auto *Dec : D->decls()) { + const auto *V = cast(Dec); + // Ensure that every DeclStmt child is a VarDecl. + if (!V) + return; + + const auto *NewExpr = cast(V->getInit()->IgnoreParenImpCasts()); + // Ensure that every VarDecl has a CXXNewExpr initializer. + if (!NewExpr) + return; + + // If VarDecl and Initializer have mismatching unqualified types. + if (!Context->hasSameUnqualifiedType(V->getType(), NewExpr->getType())) + return; + + // Remove explicitly written '*' from declarations where there's more than + // one declaration in the declaration list. + if (Dec == *D->decl_begin()) + continue; + + // All subsequent declarations should match the same non-decorated type. + if (FirstDeclType != V->getType().getCanonicalType()) + return; + + auto Q = V->getTypeSourceInfo()->getTypeLoc().getAs(); + while (!Q.isNull()) { + StarLocations.push_back(Q.getStarLoc()); + Q = Q.getNextTypeLoc().getAs(); + } + } + + // FIXME: There is, however, one case we can address: when the VarDecl pointee + // is the same as the initializer, just more CV-qualified. However, TypeLoc + // information is not reliable where CV qualifiers are concerned so we can't + // do anything about this case for now. + SourceRange Range( + FirstDecl->getTypeSourceInfo()->getTypeLoc().getSourceRange()); + auto Diag = diag(Range.getBegin(), "use auto when initializing with new" + " to avoid duplicating the type name"); + + // Space after 'auto' to handle cases where the '*' in the pointer type is + // next to the identifier. This avoids changing 'int *p' into 'autop'. + Diag << FixItHint::CreateReplacement(Range, "auto "); + + // Remove '*' from declarations using the saved star locations. + for (const auto &Loc : StarLocations) { + Diag << FixItHint::CreateReplacement(Loc, ""); + } +} + +void UseAutoCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Decl = Result.Nodes.getNodeAs(IteratorDeclStmtId)) { + replaceIterators(Decl, Result.Context); + } else if (const auto *Decl = + Result.Nodes.getNodeAs(DeclWithNewId)) { + replaceNew(Decl, Result.Context); + } else { + llvm_unreachable("Bad Callback. No node provided."); + } +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseAutoCheck.h b/clang-tidy/modernize/UseAutoCheck.h new file mode 100644 index 00000000..d2218876 --- /dev/null +++ b/clang-tidy/modernize/UseAutoCheck.h @@ -0,0 +1,36 @@ +//===--- UseAutoCheck.h - clang-tidy-----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_AUTO_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_AUTO_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +class UseAutoCheck : public ClangTidyCheck { +public: + UseAutoCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void replaceIterators(const DeclStmt *D, ASTContext *Context); + void replaceNew(const DeclStmt *D, ASTContext *Context); +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_AUTO_H diff --git a/clang-tidy/modernize/UseDefaultCheck.cpp b/clang-tidy/modernize/UseDefaultCheck.cpp new file mode 100644 index 00000000..dbc84ef3 --- /dev/null +++ b/clang-tidy/modernize/UseDefaultCheck.cpp @@ -0,0 +1,322 @@ +//===--- UseDefaultCheck.cpp - clang-tidy----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseDefaultCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +static const char SpecialFunction[] = "SpecialFunction"; + +/// \brief Finds the SourceLocation of the colon ':' before the initialization +/// list in the definition of a constructor. +static SourceLocation getColonLoc(const ASTContext *Context, + const CXXConstructorDecl *Ctor) { + // FIXME: First init is the first initialization that is going to be + // performed, no matter what was the real order in the source code. If the + // order of the inits is wrong in the code, it may result in a false negative. + SourceLocation FirstInit = (*Ctor->init_begin())->getSourceLocation(); + SourceLocation LastArg = + Ctor->getParamDecl(Ctor->getNumParams() - 1)->getLocEnd(); + // We need to find the colon between the ')' and the first initializer. + bool Invalid = false; + StringRef Text = Lexer::getSourceText( + CharSourceRange::getCharRange(LastArg, FirstInit), + Context->getSourceManager(), Context->getLangOpts(), &Invalid); + if (Invalid) + return SourceLocation(); + + size_t ColonPos = Text.rfind(':'); + if (ColonPos == StringRef::npos) + return SourceLocation(); + + Text = Text.drop_front(ColonPos + 1); + if (std::strspn(Text.data(), " \t\r\n") != Text.size()) { + // If there are comments, preprocessor directives or anything, abort. + return SourceLocation(); + } + // FIXME: don't remove comments in the middle of the initializers. + return LastArg.getLocWithOffset(ColonPos); +} + +/// \brief Finds all the named non-static fields of \p Record. +static std::set +getAllNamedFields(const CXXRecordDecl *Record) { + std::set Result; + for (const auto *Field : Record->fields()) { + // Static data members are not in this range. + if (Field->isUnnamedBitfield()) + continue; + Result.insert(Field); + } + return Result; +} + +/// \brief Returns the names of the direct bases of \p Record, both virtual and +/// non-virtual. +static std::set getAllDirectBases(const CXXRecordDecl *Record) { + std::set Result; + for (auto Base : Record->bases()) { + // CXXBaseSpecifier. + const auto *BaseType = Base.getTypeSourceInfo()->getType().getTypePtr(); + Result.insert(BaseType); + } + return Result; +} + +/// \brief Returns a matcher that matches member expressions where the base is +/// the variable declared as \p Var and the accessed member is the one declared +/// as \p Field. +internal::Matcher accessToFieldInVar(const FieldDecl *Field, + const ValueDecl *Var) { + return ignoringImpCasts( + memberExpr(hasObjectExpression(declRefExpr(to(varDecl(equalsNode(Var))))), + member(fieldDecl(equalsNode(Field))))); +} + +/// \brief Check that the given constructor has copy signature and that it +/// copy-initializes all its bases and members. +static bool isCopyConstructorAndCanBeDefaulted(ASTContext *Context, + const CXXConstructorDecl *Ctor) { + // An explicitly-defaulted constructor cannot have default arguments. + if (Ctor->getMinRequiredArguments() != 1) + return false; + + const auto *Record = Ctor->getParent(); + const auto *Param = Ctor->getParamDecl(0); + + // Base classes and members that have to be copied. + auto BasesToInit = getAllDirectBases(Record); + auto FieldsToInit = getAllNamedFields(Record); + + // Ensure that all the bases are copied. + for (const auto *Base : BasesToInit) { + // The initialization of a base class should be a call to a copy + // constructor of the base. + if (match( + cxxConstructorDecl(forEachConstructorInitializer(cxxCtorInitializer( + isBaseInitializer(), + withInitializer(cxxConstructExpr(allOf( + hasType(equalsNode(Base)), + hasDeclaration(cxxConstructorDecl(isCopyConstructor())), + argumentCountIs(1), + hasArgument( + 0, declRefExpr(to(varDecl(equalsNode(Param))))))))))), + *Ctor, *Context) + .empty()) + return false; + } + + // Ensure that all the members are copied. + for (const auto *Field : FieldsToInit) { + auto AccessToFieldInParam = accessToFieldInVar(Field, Param); + // The initialization is a CXXConstructExpr for class types. + if (match( + cxxConstructorDecl(forEachConstructorInitializer(cxxCtorInitializer( + isMemberInitializer(), forField(equalsNode(Field)), + withInitializer(anyOf( + AccessToFieldInParam, + cxxConstructExpr(allOf( + hasDeclaration(cxxConstructorDecl(isCopyConstructor())), + argumentCountIs(1), + hasArgument(0, AccessToFieldInParam)))))))), + *Ctor, *Context) + .empty()) + return false; + } + + // Ensure that we don't do anything else, like initializing an indirect base. + return Ctor->getNumCtorInitializers() == + BasesToInit.size() + FieldsToInit.size(); +} + +/// \brief Checks that the given method is an overloading of the assignment +/// operator, has copy signature, returns a reference to "*this" and copies +/// all its members and subobjects. +static bool isCopyAssignmentAndCanBeDefaulted(ASTContext *Context, + const CXXMethodDecl *Operator) { + const auto *Record = Operator->getParent(); + const auto *Param = Operator->getParamDecl(0); + + // Base classes and members that have to be copied. + auto BasesToInit = getAllDirectBases(Record); + auto FieldsToInit = getAllNamedFields(Record); + + const auto *Compound = cast(Operator->getBody()); + + // The assignment operator definition has to end with the following return + // statement: + // return *this; + if (Compound->body_empty() || + match(returnStmt(has(unaryOperator(hasOperatorName("*"), + hasUnaryOperand(cxxThisExpr())))), + *Compound->body_back(), *Context) + .empty()) + return false; + + // Ensure that all the bases are copied. + for (const auto *Base : BasesToInit) { + // Assignment operator of a base class: + // Base::operator=(Other); + // + // Clang translates this into: + // ((Base*)this)->operator=((Base)Other); + // + // So we are looking for a member call that fulfills: + if (match( + compoundStmt(has(cxxMemberCallExpr(allOf( + // - The object is an implicit cast of 'this' to a pointer to + // a base class. + onImplicitObjectArgument( + implicitCastExpr(hasImplicitDestinationType( + pointsTo(type(equalsNode(Base)))), + hasSourceExpression(cxxThisExpr()))), + // - The called method is the operator=. + callee(cxxMethodDecl(isCopyAssignmentOperator())), + // - The argument is (an implicit cast to a Base of) the + // argument taken by "Operator". + argumentCountIs(1), + hasArgument(0, declRefExpr(to(varDecl(equalsNode(Param))))))))), + *Compound, *Context) + .empty()) + return false; + } + + // Ensure that all the members are copied. + for (const auto *Field : FieldsToInit) { + // The assignment of data members: + // Field = Other.Field; + // Is a BinaryOperator in non-class types, and a CXXOperatorCallExpr + // otherwise. + auto LHS = memberExpr(hasObjectExpression(cxxThisExpr()), + member(fieldDecl(equalsNode(Field)))); + auto RHS = accessToFieldInVar(Field, Param); + if (match( + compoundStmt(has(stmt(anyOf( + binaryOperator(hasOperatorName("="), hasLHS(LHS), hasRHS(RHS)), + cxxOperatorCallExpr(hasOverloadedOperatorName("="), + argumentCountIs(2), hasArgument(0, LHS), + hasArgument(1, RHS)))))), + *Compound, *Context) + .empty()) + return false; + } + + // Ensure that we don't do anything else. + return Compound->size() == BasesToInit.size() + FieldsToInit.size() + 1; +} + +/// \brief Returns false if the body has any non-whitespace character. +static bool bodyEmpty(const ASTContext *Context, const CompoundStmt *Body) { + bool Invalid = false; + StringRef Text = Lexer::getSourceText( + CharSourceRange::getCharRange(Body->getLBracLoc().getLocWithOffset(1), + Body->getRBracLoc()), + Context->getSourceManager(), Context->getLangOpts(), &Invalid); + return !Invalid && std::strspn(Text.data(), " \t\r\n") == Text.size(); +} + +void UseDefaultCheck::registerMatchers(MatchFinder *Finder) { + if (getLangOpts().CPlusPlus) { + // Destructor. + Finder->addMatcher(cxxDestructorDecl(isDefinition()).bind(SpecialFunction), + this); + Finder->addMatcher( + cxxConstructorDecl( + isDefinition(), + anyOf( + // Default constructor. + allOf(unless(hasAnyConstructorInitializer(anything())), + parameterCountIs(0)), + // Copy constructor. + allOf(isCopyConstructor(), + // Discard constructors that can be used as a copy + // constructor because all the other arguments have + // default values. + parameterCountIs(1)))) + .bind(SpecialFunction), + this); + // Copy-assignment operator. + Finder->addMatcher( + cxxMethodDecl(isDefinition(), isCopyAssignmentOperator(), + // isCopyAssignmentOperator() allows the parameter to be + // passed by value, and in this case it cannot be + // defaulted. + hasParameter(0, hasType(lValueReferenceType()))) + .bind(SpecialFunction), + this); + } +} + +void UseDefaultCheck::check(const MatchFinder::MatchResult &Result) { + std::string SpecialFunctionName; + SourceLocation StartLoc, EndLoc; + + // Both CXXConstructorDecl and CXXDestructorDecl inherit from CXXMethodDecl. + const auto *SpecialFunctionDecl = + Result.Nodes.getNodeAs(SpecialFunction); + + // Discard explicitly deleted/defaulted special member functions and those + // that are not user-provided (automatically generated). + if (SpecialFunctionDecl->isDeleted() || + SpecialFunctionDecl->isExplicitlyDefaulted() || + SpecialFunctionDecl->isLateTemplateParsed() || + !SpecialFunctionDecl->isUserProvided() || !SpecialFunctionDecl->hasBody()) + return; + + const auto *Body = dyn_cast(SpecialFunctionDecl->getBody()); + if (!Body) + return; + + // Default locations. + StartLoc = Body->getLBracLoc(); + EndLoc = Body->getRBracLoc(); + + // If there are comments inside the body, don't do the change. + if (!SpecialFunctionDecl->isCopyAssignmentOperator() && + !bodyEmpty(Result.Context, Body)) + return; + + if (const auto *Ctor = dyn_cast(SpecialFunctionDecl)) { + if (Ctor->getNumParams() == 0) { + SpecialFunctionName = "default constructor"; + } else { + if (!isCopyConstructorAndCanBeDefaulted(Result.Context, Ctor)) + return; + SpecialFunctionName = "copy constructor"; + } + // If there are constructor initializers, they must be removed. + if (Ctor->getNumCtorInitializers() != 0) { + StartLoc = getColonLoc(Result.Context, Ctor); + if (!StartLoc.isValid()) + return; + } + } else if (isa(SpecialFunctionDecl)) { + SpecialFunctionName = "destructor"; + } else { + if (!isCopyAssignmentAndCanBeDefaulted(Result.Context, SpecialFunctionDecl)) + return; + SpecialFunctionName = "copy-assignment operator"; + } + + diag(SpecialFunctionDecl->getLocStart(), + "use '= default' to define a trivial " + SpecialFunctionName) + << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(StartLoc, EndLoc), "= default;"); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseDefaultCheck.h b/clang-tidy/modernize/UseDefaultCheck.h new file mode 100644 index 00000000..74a9b5e5 --- /dev/null +++ b/clang-tidy/modernize/UseDefaultCheck.h @@ -0,0 +1,51 @@ +//===--- UseDefaultCheck.h - clang-tidy--------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_DEFAULT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_DEFAULT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// \brief Replace default bodies of special member functions with '= default;'. +/// \code +/// struct A { +/// A() {} +/// ~A(); +/// }; +/// A::~A() {} +/// \endcode +/// Is converted to: +/// \code +/// struct A { +/// A() = default; +/// ~A(); +/// }; +/// A::~A() = default; +/// \endcode +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-use-default.html +class UseDefaultCheck : public ClangTidyCheck { +public: + UseDefaultCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_DEFAULT_H + diff --git a/clang-tidy/modernize/UseNullptrCheck.cpp b/clang-tidy/modernize/UseNullptrCheck.cpp new file mode 100644 index 00000000..99e44068 --- /dev/null +++ b/clang-tidy/modernize/UseNullptrCheck.cpp @@ -0,0 +1,495 @@ +//===--- UseNullptrCheck.cpp - clang-tidy----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseNullptrCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang; +using namespace clang::ast_matchers; +using namespace llvm; + +namespace clang { +namespace tidy { +namespace modernize { +namespace { + +const char CastSequence[] = "sequence"; + +/// \brief Matches cast expressions that have a cast kind of CK_NullToPointer +/// or CK_NullToMemberPointer. +/// +/// Given +/// \code +/// int *p = 0; +/// \endcode +/// implicitCastExpr(isNullToPointer()) matches the implicit cast clang adds +/// around \c 0. +AST_MATCHER(CastExpr, isNullToPointer) { + return Node.getCastKind() == CK_NullToPointer || + Node.getCastKind() == CK_NullToMemberPointer; +} + +AST_MATCHER(Type, sugaredNullptrType) { + const Type *DesugaredType = Node.getUnqualifiedDesugaredType(); + if (const BuiltinType *BT = dyn_cast(DesugaredType)) + return BT->getKind() == BuiltinType::NullPtr; + return false; +} + +/// \brief Create a matcher that finds implicit casts as well as the head of a +/// sequence of zero or more nested explicit casts that have an implicit cast +/// to null within. +/// Finding sequences of explict casts is necessary so that an entire sequence +/// can be replaced instead of just the inner-most implicit cast. +StatementMatcher makeCastSequenceMatcher() { + StatementMatcher ImplicitCastToNull = implicitCastExpr( + isNullToPointer(), + unless(hasSourceExpression(hasType(sugaredNullptrType())))); + + return castExpr(anyOf(ImplicitCastToNull, + explicitCastExpr(hasDescendant(ImplicitCastToNull))), + unless(hasAncestor(explicitCastExpr()))) + .bind(CastSequence); +} + +bool isReplaceableRange(SourceLocation StartLoc, SourceLocation EndLoc, + const SourceManager &SM) { + return SM.isWrittenInSameFile(StartLoc, EndLoc); +} + +/// \brief Replaces the provided range with the text "nullptr", but only if +/// the start and end location are both in main file. +/// Returns true if and only if a replacement was made. +void replaceWithNullptr(ClangTidyCheck &Check, SourceManager &SM, + SourceLocation StartLoc, SourceLocation EndLoc) { + CharSourceRange Range(SourceRange(StartLoc, EndLoc), true); + // Add a space if nullptr follows an alphanumeric character. This happens + // whenever there is an c-style explicit cast to nullptr not surrounded by + // parentheses and right beside a return statement. + SourceLocation PreviousLocation = StartLoc.getLocWithOffset(-1); + bool NeedsSpace = isAlphanumeric(*SM.getCharacterData(PreviousLocation)); + Check.diag(Range.getBegin(), "use nullptr") << FixItHint::CreateReplacement( + Range, NeedsSpace ? " nullptr" : "nullptr"); +} + +/// \brief Returns the name of the outermost macro. +/// +/// Given +/// \code +/// #define MY_NULL NULL +/// \endcode +/// If \p Loc points to NULL, this function will return the name MY_NULL. +StringRef getOutermostMacroName(SourceLocation Loc, const SourceManager &SM, + const LangOptions &LO) { + assert(Loc.isMacroID()); + SourceLocation OutermostMacroLoc; + + while (Loc.isMacroID()) { + OutermostMacroLoc = Loc; + Loc = SM.getImmediateMacroCallerLoc(Loc); + } + + return Lexer::getImmediateMacroName(OutermostMacroLoc, SM, LO); +} + +/// \brief RecursiveASTVisitor for ensuring all nodes rooted at a given AST +/// subtree that have file-level source locations corresponding to a macro +/// argument have implicit NullTo(Member)Pointer nodes as ancestors. +class MacroArgUsageVisitor : public RecursiveASTVisitor { +public: + MacroArgUsageVisitor(SourceLocation CastLoc, const SourceManager &SM) + : CastLoc(CastLoc), SM(SM), Visited(false), CastFound(false), + InvalidFound(false) { + assert(CastLoc.isFileID()); + } + + bool TraverseStmt(Stmt *S) { + bool VisitedPreviously = Visited; + + if (!RecursiveASTVisitor::TraverseStmt(S)) + return false; + + // The point at which VisitedPreviously is false and Visited is true is the + // root of a subtree containing nodes whose locations match CastLoc. It's + // at this point we test that the Implicit NullTo(Member)Pointer cast was + // found or not. + if (!VisitedPreviously) { + if (Visited && !CastFound) { + // Found nodes with matching SourceLocations but didn't come across a + // cast. This is an invalid macro arg use. Can stop traversal + // completely now. + InvalidFound = true; + return false; + } + // Reset state as we unwind back up the tree. + CastFound = false; + Visited = false; + } + return true; + } + + bool VisitStmt(Stmt *S) { + if (SM.getFileLoc(S->getLocStart()) != CastLoc) + return true; + Visited = true; + + const ImplicitCastExpr *Cast = dyn_cast(S); + if (Cast && (Cast->getCastKind() == CK_NullToPointer || + Cast->getCastKind() == CK_NullToMemberPointer)) + CastFound = true; + + return true; + } + + bool TraverseInitListExpr(InitListExpr *S) { + // Only go through the semantic form of the InitListExpr, because + // ImplicitCast might not appear in the syntactic form, and this results in + // finding usages of the macro argument that don't have a ImplicitCast as an + // ancestor (thus invalidating the replacement) when they actually have. + return RecursiveASTVisitor:: + TraverseSynOrSemInitListExpr( + S->isSemanticForm() ? S : S->getSemanticForm()); + } + + bool foundInvalid() const { return InvalidFound; } + +private: + SourceLocation CastLoc; + const SourceManager &SM; + + bool Visited; + bool CastFound; + bool InvalidFound; +}; + +/// \brief Looks for implicit casts as well as sequences of 0 or more explicit +/// casts with an implicit null-to-pointer cast within. +/// +/// The matcher this visitor is used with will find a single implicit cast or a +/// top-most explicit cast (i.e. it has no explicit casts as an ancestor) where +/// an implicit cast is nested within. However, there is no guarantee that only +/// explicit casts exist between the found top-most explicit cast and the +/// possibly more than one nested implicit cast. This visitor finds all cast +/// sequences with an implicit cast to null within and creates a replacement +/// leaving the outermost explicit cast unchanged to avoid introducing +/// ambiguities. +class CastSequenceVisitor : public RecursiveASTVisitor { +public: + CastSequenceVisitor(ASTContext &Context, ArrayRef NullMacros, + ClangTidyCheck &check) + : SM(Context.getSourceManager()), Context(Context), + NullMacros(NullMacros), Check(check), FirstSubExpr(nullptr), + PruneSubtree(false) {} + + bool TraverseStmt(Stmt *S) { + // Stop traversing down the tree if requested. + if (PruneSubtree) { + PruneSubtree = false; + return true; + } + return RecursiveASTVisitor::TraverseStmt(S); + } + + // Only VisitStmt is overridden as we shouldn't find other base AST types + // within a cast expression. + bool VisitStmt(Stmt *S) { + CastExpr *C = dyn_cast(S); + if (!C) { + FirstSubExpr = nullptr; + return true; + } + if (!FirstSubExpr) + FirstSubExpr = C->getSubExpr()->IgnoreParens(); + + if (C->getCastKind() != CK_NullToPointer && + C->getCastKind() != CK_NullToMemberPointer) { + return true; + } + + SourceLocation StartLoc = FirstSubExpr->getLocStart(); + SourceLocation EndLoc = FirstSubExpr->getLocEnd(); + + // If the location comes from a macro arg expansion, *all* uses of that + // arg must be checked to result in NullTo(Member)Pointer casts. + // + // If the location comes from a macro body expansion, check to see if its + // coming from one of the allowed 'NULL' macros. + if (SM.isMacroArgExpansion(StartLoc) && SM.isMacroArgExpansion(EndLoc)) { + SourceLocation FileLocStart = SM.getFileLoc(StartLoc), + FileLocEnd = SM.getFileLoc(EndLoc); + if (isReplaceableRange(FileLocStart, FileLocEnd, SM) && + allArgUsesValid(C)) { + replaceWithNullptr(Check, SM, FileLocStart, FileLocEnd); + } + return skipSubTree(); + } + + if (SM.isMacroBodyExpansion(StartLoc) && SM.isMacroBodyExpansion(EndLoc)) { + StringRef OutermostMacroName = + getOutermostMacroName(StartLoc, SM, Context.getLangOpts()); + + // Check to see if the user wants to replace the macro being expanded. + if (std::find(NullMacros.begin(), NullMacros.end(), OutermostMacroName) == + NullMacros.end()) { + return skipSubTree(); + } + + StartLoc = SM.getFileLoc(StartLoc); + EndLoc = SM.getFileLoc(EndLoc); + } + + if (!isReplaceableRange(StartLoc, EndLoc, SM)) { + return skipSubTree(); + } + replaceWithNullptr(Check, SM, StartLoc, EndLoc); + + return skipSubTree(); + } + +private: + bool skipSubTree() { + PruneSubtree = true; + return true; + } + + /// \brief Tests that all expansions of a macro arg, one of which expands to + /// result in \p CE, yield NullTo(Member)Pointer casts. + bool allArgUsesValid(const CastExpr *CE) { + SourceLocation CastLoc = CE->getLocStart(); + + // Step 1: Get location of macro arg and location of the macro the arg was + // provided to. + SourceLocation ArgLoc, MacroLoc; + if (!getMacroAndArgLocations(CastLoc, ArgLoc, MacroLoc)) + return false; + + // Step 2: Find the first ancestor that doesn't expand from this macro. + ast_type_traits::DynTypedNode ContainingAncestor; + if (!findContainingAncestor( + ast_type_traits::DynTypedNode::create(*CE), MacroLoc, + ContainingAncestor)) + return false; + + // Step 3: + // Visit children of this containing parent looking for the least-descended + // nodes of the containing parent which are macro arg expansions that expand + // from the given arg location. + // Visitor needs: arg loc. + MacroArgUsageVisitor ArgUsageVisitor(SM.getFileLoc(CastLoc), SM); + if (const auto *D = ContainingAncestor.get()) + ArgUsageVisitor.TraverseDecl(const_cast(D)); + else if (const auto *S = ContainingAncestor.get()) + ArgUsageVisitor.TraverseStmt(const_cast(S)); + else + llvm_unreachable("Unhandled ContainingAncestor node type"); + + return !ArgUsageVisitor.foundInvalid(); + } + + /// \brief Given the SourceLocation for a macro arg expansion, finds the + /// non-macro SourceLocation of the macro the arg was passed to and the + /// non-macro SourceLocation of the argument in the arg list to that macro. + /// These results are returned via \c MacroLoc and \c ArgLoc respectively. + /// These values are undefined if the return value is false. + /// + /// \returns false if one of the returned SourceLocations would be a + /// SourceLocation pointing within the definition of another macro. + bool getMacroAndArgLocations(SourceLocation Loc, SourceLocation &ArgLoc, + SourceLocation &MacroLoc) { + assert(Loc.isMacroID() && "Only reasonble to call this on macros"); + + ArgLoc = Loc; + + // Find the location of the immediate macro expansion. + while (true) { + std::pair LocInfo = SM.getDecomposedLoc(ArgLoc); + const SrcMgr::SLocEntry *E = &SM.getSLocEntry(LocInfo.first); + const SrcMgr::ExpansionInfo &Expansion = E->getExpansion(); + + SourceLocation OldArgLoc = ArgLoc; + ArgLoc = Expansion.getExpansionLocStart(); + if (!Expansion.isMacroArgExpansion()) { + if (!MacroLoc.isFileID()) + return false; + + StringRef Name = + Lexer::getImmediateMacroName(OldArgLoc, SM, Context.getLangOpts()); + return std::find(NullMacros.begin(), NullMacros.end(), Name) != + NullMacros.end(); + } + + MacroLoc = SM.getImmediateExpansionRange(ArgLoc).first; + + ArgLoc = Expansion.getSpellingLoc().getLocWithOffset(LocInfo.second); + if (ArgLoc.isFileID()) + return true; + + // If spelling location resides in the same FileID as macro expansion + // location, it means there is no inner macro. + FileID MacroFID = SM.getFileID(MacroLoc); + if (SM.isInFileID(ArgLoc, MacroFID)) { + // Don't transform this case. If the characters that caused the + // null-conversion come from within a macro, they can't be changed. + return false; + } + } + + llvm_unreachable("getMacroAndArgLocations"); + } + + /// \brief Tests if TestMacroLoc is found while recursively unravelling + /// expansions starting at TestLoc. TestMacroLoc.isFileID() must be true. + /// Implementation is very similar to getMacroAndArgLocations() except in this + /// case, it's not assumed that TestLoc is expanded from a macro argument. + /// While unravelling expansions macro arguments are handled as with + /// getMacroAndArgLocations() but in this function macro body expansions are + /// also handled. + /// + /// False means either: + /// - TestLoc is not from a macro expansion. + /// - TestLoc is from a different macro expansion. + bool expandsFrom(SourceLocation TestLoc, SourceLocation TestMacroLoc) { + if (TestLoc.isFileID()) { + return false; + } + + SourceLocation Loc = TestLoc, MacroLoc; + + while (true) { + std::pair LocInfo = SM.getDecomposedLoc(Loc); + const SrcMgr::SLocEntry *E = &SM.getSLocEntry(LocInfo.first); + const SrcMgr::ExpansionInfo &Expansion = E->getExpansion(); + + Loc = Expansion.getExpansionLocStart(); + + if (!Expansion.isMacroArgExpansion()) { + if (Loc.isFileID()) { + return Loc == TestMacroLoc; + } + // Since Loc is still a macro ID and it's not an argument expansion, we + // don't need to do the work of handling an argument expansion. Simply + // keep recursively expanding until we hit a FileID or a macro arg + // expansion or a macro arg expansion. + continue; + } + + MacroLoc = SM.getImmediateExpansionRange(Loc).first; + if (MacroLoc.isFileID() && MacroLoc == TestMacroLoc) { + // Match made. + return true; + } + + Loc = Expansion.getSpellingLoc().getLocWithOffset(LocInfo.second); + if (Loc.isFileID()) { + // If we made it this far without finding a match, there is no match to + // be made. + return false; + } + } + + llvm_unreachable("expandsFrom"); + } + + /// \brief Given a starting point \c Start in the AST, find an ancestor that + /// doesn't expand from the macro called at file location \c MacroLoc. + /// + /// \pre MacroLoc.isFileID() + /// \returns true if such an ancestor was found, false otherwise. + bool findContainingAncestor(ast_type_traits::DynTypedNode Start, + SourceLocation MacroLoc, + ast_type_traits::DynTypedNode &Result) { + // Below we're only following the first parent back up the AST. This should + // be fine since for the statements we care about there should only be one + // parent, except for the case specified below. + + assert(MacroLoc.isFileID()); + + while (true) { + const auto &Parents = Context.getParents(Start); + if (Parents.empty()) + return false; + if (Parents.size() > 1) { + // If there are more than one parents, don't do the replacement unless + // they are InitListsExpr (semantic and syntactic form). In this case we + // can choose any one here, and the ASTVisitor will take care of + // traversing the right one. + for (const auto &Parent : Parents) { + if (!Parent.get()) + return false; + } + } + + const ast_type_traits::DynTypedNode &Parent = Parents[0]; + + SourceLocation Loc; + if (const auto *D = Parent.get()) + Loc = D->getLocStart(); + else if (const auto *S = Parent.get()) + Loc = S->getLocStart(); + + // TypeLoc and NestedNameSpecifierLoc are members of the parent map. Skip + // them and keep going up. + if (Loc.isValid()) { + if (!expandsFrom(Loc, MacroLoc)) { + Result = Parent; + return true; + } + } + Start = Parent; + } + + llvm_unreachable("findContainingAncestor"); + } + +private: + SourceManager &SM; + ASTContext &Context; + ArrayRef NullMacros; + ClangTidyCheck &Check; + Expr *FirstSubExpr; + bool PruneSubtree; +}; + +} // namespace + +UseNullptrCheck::UseNullptrCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + NullMacrosStr(Options.get("NullMacros", "")) { + StringRef(NullMacrosStr).split(NullMacros, ","); +} + +void UseNullptrCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "NullMacros", NullMacrosStr); +} + +void UseNullptrCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matcher for C++. Because this checker is used for + // modernization, it is reasonable to run it on any C++ standard with the + // assumption the user is trying to modernize their codebase. + if (getLangOpts().CPlusPlus) + Finder->addMatcher(makeCastSequenceMatcher(), this); +} + +void UseNullptrCheck::check(const MatchFinder::MatchResult &Result) { + const auto *NullCast = Result.Nodes.getNodeAs(CastSequence); + assert(NullCast && "Bad Callback. No node provided"); + + // Given an implicit null-ptr cast or an explicit cast with an implicit + // null-to-pointer cast within use CastSequenceVisitor to identify sequences + // of explicit casts that can be converted into 'nullptr'. + CastSequenceVisitor(*Result.Context, NullMacros, *this) + .TraverseStmt(const_cast(NullCast)); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseNullptrCheck.h b/clang-tidy/modernize/UseNullptrCheck.h new file mode 100644 index 00000000..4b33f1ee --- /dev/null +++ b/clang-tidy/modernize/UseNullptrCheck.h @@ -0,0 +1,35 @@ +//===--- UseNullptrCheck.h - clang-tidy--------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_NULLPTR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_NULLPTR_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +class UseNullptrCheck : public ClangTidyCheck { +public: + UseNullptrCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const std::string NullMacrosStr; + SmallVector NullMacros; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_NULLPTR_H diff --git a/clang-tidy/modernize/UseOverrideCheck.cpp b/clang-tidy/modernize/UseOverrideCheck.cpp new file mode 100644 index 00000000..a3357483 --- /dev/null +++ b/clang-tidy/modernize/UseOverrideCheck.cpp @@ -0,0 +1,197 @@ +//===--- UseOverrideCheck.cpp - clang-tidy --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseOverrideCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +void UseOverrideCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matcher for C++11. + if (getLangOpts().CPlusPlus11) + Finder->addMatcher(cxxMethodDecl(isOverride()).bind("method"), this); +} + +// Re-lex the tokens to get precise locations to insert 'override' and remove +// 'virtual'. +static SmallVector +ParseTokens(CharSourceRange Range, const MatchFinder::MatchResult &Result) { + const SourceManager &Sources = *Result.SourceManager; + std::pair LocInfo = + Sources.getDecomposedLoc(Range.getBegin()); + StringRef File = Sources.getBufferData(LocInfo.first); + const char *TokenBegin = File.data() + LocInfo.second; + Lexer RawLexer(Sources.getLocForStartOfFile(LocInfo.first), + Result.Context->getLangOpts(), File.begin(), TokenBegin, + File.end()); + SmallVector Tokens; + Token Tok; + while (!RawLexer.LexFromRawLexer(Tok)) { + if (Tok.is(tok::semi) || Tok.is(tok::l_brace)) + break; + if (Sources.isBeforeInTranslationUnit(Range.getEnd(), Tok.getLocation())) + break; + if (Tok.is(tok::raw_identifier)) { + IdentifierInfo &Info = Result.Context->Idents.get(StringRef( + Sources.getCharacterData(Tok.getLocation()), Tok.getLength())); + Tok.setIdentifierInfo(&Info); + Tok.setKind(Info.getTokenID()); + } + Tokens.push_back(Tok); + } + return Tokens; +} + +static StringRef GetText(const Token &Tok, const SourceManager &Sources) { + return StringRef(Sources.getCharacterData(Tok.getLocation()), + Tok.getLength()); +} + +void UseOverrideCheck::check(const MatchFinder::MatchResult &Result) { + const FunctionDecl *Method = Result.Nodes.getStmtAs("method"); + const SourceManager &Sources = *Result.SourceManager; + + assert(Method != nullptr); + if (Method->getInstantiatedFromMemberFunction() != nullptr) + Method = Method->getInstantiatedFromMemberFunction(); + + if (Method->isImplicit() || Method->getLocation().isMacroID() || + Method->isOutOfLine()) + return; + + bool HasVirtual = Method->isVirtualAsWritten(); + bool HasOverride = Method->getAttr(); + bool HasFinal = Method->getAttr(); + + bool OnlyVirtualSpecified = HasVirtual && !HasOverride && !HasFinal; + unsigned KeywordCount = HasVirtual + HasOverride + HasFinal; + + if (!OnlyVirtualSpecified && KeywordCount == 1) + return; // Nothing to do. + + std::string Message; + + if (OnlyVirtualSpecified) { + Message = + "prefer using 'override' or (rarely) 'final' instead of 'virtual'"; + } else if (KeywordCount == 0) { + Message = "annotate this function with 'override' or (rarely) 'final'"; + } else { + StringRef Redundant = + HasVirtual ? (HasOverride && HasFinal ? "'virtual' and 'override' are" + : "'virtual' is") + : "'override' is"; + StringRef Correct = HasFinal ? "'final'" : "'override'"; + + Message = + (llvm::Twine(Redundant) + + " redundant since the function is already declared " + Correct).str(); + } + + DiagnosticBuilder Diag = diag(Method->getLocation(), Message); + + CharSourceRange FileRange = Lexer::makeFileCharRange( + CharSourceRange::getTokenRange(Method->getSourceRange()), Sources, + Result.Context->getLangOpts()); + + if (!FileRange.isValid()) + return; + + // FIXME: Instead of re-lexing and looking for specific macros such as + // 'ABSTRACT', properly store the location of 'virtual' and '= 0' in each + // FunctionDecl. + SmallVector Tokens = ParseTokens(FileRange, Result); + + // Add 'override' on inline declarations that don't already have it. + if (!HasFinal && !HasOverride) { + SourceLocation InsertLoc; + StringRef ReplacementText = "override "; + + for (Token T : Tokens) { + if (T.is(tok::kw___attribute)) { + InsertLoc = T.getLocation(); + break; + } + } + + if (Method->hasAttrs()) { + for (const clang::Attr *A : Method->getAttrs()) { + if (!A->isImplicit()) { + SourceLocation Loc = + Sources.getExpansionLoc(A->getRange().getBegin()); + if (!InsertLoc.isValid() || + Sources.isBeforeInTranslationUnit(Loc, InsertLoc)) + InsertLoc = Loc; + } + } + } + + if (InsertLoc.isInvalid() && Method->doesThisDeclarationHaveABody() && + Method->getBody() && !Method->isDefaulted()) { + // For methods with inline definition, add the override keyword at the + // end of the declaration of the function, but prefer to put it on the + // same line as the declaration if the beginning brace for the start of + // the body falls on the next line. + Token LastNonCommentToken; + for (Token T : Tokens) { + if (!T.is(tok::comment)) { + LastNonCommentToken = T; + } + } + InsertLoc = LastNonCommentToken.getEndLoc(); + ReplacementText = " override"; + } + + if (!InsertLoc.isValid()) { + // For declarations marked with "= 0" or "= [default|delete]", the end + // location will point until after those markings. Therefore, the override + // keyword shouldn't be inserted at the end, but before the '='. + if (Tokens.size() > 2 && (GetText(Tokens.back(), Sources) == "0" || + Tokens.back().is(tok::kw_default) || + Tokens.back().is(tok::kw_delete)) && + GetText(Tokens[Tokens.size() - 2], Sources) == "=") { + InsertLoc = Tokens[Tokens.size() - 2].getLocation(); + } else if (GetText(Tokens.back(), Sources) == "ABSTRACT") { + InsertLoc = Tokens.back().getLocation(); + } + } + + if (!InsertLoc.isValid()) { + InsertLoc = FileRange.getEnd(); + ReplacementText = " override"; + } + Diag << FixItHint::CreateInsertion(InsertLoc, ReplacementText); + } + + if (HasFinal && HasOverride) { + SourceLocation OverrideLoc = Method->getAttr()->getLocation(); + Diag << FixItHint::CreateRemoval( + CharSourceRange::getTokenRange(OverrideLoc, OverrideLoc)); + } + + if (HasVirtual) { + for (Token Tok : Tokens) { + if (Tok.is(tok::kw_virtual)) { + Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange( + Tok.getLocation(), Tok.getLocation())); + break; + } + } + } +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseOverrideCheck.h b/clang-tidy/modernize/UseOverrideCheck.h new file mode 100644 index 00000000..83ce7da7 --- /dev/null +++ b/clang-tidy/modernize/UseOverrideCheck.h @@ -0,0 +1,32 @@ +//===--- UseOverrideCheck.h - clang-tidy ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEOVERRIDECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEOVERRIDECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Use C++11's `override` and remove `virtual` where applicable. +class UseOverrideCheck : public ClangTidyCheck { +public: + UseOverrideCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEOVERRIDECHECK_H diff --git a/clang-tidy/performance/CMakeLists.txt b/clang-tidy/performance/CMakeLists.txt new file mode 100644 index 00000000..97225781 --- /dev/null +++ b/clang-tidy/performance/CMakeLists.txt @@ -0,0 +1,14 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyPerformanceModule + PerformanceTidyModule.cpp + UnnecessaryCopyInitialization.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + ) diff --git a/clang-tidy/performance/Makefile b/clang-tidy/performance/Makefile new file mode 100644 index 00000000..afa9535b --- /dev/null +++ b/clang-tidy/performance/Makefile @@ -0,0 +1,12 @@ +##===- clang-tidy/performance/Makefile ---------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## +CLANG_LEVEL := ../../../.. +LIBRARYNAME := clangTidyPerformanceModule + +include $(CLANG_LEVEL)/Makefile diff --git a/clang-tidy/performance/PerformanceTidyModule.cpp b/clang-tidy/performance/PerformanceTidyModule.cpp new file mode 100644 index 00000000..ab0dbc80 --- /dev/null +++ b/clang-tidy/performance/PerformanceTidyModule.cpp @@ -0,0 +1,39 @@ +//===--- PeformanceTidyModule.cpp - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" + +#include "UnnecessaryCopyInitialization.h" + +namespace clang { +namespace tidy { +namespace performance { + +class PerformanceModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "performance-unnecessary-copy-initialization"); + } +}; + +// Register the PerformanceModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("performance-module", "Adds performance checks."); + +} // namespace performance + +// This anchor is used to force the linker to link in the generated object file +// and thus register the PerformanceModule. +volatile int PerformanceModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/UnnecessaryCopyInitialization.cpp b/clang-tidy/performance/UnnecessaryCopyInitialization.cpp new file mode 100644 index 00000000..37883e98 --- /dev/null +++ b/clang-tidy/performance/UnnecessaryCopyInitialization.cpp @@ -0,0 +1,72 @@ +//===--- UnnecessaryCopyInitialization.cpp - clang-tidy--------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnnecessaryCopyInitialization.h" + +#include "../utils/LexerUtils.h" +#include "../utils/Matchers.h" + +namespace clang { +namespace tidy { +namespace performance { + +using namespace ::clang::ast_matchers; + +namespace { +AST_MATCHER(QualType, isPointerType) { return Node->isPointerType(); } +} // namespace + +void UnnecessaryCopyInitialization::registerMatchers( + ast_matchers::MatchFinder *Finder) { + auto ConstReference = referenceType(pointee(qualType(isConstQualified()))); + auto ConstOrConstReference = + allOf(anyOf(ConstReference, isConstQualified()), + unless(allOf(isPointerType(), unless(pointerType(pointee(qualType( + isConstQualified()))))))); + // Match method call expressions where the this argument is a const + // type or const reference. This returned const reference is highly likely to + // outlive the local const reference of the variable being declared. + // The assumption is that the const reference being returned either points + // to a global static variable or to a member of the called object. + auto ConstRefReturningMethodCallOfConstParam = cxxMemberCallExpr( + callee(cxxMethodDecl(returns(ConstReference))), + on(declRefExpr(to(varDecl(hasType(qualType(ConstOrConstReference))))))); + auto ConstRefReturningFunctionCall = + callExpr(callee(functionDecl(returns(ConstReference))), + unless(callee(cxxMethodDecl()))); + Finder->addMatcher( + varDecl( + hasLocalStorage(), hasType(isConstQualified()), + hasType(matchers::isExpensiveToCopy()), + hasInitializer(cxxConstructExpr( + hasDeclaration(cxxConstructorDecl(isCopyConstructor())), + hasArgument(0, anyOf(ConstRefReturningFunctionCall, + ConstRefReturningMethodCallOfConstParam))))) + .bind("varDecl"), + this); +} + +void UnnecessaryCopyInitialization::check( + const ast_matchers::MatchFinder::MatchResult &Result) { + const auto *Var = Result.Nodes.getNodeAs("varDecl"); + SourceLocation AmpLocation = Var->getLocation(); + auto Token = lexer_utils::getPreviousNonCommentToken(*Result.Context, + Var->getLocation()); + if (!Token.is(tok::unknown)) { + AmpLocation = Token.getLocation().getLocWithOffset(Token.getLength()); + } + diag(Var->getLocation(), + "the const qualified variable '%0' is copy-constructed from a " + "const reference; consider making it a const reference") + << Var->getName() << FixItHint::CreateInsertion(AmpLocation, "&"); +} + +} // namespace performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/UnnecessaryCopyInitialization.h b/clang-tidy/performance/UnnecessaryCopyInitialization.h new file mode 100644 index 00000000..22a80a86 --- /dev/null +++ b/clang-tidy/performance/UnnecessaryCopyInitialization.h @@ -0,0 +1,39 @@ +//===--- UnnecessaryCopyInitialization.h - clang-tidy------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_UNNECESSARY_COPY_INITIALIZATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_UNNECESSARY_COPY_INITIALIZATION_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace performance { + +// A check that detects const local variable declarations that are copy +// initialized with the const reference of a function call or the const +// reference of a method call whose object is guaranteed to outlive the +// variable's scope and suggests to use a const reference. +// +// The check currently only understands a subset of variables that are +// guaranteed to outlive the const reference returned, namely: const variables, +// const references, and const pointers to const. +class UnnecessaryCopyInitialization : public ClangTidyCheck { +public: + UnnecessaryCopyInitialization(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_UNNECESSARY_COPY_INITIALIZATION_H diff --git a/clang-tidy/readability/BracesAroundStatementsCheck.cpp b/clang-tidy/readability/BracesAroundStatementsCheck.cpp new file mode 100644 index 00000000..9077a027 --- /dev/null +++ b/clang-tidy/readability/BracesAroundStatementsCheck.cpp @@ -0,0 +1,274 @@ +//===--- BracesAroundStatementsCheck.cpp - clang-tidy ---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "BracesAroundStatementsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { +namespace { + +tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM, + const ASTContext *Context) { + Token Tok; + SourceLocation Beginning = + Lexer::GetBeginningOfToken(Loc, SM, Context->getLangOpts()); + const bool Invalid = + Lexer::getRawToken(Beginning, Tok, SM, Context->getLangOpts()); + assert(!Invalid && "Expected a valid token."); + + if (Invalid) + return tok::NUM_TOKENS; + + return Tok.getKind(); +} + +SourceLocation forwardSkipWhitespaceAndComments(SourceLocation Loc, + const SourceManager &SM, + const ASTContext *Context) { + assert(Loc.isValid()); + for (;;) { + while (isWhitespace(*FullSourceLoc(Loc, SM).getCharacterData())) + Loc = Loc.getLocWithOffset(1); + + tok::TokenKind TokKind = getTokenKind(Loc, SM, Context); + if (TokKind == tok::NUM_TOKENS || TokKind != tok::comment) + return Loc; + + // Fast-forward current token. + Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts()); + } +} + +SourceLocation findEndLocation(SourceLocation LastTokenLoc, + const SourceManager &SM, + const ASTContext *Context) { + SourceLocation Loc = LastTokenLoc; + // Loc points to the beginning of the last (non-comment non-ws) token + // before end or ';'. + assert(Loc.isValid()); + bool SkipEndWhitespaceAndComments = true; + tok::TokenKind TokKind = getTokenKind(Loc, SM, Context); + if (TokKind == tok::NUM_TOKENS || TokKind == tok::semi || + TokKind == tok::r_brace) { + // If we are at ";" or "}", we found the last token. We could use as well + // `if (isa(S))`, but it wouldn't work for nested statements. + SkipEndWhitespaceAndComments = false; + } + + Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts()); + // Loc points past the last token before end or after ';'. + + if (SkipEndWhitespaceAndComments) { + Loc = forwardSkipWhitespaceAndComments(Loc, SM, Context); + tok::TokenKind TokKind = getTokenKind(Loc, SM, Context); + if (TokKind == tok::semi) + Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts()); + } + + for (;;) { + assert(Loc.isValid()); + while (isHorizontalWhitespace(*FullSourceLoc(Loc, SM).getCharacterData())) + Loc = Loc.getLocWithOffset(1); + + if (isVerticalWhitespace(*FullSourceLoc(Loc, SM).getCharacterData())) { + // EOL, insert brace before. + break; + } + tok::TokenKind TokKind = getTokenKind(Loc, SM, Context); + if (TokKind != tok::comment) { + // Non-comment token, insert brace before. + break; + } + + SourceLocation TokEndLoc = + Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts()); + SourceRange TokRange(Loc, TokEndLoc); + StringRef Comment = Lexer::getSourceText( + CharSourceRange::getTokenRange(TokRange), SM, Context->getLangOpts()); + if (Comment.startswith("/*") && Comment.find('\n') != StringRef::npos) { + // Multi-line block comment, insert brace before. + break; + } + // else: Trailing comment, insert brace after the newline. + + // Fast-forward current token. + Loc = TokEndLoc; + } + return Loc; +} + +} // namespace + +BracesAroundStatementsCheck::BracesAroundStatementsCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + // Always add braces by default. + ShortStatementLines(Options.get("ShortStatementLines", 0U)) {} + +void +BracesAroundStatementsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ShortStatementLines", ShortStatementLines); +} + +void BracesAroundStatementsCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(ifStmt().bind("if"), this); + Finder->addMatcher(whileStmt().bind("while"), this); + Finder->addMatcher(doStmt().bind("do"), this); + Finder->addMatcher(forStmt().bind("for"), this); + Finder->addMatcher(cxxForRangeStmt().bind("for-range"), this); +} + +void +BracesAroundStatementsCheck::check(const MatchFinder::MatchResult &Result) { + const SourceManager &SM = *Result.SourceManager; + const ASTContext *Context = Result.Context; + + // Get location of closing parenthesis or 'do' to insert opening brace. + if (auto S = Result.Nodes.getNodeAs("for")) { + checkStmt(Result, S->getBody(), S->getRParenLoc()); + } else if (auto S = Result.Nodes.getNodeAs("for-range")) { + checkStmt(Result, S->getBody(), S->getRParenLoc()); + } else if (auto S = Result.Nodes.getNodeAs("do")) { + checkStmt(Result, S->getBody(), S->getDoLoc(), S->getWhileLoc()); + } else if (auto S = Result.Nodes.getNodeAs("while")) { + SourceLocation StartLoc = findRParenLoc(S, SM, Context); + if (StartLoc.isInvalid()) + return; + checkStmt(Result, S->getBody(), StartLoc); + } else if (auto S = Result.Nodes.getNodeAs("if")) { + SourceLocation StartLoc = findRParenLoc(S, SM, Context); + if (StartLoc.isInvalid()) + return; + if (ForceBracesStmts.erase(S)) + ForceBracesStmts.insert(S->getThen()); + bool BracedIf = checkStmt(Result, S->getThen(), StartLoc, S->getElseLoc()); + const Stmt *Else = S->getElse(); + if (Else && BracedIf) + ForceBracesStmts.insert(Else); + if (Else && !isa(Else)) { + // Omit 'else if' statements here, they will be handled directly. + checkStmt(Result, Else, S->getElseLoc(), SourceLocation()); + } + } else { + llvm_unreachable("Invalid match"); + } +} + +/// Find location of right parenthesis closing condition +template +SourceLocation +BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt *S, + const SourceManager &SM, + const ASTContext *Context) { + // Skip macros. + if (S->getLocStart().isMacroID()) + return SourceLocation(); + + SourceLocation CondEndLoc = S->getCond()->getLocEnd(); + if (const DeclStmt *CondVar = S->getConditionVariableDeclStmt()) + CondEndLoc = CondVar->getLocEnd(); + + assert(CondEndLoc.isValid()); + SourceLocation PastCondEndLoc = + Lexer::getLocForEndOfToken(CondEndLoc, 0, SM, Context->getLangOpts()); + if (PastCondEndLoc.isInvalid()) + return SourceLocation(); + SourceLocation RParenLoc = + forwardSkipWhitespaceAndComments(PastCondEndLoc, SM, Context); + if (RParenLoc.isInvalid()) + return SourceLocation(); + tok::TokenKind TokKind = getTokenKind(RParenLoc, SM, Context); + if (TokKind != tok::r_paren) + return SourceLocation(); + return RParenLoc; +} + +/// Determine if the statement needs braces around it, and add them if it does. +/// Returns true if braces where added. +bool BracesAroundStatementsCheck::checkStmt( + const MatchFinder::MatchResult &Result, const Stmt *S, + SourceLocation InitialLoc, SourceLocation EndLocHint) { + // 1) If there's a corresponding "else" or "while", the check inserts "} " + // right before that token. + // 2) If there's a multi-line block comment starting on the same line after + // the location we're inserting the closing brace at, or there's a non-comment + // token, the check inserts "\n}" right before that token. + // 3) Otherwise the check finds the end of line (possibly after some block or + // line comments) and inserts "\n}" right before that EOL. + if (!S || isa(S)) { + // Already inside braces. + return false; + } + + const SourceManager &SM = *Result.SourceManager; + const ASTContext *Context = Result.Context; + + // Treat macros. + CharSourceRange FileRange = Lexer::makeFileCharRange( + CharSourceRange::getTokenRange(S->getSourceRange()), SM, + Context->getLangOpts()); + if (FileRange.isInvalid()) + return false; + + // InitialLoc points at the last token before opening brace to be inserted. + assert(InitialLoc.isValid()); + // Convert InitialLoc to file location, if it's on the same macro expansion + // level as the start of the statement. We also need file locations for + // Lexer::getLocForEndOfToken working properly. + InitialLoc = Lexer::makeFileCharRange( + CharSourceRange::getCharRange(InitialLoc, S->getLocStart()), + SM, Context->getLangOpts()) + .getBegin(); + if (InitialLoc.isInvalid()) + return false; + SourceLocation StartLoc = + Lexer::getLocForEndOfToken(InitialLoc, 0, SM, Context->getLangOpts()); + + // StartLoc points at the location of the opening brace to be inserted. + SourceLocation EndLoc; + std::string ClosingInsertion; + if (EndLocHint.isValid()) { + EndLoc = EndLocHint; + ClosingInsertion = "} "; + } else { + const auto FREnd = FileRange.getEnd().getLocWithOffset(-1); + EndLoc = findEndLocation(FREnd, SM, Context); + ClosingInsertion = "\n}"; + } + + assert(StartLoc.isValid()); + assert(EndLoc.isValid()); + // Don't require braces for statements spanning less than certain number of + // lines. + if (ShortStatementLines && !ForceBracesStmts.erase(S)) { + unsigned StartLine = SM.getSpellingLineNumber(StartLoc); + unsigned EndLine = SM.getSpellingLineNumber(EndLoc); + if (EndLine - StartLine < ShortStatementLines) + return false; + } + + auto Diag = diag(StartLoc, "statement should be inside braces"); + Diag << FixItHint::CreateInsertion(StartLoc, " {") + << FixItHint::CreateInsertion(EndLoc, ClosingInsertion); + return true; +} + +void BracesAroundStatementsCheck::onEndOfTranslationUnit() { + ForceBracesStmts.clear(); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/BracesAroundStatementsCheck.h b/clang-tidy/readability/BracesAroundStatementsCheck.h new file mode 100644 index 00000000..12d7b37a --- /dev/null +++ b/clang-tidy/readability/BracesAroundStatementsCheck.h @@ -0,0 +1,69 @@ +//===--- BracesAroundStatementsCheck.h - clang-tidy -------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_BRACESAROUNDSTATEMENTSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_BRACESAROUNDSTATEMENTSCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Checks that bodies of `if` statements and loops (`for`, `range-for`, +/// `do-while`, and `while`) are inside braces +/// +/// Before: +/// +/// \code +/// if (condition) +/// statement; +/// \endcode +/// +/// After: +/// +/// \code +/// if (condition) { +/// statement; +/// } +/// \endcode +/// +/// Additionally, one can define an option `ShortStatementLines` defining the +/// minimal number of lines that the statement should have in order to trigger +/// this check. +/// +/// The number of lines is counted from the end of condition or initial keyword +/// (`do`/`else`) until the last line of the inner statement. Default value 0 +/// means that braces will be added to all statements (not having them already). +class BracesAroundStatementsCheck : public ClangTidyCheck { +public: + BracesAroundStatementsCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; + +private: + bool checkStmt(const ast_matchers::MatchFinder::MatchResult &Result, + const Stmt *S, SourceLocation StartLoc, + SourceLocation EndLocHint = SourceLocation()); + template + SourceLocation findRParenLoc(const IfOrWhileStmt *S, const SourceManager &SM, + const ASTContext *Context); + +private: + std::set ForceBracesStmts; + const unsigned ShortStatementLines; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_BRACESAROUNDSTATEMENTSCHECK_H diff --git a/clang-tidy/readability/CMakeLists.txt b/clang-tidy/readability/CMakeLists.txt new file mode 100644 index 00000000..30ae5494 --- /dev/null +++ b/clang-tidy/readability/CMakeLists.txt @@ -0,0 +1,26 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyReadabilityModule + BracesAroundStatementsCheck.cpp + ContainerSizeEmptyCheck.cpp + ElseAfterReturnCheck.cpp + FunctionSizeCheck.cpp + IdentifierNamingCheck.cpp + ImplicitBoolCastCheck.cpp + InconsistentDeclarationParameterNameCheck.cpp + NamedParameterCheck.cpp + NamespaceCommentCheck.cpp + ReadabilityTidyModule.cpp + RedundantStringCStrCheck.cpp + RedundantSmartptrGetCheck.cpp + SimplifyBooleanExprCheck.cpp + UniqueptrDeleteReleaseCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTooling + ) diff --git a/clang-tidy/readability/ContainerSizeEmptyCheck.cpp b/clang-tidy/readability/ContainerSizeEmptyCheck.cpp new file mode 100644 index 00000000..bb4cd6cf --- /dev/null +++ b/clang-tidy/readability/ContainerSizeEmptyCheck.cpp @@ -0,0 +1,173 @@ +//===--- ContainerSizeEmptyCheck.cpp - clang-tidy -------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#include "ContainerSizeEmptyCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/StringRef.h" + +using namespace clang::ast_matchers; + +static bool isContainer(llvm::StringRef ClassName) { + static const char *const ContainerNames[] = {"std::array", + "std::deque", + "std::forward_list", + "std::list", + "std::map", + "std::multimap", + "std::multiset", + "std::priority_queue", + "std::queue", + "std::set", + "std::stack", + "std::unordered_map", + "std::unordered_multimap", + "std::unordered_multiset", + "std::unordered_set", + "std::vector"}; + return std::binary_search(std::begin(ContainerNames), + std::end(ContainerNames), ClassName); +} + +namespace clang { +namespace { +AST_MATCHER(QualType, isBoolType) { return Node->isBooleanType(); } + +AST_MATCHER(NamedDecl, stlContainer) { + return isContainer(Node.getQualifiedNameAsString()); +} +} // namespace + +namespace tidy { +namespace readability { + +ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + +void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + const auto WrongUse = anyOf( + hasParent( + binaryOperator( + anyOf(has(integerLiteral(equals(0))), + allOf(anyOf(hasOperatorName("<"), hasOperatorName(">="), + hasOperatorName(">"), hasOperatorName("<=")), + hasEitherOperand( + ignoringImpCasts(integerLiteral(equals(1))))))) + .bind("SizeBinaryOp")), + hasParent(implicitCastExpr( + hasImplicitDestinationType(isBoolType()), + anyOf( + hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")), + anything()))), + hasParent(explicitCastExpr(hasDestinationType(isBoolType())))); + + Finder->addMatcher( + cxxMemberCallExpr( + on(expr(anyOf(hasType(namedDecl(stlContainer())), + hasType(pointsTo(namedDecl(stlContainer()))), + hasType(references(namedDecl(stlContainer()))))) + .bind("STLObject")), + callee(cxxMethodDecl(hasName("size"))), WrongUse) + .bind("SizeCallExpr"), + this); +} + +void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MemberCall = + Result.Nodes.getNodeAs("SizeCallExpr"); + const auto *BinaryOp = Result.Nodes.getNodeAs("SizeBinaryOp"); + const auto *E = Result.Nodes.getNodeAs("STLObject"); + FixItHint Hint; + std::string ReplacementText = Lexer::getSourceText( + CharSourceRange::getTokenRange(E->getSourceRange()), + *Result.SourceManager, Result.Context->getLangOpts()); + if (E->getType()->isPointerType()) + ReplacementText += "->empty()"; + else + ReplacementText += ".empty()"; + + if (BinaryOp) { // Determine the correct transformation. + bool Negation = false; + const bool ContainerIsLHS = + !llvm::isa(BinaryOp->getLHS()->IgnoreImpCasts()); + const auto OpCode = BinaryOp->getOpcode(); + uint64_t Value = 0; + if (ContainerIsLHS) { + if (const auto *Literal = llvm::dyn_cast( + BinaryOp->getRHS()->IgnoreImpCasts())) + Value = Literal->getValue().getLimitedValue(); + else + return; + } else { + Value = + llvm::dyn_cast(BinaryOp->getLHS()->IgnoreImpCasts()) + ->getValue() + .getLimitedValue(); + } + + // Constant that is not handled. + if (Value > 1) + return; + + // Always true, no warnings for that. + if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) || + (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS)) + return; + + // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size. + if (Value == 1) { + if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) || + (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS)) + return; + if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) || + (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS)) + return; + } + + if (OpCode == BinaryOperatorKind::BO_NE && Value == 0) + Negation = true; + if ((OpCode == BinaryOperatorKind::BO_GT || + OpCode == BinaryOperatorKind::BO_GE) && + ContainerIsLHS) + Negation = true; + if ((OpCode == BinaryOperatorKind::BO_LT || + OpCode == BinaryOperatorKind::BO_LE) && + !ContainerIsLHS) + Negation = true; + + if (Negation) + ReplacementText = "!" + ReplacementText; + Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(), + ReplacementText); + + } else { + // If there is a conversion above the size call to bool, it is safe to just + // replace size with empty. + if (const auto *UnaryOp = + Result.Nodes.getNodeAs("NegOnSize")) + Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(), + ReplacementText); + else + Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(), + "!" + ReplacementText); + } + diag(MemberCall->getLocStart(), "the 'empty' method should be used to check " + "for emptiness instead of 'size'") + << Hint; +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/ContainerSizeEmptyCheck.h b/clang-tidy/readability/ContainerSizeEmptyCheck.h new file mode 100644 index 00000000..bde83f88 --- /dev/null +++ b/clang-tidy/readability/ContainerSizeEmptyCheck.h @@ -0,0 +1,40 @@ +//===--- ContainerSizeEmptyCheck.h - clang-tidy -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERSIZEEMPTYCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERSIZEEMPTYCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Checks whether a call to the `size()` method can be replaced with a call to +/// `empty()`. +/// +/// The emptiness of a container should be checked using the `empty()` method +/// instead of the `size()` method. It is not guaranteed that `size()` is a +/// constant-time function, and it is generally more efficient and also shows +/// clearer intent to use `empty()`. Furthermore some containers may implement +/// the `empty()` method but not implement the `size()` method. Using `empty()` +/// whenever possible makes it easier to switch to another container in the +/// future. +class ContainerSizeEmptyCheck : public ClangTidyCheck { +public: + ContainerSizeEmptyCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERSIZEEMPTYCHECK_H diff --git a/clang-tidy/readability/ElseAfterReturnCheck.cpp b/clang-tidy/readability/ElseAfterReturnCheck.cpp new file mode 100644 index 00000000..08ea207d --- /dev/null +++ b/clang-tidy/readability/ElseAfterReturnCheck.cpp @@ -0,0 +1,49 @@ +//===--- ElseAfterReturnCheck.cpp - clang-tidy-----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ElseAfterReturnCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void ElseAfterReturnCheck::registerMatchers(MatchFinder *Finder) { + // FIXME: Support continue, break and throw. + Finder->addMatcher( + compoundStmt( + forEach(ifStmt(hasThen(stmt(anyOf(returnStmt(), + compoundStmt(has(returnStmt()))))), + hasElse(stmt().bind("else"))) + .bind("if"))), + this); +} + +static FixItHint removeToken(SourceLocation Loc) { + return FixItHint::CreateRemoval(CharSourceRange::getTokenRange(Loc, Loc)); +} + +void ElseAfterReturnCheck::check(const MatchFinder::MatchResult &Result) { + const auto *If = Result.Nodes.getNodeAs("if"); + SourceLocation ElseLoc = If->getElseLoc(); + DiagnosticBuilder Diag = diag(ElseLoc, "don't use else after return"); + Diag << removeToken(ElseLoc); + + // FIXME: Removing the braces isn't always safe. Do a more careful analysis. + // FIXME: Change clang-format to correctly un-indent the code. + if (const auto *CS = Result.Nodes.getNodeAs("else")) + Diag << removeToken(CS->getLBracLoc()) << removeToken(CS->getRBracLoc()); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/ElseAfterReturnCheck.h b/clang-tidy/readability/ElseAfterReturnCheck.h new file mode 100644 index 00000000..8479ab50 --- /dev/null +++ b/clang-tidy/readability/ElseAfterReturnCheck.h @@ -0,0 +1,34 @@ +//===--- ElseAfterReturnCheck.h - clang-tidy---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ELSEAFTERRETURNCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ELSEAFTERRETURNCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Flags the usages of `else` after `return`. +/// +/// http://llvm.org/docs/CodingStandards.html#don-t-use-else-after-a-return +class ElseAfterReturnCheck : public ClangTidyCheck { +public: + ElseAfterReturnCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ELSEAFTERRETURNCHECK_H diff --git a/clang-tidy/readability/FunctionSizeCheck.cpp b/clang-tidy/readability/FunctionSizeCheck.cpp new file mode 100644 index 00000000..42ce65b3 --- /dev/null +++ b/clang-tidy/readability/FunctionSizeCheck.cpp @@ -0,0 +1,106 @@ +//===--- FunctionSize.cpp - clang-tidy ------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "FunctionSizeCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + LineThreshold(Options.get("LineThreshold", -1U)), + StatementThreshold(Options.get("StatementThreshold", 800U)), + BranchThreshold(Options.get("BranchThreshold", -1U)) {} + +void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "LineThreshold", LineThreshold); + Options.store(Opts, "StatementThreshold", StatementThreshold); + Options.store(Opts, "BranchThreshold", BranchThreshold); +} + +void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + functionDecl( + unless(isInstantiated()), + forEachDescendant( + stmt(unless(compoundStmt()), + hasParent(stmt(anyOf(compoundStmt(), ifStmt(), + anyOf(whileStmt(), doStmt(), + cxxForRangeStmt(), forStmt()))))) + .bind("stmt"))).bind("func"), + this); +} + +void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Func = Result.Nodes.getNodeAs("func"); + + FunctionInfo &FI = FunctionInfos[Func]; + + // Count the lines including whitespace and comments. Really simple. + if (!FI.Lines) { + if (const Stmt *Body = Func->getBody()) { + SourceManager *SM = Result.SourceManager; + if (SM->isWrittenInSameFile(Body->getLocStart(), Body->getLocEnd())) { + FI.Lines = SM->getSpellingLineNumber(Body->getLocEnd()) - + SM->getSpellingLineNumber(Body->getLocStart()); + } + } + } + + const auto *Statement = Result.Nodes.getNodeAs("stmt"); + ++FI.Statements; + + // TODO: switch cases, gotos + if (isa(Statement) || isa(Statement) || + isa(Statement) || isa(Statement) || + isa(Statement) || isa(Statement)) + ++FI.Branches; +} + +void FunctionSizeCheck::onEndOfTranslationUnit() { + // If we're above the limit emit a warning. + for (const auto &P : FunctionInfos) { + const FunctionInfo &FI = P.second; + if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold || + FI.Branches > BranchThreshold) { + diag(P.first->getLocation(), + "function '%0' exceeds recommended size/complexity thresholds") + << P.first->getNameAsString(); + } + + if (FI.Lines > LineThreshold) { + diag(P.first->getLocation(), + "%0 lines including whitespace and comments (threshold %1)", + DiagnosticIDs::Note) + << FI.Lines << LineThreshold; + } + + if (FI.Statements > StatementThreshold) { + diag(P.first->getLocation(), "%0 statements (threshold %1)", + DiagnosticIDs::Note) + << FI.Statements << StatementThreshold; + } + + if (FI.Branches > BranchThreshold) { + diag(P.first->getLocation(), "%0 branches (threshold %1)", + DiagnosticIDs::Note) + << FI.Branches << BranchThreshold; + } + } + + FunctionInfos.clear(); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/FunctionSizeCheck.h b/clang-tidy/readability/FunctionSizeCheck.h new file mode 100644 index 00000000..1231cb13 --- /dev/null +++ b/clang-tidy/readability/FunctionSizeCheck.h @@ -0,0 +1,58 @@ +//===--- FunctionSizeCheck.h - clang-tidy -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_FUNCTIONSIZECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_FUNCTIONSIZECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Checks for large functions based on various metrics. +/// +/// These options are supported: +/// +/// * `LineThreshold` - flag functions exceeding this number of lines. The +/// default is `-1` (ignore the number of lines). +/// * `StatementThreshold` - flag functions exceeding this number of +/// statements. This may differ significantly from the number of lines for +/// macro-heavy code. The default is `800`. +/// * `BranchThreshold` - flag functions exceeding this number of control +/// statements. The default is `-1` (ignore the number of branches). +class FunctionSizeCheck : public ClangTidyCheck { +public: + FunctionSizeCheck(StringRef Name, ClangTidyContext *Context); + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; + +private: + struct FunctionInfo { + FunctionInfo() : Lines(0), Statements(0), Branches(0) {} + unsigned Lines; + unsigned Statements; + unsigned Branches; + }; + + const unsigned LineThreshold; + const unsigned StatementThreshold; + const unsigned BranchThreshold; + + llvm::DenseMap FunctionInfos; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_FUNCTIONSIZECHECK_H diff --git a/clang-tidy/readability/IdentifierNamingCheck.cpp b/clang-tidy/readability/IdentifierNamingCheck.cpp new file mode 100644 index 00000000..2e4e3fe1 --- /dev/null +++ b/clang-tidy/readability/IdentifierNamingCheck.cpp @@ -0,0 +1,688 @@ +//===--- IdentifierNamingCheck.cpp - clang-tidy ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "IdentifierNamingCheck.h" + +#include "llvm/Support/Debug.h" +#include "llvm/Support/Format.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +#define DEBUG_TYPE "clang-tidy" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +// clang-format off +#define NAMING_KEYS(m) \ + m(Namespace) \ + m(InlineNamespace) \ + m(EnumConstant) \ + m(ConstexprVariable) \ + m(ConstantMember) \ + m(PrivateMember) \ + m(ProtectedMember) \ + m(PublicMember) \ + m(Member) \ + m(ClassConstant) \ + m(ClassMember) \ + m(GlobalConstant) \ + m(GlobalVariable) \ + m(LocalConstant) \ + m(LocalVariable) \ + m(StaticConstant) \ + m(StaticVariable) \ + m(Constant) \ + m(Variable) \ + m(ConstantParameter) \ + m(ParameterPack) \ + m(Parameter) \ + m(AbstractClass) \ + m(Struct) \ + m(Class) \ + m(Union) \ + m(Enum) \ + m(GlobalFunction) \ + m(ConstexprFunction) \ + m(Function) \ + m(ConstexprMethod) \ + m(VirtualMethod) \ + m(ClassMethod) \ + m(PrivateMethod) \ + m(ProtectedMethod) \ + m(PublicMethod) \ + m(Method) \ + m(Typedef) \ + m(TypeTemplateParameter) \ + m(ValueTemplateParameter) \ + m(TemplateTemplateParameter) \ + m(TemplateParameter) \ + +enum StyleKind { +#define ENUMERATE(v) SK_ ## v, + NAMING_KEYS(ENUMERATE) +#undef ENUMERATE + SK_Count, + SK_Invalid +}; + +static StringRef const StyleNames[] = { +#define STRINGIZE(v) #v, + NAMING_KEYS(STRINGIZE) +#undef STRINGIZE +}; + +#undef NAMING_KEYS +// clang-format on + +IdentifierNamingCheck::IdentifierNamingCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) { + auto const fromString = [](StringRef Str) { + return llvm::StringSwitch(Str) + .Case("lower_case", CT_LowerCase) + .Case("UPPER_CASE", CT_UpperCase) + .Case("camelBack", CT_CamelBack) + .Case("CamelCase", CT_CamelCase) + .Default(CT_AnyCase); + }; + + for (auto const &Name : StyleNames) { + NamingStyles.push_back( + NamingStyle(fromString(Options.get((Name + "Case").str(), "")), + Options.get((Name + "Prefix").str(), ""), + Options.get((Name + "Suffix").str(), ""))); + } + + IgnoreFailedSplit = Options.get("IgnoreFailedSplit", 0); +} + +void IdentifierNamingCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + auto const toString = [](CaseType Type) { + switch (Type) { + case CT_AnyCase: + return "aNy_CasE"; + case CT_LowerCase: + return "lower_case"; + case CT_CamelBack: + return "camelBack"; + case CT_UpperCase: + return "UPPER_CASE"; + case CT_CamelCase: + return "CamelCase"; + } + + llvm_unreachable("Unknown Case Type"); + }; + + for (size_t i = 0; i < SK_Count; ++i) { + Options.store(Opts, (StyleNames[i] + "Case").str(), + toString(NamingStyles[i].Case)); + Options.store(Opts, (StyleNames[i] + "Prefix").str(), + NamingStyles[i].Prefix); + Options.store(Opts, (StyleNames[i] + "Suffix").str(), + NamingStyles[i].Suffix); + } + + Options.store(Opts, "IgnoreFailedSplit", IgnoreFailedSplit); +} + +void IdentifierNamingCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(namedDecl().bind("decl"), this); + Finder->addMatcher(usingDecl().bind("using"), this); + Finder->addMatcher(declRefExpr().bind("declRef"), this); + Finder->addMatcher(cxxConstructorDecl().bind("classRef"), this); + Finder->addMatcher(cxxDestructorDecl().bind("classRef"), this); + Finder->addMatcher(typeLoc().bind("typeLoc"), this); + Finder->addMatcher(nestedNameSpecifierLoc().bind("nestedNameLoc"), this); +} + +static bool matchesStyle(StringRef Name, + IdentifierNamingCheck::NamingStyle Style) { + static llvm::Regex Matchers[] = { + llvm::Regex("^.*$"), + llvm::Regex("^[a-z][a-z0-9_]*$"), + llvm::Regex("^[a-z][a-zA-Z0-9]*$"), + llvm::Regex("^[A-Z][A-Z0-9_]*$"), + llvm::Regex("^[A-Z][a-zA-Z0-9]*$"), + }; + + bool Matches = true; + if (Name.startswith(Style.Prefix)) + Name = Name.drop_front(Style.Prefix.size()); + else + Matches = false; + + if (Name.endswith(Style.Suffix)) + Name = Name.drop_back(Style.Suffix.size()); + else + Matches = false; + + if (!Matchers[static_cast(Style.Case)].match(Name)) + Matches = false; + + return Matches; +} + +static std::string fixupWithCase(StringRef Name, + IdentifierNamingCheck::CaseType Case) { + static llvm::Regex Splitter( + "([a-z0-9A-Z]*)(_+)|([A-Z]?[a-z0-9]+)([A-Z]|$)|([A-Z]+)([A-Z]|$)"); + + SmallVector Substrs; + Name.split(Substrs, "_", -1, false); + + SmallVector Words; + for (auto Substr : Substrs) { + while (!Substr.empty()) { + SmallVector Groups; + if (!Splitter.match(Substr, &Groups)) + break; + + if (Groups[2].size() > 0) { + Words.push_back(Groups[1]); + Substr = Substr.substr(Groups[0].size()); + } else if (Groups[3].size() > 0) { + Words.push_back(Groups[3]); + Substr = Substr.substr(Groups[0].size() - Groups[4].size()); + } else if (Groups[5].size() > 0) { + Words.push_back(Groups[5]); + Substr = Substr.substr(Groups[0].size() - Groups[6].size()); + } + } + } + + if (Words.empty()) + return Name; + + std::string Fixup; + switch (Case) { + case IdentifierNamingCheck::CT_AnyCase: + Fixup += Name; + break; + + case IdentifierNamingCheck::CT_LowerCase: + for (auto const &Word : Words) { + if (&Word != &Words.front()) + Fixup += "_"; + Fixup += Word.lower(); + } + break; + + case IdentifierNamingCheck::CT_UpperCase: + for (auto const &Word : Words) { + if (&Word != &Words.front()) + Fixup += "_"; + Fixup += Word.upper(); + } + break; + + case IdentifierNamingCheck::CT_CamelCase: + for (auto const &Word : Words) { + Fixup += Word.substr(0, 1).upper(); + Fixup += Word.substr(1).lower(); + } + break; + + case IdentifierNamingCheck::CT_CamelBack: + for (auto const &Word : Words) { + if (&Word == &Words.front()) { + Fixup += Word.lower(); + } else { + Fixup += Word.substr(0, 1).upper(); + Fixup += Word.substr(1).lower(); + } + } + break; + } + + return Fixup; +} + +static std::string fixupWithStyle(StringRef Name, + IdentifierNamingCheck::NamingStyle Style) { + return Style.Prefix + fixupWithCase(Name, Style.Case) + Style.Suffix; +} + +static StyleKind findStyleKind( + const NamedDecl *D, + const std::vector &NamingStyles) { + if (isa(D) && NamingStyles[SK_Typedef].isSet()) + return SK_Typedef; + + if (const auto *Decl = dyn_cast(D)) { + if (Decl->isAnonymousNamespace()) + return SK_Invalid; + + if (Decl->isInline() && NamingStyles[SK_InlineNamespace].isSet()) + return SK_InlineNamespace; + + if (NamingStyles[SK_Namespace].isSet()) + return SK_Namespace; + } + + if (isa(D) && NamingStyles[SK_Enum].isSet()) + return SK_Enum; + + if (isa(D)) { + if (NamingStyles[SK_EnumConstant].isSet()) + return SK_EnumConstant; + + if (NamingStyles[SK_Constant].isSet()) + return SK_Constant; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast(D)) { + if (Decl->isAnonymousStructOrUnion()) + return SK_Invalid; + + if (Decl->hasDefinition() && Decl->isAbstract() && + NamingStyles[SK_AbstractClass].isSet()) + return SK_AbstractClass; + + if (Decl->isStruct() && NamingStyles[SK_Struct].isSet()) + return SK_Struct; + + if (Decl->isStruct() && NamingStyles[SK_Class].isSet()) + return SK_Class; + + if (Decl->isClass() && NamingStyles[SK_Class].isSet()) + return SK_Class; + + if (Decl->isClass() && NamingStyles[SK_Struct].isSet()) + return SK_Struct; + + if (Decl->isUnion() && NamingStyles[SK_Union].isSet()) + return SK_Union; + + if (Decl->isEnum() && NamingStyles[SK_Enum].isSet()) + return SK_Enum; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast(D)) { + QualType Type = Decl->getType(); + + if (!Type.isNull() && Type.isLocalConstQualified() && + NamingStyles[SK_ConstantMember].isSet()) + return SK_ConstantMember; + + if (!Type.isNull() && Type.isLocalConstQualified() && + NamingStyles[SK_Constant].isSet()) + return SK_Constant; + + if (Decl->getAccess() == AS_private && + NamingStyles[SK_PrivateMember].isSet()) + return SK_PrivateMember; + + if (Decl->getAccess() == AS_protected && + NamingStyles[SK_ProtectedMember].isSet()) + return SK_ProtectedMember; + + if (Decl->getAccess() == AS_public && NamingStyles[SK_PublicMember].isSet()) + return SK_PublicMember; + + if (NamingStyles[SK_Member].isSet()) + return SK_Member; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast(D)) { + QualType Type = Decl->getType(); + + if (Decl->isConstexpr() && NamingStyles[SK_ConstexprVariable].isSet()) + return SK_ConstexprVariable; + + if (!Type.isNull() && Type.isLocalConstQualified() && + NamingStyles[SK_ConstantParameter].isSet()) + return SK_ConstantParameter; + + if (!Type.isNull() && Type.isLocalConstQualified() && + NamingStyles[SK_Constant].isSet()) + return SK_Constant; + + if (Decl->isParameterPack() && NamingStyles[SK_ParameterPack].isSet()) + return SK_ParameterPack; + + if (NamingStyles[SK_Parameter].isSet()) + return SK_Parameter; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast(D)) { + QualType Type = Decl->getType(); + + if (Decl->isConstexpr() && NamingStyles[SK_ConstexprVariable].isSet()) + return SK_ConstexprVariable; + + if (!Type.isNull() && Type.isLocalConstQualified() && + Decl->isStaticDataMember() && NamingStyles[SK_ClassConstant].isSet()) + return SK_ClassConstant; + + if (!Type.isNull() && Type.isLocalConstQualified() && + Decl->isFileVarDecl() && NamingStyles[SK_GlobalConstant].isSet()) + return SK_GlobalConstant; + + if (!Type.isNull() && Type.isLocalConstQualified() && + Decl->isStaticLocal() && NamingStyles[SK_StaticConstant].isSet()) + return SK_StaticConstant; + + if (!Type.isNull() && Type.isLocalConstQualified() && + Decl->isLocalVarDecl() && NamingStyles[SK_LocalConstant].isSet()) + return SK_LocalConstant; + + if (!Type.isNull() && Type.isLocalConstQualified() && + Decl->isFunctionOrMethodVarDecl() && + NamingStyles[SK_LocalConstant].isSet()) + return SK_LocalConstant; + + if (!Type.isNull() && Type.isLocalConstQualified() && + NamingStyles[SK_Constant].isSet()) + return SK_Constant; + + if (Decl->isStaticDataMember() && NamingStyles[SK_ClassMember].isSet()) + return SK_ClassMember; + + if (Decl->isFileVarDecl() && NamingStyles[SK_GlobalVariable].isSet()) + return SK_GlobalVariable; + + if (Decl->isStaticLocal() && NamingStyles[SK_StaticVariable].isSet()) + return SK_StaticVariable; + + if (Decl->isLocalVarDecl() && NamingStyles[SK_LocalVariable].isSet()) + return SK_LocalVariable; + + if (Decl->isFunctionOrMethodVarDecl() && + NamingStyles[SK_LocalVariable].isSet()) + return SK_LocalVariable; + + if (NamingStyles[SK_Variable].isSet()) + return SK_Variable; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast(D)) { + if (Decl->isMain() || !Decl->isUserProvided() || + Decl->isUsualDeallocationFunction() || + Decl->isCopyAssignmentOperator() || Decl->isMoveAssignmentOperator() || + Decl->size_overridden_methods() > 0) + return SK_Invalid; + + if (Decl->isConstexpr() && NamingStyles[SK_ConstexprMethod].isSet()) + return SK_ConstexprMethod; + + if (Decl->isConstexpr() && NamingStyles[SK_ConstexprFunction].isSet()) + return SK_ConstexprFunction; + + if (Decl->isStatic() && NamingStyles[SK_ClassMethod].isSet()) + return SK_ClassMethod; + + if (Decl->isVirtual() && NamingStyles[SK_VirtualMethod].isSet()) + return SK_VirtualMethod; + + if (Decl->getAccess() == AS_private && + NamingStyles[SK_PrivateMethod].isSet()) + return SK_PrivateMethod; + + if (Decl->getAccess() == AS_protected && + NamingStyles[SK_ProtectedMethod].isSet()) + return SK_ProtectedMethod; + + if (Decl->getAccess() == AS_public && NamingStyles[SK_PublicMethod].isSet()) + return SK_PublicMethod; + + if (NamingStyles[SK_Method].isSet()) + return SK_Method; + + if (NamingStyles[SK_Function].isSet()) + return SK_Function; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast(D)) { + if (Decl->isMain()) + return SK_Invalid; + + if (Decl->isConstexpr() && NamingStyles[SK_ConstexprFunction].isSet()) + return SK_ConstexprFunction; + + if (Decl->isGlobal() && NamingStyles[SK_GlobalFunction].isSet()) + return SK_GlobalFunction; + + if (NamingStyles[SK_Function].isSet()) + return SK_Function; + } + + if (isa(D)) { + if (NamingStyles[SK_TypeTemplateParameter].isSet()) + return SK_TypeTemplateParameter; + + if (NamingStyles[SK_TemplateParameter].isSet()) + return SK_TemplateParameter; + + return SK_Invalid; + } + + if (isa(D)) { + if (NamingStyles[SK_ValueTemplateParameter].isSet()) + return SK_ValueTemplateParameter; + + if (NamingStyles[SK_TemplateParameter].isSet()) + return SK_TemplateParameter; + + return SK_Invalid; + } + + if (isa(D)) { + if (NamingStyles[SK_TemplateTemplateParameter].isSet()) + return SK_TemplateTemplateParameter; + + if (NamingStyles[SK_TemplateParameter].isSet()) + return SK_TemplateParameter; + + return SK_Invalid; + } + + return SK_Invalid; +} + +static void addUsage(IdentifierNamingCheck::NamingCheckFailureMap &Failures, + const NamedDecl *Decl, SourceRange Range, + const SourceManager *SM) { + // Do nothin if the provided range is invalid + if (Range.getBegin().isInvalid() || Range.getEnd().isInvalid()) + return; + + // Try to insert the identifier location in the Usages map, and bail out if it + // is already in there + auto &Failure = Failures[Decl]; + if (!Failure.RawUsageLocs.insert(Range.getBegin().getRawEncoding()).second) + return; + + Failure.ShouldFix = Failure.ShouldFix && !Range.getBegin().isMacroID() && + !Range.getEnd().isMacroID(); +} + +void IdentifierNamingCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Decl = + Result.Nodes.getNodeAs("classRef")) { + if (Decl->isImplicit()) + return; + + addUsage(NamingCheckFailures, Decl->getParent(), + Decl->getNameInfo().getSourceRange(), Result.SourceManager); + return; + } + + if (const auto *Decl = + Result.Nodes.getNodeAs("classRef")) { + if (Decl->isImplicit()) + return; + + SourceRange Range = Decl->getNameInfo().getSourceRange(); + if (Range.getBegin().isInvalid()) + return; + // The first token that will be found is the ~ (or the equivalent trigraph), + // we want instead to replace the next token, that will be the identifier. + Range.setBegin(CharSourceRange::getTokenRange(Range).getEnd()); + + addUsage(NamingCheckFailures, Decl->getParent(), Range, + Result.SourceManager); + return; + } + + if (const auto *Loc = Result.Nodes.getNodeAs("typeLoc")) { + NamedDecl *Decl = nullptr; + if (const auto &Ref = Loc->getAs()) { + Decl = Ref.getDecl(); + } else if (const auto &Ref = Loc->getAs()) { + Decl = Ref.getDecl(); + } else if (const auto &Ref = Loc->getAs()) { + Decl = Ref.getDecl(); + } else if (const auto &Ref = Loc->getAs()) { + Decl = Ref.getDecl(); + } + + if (Decl) { + addUsage(NamingCheckFailures, Decl, Loc->getSourceRange(), + Result.SourceManager); + return; + } + + if (const auto &Ref = Loc->getAs()) { + const auto *Decl = + Ref.getTypePtr()->getTemplateName().getAsTemplateDecl(); + + SourceRange Range(Ref.getTemplateNameLoc(), Ref.getTemplateNameLoc()); + if (const auto *ClassDecl = dyn_cast(Decl)) { + addUsage(NamingCheckFailures, ClassDecl->getTemplatedDecl(), Range, + Result.SourceManager); + return; + } + } + + if (const auto &Ref = + Loc->getAs()) { + addUsage(NamingCheckFailures, Ref.getTypePtr()->getAsTagDecl(), + Loc->getSourceRange(), Result.SourceManager); + return; + } + } + + if (const auto *Loc = + Result.Nodes.getNodeAs("nestedNameLoc")) { + if (NestedNameSpecifier *Spec = Loc->getNestedNameSpecifier()) { + if (NamespaceDecl *Decl = Spec->getAsNamespace()) { + addUsage(NamingCheckFailures, Decl, Loc->getLocalSourceRange(), + Result.SourceManager); + return; + } + } + } + + if (const auto *Decl = Result.Nodes.getNodeAs("using")) { + for (const auto &Shadow : Decl->shadows()) { + addUsage(NamingCheckFailures, Shadow->getTargetDecl(), + Decl->getNameInfo().getSourceRange(), Result.SourceManager); + } + return; + } + + if (const auto *DeclRef = Result.Nodes.getNodeAs("declRef")) { + SourceRange Range = DeclRef->getNameInfo().getSourceRange(); + addUsage(NamingCheckFailures, DeclRef->getDecl(), Range, + Result.SourceManager); + return; + } + + if (const auto *Decl = Result.Nodes.getNodeAs("decl")) { + if (!Decl->getIdentifier() || Decl->getName().empty() || Decl->isImplicit()) + return; + + // Ignore ClassTemplateSpecializationDecl which are creating duplicate + // replacements with CXXRecordDecl + if (isa(Decl)) + return; + + StyleKind SK = findStyleKind(Decl, NamingStyles); + if (SK == SK_Invalid) + return; + + NamingStyle Style = NamingStyles[SK]; + StringRef Name = Decl->getName(); + if (matchesStyle(Name, Style)) + return; + + std::string KindName = fixupWithCase(StyleNames[SK], CT_LowerCase); + std::replace(KindName.begin(), KindName.end(), '_', ' '); + + std::string Fixup = fixupWithStyle(Name, Style); + if (StringRef(Fixup).equals(Name)) { + if (!IgnoreFailedSplit) { + DEBUG(llvm::dbgs() << Decl->getLocStart().printToString( + *Result.SourceManager) + << format(": unable to split words for %s '%s'\n", + KindName.c_str(), Name)); + } + } else { + NamingCheckFailure &Failure = NamingCheckFailures[Decl]; + SourceRange Range = + DeclarationNameInfo(Decl->getDeclName(), Decl->getLocation()) + .getSourceRange(); + + Failure.Fixup = std::move(Fixup); + Failure.KindName = std::move(KindName); + addUsage(NamingCheckFailures, Decl, Range, Result.SourceManager); + } + } +} + +void IdentifierNamingCheck::onEndOfTranslationUnit() { + for (const auto &Pair : NamingCheckFailures) { + const NamedDecl &Decl = *Pair.first; + const NamingCheckFailure &Failure = Pair.second; + + if (Failure.KindName.empty()) + continue; + + if (Failure.ShouldFix) { + auto Diag = diag(Decl.getLocation(), "invalid case style for %0 '%1'") + << Failure.KindName << Decl.getName(); + + for (const auto &Loc : Failure.RawUsageLocs) { + // We assume that the identifier name is made of one token only. This is + // always the case as we ignore usages in macros that could build + // identifier names by combining multiple tokens. + // + // For destructors, we alread take care of it by remembering the + // location of the start of the identifier and not the start of the + // tilde. + // + // Other multi-token identifiers, such as operators are not checked at + // all. + Diag << FixItHint::CreateReplacement( + SourceRange(SourceLocation::getFromRawEncoding(Loc)), + Failure.Fixup); + } + } + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/IdentifierNamingCheck.h b/clang-tidy/readability/IdentifierNamingCheck.h new file mode 100644 index 00000000..550b3341 --- /dev/null +++ b/clang-tidy/readability/IdentifierNamingCheck.h @@ -0,0 +1,97 @@ +//===--- IdentifierNamingCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERNAMINGCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERNAMINGCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Checks for identifiers naming style mismatch. +/// +/// This check will try to enforce coding guidelines on the identifiers naming. +/// It supports `lower_case`, `UPPER_CASE`, `camelBack` and `CamelCase` casing +/// and tries to convert from one to another if a mismatch is detected. +/// +/// It also supports a fixed prefix and suffix that will be prepended or +/// appended to the identifiers, regardless of the casing. +/// +/// Many configuration options are available, in order to be able to create +/// different rules for different kind of identifier. In general, the +/// rules are falling back to a more generic rule if the specific case is not +/// configured. +class IdentifierNamingCheck : public ClangTidyCheck { +public: + IdentifierNamingCheck(StringRef Name, ClangTidyContext *Context); + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; + + enum CaseType { + CT_AnyCase = 0, + CT_LowerCase, + CT_CamelBack, + CT_UpperCase, + CT_CamelCase, + }; + + struct NamingStyle { + NamingStyle() : Case(CT_AnyCase) {} + + NamingStyle(CaseType Case, const std::string &Prefix, + const std::string &Suffix) + : Case(Case), Prefix(Prefix), Suffix(Suffix) {} + + CaseType Case; + std::string Prefix; + std::string Suffix; + + bool isSet() const { + return !(Case == CT_AnyCase && Prefix.empty() && Suffix.empty()); + } + }; + + /// \brief Holds an identifier name check failure, tracking the kind of the + /// identifer, its possible fixup and the starting locations of all the + /// idenfiier usages. + struct NamingCheckFailure { + std::string KindName; + std::string Fixup; + + /// \brief Whether the failure should be fixed or not. + /// + /// ie: if the identifier was used or declared within a macro we won't offer + /// a fixup for safety reasons. + bool ShouldFix; + + /// \brief A set of all the identifier usages starting SourceLocation, in + /// their encoded form. + llvm::DenseSet RawUsageLocs; + + NamingCheckFailure() : ShouldFix(true) {} + }; + typedef llvm::DenseMap + NamingCheckFailureMap; + +private: + std::vector NamingStyles; + bool IgnoreFailedSplit; + NamingCheckFailureMap NamingCheckFailures; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERNAMINGCHECK_H diff --git a/clang-tidy/readability/ImplicitBoolCastCheck.cpp b/clang-tidy/readability/ImplicitBoolCastCheck.cpp new file mode 100644 index 00000000..0584f58e --- /dev/null +++ b/clang-tidy/readability/ImplicitBoolCastCheck.cpp @@ -0,0 +1,425 @@ +//===--- ImplicitBoolCastCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ImplicitBoolCastCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +namespace { + +const internal::VariadicDynCastAllOfMatcher parenExpr; + +AST_MATCHER_P(CastExpr, hasCastKind, CastKind, Kind) { + return Node.getCastKind() == Kind; +} + +AST_MATCHER(QualType, isBool) { + return !Node.isNull() && Node->isBooleanType(); +} + +AST_MATCHER(Stmt, isMacroExpansion) { + SourceManager &SM = Finder->getASTContext().getSourceManager(); + SourceLocation Loc = Node.getLocStart(); + return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc); +} + +bool isNULLMacroExpansion(const Stmt *Statement, ASTContext &Context) { + SourceManager &SM = Context.getSourceManager(); + const LangOptions &LO = Context.getLangOpts(); + SourceLocation Loc = Statement->getLocStart(); + return SM.isMacroBodyExpansion(Loc) && + clang::Lexer::getImmediateMacroName(Loc, SM, LO) == "NULL"; +} + +AST_MATCHER(Stmt, isNULLMacroExpansion) { + return isNULLMacroExpansion(&Node, Finder->getASTContext()); +} + +ast_matchers::internal::Matcher createExceptionCasesMatcher() { + return expr(anyOf(hasParent(explicitCastExpr()), + allOf(isMacroExpansion(), unless(isNULLMacroExpansion())), + isInTemplateInstantiation(), + hasAncestor(functionTemplateDecl()))); +} + +StatementMatcher createImplicitCastFromBoolMatcher() { + return implicitCastExpr( + unless(createExceptionCasesMatcher()), + anyOf(hasCastKind(CK_IntegralCast), hasCastKind(CK_IntegralToFloating), + // Prior to C++11 cast from bool literal to pointer was allowed. + allOf(anyOf(hasCastKind(CK_NullToPointer), + hasCastKind(CK_NullToMemberPointer)), + hasSourceExpression(cxxBoolLiteral()))), + hasSourceExpression(expr(hasType(qualType(isBool()))))); +} + +StringRef +getZeroLiteralToCompareWithForGivenType(CastKind CastExpressionKind, + QualType CastSubExpressionType, + ASTContext &Context) { + switch (CastExpressionKind) { + case CK_IntegralToBoolean: + return CastSubExpressionType->isUnsignedIntegerType() ? "0u" : "0"; + + case CK_FloatingToBoolean: + return Context.hasSameType(CastSubExpressionType, Context.FloatTy) ? "0.0f" + : "0.0"; + + case CK_PointerToBoolean: + case CK_MemberPointerToBoolean: // Fall-through on purpose. + return Context.getLangOpts().CPlusPlus11 ? "nullptr" : "0"; + + default: + llvm_unreachable("Unexpected cast kind"); + } +} + +bool isUnaryLogicalNotOperator(const Stmt *Statement) { + const auto *UnaryOperatorExpression = + llvm::dyn_cast(Statement); + return UnaryOperatorExpression != nullptr && + UnaryOperatorExpression->getOpcode() == UO_LNot; +} + +bool areParensNeededForOverloadedOperator(OverloadedOperatorKind OperatorKind) { + switch (OperatorKind) { + case OO_New: + case OO_Delete: // Fall-through on purpose. + case OO_Array_New: + case OO_Array_Delete: + case OO_ArrowStar: + case OO_Arrow: + case OO_Call: + case OO_Subscript: + return false; + + default: + return true; + } +} + +bool areParensNeededForStatement(const Stmt *Statement) { + if (const CXXOperatorCallExpr *OverloadedOperatorCall = + llvm::dyn_cast(Statement)) { + return areParensNeededForOverloadedOperator( + OverloadedOperatorCall->getOperator()); + } + + return llvm::isa(Statement) || + llvm::isa(Statement); +} + +void addFixItHintsForGenericExpressionCastToBool( + DiagnosticBuilder &Diagnostic, const ImplicitCastExpr *CastExpression, + const Stmt *ParentStatement, ASTContext &Context) { + // In case of expressions like (! integer), we should remove the redundant not + // operator and use inverted comparison (integer == 0). + bool InvertComparison = + ParentStatement != nullptr && isUnaryLogicalNotOperator(ParentStatement); + if (InvertComparison) { + SourceLocation ParentStartLoc = ParentStatement->getLocStart(); + SourceLocation ParentEndLoc = + llvm::cast(ParentStatement)->getSubExpr()->getLocStart(); + Diagnostic.AddFixItHint(FixItHint::CreateRemoval( + CharSourceRange::getCharRange(ParentStartLoc, ParentEndLoc))); + + auto FurtherParents = Context.getParents(*ParentStatement); + ParentStatement = FurtherParents[0].get(); + } + + const Expr *SubExpression = CastExpression->getSubExpr(); + + bool NeedInnerParens = areParensNeededForStatement(SubExpression); + bool NeedOuterParens = ParentStatement != nullptr && + areParensNeededForStatement(ParentStatement); + + std::string StartLocInsertion; + + if (NeedOuterParens) { + StartLocInsertion += "("; + } + if (NeedInnerParens) { + StartLocInsertion += "("; + } + + if (!StartLocInsertion.empty()) { + SourceLocation StartLoc = CastExpression->getLocStart(); + Diagnostic.AddFixItHint( + FixItHint::CreateInsertion(StartLoc, StartLocInsertion)); + } + + std::string EndLocInsertion; + + if (NeedInnerParens) { + EndLocInsertion += ")"; + } + + if (InvertComparison) { + EndLocInsertion += " == "; + } else { + EndLocInsertion += " != "; + } + + EndLocInsertion += getZeroLiteralToCompareWithForGivenType( + CastExpression->getCastKind(), SubExpression->getType(), Context); + + if (NeedOuterParens) { + EndLocInsertion += ")"; + } + + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + CastExpression->getLocEnd(), 0, Context.getSourceManager(), + Context.getLangOpts()); + Diagnostic.AddFixItHint(FixItHint::CreateInsertion(EndLoc, EndLocInsertion)); +} + +StringRef getEquivalentBoolLiteralForExpression(const Expr *Expression, + ASTContext &Context) { + if (isNULLMacroExpansion(Expression, Context)) { + return "false"; + } + + if (const auto *IntLit = llvm::dyn_cast(Expression)) { + return (IntLit->getValue() == 0) ? "false" : "true"; + } + + if (const auto *FloatLit = llvm::dyn_cast(Expression)) { + llvm::APFloat FloatLitAbsValue = FloatLit->getValue(); + FloatLitAbsValue.clearSign(); + return (FloatLitAbsValue.bitcastToAPInt() == 0) ? "false" : "true"; + } + + if (const auto *CharLit = llvm::dyn_cast(Expression)) { + return (CharLit->getValue() == 0) ? "false" : "true"; + } + + if (llvm::isa(Expression->IgnoreCasts())) { + return "true"; + } + + return StringRef(); +} + +void addFixItHintsForLiteralCastToBool(DiagnosticBuilder &Diagnostic, + const ImplicitCastExpr *CastExpression, + StringRef EquivalentLiteralExpression) { + SourceLocation StartLoc = CastExpression->getLocStart(); + SourceLocation EndLoc = CastExpression->getLocEnd(); + + Diagnostic.AddFixItHint(FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(StartLoc, EndLoc), + EquivalentLiteralExpression)); +} + +void addFixItHintsForGenericExpressionCastFromBool( + DiagnosticBuilder &Diagnostic, const ImplicitCastExpr *CastExpression, + ASTContext &Context, StringRef OtherType) { + const Expr *SubExpression = CastExpression->getSubExpr(); + bool NeedParens = !llvm::isa(SubExpression); + + std::string StartLocInsertion = "static_cast<"; + StartLocInsertion += OtherType.str(); + StartLocInsertion += ">"; + if (NeedParens) { + StartLocInsertion += "("; + } + + SourceLocation StartLoc = CastExpression->getLocStart(); + Diagnostic.AddFixItHint( + FixItHint::CreateInsertion(StartLoc, StartLocInsertion)); + + if (NeedParens) { + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + CastExpression->getLocEnd(), 0, Context.getSourceManager(), + Context.getLangOpts()); + + Diagnostic.AddFixItHint(FixItHint::CreateInsertion(EndLoc, ")")); + } +} + +StringRef getEquivalentLiteralForBoolLiteral( + const CXXBoolLiteralExpr *BoolLiteralExpression, QualType DestinationType, + ASTContext &Context) { + // Prior to C++11, false literal could be implicitly converted to pointer. + if (!Context.getLangOpts().CPlusPlus11 && + (DestinationType->isPointerType() || + DestinationType->isMemberPointerType()) && + BoolLiteralExpression->getValue() == false) { + return "0"; + } + + if (DestinationType->isFloatingType()) { + if (BoolLiteralExpression->getValue() == true) { + return Context.hasSameType(DestinationType, Context.FloatTy) ? "1.0f" + : "1.0"; + } + return Context.hasSameType(DestinationType, Context.FloatTy) ? "0.0f" + : "0.0"; + } + + if (BoolLiteralExpression->getValue() == true) { + return DestinationType->isUnsignedIntegerType() ? "1u" : "1"; + } + return DestinationType->isUnsignedIntegerType() ? "0u" : "0"; +} + +void addFixItHintsForLiteralCastFromBool(DiagnosticBuilder &Diagnostic, + const ImplicitCastExpr *CastExpression, + ASTContext &Context, + QualType DestinationType) { + SourceLocation StartLoc = CastExpression->getLocStart(); + SourceLocation EndLoc = CastExpression->getLocEnd(); + const auto *BoolLiteralExpression = + llvm::dyn_cast(CastExpression->getSubExpr()); + + Diagnostic.AddFixItHint(FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(StartLoc, EndLoc), + getEquivalentLiteralForBoolLiteral(BoolLiteralExpression, DestinationType, + Context))); +} + +StatementMatcher createConditionalExpressionMatcher() { + return stmt(anyOf(ifStmt(), conditionalOperator(), + parenExpr(hasParent(conditionalOperator())))); +} + +bool isAllowedConditionalCast(const ImplicitCastExpr *CastExpression, + ASTContext &Context) { + auto AllowedConditionalMatcher = stmt(hasParent(stmt( + anyOf(createConditionalExpressionMatcher(), + unaryOperator(hasOperatorName("!"), + hasParent(createConditionalExpressionMatcher())))))); + + auto MatchResult = match(AllowedConditionalMatcher, *CastExpression, Context); + return !MatchResult.empty(); +} + +} // anonymous namespace + +void ImplicitBoolCastCheck::registerMatchers(MatchFinder *Finder) { + // This check doesn't make much sense if we run it on language without + // built-in bool support. + if (!getLangOpts().Bool) { + return; + } + + Finder->addMatcher( + implicitCastExpr( + // Exclude cases common to implicit cast to and from bool. + unless(createExceptionCasesMatcher()), + // Exclude case of using if or while statements with variable + // declaration, e.g.: + // if (int var = functionCall()) {} + unless( + hasParent(stmt(anyOf(ifStmt(), whileStmt()), has(declStmt())))), + anyOf(hasCastKind(CK_IntegralToBoolean), + hasCastKind(CK_FloatingToBoolean), + hasCastKind(CK_PointerToBoolean), + hasCastKind(CK_MemberPointerToBoolean)), + // Retrive also parent statement, to check if we need additional + // parens in replacement. + anyOf(hasParent(stmt().bind("parentStmt")), anything())) + .bind("implicitCastToBool"), + this); + + Finder->addMatcher( + implicitCastExpr( + createImplicitCastFromBoolMatcher(), + // Exclude comparisons of bools, as they are always cast to integers + // in such context: + // bool_expr_a == bool_expr_b + // bool_expr_a != bool_expr_b + unless(hasParent(binaryOperator( + anyOf(hasOperatorName("=="), hasOperatorName("!=")), + hasLHS(createImplicitCastFromBoolMatcher()), + hasRHS(createImplicitCastFromBoolMatcher())))), + // Check also for nested casts, for example: bool -> int -> float. + anyOf(hasParent(implicitCastExpr().bind("furtherImplicitCast")), + anything())) + .bind("implicitCastFromBool"), + this); +} + +void ImplicitBoolCastCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *CastToBool = + Result.Nodes.getNodeAs("implicitCastToBool")) { + const auto *ParentStatement = Result.Nodes.getNodeAs("parentStmt"); + return handleCastToBool(CastToBool, ParentStatement, *Result.Context); + } + + if (const auto *CastFromBool = + Result.Nodes.getNodeAs("implicitCastFromBool")) { + const auto *FurtherImplicitCastExpression = + Result.Nodes.getNodeAs("furtherImplicitCast"); + return handleCastFromBool(CastFromBool, FurtherImplicitCastExpression, + *Result.Context); + } +} + +void ImplicitBoolCastCheck::handleCastToBool( + const ImplicitCastExpr *CastExpression, const Stmt *ParentStatement, + ASTContext &Context) { + if (AllowConditionalPointerCasts && + (CastExpression->getCastKind() == CK_PointerToBoolean || + CastExpression->getCastKind() == CK_MemberPointerToBoolean) && + isAllowedConditionalCast(CastExpression, Context)) { + return; + } + + if (AllowConditionalIntegerCasts && + CastExpression->getCastKind() == CK_IntegralToBoolean && + isAllowedConditionalCast(CastExpression, Context)) { + return; + } + + std::string OtherType = CastExpression->getSubExpr()->getType().getAsString(); + DiagnosticBuilder Diagnostic = + diag(CastExpression->getLocStart(), "implicit cast '%0' -> bool") + << OtherType; + + StringRef EquivalentLiteralExpression = getEquivalentBoolLiteralForExpression( + CastExpression->getSubExpr(), Context); + if (!EquivalentLiteralExpression.empty()) { + addFixItHintsForLiteralCastToBool(Diagnostic, CastExpression, + EquivalentLiteralExpression); + } else { + addFixItHintsForGenericExpressionCastToBool(Diagnostic, CastExpression, + ParentStatement, Context); + } +} + +void ImplicitBoolCastCheck::handleCastFromBool( + const ImplicitCastExpr *CastExpression, + const ImplicitCastExpr *FurtherImplicitCastExpression, + ASTContext &Context) { + QualType DestinationType = (FurtherImplicitCastExpression != nullptr) + ? FurtherImplicitCastExpression->getType() + : CastExpression->getType(); + std::string DestinationTypeString = DestinationType.getAsString(); + DiagnosticBuilder Diagnostic = + diag(CastExpression->getLocStart(), "implicit cast bool -> '%0'") + << DestinationTypeString; + + if (llvm::isa(CastExpression->getSubExpr())) { + addFixItHintsForLiteralCastFromBool(Diagnostic, CastExpression, Context, + DestinationType); + } else { + addFixItHintsForGenericExpressionCastFromBool( + Diagnostic, CastExpression, Context, DestinationTypeString); + } +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/ImplicitBoolCastCheck.h b/clang-tidy/readability/ImplicitBoolCastCheck.h new file mode 100644 index 00000000..a33b96d5 --- /dev/null +++ b/clang-tidy/readability/ImplicitBoolCastCheck.h @@ -0,0 +1,47 @@ +//===--- ImplicitBoolCastCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IMPLICIT_BOOL_CAST_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IMPLICIT_BOOL_CAST_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// \brief Checks for use of implicit bool casts in expressions. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-implicit-bool-cast.html +class ImplicitBoolCastCheck : public ClangTidyCheck { +public: + ImplicitBoolCastCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + AllowConditionalIntegerCasts( + Options.get("AllowConditionalIntegerCasts", 0) != 0), + AllowConditionalPointerCasts( + Options.get("AllowConditionalPointerCasts", 0) != 0) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void handleCastToBool(const ImplicitCastExpr *CastExpression, + const Stmt *ParentStatement, ASTContext &Context); + void handleCastFromBool(const ImplicitCastExpr *CastExpression, + const ImplicitCastExpr *FurtherImplicitCastExpression, + ASTContext &Context); + + bool AllowConditionalIntegerCasts; + bool AllowConditionalPointerCasts; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IMPLICIT_BOOL_CAST_H diff --git a/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp new file mode 100644 index 00000000..7c24d5be --- /dev/null +++ b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp @@ -0,0 +1,336 @@ +//===--- InconsistentDeclarationParameterNameCheck.cpp - clang-tidy-------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "InconsistentDeclarationParameterNameCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +#include +#include +#include + +namespace clang { +namespace tidy { +namespace readability { + +using namespace ast_matchers; + +namespace { + +AST_MATCHER(FunctionDecl, hasOtherDeclarations) { + auto It = Node.redecls_begin(); + auto EndIt = Node.redecls_end(); + + if (It == EndIt) + return false; + + ++It; + return It != EndIt; +} + +struct DifferingParamInfo { + DifferingParamInfo(StringRef SourceName, StringRef OtherName, + SourceRange OtherNameRange, bool GenerateFixItHint) + : SourceName(SourceName), OtherName(OtherName), + OtherNameRange(OtherNameRange), GenerateFixItHint(GenerateFixItHint) {} + + StringRef SourceName; + StringRef OtherName; + SourceRange OtherNameRange; + bool GenerateFixItHint; +}; + +using DifferingParamsContainer = llvm::SmallVector; + +struct InconsistentDeclarationInfo { + InconsistentDeclarationInfo(SourceLocation DeclarationLocation, + DifferingParamsContainer &&DifferingParams) + : DeclarationLocation(DeclarationLocation), + DifferingParams(std::move(DifferingParams)) {} + + SourceLocation DeclarationLocation; + DifferingParamsContainer DifferingParams; +}; + +using InconsistentDeclarationsContainer = + llvm::SmallVector; + +bool checkIfFixItHintIsApplicable( + const FunctionDecl *ParameterSourceDeclaration, + const ParmVarDecl *SourceParam, const FunctionDecl *OriginalDeclaration) { + // Assumptions with regard to function declarations/definition: + // * If both function declaration and definition are seen, assume that + // definition is most up-to-date, and use it to generate replacements. + // * If only function declarations are seen, there is no easy way to tell + // which is up-to-date and which is not, so don't do anything. + // TODO: This may be changed later, but for now it seems the reasonable + // solution. + if (!ParameterSourceDeclaration->isThisDeclarationADefinition()) + return false; + + // Assumption: if parameter is not referenced in function defintion body, it + // may indicate that it's outdated, so don't touch it. + if (!SourceParam->isReferenced()) + return false; + + // In case there is the primary template definition and (possibly several) + // template specializations (and each with possibly several redeclarations), + // it is not at all clear what to change. + if (OriginalDeclaration->getTemplatedKind() == + FunctionDecl::TK_FunctionTemplateSpecialization) + return false; + + // Other cases seem OK to allow replacements. + return true; +} + +DifferingParamsContainer +findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration, + const FunctionDecl *OtherDeclaration, + const FunctionDecl *OriginalDeclaration) { + DifferingParamsContainer DifferingParams; + + auto SourceParamIt = ParameterSourceDeclaration->param_begin(); + auto OtherParamIt = OtherDeclaration->param_begin(); + + while (SourceParamIt != ParameterSourceDeclaration->param_end() && + OtherParamIt != OtherDeclaration->param_end()) { + auto SourceParamName = (*SourceParamIt)->getName(); + auto OtherParamName = (*OtherParamIt)->getName(); + + // FIXME: Provide a way to extract commented out parameter name from comment + // next to it. + if (!SourceParamName.empty() && !OtherParamName.empty() && + SourceParamName != OtherParamName) { + SourceRange OtherParamNameRange = + DeclarationNameInfo((*OtherParamIt)->getDeclName(), + (*OtherParamIt)->getLocation()).getSourceRange(); + + bool GenerateFixItHint = checkIfFixItHintIsApplicable( + ParameterSourceDeclaration, *SourceParamIt, OriginalDeclaration); + + DifferingParams.emplace_back(SourceParamName, OtherParamName, + OtherParamNameRange, GenerateFixItHint); + } + + ++SourceParamIt; + ++OtherParamIt; + } + + return DifferingParams; +} + +InconsistentDeclarationsContainer +findInconsitentDeclarations(const FunctionDecl *OriginalDeclaration, + const FunctionDecl *ParameterSourceDeclaration, + SourceManager &SM) { + InconsistentDeclarationsContainer InconsistentDeclarations; + SourceLocation ParameterSourceLocation = + ParameterSourceDeclaration->getLocation(); + + for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) { + SourceLocation OtherLocation = OtherDeclaration->getLocation(); + if (OtherLocation != ParameterSourceLocation) { // Skip self. + DifferingParamsContainer DifferingParams = + findDifferingParamsInDeclaration(ParameterSourceDeclaration, + OtherDeclaration, + OriginalDeclaration); + if (!DifferingParams.empty()) { + InconsistentDeclarations.emplace_back(OtherDeclaration->getLocation(), + std::move(DifferingParams)); + } + } + } + + // Sort in order of appearance in translation unit to generate clear + // diagnostics. + std::sort(InconsistentDeclarations.begin(), InconsistentDeclarations.end(), + [&SM](const InconsistentDeclarationInfo &Info1, + const InconsistentDeclarationInfo &Info2) { + return SM.isBeforeInTranslationUnit(Info1.DeclarationLocation, + Info2.DeclarationLocation); + }); + return InconsistentDeclarations; +} + +const FunctionDecl * +getParameterSourceDeclaration(const FunctionDecl *OriginalDeclaration) { + const FunctionTemplateDecl *PrimaryTemplate = + OriginalDeclaration->getPrimaryTemplate(); + if (PrimaryTemplate != nullptr) { + // In case of template specializations, use primary template declaration as + // the source of parameter names. + return PrimaryTemplate->getTemplatedDecl(); + } + + // In other cases, try to change to function definition, if available. + + if (OriginalDeclaration->isThisDeclarationADefinition()) + return OriginalDeclaration; + + for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) { + if (OtherDeclaration->isThisDeclarationADefinition()) { + return OtherDeclaration; + } + } + + // No definition found, so return original declaration. + return OriginalDeclaration; +} + +std::string joinParameterNames( + const DifferingParamsContainer &DifferingParams, + std::function ChooseParamName) { + llvm::SmallVector Buffer; + llvm::raw_svector_ostream Str(Buffer); + bool First = true; + for (const DifferingParamInfo &ParamInfo : DifferingParams) { + if (First) + First = false; + else + Str << ", "; + + Str << "'" << ChooseParamName(ParamInfo).str() << "'"; + } + return Str.str().str(); +} + +void formatDifferingParamsDiagnostic( + InconsistentDeclarationParameterNameCheck *Check, + SourceLocation Location, StringRef OtherDeclarationDescription, + const DifferingParamsContainer &DifferingParams) { + auto ChooseOtherName = + [](const DifferingParamInfo &ParamInfo) { return ParamInfo.OtherName; }; + auto ChooseSourceName = + [](const DifferingParamInfo &ParamInfo) { return ParamInfo.SourceName; }; + + auto ParamDiag = + Check->diag(Location, + "differing parameters are named here: (%0), in %1: (%2)", + DiagnosticIDs::Level::Note) + << joinParameterNames(DifferingParams, ChooseOtherName) + << OtherDeclarationDescription + << joinParameterNames(DifferingParams, ChooseSourceName); + + for (const DifferingParamInfo &ParamInfo : DifferingParams) { + if (ParamInfo.GenerateFixItHint) { + ParamDiag << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(ParamInfo.OtherNameRange), + ParamInfo.SourceName); + } + } +} + +void formatDiagnosticsForDeclarations( + InconsistentDeclarationParameterNameCheck *Check, + const FunctionDecl *ParameterSourceDeclaration, + const FunctionDecl *OriginalDeclaration, + const InconsistentDeclarationsContainer &InconsistentDeclarations) { + Check->diag( + OriginalDeclaration->getLocation(), + "function %q0 has %1 other declaration%s1 with different parameter names") + << OriginalDeclaration + << static_cast(InconsistentDeclarations.size()); + int Count = 1; + for (const InconsistentDeclarationInfo &InconsistentDeclaration : + InconsistentDeclarations) { + Check->diag(InconsistentDeclaration.DeclarationLocation, + "the %ordinal0 inconsistent declaration seen here", + DiagnosticIDs::Level::Note) + << Count; + + formatDifferingParamsDiagnostic( + Check, InconsistentDeclaration.DeclarationLocation, + "the other declaration", InconsistentDeclaration.DifferingParams); + + ++Count; + } +} + +void formatDiagnostics( + InconsistentDeclarationParameterNameCheck *Check, + const FunctionDecl *ParameterSourceDeclaration, + const FunctionDecl *OriginalDeclaration, + const InconsistentDeclarationsContainer &InconsistentDeclarations, + StringRef FunctionDescription, StringRef ParameterSourceDescription) { + for (const InconsistentDeclarationInfo &InconsistentDeclaration : + InconsistentDeclarations) { + Check->diag(InconsistentDeclaration.DeclarationLocation, + "%0 %q1 has a %2 with different parameter names") + << FunctionDescription << OriginalDeclaration + << ParameterSourceDescription; + + Check->diag(ParameterSourceDeclaration->getLocation(), "the %0 seen here", + DiagnosticIDs::Level::Note) + << ParameterSourceDescription; + + formatDifferingParamsDiagnostic( + Check, InconsistentDeclaration.DeclarationLocation, + ParameterSourceDescription, InconsistentDeclaration.DifferingParams); + } +} + +} // anonymous namespace + +void InconsistentDeclarationParameterNameCheck::registerMatchers( + MatchFinder *Finder) { + Finder->addMatcher(functionDecl(unless(isImplicit()), hasOtherDeclarations()) + .bind("functionDecl"), + this); +} + +void InconsistentDeclarationParameterNameCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *OriginalDeclaration = + Result.Nodes.getNodeAs("functionDecl"); + + if (VisitedDeclarations.count(OriginalDeclaration) > 0) + return; // Avoid multiple warnings. + + const FunctionDecl *ParameterSourceDeclaration = + getParameterSourceDeclaration(OriginalDeclaration); + + InconsistentDeclarationsContainer InconsistentDeclarations = + findInconsitentDeclarations(OriginalDeclaration, + ParameterSourceDeclaration, + *Result.SourceManager); + if (InconsistentDeclarations.empty()) { + // Avoid unnecessary further visits. + markRedeclarationsAsVisited(OriginalDeclaration); + return; + } + + if (OriginalDeclaration->getTemplatedKind() == + FunctionDecl::TK_FunctionTemplateSpecialization) { + formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration, + InconsistentDeclarations, + "function template specialization", + "primary template declaration"); + } else if (ParameterSourceDeclaration->isThisDeclarationADefinition()) { + formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration, + InconsistentDeclarations, "function", "definition"); + } else { + formatDiagnosticsForDeclarations(this, ParameterSourceDeclaration, + OriginalDeclaration, + InconsistentDeclarations); + } + + markRedeclarationsAsVisited(OriginalDeclaration); +} + +void InconsistentDeclarationParameterNameCheck::markRedeclarationsAsVisited( + const FunctionDecl *OriginalDeclaration) { + for (const FunctionDecl *Redecl : OriginalDeclaration->redecls()) { + VisitedDeclarations.insert(Redecl); + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h new file mode 100644 index 00000000..54860312 --- /dev/null +++ b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h @@ -0,0 +1,45 @@ +//===- InconsistentDeclarationParameterNameCheck.h - clang-tidy-*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_INCONSISTENT_DECLARATION_PARAMETER_NAME_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_INCONSISTENT_DECLARATION_PARAMETER_NAME_H + +#include "../ClangTidy.h" + +#include "llvm/ADT/DenseSet.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// \brief Checks for declarations of functions which differ in parameter names. +/// +/// For detailed documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.html +/// +class InconsistentDeclarationParameterNameCheck : public ClangTidyCheck { +public: + InconsistentDeclarationParameterNameCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void markRedeclarationsAsVisited(const FunctionDecl *FunctionDeclaration); + + llvm::DenseSet VisitedDeclarations; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_INCONSISTENT_DECLARATION_PARAMETER_NAME_H diff --git a/clang-tidy/readability/Makefile b/clang-tidy/readability/Makefile new file mode 100644 index 00000000..7d9e2ace --- /dev/null +++ b/clang-tidy/readability/Makefile @@ -0,0 +1,12 @@ +##===- clang-tidy/readability/Makefile ---------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## +CLANG_LEVEL := ../../../.. +LIBRARYNAME := clangTidyReadability + +include $(CLANG_LEVEL)/Makefile diff --git a/clang-tidy/readability/NamedParameterCheck.cpp b/clang-tidy/readability/NamedParameterCheck.cpp new file mode 100644 index 00000000..ffdc813b --- /dev/null +++ b/clang-tidy/readability/NamedParameterCheck.cpp @@ -0,0 +1,127 @@ +//===--- NamedParameterCheck.cpp - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NamedParameterCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void NamedParameterCheck::registerMatchers(ast_matchers::MatchFinder *Finder) { + Finder->addMatcher(functionDecl(unless(isInstantiated())).bind("decl"), this); +} + +void NamedParameterCheck::check(const MatchFinder::MatchResult &Result) { + const SourceManager &SM = *Result.SourceManager; + const auto *Function = Result.Nodes.getNodeAs("decl"); + SmallVector, 4> UnnamedParams; + + // Ignore implicitly generated members. + if (Function->isImplicit()) + return; + + // Ignore declarations without a definition if we're not dealing with an + // overriden method. + const FunctionDecl *Definition = nullptr; + if ((!Function->isDefined(Definition) || Function->isDefaulted() || + Function->isDeleted()) && + (!isa(Function) || + cast(Function)->size_overridden_methods() == 0)) + return; + + // TODO: Handle overloads. + // TODO: We could check that all redeclarations use the same name for + // arguments in the same position. + for (unsigned I = 0, E = Function->getNumParams(); I != E; ++I) { + const ParmVarDecl *Parm = Function->getParamDecl(I); + if (Parm->isImplicit()) + continue; + // Look for unnamed parameters. + if (!Parm->getName().empty()) + continue; + + // Don't warn on the dummy argument on post-inc and post-dec operators. + if ((Function->getOverloadedOperator() == OO_PlusPlus || + Function->getOverloadedOperator() == OO_MinusMinus) && + Parm->getType()->isSpecificBuiltinType(BuiltinType::Int)) + continue; + + // Sanity check the source locations. + if (!Parm->getLocation().isValid() || Parm->getLocation().isMacroID() || + !SM.isWrittenInSameFile(Parm->getLocStart(), Parm->getLocation())) + continue; + + // Skip gmock testing::Unused parameters. + if (auto Typedef = Parm->getType()->getAs()) + if (Typedef->getDecl()->getQualifiedNameAsString() == "testing::Unused") + continue; + + // Skip std::nullptr_t. + if (Parm->getType().getCanonicalType()->isNullPtrType()) + continue; + + // Look for comments. We explicitly want to allow idioms like + // void foo(int /*unused*/) + const char *Begin = SM.getCharacterData(Parm->getLocStart()); + const char *End = SM.getCharacterData(Parm->getLocation()); + StringRef Data(Begin, End - Begin); + if (Data.find("/*") != StringRef::npos) + continue; + + UnnamedParams.push_back(std::make_pair(Function, I)); + } + + // Emit only one warning per function but fixits for all unnamed parameters. + if (!UnnamedParams.empty()) { + const ParmVarDecl *FirstParm = + UnnamedParams.front().first->getParamDecl(UnnamedParams.front().second); + auto D = diag(FirstParm->getLocation(), + "all parameters should be named in a function"); + + for (auto P : UnnamedParams) { + // Fallback to an unused marker. + StringRef NewName = "unused"; + + // If the method is overridden, try to copy the name from the base method + // into the overrider. + const auto *M = dyn_cast(P.first); + if (M && M->size_overridden_methods() > 0) { + const ParmVarDecl *OtherParm = + (*M->begin_overridden_methods())->getParamDecl(P.second); + StringRef Name = OtherParm->getName(); + if (!Name.empty()) + NewName = Name; + } + + // If the definition has a named parameter use that name. + if (Definition) { + const ParmVarDecl *DefParm = Definition->getParamDecl(P.second); + StringRef Name = DefParm->getName(); + if (!Name.empty()) + NewName = Name; + } + + // Now insert the comment. Note that getLocation() points to the place + // where the name would be, this allows us to also get complex cases like + // function pointers right. + const ParmVarDecl *Parm = P.first->getParamDecl(P.second); + D << FixItHint::CreateInsertion(Parm->getLocation(), + " /*" + NewName.str() + "*/"); + } + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/NamedParameterCheck.h b/clang-tidy/readability/NamedParameterCheck.h new file mode 100644 index 00000000..3c22642a --- /dev/null +++ b/clang-tidy/readability/NamedParameterCheck.h @@ -0,0 +1,42 @@ +//===--- NamedParameterCheck.h - clang-tidy ---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMEDPARAMETERCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMEDPARAMETERCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Find functions with unnamed arguments. +/// +/// The check implements the following rule originating in the Google C++ Style +/// Guide: +/// +/// http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Function_Declarations_and_Definitions#Function_Declarations_and_Definitions +/// +/// All parameters should be named, with identical names in the declaration and +/// implementation. +/// +/// Corresponding cpplint.py check name: 'readability/function'. +class NamedParameterCheck : public ClangTidyCheck { +public: + NamedParameterCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMEDPARAMETERCHECK_H diff --git a/clang-tidy/readability/NamespaceCommentCheck.cpp b/clang-tidy/readability/NamespaceCommentCheck.cpp new file mode 100644 index 00000000..e57f736f --- /dev/null +++ b/clang-tidy/readability/NamespaceCommentCheck.cpp @@ -0,0 +1,146 @@ +//===--- NamespaceCommentCheck.cpp - clang-tidy ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NamespaceCommentCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/StringExtras.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +NamespaceCommentCheck::NamespaceCommentCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + NamespaceCommentPattern("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *" + "namespace( +([a-zA-Z0-9_]+))?\\.? *(\\*/)?$", + llvm::Regex::IgnoreCase), + ShortNamespaceLines(Options.get("ShortNamespaceLines", 1u)), + SpacesBeforeComments(Options.get("SpacesBeforeComments", 1u)) {} + +void NamespaceCommentCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ShortNamespaceLines", ShortNamespaceLines); + Options.store(Opts, "SpacesBeforeComments", SpacesBeforeComments); +} + +void NamespaceCommentCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (getLangOpts().CPlusPlus) + Finder->addMatcher(namespaceDecl().bind("namespace"), this); +} + +static bool locationsInSameFile(const SourceManager &Sources, + SourceLocation Loc1, SourceLocation Loc2) { + return Loc1.isFileID() && Loc2.isFileID() && + Sources.getFileID(Loc1) == Sources.getFileID(Loc2); +} + +static std::string getNamespaceComment(const NamespaceDecl *ND, + bool InsertLineBreak) { + std::string Fix = "// namespace"; + if (!ND->isAnonymousNamespace()) + Fix.append(" ").append(ND->getNameAsString()); + if (InsertLineBreak) + Fix.append("\n"); + return Fix; +} + +void NamespaceCommentCheck::check(const MatchFinder::MatchResult &Result) { + const NamespaceDecl *ND = Result.Nodes.getNodeAs("namespace"); + const SourceManager &Sources = *Result.SourceManager; + + if (!locationsInSameFile(Sources, ND->getLocStart(), ND->getRBraceLoc())) + return; + + // Don't require closing comments for namespaces spanning less than certain + // number of lines. + unsigned StartLine = Sources.getSpellingLineNumber(ND->getLocStart()); + unsigned EndLine = Sources.getSpellingLineNumber(ND->getRBraceLoc()); + if (EndLine - StartLine + 1 <= ShortNamespaceLines) + return; + + // Find next token after the namespace closing brace. + SourceLocation AfterRBrace = ND->getRBraceLoc().getLocWithOffset(1); + SourceLocation Loc = AfterRBrace; + Token Tok; + // Skip whitespace until we find the next token. + while (Lexer::getRawToken(Loc, Tok, Sources, Result.Context->getLangOpts()) || + Tok.is(tok::semi)) { + Loc = Loc.getLocWithOffset(1); + } + if (!locationsInSameFile(Sources, ND->getRBraceLoc(), Loc)) + return; + + bool NextTokenIsOnSameLine = Sources.getSpellingLineNumber(Loc) == EndLine; + // If we insert a line comment before the token in the same line, we need + // to insert a line break. + bool NeedLineBreak = NextTokenIsOnSameLine && Tok.isNot(tok::eof); + + SourceRange OldCommentRange(AfterRBrace, AfterRBrace); + std::string Message = "%0 not terminated with a closing comment"; + + // Try to find existing namespace closing comment on the same line. + if (Tok.is(tok::comment) && NextTokenIsOnSameLine) { + StringRef Comment(Sources.getCharacterData(Loc), Tok.getLength()); + SmallVector Groups; + if (NamespaceCommentPattern.match(Comment, &Groups)) { + StringRef NamespaceNameInComment = Groups.size() > 5 ? Groups[5] : ""; + StringRef Anonymous = Groups.size() > 3 ? Groups[3] : ""; + + // Check if the namespace in the comment is the same. + if ((ND->isAnonymousNamespace() && NamespaceNameInComment.empty()) || + (ND->getNameAsString() == NamespaceNameInComment && + Anonymous.empty())) { + // FIXME: Maybe we need a strict mode, where we always fix namespace + // comments with different format. + return; + } + + // Otherwise we need to fix the comment. + NeedLineBreak = Comment.startswith("/*"); + OldCommentRange = + SourceRange(AfterRBrace, Loc.getLocWithOffset(Tok.getLength())); + Message = + (llvm::Twine( + "%0 ends with a comment that refers to a wrong namespace '") + + NamespaceNameInComment + "'").str(); + } else if (Comment.startswith("//")) { + // Assume that this is an unrecognized form of a namespace closing line + // comment. Replace it. + NeedLineBreak = false; + OldCommentRange = + SourceRange(AfterRBrace, Loc.getLocWithOffset(Tok.getLength())); + Message = "%0 ends with an unrecognized comment"; + } + // If it's a block comment, just move it to the next line, as it can be + // multi-line or there may be other tokens behind it. + } + + std::string NamespaceName = + ND->isAnonymousNamespace() + ? "anonymous namespace" + : ("namespace '" + ND->getNameAsString() + "'"); + + diag(AfterRBrace, Message) + << NamespaceName << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(OldCommentRange), + std::string(SpacesBeforeComments, ' ') + + getNamespaceComment(ND, NeedLineBreak)); + diag(ND->getLocation(), "%0 starts here", DiagnosticIDs::Note) + << NamespaceName; +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/NamespaceCommentCheck.h b/clang-tidy/readability/NamespaceCommentCheck.h new file mode 100644 index 00000000..1dd350e9 --- /dev/null +++ b/clang-tidy/readability/NamespaceCommentCheck.h @@ -0,0 +1,43 @@ +//===--- NamespaceCommentCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMESPACECOMMENTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMESPACECOMMENTCHECK_H + +#include "../ClangTidy.h" +#include "llvm/Support/Regex.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Checks that long namespaces have a closing comment. +/// +/// http://llvm.org/docs/CodingStandards.html#namespace-indentation +/// +/// http://google-styleguide.googlecode.com/svn/trunk/cppguide.html#Namespaces +class NamespaceCommentCheck : public ClangTidyCheck { +public: + NamespaceCommentCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void storeOptions(ClangTidyOptions::OptionMap &Options) override; + + llvm::Regex NamespaceCommentPattern; + const unsigned ShortNamespaceLines; + const unsigned SpacesBeforeComments; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMESPACECOMMENTCHECK_H diff --git a/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tidy/readability/ReadabilityTidyModule.cpp new file mode 100644 index 00000000..314baefc --- /dev/null +++ b/clang-tidy/readability/ReadabilityTidyModule.cpp @@ -0,0 +1,71 @@ +//===--- ReadabilityTidyModule.cpp - clang-tidy ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "BracesAroundStatementsCheck.h" +#include "ContainerSizeEmptyCheck.h" +#include "ElseAfterReturnCheck.h" +#include "FunctionSizeCheck.h" +#include "IdentifierNamingCheck.h" +#include "ImplicitBoolCastCheck.h" +#include "InconsistentDeclarationParameterNameCheck.h" +#include "NamedParameterCheck.h" +#include "RedundantSmartptrGetCheck.h" +#include "RedundantStringCStrCheck.h" +#include "SimplifyBooleanExprCheck.h" +#include "UniqueptrDeleteReleaseCheck.h" + +namespace clang { +namespace tidy { +namespace readability { + +class ReadabilityModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "readability-braces-around-statements"); + CheckFactories.registerCheck( + "readability-container-size-empty"); + CheckFactories.registerCheck( + "readability-else-after-return"); + CheckFactories.registerCheck( + "readability-function-size"); + CheckFactories.registerCheck( + "readability-identifier-naming"); + CheckFactories.registerCheck( + "readability-implicit-bool-cast"); + CheckFactories.registerCheck( + "readability-inconsistent-declaration-parameter-name"); + CheckFactories.registerCheck( + "readability-uniqueptr-delete-release"); + CheckFactories.registerCheck( + "readability-named-parameter"); + CheckFactories.registerCheck( + "readability-redundant-smartptr-get"); + CheckFactories.registerCheck( + "readability-redundant-string-cstr"); + CheckFactories.registerCheck( + "readability-simplify-boolean-expr"); + } +}; + +// Register the ReadabilityModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("readability-module", "Adds readability-related checks."); + +} // namespace readability + +// This anchor is used to force the linker to link in the generated object file +// and thus register the ReadabilityModule. +volatile int ReadabilityModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantSmartptrGetCheck.cpp b/clang-tidy/readability/RedundantSmartptrGetCheck.cpp new file mode 100644 index 00000000..158fe2e3 --- /dev/null +++ b/clang-tidy/readability/RedundantSmartptrGetCheck.cpp @@ -0,0 +1,131 @@ +//===--- RedundantSmartptrGetCheck.cpp - clang-tidy -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RedundantSmartptrGetCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +namespace { +internal::Matcher callToGet(internal::Matcher OnClass) { + return cxxMemberCallExpr( + on(expr(anyOf(hasType(OnClass), + hasType(qualType( + pointsTo(decl(OnClass).bind("ptr_to_ptr")))))) + .bind("smart_pointer")), + unless(callee(memberExpr(hasObjectExpression(cxxThisExpr())))), + callee(cxxMethodDecl(hasName("get")))) + .bind("redundant_get"); +} + +void registerMatchersForGetArrowStart(MatchFinder *Finder, + MatchFinder::MatchCallback *Callback) { + const auto QuacksLikeASmartptr = recordDecl( + recordDecl().bind("duck_typing"), + has(cxxMethodDecl(hasName("operator->"), + returns(qualType(pointsTo(type().bind("op->Type")))))), + has(cxxMethodDecl(hasName("operator*"), + returns(qualType(references(type().bind("op*Type")))))), + has(cxxMethodDecl(hasName("get"), + returns(qualType(pointsTo(type().bind("getType"))))))); + + // Catch 'ptr.get()->Foo()' + Finder->addMatcher(memberExpr(expr().bind("memberExpr"), isArrow(), + hasObjectExpression(ignoringImpCasts( + callToGet(QuacksLikeASmartptr)))), + Callback); + + // Catch '*ptr.get()' or '*ptr->get()' + Finder->addMatcher( + unaryOperator(hasOperatorName("*"), + hasUnaryOperand(callToGet(QuacksLikeASmartptr))), + Callback); +} + +void registerMatchersForGetEquals(MatchFinder *Finder, + MatchFinder::MatchCallback *Callback) { + // This one is harder to do with duck typing. + // The operator==/!= that we are looking for might be member or non-member, + // might be on global namespace or found by ADL, might be a template, etc. + // For now, lets keep a list of known standard types. + + const auto IsAKnownSmartptr = recordDecl( + anyOf(hasName("::std::unique_ptr"), hasName("::std::shared_ptr"))); + + // Matches against nullptr. + Finder->addMatcher( + binaryOperator( + anyOf(hasOperatorName("=="), hasOperatorName("!=")), + hasEitherOperand(ignoringImpCasts(cxxNullPtrLiteralExpr())), + hasEitherOperand(callToGet(IsAKnownSmartptr))), + Callback); + // TODO: Catch ptr.get() == other_ptr.get() +} + + +} // namespace + +void RedundantSmartptrGetCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + registerMatchersForGetArrowStart(Finder, this); + registerMatchersForGetEquals(Finder, this); +} + +namespace { +bool allReturnTypesMatch(const MatchFinder::MatchResult &Result) { + if (Result.Nodes.getNodeAs("duck_typing") == nullptr) + return true; + // Verify that the types match. + // We can't do this on the matcher because the type nodes can be different, + // even though they represent the same type. This difference comes from how + // the type is referenced (eg. through a typedef, a type trait, etc). + const Type *OpArrowType = + Result.Nodes.getNodeAs("op->Type")->getUnqualifiedDesugaredType(); + const Type *OpStarType = + Result.Nodes.getNodeAs("op*Type")->getUnqualifiedDesugaredType(); + const Type *GetType = + Result.Nodes.getNodeAs("getType")->getUnqualifiedDesugaredType(); + return OpArrowType == OpStarType && OpArrowType == GetType; +} +} // namespace + +void RedundantSmartptrGetCheck::check(const MatchFinder::MatchResult &Result) { + if (!allReturnTypesMatch(Result)) return; + + bool IsPtrToPtr = Result.Nodes.getNodeAs("ptr_to_ptr") != nullptr; + bool IsMemberExpr = Result.Nodes.getNodeAs("memberExpr") != nullptr; + const Expr *GetCall = Result.Nodes.getNodeAs("redundant_get"); + const Expr *Smartptr = Result.Nodes.getNodeAs("smart_pointer"); + + if (IsPtrToPtr && IsMemberExpr) { + // Ignore this case (eg. Foo->get()->DoSomething()); + return; + } + + StringRef SmartptrText = Lexer::getSourceText( + CharSourceRange::getTokenRange(Smartptr->getSourceRange()), + *Result.SourceManager, Result.Context->getLangOpts()); + // Replace foo->get() with *foo, and foo.get() with foo. + std::string Replacement = Twine(IsPtrToPtr ? "*" : "", SmartptrText).str(); + diag(GetCall->getLocStart(), "redundant get() call on smart pointer") + << FixItHint::CreateReplacement(GetCall->getSourceRange(), Replacement); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantSmartptrGetCheck.h b/clang-tidy/readability/RedundantSmartptrGetCheck.h new file mode 100644 index 00000000..1da61958 --- /dev/null +++ b/clang-tidy/readability/RedundantSmartptrGetCheck.h @@ -0,0 +1,40 @@ +//===--- RedundantSmartptrGetCheck.h - clang-tidy ---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSMARTPTRGETCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSMARTPTRGETCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Find and remove redundant calls to smart pointer's `.get()` method. +/// +/// Examples: +/// +/// \code +/// ptr.get()->Foo() ==> ptr->Foo() +/// *ptr.get() ==> *ptr +/// *ptr->get() ==> **ptr +/// \endcode +class RedundantSmartptrGetCheck : public ClangTidyCheck { +public: + RedundantSmartptrGetCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSMARTPTRGETCHECK_H diff --git a/clang-tidy/readability/RedundantStringCStrCheck.cpp b/clang-tidy/readability/RedundantStringCStrCheck.cpp new file mode 100644 index 00000000..c58c1dc7 --- /dev/null +++ b/clang-tidy/readability/RedundantStringCStrCheck.cpp @@ -0,0 +1,145 @@ +//===- RedundantStringCStrCheck.cpp - Check for redundant c_str calls -----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements a check for redundant calls of c_str() on strings. +// +//===----------------------------------------------------------------------===// + +#include "RedundantStringCStrCheck.h" +#include "clang/Lex/Lexer.h" + +namespace clang { + +using namespace ast_matchers; + +namespace { + +template +StringRef getText(const ast_matchers::MatchFinder::MatchResult &Result, + T const &Node) { + return Lexer::getSourceText( + CharSourceRange::getTokenRange(Node.getSourceRange()), + *Result.SourceManager, Result.Context->getLangOpts()); +} + +// Return true if expr needs to be put in parens when it is an argument of a +// prefix unary operator, e.g. when it is a binary or ternary operator +// syntactically. +bool needParensAfterUnaryOperator(const Expr &ExprNode) { + if (isa(&ExprNode) || + isa(&ExprNode)) { + return true; + } + if (const auto *Op = dyn_cast(&ExprNode)) { + return Op->getNumArgs() == 2 && Op->getOperator() != OO_PlusPlus && + Op->getOperator() != OO_MinusMinus && Op->getOperator() != OO_Call && + Op->getOperator() != OO_Subscript; + } + return false; +} + +// Format a pointer to an expression: prefix with '*' but simplify +// when it already begins with '&'. Return empty string on failure. +std::string +formatDereference(const ast_matchers::MatchFinder::MatchResult &Result, + const Expr &ExprNode) { + if (const auto *Op = dyn_cast(&ExprNode)) { + if (Op->getOpcode() == UO_AddrOf) { + // Strip leading '&'. + return getText(Result, *Op->getSubExpr()->IgnoreParens()); + } + } + StringRef Text = getText(Result, ExprNode); + if (Text.empty()) + return std::string(); + // Add leading '*'. + if (needParensAfterUnaryOperator(ExprNode)) { + return (llvm::Twine("*(") + Text + ")").str(); + } + return (llvm::Twine("*") + Text).str(); +} + +const char StringConstructor[] = + "::std::basic_string, std::allocator >" + "::basic_string"; + +const char StringCStrMethod[] = + "::std::basic_string, std::allocator >" + "::c_str"; + +} // end namespace + +namespace tidy { +namespace readability { + +void RedundantStringCStrCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + cxxConstructExpr( + hasDeclaration(cxxMethodDecl(hasName(StringConstructor))), + argumentCountIs(2), + // The first argument must have the form x.c_str() or p->c_str() + // where the method is string::c_str(). We can use the copy + // constructor of string instead (or the compiler might share + // the string object). + hasArgument(0, cxxMemberCallExpr( + callee(memberExpr().bind("member")), + callee(cxxMethodDecl(hasName(StringCStrMethod))), + on(expr().bind("arg"))) + .bind("call")), + // The second argument is the alloc object which must not be + // present explicitly. + hasArgument(1, cxxDefaultArgExpr())), + this); + Finder->addMatcher( + cxxConstructExpr( + // Implicit constructors of these classes are overloaded + // wrt. string types and they internally make a StringRef + // referring to the argument. Passing a string directly to + // them is preferred to passing a char pointer. + hasDeclaration( + cxxMethodDecl(anyOf(hasName("::llvm::StringRef::StringRef"), + hasName("::llvm::Twine::Twine")))), + argumentCountIs(1), + // The only argument must have the form x.c_str() or p->c_str() + // where the method is string::c_str(). StringRef also has + // a constructor from string which is more efficient (avoids + // strlen), so we can construct StringRef from the string + // directly. + hasArgument(0, cxxMemberCallExpr( + callee(memberExpr().bind("member")), + callee(cxxMethodDecl(hasName(StringCStrMethod))), + on(expr().bind("arg"))) + .bind("call"))), + this); +} + +void RedundantStringCStrCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getStmtAs("call"); + const auto *Arg = Result.Nodes.getStmtAs("arg"); + bool Arrow = Result.Nodes.getStmtAs("member")->isArrow(); + // Replace the "call" node with the "arg" node, prefixed with '*' + // if the call was using '->' rather than '.'. + std::string ArgText = + Arrow ? formatDereference(Result, *Arg) : getText(Result, *Arg).str(); + if (ArgText.empty()) + return; + + diag(Call->getLocStart(), "redundant call to `c_str()`") + << FixItHint::CreateReplacement(Call->getSourceRange(), ArgText); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantStringCStrCheck.h b/clang-tidy/readability/RedundantStringCStrCheck.h new file mode 100644 index 00000000..9406f8ea --- /dev/null +++ b/clang-tidy/readability/RedundantStringCStrCheck.h @@ -0,0 +1,32 @@ +//===--- RedundantStringCStrCheck.h - clang-tidy ----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSTRINGCSTRCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSTRINGCSTRCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Finds unnecessary calls to `std::string::c_str()`. +class RedundantStringCStrCheck : public ClangTidyCheck { +public: + RedundantStringCStrCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSTRINGCSTRCHECK_H diff --git a/clang-tidy/readability/SimplifyBooleanExprCheck.cpp b/clang-tidy/readability/SimplifyBooleanExprCheck.cpp new file mode 100644 index 00000000..db09aaab --- /dev/null +++ b/clang-tidy/readability/SimplifyBooleanExprCheck.cpp @@ -0,0 +1,637 @@ +//===--- SimplifyBooleanExpr.cpp clang-tidy ---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SimplifyBooleanExprCheck.h" +#include "clang/Lex/Lexer.h" + +#include +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +namespace { + +StringRef getText(const MatchFinder::MatchResult &Result, SourceRange Range) { + return Lexer::getSourceText(CharSourceRange::getTokenRange(Range), + *Result.SourceManager, + Result.Context->getLangOpts()); +} + +template +StringRef getText(const MatchFinder::MatchResult &Result, T &Node) { + return getText(Result, Node.getSourceRange()); +} + +const char RightExpressionId[] = "bool-op-expr-yields-expr"; +const char LeftExpressionId[] = "expr-op-bool-yields-expr"; +const char NegatedRightExpressionId[] = "bool-op-expr-yields-not-expr"; +const char NegatedLeftExpressionId[] = "expr-op-bool-yields-not-expr"; +const char ConditionThenStmtId[] = "if-bool-yields-then"; +const char ConditionElseStmtId[] = "if-bool-yields-else"; +const char TernaryId[] = "ternary-bool-yields-condition"; +const char TernaryNegatedId[] = "ternary-bool-yields-not-condition"; +const char IfReturnsBoolId[] = "if-return"; +const char IfReturnsNotBoolId[] = "if-not-return"; +const char ThenLiteralId[] = "then-literal"; +const char IfAssignVariableId[] = "if-assign-lvalue"; +const char IfAssignLocId[] = "if-assign-loc"; +const char IfAssignBoolId[] = "if-assign"; +const char IfAssignNotBoolId[] = "if-assign-not"; +const char IfAssignObjId[] = "if-assign-obj"; +const char CompoundReturnId[] = "compound-return"; +const char CompoundBoolId[] = "compound-bool"; +const char CompoundNotBoolId[] = "compound-bool-not"; + +const char IfStmtId[] = "if"; +const char LHSId[] = "lhs-expr"; +const char RHSId[] = "rhs-expr"; + +const char SimplifyOperatorDiagnostic[] = + "redundant boolean literal supplied to boolean operator"; +const char SimplifyConditionDiagnostic[] = + "redundant boolean literal in if statement condition"; +const char SimplifyConditionalReturnDiagnostic[] = + "redundant boolean literal in conditional return statement"; + +const CXXBoolLiteralExpr *getBoolLiteral(const MatchFinder::MatchResult &Result, + StringRef Id) { + const auto *Literal = Result.Nodes.getNodeAs(Id); + return (Literal && + Result.SourceManager->isMacroBodyExpansion(Literal->getLocStart())) + ? nullptr + : Literal; +} + +internal::Matcher returnsBool(bool Value, StringRef Id = "ignored") { + auto SimpleReturnsBool = + returnStmt(has(cxxBoolLiteral(equals(Value)).bind(Id))) + .bind("returns-bool"); + return anyOf(SimpleReturnsBool, + compoundStmt(statementCountIs(1), has(SimpleReturnsBool))); +} + +bool needsParensAfterUnaryNegation(const Expr *E) { + E = E->IgnoreImpCasts(); + if (isa(E) || isa(E)) + return true; + if (const auto *Op = dyn_cast(E)) { + return Op->getNumArgs() == 2 && Op->getOperator() != OO_Call && + Op->getOperator() != OO_Subscript; + } + return false; +} + +std::pair Opposites[] = { + {BO_LT, BO_GE}, {BO_GT, BO_LE}, {BO_EQ, BO_NE}}; + +StringRef negatedOperator(const BinaryOperator *BinOp) { + const BinaryOperatorKind Opcode = BinOp->getOpcode(); + for (auto NegatableOp : Opposites) { + if (Opcode == NegatableOp.first) + return BinOp->getOpcodeStr(NegatableOp.second); + if (Opcode == NegatableOp.second) + return BinOp->getOpcodeStr(NegatableOp.first); + } + return StringRef(); +} + +std::pair OperatorNames[] = { + {OO_EqualEqual, "=="}, + {OO_ExclaimEqual, "!="}, + {OO_Less, "<"}, + {OO_GreaterEqual, ">="}, + {OO_Greater, ">"}, + {OO_LessEqual, "<="}}; + +StringRef getOperatorName(OverloadedOperatorKind OpKind) { + for (auto Name : OperatorNames) { + if (Name.first == OpKind) + return Name.second; + } + + return StringRef(); +} + +std::pair OppositeOverloads[] = + {{OO_EqualEqual, OO_ExclaimEqual}, + {OO_Less, OO_GreaterEqual}, + {OO_Greater, OO_LessEqual}}; + +StringRef negatedOperator(const CXXOperatorCallExpr *OpCall) { + const OverloadedOperatorKind Opcode = OpCall->getOperator(); + for (auto NegatableOp : OppositeOverloads) { + if (Opcode == NegatableOp.first) + return getOperatorName(NegatableOp.second); + if (Opcode == NegatableOp.second) + return getOperatorName(NegatableOp.first); + } + return StringRef(); +} + +std::string asBool(StringRef text, bool NeedsStaticCast) { + if (NeedsStaticCast) + return ("static_cast(" + text + ")").str(); + + return text; +} + +bool needsNullPtrComparison(const Expr *E) { + if (const auto *ImpCast = dyn_cast(E)) + return ImpCast->getCastKind() == CK_PointerToBoolean; + + return false; +} + +bool needsStaticCast(const Expr *E) { + if (const auto *ImpCast = dyn_cast(E)) { + if (ImpCast->getCastKind() == CK_UserDefinedConversion && + ImpCast->getSubExpr()->getType()->isBooleanType()) { + if (const auto *MemCall = + dyn_cast(ImpCast->getSubExpr())) { + if (const auto *MemDecl = + dyn_cast(MemCall->getMethodDecl())) { + if (MemDecl->isExplicit()) + return true; + } + } + } + } + + E = E->IgnoreImpCasts(); + return !E->getType()->isBooleanType(); +} + +std::string replacementExpression(const MatchFinder::MatchResult &Result, + bool Negated, const Expr *E) { + E = E->ignoreParenBaseCasts(); + const bool NeedsStaticCast = needsStaticCast(E); + if (Negated) { + if (const auto *UnOp = dyn_cast(E)) { + if (UnOp->getOpcode() == UO_LNot) { + if (needsNullPtrComparison(UnOp->getSubExpr())) + return (getText(Result, *UnOp->getSubExpr()) + " != nullptr").str(); + + return replacementExpression(Result, false, UnOp->getSubExpr()); + } + } + + if (needsNullPtrComparison(E)) + return (getText(Result, *E) + " == nullptr").str(); + + StringRef NegatedOperator; + const Expr *LHS = nullptr; + const Expr *RHS = nullptr; + if (const auto *BinOp = dyn_cast(E)) { + NegatedOperator = negatedOperator(BinOp); + LHS = BinOp->getLHS(); + RHS = BinOp->getRHS(); + } else if (const auto *OpExpr = dyn_cast(E)) { + if (OpExpr->getNumArgs() == 2) { + NegatedOperator = negatedOperator(OpExpr); + LHS = OpExpr->getArg(0); + RHS = OpExpr->getArg(1); + } + } + if (!NegatedOperator.empty() && LHS && RHS) { + return (asBool((getText(Result, *LHS) + " " + NegatedOperator + " " + + getText(Result, *RHS)).str(), + NeedsStaticCast)); + } + + StringRef Text = getText(Result, *E); + if (!NeedsStaticCast && needsParensAfterUnaryNegation(E)) + return ("!(" + Text + ")").str(); + + if (needsNullPtrComparison(E)) + return (getText(Result, *E) + " == nullptr").str(); + + return ("!" + asBool(Text, NeedsStaticCast)); + } + + if (const auto *UnOp = dyn_cast(E)) { + if (UnOp->getOpcode() == UO_LNot) { + if (needsNullPtrComparison(UnOp->getSubExpr())) + return (getText(Result, *UnOp->getSubExpr()) + " == nullptr").str(); + } + } + + if (needsNullPtrComparison(E)) + return (getText(Result, *E) + " != nullptr").str(); + + return asBool(getText(Result, *E), NeedsStaticCast); +} + +const CXXBoolLiteralExpr *stmtReturnsBool(const ReturnStmt *Ret, bool Negated) { + if (const auto *Bool = dyn_cast(Ret->getRetValue())) { + if (Bool->getValue() == !Negated) + return Bool; + } + + return nullptr; +} + +const CXXBoolLiteralExpr *stmtReturnsBool(const IfStmt *IfRet, bool Negated) { + if (IfRet->getElse() != nullptr) + return nullptr; + + if (const auto *Ret = dyn_cast(IfRet->getThen())) + return stmtReturnsBool(Ret, Negated); + + if (const auto *Compound = dyn_cast(IfRet->getThen())) { + if (Compound->size() == 1) { + if (const auto *CompoundRet = dyn_cast(Compound->body_back())) + return stmtReturnsBool(CompoundRet, Negated); + } + } + + return nullptr; +} + +} // namespace + +SimplifyBooleanExprCheck::SimplifyBooleanExprCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + ChainedConditionalReturn(Options.get("ChainedConditionalReturn", 0U)), + ChainedConditionalAssignment( + Options.get("ChainedConditionalAssignment", 0U)) {} + +void SimplifyBooleanExprCheck::matchBoolBinOpExpr(MatchFinder *Finder, + bool Value, + StringRef OperatorName, + StringRef BooleanId) { + Finder->addMatcher( + binaryOperator( + isExpansionInMainFile(), hasOperatorName(OperatorName), + hasLHS(allOf(expr().bind(LHSId), + cxxBoolLiteral(equals(Value)).bind(BooleanId))), + hasRHS(expr().bind(RHSId)), + unless(hasRHS(hasDescendant(cxxBoolLiteral())))), + this); +} + +void SimplifyBooleanExprCheck::matchExprBinOpBool(MatchFinder *Finder, + bool Value, + StringRef OperatorName, + StringRef BooleanId) { + Finder->addMatcher( + binaryOperator( + isExpansionInMainFile(), hasOperatorName(OperatorName), + hasLHS(expr().bind(LHSId)), + unless( + hasLHS(anyOf(cxxBoolLiteral(), hasDescendant(cxxBoolLiteral())))), + hasRHS(allOf(expr().bind(RHSId), + cxxBoolLiteral(equals(Value)).bind(BooleanId)))), + this); +} + +void SimplifyBooleanExprCheck::matchBoolCompOpExpr(MatchFinder *Finder, + bool Value, + StringRef OperatorName, + StringRef BooleanId) { + Finder->addMatcher( + binaryOperator(isExpansionInMainFile(), hasOperatorName(OperatorName), + hasLHS(allOf(expr().bind(LHSId), + ignoringImpCasts(cxxBoolLiteral(equals(Value)) + .bind(BooleanId)))), + hasRHS(expr().bind(RHSId)), + unless(hasRHS(hasDescendant(cxxBoolLiteral())))), + this); +} + +void SimplifyBooleanExprCheck::matchExprCompOpBool(MatchFinder *Finder, + bool Value, + StringRef OperatorName, + StringRef BooleanId) { + Finder->addMatcher( + binaryOperator(isExpansionInMainFile(), hasOperatorName(OperatorName), + unless(hasLHS(hasDescendant(cxxBoolLiteral()))), + hasLHS(expr().bind(LHSId)), + hasRHS(allOf(expr().bind(RHSId), + ignoringImpCasts(cxxBoolLiteral(equals(Value)) + .bind(BooleanId))))), + this); +} + +void SimplifyBooleanExprCheck::matchBoolCondition(MatchFinder *Finder, + bool Value, + StringRef BooleanId) { + Finder->addMatcher(ifStmt(isExpansionInMainFile(), + hasCondition(cxxBoolLiteral(equals(Value)) + .bind(BooleanId))).bind(IfStmtId), + this); +} + +void SimplifyBooleanExprCheck::matchTernaryResult(MatchFinder *Finder, + bool Value, + StringRef TernaryId) { + Finder->addMatcher( + conditionalOperator(isExpansionInMainFile(), + hasTrueExpression(cxxBoolLiteral(equals(Value))), + hasFalseExpression(cxxBoolLiteral(equals(!Value)))) + .bind(TernaryId), + this); +} + +void SimplifyBooleanExprCheck::matchIfReturnsBool(MatchFinder *Finder, + bool Value, StringRef Id) { + if (ChainedConditionalReturn) { + Finder->addMatcher(ifStmt(isExpansionInMainFile(), + hasThen(returnsBool(Value, ThenLiteralId)), + hasElse(returnsBool(!Value))).bind(Id), + this); + } else { + Finder->addMatcher(ifStmt(isExpansionInMainFile(), + unless(hasParent(ifStmt())), + hasThen(returnsBool(Value, ThenLiteralId)), + hasElse(returnsBool(!Value))).bind(Id), + this); + } +} + +void SimplifyBooleanExprCheck::matchIfAssignsBool(MatchFinder *Finder, + bool Value, StringRef Id) { + auto SimpleThen = binaryOperator( + hasOperatorName("="), + hasLHS(declRefExpr(hasDeclaration(decl().bind(IfAssignObjId)))), + hasLHS(expr().bind(IfAssignVariableId)), + hasRHS(cxxBoolLiteral(equals(Value)).bind(IfAssignLocId))); + auto Then = anyOf(SimpleThen, compoundStmt(statementCountIs(1), + hasAnySubstatement(SimpleThen))); + auto SimpleElse = binaryOperator( + hasOperatorName("="), + hasLHS(declRefExpr(hasDeclaration(equalsBoundNode(IfAssignObjId)))), + hasRHS(cxxBoolLiteral(equals(!Value)))); + auto Else = anyOf(SimpleElse, compoundStmt(statementCountIs(1), + hasAnySubstatement(SimpleElse))); + if (ChainedConditionalAssignment) { + Finder->addMatcher( + ifStmt(isExpansionInMainFile(), hasThen(Then), hasElse(Else)).bind(Id), + this); + } else { + Finder->addMatcher(ifStmt(isExpansionInMainFile(), + unless(hasParent(ifStmt())), hasThen(Then), + hasElse(Else)).bind(Id), + this); + } +} + +void SimplifyBooleanExprCheck::matchCompoundIfReturnsBool(MatchFinder *Finder, + bool Value, + StringRef Id) { + Finder->addMatcher( + compoundStmt(allOf(hasAnySubstatement(ifStmt(hasThen(returnsBool(Value)), + unless(hasElse(stmt())))), + hasAnySubstatement( + returnStmt(has(cxxBoolLiteral(equals(!Value)))) + .bind(CompoundReturnId)))).bind(Id), + this); +} + +void SimplifyBooleanExprCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ChainedConditionalReturn", ChainedConditionalReturn); + Options.store(Opts, "ChainedConditionalAssignment", + ChainedConditionalAssignment); +} + +void SimplifyBooleanExprCheck::registerMatchers(MatchFinder *Finder) { + matchBoolBinOpExpr(Finder, true, "&&", RightExpressionId); + matchBoolBinOpExpr(Finder, false, "||", RightExpressionId); + matchExprBinOpBool(Finder, false, "&&", RightExpressionId); + matchExprBinOpBool(Finder, true, "||", RightExpressionId); + matchBoolCompOpExpr(Finder, true, "==", RightExpressionId); + matchBoolCompOpExpr(Finder, false, "!=", RightExpressionId); + + matchExprBinOpBool(Finder, true, "&&", LeftExpressionId); + matchExprBinOpBool(Finder, false, "||", LeftExpressionId); + matchBoolBinOpExpr(Finder, false, "&&", LeftExpressionId); + matchBoolBinOpExpr(Finder, true, "||", LeftExpressionId); + matchExprCompOpBool(Finder, true, "==", LeftExpressionId); + matchExprCompOpBool(Finder, false, "!=", LeftExpressionId); + + matchBoolCompOpExpr(Finder, false, "==", NegatedRightExpressionId); + matchBoolCompOpExpr(Finder, true, "!=", NegatedRightExpressionId); + + matchExprCompOpBool(Finder, false, "==", NegatedLeftExpressionId); + matchExprCompOpBool(Finder, true, "!=", NegatedLeftExpressionId); + + matchBoolCondition(Finder, true, ConditionThenStmtId); + matchBoolCondition(Finder, false, ConditionElseStmtId); + + matchTernaryResult(Finder, true, TernaryId); + matchTernaryResult(Finder, false, TernaryNegatedId); + + matchIfReturnsBool(Finder, true, IfReturnsBoolId); + matchIfReturnsBool(Finder, false, IfReturnsNotBoolId); + + matchIfAssignsBool(Finder, true, IfAssignBoolId); + matchIfAssignsBool(Finder, false, IfAssignNotBoolId); + + matchCompoundIfReturnsBool(Finder, true, CompoundBoolId); + matchCompoundIfReturnsBool(Finder, false, CompoundNotBoolId); +} + +void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) { + if (const CXXBoolLiteralExpr *LeftRemoved = + getBoolLiteral(Result, RightExpressionId)) { + replaceWithExpression(Result, LeftRemoved, false); + } else if (const CXXBoolLiteralExpr *RightRemoved = + getBoolLiteral(Result, LeftExpressionId)) { + replaceWithExpression(Result, RightRemoved, true); + } else if (const CXXBoolLiteralExpr *NegatedLeftRemoved = + getBoolLiteral(Result, NegatedRightExpressionId)) { + replaceWithExpression(Result, NegatedLeftRemoved, false, true); + } else if (const CXXBoolLiteralExpr *NegatedRightRemoved = + getBoolLiteral(Result, NegatedLeftExpressionId)) { + replaceWithExpression(Result, NegatedRightRemoved, true, true); + } else if (const CXXBoolLiteralExpr *TrueConditionRemoved = + getBoolLiteral(Result, ConditionThenStmtId)) { + replaceWithThenStatement(Result, TrueConditionRemoved); + } else if (const CXXBoolLiteralExpr *FalseConditionRemoved = + getBoolLiteral(Result, ConditionElseStmtId)) { + replaceWithElseStatement(Result, FalseConditionRemoved); + } else if (const auto *Ternary = + Result.Nodes.getNodeAs(TernaryId)) { + replaceWithCondition(Result, Ternary); + } else if (const auto *TernaryNegated = + Result.Nodes.getNodeAs( + TernaryNegatedId)) { + replaceWithCondition(Result, TernaryNegated, true); + } else if (const auto *If = Result.Nodes.getNodeAs(IfReturnsBoolId)) { + replaceWithReturnCondition(Result, If); + } else if (const auto *IfNot = + Result.Nodes.getNodeAs(IfReturnsNotBoolId)) { + replaceWithReturnCondition(Result, IfNot, true); + } else if (const auto *IfAssign = + Result.Nodes.getNodeAs(IfAssignBoolId)) { + replaceWithAssignment(Result, IfAssign); + } else if (const auto *IfAssignNot = + Result.Nodes.getNodeAs(IfAssignNotBoolId)) { + replaceWithAssignment(Result, IfAssignNot, true); + } else if (const auto *Compound = + Result.Nodes.getNodeAs(CompoundBoolId)) { + replaceCompoundReturnWithCondition(Result, Compound); + } else if (const auto *Compound = + Result.Nodes.getNodeAs(CompoundNotBoolId)) { + replaceCompoundReturnWithCondition(Result, Compound, true); + } +} + +bool containsDiscardedTokens( + const ast_matchers::MatchFinder::MatchResult &Result, + CharSourceRange CharRange) { + std::string ReplacementText = + Lexer::getSourceText(CharRange, *Result.SourceManager, + Result.Context->getLangOpts()) + .str(); + Lexer Lex(CharRange.getBegin(), Result.Context->getLangOpts(), + ReplacementText.data(), ReplacementText.data(), + ReplacementText.data() + ReplacementText.size()); + Lex.SetCommentRetentionState(true); + Token Tok; + + while (!Lex.LexFromRawLexer(Tok)) { + if (Tok.is(tok::TokenKind::comment) || Tok.is(tok::TokenKind::hash)) + return true; + } + + return false; +} + +void SimplifyBooleanExprCheck::issueDiag( + const ast_matchers::MatchFinder::MatchResult &Result, SourceLocation Loc, + StringRef Description, SourceRange ReplacementRange, + StringRef Replacement) { + CharSourceRange CharRange = Lexer::makeFileCharRange( + CharSourceRange::getTokenRange(ReplacementRange), *Result.SourceManager, + Result.Context->getLangOpts()); + + DiagnosticBuilder Diag = diag(Loc, Description); + if (!containsDiscardedTokens(Result, CharRange)) + Diag << FixItHint::CreateReplacement(CharRange, Replacement); +} + +void SimplifyBooleanExprCheck::replaceWithExpression( + const ast_matchers::MatchFinder::MatchResult &Result, + const CXXBoolLiteralExpr *BoolLiteral, bool UseLHS, bool Negated) { + const auto *LHS = Result.Nodes.getNodeAs(LHSId); + const auto *RHS = Result.Nodes.getNodeAs(RHSId); + std::string Replacement = + replacementExpression(Result, Negated, UseLHS ? LHS : RHS); + SourceRange Range(LHS->getLocStart(), RHS->getLocEnd()); + issueDiag(Result, BoolLiteral->getLocStart(), SimplifyOperatorDiagnostic, + Range, Replacement); +} + +void SimplifyBooleanExprCheck::replaceWithThenStatement( + const MatchFinder::MatchResult &Result, + const CXXBoolLiteralExpr *TrueConditionRemoved) { + const auto *IfStatement = Result.Nodes.getNodeAs(IfStmtId); + issueDiag(Result, TrueConditionRemoved->getLocStart(), + SimplifyConditionDiagnostic, IfStatement->getSourceRange(), + getText(Result, *IfStatement->getThen())); +} + +void SimplifyBooleanExprCheck::replaceWithElseStatement( + const MatchFinder::MatchResult &Result, + const CXXBoolLiteralExpr *FalseConditionRemoved) { + const auto *IfStatement = Result.Nodes.getNodeAs(IfStmtId); + const Stmt *ElseStatement = IfStatement->getElse(); + issueDiag(Result, FalseConditionRemoved->getLocStart(), + SimplifyConditionDiagnostic, IfStatement->getSourceRange(), + ElseStatement ? getText(Result, *ElseStatement) : ""); +} + +void SimplifyBooleanExprCheck::replaceWithCondition( + const MatchFinder::MatchResult &Result, const ConditionalOperator *Ternary, + bool Negated) { + std::string Replacement = + replacementExpression(Result, Negated, Ternary->getCond()); + issueDiag(Result, Ternary->getTrueExpr()->getLocStart(), + "redundant boolean literal in ternary expression result", + Ternary->getSourceRange(), Replacement); +} + +void SimplifyBooleanExprCheck::replaceWithReturnCondition( + const MatchFinder::MatchResult &Result, const IfStmt *If, bool Negated) { + StringRef Terminator = isa(If->getElse()) ? ";" : ""; + std::string Condition = replacementExpression(Result, Negated, If->getCond()); + std::string Replacement = ("return " + Condition + Terminator).str(); + SourceLocation Start = + Result.Nodes.getNodeAs(ThenLiteralId)->getLocStart(); + issueDiag(Result, Start, SimplifyConditionalReturnDiagnostic, + If->getSourceRange(), Replacement); +} + +void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition( + const MatchFinder::MatchResult &Result, const CompoundStmt *Compound, + bool Negated) { + const auto *Ret = Result.Nodes.getNodeAs(CompoundReturnId); + + // The body shouldn't be empty because the matcher ensures that it must + // contain at least two statements: + // 1) A `return` statement returning a boolean literal `false` or `true` + // 2) An `if` statement with no `else` clause that consists fo a single + // `return` statement returning the opposite boolean literal `true` or + // `false`. + assert(Compound->size() >= 2); + const IfStmt *BeforeIf = nullptr; + CompoundStmt::const_body_iterator Current = Compound->body_begin(); + CompoundStmt::const_body_iterator After = Compound->body_begin(); + for (++After; After != Compound->body_end() && *Current != Ret; + ++Current, ++After) { + if (const auto *If = dyn_cast(*Current)) { + if (const CXXBoolLiteralExpr *Lit = stmtReturnsBool(If, Negated)) { + if (*After == Ret) { + if (!ChainedConditionalReturn && BeforeIf) + continue; + + const Expr *Condition = If->getCond(); + std::string Replacement = + "return " + replacementExpression(Result, Negated, Condition); + issueDiag( + Result, Lit->getLocStart(), SimplifyConditionalReturnDiagnostic, + SourceRange(If->getLocStart(), Ret->getLocEnd()), Replacement); + return; + } + + BeforeIf = If; + } + } else { + BeforeIf = nullptr; + } + } +} + +void SimplifyBooleanExprCheck::replaceWithAssignment( + const MatchFinder::MatchResult &Result, const IfStmt *IfAssign, + bool Negated) { + SourceRange Range = IfAssign->getSourceRange(); + StringRef VariableName = + getText(Result, *Result.Nodes.getNodeAs(IfAssignVariableId)); + StringRef Terminator = isa(IfAssign->getElse()) ? ";" : ""; + std::string Condition = + replacementExpression(Result, Negated, IfAssign->getCond()); + std::string Replacement = + (VariableName + " = " + Condition + Terminator).str(); + SourceLocation Location = + Result.Nodes.getNodeAs(IfAssignLocId)->getLocStart(); + issueDiag(Result, Location, + "redundant boolean literal in conditional assignment", Range, + Replacement); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/SimplifyBooleanExprCheck.h b/clang-tidy/readability/SimplifyBooleanExprCheck.h new file mode 100644 index 00000000..c74e6c45 --- /dev/null +++ b/clang-tidy/readability/SimplifyBooleanExprCheck.h @@ -0,0 +1,169 @@ +//===--- SimplifyBooleanExpr.h clang-tidy -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SIMPLIFY_BOOLEAN_EXPR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SIMPLIFY_BOOLEAN_EXPR_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Looks for boolean expressions involving boolean constants and simplifies +/// them to use the appropriate boolean expression directly. +/// +/// Examples: +/// +/// =========================================== ================ +/// Initial expression Result +/// ------------------------------------------- ---------------- +/// `if (b == true)` `if (b)` +/// `if (b == false)` `if (!b)` +/// `if (b && true)` `if (b)` +/// `if (b && false)` `if (false)` +/// `if (b || true)` `if (true)` +/// `if (b || false)` `if (b)` +/// `e ? true : false` `e` +/// `e ? false : true` `!e` +/// `if (true) t(); else f();` `t();` +/// `if (false) t(); else f();` `f();` +/// `if (e) return true; else return false;` `return e;` +/// `if (e) return false; else return true;` `return !e;` +/// `if (e) b = true; else b = false;` `b = e;` +/// `if (e) b = false; else b = true;` `b = !e;` +/// `if (e) return true; return false;` `return e;` +/// `if (e) return false; return true;` `return !e;` +/// =========================================== ================ +/// +/// The resulting expression `e` is modified as follows: +/// 1. Unnecessary parentheses around the expression are removed. +/// 2. Negated applications of `!` are eliminated. +/// 3. Negated applications of comparison operators are changed to use the +/// opposite condition. +/// 4. Implicit conversions of pointer to `bool` are replaced with explicit +/// comparisons to `nullptr`. +/// 5. Implicit casts to `bool` are replaced with explicit casts to `bool`. +/// 6. Object expressions with `explicit operator bool` conversion operators +/// are replaced with explicit casts to `bool`. +/// +/// Examples: +/// 1. The ternary assignment `bool b = (i < 0) ? true : false;` has redundant +/// parentheses and becomes `bool b = i < 0;`. +/// +/// 2. The conditional return `if (!b) return false; return true;` has an +/// implied double negation and becomes `return b;`. +/// +/// 3. The conditional return `if (i < 0) return false; return true;` becomes +/// `return i >= 0;`. +/// +/// The conditional return `if (i != 0) return false; return true;` becomes +/// `return i == 0;`. +/// +/// 4. The conditional return `if (p) return true; return false;` has an +/// implicit conversion of a pointer to `bool` and becomes +/// `return p != nullptr;`. +/// +/// The ternary assignment `bool b = (i & 1) ? true : false;` has an +/// implicit conversion of `i & 1` to `bool` and becomes +/// `bool b = static_cast(i & 1);`. +/// +/// 5. The conditional return `if (i & 1) return true; else return false;` has +/// an implicit conversion of an integer quantity `i & 1` to `bool` and +/// becomes `return static_cast(i & 1);` +/// +/// 6. Given `struct X { explicit operator bool(); };`, and an instance `x` of +/// `struct X`, the conditional return `if (x) return true; return false;` +/// becomes `return static_cast(x);` +/// +/// When a conditional boolean return or assignment appears at the end of a +/// chain of `if`, `else if` statements, the conditional statement is left +/// unchanged unless the option `ChainedConditionalReturn` or +/// `ChainedConditionalAssignment`, respectively, is specified as non-zero. +/// The default value for both options is zero. +/// +class SimplifyBooleanExprCheck : public ClangTidyCheck { +public: + SimplifyBooleanExprCheck(StringRef Name, ClangTidyContext *Context); + + void storeOptions(ClangTidyOptions::OptionMap &Options) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void matchBoolBinOpExpr(ast_matchers::MatchFinder *Finder, bool Value, + StringRef OperatorName, StringRef BooleanId); + + void matchExprBinOpBool(ast_matchers::MatchFinder *Finder, bool Value, + StringRef OperatorName, StringRef BooleanId); + + void matchBoolCompOpExpr(ast_matchers::MatchFinder *Finder, bool Value, + StringRef OperatorName, StringRef BooleanId); + + void matchExprCompOpBool(ast_matchers::MatchFinder *Finder, bool Value, + StringRef OperatorName, StringRef BooleanId); + + void matchBoolCondition(ast_matchers::MatchFinder *Finder, bool Value, + StringRef BooleanId); + + void matchTernaryResult(ast_matchers::MatchFinder *Finder, bool Value, + StringRef TernaryId); + + void matchIfReturnsBool(ast_matchers::MatchFinder *Finder, bool Value, + StringRef Id); + + void matchIfAssignsBool(ast_matchers::MatchFinder *Finder, bool Value, + StringRef Id); + + void matchCompoundIfReturnsBool(ast_matchers::MatchFinder *Finder, bool Value, + StringRef Id); + + void + replaceWithExpression(const ast_matchers::MatchFinder::MatchResult &Result, + const CXXBoolLiteralExpr *BoolLiteral, bool UseLHS, + bool Negated = false); + + void + replaceWithThenStatement(const ast_matchers::MatchFinder::MatchResult &Result, + const CXXBoolLiteralExpr *BoolLiteral); + + void + replaceWithElseStatement(const ast_matchers::MatchFinder::MatchResult &Result, + const CXXBoolLiteralExpr *FalseConditionRemoved); + + void + replaceWithCondition(const ast_matchers::MatchFinder::MatchResult &Result, + const ConditionalOperator *Ternary, + bool Negated = false); + + void replaceWithReturnCondition( + const ast_matchers::MatchFinder::MatchResult &Result, const IfStmt *If, + bool Negated = false); + + void + replaceWithAssignment(const ast_matchers::MatchFinder::MatchResult &Result, + const IfStmt *If, bool Negated = false); + + void replaceCompoundReturnWithCondition( + const ast_matchers::MatchFinder::MatchResult &Result, + const CompoundStmt *Compound, bool Negated = false); + + void issueDiag(const ast_matchers::MatchFinder::MatchResult &Result, + SourceLocation Loc, StringRef Description, + SourceRange ReplacementRange, StringRef Replacement); + + const bool ChainedConditionalReturn; + const bool ChainedConditionalAssignment; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SIMPLIFY_BOOLEAN_EXPR_H diff --git a/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp b/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp new file mode 100644 index 00000000..8a1ca4c1 --- /dev/null +++ b/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp @@ -0,0 +1,69 @@ +//===--- UniqueptrDeleteReleaseCheck.cpp - clang-tidy----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UniqueptrDeleteReleaseCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +void UniqueptrDeleteReleaseCheck::registerMatchers(MatchFinder *Finder) { + auto IsSusbstituted = qualType(anyOf( + substTemplateTypeParmType(), hasDescendant(substTemplateTypeParmType()))); + + auto UniquePtrWithDefaultDelete = classTemplateSpecializationDecl( + hasName("std::unique_ptr"), + hasTemplateArgument(1, refersToType(qualType(hasDeclaration(cxxRecordDecl( + hasName("std::default_delete"))))))); + + Finder->addMatcher( + cxxDeleteExpr( + has(cxxMemberCallExpr(on(expr(hasType(UniquePtrWithDefaultDelete), + unless(hasType(IsSusbstituted))) + .bind("uptr")), + callee(cxxMethodDecl(hasName("release")))))) + .bind("delete"), + this); +} + +void UniqueptrDeleteReleaseCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *PtrExpr = Result.Nodes.getNodeAs("uptr"); + const auto *DeleteExpr = Result.Nodes.getNodeAs("delete"); + + if (PtrExpr->getLocStart().isMacroID()) + return; + + // Ignore dependent types. + // It can give us false positives, so we go with false negatives instead to + // be safe. + if (PtrExpr->getType()->isDependentType()) + return; + + SourceLocation AfterPtr = + Lexer::getLocForEndOfToken(PtrExpr->getLocEnd(), 0, *Result.SourceManager, + Result.Context->getLangOpts()); + + diag(DeleteExpr->getLocStart(), + "prefer '= nullptr' to 'delete x.release()' to reset unique_ptr<> " + "objects") + << FixItHint::CreateRemoval(CharSourceRange::getCharRange( + DeleteExpr->getLocStart(), PtrExpr->getLocStart())) + << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(AfterPtr, DeleteExpr->getLocEnd()), + " = nullptr"); +} + +} // namespace tidy +} // namespace clang + diff --git a/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h b/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h new file mode 100644 index 00000000..ac55c769 --- /dev/null +++ b/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h @@ -0,0 +1,35 @@ +//===--- UniqueptrDeleteReleaseCheck.h - clang-tidy--------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_UNIQUEPTR_DELETE_RELEASE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_UNIQUEPTR_DELETE_RELEASE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// Flag statements of the form: delete .release() +/// and replace them with: = nullptr +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-uniqueptr-delete-release.html +class UniqueptrDeleteReleaseCheck : public ClangTidyCheck { +public: + UniqueptrDeleteReleaseCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_UNIQUEPTR_DELETE_RELEASE_H + diff --git a/clang-tidy/rename_check.py b/clang-tidy/rename_check.py new file mode 100755 index 00000000..4f11df86 --- /dev/null +++ b/clang-tidy/rename_check.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# +#===- rename_check.py - clang-tidy check renamer -------------*- python -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +import os +import re +import sys +import glob + +def replaceInFile(fileName, sFrom, sTo): + if sFrom == sTo: + return + txt = None + with open(fileName, "r") as f: + txt = f.read() + + if sFrom not in txt: + return + + txt = txt.replace(sFrom, sTo) + print("Replace '%s' -> '%s' in '%s'" % (sFrom, sTo, fileName)) + with open(fileName, "w") as f: + f.write(txt) + +def generateCommentLineHeader(filename): + return ''.join(['//===--- ', + os.path.basename(filename), + ' - clang-tidy ', + '-' * max(0, 42 - len(os.path.basename(filename))), + '*- C++ -*-===//']) + +def generateCommentLineSource(filename): + return ''.join(['//===--- ', + os.path.basename(filename), + ' - clang-tidy', + '-' * max(0, 52 - len(os.path.basename(filename))), + '-===//']) + +def fileRename(fileName, sFrom, sTo): + if sFrom not in fileName: + return fileName + newFileName = fileName.replace(sFrom, sTo) + print("Rename '%s' -> '%s'" % (fileName, newFileName)) + os.rename(fileName, newFileName) + return newFileName + +def getListOfFiles(clang_tidy_path): + files = glob.glob(os.path.join(clang_tidy_path,'*')) + for dirname in files: + if os.path.isdir(dirname): + files += glob.glob(os.path.join(dirname,'*')) + files += glob.glob(os.path.join(clang_tidy_path,'..', 'test', 'clang-tidy', '*')) + files += glob.glob(os.path.join(clang_tidy_path,'..', 'docs', 'clang-tidy', 'checks', '*')) + return [filename for filename in files if os.path.isfile(filename)] + +def main(): + if len(sys.argv) != 4: + print('Usage: rename_check.py \n') + print(' example: rename_check.py misc awesome-functions new-awesome-function') + return + + module = sys.argv[1].lower() + check_name = sys.argv[2] + check_name_camel = ''.join(map(lambda elem: elem.capitalize(), + check_name.split('-'))) + 'Check' + check_name_new = sys.argv[3] + check_name_new_camel = ''.join(map(lambda elem: elem.capitalize(), + check_name_new.split('-'))) + 'Check' + + clang_tidy_path = os.path.dirname(sys.argv[0]) + + header_guard_old = module.upper() + '_' + check_name.upper().replace('-', '_') + header_guard_new = module.upper() + '_' + check_name_new.upper().replace('-', '_') + + for filename in getListOfFiles(clang_tidy_path): + originalName = filename + filename = fileRename(filename, check_name, check_name_new) + filename = fileRename(filename, check_name_camel, check_name_new_camel) + replaceInFile(filename, generateCommentLineHeader(originalName), generateCommentLineHeader(filename)) + replaceInFile(filename, generateCommentLineSource(originalName), generateCommentLineSource(filename)) + replaceInFile(filename, header_guard_old, header_guard_new) + replaceInFile(filename, check_name, check_name_new) + replaceInFile(filename, check_name_camel, check_name_new_camel) + +if __name__ == '__main__': + main() diff --git a/clang-tidy/tool/CMakeLists.txt b/clang-tidy/tool/CMakeLists.txt new file mode 100644 index 00000000..d66aa9ce --- /dev/null +++ b/clang-tidy/tool/CMakeLists.txt @@ -0,0 +1,28 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +add_clang_executable(clang-tidy + ClangTidyMain.cpp + ) +target_link_libraries(clang-tidy + clangAST + clangASTMatchers + clangBasic + clangTidy + clangTidyCERTModule + clangTidyCppCoreGuidelinesModule + clangTidyGoogleModule + clangTidyLLVMModule + clangTidyMiscModule + clangTidyModernizeModule + clangTidyPerformanceModule + clangTidyReadabilityModule + clangTooling + ) + +install(TARGETS clang-tidy + RUNTIME DESTINATION bin) + +install(PROGRAMS clang-tidy-diff.py DESTINATION share/clang) +install(PROGRAMS run-clang-tidy.py DESTINATION share/clang) diff --git a/clang-tidy/tool/ClangTidyMain.cpp b/clang-tidy/tool/ClangTidyMain.cpp new file mode 100644 index 00000000..c5f57b12 --- /dev/null +++ b/clang-tidy/tool/ClangTidyMain.cpp @@ -0,0 +1,395 @@ +//===--- tools/extra/clang-tidy/ClangTidyMain.cpp - Clang tidy tool -------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file This file implements a clang-tidy tool. +/// +/// This tool uses the Clang Tooling infrastructure, see +/// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +/// for details on setting it up with LLVM source tree. +/// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "llvm/Support/Process.h" + +using namespace clang::ast_matchers; +using namespace clang::driver; +using namespace clang::tooling; +using namespace llvm; + +static cl::OptionCategory ClangTidyCategory("clang-tidy options"); + +static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); +static cl::extrahelp ClangTidyHelp( + "Configuration files:\n" + " clang-tidy attempts to read configuration for each source file from a\n" + " .clang-tidy file located in the closest parent directory of the source\n" + " file. If any configuration options have a corresponding command-line\n" + " option, command-line option takes precedence. The effective\n" + " configuration can be inspected using -dump-config:\n" + "\n" + " $ clang-tidy -dump-config - --\n" + " ---\n" + " Checks: '-*,some-check'\n" + " HeaderFilterRegex: ''\n" + " AnalyzeTemporaryDtors: false\n" + " User: user\n" + " CheckOptions: \n" + " - key: some-check.SomeOption\n" + " value: 'some value'\n" + " ...\n" + "\n\n"); + +const char DefaultChecks[] = // Enable these checks: + "clang-diagnostic-*," // * compiler diagnostics + "clang-analyzer-*," // * Static Analyzer checks + "-clang-analyzer-alpha*"; // * but not alpha checks: many false positives + +static cl::opt +Checks("checks", cl::desc("Comma-separated list of globs with optional '-'\n" + "prefix. Globs are processed in order of appearance\n" + "in the list. Globs without '-' prefix add checks\n" + "with matching names to the set, globs with the '-'\n" + "prefix remove checks with matching names from the\n" + "set of enabled checks.\n" + "This option's value is appended to the value read\n" + "from a .clang-tidy file, if any."), + cl::init(""), cl::cat(ClangTidyCategory)); + +static cl::opt +HeaderFilter("header-filter", + cl::desc("Regular expression matching the names of the\n" + "headers to output diagnostics from. Diagnostics\n" + "from the main file of each translation unit are\n" + "always displayed.\n" + "Can be used together with -line-filter.\n" + "This option overrides the value read from a\n" + ".clang-tidy file."), + cl::init(""), cl::cat(ClangTidyCategory)); + +static cl::opt + SystemHeaders("system-headers", + cl::desc("Display the errors from system headers."), + cl::init(false), cl::cat(ClangTidyCategory)); +static cl::opt +LineFilter("line-filter", + cl::desc("List of files with line ranges to filter the\n" + "warnings. Can be used together with\n" + "-header-filter. The format of the list is a JSON\n" + "array of objects:\n" + " [\n" + " {\"name\":\"file1.cpp\",\"lines\":[[1,3],[5,7]]},\n" + " {\"name\":\"file2.h\"}\n" + " ]"), + cl::init(""), cl::cat(ClangTidyCategory)); + +static cl::opt + Fix("fix", cl::desc("Apply suggested fixes. Without -fix-errors\n" + "clang-tidy will bail out if any compilation\n" + "errors were found."), + cl::init(false), cl::cat(ClangTidyCategory)); + +static cl::opt + FixErrors("fix-errors", + cl::desc("Apply suggested fixes even if compilation errors\n" + "were found. If compiler errors have attached\n" + "fix-its, clang-tidy will apply them as well."), + cl::init(false), cl::cat(ClangTidyCategory)); + +static cl::opt +ListChecks("list-checks", + cl::desc("List all enabled checks and exit. Use with\n" + "-checks=* to list all available checks."), + cl::init(false), cl::cat(ClangTidyCategory)); + +static cl::opt Config( + "config", + cl::desc("Specifies a configuration in YAML/JSON format:\n" + " -config=\"{Checks: '*', CheckOptions: [{key: x, value: y}]}\"\n" + "When the value is empty, clang-tidy will attempt to find\n" + "a file named .clang-tidy for each source file in its parent\n" + "directories."), + cl::init(""), cl::cat(ClangTidyCategory)); + +static cl::opt DumpConfig( + "dump-config", + cl::desc("Dumps configuration in the YAML format to stdout. This option\n" + "can be used along with a file name (and '--' if the file is\n" + "outside of a project with configured compilation database). The\n" + "configuration used for this file will be printed.\n" + "Use along with -checks=* to include configuration of all\n" + "checks.\n"), + cl::init(false), cl::cat(ClangTidyCategory)); + +static cl::opt EnableCheckProfile( + "enable-check-profile", + cl::desc("Enable per-check timing profiles, and print a report to stderr."), + cl::init(false), cl::cat(ClangTidyCategory)); + +static cl::opt AnalyzeTemporaryDtors( + "analyze-temporary-dtors", + cl::desc("Enable temporary destructor-aware analysis in\n" + "clang-analyzer- checks.\n" + "This option overrides the value read from a\n" + ".clang-tidy file."), + cl::init(false), cl::cat(ClangTidyCategory)); + +static cl::opt ExportFixes( + "export-fixes", + cl::desc("YAML file to store suggested fixes in. The\n" + "stored fixes can be applied to the input source\n" + "code with clang-apply-replacements."), + cl::value_desc("filename"), cl::cat(ClangTidyCategory)); + +namespace clang { +namespace tidy { + +static void printStats(const ClangTidyStats &Stats) { + if (Stats.errorsIgnored()) { + llvm::errs() << "Suppressed " << Stats.errorsIgnored() << " warnings ("; + StringRef Separator = ""; + if (Stats.ErrorsIgnoredNonUserCode) { + llvm::errs() << Stats.ErrorsIgnoredNonUserCode << " in non-user code"; + Separator = ", "; + } + if (Stats.ErrorsIgnoredLineFilter) { + llvm::errs() << Separator << Stats.ErrorsIgnoredLineFilter + << " due to line filter"; + Separator = ", "; + } + if (Stats.ErrorsIgnoredNOLINT) { + llvm::errs() << Separator << Stats.ErrorsIgnoredNOLINT << " NOLINT"; + Separator = ", "; + } + if (Stats.ErrorsIgnoredCheckFilter) + llvm::errs() << Separator << Stats.ErrorsIgnoredCheckFilter + << " with check filters"; + llvm::errs() << ").\n"; + if (Stats.ErrorsIgnoredNonUserCode) + llvm::errs() << "Use -header-filter=.* to display errors from all " + "non-system headers.\n"; + } +} + +static void printProfileData(const ProfileData &Profile, + llvm::raw_ostream &OS) { + // Time is first to allow for sorting by it. + std::vector> Timers; + TimeRecord Total; + + for (const auto& P : Profile.Records) { + Timers.emplace_back(P.getValue(), P.getKey()); + Total += P.getValue(); + } + + std::sort(Timers.begin(), Timers.end()); + + std::string Line = "===" + std::string(73, '-') + "===\n"; + OS << Line; + + if (Total.getUserTime()) + OS << " ---User Time---"; + if (Total.getSystemTime()) + OS << " --System Time--"; + if (Total.getProcessTime()) + OS << " --User+System--"; + OS << " ---Wall Time---"; + if (Total.getMemUsed()) + OS << " ---Mem---"; + OS << " --- Name ---\n"; + + // Loop through all of the timing data, printing it out. + for (auto I = Timers.rbegin(), E = Timers.rend(); I != E; ++I) { + I->first.print(Total, OS); + OS << I->second << '\n'; + } + + Total.print(Total, OS); + OS << "Total\n"; + OS << Line << "\n"; + OS.flush(); +} + +static std::unique_ptr createOptionsProvider() { + ClangTidyGlobalOptions GlobalOptions; + if (std::error_code Err = parseLineFilter(LineFilter, GlobalOptions)) { + llvm::errs() << "Invalid LineFilter: " << Err.message() << "\n\nUsage:\n"; + llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true); + return nullptr; + } + + ClangTidyOptions DefaultOptions; + DefaultOptions.Checks = DefaultChecks; + DefaultOptions.HeaderFilterRegex = HeaderFilter; + DefaultOptions.SystemHeaders = SystemHeaders; + DefaultOptions.AnalyzeTemporaryDtors = AnalyzeTemporaryDtors; + DefaultOptions.User = llvm::sys::Process::GetEnv("USER"); + // USERNAME is used on Windows. + if (!DefaultOptions.User) + DefaultOptions.User = llvm::sys::Process::GetEnv("USERNAME"); + + ClangTidyOptions OverrideOptions; + if (Checks.getNumOccurrences() > 0) + OverrideOptions.Checks = Checks; + if (HeaderFilter.getNumOccurrences() > 0) + OverrideOptions.HeaderFilterRegex = HeaderFilter; + if (SystemHeaders.getNumOccurrences() > 0) + OverrideOptions.SystemHeaders = SystemHeaders; + if (AnalyzeTemporaryDtors.getNumOccurrences() > 0) + OverrideOptions.AnalyzeTemporaryDtors = AnalyzeTemporaryDtors; + + if (!Config.empty()) { + if (llvm::ErrorOr ParsedConfig = + parseConfiguration(Config)) { + return llvm::make_unique( + GlobalOptions, ClangTidyOptions::getDefaults() + .mergeWith(DefaultOptions) + .mergeWith(*ParsedConfig) + .mergeWith(OverrideOptions)); + } else { + llvm::errs() << "Error: invalid configuration specified.\n" + << ParsedConfig.getError().message() << "\n"; + return nullptr; + } + } + return llvm::make_unique(GlobalOptions, DefaultOptions, + OverrideOptions); +} + +static int clangTidyMain(int argc, const char **argv) { + CommonOptionsParser OptionsParser(argc, argv, ClangTidyCategory, + cl::ZeroOrMore); + + auto OptionsProvider = createOptionsProvider(); + if (!OptionsProvider) + return 1; + + StringRef FileName("dummy"); + auto PathList = OptionsParser.getSourcePathList(); + if (!PathList.empty()) { + FileName = PathList.front(); + } + ClangTidyOptions EffectiveOptions = OptionsProvider->getOptions(FileName); + std::vector EnabledChecks = getCheckNames(EffectiveOptions); + + if (ListChecks) { + llvm::outs() << "Enabled checks:"; + for (auto CheckName : EnabledChecks) + llvm::outs() << "\n " << CheckName; + llvm::outs() << "\n\n"; + return 0; + } + + if (DumpConfig) { + EffectiveOptions.CheckOptions = getCheckOptions(EffectiveOptions); + llvm::outs() << configurationAsText( + ClangTidyOptions::getDefaults().mergeWith( + EffectiveOptions)) + << "\n"; + return 0; + } + + if (EnabledChecks.empty()) { + llvm::errs() << "Error: no checks enabled.\n"; + llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true); + return 1; + } + + if (PathList.empty()) { + llvm::errs() << "Error: no input files specified.\n"; + llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true); + return 1; + } + + ProfileData Profile; + + std::vector Errors; + ClangTidyStats Stats = + runClangTidy(std::move(OptionsProvider), OptionsParser.getCompilations(), + PathList, &Errors, + EnableCheckProfile ? &Profile : nullptr); + bool FoundErrors = + std::find_if(Errors.begin(), Errors.end(), [](const ClangTidyError &E) { + return E.DiagLevel == ClangTidyError::Error; + }) != Errors.end(); + + const bool DisableFixes = Fix && FoundErrors && !FixErrors; + + // -fix-errors implies -fix. + handleErrors(Errors, (FixErrors || Fix) && !DisableFixes); + + if (!ExportFixes.empty() && !Errors.empty()) { + std::error_code EC; + llvm::raw_fd_ostream OS(ExportFixes, EC, llvm::sys::fs::F_None); + if (EC) { + llvm::errs() << "Error opening output file: " << EC.message() << '\n'; + return 1; + } + exportReplacements(Errors, OS); + } + + printStats(Stats); + if (DisableFixes) + llvm::errs() + << "Found compiler errors, but -fix-errors was not specified.\n" + "Fixes have NOT been applied.\n\n"; + + if (EnableCheckProfile) + printProfileData(Profile, llvm::errs()); + + return 0; +} + +// This anchor is used to force the linker to link the CERTModule. +extern volatile int CERTModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED CERTModuleAnchorDestination = + CERTModuleAnchorSource; + +// This anchor is used to force the linker to link the LLVMModule. +extern volatile int LLVMModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED LLVMModuleAnchorDestination = + LLVMModuleAnchorSource; + +// This anchor is used to force the linker to link the CppCoreGuidelinesModule. +extern volatile int CppCoreGuidelinesModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED CppCoreGuidelinesModuleAnchorDestination = + CppCoreGuidelinesModuleAnchorSource; + +// This anchor is used to force the linker to link the GoogleModule. +extern volatile int GoogleModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED GoogleModuleAnchorDestination = + GoogleModuleAnchorSource; + +// This anchor is used to force the linker to link the MiscModule. +extern volatile int MiscModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED MiscModuleAnchorDestination = + MiscModuleAnchorSource; + +// This anchor is used to force the linker to link the ModernizeModule. +extern volatile int ModernizeModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED ModernizeModuleAnchorDestination = + ModernizeModuleAnchorSource; + +// This anchor is used to force the linker to link the PerformanceModule. +extern volatile int PerformanceModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED PerformanceModuleAnchorDestination = + PerformanceModuleAnchorSource; + +// This anchor is used to force the linker to link the ReadabilityModule. +extern volatile int ReadabilityModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED ReadabilityModuleAnchorDestination = + ReadabilityModuleAnchorSource; + +} // namespace tidy +} // namespace clang + +int main(int argc, const char **argv) { + return clang::tidy::clangTidyMain(argc, argv); +} diff --git a/clang-tidy/tool/Makefile b/clang-tidy/tool/Makefile new file mode 100644 index 00000000..2f7771df --- /dev/null +++ b/clang-tidy/tool/Makefile @@ -0,0 +1,49 @@ +##===- clang-tidy/tool/Makefile ----------------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL := ../../../.. + +TOOLNAME = clang-tidy + +# No plugins, optimize startup time. +TOOL_NO_EXPORTS = 1 + +include $(CLANG_LEVEL)/../../Makefile.config +LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader support mc option +USEDLIBS = clangTidy.a clangTidyLLVMModule.a clangTidyGoogleModule.a \ + clangTidyMiscModule.a clangTidyModernizeModule.a \ + clangTidyPerformanceModule.a clangTidyReadability.a \ + clangTidyUtils.a clangTidyCERTModule.a clangStaticAnalyzerFrontend.a \ + clangTidyCppCoreGuidelinesModule.a \ + clangStaticAnalyzerCheckers.a clangStaticAnalyzerCore.a \ + clangFormat.a clangASTMatchers.a clangTooling.a clangToolingCore.a \ + clangFrontend.a clangSerialization.a clangDriver.a clangParse.a \ + clangSema.a clangAnalysis.a clangRewriteFrontend.a clangRewrite.a \ + clangEdit.a clangAST.a clangLex.a clangBasic.a + +include $(CLANG_LEVEL)/Makefile + +PROJ_sharedir := $(DESTDIR)$(PROJ_prefix)/share/clang + +FILESLIST := $(notdir $(wildcard $(PROJ_SRC_DIR)/*.py)) + +SRCFILES := $(addprefix $(PROJ_SRC_DIR)/, $(FILESLIST)) +DESTFILES := $(addprefix $(PROJ_sharedir)/, $(FILESLIST)) + +$(PROJ_sharedir): + $(Echo) Making install directory: $@ + $(Verb) $(MKDIR) $@ + +$(DESTFILES): $(SRCFILES) $(PROJ_sharedir) + +$(PROJ_sharedir)/%.py: $(PROJ_SRC_DIR)/%.py + $(Echo) Installing script file: $(notdir $<) + $(Verb) $(ScriptInstall) $< $(PROJ_sharedir) + +install-local:: $(DESTFILES) diff --git a/clang-tidy/tool/clang-tidy-diff.py b/clang-tidy/tool/clang-tidy-diff.py new file mode 100755 index 00000000..700c5db7 --- /dev/null +++ b/clang-tidy/tool/clang-tidy-diff.py @@ -0,0 +1,121 @@ +#!/usr/bin/python +# +#===- clang-tidy-diff.py - ClangTidy Diff Checker ------------*- python -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +r""" +ClangTidy Diff Checker +====================== + +This script reads input from a unified diff, runs clang-tidy on all changed +files and outputs clang-tidy warnings in changed lines only. This is useful to +detect clang-tidy regressions in the lines touched by a specific patch. +Example usage for git/svn users: + + git diff -U0 HEAD^ | clang-tidy-diff.py -p1 + svn diff --diff-cmd=diff -x-U0 | \ + clang-tidy-diff.py -fix -checks=-*,modernize-use-override + +""" + +import argparse +import json +import re +import subprocess +import sys + + +def main(): + parser = argparse.ArgumentParser(description= + 'Reformat changed lines in diff. Without -i ' + 'option just output the diff that would be ' + 'introduced.') + parser.add_argument('-clang-tidy-binary', metavar='PATH', + default='clang-tidy', + help='path to clang-tidy binary') + parser.add_argument('-p', metavar='NUM', default=0, + help='strip the smallest prefix containing P slashes') + parser.add_argument('-regex', metavar='PATTERN', default=None, + help='custom pattern selecting file paths to reformat ' + '(case sensitive, overrides -iregex)') + parser.add_argument('-iregex', metavar='PATTERN', default= + r'.*\.(cpp|cc|c\+\+|cxx|c|cl|h|hpp|m|mm|inc)', + help='custom pattern selecting file paths to reformat ' + '(case insensitive, overridden by -regex)') + + parser.add_argument('-fix', action='store_true', default=False, + help='apply suggested fixes') + parser.add_argument('-checks', + help='checks filter, when not specified, use clang-tidy ' + 'default', + default='') + clang_tidy_args = [] + argv = sys.argv[1:] + if '--' in argv: + clang_tidy_args.extend(argv[argv.index('--'):]) + argv = argv[:argv.index('--')] + + args = parser.parse_args(argv) + + # Extract changed lines for each file. + filename = None + lines_by_file = {} + for line in sys.stdin: + match = re.search('^\+\+\+\ \"?(.*?/){%s}([^ \t\n\"]*)' % args.p, line) + if match: + filename = match.group(2) + if filename == None: + continue + + if args.regex is not None: + if not re.match('^%s$' % args.regex, filename): + continue + else: + if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE): + continue + + match = re.search('^@@.*\+(\d+)(,(\d+))?', line) + if match: + start_line = int(match.group(1)) + line_count = 1 + if match.group(3): + line_count = int(match.group(3)) + if line_count == 0: + continue + end_line = start_line + line_count - 1; + lines_by_file.setdefault(filename, []).append([start_line, end_line]) + + if len(lines_by_file) == 0: + print("No relevant changes found.") + sys.exit(0) + + line_filter_json = json.dumps( + [{"name" : name, "lines" : lines_by_file[name]} for name in lines_by_file], + separators = (',', ':')) + + quote = ""; + if sys.platform == 'win32': + line_filter_json=re.sub(r'"', r'"""', line_filter_json) + else: + quote = "'"; + + # Run clang-tidy on files containing changes. + command = [args.clang_tidy_binary] + command.append('-line-filter=' + quote + line_filter_json + quote) + if args.fix: + command.append('-fix') + if args.checks != '': + command.append('-checks=' + quote + args.checks + quote) + command.extend(lines_by_file.keys()) + command.extend(clang_tidy_args) + + sys.exit(subprocess.call(' '.join(command), shell=True)) + +if __name__ == '__main__': + main() diff --git a/clang-tidy/tool/run-clang-tidy.py b/clang-tidy/tool/run-clang-tidy.py new file mode 100755 index 00000000..e4dd3da4 --- /dev/null +++ b/clang-tidy/tool/run-clang-tidy.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python +# +#===- run-clang-tidy.py - Parallel clang-tidy runner ---------*- python -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# +# FIXME: Integrate with clang-tidy-diff.py + +""" +Parallel clang-tidy runner +========================== + +Runs clang-tidy over all files in a compilation database. Requires clang-tidy +and clang-apply-replacements in $PATH. + +Example invocations. +- Run clang-tidy on all files in the current working directory with a default + set of checks and show warnings in the cpp files and all project headers. + run-clang-tidy.py $PWD + +- Fix all header guards. + run-clang-tidy.py -fix -checks=-*,llvm-header-guard + +- Fix all header guards included from clang-tidy and header guards + for clang-tidy headers. + run-clang-tidy.py -fix -checks=-*,llvm-header-guard extra/clang-tidy \ + -header-filter=extra/clang-tidy + +Compilation database setup: +http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +""" + +import argparse +import json +import multiprocessing +import os +import Queue +import re +import shutil +import subprocess +import sys +import tempfile +import threading + + +def find_compilation_database(path): + """Adjusts the directory until a compilation database is found.""" + result = './' + while not os.path.isfile(os.path.join(result, path)): + if os.path.realpath(result) == '/': + print 'Error: could not find compilation database.' + sys.exit(1) + result += '../' + return os.path.realpath(result) + + +def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path, + header_filter): + """Gets a command line for clang-tidy.""" + start = [clang_tidy_binary] + if header_filter is not None: + start.append('-header-filter=' + header_filter) + else: + # Show warnings in all in-project headers by default. + start.append('-header-filter=^' + build_path + '/.*') + if checks: + start.append('-checks=' + checks) + if tmpdir is not None: + start.append('-export-fixes') + # Get a temporary file. We immediately close the handle so clang-tidy can + # overwrite it. + (handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir) + os.close(handle) + start.append(name) + start.append('-p=' + build_path) + start.append(f) + return start + + +def apply_fixes(args, tmpdir): + """Calls clang-apply-fixes on a given directory. Deletes the dir when done.""" + invocation = [args.clang_apply_replacements_binary] + if args.format: + invocation.append('-format') + invocation.append(tmpdir) + subprocess.call(invocation) + shutil.rmtree(tmpdir) + + +def run_tidy(args, tmpdir, build_path, queue): + """Takes filenames out of queue and runs clang-tidy on them.""" + while True: + name = queue.get() + invocation = get_tidy_invocation(name, args.clang_tidy_binary, args.checks, + tmpdir, build_path, args.header_filter) + sys.stdout.write(' '.join(invocation) + '\n') + subprocess.call(invocation) + queue.task_done() + + +def main(): + parser = argparse.ArgumentParser(description='Runs clang-tidy over all files ' + 'in a compilation database. Requires ' + 'clang-tidy and clang-apply-replacements in ' + '$PATH.') + parser.add_argument('-clang-tidy-binary', metavar='PATH', + default='clang-tidy', + help='path to clang-tidy binary') + parser.add_argument('-clang-apply-replacements-binary', metavar='PATH', + default='clang-apply-replacements', + help='path to clang-apply-replacements binary') + parser.add_argument('-checks', default=None, + help='checks filter, when not specified, use clang-tidy ' + 'default') + parser.add_argument('-header-filter', default=None, + help='regular expression matching the names of the ' + 'headers to output diagnostics from. Diagnostics from ' + 'the main file of each translation unit are always ' + 'displayed.') + parser.add_argument('-j', type=int, default=0, + help='number of tidy instances to be run in parallel.') + parser.add_argument('files', nargs='*', default=['.*'], + help='files to be processed (regex on path)') + parser.add_argument('-fix', action='store_true', help='apply fix-its') + parser.add_argument('-format', action='store_true', help='Reformat code ' + 'after applying fixes') + parser.add_argument('-p', dest='build_path', + help='Path used to read a compile command database.') + args = parser.parse_args() + + db_path = 'compile_commands.json' + + if args.build_path is not None: + build_path = args.build_path + else: + # Find our database + build_path = find_compilation_database(db_path) + + try: + invocation = [args.clang_tidy_binary, '-list-checks'] + invocation.append('-p=' + build_path) + if args.checks: + invocation.append('-checks=' + args.checks) + invocation.append('-') + print subprocess.check_output(invocation) + except: + print >>sys.stderr, "Unable to run clang-tidy." + sys.exit(1) + + # Load the database and extract all files. + database = json.load(open(os.path.join(build_path, db_path))) + files = [entry['file'] for entry in database] + + max_task = args.j + if max_task == 0: + max_task = multiprocessing.cpu_count() + + tmpdir = None + if args.fix: + tmpdir = tempfile.mkdtemp() + + # Build up a big regexy filter from all command line arguments. + file_name_re = re.compile('(' + ')|('.join(args.files) + ')') + + try: + # Spin up a bunch of tidy-launching threads. + queue = Queue.Queue(max_task) + for _ in range(max_task): + t = threading.Thread(target=run_tidy, + args=(args, tmpdir, build_path, queue)) + t.daemon = True + t.start() + + # Fill the queue with files. + for name in files: + if file_name_re.search(name): + queue.put(name) + + # Wait for all threads to be done. + queue.join() + + except KeyboardInterrupt: + # This is a sad hack. Unfortunately subprocess goes + # bonkers with ctrl-c and we start forking merrily. + print '\nCtrl-C detected, goodbye.' + if args.fix: + shutil.rmtree(tmpdir) + os.kill(0, 9) + + if args.fix: + print 'Applying fixes ...' + apply_fixes(args, tmpdir) + +if __name__ == '__main__': + main() diff --git a/clang-tidy/utils/CMakeLists.txt b/clang-tidy/utils/CMakeLists.txt new file mode 100644 index 00000000..9b48b7b2 --- /dev/null +++ b/clang-tidy/utils/CMakeLists.txt @@ -0,0 +1,16 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyUtils + HeaderGuard.cpp + IncludeInserter.cpp + IncludeSorter.cpp + LexerUtils.cpp + TypeTraits.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + ) diff --git a/clang-tidy/utils/HeaderGuard.cpp b/clang-tidy/utils/HeaderGuard.cpp new file mode 100644 index 00000000..1d9cc808 --- /dev/null +++ b/clang-tidy/utils/HeaderGuard.cpp @@ -0,0 +1,303 @@ +//===--- HeaderGuard.cpp - clang-tidy -------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "HeaderGuard.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/Path.h" + +namespace clang { +namespace tidy { + +/// \brief canonicalize a path by removing ./ and ../ components. +// FIXME: Consider moving this to llvm::sys::path. +static std::string cleanPath(StringRef Path) { + SmallString<256> NewPath; + for (auto I = llvm::sys::path::begin(Path), E = llvm::sys::path::end(Path); + I != E; ++I) { + if (*I == ".") + continue; + if (*I == "..") { + // Drop the last component. + NewPath.resize(llvm::sys::path::parent_path(NewPath).size()); + } else { + if (!NewPath.empty() && !NewPath.endswith("/")) + NewPath += '/'; + NewPath += *I; + } + } + return NewPath.str(); +} + +namespace { +class HeaderGuardPPCallbacks : public PPCallbacks { +public: + explicit HeaderGuardPPCallbacks(Preprocessor *PP, HeaderGuardCheck *Check) + : PP(PP), Check(Check) {} + + void FileChanged(SourceLocation Loc, FileChangeReason Reason, + SrcMgr::CharacteristicKind FileType, + FileID PrevFID) override { + // Record all files we enter. We'll need them to diagnose headers without + // guards. + SourceManager &SM = PP->getSourceManager(); + if (Reason == EnterFile && FileType == SrcMgr::C_User) { + if (const FileEntry *FE = SM.getFileEntryForID(SM.getFileID(Loc))) { + std::string FileName = cleanPath(FE->getName()); + Files[FileName] = FE; + } + } + } + + void Ifndef(SourceLocation Loc, const Token &MacroNameTok, + const MacroDefinition &MD) override { + if (MD) + return; + + // Record #ifndefs that succeeded. We also need the Location of the Name. + Ifndefs[MacroNameTok.getIdentifierInfo()] = + std::make_pair(Loc, MacroNameTok.getLocation()); + } + + void MacroDefined(const Token &MacroNameTok, + const MacroDirective *MD) override { + // Record all defined macros. We store the whole token to get info on the + // name later. + Macros.emplace_back(MacroNameTok, MD->getMacroInfo()); + } + + void Endif(SourceLocation Loc, SourceLocation IfLoc) override { + // Record all #endif and the corresponding #ifs (including #ifndefs). + EndIfs[IfLoc] = Loc; + } + + void EndOfMainFile() override { + // Now that we have all this information from the preprocessor, use it! + SourceManager &SM = PP->getSourceManager(); + + for (const auto &MacroEntry : Macros) { + const MacroInfo *MI = MacroEntry.second; + + // We use clang's header guard detection. This has the advantage of also + // emitting a warning for cases where a pseudo header guard is found but + // preceeded by something blocking the header guard optimization. + if (!MI->isUsedForHeaderGuard()) + continue; + + const FileEntry *FE = + SM.getFileEntryForID(SM.getFileID(MI->getDefinitionLoc())); + std::string FileName = cleanPath(FE->getName()); + Files.erase(FileName); + + // See if we should check and fix this header guard. + if (!Check->shouldFixHeaderGuard(FileName)) + continue; + + // Look up Locations for this guard. + SourceLocation Ifndef = + Ifndefs[MacroEntry.first.getIdentifierInfo()].second; + SourceLocation Define = MacroEntry.first.getLocation(); + SourceLocation EndIf = + EndIfs[Ifndefs[MacroEntry.first.getIdentifierInfo()].first]; + + // If the macro Name is not equal to what we can compute, correct it in + // the #ifndef and #define. + StringRef CurHeaderGuard = + MacroEntry.first.getIdentifierInfo()->getName(); + std::vector FixIts; + std::string NewGuard = checkHeaderGuardDefinition( + Ifndef, Define, EndIf, FileName, CurHeaderGuard, FixIts); + + // Now look at the #endif. We want a comment with the header guard. Fix it + // at the slightest deviation. + checkEndifComment(FileName, EndIf, NewGuard, FixIts); + + // Bundle all fix-its into one warning. The message depends on whether we + // changed the header guard or not. + if (!FixIts.empty()) { + if (CurHeaderGuard != NewGuard) { + Check->diag(Ifndef, "header guard does not follow preferred style") + << FixIts; + } else { + Check->diag(EndIf, "#endif for a header guard should reference the " + "guard macro in a comment") + << FixIts; + } + } + } + + // Emit warnings for headers that are missing guards. + checkGuardlessHeaders(); + + // Clear all state. + Macros.clear(); + Files.clear(); + Ifndefs.clear(); + EndIfs.clear(); + } + + bool wouldFixEndifComment(StringRef FileName, SourceLocation EndIf, + StringRef HeaderGuard, + size_t *EndIfLenPtr = nullptr) { + if (!EndIf.isValid()) + return false; + const char *EndIfData = PP->getSourceManager().getCharacterData(EndIf); + size_t EndIfLen = std::strcspn(EndIfData, "\r\n"); + if (EndIfLenPtr) + *EndIfLenPtr = EndIfLen; + + StringRef EndIfStr(EndIfData, EndIfLen); + EndIfStr = EndIfStr.substr(EndIfStr.find_first_not_of("#endif \t")); + + // Give up if there's an escaped newline. + size_t FindEscapedNewline = EndIfStr.find_last_not_of(' '); + if (FindEscapedNewline != StringRef::npos && + EndIfStr[FindEscapedNewline] == '\\') + return false; + + if (!Check->shouldSuggestEndifComment(FileName) && + !(EndIfStr.startswith("//") || + (EndIfStr.startswith("/*") && EndIfStr.endswith("*/")))) + return false; + + return (EndIfStr != "// " + HeaderGuard.str()) && + (EndIfStr != "/* " + HeaderGuard.str() + " */"); + } + + /// \brief Look for header guards that don't match the preferred style. Emit + /// fix-its and return the suggested header guard (or the original if no + /// change was made. + std::string checkHeaderGuardDefinition(SourceLocation Ifndef, + SourceLocation Define, + SourceLocation EndIf, + StringRef FileName, + StringRef CurHeaderGuard, + std::vector &FixIts) { + std::string CPPVar = Check->getHeaderGuard(FileName, CurHeaderGuard); + std::string CPPVarUnder = CPPVar + '_'; + + // Allow a trailing underscore iff we don't have to change the endif comment + // too. + if (Ifndef.isValid() && CurHeaderGuard != CPPVar && + (CurHeaderGuard != CPPVarUnder || + wouldFixEndifComment(FileName, EndIf, CurHeaderGuard))) { + FixIts.push_back(FixItHint::CreateReplacement( + CharSourceRange::getTokenRange( + Ifndef, Ifndef.getLocWithOffset(CurHeaderGuard.size())), + CPPVar)); + FixIts.push_back(FixItHint::CreateReplacement( + CharSourceRange::getTokenRange( + Define, Define.getLocWithOffset(CurHeaderGuard.size())), + CPPVar)); + return CPPVar; + } + return CurHeaderGuard; + } + + /// \brief Checks the comment after the #endif of a header guard and fixes it + /// if it doesn't match \c HeaderGuard. + void checkEndifComment(StringRef FileName, SourceLocation EndIf, + StringRef HeaderGuard, + std::vector &FixIts) { + size_t EndIfLen; + if (wouldFixEndifComment(FileName, EndIf, HeaderGuard, &EndIfLen)) { + FixIts.push_back(FixItHint::CreateReplacement( + CharSourceRange::getCharRange(EndIf, + EndIf.getLocWithOffset(EndIfLen)), + Check->formatEndIf(HeaderGuard))); + } + } + + /// \brief Looks for files that were visited but didn't have a header guard. + /// Emits a warning with fixits suggesting adding one. + void checkGuardlessHeaders() { + // Look for header files that didn't have a header guard. Emit a warning and + // fix-its to add the guard. + // TODO: Insert the guard after top comments. + for (const auto &FE : Files) { + StringRef FileName = FE.getKey(); + if (!Check->shouldSuggestToAddHeaderGuard(FileName)) + continue; + + SourceManager &SM = PP->getSourceManager(); + FileID FID = SM.translateFile(FE.getValue()); + SourceLocation StartLoc = SM.getLocForStartOfFile(FID); + if (StartLoc.isInvalid()) + continue; + + std::string CPPVar = Check->getHeaderGuard(FileName); + std::string CPPVarUnder = CPPVar + '_'; // Allow a trailing underscore. + // If there is a header guard macro but it's not in the topmost position + // emit a plain warning without fix-its. This often happens when the guard + // macro is preceeded by includes. + // FIXME: Can we move it into the right spot? + bool SeenMacro = false; + for (const auto &MacroEntry : Macros) { + StringRef Name = MacroEntry.first.getIdentifierInfo()->getName(); + SourceLocation DefineLoc = MacroEntry.first.getLocation(); + if ((Name == CPPVar || Name == CPPVarUnder) && + SM.isWrittenInSameFile(StartLoc, DefineLoc)) { + Check->diag( + DefineLoc, + "Header guard after code/includes. Consider moving it up."); + SeenMacro = true; + break; + } + } + + if (SeenMacro) + continue; + + Check->diag(StartLoc, "header is missing header guard") + << FixItHint::CreateInsertion( + StartLoc, "#ifndef " + CPPVar + "\n#define " + CPPVar + "\n\n") + << FixItHint::CreateInsertion( + SM.getLocForEndOfFile(FID), + Check->shouldSuggestEndifComment(FileName) + ? "\n#" + Check->formatEndIf(CPPVar) + "\n" + : "\n#endif\n"); + } + } + +private: + std::vector> Macros; + llvm::StringMap Files; + std::map> + Ifndefs; + std::map EndIfs; + + Preprocessor *PP; + HeaderGuardCheck *Check; +}; +} // namespace + +void HeaderGuardCheck::registerPPCallbacks(CompilerInstance &Compiler) { + Compiler.getPreprocessor().addPPCallbacks( + llvm::make_unique(&Compiler.getPreprocessor(), + this)); +} + +bool HeaderGuardCheck::shouldSuggestEndifComment(StringRef FileName) { + return FileName.endswith(".h"); +} + +bool HeaderGuardCheck::shouldFixHeaderGuard(StringRef FileName) { return true; } + +bool HeaderGuardCheck::shouldSuggestToAddHeaderGuard(StringRef FileName) { + return FileName.endswith(".h"); +} + +std::string HeaderGuardCheck::formatEndIf(StringRef HeaderGuard) { + return "endif // " + HeaderGuard.str(); +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/HeaderGuard.h b/clang-tidy/utils/HeaderGuard.h new file mode 100644 index 00000000..8b968aee --- /dev/null +++ b/clang-tidy/utils/HeaderGuard.h @@ -0,0 +1,46 @@ +//===--- HeaderGuard.h - clang-tidy -----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADERGUARD_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADERGUARD_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// Finds and fixes header guards. +class HeaderGuardCheck : public ClangTidyCheck { +public: + HeaderGuardCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerPPCallbacks(CompilerInstance &Compiler) override; + + /// \brief Returns true if the checker should suggest inserting a trailing + /// comment on the #endif of the header guard. It will use the same name as + /// returned by getHeaderGuard. + virtual bool shouldSuggestEndifComment(StringRef Filename); + /// \brief Returns true if the checker should suggest changing an existing + /// header guard to the string returned by getHeaderGuard. + virtual bool shouldFixHeaderGuard(StringRef Filename); + /// \brief Returns true if the checker should add a header guard to the file + /// if it has none. + virtual bool shouldSuggestToAddHeaderGuard(StringRef Filename); + /// \brief Returns a replacement for endif line with a comment mentioning + /// \p HeaderGuard. The replacement should start with "endif". + virtual std::string formatEndIf(StringRef HeaderGuard); + /// \brief Get the canonical header guard for a file. + virtual std::string getHeaderGuard(StringRef Filename, + StringRef OldGuard = StringRef()) = 0; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADERGUARD_H diff --git a/clang-tidy/utils/IncludeInserter.cpp b/clang-tidy/utils/IncludeInserter.cpp new file mode 100644 index 00000000..8f4bee6a --- /dev/null +++ b/clang-tidy/utils/IncludeInserter.cpp @@ -0,0 +1,83 @@ +//===-------- IncludeInserter.cpp - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "IncludeInserter.h" + +namespace clang { +namespace tidy { + +class IncludeInserterCallback : public PPCallbacks { +public: + explicit IncludeInserterCallback(IncludeInserter *Inserter) + : Inserter(Inserter) {} + // Implements PPCallbacks::InclusionDerective(). Records the names and source + // locations of the inclusions in the main source file being processed. + void InclusionDirective(SourceLocation HashLocation, + const Token & /*include_token*/, + StringRef FileNameRef, bool IsAngled, + CharSourceRange FileNameRange, + const FileEntry * /*IncludedFile*/, + StringRef /*SearchPath*/, StringRef /*RelativePath*/, + const Module * /*ImportedModule*/) override { + Inserter->AddInclude(FileNameRef, IsAngled, HashLocation, + FileNameRange.getEnd()); + } + +private: + IncludeInserter *Inserter; +}; + +IncludeInserter::IncludeInserter(const SourceManager &SourceMgr, + const LangOptions &LangOpts, + IncludeSorter::IncludeStyle Style) + : SourceMgr(SourceMgr), LangOpts(LangOpts), Style(Style) {} + +IncludeInserter::~IncludeInserter() {} + +std::unique_ptr IncludeInserter::CreatePPCallbacks() { + return llvm::make_unique(this); +} + +llvm::Optional +IncludeInserter::CreateIncludeInsertion(FileID FileID, StringRef Header, + bool IsAngled) { + // We assume the same Header will never be included both angled and not + // angled. + if (!InsertedHeaders[FileID].insert(Header).second) + return llvm::None; + + if (IncludeSorterByFile.find(FileID) == IncludeSorterByFile.end()) { + // This may happen if there have been no preprocessor directives in this + // file. + IncludeSorterByFile.insert(std::make_pair( + FileID, + llvm::make_unique( + &SourceMgr, &LangOpts, FileID, + SourceMgr.getFilename(SourceMgr.getLocForStartOfFile(FileID)), + Style))); + } + return IncludeSorterByFile[FileID]->CreateIncludeInsertion(Header, IsAngled); +} + +void IncludeInserter::AddInclude(StringRef file_name, bool IsAngled, + SourceLocation HashLocation, + SourceLocation end_location) { + FileID FileID = SourceMgr.getFileID(HashLocation); + if (IncludeSorterByFile.find(FileID) == IncludeSorterByFile.end()) { + IncludeSorterByFile.insert(std::make_pair( + FileID, llvm::make_unique( + &SourceMgr, &LangOpts, FileID, + SourceMgr.getFilename(HashLocation), Style))); + } + IncludeSorterByFile[FileID]->AddInclude(file_name, IsAngled, HashLocation, + end_location); +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/IncludeInserter.h b/clang-tidy/utils/IncludeInserter.h new file mode 100644 index 00000000..31f57d84 --- /dev/null +++ b/clang-tidy/utils/IncludeInserter.h @@ -0,0 +1,75 @@ +//===---------- IncludeInserter.h - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDEINSERTER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDEINSERTER_H + +#include "IncludeSorter.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/PPCallbacks.h" +#include +#include + +namespace clang { +namespace tidy { + +// IncludeInserter can be used by ClangTidyChecks in the following fashion: +// class MyCheck : public ClangTidyCheck { +// public: +// void registerPPCallbacks(CompilerInstance& Compiler) override { +// Inserter.reset(new IncludeInserter(&Compiler.getSourceManager(), +// &Compiler.getLangOpts())); +// Compiler.getPreprocessor().addPPCallbacks( +// Inserter->CreatePPCallback()); +// } +// +// void registerMatchers(ast_matchers::MatchFinder* Finder) override { ... } +// +// void check(const ast_matchers::MatchFinder::MatchResult& Result) override { +// ... +// Inserter->CreateIncludeInsertion( +// Result.SourceManager->getMainFileID(), "path/to/Header.h", +// /*IsAngled=*/false); +// ... +// } +// +// private: +// std::unique_ptr Inserter; +// }; +class IncludeInserter { +public: + IncludeInserter(const SourceManager &SourceMgr, const LangOptions &LangOpts, + IncludeSorter::IncludeStyle Style); + ~IncludeInserter(); + + // Create PPCallbacks for registration with the compiler's preprocessor. + std::unique_ptr CreatePPCallbacks(); + + // Creates a Header inclusion directive fixit. Returns None on error or + // if inclusion directive already exists. + llvm::Optional + CreateIncludeInsertion(FileID FileID, llvm::StringRef Header, bool IsAngled); + +private: + void AddInclude(StringRef file_name, bool IsAngled, + SourceLocation hash_location, SourceLocation end_location); + + llvm::DenseMap> IncludeSorterByFile; + llvm::DenseMap> InsertedHeaders; + const SourceManager &SourceMgr; + const LangOptions &LangOpts; + const IncludeSorter::IncludeStyle Style; + friend class IncludeInserterCallback; +}; + +} // namespace tidy +} // namespace clang +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDEINSERTER_H diff --git a/clang-tidy/utils/IncludeSorter.cpp b/clang-tidy/utils/IncludeSorter.cpp new file mode 100644 index 00000000..0b89e1c2 --- /dev/null +++ b/clang-tidy/utils/IncludeSorter.cpp @@ -0,0 +1,295 @@ +//===---------- IncludeSorter.cpp - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "IncludeSorter.h" +#include "clang/Lex/Lexer.h" + +namespace clang { +namespace tidy { + +namespace { + +StringRef RemoveFirstSuffix(StringRef Str, ArrayRef Suffixes) { + for (StringRef Suffix : Suffixes) { + if (Str.endswith(Suffix)) { + return Str.substr(0, Str.size() - Suffix.size()); + } + } + return Str; +} + +StringRef MakeCanonicalName(StringRef Str, IncludeSorter::IncludeStyle Style) { + // The list of suffixes to remove from source file names to get the + // "canonical" file names. + // E.g. tools/sort_includes.cc and tools/sort_includes_test.cc + // would both canonicalize to tools/sort_includes and tools/sort_includes.h + // (once canonicalized) will match as being the main include file associated + // with the source files. + if (Style == IncludeSorter::IS_LLVM) { + return RemoveFirstSuffix( + RemoveFirstSuffix(Str, {".cc", ".cpp", ".c", ".h", ".hpp"}), {"Test"}); + } + return RemoveFirstSuffix( + RemoveFirstSuffix(Str, {".cc", ".cpp", ".c", ".h", ".hpp"}), + {"_unittest", "_regtest", "_test"}); +} + +// Scan to the end of the line and return the offset of the next line. +size_t FindNextLine(const char *Text) { + size_t EOLIndex = std::strcspn(Text, "\n"); + return Text[EOLIndex] == '\0' ? EOLIndex : EOLIndex + 1; +} + +IncludeSorter::IncludeKinds +DetermineIncludeKind(StringRef CanonicalFile, StringRef IncludeFile, + bool IsAngled, IncludeSorter::IncludeStyle Style) { + // Compute the two "canonical" forms of the include's filename sans extension. + // The first form is the include's filename without ".h" or "-inl.h" at the + // end. The second form is the first form with "/public/" in the file path + // replaced by "/internal/". + if (IsAngled) { + // If the system include () ends with ".h", then it is a normal C-style + // include. Otherwise assume it is a C++-style extensionless include. + return IncludeFile.endswith(".h") ? IncludeSorter::IK_CSystemInclude + : IncludeSorter::IK_CXXSystemInclude; + } + StringRef CanonicalInclude = MakeCanonicalName(IncludeFile, Style); + if (CanonicalFile.equals(CanonicalInclude)) { + return IncludeSorter::IK_MainTUInclude; + } + if (Style == IncludeSorter::IS_Google) { + std::pair Parts = CanonicalInclude.split("/public/"); + std::string AltCanonicalInclude = + Parts.first.str() + "/internal/" + Parts.second.str(); + std::string ProtoCanonicalInclude = + Parts.first.str() + "/proto/" + Parts.second.str(); + + // Determine the kind of this inclusion. + if (CanonicalFile.equals(AltCanonicalInclude) || + CanonicalFile.equals(ProtoCanonicalInclude)) { + return IncludeSorter::IK_MainTUInclude; + } + } + return IncludeSorter::IK_NonSystemInclude; +} + +} // namespace + +IncludeSorter::IncludeSorter(const SourceManager *SourceMgr, + const LangOptions *LangOpts, const FileID FileID, + StringRef FileName, IncludeStyle Style) + : SourceMgr(SourceMgr), LangOpts(LangOpts), Style(Style), + CurrentFileID(FileID), CanonicalFile(MakeCanonicalName(FileName, Style)) { +} + +void IncludeSorter::AddInclude(StringRef FileName, bool IsAngled, + SourceLocation HashLocation, + SourceLocation EndLocation) { + int Offset = FindNextLine(SourceMgr->getCharacterData(EndLocation)); + + // Record the relevant location information for this inclusion directive. + IncludeLocations[FileName].push_back( + SourceRange(HashLocation, EndLocation.getLocWithOffset(Offset))); + SourceLocations.push_back(IncludeLocations[FileName].back()); + + // Stop if this inclusion is a duplicate. + if (IncludeLocations[FileName].size() > 1) + return; + + // Add the included file's name to the appropriate bucket. + IncludeKinds Kind = + DetermineIncludeKind(CanonicalFile, FileName, IsAngled, Style); + if (Kind != IK_InvalidInclude) + IncludeBucket[Kind].push_back(FileName.str()); +} + +Optional IncludeSorter::CreateIncludeInsertion(StringRef FileName, + bool IsAngled) { + std::string IncludeStmt = + IsAngled ? llvm::Twine("#include <" + FileName + ">\n").str() + : llvm::Twine("#include \"" + FileName + "\"\n").str(); + if (SourceLocations.empty()) { + // If there are no includes in this file, add it in the first line. + // FIXME: insert after the file comment or the header guard, if present. + IncludeStmt.append("\n"); + return FixItHint::CreateInsertion( + SourceMgr->getLocForStartOfFile(CurrentFileID), IncludeStmt); + } + + auto IncludeKind = + DetermineIncludeKind(CanonicalFile, FileName, IsAngled, Style); + + if (!IncludeBucket[IncludeKind].empty()) { + for (const std::string &IncludeEntry : IncludeBucket[IncludeKind]) { + if (FileName < IncludeEntry) { + const auto &Location = IncludeLocations[IncludeEntry][0]; + return FixItHint::CreateInsertion(Location.getBegin(), IncludeStmt); + } else if (FileName == IncludeEntry) { + return llvm::None; + } + } + // FileName comes after all include entries in bucket, insert it after + // last. + const std::string &LastInclude = IncludeBucket[IncludeKind].back(); + SourceRange LastIncludeLocation = IncludeLocations[LastInclude].back(); + return FixItHint::CreateInsertion(LastIncludeLocation.getEnd(), + IncludeStmt); + } + // Find the non-empty include bucket to be sorted directly above + // 'IncludeKind'. If such a bucket exists, we'll want to sort the include + // after that bucket. If no such bucket exists, find the first non-empty + // include bucket in the file. In that case, we'll want to sort the include + // before that bucket. + IncludeKinds NonEmptyKind = IK_InvalidInclude; + for (int i = IK_InvalidInclude - 1; i >= 0; --i) { + if (!IncludeBucket[i].empty()) { + NonEmptyKind = static_cast(i); + if (NonEmptyKind < IncludeKind) + break; + } + } + if (NonEmptyKind == IK_InvalidInclude) { + return llvm::None; + } + + if (NonEmptyKind < IncludeKind) { + // Create a block after. + const std::string &LastInclude = IncludeBucket[NonEmptyKind].back(); + SourceRange LastIncludeLocation = IncludeLocations[LastInclude].back(); + IncludeStmt = '\n' + IncludeStmt; + return FixItHint::CreateInsertion(LastIncludeLocation.getEnd(), + IncludeStmt); + } + // Create a block before. + const std::string &FirstInclude = IncludeBucket[NonEmptyKind][0]; + SourceRange FirstIncludeLocation = IncludeLocations[FirstInclude].back(); + IncludeStmt.append("\n"); + return FixItHint::CreateInsertion(FirstIncludeLocation.getBegin(), + IncludeStmt); +} + +std::vector IncludeSorter::GetEdits() { + if (SourceLocations.empty()) + return {}; + + typedef std::map> + FileLineToSourceEditMap; + FileLineToSourceEditMap Edits; + auto SourceLocationIterator = SourceLocations.begin(); + auto SourceLocationIteratorEnd = SourceLocations.end(); + + // Compute the Edits that need to be done to each line to add, replace, or + // delete inclusions. + for (int IncludeKind = 0; IncludeKind < IK_InvalidInclude; ++IncludeKind) { + std::sort(IncludeBucket[IncludeKind].begin(), + IncludeBucket[IncludeKind].end()); + for (const auto &IncludeEntry : IncludeBucket[IncludeKind]) { + auto &Location = IncludeLocations[IncludeEntry]; + SourceRangeVector::iterator LocationIterator = Location.begin(); + SourceRangeVector::iterator LocationIteratorEnd = Location.end(); + SourceRange FirstLocation = *LocationIterator; + + // If the first occurrence of a particular include is on the current + // source line we are examining, leave it alone. + if (FirstLocation == *SourceLocationIterator) + ++LocationIterator; + + // Add the deletion Edits for any (remaining) instances of this inclusion, + // and remove their Locations from the source Locations to be processed. + for (; LocationIterator != LocationIteratorEnd; ++LocationIterator) { + int LineNumber = + SourceMgr->getSpellingLineNumber(LocationIterator->getBegin()); + Edits[LineNumber] = std::make_pair(*LocationIterator, ""); + SourceLocationIteratorEnd = + std::remove(SourceLocationIterator, SourceLocationIteratorEnd, + *LocationIterator); + } + + if (FirstLocation == *SourceLocationIterator) { + // Do nothing except move to the next source Location (Location of an + // inclusion in the original, unchanged source file). + ++SourceLocationIterator; + continue; + } + + // Add (or append to) the replacement text for this line in source file. + int LineNumber = + SourceMgr->getSpellingLineNumber(SourceLocationIterator->getBegin()); + if (Edits.find(LineNumber) == Edits.end()) { + Edits[LineNumber].first = + SourceRange(SourceLocationIterator->getBegin()); + } + StringRef SourceText = Lexer::getSourceText( + CharSourceRange::getCharRange(FirstLocation), *SourceMgr, *LangOpts); + Edits[LineNumber].second.append(SourceText.data(), SourceText.size()); + } + + // Clear the bucket. + IncludeBucket[IncludeKind].clear(); + } + + // Go through the single-line Edits and combine them into blocks of Edits. + int CurrentEndLine = 0; + SourceRange CurrentRange; + std::string CurrentText; + std::vector Fixes; + for (const auto &LineEdit : Edits) { + // If the current edit is on the next line after the previous edit, add it + // to the current block edit. + if (LineEdit.first == CurrentEndLine + 1 && + CurrentRange.getBegin() != CurrentRange.getEnd()) { + SourceRange EditRange = LineEdit.second.first; + if (EditRange.getBegin() != EditRange.getEnd()) { + ++CurrentEndLine; + CurrentRange.setEnd(EditRange.getEnd()); + } + CurrentText += LineEdit.second.second; + // Otherwise report the current block edit and start a new block. + } else { + if (CurrentEndLine) { + Fixes.push_back(CreateFixIt(CurrentRange, CurrentText)); + } + + CurrentEndLine = LineEdit.first; + CurrentRange = LineEdit.second.first; + CurrentText = LineEdit.second.second; + } + } + // Finally, report the current block edit if there is one. + if (CurrentEndLine) { + Fixes.push_back(CreateFixIt(CurrentRange, CurrentText)); + } + + // Reset the remaining internal state. + SourceLocations.clear(); + IncludeLocations.clear(); + return Fixes; +} + +// Creates a fix-it for the given replacements. +// Takes the the source location that will be replaced, and the new text. +FixItHint IncludeSorter::CreateFixIt(SourceRange EditRange, + const std::string &NewText) { + FixItHint Fix; + Fix.RemoveRange = CharSourceRange::getCharRange(EditRange); + Fix.CodeToInsert = NewText; + return Fix; +} + +IncludeSorter::IncludeStyle +IncludeSorter::parseIncludeStyle(const std::string &Value) { + return Value == "llvm" ? IS_LLVM : IS_Google; +} + +StringRef IncludeSorter::toString(IncludeStyle Style) { + return Style == IS_LLVM ? "llvm" : "google"; +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/IncludeSorter.h b/clang-tidy/utils/IncludeSorter.h new file mode 100644 index 00000000..a5f71db3 --- /dev/null +++ b/clang-tidy/utils/IncludeSorter.h @@ -0,0 +1,88 @@ +//===------------ IncludeSorter.h - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDESORTER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDESORTER_H + +#include "../ClangTidy.h" +#include + +namespace clang { +namespace tidy { + +// Class used by IncludeSorterCallback and IncludeInserterCallback to record the +// names of the inclusions in a given source file being processed and generate +// the necessary commands to sort the inclusions according to the precedence +// enocded in IncludeKinds. +class IncludeSorter { +public: + // Supported include styles. + enum IncludeStyle { IS_LLVM = 0, IS_Google = 1 }; + + // Converts "llvm" to IS_LLVM, otherwise returns IS_Google. + static IncludeStyle parseIncludeStyle(const std::string &Value); + + // Converts IncludeStyle to string representation. + static StringRef toString(IncludeStyle Style); + + // The classifications of inclusions, in the order they should be sorted. + enum IncludeKinds { + IK_MainTUInclude = 0, // e.g. #include "foo.h" when editing foo.cc + IK_CSystemInclude = 1, // e.g. #include + IK_CXXSystemInclude = 2, // e.g. #include + IK_NonSystemInclude = 3, // e.g. #include "bar.h" + IK_InvalidInclude = 4 // total number of valid IncludeKinds + }; + + // IncludeSorter constructor; takes the FileID and name of the file to be + // processed by the sorter. + IncludeSorter(const SourceManager *SourceMgr, const LangOptions *LangOpts, + const FileID FileID, StringRef FileName, IncludeStyle Style); + + // Returns the SourceManager-specific file ID for the file being handled by + // the sorter. + const FileID current_FileID() const { return CurrentFileID; } + + // Adds the given #include to the sorter. + void AddInclude(StringRef FileName, bool IsAngled, + SourceLocation HashLocation, SourceLocation EndLocation); + + // Returns the edits needed to sort the current set of includes and reset the + // internal state (so that different blocks of includes are sorted separately + // within the same file). + std::vector GetEdits(); + + // Creates a quoted inclusion directive in the right sort order. Returns None + // on error or if header inclusion directive for header already exists. + Optional CreateIncludeInsertion(StringRef FileName, bool IsAngled); + +private: + typedef SmallVector SourceRangeVector; + + // Creates a fix-it for the given replacements. + // Takes the the source location that will be replaced, and the new text. + FixItHint CreateFixIt(SourceRange EditRange, const std::string &NewText); + + const SourceManager *SourceMgr; + const LangOptions *LangOpts; + const IncludeStyle Style; + FileID CurrentFileID; + // The file name stripped of common suffixes. + StringRef CanonicalFile; + // Locations of visited include directives. + SourceRangeVector SourceLocations; + // Mapping from file name to #include locations. + llvm::StringMap IncludeLocations; + // Includes sorted into buckets. + SmallVector IncludeBucket[IK_InvalidInclude]; +}; + +} // namespace tidy +} // namespace clang +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDESORTER_H diff --git a/clang-tidy/utils/LexerUtils.cpp b/clang-tidy/utils/LexerUtils.cpp new file mode 100644 index 00000000..239c0f7d --- /dev/null +++ b/clang-tidy/utils/LexerUtils.cpp @@ -0,0 +1,39 @@ +//===--- LexerUtils.cpp - clang-tidy---------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "LexerUtils.h" + +namespace clang { +namespace tidy { +namespace lexer_utils { + +Token getPreviousNonCommentToken(const ASTContext &Context, + SourceLocation Location) { + const auto &SourceManager = Context.getSourceManager(); + Token Token; + Token.setKind(tok::unknown); + Location = Location.getLocWithOffset(-1); + auto StartOfFile = + SourceManager.getLocForStartOfFile(SourceManager.getFileID(Location)); + while (Location != StartOfFile) { + Location = Lexer::GetBeginningOfToken(Location, SourceManager, + Context.getLangOpts()); + if (!Lexer::getRawToken(Location, Token, SourceManager, + Context.getLangOpts()) && + !Token.is(tok::comment)) { + break; + } + Location = Location.getLocWithOffset(-1); + } + return Token; +} + +} // namespace lexer_utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/LexerUtils.h b/clang-tidy/utils/LexerUtils.h new file mode 100644 index 00000000..0997d2f0 --- /dev/null +++ b/clang-tidy/utils/LexerUtils.h @@ -0,0 +1,29 @@ +//===--- LexerUtils.h - clang-tidy-------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_LEXER_UTILS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_LEXER_UTILS_H + +#include "clang/AST/ASTContext.h" +#include "clang/Lex/Lexer.h" + +namespace clang { +namespace tidy { +namespace lexer_utils { + +// Returns previous non-comment token skipping over any comment text or +// tok::unknown if not found. +Token getPreviousNonCommentToken(const ASTContext &Context, + SourceLocation Location); + +} // namespace lexer_utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_LEXER_UTILS_H diff --git a/clang-tidy/utils/Makefile b/clang-tidy/utils/Makefile new file mode 100644 index 00000000..b9e0337c --- /dev/null +++ b/clang-tidy/utils/Makefile @@ -0,0 +1,12 @@ +##===- clang-tidy/google/Makefile --------------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## +CLANG_LEVEL := ../../../.. +LIBRARYNAME := clangTidyUtils + +include $(CLANG_LEVEL)/Makefile diff --git a/clang-tidy/utils/Matchers.h b/clang-tidy/utils/Matchers.h new file mode 100644 index 00000000..834c06b2 --- /dev/null +++ b/clang-tidy/utils/Matchers.h @@ -0,0 +1,30 @@ +//===--- Matchers.h - clang-tidy-------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_MATCHERS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_MATCHERS_H + +#include "clang/ASTMatchers/ASTMatchers.h" +#include "TypeTraits.h" + +namespace clang { +namespace tidy { +namespace matchers { + +AST_MATCHER(QualType, isExpensiveToCopy) { + llvm::Optional IsExpensive = + type_traits::isExpensiveToCopy(Node, Finder->getASTContext()); + return IsExpensive && *IsExpensive; +} + +} // namespace matchers +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_MATCHERS_H diff --git a/clang-tidy/utils/TypeTraits.cpp b/clang-tidy/utils/TypeTraits.cpp new file mode 100644 index 00000000..6c17ee35 --- /dev/null +++ b/clang-tidy/utils/TypeTraits.cpp @@ -0,0 +1,36 @@ +//===--- TypeTraits.cpp - clang-tidy---------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TypeTraits.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclCXX.h" + +namespace clang { +namespace tidy { +namespace type_traits { + +namespace { +bool classHasTrivialCopyAndDestroy(QualType Type) { + auto *Record = Type->getAsCXXRecordDecl(); + return Record && Record->hasDefinition() && + !Record->hasNonTrivialCopyConstructor() && + !Record->hasNonTrivialDestructor(); +} +} // namespace + +llvm::Optional isExpensiveToCopy(QualType Type, ASTContext &Context) { + if (Type->isDependentType()) + return llvm::None; + return !Type.isTriviallyCopyableType(Context) && + !classHasTrivialCopyAndDestroy(Type); +} + +} // type_traits +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/TypeTraits.h b/clang-tidy/utils/TypeTraits.h new file mode 100644 index 00000000..573e61f2 --- /dev/null +++ b/clang-tidy/utils/TypeTraits.h @@ -0,0 +1,27 @@ +//===--- TypeTraits.h - clang-tidy-------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_TYPETRAITS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_TYPETRAITS_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Type.h" + +namespace clang { +namespace tidy { +namespace type_traits { + +// \brief Returns true If \c Type is expensive to copy. +llvm::Optional isExpensiveToCopy(QualType Type, ASTContext &Context); + +} // type_traits +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_TYPETRAITS_H diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 00000000..d6c2456e --- /dev/null +++ b/docs/CMakeLists.txt @@ -0,0 +1,102 @@ +if (DOXYGEN_FOUND) + if (LLVM_ENABLE_DOXYGEN) + set(abs_srcdir ${CMAKE_CURRENT_SOURCE_DIR}) + set(abs_builddir ${CMAKE_CURRENT_BINARY_DIR}) + + if (HAVE_DOT) + set(DOT ${LLVM_PATH_DOT}) + endif() + + if (LLVM_DOXYGEN_EXTERNAL_SEARCH) + set(enable_searchengine "YES") + set(searchengine_url "${LLVM_DOXYGEN_SEARCHENGINE_URL}") + set(enable_server_based_search "YES") + set(enable_external_search "YES") + set(extra_search_mappings "${LLVM_DOXYGEN_SEARCH_MAPPINGS}") + else() + set(enable_searchengine "NO") + set(searchengine_url "") + set(enable_server_based_search "NO") + set(enable_external_search "NO") + set(extra_search_mappings "") + endif() + + # If asked, configure doxygen for the creation of a Qt Compressed Help file. + if (LLVM_ENABLE_DOXYGEN_QT_HELP) + set(CLANG_TOOLS_DOXYGEN_QCH_FILENAME "org.llvm.clang.qch" CACHE STRING + "Filename of the Qt Compressed help file") + set(CLANG_TOOLS_DOXYGEN_QHP_NAMESPACE "org.llvm.clang" CACHE STRING + "Namespace under which the intermediate Qt Help Project file lives") + set(CLANG_TOOLS_DOXYGEN_QHP_CUST_FILTER_NAME "Clang ${CLANG_VERSION}" CACHE STRING + "See http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-filters") + set(CLANG_TOOLS_DOXYGEN_QHP_CUST_FILTER_ATTRS "Clang,${CLANG_VERSION}" CACHE STRING + "See http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes") + set(clang_tools_doxygen_generate_qhp "YES") + set(clang_tools_doxygen_qch_filename "${CLANG_DOXYGEN_QCH_FILENAME}") + set(clang_tools_doxygen_qhp_namespace "${CLANG_DOXYGEN_QHP_NAMESPACE}") + set(clang_tools_doxygen_qhelpgenerator_path "${LLVM_DOXYGEN_QHELPGENERATOR_PATH}") + set(clang_tools_doxygen_qhp_cust_filter_name "${CLANG_DOXYGEN_QHP_CUST_FILTER_NAME}") + set(clang_tools_doxygen_qhp_cust_filter_attrs "${CLANG_DOXYGEN_QHP_CUST_FILTER_ATTRS}") + else() + set(clang_tools_doxygen_generate_qhp "NO") + set(clang_tools_doxygen_qch_filename "") + set(clang_tools_doxygen_qhp_namespace "") + set(clang_tools_doxygen_qhelpgenerator_path "") + set(clang_tools_doxygen_qhp_cust_filter_name "") + set(clang_tools_doxygen_qhp_cust_filter_attrs "") + endif() + + option(LLVM_DOXYGEN_SVG + "Use svg instead of png files for doxygen graphs." OFF) + if (LLVM_DOXYGEN_SVG) + set(DOT_IMAGE_FORMAT "svg") + else() + set(DOT_IMAGE_FORMAT "png") + endif() + + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/doxygen.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/doxygen.cfg @ONLY) + + set(abs_top_srcdir) + set(abs_top_builddir) + set(DOT) + set(enable_searchengine) + set(searchengine_url) + set(enable_server_based_search) + set(enable_external_search) + set(extra_search_mappings) + set(clang_tools_doxygen_generate_qhp) + set(clang_tools_doxygen_qch_filename) + set(clang_tools_doxygen_qhp_namespace) + set(clang_tools_doxygen_qhelpgenerator_path) + set(clang_tools_doxygen_qhp_cust_filter_name) + set(clang_tools_doxygen_qhp_cust_filter_attrs) + set(DOT_IMAGE_FORMAT) + + add_custom_target(doxygen-clang-tools + COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxygen.cfg + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating clang doxygen documentation." VERBATIM) + + if (LLVM_BUILD_DOCS) + add_dependencies(doxygen doxygen-clang-tools) + endif() + + if (NOT LLVM_INSTALL_TOOLCHAIN_ONLY) + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doxygen/html + DESTINATION docs/html) + endif() + endif() +endif() + +if (LLVM_ENABLE_SPHINX) + if (SPHINX_FOUND) + include(AddSphinxTarget) + if (${SPHINX_OUTPUT_HTML}) + add_sphinx_target(html clang-tools) + endif() + if (${SPHINX_OUTPUT_MAN}) + add_sphinx_target(man clang-tools) + endif() + endif() +endif() diff --git a/docs/Doxyfile b/docs/Doxyfile new file mode 100644 index 00000000..d674390f --- /dev/null +++ b/docs/Doxyfile @@ -0,0 +1,1808 @@ +# Doxyfile 1.7.6.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = clang-tools-extra + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +# Same directory that Sphinx uses. +OUTPUT_DIRECTORY = ./_build/ + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 2 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +SYMBOL_CACHE_SIZE = 0 + +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. The create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ../clang-modernize ../clang-apply-replacements ../clang-tidy + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.d \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.dox \ + *.py \ + *.f90 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# none of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 4 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = clang:: + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +# This directory nicely fits with the way the Sphinx outputs html, so that +# the doxygen documentation will be visible in the doxygen/ path in the web +# output (e.g. github pages). +HTML_OUTPUT = html/doxygen/ + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the +# mathjax.org site, so you can quickly see the result without installing +# MathJax, but it is strongly recommended to install a local copy of MathJax +# before deployment. + +MATHJAX_RELPATH = http://www.mathjax.org/mathjax + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +# For now, no latex output. +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex/doxygen + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = letter + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = YES + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = NO + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..5ad028a1 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,160 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +all: doxygen html + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ExtraClangTools.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ExtraClangTools.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/ExtraClangTools" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ExtraClangTools" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +doxygen: + mkdir -p $(BUILDDIR)/html/doxygen + doxygen + diff --git a/docs/ModularizeUsage.rst b/docs/ModularizeUsage.rst new file mode 100644 index 00000000..b7c4b65c --- /dev/null +++ b/docs/ModularizeUsage.rst @@ -0,0 +1,98 @@ +================ +Modularize Usage +================ + +``modularize [] [|]* +[...]`` + +```` is a place-holder for options +specific to modularize, which are described below in +`Modularize Command Line Options`. + +```` specifies the path of a file name for an +existing module map. The module map must be well-formed in +terms of syntax. Modularize will extract the header file names +from the map. Only normal headers are checked, assuming headers +marked "private", "textual", or "exclude" are not to be checked +as a top-level include, assuming they either are included by +other headers which are checked, or they are not suitable for +modules. + +```` specifies the path of a file name for a +file containing the newline-separated list of headers to check +with respect to each other. Lines beginning with '#' and empty +lines are ignored. Header file names followed by a colon and +other space-separated file names will include those extra files +as dependencies. The file names can be relative or full paths, +but must be on the same line. For example:: + + header1.h + header2.h + header3.h: header1.h header2.h + +Note that unless a ``-prefix (header path)`` option is specified, +non-absolute file paths in the header list file will be relative +to the header list file directory. Use -prefix to specify a different +directory. + +```` is a place-holder for regular Clang +front-end arguments, which must follow the . +Note that by default, modularize assumes .h files +contain C++ source, so if you are using a different language, +you might need to use a ``-x`` option to tell Clang that the +header contains another language, i.e.: ``-x c`` + +Note also that because modularize does not use the clang driver, +you will likely need to pass in additional compiler front-end +arguments to match those passed in by default by the driver. + +Modularize Command Line Options +=============================== + +.. option:: -prefix= + + Prepend the given path to non-absolute file paths in the header list file. + By default, headers are assumed to be relative to the header list file + directory. Use ``-prefix`` to specify a different directory. + +.. option:: -module-map-path= + + Generate a module map and output it to the given file. See the description + in :ref:`module-map-generation`. + +.. option:: -problem-files-list= + + For use only with module map assistant. Input list of files that + have problems with respect to modules. These will still be + included in the generated module map, but will be marked as + "excluded" headers. + +.. option:: -root-module= + + Put modules generated by the -module-map-path option in an enclosing + module with the given name. See the description in :ref:`module-map-generation`. + +.. option:: -block-check-header-list-only + + Limit the #include-inside-extern-or-namespace-block + check to only those headers explicitly listed in the header list. + This is a work-around for avoiding error messages for private includes that + purposefully get included inside blocks. + +.. option:: -no-coverage-check + + Don't do the coverage check for a module map. + +.. option:: -coverage-check-only + + Only do the coverage check for a module map. + +.. option:: -display-file-lists + + Display lists of good files (no compile errors), problem files, + and a combined list with problem files preceded by a '#'. + This can be used to quickly determine which files have problems. + The latter combined list might be useful in starting to modularize + a set of headers. You can start with a full list of headers, + use -display-file-lists option, and then use the combined list as + your intermediate list, uncommenting-out headers as you fix them. diff --git a/docs/README.txt b/docs/README.txt new file mode 100644 index 00000000..4b607775 --- /dev/null +++ b/docs/README.txt @@ -0,0 +1,11 @@ +------------------------------------------------------------- +Documentation for the tools of clang-tools-extra repo project +------------------------------------------------------------- + +Sphinx and doxygen documentation is generated by executing make. + +Sphinx html files can be generated separately using make html. + +Doxygen html files can also be generated using make doxygen. + +The generated documentation will be placed in _build/html. diff --git a/docs/clang-modernize.rst b/docs/clang-modernize.rst new file mode 100644 index 00000000..0a429611 --- /dev/null +++ b/docs/clang-modernize.rst @@ -0,0 +1,4 @@ +:orphan: + +All :program:`clang-modernize` transforms have moved to :doc:`clang-tidy/index` +(see the ``modernize`` module). diff --git a/docs/clang-tidy.rst b/docs/clang-tidy.rst new file mode 100644 index 00000000..5fcfa7f4 --- /dev/null +++ b/docs/clang-tidy.rst @@ -0,0 +1,6 @@ +:orphan: + +.. meta:: + :http-equiv=refresh: 0;URL='http://clang.llvm.org/extra/clang-tidy/' + +clang-tidy documentation has moved here: http://clang.llvm.org/extra/clang-tidy/ diff --git a/docs/clang-tidy/checks/cert-dcl03-c.rst b/docs/clang-tidy/checks/cert-dcl03-c.rst new file mode 100644 index 00000000..cd384ff7 --- /dev/null +++ b/docs/clang-tidy/checks/cert-dcl03-c.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - cert-dcl03-c +.. meta:: + :http-equiv=refresh: 5;URL=misc-static-assert.html + +cert-dcl03-c +============ + +The cert-dcl03-c checker is an alias, please see +`misc-static-assert `_ for more information. diff --git a/docs/clang-tidy/checks/cert-dcl50-cpp.rst b/docs/clang-tidy/checks/cert-dcl50-cpp.rst new file mode 100644 index 00000000..5fc1fbfb --- /dev/null +++ b/docs/clang-tidy/checks/cert-dcl50-cpp.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - cert-dcl50-cpp + +cert-dcl50-cpp +============== + +This check flags all function definitions (but not declarations) of C-style +variadic functions. + +This check corresponds to the CERT C++ Coding Standard rule +`DCL50-CPP. Do not define a C-style variadic function +`_. diff --git a/docs/clang-tidy/checks/cert-dcl54-cpp.rst b/docs/clang-tidy/checks/cert-dcl54-cpp.rst new file mode 100644 index 00000000..7aba1e2b --- /dev/null +++ b/docs/clang-tidy/checks/cert-dcl54-cpp.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - cert-dcl54-cpp +.. meta:: + :http-equiv=refresh: 5;URL=misc-new-delete-overloads.html + +cert-dcl54-cpp +============== + +The cert-dcl54-cpp checker is an alias, please see +`misc-new-delete-overloads `_ for more +information. diff --git a/docs/clang-tidy/checks/cert-dcl59-cpp.rst b/docs/clang-tidy/checks/cert-dcl59-cpp.rst new file mode 100644 index 00000000..b8808ce9 --- /dev/null +++ b/docs/clang-tidy/checks/cert-dcl59-cpp.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - cert-dcl59-cpp +.. meta:: + :http-equiv=refresh: 5;URL=google-build-namespaces.html + +cert-dcl59-cpp +============== + +The cert-dcl59-cpp checker is an alias, please see +`google-build-namespaces `_ for more information. diff --git a/docs/clang-tidy/checks/cert-err52-cpp.rst b/docs/clang-tidy/checks/cert-err52-cpp.rst new file mode 100644 index 00000000..d8be5a8d --- /dev/null +++ b/docs/clang-tidy/checks/cert-err52-cpp.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - cert-err52-cpp + +cert-err52-cpp +============== + +This check flags all call expressions involving setjmp() and longjmp(). + +This check corresponds to the CERT C++ Coding Standard rule +`ERR52-CPP. Do not use setjmp() or longjmp() +`_. diff --git a/docs/clang-tidy/checks/cert-err58-cpp.rst b/docs/clang-tidy/checks/cert-err58-cpp.rst new file mode 100644 index 00000000..9fec67f0 --- /dev/null +++ b/docs/clang-tidy/checks/cert-err58-cpp.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - cert-err58-cpp + +cert-err58-cpp +============== + +This check flags all static or thread_local variable declarations where the +constructor for the object may throw an exception. + +This check corresponds to the CERT C++ Coding Standard rule +`ERR58-CPP. Constructors of objects with static or thread storage duration must not throw exceptions +`_. diff --git a/docs/clang-tidy/checks/cert-err60-cpp.rst b/docs/clang-tidy/checks/cert-err60-cpp.rst new file mode 100644 index 00000000..9fcb840f --- /dev/null +++ b/docs/clang-tidy/checks/cert-err60-cpp.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - cert-err60-cpp + +cert-err60-cpp +============== + +This check flags all throw expressions where the exception object is not nothrow +copy constructible. + +This check corresponds to the CERT C++ Coding Standard rule +`ERR60-CPP. Exception objects must be nothrow copy constructible +`_. diff --git a/docs/clang-tidy/checks/cert-err61-cpp.rst b/docs/clang-tidy/checks/cert-err61-cpp.rst new file mode 100644 index 00000000..2657836d --- /dev/null +++ b/docs/clang-tidy/checks/cert-err61-cpp.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - cert-err61-cpp +.. meta:: + :http-equiv=refresh: 5;URL=misc-throw-by-value-catch-by-reference.html + +cert-err61-cpp +============== + +The cert-err61-cpp checker is an alias, please see +`misc-throw-by-value-catch-by-reference `_ +for more information. diff --git a/docs/clang-tidy/checks/cert-fio38-c.rst b/docs/clang-tidy/checks/cert-fio38-c.rst new file mode 100644 index 00000000..3202790f --- /dev/null +++ b/docs/clang-tidy/checks/cert-fio38-c.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - cert-fio38-c +.. meta:: + :http-equiv=refresh: 5;URL=misc-non-copyable-objects.html + +cert-fio38-c +============ + +The cert-fio38-c checker is an alias, please see +`misc-non-copyable-objects `_ for more +information. diff --git a/docs/clang-tidy/checks/cert-oop11-cpp.rst b/docs/clang-tidy/checks/cert-oop11-cpp.rst new file mode 100644 index 00000000..cc72d388 --- /dev/null +++ b/docs/clang-tidy/checks/cert-oop11-cpp.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - cert-oop11-cpp +.. meta:: + :http-equiv=refresh: 5;URL=misc-move-constructor-init.html + +cert-oop11-cpp +============== + +The cert-oop11-cpp checker is an alias, please see +`misc-move-constructor-init `_ for more +information. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-array-to-pointer-decay.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-array-to-pointer-decay.rst new file mode 100644 index 00000000..172df2ba --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-array-to-pointer-decay.rst @@ -0,0 +1,12 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-bounds-array-to-pointer-decay + +cppcoreguidelines-pro-bounds-array-to-pointer-decay +=================================================== + +This check flags all array to pointer decays. + +Pointers should not be used as arrays. ``span`` is a bounds-checked, safe +alternative to using pointers to access arrays. + +This rule is part of the "Bounds safety" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-bounds-decay. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-constant-array-index.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-constant-array-index.rst new file mode 100644 index 00000000..dd20a52a --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-constant-array-index.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-bounds-constant-array-index + +cppcoreguidelines-pro-bounds-constant-array-index +================================================= + +This check flags all array subscript expressions on static arrays and +``std::arrays`` that either do not have a constant integer expression index or +are out of bounds (for ``std::array``). For out-of-bounds checking of static +arrays, see the clang-diagnostic-array-bounds check. + +The check can generate fixes after the option +``cppcoreguidelines-pro-bounds-constant-array-index.GslHeader`` has been set to +the name of the include file that contains ``gsl::at()``, e.g. ``"gsl/gsl.h"``. + +This rule is part of the "Bounds safety" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-bounds-arrayindex. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-pointer-arithmetic.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-pointer-arithmetic.rst new file mode 100644 index 00000000..e0660df2 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-pointer-arithmetic.rst @@ -0,0 +1,14 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-bounds-pointer-arithmetic + +cppcoreguidelines-pro-bounds-pointer-arithmetic +=============================================== + +This check flags all usage of pointer arithmetic, because it could lead to an +invalid pointer. Subtraction of two pointers is not flagged by this check. + +Pointers should only refer to single objects, and pointer arithmetic is fragile +and easy to get wrong. ``span`` is a bounds-checked, safe type for accessing +arrays of data. + +This rule is part of the "Bounds safety" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-bounds-arithmetic. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-type-const-cast.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-const-cast.rst new file mode 100644 index 00000000..f3f0fb4a --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-const-cast.rst @@ -0,0 +1,12 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-type-const-cast + +cppcoreguidelines-pro-type-const-cast +===================================== + +This check flags all uses of ``const_cast`` in C++ code. + +Modifying a variable that was declared const is undefined behavior, even with +``const_cast``. + +This rule is part of the "Type safety" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-type-constcast. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-type-cstyle-cast.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-cstyle-cast.rst new file mode 100644 index 00000000..31ba7867 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-cstyle-cast.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-type-cstyle-cast + +cppcoreguidelines-pro-type-cstyle-cast +====================================== + +This check flags all use of C-style casts that perform a ``static_cast`` +downcast, ``const_cast``, or ``reinterpret_cast``. + +Use of these casts can violate type safety and cause the program to access a +variable that is actually of type X to be accessed as if it were of an unrelated +type Z. Note that a C-style ``(T)expression`` cast means to perform the first of +the following that is possible: a ``const_cast``, a ``static_cast``, a +``static_cast`` followed by a ``const_cast``, a ``reinterpret_cast``, or a +``reinterpret_cast`` followed by a ``const_cast``. This rule bans +``(T)expression`` only when used to perform an unsafe cast. + +This rule is part of the "Type safety" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-type-cstylecast. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-type-reinterpret-cast.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-reinterpret-cast.rst new file mode 100644 index 00000000..2ef76f25 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-reinterpret-cast.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-type-reinterpret-cast + +cppcoreguidelines-pro-type-reinterpret-cast +=========================================== + +This check flags all uses of ``reinterpret_cast`` in C++ code. + +Use of these casts can violate type safety and cause the program to access a +variable that is actually of type ``X`` to be accessed as if it were of an +unrelated type ``Z``. + +This rule is part of the "Type safety" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-type-reinterpretcast. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-type-static-cast-downcast.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-static-cast-downcast.rst new file mode 100644 index 00000000..1d29af51 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-static-cast-downcast.rst @@ -0,0 +1,15 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-type-static-cast-downcast + +cppcoreguidelines-pro-type-static-cast-downcast +=============================================== + +This check flags all usages of ``static_cast``, where a base class is casted to +a derived class. In those cases, a fixit is provided to convert the cast to a +``dynamic_cast``. + +Use of these casts can violate type safety and cause the program to access a +variable that is actually of type ``X`` to be accessed as if it were of an +unrelated type ``Z``. + +This rule is part of the "Type safety" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-type-downcast. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-type-union-access.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-union-access.rst new file mode 100644 index 00000000..cdcf7138 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-union-access.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-type-union-access + +cppcoreguidelines-pro-type-union-access +======================================= + +This check flags all access to members of unions. Passing unions as a whole is +not flagged. + +Reading from a union member assumes that member was the last one written, and +writing to a union member assumes another member with a nontrivial destructor +had its destructor called. This is fragile because it cannot generally be +enforced to be safe in the language and so relies on programmer discipline to +get it right. + +This rule is part of the "Type safety" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-type-unions. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-pro-type-vararg.rst b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-vararg.rst new file mode 100644 index 00000000..26fcd0c6 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-pro-type-vararg.rst @@ -0,0 +1,17 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-type-vararg + +cppcoreguidelines-pro-type-vararg +================================= + +This check flags all calls to c-style vararg functions and all use of +``va_arg``. + +To allow for SFINAE use of vararg functions, a call is not flagged if a literal +0 is passed as the only vararg argument. + +Passing to varargs assumes the correct type will be read. This is fragile +because it cannot generally be enforced to be safe in the language and so relies +on programmer discipline to get it right. + +This rule is part of the "Type safety" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-type-varargs. diff --git a/docs/clang-tidy/checks/google-build-explicit-make-pair.rst b/docs/clang-tidy/checks/google-build-explicit-make-pair.rst new file mode 100644 index 00000000..8069932e --- /dev/null +++ b/docs/clang-tidy/checks/google-build-explicit-make-pair.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - google-build-explicit-make-pair + +google-build-explicit-make-pair +=============================== + +Check that ``make_pair``'s template arguments are deduced. + +G++ 4.6 in C++11 mode fails badly if ``make_pair``'s template arguments are +specified explicitly, and such use isn't intended in any case. + +Corresponding cpplint.py check name: 'build/explicit_make_pair'. diff --git a/docs/clang-tidy/checks/google-build-namespaces.rst b/docs/clang-tidy/checks/google-build-namespaces.rst new file mode 100644 index 00000000..63718a69 --- /dev/null +++ b/docs/clang-tidy/checks/google-build-namespaces.rst @@ -0,0 +1,12 @@ +.. title:: clang-tidy - google-build-namespaces + +google-build-namespaces +======================= + +"cert-dcl59-cpp" redirects here as an alias for this checker. + +Finds anonymous namespaces in headers. + +http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Namespaces#Namespaces + +Corresponding cpplint.py check name: 'build/namespaces'. diff --git a/docs/clang-tidy/checks/google-build-using-namespace.rst b/docs/clang-tidy/checks/google-build-using-namespace.rst new file mode 100644 index 00000000..9beb858d --- /dev/null +++ b/docs/clang-tidy/checks/google-build-using-namespace.rst @@ -0,0 +1,21 @@ +.. title:: clang-tidy - google-build-using-namespace + +google-build-using-namespace +============================ + + +Finds using namespace directives. + +http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Namespaces#Namespaces + +The check implements the following rule of the Google C++ Style Guide: + + You may not use a using-directive to make all names from a namespace + available. + + .. code:: c++ + + // Forbidden -- This pollutes the namespace. + using namespace foo; + +Corresponding cpplint.py check name: ``build/namespaces``. diff --git a/docs/clang-tidy/checks/google-explicit-constructor.rst b/docs/clang-tidy/checks/google-explicit-constructor.rst new file mode 100644 index 00000000..4d6fb104 --- /dev/null +++ b/docs/clang-tidy/checks/google-explicit-constructor.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - google-explicit-constructor + +google-explicit-constructor +=========================== + + +Checks that all single-argument constructors are explicit. + +See http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Explicit_Constructors diff --git a/docs/clang-tidy/checks/google-global-names-in-headers.rst b/docs/clang-tidy/checks/google-global-names-in-headers.rst new file mode 100644 index 00000000..8cdd2f5b --- /dev/null +++ b/docs/clang-tidy/checks/google-global-names-in-headers.rst @@ -0,0 +1,8 @@ +.. title:: clang-tidy - google-global-names-in-headers + +google-global-names-in-headers +============================== + + +Flag global namespace pollution in header files. +Right now it only triggers on using declarations and directives. diff --git a/docs/clang-tidy/checks/google-readability-braces-around-statements.rst b/docs/clang-tidy/checks/google-readability-braces-around-statements.rst new file mode 100644 index 00000000..22dfd07d --- /dev/null +++ b/docs/clang-tidy/checks/google-readability-braces-around-statements.rst @@ -0,0 +1,31 @@ +.. title:: clang-tidy - google-readability-braces-around-statements + +google-readability-braces-around-statements +=========================================== + + +Checks that bodies of ``if`` statements and loops (``for``, ``range-for``, +``do-while``, and ``while``) are inside braces + +Before: + +.. code:: c++ + + if (condition) + statement; + +After: + +.. code:: c++ + + if (condition) { + statement; + } + +Additionally, one can define an option ``ShortStatementLines`` defining the +minimal number of lines that the statement should have in order to trigger +this check. + +The number of lines is counted from the end of condition or initial keyword +(``do``/``else``) until the last line of the inner statement. Default value 0 +means that braces will be added to all statements (not having them already). diff --git a/docs/clang-tidy/checks/google-readability-casting.rst b/docs/clang-tidy/checks/google-readability-casting.rst new file mode 100644 index 00000000..7b1e08aa --- /dev/null +++ b/docs/clang-tidy/checks/google-readability-casting.rst @@ -0,0 +1,15 @@ +.. title:: clang-tidy - google-readability-casting + +google-readability-casting +========================== + + +Finds usages of C-style casts. + +http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Casting#Casting + +Corresponding cpplint.py check name: 'readability/casting'. + +This check is similar to ``-Wold-style-cast``, but it suggests automated fixes +in some cases. The reported locations should not be different from the +ones generated by ``-Wold-style-cast``. diff --git a/docs/clang-tidy/checks/google-readability-function-size.rst b/docs/clang-tidy/checks/google-readability-function-size.rst new file mode 100644 index 00000000..e542e356 --- /dev/null +++ b/docs/clang-tidy/checks/google-readability-function-size.rst @@ -0,0 +1,17 @@ +.. title:: clang-tidy - google-readability-function-size + +google-readability-function-size +================================ + + +Checks for large functions based on various metrics. + +These options are supported: + + * ``LineThreshold`` - flag functions exceeding this number of lines. The + default is ``-1`` (ignore the number of lines). + * ``StatementThreshold`` - flag functions exceeding this number of + statements. This may differ significantly from the number of lines for + macro-heavy code. The default is ``800``. + * ``BranchThreshold`` - flag functions exceeding this number of control + statements. The default is ``-1`` (ignore the number of branches). diff --git a/docs/clang-tidy/checks/google-readability-namespace-comments.rst b/docs/clang-tidy/checks/google-readability-namespace-comments.rst new file mode 100644 index 00000000..0bb5b20c --- /dev/null +++ b/docs/clang-tidy/checks/google-readability-namespace-comments.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - google-readability-namespace-comments + +google-readability-namespace-comments +===================================== + + +Checks that long namespaces have a closing comment. + +http://llvm.org/docs/CodingStandards.html#namespace-indentation + +http://google-styleguide.googlecode.com/svn/trunk/cppguide.html#Namespaces diff --git a/docs/clang-tidy/checks/google-readability-redundant-smartptr-get.rst b/docs/clang-tidy/checks/google-readability-redundant-smartptr-get.rst new file mode 100644 index 00000000..ec286266 --- /dev/null +++ b/docs/clang-tidy/checks/google-readability-redundant-smartptr-get.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - google-readability-redundant-smartptr-get + +google-readability-redundant-smartptr-get +========================================= + + +Find and remove redundant calls to smart pointer's ``.get()`` method. + +Examples: + +.. code:: c++ + + ptr.get()->Foo() ==> ptr->Foo() + *ptr.get() ==> *ptr + *ptr->get() ==> **ptr + diff --git a/docs/clang-tidy/checks/google-readability-todo.rst b/docs/clang-tidy/checks/google-readability-todo.rst new file mode 100644 index 00000000..16c9b165 --- /dev/null +++ b/docs/clang-tidy/checks/google-readability-todo.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - google-readability-todo + +google-readability-todo +======================= + + +Finds TODO comments without a username or bug number. + +Corresponding cpplint.py check: 'readability/todo' diff --git a/docs/clang-tidy/checks/google-runtime-int.rst b/docs/clang-tidy/checks/google-runtime-int.rst new file mode 100644 index 00000000..181d2dec --- /dev/null +++ b/docs/clang-tidy/checks/google-runtime-int.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - google-runtime-int + +google-runtime-int +================== + + +Finds uses of ``short``, ``long`` and ``long long`` and suggest replacing them +with ``u?intXX(_t)?``. + +The corresponding style guide rule: +https://google-styleguide.googlecode.com/svn/trunk/cppguide.html#Integer_Types. + +Correspondig cpplint.py check: 'runtime/int'. diff --git a/docs/clang-tidy/checks/google-runtime-member-string-references.rst b/docs/clang-tidy/checks/google-runtime-member-string-references.rst new file mode 100644 index 00000000..26e889bb --- /dev/null +++ b/docs/clang-tidy/checks/google-runtime-member-string-references.rst @@ -0,0 +1,27 @@ +.. title:: clang-tidy - google-runtime-member-string-references + +google-runtime-member-string-references +======================================= + + +Finds members of type ``const string&``. + +const string reference members are generally considered unsafe as they can +be created from a temporary quite easily. + +.. code:: c++ + + struct S { + S(const string &Str) : Str(Str) {} + const string &Str; + }; + S instance("string"); + +In the constructor call a string temporary is created from ``const char *`` +and destroyed immediately after the call. This leaves around a dangling +reference. + +This check emit warnings for both ``std::string`` and ``::string`` const +reference members. + +Corresponding cpplint.py check name: 'runtime/member_string_reference'. diff --git a/docs/clang-tidy/checks/google-runtime-memset.rst b/docs/clang-tidy/checks/google-runtime-memset.rst new file mode 100644 index 00000000..63a679c1 --- /dev/null +++ b/docs/clang-tidy/checks/google-runtime-memset.rst @@ -0,0 +1,12 @@ +.. title:: clang-tidy - google-runtime-memset + +google-runtime-memset +===================== + + +Finds calls to memset with a literal zero in the length argument. + +This is most likely unintended and the length and value arguments are +swapped. + +Corresponding cpplint.py check name: 'runtime/memset'. diff --git a/docs/clang-tidy/checks/google-runtime-operator.rst b/docs/clang-tidy/checks/google-runtime-operator.rst new file mode 100644 index 00000000..2906f0e3 --- /dev/null +++ b/docs/clang-tidy/checks/google-runtime-operator.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - google-runtime-operator + +google-runtime-operator +======================= + + +Finds overloads of unary ``operator &``. + +http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Operator_Overloading#Operator_Overloading + +Corresponding cpplint.py check name: 'runtime/operator'. diff --git a/docs/clang-tidy/checks/list.rst b/docs/clang-tidy/checks/list.rst new file mode 100644 index 00000000..011e5860 --- /dev/null +++ b/docs/clang-tidy/checks/list.rst @@ -0,0 +1,90 @@ +.. title:: clang-tidy - Clang-Tidy Checks + +Clang-Tidy Checks +========================= + +.. toctree:: + cert-dcl03-c (redirects to misc-static-assert) + cert-dcl50-cpp + cert-dcl54-cpp (redirects to misc-new-delete-overloads) + cert-dcl59-cpp (redirects to google-build-namespaces) + cert-err52-cpp + cert-err58-cpp + cert-err60-cpp + cert-err61-cpp (redirects to misc-throw-by-value-catch-by-reference) + cert-fio38-c (redirects to misc-non-copyable-objects) + cert-oop11-cpp (redirects to misc-move-constructor-init) + cppcoreguidelines-pro-bounds-array-to-pointer-decay + cppcoreguidelines-pro-bounds-constant-array-index + cppcoreguidelines-pro-bounds-pointer-arithmetic + cppcoreguidelines-pro-type-const-cast + cppcoreguidelines-pro-type-cstyle-cast + cppcoreguidelines-pro-type-reinterpret-cast + cppcoreguidelines-pro-type-static-cast-downcast + cppcoreguidelines-pro-type-union-access + cppcoreguidelines-pro-type-vararg + google-build-explicit-make-pair + google-build-namespaces + google-build-using-namespace + google-explicit-constructor + google-global-names-in-headers + google-readability-braces-around-statements + google-readability-casting + google-readability-function-size + google-readability-namespace-comments + google-readability-redundant-smartptr-get + google-readability-todo + google-runtime-int + google-runtime-member-string-references + google-runtime-memset + google-runtime-operator + llvm-header-guard + llvm-include-order + llvm-namespace-comment + llvm-twine-local + misc-argument-comment + misc-assert-side-effect + misc-assign-operator-signature + misc-bool-pointer-implicit-conversion + misc-definitions-in-headers + misc-inaccurate-erase + misc-inefficient-algorithm + misc-macro-parentheses + misc-macro-repeated-side-effects + misc-move-constructor-init + misc-new-delete-overloads + misc-noexcept-move-constructor + misc-non-copyable-objects + misc-sizeof-container + misc-static-assert + misc-string-integer-assignment + misc-swapped-arguments + misc-throw-by-value-catch-by-reference + misc-undelegated-constructor + misc-uniqueptr-reset-release + misc-unused-alias-decls + misc-unused-parameters + misc-unused-raii + misc-virtual-near-miss + modernize-loop-convert + modernize-make-unique + modernize-pass-by-value + modernize-redundant-void-arg + modernize-replace-auto-ptr + modernize-shrink-to-fit + modernize-use-auto + modernize-use-default + modernize-use-nullptr + modernize-use-override + readability-braces-around-statements + readability-container-size-empty + readability-else-after-return + readability-function-size + readability-identifier-naming + readability-implicit-bool-cast + readability-inconsistent-declaration-parameter-name + readability-named-parameter + readability-redundant-smartptr-get + readability-redundant-string-cstr + readability-simplify-boolean-expr + readability-uniqueptr-delete-release diff --git a/docs/clang-tidy/checks/llvm-header-guard.rst b/docs/clang-tidy/checks/llvm-header-guard.rst new file mode 100644 index 00000000..1643dfab --- /dev/null +++ b/docs/clang-tidy/checks/llvm-header-guard.rst @@ -0,0 +1,6 @@ +.. title:: clang-tidy - llvm-header-guard + +llvm-header-guard +================= + +TODO: add docs diff --git a/docs/clang-tidy/checks/llvm-include-order.rst b/docs/clang-tidy/checks/llvm-include-order.rst new file mode 100644 index 00000000..dba98376 --- /dev/null +++ b/docs/clang-tidy/checks/llvm-include-order.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - llvm-include-order + +llvm-include-order +================== + + +Checks the correct order of ``#includes``. + +See http://llvm.org/docs/CodingStandards.html#include-style diff --git a/docs/clang-tidy/checks/llvm-namespace-comment.rst b/docs/clang-tidy/checks/llvm-namespace-comment.rst new file mode 100644 index 00000000..ac44b0ca --- /dev/null +++ b/docs/clang-tidy/checks/llvm-namespace-comment.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - llvm-namespace-comment + +llvm-namespace-comment +====================== + + +Checks that long namespaces have a closing comment. + +http://llvm.org/docs/CodingStandards.html#namespace-indentation + +http://google-styleguide.googlecode.com/svn/trunk/cppguide.html#Namespaces diff --git a/docs/clang-tidy/checks/llvm-twine-local.rst b/docs/clang-tidy/checks/llvm-twine-local.rst new file mode 100644 index 00000000..5ce0302b --- /dev/null +++ b/docs/clang-tidy/checks/llvm-twine-local.rst @@ -0,0 +1,8 @@ +.. title:: clang-tidy - llvm-twine-local + +llvm-twine-local +================ + + +Looks for local ``Twine`` variables which are prone to use after frees and +should be generally avoided. diff --git a/docs/clang-tidy/checks/misc-argument-comment.rst b/docs/clang-tidy/checks/misc-argument-comment.rst new file mode 100644 index 00000000..872d4783 --- /dev/null +++ b/docs/clang-tidy/checks/misc-argument-comment.rst @@ -0,0 +1,20 @@ +.. title:: clang-tidy - misc-argument-comment + +misc-argument-comment +===================== + + +Checks that argument comments match parameter names. + +The check understands argument comments in the form ``/*parameter_name=*/`` +that are placed right before the argument. + +.. code:: c++ + + void f(bool foo); + + ... + f(/*bar=*/true); + // warning: argument name 'bar' in comment does not match parameter name 'foo' + +The check tries to detect typos and suggest automated fixes for them. diff --git a/docs/clang-tidy/checks/misc-assert-side-effect.rst b/docs/clang-tidy/checks/misc-assert-side-effect.rst new file mode 100644 index 00000000..798b6204 --- /dev/null +++ b/docs/clang-tidy/checks/misc-assert-side-effect.rst @@ -0,0 +1,19 @@ +.. title:: clang-tidy - misc-assert-side-effect + +misc-assert-side-effect +======================= + + +Finds ``assert()`` with side effect. + +The condition of ``assert()`` is evaluated only in debug builds so a +condition with side effect can cause different behavior in debug / release +builds. + +There are two options: + + - ``AssertMacros``: A comma-separated list of the names of assert macros to + be checked. + - ``CheckFunctionCalls``: Whether to treat non-const member and non-member + functions as they produce side effects. Disabled by default because it + can increase the number of false positive warnings. diff --git a/docs/clang-tidy/checks/misc-assign-operator-signature.rst b/docs/clang-tidy/checks/misc-assign-operator-signature.rst new file mode 100644 index 00000000..dc34e118 --- /dev/null +++ b/docs/clang-tidy/checks/misc-assign-operator-signature.rst @@ -0,0 +1,12 @@ +.. title:: clang-tidy - misc-assign-operator-signature + +misc-assign-operator-signature +============================== + + +Finds declarations of assign operators with the wrong return and/or argument +types. + + * The return type must be ``Class&``. + * Works with move-assign and assign by value. + * Private and deleted operators are ignored. diff --git a/docs/clang-tidy/checks/misc-bool-pointer-implicit-conversion.rst b/docs/clang-tidy/checks/misc-bool-pointer-implicit-conversion.rst new file mode 100644 index 00000000..85509c23 --- /dev/null +++ b/docs/clang-tidy/checks/misc-bool-pointer-implicit-conversion.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - misc-bool-pointer-implicit-conversion + +misc-bool-pointer-implicit-conversion +===================================== + + +Checks for conditions based on implicit conversion from a bool pointer to +bool. + +Example: + +.. code:: c++ + + bool *p; + if (p) { + // Never used in a pointer-specific way. + } + diff --git a/docs/clang-tidy/checks/misc-definitions-in-headers.rst b/docs/clang-tidy/checks/misc-definitions-in-headers.rst new file mode 100644 index 00000000..dae2cc9e --- /dev/null +++ b/docs/clang-tidy/checks/misc-definitions-in-headers.rst @@ -0,0 +1,39 @@ +misc-definitions-in-headers +=========================== + +Finds non-extern non-inline function and variable definitions in header files, +which can lead to potential ODR violations. + +.. code:: c++ + + // Foo.h + int a = 1; // Warning. + extern int d; // OK: extern variable. + + namespace N { + int e = 2; // Warning. + } + + // Internal linkage variable definitions are ignored for now. + // Although these might also cause ODR violations, we can be less certain and + // should try to keep the false-positive rate down. + static int b = 1; + const int c = 1; + + // Warning. + int g() { + return 1; + } + + // OK: inline function definition. + inline int e() { + return 1; + } + + class A { + public: + int f1() { return 1; } // OK: inline member function definition. + int f2(); + }; + + int A::f2() { return 1; } // Warning. diff --git a/docs/clang-tidy/checks/misc-inaccurate-erase.rst b/docs/clang-tidy/checks/misc-inaccurate-erase.rst new file mode 100644 index 00000000..f55bfa73 --- /dev/null +++ b/docs/clang-tidy/checks/misc-inaccurate-erase.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - misc-inaccurate-erase + +misc-inaccurate-erase +===================== + + +Checks for inaccurate use of the ``erase()`` method. + +Algorithms like ``remove()`` do not actually remove any element from the +container but return an iterator to the first redundant element at the end +of the container. These redundant elements must be removed using the +``erase()`` method. This check warns when not all of the elements will be +removed due to using an inappropriate overload. diff --git a/docs/clang-tidy/checks/misc-inefficient-algorithm.rst b/docs/clang-tidy/checks/misc-inefficient-algorithm.rst new file mode 100644 index 00000000..c61db68e --- /dev/null +++ b/docs/clang-tidy/checks/misc-inefficient-algorithm.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - misc-inefficient-algorithm + +misc-inefficient-algorithm +========================== + + +Warns on inefficient use of STL algorithms on associative containers. + +Associative containers implements some of the algorithms as methods which +should be preferred to the algorithms in the algorithm header. The methods +can take advanatage of the order of the elements. diff --git a/docs/clang-tidy/checks/misc-macro-parentheses.rst b/docs/clang-tidy/checks/misc-macro-parentheses.rst new file mode 100644 index 00000000..6120170b --- /dev/null +++ b/docs/clang-tidy/checks/misc-macro-parentheses.rst @@ -0,0 +1,19 @@ +.. title:: clang-tidy - misc-macro-parentheses + +misc-macro-parentheses +====================== + + +Finds macros that can have unexpected behaviour due to missing parentheses. + +Macros are expanded by the preprocessor as-is. As a result, there can be +unexpected behaviour; operators may be evaluated in unexpected order and +unary operators may become binary operators, etc. + +When the replacement list has an expression, it is recommended to surround +it with parentheses. This ensures that the macro result is evaluated +completely before it is used. + +It is also recommended to surround macro arguments in the replacement list +with parentheses. This ensures that the argument value is calculated +properly. diff --git a/docs/clang-tidy/checks/misc-macro-repeated-side-effects.rst b/docs/clang-tidy/checks/misc-macro-repeated-side-effects.rst new file mode 100644 index 00000000..7cd3781e --- /dev/null +++ b/docs/clang-tidy/checks/misc-macro-repeated-side-effects.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - misc-macro-repeated-side-effects + +misc-macro-repeated-side-effects +================================ + + +Checks for repeated argument with side effects in macros. diff --git a/docs/clang-tidy/checks/misc-move-constructor-init.rst b/docs/clang-tidy/checks/misc-move-constructor-init.rst new file mode 100644 index 00000000..85e99a45 --- /dev/null +++ b/docs/clang-tidy/checks/misc-move-constructor-init.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - misc-move-constructor-init + +misc-move-constructor-init +========================== + +"cert-oop11-cpp" redirects here as an alias for this checker. + +The check flags user-defined move constructors that have a ctor-initializer +initializing a member or base class through a copy constructor instead of a +move constructor. + +It also flags constructor arguments that are passed by value, have a non-deleted +move-constructor and are assigned to a class field by copy construction. diff --git a/docs/clang-tidy/checks/misc-new-delete-overloads.rst b/docs/clang-tidy/checks/misc-new-delete-overloads.rst new file mode 100644 index 00000000..ba596091 --- /dev/null +++ b/docs/clang-tidy/checks/misc-new-delete-overloads.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - misc-new-delete-overloads + +misc-new-delete-overloads +========================= + +"cert-dcl54-cpp" redirects here as an alias for this checker. + +The check flags overloaded operator new() and operator delete() functions that +do not have a corresponding free store function defined within the same scope. +For instance, the check will flag a class implementation of a non-placement +operator new() when the class does not also define a non-placement operator +delete() function as well. + +The check does not flag implicitly-defined operators, deleted or private +operators, or placement operators. + +This check corresponds to CERT C++ Coding Standard rule `DCL54-CPP. Overload allocation and deallocation functions as a pair in the same scope +`_. diff --git a/docs/clang-tidy/checks/misc-noexcept-move-constructor.rst b/docs/clang-tidy/checks/misc-noexcept-move-constructor.rst new file mode 100644 index 00000000..9d3d4f79 --- /dev/null +++ b/docs/clang-tidy/checks/misc-noexcept-move-constructor.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - misc-noexcept-move-constructor + +misc-noexcept-move-constructor +============================== + + +The check flags user-defined move constructors and assignment operators not +marked with ``noexcept`` or marked with ``noexcept(expr)`` where ``expr`` +evaluates to ``false`` (but is not a ``false`` literal itself). + +Move constructors of all the types used with STL containers, for example, +need to be declared ``noexcept``. Otherwise STL will choose copy constructors +instead. The same is valid for move assignment operations. diff --git a/docs/clang-tidy/checks/misc-non-copyable-objects.rst b/docs/clang-tidy/checks/misc-non-copyable-objects.rst new file mode 100644 index 00000000..3f3f29eb --- /dev/null +++ b/docs/clang-tidy/checks/misc-non-copyable-objects.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - misc-non-copyable-objects + +misc-non-copyable-objects +========================= + +"cert-fio38-c" redirects here as an alias for this checker. + +The check flags dereferences and non-pointer declarations of objects that are +not meant to be passed by value, such as C FILE objects or POSIX +pthread_mutex_t objects. + +This check corresponds to CERT C++ Coding Standard rule `FIO38-C. Do not copy a FILE object +`_. diff --git a/docs/clang-tidy/checks/misc-sizeof-container.rst b/docs/clang-tidy/checks/misc-sizeof-container.rst new file mode 100644 index 00000000..1d62abff --- /dev/null +++ b/docs/clang-tidy/checks/misc-sizeof-container.rst @@ -0,0 +1,27 @@ +.. title:: clang-tidy - misc-sizeof-container + +misc-sizeof-container +===================== + +The check finds usages of ``sizeof`` on expressions of STL container types. Most +likely the user wanted to use ``.size()`` instead. + +All class/struct types declared in namespace ``std::`` having a const ``size()`` +method are considered containers, with the exception of ``std::bitset`` and +``std::array``. + +Examples: + +.. code:: c++ + + std::string s; + int a = 47 + sizeof(s); // warning: sizeof() doesn't return the size of the container. Did you mean .size()? + + int b = sizeof(std::string); // no warning, probably intended. + + std::string array_of_strings[10]; + int c = sizeof(array_of_strings) / sizeof(array_of_strings[0]); // no warning, definitely intended. + + std::array std_array; + int d = sizeof(std_array); // no warning, probably intended. + diff --git a/docs/clang-tidy/checks/misc-static-assert.rst b/docs/clang-tidy/checks/misc-static-assert.rst new file mode 100644 index 00000000..0002bdd3 --- /dev/null +++ b/docs/clang-tidy/checks/misc-static-assert.rst @@ -0,0 +1,12 @@ +.. title:: clang-tidy - misc-static-assert + +misc-static-assert +================== + +"cert-dcl03-c" redirects here as an alias for this checker. + +Replaces ``assert()`` with ``static_assert()`` if the condition is evaluatable +at compile time. + +The condition of ``static_assert()`` is evaluated at compile time which is +safer and more efficient. diff --git a/docs/clang-tidy/checks/misc-string-integer-assignment.rst b/docs/clang-tidy/checks/misc-string-integer-assignment.rst new file mode 100644 index 00000000..faafcd99 --- /dev/null +++ b/docs/clang-tidy/checks/misc-string-integer-assignment.rst @@ -0,0 +1,37 @@ +.. title:: clang-tidy - misc-string-integer-assignment + +misc-string-integer-assignment +============================== + +The check finds assignments of an integer to ``std::basic_string`` +(``std::string``, ``std::wstring``, etc.). The source of the problem is the +following assignment operator of ``std::basic_string``: + +.. code:: c++ + + basic_string& operator=( CharT ch ); + +Numeric types can be implicity casted to character types. + +.. code:: c++ + + std::string s; + int x = 5965; + s = 6; + s = x; + +Use the appropriate conversion functions or character literals. + +.. code:: c++ + + std::string s; + int x = 5965; + s = '6'; + s = std::to_string(x); + +In order to suppress false positives, use an explicit cast. + +.. code:: c++ + + std::string s; + s = static_cast(6); diff --git a/docs/clang-tidy/checks/misc-swapped-arguments.rst b/docs/clang-tidy/checks/misc-swapped-arguments.rst new file mode 100644 index 00000000..67a604a1 --- /dev/null +++ b/docs/clang-tidy/checks/misc-swapped-arguments.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - misc-swapped-arguments + +misc-swapped-arguments +====================== + + +Finds potentially swapped arguments by looking at implicit conversions. diff --git a/docs/clang-tidy/checks/misc-throw-by-value-catch-by-reference.rst b/docs/clang-tidy/checks/misc-throw-by-value-catch-by-reference.rst new file mode 100644 index 00000000..388d704d --- /dev/null +++ b/docs/clang-tidy/checks/misc-throw-by-value-catch-by-reference.rst @@ -0,0 +1,15 @@ +.. title:: clang-tidy - misc-throw-by-value-catch-by-reference + +misc-throw-by-value-catch-by-reference +====================================== + +"cert-err61-cpp" redirects here as an alias for this checker. + +Finds violations of the rule "Throw by value, catch by reference" presented for example in "C++ Coding Standards" by H. Sutter and A. Alexandrescu. This check also has the option to find violations of the rule "Throw anonymous temporaries" (https://www.securecoding.cert.org/confluence/display/cplusplus/ERR09-CPP.+Throw+anonymous+temporaries). The option is named "CheckThrowTemporaries" and it's on by default. + +Exceptions: +- throwing string literals will not be flagged despite being a pointer. They are not susceptible to slicing and the usage of string literals is idomatic. +- catching character pointers (char, wchar_t, unicode character types) will not be flagged to allow catching sting literals. +- moved named values will not be flagged as not throwing an anonymous temporary. In this case we can be sure that the user knows that the object can't be accessed outside catch blocks handling the error. +- throwing function parameters will not be flagged as not throwing an anonymous temporary. This allows helper functions for throwing. +- re-throwing caught exception variables will not be flragged as not throwing an anonymous temporary. Although this can usually be done by just writing "throw;" it happens often enough in real code. diff --git a/docs/clang-tidy/checks/misc-undelegated-constructor.rst b/docs/clang-tidy/checks/misc-undelegated-constructor.rst new file mode 100644 index 00000000..3b7fffcd --- /dev/null +++ b/docs/clang-tidy/checks/misc-undelegated-constructor.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - misc-undelegated-constructor + +misc-undelegated-constructor +============================ + + +Finds creation of temporary objects in constructors that look like a +function call to another constructor of the same class. + +The user most likely meant to use a delegating constructor or base class +initializer. diff --git a/docs/clang-tidy/checks/misc-uniqueptr-reset-release.rst b/docs/clang-tidy/checks/misc-uniqueptr-reset-release.rst new file mode 100644 index 00000000..2d4553e7 --- /dev/null +++ b/docs/clang-tidy/checks/misc-uniqueptr-reset-release.rst @@ -0,0 +1,17 @@ +.. title:: clang-tidy - misc-uniqueptr-reset-release + +misc-uniqueptr-reset-release +============================ + + +Find and replace ``unique_ptr::reset(release())`` with ``std::move()``. + +Example: + +.. code:: c++ + + std::unique_ptr x, y; + x.reset(y.release()); -> x = std::move(y); + +If ``y`` is already rvalue, ``std::move()`` is not added. ``x`` and ``y`` can also +be ``std::unique_ptr*``. diff --git a/docs/clang-tidy/checks/misc-unused-alias-decls.rst b/docs/clang-tidy/checks/misc-unused-alias-decls.rst new file mode 100644 index 00000000..d0e8c718 --- /dev/null +++ b/docs/clang-tidy/checks/misc-unused-alias-decls.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - misc-unused-alias-decls + +misc-unused-alias-decls +======================= + + +Finds unused namespace alias declarations. diff --git a/docs/clang-tidy/checks/misc-unused-parameters.rst b/docs/clang-tidy/checks/misc-unused-parameters.rst new file mode 100644 index 00000000..224c997d --- /dev/null +++ b/docs/clang-tidy/checks/misc-unused-parameters.rst @@ -0,0 +1,8 @@ +.. title:: clang-tidy - misc-unused-parameters + +misc-unused-parameters +====================== + + +Finds unused parameters and fixes them, so that ``-Wunused-parameter`` can be +turned on. diff --git a/docs/clang-tidy/checks/misc-unused-raii.rst b/docs/clang-tidy/checks/misc-unused-raii.rst new file mode 100644 index 00000000..112073dc --- /dev/null +++ b/docs/clang-tidy/checks/misc-unused-raii.rst @@ -0,0 +1,28 @@ +.. title:: clang-tidy - misc-unused-raii + +misc-unused-raii +================ + + +Finds temporaries that look like RAII objects. + +The canonical example for this is a scoped lock. + +.. code:: c++ + + { + scoped_lock(&global_mutex); + critical_section(); + } + +The destructor of the scoped_lock is called before the ``critical_section`` is +entered, leaving it unprotected. + +We apply a number of heuristics to reduce the false positive count of this +check: + + * Ignore code expanded from macros. Testing frameworks make heavy use of this. + * Ignore types with trivial destructors. They are very unlikely to be RAII + objects and there's no difference when they are deleted. + * Ignore objects at the end of a compound statement (doesn't change behavior). + * Ignore objects returned from a call. diff --git a/docs/clang-tidy/checks/misc-virtual-near-miss.rst b/docs/clang-tidy/checks/misc-virtual-near-miss.rst new file mode 100644 index 00000000..dd40ce35 --- /dev/null +++ b/docs/clang-tidy/checks/misc-virtual-near-miss.rst @@ -0,0 +1,17 @@ +misc-virtual-near-miss +====================== + +Warn if a function is a near miss (ie. the name is very similar and the function signiture is the same) to a virtual function from a base class. + +Example: + +.. code-block:: c++ + + struct Base { + virtual void func(); + }; + + struct Derived : Base { + virtual funk(); + // warning: Do you want to override 'func'? + }; diff --git a/docs/clang-tidy/checks/modernize-loop-convert.rst b/docs/clang-tidy/checks/modernize-loop-convert.rst new file mode 100644 index 00000000..e65c9e59 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-loop-convert.rst @@ -0,0 +1,255 @@ +.. title:: clang-tidy - modernize-loop-convert + +modernize-loop-convert +====================== + +This check converts ``for(...; ...; ...)`` loops to use the new range-based +loops in C++11. + +Three kinds of loops can be converted: + +- Loops over statically allocated arrays. +- Loops over containers, using iterators. +- Loops over array-like containers, using ``operator[]`` and ``at()``. + +MinConfidence option +-------------------- + +risky +^^^^^ + +In loops where the container expression is more complex than just a +reference to a declared expression (a variable, function, enum, etc.), +and some part of it appears elsewhere in the loop, we lower our confidence +in the transformation due to the increased risk of changing semantics. +Transformations for these loops are marked as `risky`, and thus will only +be converted if the minimum required confidence level is set to ``risky``. + +.. code-block:: c++ + + int arr[10][20]; + int l = 5; + + for (int j = 0; j < 20; ++j) + int k = arr[l][j] + l; // using l outside arr[l] is considered risky + + for (int i = 0; i < obj.getVector().size(); ++i) + obj.foo(10); // using 'obj' is considered risky + +See +:ref:`Range-based loops evaluate end() only once` +for an example of an incorrect transformation when the minimum required confidence +level is set to `risky`. + +reasonable (Default) +^^^^^^^^^^^^^^^^^^^^ + +If a loop calls ``.end()`` or ``.size()`` after each iteration, the +transformation for that loop is marked as `reasonable`, and thus will +be converted if the required confidence level is set to ``reasonable`` +(default) or lower. + +.. code-block:: c++ + + // using size() is considered reasonable + for (int i = 0; i < container.size(); ++i) + cout << container[i]; + +safe +^^^^ + +Any other loops that do not match the above criteria to be marked as +`risky` or `reasonable` are marked `safe`, and thus will be converted +if the required confidence level is set to ``safe`` or lower. + +.. code-block:: c++ + + int arr[] = {1,2,3}; + + for (int i = 0; i < 3; ++i) + cout << arr[i]; + +Example +------- + +Original: + +.. code-block:: c++ + + const int N = 5; + int arr[] = {1,2,3,4,5}; + vector v; + v.push_back(1); + v.push_back(2); + v.push_back(3); + + // safe conversion + for (int i = 0; i < N; ++i) + cout << arr[i]; + + // reasonable conversion + for (vector::iterator it = v.begin(); it != v.end(); ++it) + cout << *it;* + + // reasonable conversion + for (int i = 0; i < v.size(); ++i) + cout << v[i]; + +After applying the check with minimum confidence level set to ``reasonable`` (default): + +.. code-block:: c++ + + const int N = 5; + int arr[] = {1,2,3,4,5}; + vector v; + v.push_back(1); + v.push_back(2); + v.push_back(3); + + // safe conversion + for (auto & elem : arr) + cout << elem; + + // reasonable conversion + for (auto & elem : v) + cout << elem; + + // reasonable conversion + for (auto & elem : v) + cout << elem; + +Limitations +----------- + +There are certain situations where the tool may erroneously perform +transformations that remove information and change semantics. Users of the tool +should be aware of the behaviour and limitations of the check outlined by +the cases below. + +Comments inside loop headers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Comments inside the original loop header are ignored and deleted when +transformed. + +.. code-block:: c++ + + for (int i = 0; i < N; /* This will be deleted */ ++i) { } + +Range-based loops evaluate end() only once +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The C++11 range-based for loop calls ``.end()`` only once during the +initialization of the loop. If in the original loop ``.end()`` is called after +each iteration the semantics of the transformed loop may differ. + +.. code-block:: c++ + + // The following is semantically equivalent to the C++11 range-based for loop, + // therefore the semantics of the header will not change. + for (iterator it = container.begin(), e = container.end(); it != e; ++it) { } + + // Instead of calling .end() after each iteration, this loop will be + // transformed to call .end() only once during the initialization of the loop, + // which may affect semantics. + for (iterator it = container.begin(); it != container.end(); ++it) { } + +.. _IncorrectRiskyTransformation: + +As explained above, calling member functions of the container in the body +of the loop is considered `risky`. If the called member function modifies the +container the semantics of the converted loop will differ due to ``.end()`` +being called only once. + +.. code-block:: c++ + + bool flag = false; + for (vector::iterator it = vec.begin(); it != vec.end(); ++it) { + // Add a copy of the first element to the end of the vector. + if (!flag) { + // This line makes this transformation 'risky'. + vec.push_back(*it); + flag = true; + } + cout << *it; + } + +The original code above prints out the contents of the container including the +newly added element while the converted loop, shown below, will only print the +original contents and not the newly added element. + +.. code-block:: c++ + + bool flag = false; + for (auto & elem : vec) { + // Add a copy of the first element to the end of the vector. + if (!flag) { + // This line makes this transformation 'risky' + vec.push_back(elem); + flag = true; + } + cout << elem; + } + +Semantics will also be affected if ``.end()`` has side effects. For example, in +the case where calls to ``.end()`` are logged the semantics will change in the +transformed loop if ``.end()`` was originally called after each iteration. + +.. code-block:: c++ + + iterator end() { + num_of_end_calls++; + return container.end(); + } + +Overloaded operator->() with side effects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Similarly, if ``operator->()`` was overloaded to have side effects, such as +logging, the semantics will change. If the iterator's ``operator->()`` was used +in the original loop it will be replaced with ``.`` +instead due to the implicit dereference as part of the range-based for loop. +Therefore any side effect of the overloaded ``operator->()`` will no longer be +performed. + +.. code-block:: c++ + + for (iterator it = c.begin(); it != c.end(); ++it) { + it->func(); // Using operator->() + } + // Will be transformed to: + for (auto & elem : c) { + elem.func(); // No longer using operator->() + } + +Pointers and references to containers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +While most of the check's risk analysis is dedicated to determining whether +the iterator or container was modified within the loop, it is possible to +circumvent the analysis by accessing and modifying the container through a +pointer or reference. + +If the container were directly used instead of using the pointer or reference +the following transformation would have only been applied at the ``risky`` +level since calling a member function of the container is considered `risky`. +The check cannot identify expressions associated with the container that are +different than the one used in the loop header, therefore the transformation +below ends up being performed at the ``safe`` level. + +.. code-block:: c++ + + vector vec; + + vector *ptr = &vec; + vector &ref = vec; + + for (vector::iterator it = vec.begin(), e = vec.end(); it != e; ++it) { + if (!flag) { + // Accessing and modifying the container is considered risky, but the risk + // level is not raised here. + ptr->push_back(*it); + ref.push_back(*it); + flag = true; + } + } diff --git a/docs/clang-tidy/checks/modernize-make-unique.rst b/docs/clang-tidy/checks/modernize-make-unique.rst new file mode 100644 index 00000000..dcb873f0 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-make-unique.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - modernize-make-unique + +modernize-make-unique +===================== + +This check finds the creation of ``std::unique_ptr`` objects by explicitly +calling the constructor and a ``new`` expression, and replaces it with a call +to ``std::make_unique``, introduced in C++14. + +.. code-block:: c++ + + auto my_ptr = std::unique_ptr(new MyPair(1, 2)); + + // becomes + + auto my_ptr = std::make_unique(1, 2); diff --git a/docs/clang-tidy/checks/modernize-pass-by-value.rst b/docs/clang-tidy/checks/modernize-pass-by-value.rst new file mode 100644 index 00000000..6a3c9000 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-pass-by-value.rst @@ -0,0 +1,153 @@ +.. title:: clang-tidy - modernize-pass-by-value + +modernize-pass-by-value +======================= + +With move semantics added to the language and the standard library updated with +move constructors added for many types it is now interesting to take an +argument directly by value, instead of by const-reference, and then copy. This +check allows the compiler to take care of choosing the best way to construct +the copy. + +The transformation is usually beneficial when the calling code passes an +*rvalue* and assumes the move construction is a cheap operation. This short +example illustrates how the construction of the value happens: + + .. code-block:: c++ + + void foo(std::string s); + std::string get_str(); + + void f(const std::string &str) { + foo(str); // lvalue -> copy construction + foo(get_str()); // prvalue -> move construction + } + +.. note:: + + Currently, only constructors are transformed to make use of pass-by-value. + Contributions that handle other situations are welcome! + + +Pass-by-value in constructors +----------------------------- + +Replaces the uses of const-references constructor parameters that are copied +into class fields. The parameter is then moved with `std::move()`. + +Since `std::move()` is a library function declared in `` it may be +necessary to add this include. The check will add the include directive when +necessary. + + .. code-block:: c++ + + #include + + class Foo { + public: + - Foo(const std::string &Copied, const std::string &ReadOnly) + - : Copied(Copied), ReadOnly(ReadOnly) + + Foo(std::string Copied, const std::string &ReadOnly) + + : Copied(std::move(Copied)), ReadOnly(ReadOnly) + {} + + private: + std::string Copied; + const std::string &ReadOnly; + }; + + std::string get_cwd(); + + void f(const std::string &Path) { + // The parameter corresponding to 'get_cwd()' is move-constructed. By + // using pass-by-value in the Foo constructor we managed to avoid a + // copy-construction. + Foo foo(get_cwd(), Path); + } + + +If the parameter is used more than once no transformation is performed since +moved objects have an undefined state. It means the following code will be left +untouched: + +.. code-block:: c++ + + #include + + void pass(const std::string &S); + + struct Foo { + Foo(const std::string &S) : Str(S) { + pass(S); + } + + std::string Str; + }; + + +Known limitations +^^^^^^^^^^^^^^^^^ + +A situation where the generated code can be wrong is when the object referenced +is modified before the assignment in the init-list through a "hidden" reference. + +Example: + +.. code-block:: c++ + + std::string s("foo"); + + struct Base { + Base() { + s = "bar"; + } + }; + + struct Derived : Base { + - Derived(const std::string &S) : Field(S) + + Derived(std::string S) : Field(std::move(S)) + { } + + std::string Field; + }; + + void f() { + - Derived d(s); // d.Field holds "bar" + + Derived d(s); // d.Field holds "foo" + } + + +Note about delayed template parsing +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When delayed template parsing is enabled, constructors part of templated +contexts; templated constructors, constructors in class templates, constructors +of inner classes of template classes, etc., are not transformed. Delayed +template parsing is enabled by default on Windows as a Microsoft extension: +`Clang Compiler User’s Manual - Microsoft extensions`_. + +Delayed template parsing can be enabled using the `-fdelayed-template-parsing` +flag and disabled using `-fno-delayed-template-parsing`. + +Example: + +.. code-block:: c++ + + template class C { + std::string S; + + public: + = // using -fdelayed-template-parsing (default on Windows) + = C(const std::string &S) : S(S) {} + + + // using -fno-delayed-template-parsing (default on non-Windows systems) + + C(std::string S) : S(std::move(S)) {} + }; + +.. _Clang Compiler User’s Manual - Microsoft extensions: http://clang.llvm.org/docs/UsersManual.html#microsoft-extensions + +.. seealso:: + + For more information about the pass-by-value idiom, read: `Want Speed? Pass by Value`_. + + .. _Want Speed? Pass by Value: http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/ diff --git a/docs/clang-tidy/checks/modernize-redundant-void-arg.rst b/docs/clang-tidy/checks/modernize-redundant-void-arg.rst new file mode 100644 index 00000000..d1a03e3f --- /dev/null +++ b/docs/clang-tidy/checks/modernize-redundant-void-arg.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - modernize-redundant-void-arg + +modernize-redundant-void-arg +============================ + +Find and remove redundant ``void`` argument lists. + +Examples: + =================================== =========================== + Initial code Code with applied fixes + =================================== =========================== + ``int f(void);`` ``int f();`` + ``int (*f(void))(void);`` ``int (*f())();`` + ``typedef int (*f_t(void))(void);`` ``typedef int (*f_t())();`` + ``void (C::*p)(void);`` ``void (C::*p)();`` + ``C::C(void) {}`` ``C::C() {}`` + ``C::~C(void) {}`` ``C::~C() {}`` + =================================== =========================== diff --git a/docs/clang-tidy/checks/modernize-replace-auto-ptr.rst b/docs/clang-tidy/checks/modernize-replace-auto-ptr.rst new file mode 100644 index 00000000..975c884e --- /dev/null +++ b/docs/clang-tidy/checks/modernize-replace-auto-ptr.rst @@ -0,0 +1,72 @@ +.. title:: clang-tidy - modernize-replace-auto-ptr + +modernize-replace-auto-ptr +========================== + +This check replaces the uses of the deprecated class ``std::auto_ptr`` by +``std::unique_ptr`` (introduced in C++11). The transfer of ownership, done +by the copy-constructor and the assignment operator, is changed to match +``std::unique_ptr`` usage by using explicit calls to ``std::move()``. + +Migration example: + +.. code-block:: c++ + + -void take_ownership_fn(std::auto_ptr int_ptr); + +void take_ownership_fn(std::unique_ptr int_ptr); + + void f(int x) { + - std::auto_ptr a(new int(x)); + - std::auto_ptr b; + + std::unique_ptr a(new int(x)); + + std::unique_ptr b; + + - b = a; + - take_ownership_fn(b); + + b = std::move(a); + + take_ownership_fn(std::move(b)); + } + +Since ``std::move()`` is a library function declared in ```` it may be +necessary to add this include. The check will add the include directive when +necessary. + +Known Limitations +----------------- +* If headers modification is not activated or if a header is not allowed to be + changed this check will produce broken code (compilation error), where the + headers' code will stay unchanged while the code using them will be changed. + +* Client code that declares a reference to an ``std::auto_ptr`` coming from + code that can't be migrated (such as a header coming from a 3\ :sup:`rd` + party library) will produce a compilation error after migration. This is + because the type of the reference will be changed to ``std::unique_ptr`` but + the type returned by the library won't change, binding a reference to + ``std::unique_ptr`` from an ``std::auto_ptr``. This pattern doesn't make much + sense and usually ``std::auto_ptr`` are stored by value (otherwise what is + the point in using them instead of a reference or a pointer?). + + .. code-block:: c++ + + // <3rd-party header...> + std::auto_ptr get_value(); + const std::auto_ptr & get_ref(); + + // + -std::auto_ptr a(get_value()); + +std::unique_ptr a(get_value()); // ok, unique_ptr constructed from auto_ptr + + -const std::auto_ptr & p = get_ptr(); + +const std::unique_ptr & p = get_ptr(); // won't compile + +* Non-instantiated templates aren't modified. + + .. code-block:: c++ + + template + void f() { + std::auto_ptr p; + } + + // only 'f()' (or similar) will trigger the replacement. + diff --git a/docs/clang-tidy/checks/modernize-shrink-to-fit.rst b/docs/clang-tidy/checks/modernize-shrink-to-fit.rst new file mode 100644 index 00000000..4d7ffafe --- /dev/null +++ b/docs/clang-tidy/checks/modernize-shrink-to-fit.rst @@ -0,0 +1,12 @@ +.. title:: clang-tidy - modernize-shrink-to-fit + +modernize-shrink-to-fit +======================= + + +Replace copy and swap tricks on shrinkable containers with the +``shrink_to_fit()`` method call. + +The ``shrink_to_fit()`` method is more readable and more effective than +the copy and swap trick to reduce the capacity of a shrinkable container. +Note that, the ``shrink_to_fit()`` method is only available in C++11 and up. diff --git a/docs/clang-tidy/checks/modernize-use-auto.rst b/docs/clang-tidy/checks/modernize-use-auto.rst new file mode 100644 index 00000000..3329e977 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-auto.rst @@ -0,0 +1,152 @@ +.. title:: clang-tidy - modernize-use-auto + +modernize-use-auto +================== + +This check is responsible for using the ``auto`` type specifier for variable +declarations to *improve code readability and maintainability*. For example: + +.. code-block:: c++ + + std::vector::iterator I = my_container.begin(); + + // transforms to: + + auto I = my_container.begin(); + +The ``auto`` type specifier will only be introduced in situations where the +variable type matches the type of the initializer expression. In other words +``auto`` should deduce the same type that was originally spelled in the source. +However, not every situation should be transformed: + +.. code-block:: c++ + + int val = 42; + InfoStruct &I = SomeObject.getInfo(); + + // Should not become: + + auto val = 42; + auto &I = SomeObject.getInfo(); + +In this example using ``auto`` for builtins doesn't improve readability. In +other situations it makes the code less self-documenting impairing readability +and maintainability. As a result, ``auto`` is used only introduced in specific +situations described below. + +Iterators +--------- + +Iterator type specifiers tend to be long and used frequently, especially in +loop constructs. Since the functions generating iterators have a common format, +the type specifier can be replaced without obscuring the meaning of code while +improving readability and maintainability. + +.. code-block:: c++ + + for (std::vector::iterator I = my_container.begin(), + E = my_container.end(); + I != E; ++I) { + } + + // becomes + + for (auto I = my_container.begin(), E = my_container.end(); I != E; ++I) { + } + +The check will only replace iterator type-specifiers when all of the following +conditions are satisfied: + +* The iterator is for one of the standard container in ``std`` namespace: + + * ``array`` + * ``deque`` + * ``forward_list`` + * ``list`` + * ``vector`` + * ``map`` + * ``multimap`` + * ``set`` + * ``multiset`` + * ``unordered_map`` + * ``unordered_multimap`` + * ``unordered_set`` + * ``unordered_multiset`` + * ``queue`` + * ``priority_queue`` + * ``stack`` + +* The iterator is one of the possible iterator types for standard containers: + + * ``iterator`` + * ``reverse_iterator`` + * ``const_iterator`` + * ``const_reverse_iterator`` + +* In addition to using iterator types directly, typedefs or other ways of + referring to those types are also allowed. However, implementation-specific + types for which a type like ``std::vector::iterator`` is itself a + typedef will not be transformed. Consider the following examples: + +.. code-block:: c++ + + // The following direct uses of iterator types will be transformed. + std::vector::iterator I = MyVec.begin(); + { + using namespace std; + list::iterator I = MyList.begin(); + } + + // The type specifier for J would transform to auto since it's a typedef + // to a standard iterator type. + typedef std::map::const_iterator map_iterator; + map_iterator J = MyMap.begin(); + + // The following implementation-specific iterator type for which + // std::vector::iterator could be a typedef would not be transformed. + __gnu_cxx::__normal_iterator K = MyVec.begin(); + +* The initializer for the variable being declared is not a braced initializer + list. Otherwise, use of ``auto`` would cause the type of the variable to be + deduced as``std::initializer_list``. + +New expressions +--------------- + +Frequently, when a pointer is declared and initialized with ``new``, the +pointee type has to be written twice: in the declaration type and in the +``new`` expression. In this cases, the declaration type can be replaced with +``auto`` improving readability and maintainability. + +.. code-block:: c++ + + TypeName *my_pointer = new TypeName(my_param); + + // becomes + + auto my_pointer = new TypeName(my_param); + +The check will also replace the declaration type in multiple declarations, if +the following conditions are satisfied: + +* All declared variables have the same type (i.e. all of them are pointers to + the same type). +* All declared variables are initialized with a ``new`` expression. +* The types of all the new expressions are the same than the pointee of the + declaration type. + +.. code-block:: c++ + + TypeName *my_first_pointer = new TypeName, *my_second_pointer = new TypeName; + + // becomes + + auto my_first_pointer = new TypeName, my_second_pointer = new TypeName; + +Known Limitations +----------------- +* If the initializer is an explicit conversion constructor, the check will not + replace the type specifier even though it would be safe to do so. + +* User-defined iterators are not handled at this time. + diff --git a/docs/clang-tidy/checks/modernize-use-default.rst b/docs/clang-tidy/checks/modernize-use-default.rst new file mode 100644 index 00000000..157a337c --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-default.rst @@ -0,0 +1,29 @@ +.. title:: clang-tidy - modernize-use-default + +modernize-use-default +===================== + +This check replaces default bodies of special member functions with ``= +default;``. The explicitly defaulted function declarations enable more +opportunities in optimization, because the compiler might treat explicitly +defaulted functions as trivial. + +.. code-block:: c++ + + struct A { + A() {} + ~A(); + }; + A::~A() {} + + // becomes + + struct A { + A() = default; + ~A(); + }; + A::~A() = default; + +.. note:: + Copy-constructor, copy-assignment operator, move-constructor and + move-assignment operator are not supported yet. diff --git a/docs/clang-tidy/checks/modernize-use-nullptr.rst b/docs/clang-tidy/checks/modernize-use-nullptr.rst new file mode 100644 index 00000000..f0219e3a --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-nullptr.rst @@ -0,0 +1,67 @@ +.. title:: clang-tidy - modernize-use-nullptr + +modernize-use-nullptr +===================== + +The check converts the usage of null pointer constants (eg. ``NULL``, ``0``) +to use the new C++11 ``nullptr`` keyword. + +Example +------- + +.. code-block:: c++ + + void assignment() { + char *a = NULL; + char *b = 0; + char c = 0; + } + + int *ret_ptr() { + return 0; + } + + +transforms to: + +.. code-block:: c++ + + void assignment() { + char *a = nullptr; + char *b = nullptr; + char c = 0; + } + + int *ret_ptr() { + return nullptr; + } + + +User defined macros +------------------- + +By default this check will only replace the ``NULL`` macro and will skip any +user-defined macros that behaves like ``NULL``. The user can use the +:option:``UserNullMacros`` option to specify a comma-separated list of macro +names that will be transformed along with ``NULL``. + +Example +^^^^^^^ + +.. code-block:: c++ + + #define MY_NULL (void*)0 + void assignment() { + void *p = MY_NULL; + } + +transforms to: + +.. code-block:: c++ + + #define MY_NULL NULL + void assignment() { + int *p = nullptr; + } + +if the ``UserNullMacros`` option is set to ``MY_NULL``. diff --git a/docs/clang-tidy/checks/modernize-use-override.rst b/docs/clang-tidy/checks/modernize-use-override.rst new file mode 100644 index 00000000..f2c778aa --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-override.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - modernize-use-override + +modernize-use-override +====================== + + +Use C++11's ``override`` and remove ``virtual`` where applicable. diff --git a/docs/clang-tidy/checks/readability-braces-around-statements.rst b/docs/clang-tidy/checks/readability-braces-around-statements.rst new file mode 100644 index 00000000..a1041f6e --- /dev/null +++ b/docs/clang-tidy/checks/readability-braces-around-statements.rst @@ -0,0 +1,31 @@ +.. title:: clang-tidy - readability-braces-around-statements + +readability-braces-around-statements +==================================== + + +Checks that bodies of ``if`` statements and loops (``for``, ``range-for``, +``do-while``, and ``while``) are inside braces + +Before: + +.. code:: c++ + + if (condition) + statement; + +After: + +.. code:: c++ + + if (condition) { + statement; + } + +Additionally, one can define an option ``ShortStatementLines`` defining the +minimal number of lines that the statement should have in order to trigger +this check. + +The number of lines is counted from the end of condition or initial keyword +(``do``/``else``) until the last line of the inner statement. Default value 0 +means that braces will be added to all statements (not having them already). diff --git a/docs/clang-tidy/checks/readability-container-size-empty.rst b/docs/clang-tidy/checks/readability-container-size-empty.rst new file mode 100644 index 00000000..40a878a2 --- /dev/null +++ b/docs/clang-tidy/checks/readability-container-size-empty.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - readability-container-size-empty + +readability-container-size-empty +================================ + + +Checks whether a call to the ``size()`` method can be replaced with a call to +``empty()``. + +The emptiness of a container should be checked using the ``empty()`` method +instead of the ``size()`` method. It is not guaranteed that ``size()`` is a +constant-time function, and it is generally more efficient and also shows +clearer intent to use ``empty()``. Furthermore some containers may implement +the ``empty()`` method but not implement the ``size()`` method. Using ``empty()`` +whenever possible makes it easier to switch to another container in the +future. diff --git a/docs/clang-tidy/checks/readability-else-after-return.rst b/docs/clang-tidy/checks/readability-else-after-return.rst new file mode 100644 index 00000000..a8e4c460 --- /dev/null +++ b/docs/clang-tidy/checks/readability-else-after-return.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - readability-else-after-return + +readability-else-after-return +============================= + + +Flags the usages of ``else`` after ``return``. + +http://llvm.org/docs/CodingStandards.html#don-t-use-else-after-a-return diff --git a/docs/clang-tidy/checks/readability-function-size.rst b/docs/clang-tidy/checks/readability-function-size.rst new file mode 100644 index 00000000..44958d71 --- /dev/null +++ b/docs/clang-tidy/checks/readability-function-size.rst @@ -0,0 +1,17 @@ +.. title:: clang-tidy - readability-function-size + +readability-function-size +========================= + + +Checks for large functions based on various metrics. + +These options are supported: + + * ``LineThreshold`` - flag functions exceeding this number of lines. The + default is ``-1`` (ignore the number of lines). + * ``StatementThreshold`` - flag functions exceeding this number of + statements. This may differ significantly from the number of lines for + macro-heavy code. The default is ``800``. + * ``BranchThreshold`` - flag functions exceeding this number of control + statements. The default is ``-1`` (ignore the number of branches). diff --git a/docs/clang-tidy/checks/readability-identifier-naming.rst b/docs/clang-tidy/checks/readability-identifier-naming.rst new file mode 100644 index 00000000..c03011ad --- /dev/null +++ b/docs/clang-tidy/checks/readability-identifier-naming.rst @@ -0,0 +1,19 @@ +.. title:: clang-tidy - readability-identifier-naming + +readability-identifier-naming +============================= + + +Checks for identifiers naming style mismatch. + +This check will try to enforce coding guidelines on the identifiers naming. +It supports ``lower_case``, ``UPPER_CASE``, ``camelBack`` and ``CamelCase`` casing +and tries to convert from one to another if a mismatch is detected. + +It also supports a fixed prefix and suffix that will be prepended or +appended to the identifiers, regardless of the casing. + +Many configuration options are available, in order to be able to create +different rules for different kind of identifier. In general, the +rules are falling back to a more generic rule if the specific case is not +configured. diff --git a/docs/clang-tidy/checks/readability-implicit-bool-cast.rst b/docs/clang-tidy/checks/readability-implicit-bool-cast.rst new file mode 100644 index 00000000..76c82b41 --- /dev/null +++ b/docs/clang-tidy/checks/readability-implicit-bool-cast.rst @@ -0,0 +1,99 @@ +.. title:: clang-tidy - readability-implicit-bool-cast + +readability-implicit-bool-cast +============================== + +This check can be used to find implicit conversions between built-in types and +booleans. Depending on use case, it may simply help with readability of the code, +or in some cases, point to potential bugs which remain unnoticed due to implicit +conversions. + +The following is a real-world example of bug which was hiding behind implicit +bool cast: + +.. code:: c++ + + class Foo { + int m_foo; + public: + void setFoo(bool foo) { m_foo = foo; } // warning: implicit cast bool -> int + int getFoo() { return m_foo; } + }; + + void use(Foo& foo) { + bool value = foo.getFoo(); // warning: implicit cast int -> bool + } + +This code is the result of unsuccessful refactoring, where type of ``m_foo`` +changed from ``bool`` to ``int``. The programmer forgot to change all +occurences of ``bool``, and the remaining code is no longer correct, yet it +still compiles without any visible warnings. + +In addition to issuing warnings, FixIt hints are provided to help solve +the reported issues. This can be used for improving readabilty of code, for example: + +.. code:: c++ + + void conversionsToBool() { + float floating; + bool boolean = floating; + // ^ propose replacement: bool boolean = floating != 0.0f; + + int integer; + if (integer) {} + // ^ propose replacement: if (integer != 0) {} + + int* pointer; + if (!pointer) {} + // ^ propose replacement: if (pointer == nullptr) {} + + while (1) {} + // ^ propose replacement: while (true) {} + } + + void functionTakingInt(int param); + + void conversionsFromBool() { + bool boolean; + functionTakingInt(boolean); + // ^ propose replacement: functionTakingInt(static_cast(boolean)); + + functionTakingInt(true); + // ^ propose replacement: functionTakingInt(1); + } + +In general, the following cast types are checked: + - integer expression/literal to boolean, + - floating expression/literal to boolean, + - pointer/pointer to member/``nullptr``/``NULL`` to boolean, + - boolean expression/literal to integer, + - boolean expression/literal to floating. + +The rules for generating FixIt hints are: + - in case of casts from other built-in type to bool, an explicit comparison + is proposed to make it clear what exaclty is being compared: + + - ``bool boolean = floating;`` is changed to ``bool boolean = floating == 0.0f;``, + - for other types, appropriate literals are used (``0``, ``0u``, ``0.0f``, ``0.0``, ``nullptr``), + - in case of negated expressions cast to bool, the proposed replacement with + comparison is simplified: + + - ``if (!pointer)`` is changed to ``if (pointer == nullptr)``, + - in case of casts from bool to other built-in types, an explicit ``static_cast`` + is proposed to make it clear that a cast is taking place: + + - ``int integer = boolean;`` is changed to ``int integer = static_cast(boolean);``, + - if the cast is performed on type literals, an equivalent literal is proposed, + according to what type is actually expected, for example: + + - ``functionTakingBool(0);`` is changed to ``functionTakingBool(false);``, + - ``functionTakingInt(true);`` is changed to ``functionTakingInt(1);``, + - for other types, appropriate literals are used (``false``, ``true``, ``0``, ``1``, ``0u``, ``1u``, + ``0.0f``, ``1.0f``, ``0.0``, ``1.0f``). + +Some additional accommodations are made for pre-C++11 dialects: + - ``false`` literal cast to pointer is detected, + - instead of ``nullptr`` literal, ``0`` is proposed as replacement. + +Occurences of implicit casts inside macros and template instantiations are +deliberately ignored, as it is not clear how to deal with such cases. diff --git a/docs/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.rst b/docs/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.rst new file mode 100644 index 00000000..aa0246c1 --- /dev/null +++ b/docs/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.rst @@ -0,0 +1,45 @@ +.. title:: clang-tidy - readability-inconsistent-declaration-parameter-name + +readability-inconsistent-declaration-parameter-name +=================================================== + + +Find function declarations which differ in parameter names. + +Example: + +.. code:: c++ + + // in foo.hpp: + void foo(int a, int b, int c); + + // in foo.cpp: + void foo(int d, int e, int f); // warning + +This check should help to enforce consistency in large projects, where it often +happens that a definition of function is refactored, changing the parameter +names, but its declaration in header file is not updated. With this check, we +can easily find and correct such inconsistencies, keeping declaration and +definition always in sync. + +Unnamed parameters are allowed and are not taken into account when comparing +function declarations, for example: + +.. code:: c++ + + void foo(int a); + void foo(int); // no warning + +To help with refactoring, in some cases FixIt hints are generated to align +parameter names to a single naming convention. This works with the assumption +that the function definition is the most up-to-date version, as it directly +references parameter names in its body. Example: + +.. code:: c++ + + void foo(int a); // warning and FixIt hint (replace "a" to "b") + int foo(int b) { return b + 2; } // definition with use of "b" + +In the case of multiple redeclarations or function template specializations, +a warning is issued for every redeclaration or specialization inconsistent with +the definition or the first declaration seen in a translation unit. diff --git a/docs/clang-tidy/checks/readability-named-parameter.rst b/docs/clang-tidy/checks/readability-named-parameter.rst new file mode 100644 index 00000000..97c72de9 --- /dev/null +++ b/docs/clang-tidy/checks/readability-named-parameter.rst @@ -0,0 +1,17 @@ +.. title:: clang-tidy - readability-named-parameter + +readability-named-parameter +=========================== + + +Find functions with unnamed arguments. + +The check implements the following rule originating in the Google C++ Style +Guide: + +http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Function_Declarations_and_Definitions#Function_Declarations_and_Definitions + +All parameters should be named, with identical names in the declaration and +implementation. + +Corresponding cpplint.py check name: 'readability/function'. diff --git a/docs/clang-tidy/checks/readability-redundant-smartptr-get.rst b/docs/clang-tidy/checks/readability-redundant-smartptr-get.rst new file mode 100644 index 00000000..7a32d425 --- /dev/null +++ b/docs/clang-tidy/checks/readability-redundant-smartptr-get.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - readability-redundant-smartptr-get + +readability-redundant-smartptr-get +================================== + + +Find and remove redundant calls to smart pointer's ``.get()`` method. + +Examples: + +.. code:: c++ + + ptr.get()->Foo() ==> ptr->Foo() + *ptr.get() ==> *ptr + *ptr->get() ==> **ptr + diff --git a/docs/clang-tidy/checks/readability-redundant-string-cstr.rst b/docs/clang-tidy/checks/readability-redundant-string-cstr.rst new file mode 100644 index 00000000..4614c53a --- /dev/null +++ b/docs/clang-tidy/checks/readability-redundant-string-cstr.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - readability-redundant-string-cstr + +readability-redundant-string-cstr +================================= + + +Finds unnecessary calls to ``std::string::c_str()``. diff --git a/docs/clang-tidy/checks/readability-simplify-boolean-expr.rst b/docs/clang-tidy/checks/readability-simplify-boolean-expr.rst new file mode 100644 index 00000000..4c3a1c8f --- /dev/null +++ b/docs/clang-tidy/checks/readability-simplify-boolean-expr.rst @@ -0,0 +1,78 @@ +.. title:: clang-tidy - readability-simplify-boolean-expr + +readability-simplify-boolean-expr +================================= + + +Looks for boolean expressions involving boolean constants and simplifies +them to use the appropriate boolean expression directly. + +Examples: + +=========================================== ================ +Initial expression Result +------------------------------------------- ---------------- +``if (b == true)`` ``if (b)`` +``if (b == false)`` ``if (!b)`` +``if (b && true)`` ``if (b)`` +``if (b && false)`` ``if (false)`` +``if (b || true)`` ``if (true)`` +``if (b || false)`` ``if (b)`` +``e ? true : false`` ``e`` +``e ? false : true`` ``!e`` +``if (true) t(); else f();`` ``t();`` +``if (false) t(); else f();`` ``f();`` +``if (e) return true; else return false;`` ``return e;`` +``if (e) return false; else return true;`` ``return !e;`` +``if (e) b = true; else b = false;`` ``b = e;`` +``if (e) b = false; else b = true;`` ``b = !e;`` +``if (e) return true; return false;`` ``return e;`` +``if (e) return false; return true;`` ``return !e;`` +=========================================== ================ + +The resulting expression ``e`` is modified as follows: + 1. Unnecessary parentheses around the expression are removed. + 2. Negated applications of ``!`` are eliminated. + 3. Negated applications of comparison operators are changed to use the + opposite condition. + 4. Implicit conversions of pointer to ``bool`` are replaced with explicit + comparisons to ``nullptr``. + 5. Implicit casts to ``bool`` are replaced with explicit casts to ``bool``. + 6. Object expressions with ``explicit operator bool`` conversion operators + are replaced with explicit casts to ``bool``. + +Examples: + 1. The ternary assignment ``bool b = (i < 0) ? true : false;`` has redundant + parentheses and becomes ``bool b = i < 0;``. + + 2. The conditional return ``if (!b) return false; return true;`` has an + implied double negation and becomes ``return b;``. + + 3. The conditional return ``if (i < 0) return false; return true;`` becomes + ``return i >= 0;``. + + The conditional return ``if (i != 0) return false; return true;`` becomes + ``return i == 0;``. + + 4. The conditional return ``if (p) return true; return false;`` has an + implicit conversion of a pointer to ``bool`` and becomes + ``return p != nullptr;``. + + The ternary assignment ``bool b = (i & 1) ? true : false;`` has an + implicit conversion of ``i & 1`` to ``bool`` and becomes + ``bool b = static_cast(i & 1);``. + + 5. The conditional return ``if (i & 1) return true; else return false;`` has + an implicit conversion of an integer quantity ``i & 1`` to ``bool`` and + becomes ``return static_cast(i & 1);`` + + 6. Given ``struct X { explicit operator bool(); };``, and an instance ``x`` of + ``struct X``, the conditional return ``if (x) return true; return false;`` + becomes ``return static_cast(x);`` + +When a conditional boolean return or assignment appears at the end of a +chain of ``if``, ``else if`` statements, the conditional statement is left +unchanged unless the option ``ChainedConditionalReturn`` or +``ChainedConditionalAssignment``, respectively, is specified as non-zero. +The default value for both options is zero. + diff --git a/docs/clang-tidy/checks/readability-uniqueptr-delete-release.rst b/docs/clang-tidy/checks/readability-uniqueptr-delete-release.rst new file mode 100644 index 00000000..83122356 --- /dev/null +++ b/docs/clang-tidy/checks/readability-uniqueptr-delete-release.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - readability-uniqueptr-delete-release + +readability-uniqueptr-delete-release +==================================== + +Replace ``delete .release()`` with `` = nullptr``. +The latter is shorter, simpler and does not require use of raw pointer APIs. diff --git a/docs/clang-tidy/index.rst b/docs/clang-tidy/index.rst new file mode 100644 index 00000000..6dce4102 --- /dev/null +++ b/docs/clang-tidy/index.rst @@ -0,0 +1,516 @@ +========== +Clang-Tidy +========== + +.. toctree:: + :maxdepth: 1 + + checks/list + + +:program:`clang-tidy` is a clang-based C++ linter tool. Its purpose is to +provide an extensible framework for diagnosing and fixing typical programming +errors, like style violations, interface misuse, or bugs that can be deduced via +static analysis. :program:`clang-tidy` is modular and provides a convenient +interface for writing new checks. + + +Using clang-tidy +================ + +:program:`clang-tidy` is a `LibTooling`_-based tool, and it's easier to work +with if you set up a compile command database for your project (for an example +of how to do this see `How To Setup Tooling For LLVM`_). You can also specify +compilation options on the command line after ``--``: + +.. code-block:: console + + $ clang-tidy test.cpp -- -Imy_project/include -DMY_DEFINES ... + +:program:`clang-tidy` has its own checks and can also run Clang static analyzer +checks. Each check has a name and the checks to run can be chosen using the +``-checks=`` option, which specifies a comma-separated list of positive and +negative (prefixed with ``-``) globs. Positive globs add subsets of checks, +negative globs remove them. For example, + +.. code-block:: console + + $ clang-tidy test.cpp -checks=-*,clang-analyzer-*,-clang-analyzer-alpha* + +will disable all default checks (``-*``) and enable all ``clang-analyzer-*`` +checks except for ``clang-analyzer-alpha*`` ones. + +The ``-list-checks`` option lists all the enabled checks. When used without +``-checks=``, it shows checks enabled by default. Use ``-checks=*`` to see all +available checks or with any other value of ``-checks=`` to see which checks are +enabled by this value. + +There are currently the following groups of checks: + +* Checks related to the LLVM coding conventions have names starting with + ``llvm-``. + +* Checks related to the Google coding conventions have names starting with + ``google-``. + +* Checks named ``modernize-*`` advocate the usage of modern (currently "modern" + means "C++11") language constructs. + +* The ``readability-`` checks target readability-related issues that don't + relate to any particular coding style. + +* Checks with names starting with ``misc-`` the checks that we didn't have a + better category for. + +* Clang static analyzer checks are named starting with ``clang-analyzer-``. + +Clang diagnostics are treated in a similar way as check diagnostics. Clang +diagnostics are displayed by clang-tidy and can be filtered out using +``-checks=`` option. However, the ``-checks=`` option does not affect +compilation arguments, so it can not turn on Clang warnings which are not +already turned on in build configuration. + +Clang diagnostics have check names starting with ``clang-diagnostic-``. +Diagnostics which have a corresponding warning option, are named +``clang-diagostic-``, e.g. Clang warning controlled by +``-Wliteral-conversion`` will be reported with check name +``clang-diagnostic-literal-conversion``. + +The ``-fix`` flag instructs :program:`clang-tidy` to fix found errors if +supported by corresponding checks. + +An overview of all the command-line options: + +.. code-block:: console + + $ clang-tidy -help + USAGE: clang-tidy [options] [... ] + + OPTIONS: + + General options: + + -help - Display available options (-help-hidden + for more) + -help-list - Display list of available options + (-help-list-hidden for more) + -version - Display the version of this program + + clang-tidy options: + + -analyze-temporary-dtors - Enable temporary destructor-aware analysis in + clang-analyzer- checks. + This option overrides the value read from a + .clang-tidy file. + -checks= - Comma-separated list of globs with optional '-' + prefix. Globs are processed in order of appearance + in the list. Globs without '-' prefix add checks + with matching names to the set, globs with the '-' + prefix remove checks with matching names from the + set of enabled checks. + This option's value is appended to the value read + from a .clang-tidy file, if any. + -config= - Specifies a configuration in YAML/JSON format: + -config="{Checks: '*', CheckOptions: [{key: x, value: y}]}" + When the value is empty, clang-tidy will attempt to find + a file named .clang-tidy for each source file in its parent + directories. + -dump-config - Dumps configuration in the YAML format to stdout. This option + should be used along with a file name (and '--' if the file is + outside of a project with configured compilation database). The + configuration used for this file will be printed. + -enable-check-profile - Enable per-check timing profiles, and print a report to stderr. + -export-fixes= - YAML file to store suggested fixes in. The + stored fixes can be applied to the input source + code with clang-apply-replacements. + -extra-arg= - Additional argument to append to the compiler command line + -extra-arg-before= - Additional argument to prepend to the compiler command line + -fix - Apply suggested fixes. Without -fix-errors + clang-tidy will bail out if any compilation + errors were found. + -fix-errors - Apply suggested fixes even if compilation errors + were found. If compiler errors have attached + fix-its, clang-tidy will apply them as well. + -header-filter= - Regular expression matching the names of the + headers to output diagnostics from. Diagnostics + from the main file of each translation unit are + always displayed. + Can be used together with -line-filter. + This option overrides the value read from a + .clang-tidy file. + -line-filter= - List of files with line ranges to filter the + warnings. Can be used together with + -header-filter. The format of the list is a JSON + array of objects: + [ + {"name":"file1.cpp","lines":[[1,3],[5,7]]}, + {"name":"file2.h"} + ] + -list-checks - List all enabled checks and exit. Use with + -checks=* to list all available checks. + -p= - Build path + -system-headers - Display the errors from system headers. + + -p is used to read a compile command database. + + For example, it can be a CMake build directory in which a file named + compile_commands.json exists (use -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + CMake option to get this output). When no build path is specified, + a search for compile_commands.json will be attempted through all + parent paths of the first input file . See: + http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html for an + example of setting up Clang Tooling on a source tree. + + ... specify the paths of source files. These paths are + looked up in the compile command database. If the path of a file is + absolute, it needs to point into CMake's source tree. If the path is + relative, the current working directory needs to be in the CMake + source tree and the file must be in a subdirectory of the current + working directory. "./" prefixes in the relative files will be + automatically removed, but the rest of a relative path must be a + suffix of a path in the compile command database. + + Configuration files: + clang-tidy attempts to read configuration for each source file from a + .clang-tidy file located in the closest parent directory of the source + file. If any configuration options have a corresponding command-line + option, command-line option takes precedence. The effective + configuration can be inspected using -dump-config: + + $ clang-tidy -dump-config - -- + --- + Checks: '-*,some-check' + HeaderFilterRegex: '' + AnalyzeTemporaryDtors: false + User: user + CheckOptions: + - key: some-check.SomeOption + value: 'some value' + ... + +.. _LibTooling: http://clang.llvm.org/docs/LibTooling.html +.. _How To Setup Tooling For LLVM: http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html + + +Getting Involved +================ + +:program:`clang-tidy` has several own checks and can run Clang static analyzer +checks, but its power is in the ability to easily write custom checks. + +Checks are organized in modules, which can be linked into :program:`clang-tidy` +with minimal or no code changes in clang-tidy. + +Checks can plug the analysis on the preprocessor level using `PPCallbacks`_ or +on the AST level using `AST Matchers`_. When an error is found, checks can +report them in a way similar to how Clang diagnostics work. A fix-it hint can be +attached to a diagnostic message. + +The interface provided by clang-tidy makes it easy to write useful and precise +checks in just a few lines of code. If you have an idea for a good check, the +rest of this document explains how to do this. + +.. _AST Matchers: http://clang.llvm.org/docs/LibASTMatchers.html +.. _PPCallbacks: http://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html + + +Choosing the Right Place for your Check +--------------------------------------- + +If you have an idea of a check, you should decide whether it should be +implemented as a: + ++ *Clang diagnostic*: if the check is generic enough, targets code patterns that + most probably are bugs (rather than style or readability issues), can be + implemented effectively and with extremely low false positive rate, it may + make a good Clang diagnostic. + ++ *Clang static analyzer check*: if the check requires some sort of control flow + analysis, it should probably be implemented as a static analyzer check. + ++ *clang-tidy check* is a good choice for linter-style checks, checks that are + related to a certain coding style, checks that address code readability, etc. + + +Preparing your Workspace +------------------------ + +If you are new to LLVM development, you should read the `Getting Started with +the LLVM System`_, `Using Clang Tools`_ and `How To Setup Tooling For LLVM`_ +documents to check out and build LLVM, Clang and Clang Extra Tools with CMake. + +Once you are done, change to the ``llvm/tools/clang/tools/extra`` directory, and +let's start! + +.. _Getting Started with the LLVM System: http://llvm.org/docs/GettingStarted.html +.. _Using Clang Tools: http://clang.llvm.org/docs/ClangTools.html + + +The Directory Structure +----------------------- + +:program:`clang-tidy` source code resides in the +``llvm/tools/clang/tools/extra`` directory and is structured as follows: + +:: + + clang-tidy/ # Clang-tidy core. + |-- ClangTidy.h # Interfaces for users and checks. + |-- ClangTidyModule.h # Interface for clang-tidy modules. + |-- ClangTidyModuleRegistry.h # Interface for registering of modules. + ... + |-- google/ # Google clang-tidy module. + |-+ + |-- GoogleTidyModule.cpp + |-- GoogleTidyModule.h + ... + |-- llvm/ # LLVM clang-tidy module. + |-+ + |-- LLVMTidyModule.cpp + |-- LLVMTidyModule.h + ... + |-- tool/ # Sources of the clang-tidy binary. + ... + test/clang-tidy/ # Integration tests. + ... + unittests/clang-tidy/ # Unit tests. + |-- ClangTidyTest.h + |-- GoogleModuleTest.cpp + |-- LLVMModuleTest.cpp + ... + + +Writing a clang-tidy Check +-------------------------- + +So you have an idea of a useful check for :program:`clang-tidy`. + +You need to decide which module the check belongs to. If the check verifies +conformance of the code to a certain coding style, it probably deserves a +separate module and a directory in ``clang-tidy/`` (there are LLVM and Google +modules already). + +After choosing the module, you need to create a class for your check: + +.. code-block:: c++ + + #include "../ClangTidy.h" + + namespace clang { + namespace tidy { + + class MyCheck : public ClangTidyCheck { + }; + + } // namespace tidy + } // namespace clang + +Next, you need to decide whether it should operate on the preprocessor level or +on the AST level. Let's imagine that we need to work with the AST in our check. +In this case we need to override two methods: + +.. code-block:: c++ + + ... + class ExplicitConstructorCheck : public ClangTidyCheck { + public: + ExplicitConstructorCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(ast_matchers::MatchFinder::MatchResult &Result) override; + }; + +Constructor of the check receives the ``Name`` and ``Context`` parameters, and +must forward them to the ``ClangTidyCheck`` constructor. + +In the ``registerMatchers`` method we create an AST Matcher (see `AST Matchers`_ +for more information) that will find the pattern in the AST that we want to +inspect. The results of the matching are passed to the ``check`` method, which +can further inspect them and report diagnostics. + +.. code-block:: c++ + + using namespace ast_matchers; + + void ExplicitConstructorCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(constructorDecl().bind("ctor"), this); + } + + void ExplicitConstructorCheck::check(const MatchFinder::MatchResult &Result) { + const CXXConstructorDecl *Ctor = + Result.Nodes.getNodeAs("ctor"); + // Do not be confused: isExplicit means 'explicit' keyword is present, + // isImplicit means that it's a compiler-generated constructor. + if (Ctor->isOutOfLine() || Ctor->isExplicit() || Ctor->isImplicit()) + return; + if (Ctor->getNumParams() == 0 || Ctor->getMinRequiredArguments() > 1) + return; + SourceLocation Loc = Ctor->getLocation(); + diag(Loc, "single-argument constructors must be explicit") + << FixItHint::CreateInsertion(Loc, "explicit "); + } + +(The full code for this check resides in +``clang-tidy/google/ExplicitConstructorCheck.{h,cpp}``). + + +Registering your Check +---------------------- + +The check should be registered in the corresponding module with a distinct name: + +.. code-block:: c++ + + class MyModule : public ClangTidyModule { + public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "my-explicit-constructor"); + } + }; + +Now we need to register the module in the ``ClangTidyModuleRegistry`` using a +statically initialized variable: + +.. code-block:: c++ + + static ClangTidyModuleRegistry::Add X("my-module", + "Adds my lint checks."); + + +When using LLVM build system, we need to use the following hack to ensure the +module is linked into the clang-tidy binary: + +Add this near the ``ClangTidyModuleRegistry::Add`` variable: + +.. code-block:: c++ + + // This anchor is used to force the linker to link in the generated object file + // and thus register the MyModule. + volatile int MyModuleAnchorSource = 0; + +And this to the main translation unit of the clang-tidy binary (or the binary +you link the ``clang-tidy`` library in) ``clang-tidy/tool/ClangTidyMain.cpp``: + +.. code-block:: c++ + + // This anchor is used to force the linker to link the MyModule. + extern volatile int MyModuleAnchorSource; + static int MyModuleAnchorDestination = MyModuleAnchorSource; + + +Configuring Checks +------------------ + +If a check needs configuration options, it can access check-specific options +using the ``Options.get("SomeOption", DefaultValue)`` call in the check +constructor. In this case the check should also override the +``ClangTidyCheck::storeOptions`` method to make the options provided by the +check discoverable. This method lets :program:`clang-tidy` know which options +the check implements and what the current values are (e.g. for the +``-dump-config`` command line option). + +.. code-block:: c++ + + class MyCheck : public ClangTidyCheck { + const unsigned SomeOption1; + const std::string SomeOption2; + + public: + MyCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + SomeOption(Options.get("SomeOption1", -1U)), + SomeOption(Options.get("SomeOption2", "some default")) {} + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override { + Options.store(Opts, "SomeOption1", SomeOption1); + Options.store(Opts, "SomeOption2", SomeOption2); + } + ... + +Assuming the check is registered with the name "my-check", the option can then +be set in a ``.clang-tidy`` file in the following way: + +.. code-block:: yaml + + CheckOptions: + - key: my-check.SomeOption1 + value: 123 + - key: my-check.SomeOption2 + value: 'some other value' + +If you need to specify check options on a command line, you can use the inline +YAML format: + +.. code-block:: bash + + $ clang-tidy -config="{CheckOptions: [{key: a, value: b}, {key: x, value: y}]}" ... + + +Testing Checks +-------------- + +:program:`clang-tidy` checks can be tested using either unit tests or +`lit`_ tests. Unit tests may be more convenient to test complex replacements +with strict checks. `Lit`_ tests allow using partial text matching and regular +expressions which makes them more suitable for writing compact tests for +diagnostic messages. + +The ``check_clang_tidy.py`` script provides an easy way to test both +diagnostic messages and fix-its. It filters out ``CHECK`` lines from the test +file, runs :program:`clang-tidy` and verifies messages and fixes with two +separate `FileCheck`_ invocations. To use the script, put a .cpp file with the +appropriate ``RUN`` line in the ``test/clang-tidy`` directory. Use +``CHECK-MESSAGES:`` and ``CHECK-FIXES:`` lines to write checks against +diagnostic messages and fixed code. + +It's advised to make the checks as specific as possible to avoid checks matching +to incorrect parts of the input. Use ``[[@LINE+X]]``/``[[@LINE-X]]`` +substitutions and distinct function and variable names in the test code. + +Here's an example of a test using the ``check_clang_tidy.py`` script: + +.. code-block:: bash + + // RUN: %python %S/check_clang_tidy.py %s google-readability-casting %t + + void f(int a) { + int b = (int)a; + // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant cast to the same type [google-readability-casting] + // CHECK-FIXES: int b = a; + } + +.. _lit: http://llvm.org/docs/CommandGuide/lit.html +.. _FileCheck: http://llvm.org/docs/CommandGuide/FileCheck.html + + +Running clang-tidy on LLVM +-------------------------- + +To test a check it's best to try it out on a larger code base. LLVM and Clang +are the natural targets as you already have the source around. The most +convenient way to run :program:`clang-tidy` is with a compile command database; +CMake can automatically generate one, for a description of how to enable it see +`How To Setup Tooling For LLVM`_. Once ``compile_commands.json`` is in place and +a working version of :program:`clang-tidy` is in ``PATH`` the entire code base +can be analyzed with ``clang-tidy/tool/run-clang-tidy.py``. The script executes +:program:`clang-tidy` with the default set of checks on every translation unit +in the compile command database and displays the resulting warnings and errors. +The script provides multiple configuration flags. + +* The default set of checks can be overridden using the ``-checks`` argument, + taking the identical format as :program:`clang-tidy` does. For example + ``-checks=-*,modernize-use-override`` will run the ``modernize-use-override`` + check only. + +* To restrict the files examined you can provide one or more regex arguments + that the file names are matched against. + ``run-clang-tidy.py clang-tidy/.*Check\.cpp`` will only analyze clang-tidy + checkers. It may also be necessary to restrict the header files warnings are + displayed from using the ``-header-filter`` flag. It has the same behavior + as the corresponding :program:`clang-tidy` flag. + +* To apply suggested fixes ``-fix`` can be passed as an argument. This gathers + all changes in a temporary directory and applies them. Passing ``-format`` + will run clang-format over changed lines. + diff --git a/docs/clang-tidy/tools/dump_check_docs.py b/docs/clang-tidy/tools/dump_check_docs.py new file mode 100755 index 00000000..5f376e76 --- /dev/null +++ b/docs/clang-tidy/tools/dump_check_docs.py @@ -0,0 +1,79 @@ +#!/usr/bin/python + +r""" +Create stubs for check documentation files. +""" + +import os +import re +import sys + +def main(): + clang_tidy_dir = os.path.normpath( + os.path.join(os.path.dirname(sys.argv[0]), '..', '..', '..', + 'clang-tidy')) + + checks_doc_dir = os.path.normpath( + os.path.join(clang_tidy_dir, '..', 'docs', 'clang-tidy', 'checks')) + + registered_checks = {} + defined_checks = {} + + for dir_name, subdir_list, file_list in os.walk(clang_tidy_dir): + print('Processing directory ' + dir_name + '...') + for file_name in file_list: + full_name = os.path.join(dir_name, file_name) + if file_name.endswith('Module.cpp'): + print('Module ' + file_name) + with open(full_name, 'r') as f: + text = f.read() + for class_name, check_name in re.findall( + r'\.\s*registerCheck\s*<\s*([A-Za-z0-9:]+)\s*>\(\s*"([a-z0-9-]+)"', + text): + registered_checks[check_name] = class_name + elif file_name.endswith('.h'): + print(' ' + file_name + '...') + with open(full_name, 'r') as f: + text = f.read() + for comment, _, _, class_name in re.findall( + r'((([\r\n]//)[^\r\n]*)*)\s+class (\w+)\s*:' + + '\s*public\s+ClangTidyCheck\s*\{', text): + defined_checks[class_name] = comment + + print('Registered checks [%s]: [%s]' % + (len(registered_checks), registered_checks)) + print('Check implementations: %s' % len(defined_checks)) + + checks = registered_checks.keys() + checks.sort() + + for check_name in checks: + doc_file_name = os.path.join(checks_doc_dir, check_name + '.rst') + #if os.path.exists(doc_file_name): + # print('Skipping existing file %s...') + # continue + print('Updating %s...' % doc_file_name) + with open(doc_file_name, 'w') as f: + class_name = re.sub(r'.*:', '', registered_checks[check_name]) + f.write(check_name + '\n' + ('=' * len(check_name)) + '\n\n') + if class_name in defined_checks: + text = defined_checks[class_name] + text = re.sub(r'\n//+ ?(\\brief )?', r'\n', text) + text = re.sub(r'(\n *)\\code\n', r'\1.. code:: c++\n\n', text) + text = re.sub(r'(\n *)\\endcode(\n|$)', r'\n', text) + text = re.sub(r'`', r'``', text) + f.write(text + '\n') + else: + f.write('TODO: add docs\n') + + with open(os.path.join(checks_doc_dir, 'list.rst'), 'w') as f: + f.write( +r"""List of clang-tidy Checks +========================= + +.. toctree:: + """ + '\n '.join(checks)) + + +if __name__ == '__main__': + main() diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..2a2546a4 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,243 @@ +# -*- coding: utf-8 -*- +# +# Extra Clang Tools documentation build configuration file, created by +# sphinx-quickstart on Wed Feb 13 10:00:18 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os +from datetime import date + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.todo', 'sphinx.ext.mathjax'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Extra Clang Tools' +copyright = u'2007-%d, The Clang Team' % date.today().year + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '3.8' +# The full version, including alpha/beta/rc tags. +release = '3.8' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'friendly' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'haiku' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ExtraClangToolsdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'ExtraClangTools.tex', u'Extra Clang Tools Documentation', + u'The Clang Team', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'extraclangtools', u'Extra Clang Tools Documentation', + [u'The Clang Team'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'ExtraClangTools', u'Extra Clang Tools Documentation', + u'The Clang Team', 'ExtraClangTools', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' diff --git a/docs/cpp11-migrate.rst b/docs/cpp11-migrate.rst new file mode 100644 index 00000000..0a429611 --- /dev/null +++ b/docs/cpp11-migrate.rst @@ -0,0 +1,4 @@ +:orphan: + +All :program:`clang-modernize` transforms have moved to :doc:`clang-tidy/index` +(see the ``modernize`` module). diff --git a/docs/doxygen.cfg.in b/docs/doxygen.cfg.in new file mode 100644 index 00000000..644a2f55 --- /dev/null +++ b/docs/doxygen.cfg.in @@ -0,0 +1,2311 @@ +# Doxyfile 1.8.6 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = clang-tools + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = @PACKAGE_VERSION@ + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is included in +# the documentation. The maximum height of the logo should not exceed 55 pixels +# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo +# to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @abs_builddir@/doxygen + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = NO + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = ../.. + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = YES + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a +# new page for each member. If set to NO, the documentation of a member will be +# part of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 2 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. +# +# Note For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by by putting a % sign in front of the word +# or globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 2 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO these classes will be included in the various overviews. This option has +# no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the +# todo list. This list is created by putting \todo commands in the +# documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the +# test list. This list is created by putting \test commands in the +# documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES the list +# will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. Do not use file names with spaces, bibtex cannot handle them. See +# also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = NO + +# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = NO + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO doxygen will only warn about wrong or incomplete parameter +# documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. + +INPUT = \ + @abs_srcdir@/../clang-tidy \ + @abs_srcdir@/../clang-apply-replacements \ + @abs_srcdir@/../clang-query \ + @abs_srcdir@/../clang-rename \ + @abs_srcdir@/../modularize \ + @abs_srcdir@/../pp-trace \ + @abs_srcdir@/../tool-template \ + @abs_srcdir@/doxygen.intro + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = @abs_srcdir@/../examples + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = YES + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = @abs_srcdir@/img + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER ) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 4 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = clang:: + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- +# defined cascading style sheet that is included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefor more robust against future updates. +# Doxygen will copy the style sheet file to the output directory. For an example +# see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the stylesheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler ( hhc.exe). If non-empty +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated ( +# YES) or that it should be included in the master .chm file ( NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated ( +# YES) or a normal table of contents ( NO) in the .chm file. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = @clang_tools_doxygen_generate_qhp@ + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = @clang_tools_doxygen_qch_filename@ + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = @clang_tools_doxygen_qhp_namespace@ + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = @clang_tools_doxygen_qhp_cust_filter_name@ + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = @clang_tools_doxygen_qhp_cust_filter_attrs@ + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = @clang_tools_doxygen_qhelpgenerator_path@ + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /