diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000000..c8de983e494fc --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,66 @@ +# Contributing + +## License +Intel Project for LLVM* technology is licensed under the terms of the +Apache-2.0 with LLVM-exception license ( +[LICENSE.txt](https://github.com/intel/llvm/blob/intel/llvm/LICENSE.TXT)) +to ensure our ability to contribute this project to the LLVM project +under the same license. + +By contributing to this project, you agree to the Apache-2.0 with +LLVM-exception license and copyright terms there in and release your +contribution under these terms. + +## Sign your work +Please use the sign-off line at the end of your contribution. Your +signature certifies that you wrote the contribution or otherwise have +the right to pass it on as an open-source contribution, and that you +agree to provide your contribution under the terms of the licenses +noted above. The rules are pretty simple: if you can certify the +below (from [developercertificate.org](http://developercertificate.org)): + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +Then you just add a line to every git commit message: + + Signed-off-by: Joe Smith + +Use your real name (sorry, no pseudonyms or anonymous contributions.) + +If you set your `user.name` and `user.email` git configs, you can sign your commit automatically with `git commit -s`. diff --git a/README.md b/README.md index de0891a7044a9..0ed7d42675789 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,19 @@ -# The LLVM Compiler Infrastructure +# Intel Project for LLVM* technology -This directory and its subdirectories contain source code for LLVM, -a toolkit for the construction of highly optimized compilers, -optimizers, and runtime environments. +## Introduction + +Intel staging area for llvm.org contribution. +Home for Intel LLVM-based projects: + - SYCL* Compiler and Runtimes - compiler and runtime libraries for SYCL ([https://www.khronos.org/sycl/](https://www.khronos.org/sycl/)). See **sycl** branch. + +## License +See [LICENSE.txt](https://github.com/intel/llvm/blob/intel/llvm/LICENSE.TXT) for details. + + +## Contributing +See [CONTRIBUTING.md](https://github.com/intel/llvm/blob/intel/CONTRIBUTING.md) for details. + +## Sub-projects Documentation + - SYCL Compiler and Runtimes - See [GetStartedWithSYCLCompiler.md](https://github.com/intel/llvm/blob/sycl/sycl/doc/GetStartedWithSYCLCompiler.md) + +*Other names and brands may be claimed as the property of others. diff --git a/buildbot/README.md b/buildbot/README.md new file mode 100644 index 0000000000000..930528c5867fb --- /dev/null +++ b/buildbot/README.md @@ -0,0 +1,37 @@ +# Scripts for build steps on Buildbot + +## Purpose + +The purpose of the script is for developer to customize build command for the builder on Buildbot. + +## How it works + +The scripts will be run by Buildbot at corresponding build step, for example, the "compile" step will run "compile.sh". Developer can change the build command, then the builder (e.g. pull request builder) will use the changed command to do the build. + +## Arguments for the scripts + +Common + +* -b BRANCH: the branch name to build +* -n BUILD\_NUMBER: the Buildbot build number (the build count of one builder, which will be in the url of one specific build) +* -r PR\_NUMBER: if it's a pull request build, this will be the pull request number + +LIT Test (check.sh) + +* -t TESTCASE: the LIT testing target, e.g. check-sycl + +## Assumptions + +The Buildbot worker directory structure is: + + /path/to/WORKER_ROOT/BUILDER/ + llvm.src --> source code + llvm.obj --> build directory + +Initial working directory of the scripts: + +* dependency.sh : llvm.obj +* configure.sh : llvm.obj +* compile.sh : llvm.obj +* clang-tidy.sh : llvm.src +* check.sh : llvm.obj diff --git a/buildbot/check.sh b/buildbot/check.sh new file mode 100755 index 0000000000000..49510dfdbcde0 --- /dev/null +++ b/buildbot/check.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +BRANCH= +BUILD_NUMBER= +PR_NUMBER= +TESTCASE= + +# $1 exit code +# $2 error message +exit_if_err() +{ + if [ $1 -ne 0 ]; then + echo "Error: $2" + exit $1 + fi +} + +unset OPTIND +while getopts ":b:r:n:t:" option; do + case $option in + b) BRANCH=$OPTARG ;; + n) BUILD_NUMBER=$OPTARG ;; + r) PR_NUMBER=$OPTARG ;; + t) TESTCASE=$OPTARG ;; + esac +done && shift $(($OPTIND - 1)) + +# we're in llvm.obj dir +BUILD_DIR=${PWD} + +if [ -z "${TESTCASE}" ]; then + echo "No target provided" + exit 1 +fi + +make ${TESTCASE} VERBOSE=1 -j`nproc` LIT_ARGS="-v" diff --git a/buildbot/clang-tidy.sh b/buildbot/clang-tidy.sh new file mode 100755 index 0000000000000..e5f507a3654ab --- /dev/null +++ b/buildbot/clang-tidy.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +BRANCH= +BUILD_NUMBER= +PR_NUMBER= + +# $1 exit code +# $2 error message +exit_if_err() +{ + if [ $1 -ne 0 ]; then + echo "Error: $2" + exit $1 + fi +} + +unset OPTIND +while getopts ":b:r:n:" option; do + case $option in + b) BRANCH=$OPTARG ;; + n) BUILD_NUMBER=$OPTARG ;; + r) PR_NUMBER=$OPTARG ;; + esac +done && shift $(($OPTIND - 1)) + +if [ -z "${PR_NUMBER}" ]; then + echo "No PR number provided" + exit 1 +fi + +# we're in llvm.src dir +SRC_DIR=${PWD} +BUILDER_DIR=$(cd ..; pwd) + +# Get changed files +base_commit=`git merge-base origin/sycl refs/pull/${PR_NUMBER}/merge` +exit_if_err $? "fail to get base commit" + +path_list_file=${BUILDER_DIR}/changed_files.txt +git --no-pager diff ${base_commit} refs/pull/${PR_NUMBER}/merge --name-only > ${path_list_file} +cat ${path_list_file} + +# Run clang-tidy +while IFS='' read -r line ; do + file_name=$(basename ${line}) + file_ext=${file_name##*.} + if [[ "${file_ext}" == "h" || "${file_ext}" == "hpp" || "${file_ext}" == "c" || "${file_ext}" == "cc" || "${file_ext}" == "cpp" ]]; then + ${BUILDER_DIR}/llvm.obj/bin/clang-tidy ${line} + fi +done < "${path_list_file}" diff --git a/buildbot/compile.sh b/buildbot/compile.sh new file mode 100755 index 0000000000000..cfd92a8f43fae --- /dev/null +++ b/buildbot/compile.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +BRANCH= +BUILD_NUMBER= +PR_NUMBER= + +# $1 exit code +# $2 error message +exit_if_err() +{ + if [ $1 -ne 0 ]; then + echo "Error: $2" + exit $1 + fi +} + +unset OPTIND +while getopts ":b:r:n:" option; do + case $option in + b) BRANCH=$OPTARG ;; + n) BUILD_NUMBER=$OPTARG ;; + r) PR_NUMBER=$OPTARG ;; + esac +done && shift $(($OPTIND - 1)) + +# we're in llvm.obj dir +BUILD_DIR=${PWD} + +make -j`nproc` sycl-toolchain diff --git a/buildbot/configure.sh b/buildbot/configure.sh new file mode 100755 index 0000000000000..15defea6778c5 --- /dev/null +++ b/buildbot/configure.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +BRANCH= +BUILD_NUMBER= +PR_NUMBER= + +# $1 exit code +# $2 error message +exit_if_err() +{ + if [ $1 -ne 0 ]; then + echo "Error: $2" + exit $1 + fi +} + +unset OPTIND +while getopts ":b:r:n:" option; do + case $option in + b) BRANCH=$OPTARG ;; + n) BUILD_NUMBER=$OPTARG ;; + r) PR_NUMBER=$OPTARG ;; + esac +done && shift $(($OPTIND - 1)) + +# we're in llvm.obj dir +BUILD_DIR=${PWD} + +CMAKE_ARGS="-DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang \ + -DLLVM_EXTERNAL_SYCL_SOURCE_DIR=../llvm.src/sycl \ + -DLLVM_EXTERNAL_LLVM_SPIRV_SOURCE_DIR=../llvm.src/llvm-spirv \ + -DLLVM_TOOL_SYCL_BUILD=ON -DLLVM_TOOL_LLVM_SPIRV_BUILD=ON \ + -DOpenCL_INCLUDE_DIR=OpenCL-Headers \ + -DOpenCL_LIBRARY=OpenCL-ICD-Loader/build/libOpenCL.so \ + -DLLVM_BUILD_TOOLS=OFF \ + ../llvm.src/llvm" + +cmake ${CMAKE_ARGS} +# Do clean build if configure failed due to any reason +if [ $? -ne 0 ]; then + rm -f ${BUILD_DIR}/CMakeCache.txt + cmake ${CMAKE_ARGS} +fi diff --git a/buildbot/dependency.sh b/buildbot/dependency.sh new file mode 100755 index 0000000000000..c66d993ca3af6 --- /dev/null +++ b/buildbot/dependency.sh @@ -0,0 +1,83 @@ +#!/bin/bash -x + +BRANCH= +BUILD_NUMBER= +PR_NUMBER= +SRC_DIR="../llvm.src" +DST_DIR="." + +# $1 exit code +# $2 error message +exit_if_err() +{ + if [ $1 -ne 0 ]; then + echo "Error: $2" + exit $1 + fi +} + +unset OPTIND +while getopts ":b:r:n:s:d" option; do + case $option in + b) BRANCH=$OPTARG ;; + n) BUILD_NUMBER=$OPTARG ;; + s) SRC_DIR=$OPTARG ;; + d) DST_DIR=$OPTARG ;; + r) PR_NUMBER=$OPTARG ;; + esac +done && shift $(($OPTIND - 1)) + +# we're in llvm.obj dir +BUILD_DIR=${PWD} + +# Get changed build script files if it is PR +if [ -n "${PR_NUMBER}" ];then + cd ${SRC_DIR} + git fetch origin sycl + exit_if_err $? "fail to get the latest changes in sycl branch" + git fetch -t origin refs/pull/${PR_NUMBER}/merge + exit_if_err $? "fail to get tags" + git checkout -B refs/pull/${PR_NUMBER}/merge + exit_if_err $? "fail to create branch for specific tag" + base_commit=`git merge-base origin/sycl refs/pull/${PR_NUMBER}/merge` + exit_if_err $? "fail to get base commit" + + BUILD_SCRIPT=`git --no-pager diff ${base_commit} refs/pull/${PR_NUMBER}/merge --name-only buildbot` + cd - +fi + +## Clean up build directory if build scripts has changed +cd ${DST_DIR} +if [ -n "$BUILD_SCRIPT" ]; then + rm -rf * +fi +cd - + +## GET dependencies +if [ ! -d "OpenCL-Headers" ]; then + git clone https://github.com/KhronosGroup/OpenCL-Headers OpenCL-Headers + exit_if_err $? "failed to clone OpenCL-Headers" +else + cd OpenCL-Headers + git pull --ff --ff-only origin + exit_if_err $? "failed to update OpenCL-Headers" +fi + +OPENCL_HEADERS=${BUILD_DIR}/OpenCL-Headers + +cd ${BUILD_DIR} +if [ ! -d "OpenCL-ICD-Loader" ]; then + git clone https://github.com/KhronosGroup/OpenCL-ICD-Loader OpenCL-ICD-Loader + exit_if_err $? "failed to clone OpenCL-ICD-Loader" +else + cd OpenCL-ICD-Loader + git pull --ff --ff-only origin + exit_if_err $? "failed to update OpenCL-ICD-Loader" +fi + +cd ${BUILD_DIR}/OpenCL-ICD-Loader +mkdir build +cd build +cmake .. +make C_INCLUDE_PATH=${OPENCL_HEADERS} +exit_if_err $? "failed to build OpenCL-ICD-Loader" diff --git a/clang-tools-extra/CMakeLists.txt b/clang-tools-extra/CMakeLists.txt index d32b4b9f9eb95..dbb89e6257ff6 100644 --- a/clang-tools-extra/CMakeLists.txt +++ b/clang-tools-extra/CMakeLists.txt @@ -1,3 +1,5 @@ +include(CMakeDependentOption) + add_subdirectory(clang-apply-replacements) add_subdirectory(clang-reorder-fields) add_subdirectory(modularize) @@ -9,7 +11,6 @@ add_subdirectory(clang-doc) add_subdirectory(clang-include-fixer) add_subdirectory(clang-move) add_subdirectory(clang-query) -add_subdirectory(clangd) add_subdirectory(pp-trace) add_subdirectory(tool-template) @@ -25,3 +26,9 @@ if( CLANG_TOOLS_EXTRA_INCLUDE_DOCS ) add_subdirectory(docs) endif() +# clangd has its own CMake tree. It requires threads. +CMAKE_DEPENDENT_OPTION(CLANG_ENABLE_CLANGD "Build clangd language server" ON + "LLVM_ENABLE_THREADS" OFF) +if (CLANG_ENABLE_CLANGD) + add_subdirectory(clangd) +endif() diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp index 0abd63ee56bdd..68f38c41fc929 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp @@ -166,11 +166,13 @@ class ClangTidyContext::CachedGlobList { bool contains(StringRef S) { switch (auto &Result = Cache[S]) { - case Yes: return true; - case No: return false; - case None: - Result = Globs.contains(S) ? Yes : No; - return Result == Yes; + case Yes: + return true; + case No: + return false; + case None: + Result = Globs.contains(S) ? Yes : No; + return Result == Yes; } llvm_unreachable("invalid enum"); } @@ -387,16 +389,30 @@ static bool LineIsMarkedWithNOLINTinMacro(const SourceManager &SM, return false; } +namespace clang { +namespace tidy { + +bool ShouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel, + const Diagnostic &Info, ClangTidyContext &Context, + bool CheckMacroExpansion) { + return Info.getLocation().isValid() && + DiagLevel != DiagnosticsEngine::Error && + DiagLevel != DiagnosticsEngine::Fatal && + (CheckMacroExpansion ? LineIsMarkedWithNOLINTinMacro + : LineIsMarkedWithNOLINT)(Info.getSourceManager(), + Info.getLocation(), + Info.getID(), Context); +} + +} // namespace tidy +} // namespace clang + void ClangTidyDiagnosticConsumer::HandleDiagnostic( DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) { if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note) return; - if (Info.getLocation().isValid() && DiagLevel != DiagnosticsEngine::Error && - DiagLevel != DiagnosticsEngine::Fatal && - LineIsMarkedWithNOLINTinMacro(Info.getSourceManager(), - Info.getLocation(), Info.getID(), - Context)) { + if (ShouldSuppressDiagnostic(DiagLevel, Info, Context)) { ++Context.Stats.ErrorsIgnoredNOLINT; // Ignored a warning, should ignore related notes as well LastErrorWasIgnored = true; diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h index 400f0dc6785b6..dc4270f03fdad 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h +++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h @@ -217,6 +217,23 @@ class ClangTidyContext { bool AllowEnablingAnalyzerAlphaCheckers; }; +/// Check whether a given diagnostic should be suppressed due to the presence +/// of a "NOLINT" suppression comment. +/// This is exposed so that other tools that present clang-tidy diagnostics +/// (such as clangd) can respect the same suppression rules as clang-tidy. +/// This does not handle suppression of notes following a suppressed diagnostic; +/// that is left to the caller is it requires maintaining state in between calls +/// to this function. +/// The `CheckMacroExpansion` parameter determines whether the function should +/// handle the case where the diagnostic is inside a macro expansion. A degree +/// of control over this is needed because handling this case can require +/// examining source files other than the one in which the diagnostic is +/// located, and in some use cases we cannot rely on such other files being +/// mapped in the SourceMapper. +bool ShouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel, + const Diagnostic &Info, ClangTidyContext &Context, + bool CheckMacroExpansion = true); + /// \brief A diagnostic consumer that turns each \c Diagnostic into a /// \c SourceManager-independent \c ClangTidyError. // @@ -246,7 +263,7 @@ class ClangTidyDiagnosticConsumer : public DiagnosticConsumer { /// \brief Updates \c LastErrorRelatesToUserCode and LastErrorPassesLineFilter /// according to the diagnostic \p Location. - void checkFilters(SourceLocation Location, const SourceManager& Sources); + void checkFilters(SourceLocation Location, const SourceManager &Sources); bool passesLineFilter(StringRef FileName, unsigned LineNumber) const; ClangTidyContext &Context; diff --git a/clang-tools-extra/clang-tidy/add_new_check.py b/clang-tools-extra/clang-tidy/add_new_check.py index 8f96cbd874e6f..6a06e6dea5d96 100755 --- a/clang-tools-extra/clang-tidy/add_new_check.py +++ b/clang-tools-extra/clang-tidy/add_new_check.py @@ -46,7 +46,7 @@ def adapt_cmake(module_path, check_name_camel): # Adds a header for the new check. -def write_header(module_path, module, check_name, check_name_camel): +def write_header(module_path, module, namespace, 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) @@ -73,7 +73,7 @@ def write_header(module_path, module, check_name, check_name_camel): namespace clang { namespace tidy { -namespace %(module)s { +namespace %(namespace)s { /// FIXME: Write a short description. /// @@ -87,7 +87,7 @@ class %(check_name)s : public ClangTidyCheck { void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; -} // namespace %(module)s +} // namespace %(namespace)s } // namespace tidy } // namespace clang @@ -95,11 +95,12 @@ class %(check_name)s : public ClangTidyCheck { """ % {'header_guard': header_guard, 'check_name': check_name_camel, 'check_name_dashes': check_name_dashes, - 'module': module}) + 'module': module, + 'namespace': namespace}) # Adds the implementation of the new check. -def write_implementation(module_path, module, check_name_camel): +def write_implementation(module_path, module, namespace, check_name_camel): filename = os.path.join(module_path, check_name_camel) + '.cpp' print('Creating %s...' % filename) with open(filename, 'w') as f: @@ -124,7 +125,7 @@ def write_implementation(module_path, module, check_name_camel): namespace clang { namespace tidy { -namespace %(module)s { +namespace %(namespace)s { void %(check_name)s::registerMatchers(MatchFinder *Finder) { // FIXME: Add matchers. @@ -142,11 +143,12 @@ def write_implementation(module_path, module, check_name_camel): << FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_"); } -} // namespace %(module)s +} // namespace %(namespace)s } // namespace tidy } // namespace clang """ % {'check_name': check_name_camel, - 'module': module}) + 'module': module, + 'namespace': namespace}) # Modifies the module to include the new check. @@ -375,8 +377,15 @@ def main(): 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) + + # Map module names to namespace names that don't conflict with widely used top-level namespaces. + if module == 'llvm': + namespace = module + '_check' + else: + namespace = module + + write_header(module_path, module, namespace, check_name, check_name_camel) + write_implementation(module_path, module, namespace, check_name_camel) adapt_module(module_path, module, check_name, check_name_camel) add_release_notes(module_path, module, check_name) test_extension = language_to_extension.get(args.language) diff --git a/clang-tools-extra/clang-tidy/android/CloexecAcceptCheck.cpp b/clang-tools-extra/clang-tidy/android/CloexecAcceptCheck.cpp index b26ad75df9869..b79c55417077c 100644 --- a/clang-tools-extra/clang-tidy/android/CloexecAcceptCheck.cpp +++ b/clang-tools-extra/clang-tidy/android/CloexecAcceptCheck.cpp @@ -29,7 +29,7 @@ void CloexecAcceptCheck::registerMatchers(MatchFinder *Finder) { } void CloexecAcceptCheck::check(const MatchFinder::MatchResult &Result) { - const std::string &ReplacementText = + std::string ReplacementText = (Twine("accept4(") + getSpellingArg(Result, 0) + ", " + getSpellingArg(Result, 1) + ", " + getSpellingArg(Result, 2) + ", SOCK_CLOEXEC)") diff --git a/clang-tools-extra/clang-tidy/android/CloexecCheck.cpp b/clang-tools-extra/clang-tidy/android/CloexecCheck.cpp index 148839a6d1ac0..64c8797934d23 100644 --- a/clang-tools-extra/clang-tidy/android/CloexecCheck.cpp +++ b/clang-tools-extra/clang-tidy/android/CloexecCheck.cpp @@ -90,7 +90,7 @@ void CloexecCheck::insertStringFlag( if (!ModeStr || (ModeStr->getString().find(Mode) != StringRef::npos)) return; - const std::string &ReplacementText = buildFixMsgForStringFlag( + std::string ReplacementText = buildFixMsgForStringFlag( ModeArg, *Result.SourceManager, Result.Context->getLangOpts(), Mode); diag(ModeArg->getBeginLoc(), "use %0 mode '%1' to set O_CLOEXEC") diff --git a/clang-tools-extra/clang-tidy/android/CloexecDupCheck.cpp b/clang-tools-extra/clang-tidy/android/CloexecDupCheck.cpp index 9a676d1498c4d..1f068d49f777e 100644 --- a/clang-tools-extra/clang-tidy/android/CloexecDupCheck.cpp +++ b/clang-tools-extra/clang-tidy/android/CloexecDupCheck.cpp @@ -23,7 +23,7 @@ void CloexecDupCheck::registerMatchers(MatchFinder *Finder) { } void CloexecDupCheck::check(const MatchFinder::MatchResult &Result) { - const std::string &ReplacementText = + std::string ReplacementText = (Twine("fcntl(") + getSpellingArg(Result, 0) + ", F_DUPFD_CLOEXEC)") .str(); diff --git a/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp new file mode 100644 index 0000000000000..a89831188b7e2 --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp @@ -0,0 +1,226 @@ +//===--- BranchCloneCheck.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 "BranchCloneCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Analysis/CloneDetection.h" +#include "llvm/Support/Casting.h" + +using namespace clang; +using namespace clang::ast_matchers; + +/// Returns true when the statements are Type I clones of each other. +static bool areStatementsIdentical(const Stmt *LHS, const Stmt *RHS, + const ASTContext &Context) { + llvm::FoldingSetNodeID DataLHS, DataRHS; + LHS->Profile(DataLHS, Context, false); + RHS->Profile(DataRHS, Context, false); + return (DataLHS == DataRHS); +} + +namespace { +/// A branch in a switch may consist of several statements; while a branch in +/// an if/else if/else chain is one statement (which may be a CompoundStmt). +using SwitchBranch = llvm::SmallVector; +} // anonymous namespace + +/// Determines if the bodies of two branches in a switch statements are Type I +/// clones of each other. This function only examines the body of the branch +/// and ignores the `case X:` or `default:` at the start of the branch. +static bool areSwitchBranchesIdentical(const SwitchBranch LHS, + const SwitchBranch RHS, + const ASTContext &Context) { + if (LHS.size() != RHS.size()) + return false; + + for (size_t i = 0, Size = LHS.size(); i < Size; i++) { + // NOTE: We strip goto labels and annotations in addition to stripping + // the `case X:` or `default:` labels, but it is very unlikely that this + // would casue false positives in real-world code. + if (!areStatementsIdentical(LHS[i]->stripLabelLikeStatements(), + RHS[i]->stripLabelLikeStatements(), Context)) { + return false; + } + } + + return true; +} + +namespace clang { +namespace tidy { +namespace bugprone { + +void BranchCloneCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + ifStmt(stmt().bind("if"), + hasParent(stmt(unless(ifStmt(hasElse(equalsBoundNode("if")))))), + hasElse(stmt().bind("else"))), + this); + Finder->addMatcher(switchStmt().bind("switch"), this); + Finder->addMatcher(conditionalOperator().bind("condOp"), this); +} + +void BranchCloneCheck::check(const MatchFinder::MatchResult &Result) { + const ASTContext &Context = *Result.Context; + + if (const auto *IS = Result.Nodes.getNodeAs("if")) { + const Stmt *Then = IS->getThen(); + assert(Then && "An IfStmt must have a `then` branch!"); + + const Stmt *Else = Result.Nodes.getNodeAs("else"); + assert(Else && "We only look for `if` statements with an `else` branch!"); + + if (!isa(Else)) { + // Just a simple if with no `else if` branch. + if (areStatementsIdentical(Then->IgnoreContainers(), + Else->IgnoreContainers(), Context)) { + diag(IS->getBeginLoc(), "if with identical then and else branches"); + diag(IS->getElseLoc(), "else branch starts here", DiagnosticIDs::Note); + } + return; + } + + // This is the complicated case when we start an if/else if/else chain. + // To find all the duplicates, we collect all the branches into a vector. + llvm::SmallVector Branches; + const IfStmt *Cur = IS; + while (true) { + // Store the `then` branch. + Branches.push_back(Cur->getThen()); + + Else = Cur->getElse(); + // The chain ends if there is no `else` branch. + if (!Else) + break; + + // Check if there is another `else if`... + Cur = dyn_cast(Else); + if (!Cur) { + // ...this is just a plain `else` branch at the end of the chain. + Branches.push_back(Else); + break; + } + } + + size_t N = Branches.size(); + llvm::BitVector KnownAsClone(N); + + for (size_t i = 0; i + 1 < N; i++) { + // We have already seen Branches[i] as a clone of an earlier branch. + if (KnownAsClone[i]) + continue; + + int NumCopies = 1; + + for (size_t j = i + 1; j < N; j++) { + if (KnownAsClone[j] || + !areStatementsIdentical(Branches[i]->IgnoreContainers(), + Branches[j]->IgnoreContainers(), Context)) + continue; + + NumCopies++; + KnownAsClone[j] = true; + + if (NumCopies == 2) { + // We report the first occurence only when we find the second one. + diag(Branches[i]->getBeginLoc(), + "repeated branch in conditional chain"); + diag(Lexer::getLocForEndOfToken(Branches[i]->getEndLoc(), 0, + *Result.SourceManager, getLangOpts()), + "end of the original", DiagnosticIDs::Note); + } + + diag(Branches[j]->getBeginLoc(), "clone %0 starts here", + DiagnosticIDs::Note) + << (NumCopies - 1); + } + } + return; + } + + if (const auto *CO = Result.Nodes.getNodeAs("condOp")) { + // We do not try to detect chains of ?: operators. + if (areStatementsIdentical(CO->getTrueExpr(), CO->getFalseExpr(), Context)) + diag(CO->getQuestionLoc(), + "conditional operator with identical true and false expressions"); + + return; + } + + if (const auto *SS = Result.Nodes.getNodeAs("switch")) { + const CompoundStmt *Body = dyn_cast_or_null(SS->getBody()); + + // Code like + // switch (x) case 0: case 1: foobar(); + // is legal and calls foobar() if and only if x is either 0 or 1; + // but we do not try to distinguish branches in such code. + if (!Body) + return; + + // We will first collect the branches of the switch statements. For the + // sake of simplicity we say that branches are delimited by the SwitchCase + // (`case:` or `default:`) children of Body; that is, we ignore `case:` or + // `default:` labels embedded inside other statements and we do not follow + // the effects of `break` and other manipulation of the control-flow. + llvm::SmallVector Branches; + for (const Stmt *S : Body->body()) { + // If this is a `case` or `default`, we start a new, empty branch. + if (isa(S)) + Branches.emplace_back(); + + // There may be code before the first branch (which can be dead code + // and can be code reached either through goto or through case labels + // that are embedded inside e.g. inner compound statements); we do not + // store those statements in branches. + if (!Branches.empty()) + Branches.back().push_back(S); + } + + auto End = Branches.end(); + auto BeginCurrent = Branches.begin(); + while (BeginCurrent < End) { + auto EndCurrent = BeginCurrent + 1; + while (EndCurrent < End && + areSwitchBranchesIdentical(*BeginCurrent, *EndCurrent, Context)) { + ++EndCurrent; + } + // At this point the iterator range {BeginCurrent, EndCurrent} contains a + // complete family of consecutive identical branches. + if (EndCurrent > BeginCurrent + 1) { + diag(BeginCurrent->front()->getBeginLoc(), + "switch has %0 consecutive identical branches") + << static_cast(std::distance(BeginCurrent, EndCurrent)); + + SourceLocation EndLoc = (EndCurrent - 1)->back()->getEndLoc(); + // If the case statement is generated from a macro, it's SourceLocation + // may be invalid, resuling in an assertation failure down the line. + // While not optimal, try the begin location in this case, it's still + // better then nothing. + if (EndLoc.isInvalid()) + EndLoc = (EndCurrent - 1)->back()->getBeginLoc(); + + if (EndLoc.isMacroID()) + EndLoc = Context.getSourceManager().getExpansionLoc(EndLoc); + + diag(Lexer::getLocForEndOfToken(EndLoc, 0, *Result.SourceManager, + getLangOpts()), + "last of these clones ends here", DiagnosticIDs::Note); + } + BeginCurrent = EndCurrent; + } + return; + } + + llvm_unreachable("No if statement and no switch statement."); +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.h b/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.h new file mode 100644 index 0000000000000..4a3eadf68f348 --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.h @@ -0,0 +1,39 @@ +//===--- BranchCloneCheck.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_BUGPRONE_BRANCHCLONECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_BRANCHCLONECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// A check for detecting if/else if/else chains where two or more branches are +/// Type I clones of each other (that is, they contain identical code), for +/// detecting switch statements where two or more consecutive branches are +/// Type I clones of each other, and for detecting conditional operators where +/// the true and false expressions are Type I clones of each other. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-branch-clone.html +class BranchCloneCheck : public ClangTidyCheck { +public: + BranchCloneCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_BRANCHCLONECHECK_H diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp index 01bc0e5418c51..e6f555fc178c9 100644 --- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -13,6 +13,7 @@ #include "ArgumentCommentCheck.h" #include "AssertSideEffectCheck.h" #include "BoolPointerImplicitConversionCheck.h" +#include "BranchCloneCheck.h" #include "CopyConstructorInitCheck.h" #include "DanglingHandleCheck.h" #include "ExceptionEscapeCheck.h" @@ -46,6 +47,7 @@ #include "TooSmallLoopVariableCheck.h" #include "UndefinedMemoryManipulationCheck.h" #include "UndelegatedConstructorCheck.h" +#include "UnhandledSelfAssignmentCheck.h" #include "UnusedRaiiCheck.h" #include "UnusedReturnValueCheck.h" #include "UseAfterMoveCheck.h" @@ -64,6 +66,8 @@ class BugproneModule : public ClangTidyModule { "bugprone-assert-side-effect"); CheckFactories.registerCheck( "bugprone-bool-pointer-implicit-conversion"); + CheckFactories.registerCheck( + "bugprone-branch-clone"); CheckFactories.registerCheck( "bugprone-copy-constructor-init"); CheckFactories.registerCheck( @@ -96,8 +100,6 @@ class BugproneModule : public ClangTidyModule { "bugprone-move-forwarding-reference"); CheckFactories.registerCheck( "bugprone-multiple-statement-macro"); - CheckFactories.registerCheck( - "bugprone-too-small-loop-variable"); CheckFactories.registerCheck( "bugprone-narrowing-conversions"); CheckFactories.registerCheck( @@ -128,10 +130,14 @@ class BugproneModule : public ClangTidyModule { "bugprone-terminating-continue"); CheckFactories.registerCheck( "bugprone-throw-keyword-missing"); + CheckFactories.registerCheck( + "bugprone-too-small-loop-variable"); CheckFactories.registerCheck( "bugprone-undefined-memory-manipulation"); CheckFactories.registerCheck( "bugprone-undelegated-constructor"); + CheckFactories.registerCheck( + "bugprone-unhandled-self-assignment"); CheckFactories.registerCheck( "bugprone-unused-raii"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt index a8c64b7906590..571faec270a65 100644 --- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt @@ -4,6 +4,7 @@ add_clang_library(clangTidyBugproneModule ArgumentCommentCheck.cpp AssertSideEffectCheck.cpp BoolPointerImplicitConversionCheck.cpp + BranchCloneCheck.cpp BugproneTidyModule.cpp CopyConstructorInitCheck.cpp DanglingHandleCheck.cpp @@ -38,6 +39,7 @@ add_clang_library(clangTidyBugproneModule TooSmallLoopVariableCheck.cpp UndefinedMemoryManipulationCheck.cpp UndelegatedConstructorCheck.cpp + UnhandledSelfAssignmentCheck.cpp UnusedRaiiCheck.cpp UnusedReturnValueCheck.cpp UseAfterMoveCheck.cpp diff --git a/clang-tools-extra/clang-tidy/bugprone/InaccurateEraseCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/InaccurateEraseCheck.cpp index b236e9925fc41..95babc7a567d3 100644 --- a/clang-tools-extra/clang-tidy/bugprone/InaccurateEraseCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/InaccurateEraseCheck.cpp @@ -17,10 +17,6 @@ namespace clang { namespace tidy { namespace bugprone { -namespace { -AST_MATCHER(Decl, isInStdNamespace) { return Node.isInStdNamespace(); } -} - 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. diff --git a/clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.cpp index 959e0ae39b38b..bb23202197fa2 100644 --- a/clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.cpp @@ -84,8 +84,11 @@ void SizeofExpressionCheck::registerMatchers(MatchFinder *Finder) { const auto IntegerCallExpr = expr(ignoringParenImpCasts( callExpr(anyOf(hasType(isInteger()), hasType(enumType())), unless(isInTemplateInstantiation())))); - const auto SizeOfExpr = - expr(anyOf(sizeOfExpr(has(type())), sizeOfExpr(has(expr())))); + const auto SizeOfExpr = expr(anyOf( + sizeOfExpr( + has(hasUnqualifiedDesugaredType(type().bind("sizeof-arg-type")))), + sizeOfExpr(has(expr(hasType( + hasUnqualifiedDesugaredType(type().bind("sizeof-arg-type")))))))); const auto SizeOfZero = expr( sizeOfExpr(has(ignoringParenImpCasts(expr(integerLiteral(equals(0))))))); @@ -142,10 +145,17 @@ void SizeofExpressionCheck::registerMatchers(MatchFinder *Finder) { unaryOperator(hasOperatorName("&"), hasUnaryOperand(ignoringParenImpCasts(expr( hasType(qualType(hasCanonicalType(recordType()))))))); + const auto PointerToStructType = type(hasUnqualifiedDesugaredType( + pointerType(pointee(recordType())))); + const auto PointerToStructExpr = expr(ignoringParenImpCasts(expr( + hasType(qualType(hasCanonicalType(PointerToStructType))), + unless(cxxThisExpr())))); Finder->addMatcher( - expr(sizeOfExpr(has(expr(ignoringParenImpCasts( - anyOf(ArrayCastExpr, PointerToArrayExpr, StructAddrOfExpr)))))) + expr(anyOf(sizeOfExpr(has(expr(ignoringParenImpCasts( + anyOf(ArrayCastExpr, PointerToArrayExpr, StructAddrOfExpr, + PointerToStructExpr))))), + sizeOfExpr(has(PointerToStructType)))) .bind("sizeof-pointer-to-aggregate"), this); @@ -209,6 +219,36 @@ void SizeofExpressionCheck::registerMatchers(MatchFinder *Finder) { hasSizeOfDescendant(8, expr(SizeOfExpr, unless(SizeOfZero)))))))) .bind("sizeof-sizeof-expr"), this); + + // Detect sizeof in pointer aritmetic like: N * sizeof(S) == P1 - P2 or + // (P1 - P2) / sizeof(S) where P1 and P2 are pointers to type S. + const auto PtrDiffExpr = binaryOperator( + hasOperatorName("-"), + hasLHS(expr(hasType(hasUnqualifiedDesugaredType(pointerType(pointee( + hasUnqualifiedDesugaredType(type().bind("left-ptr-type")))))))), + hasRHS(expr(hasType(hasUnqualifiedDesugaredType(pointerType(pointee( + hasUnqualifiedDesugaredType(type().bind("right-ptr-type"))))))))); + + Finder->addMatcher( + binaryOperator( + anyOf(hasOperatorName("=="), hasOperatorName("!="), + hasOperatorName("<"), hasOperatorName("<="), + hasOperatorName(">"), hasOperatorName(">="), + hasOperatorName("+"), hasOperatorName("-")), + hasEitherOperand(expr(anyOf( + ignoringParenImpCasts(SizeOfExpr), + ignoringParenImpCasts(binaryOperator( + hasOperatorName("*"), + hasEitherOperand(ignoringParenImpCasts(SizeOfExpr))))))), + hasEitherOperand(ignoringParenImpCasts(PtrDiffExpr))) + .bind("sizeof-in-ptr-arithmetic-mul"), + this); + + Finder->addMatcher(binaryOperator(hasOperatorName("/"), + hasLHS(ignoringParenImpCasts(PtrDiffExpr)), + hasRHS(ignoringParenImpCasts(SizeOfExpr))) + .bind("sizeof-in-ptr-arithmetic-div"), + this); } void SizeofExpressionCheck::check(const MatchFinder::MatchResult &Result) { @@ -275,6 +315,26 @@ void SizeofExpressionCheck::check(const MatchFinder::MatchResult &Result) { } else if (const auto *E = Result.Nodes.getNodeAs("sizeof-multiply-sizeof")) { diag(E->getBeginLoc(), "suspicious 'sizeof' by 'sizeof' multiplication"); + } else if (const auto *E = + Result.Nodes.getNodeAs("sizeof-in-ptr-arithmetic-mul")) { + const auto *LPtrTy = Result.Nodes.getNodeAs("left-ptr-type"); + const auto *RPtrTy = Result.Nodes.getNodeAs("right-ptr-type"); + const auto *SizeofArgTy = Result.Nodes.getNodeAs("sizeof-arg-type"); + + if ((LPtrTy == RPtrTy) && (LPtrTy == SizeofArgTy)) { + diag(E->getBeginLoc(), "suspicious usage of 'sizeof(...)' in " + "pointer arithmetic"); + } + } else if (const auto *E = + Result.Nodes.getNodeAs("sizeof-in-ptr-arithmetic-div")) { + const auto *LPtrTy = Result.Nodes.getNodeAs("left-ptr-type"); + const auto *RPtrTy = Result.Nodes.getNodeAs("right-ptr-type"); + const auto *SizeofArgTy = Result.Nodes.getNodeAs("sizeof-arg-type"); + + if ((LPtrTy == RPtrTy) && (LPtrTy == SizeofArgTy)) { + diag(E->getBeginLoc(), "suspicious usage of 'sizeof(...)' in " + "pointer arithmetic"); + } } } diff --git a/clang-tools-extra/clang-tidy/bugprone/UnhandledSelfAssignmentCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnhandledSelfAssignmentCheck.cpp new file mode 100644 index 0000000000000..b529f72ddae32 --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/UnhandledSelfAssignmentCheck.cpp @@ -0,0 +1,99 @@ +//===--- UnhandledSelfAssignmentCheck.cpp - clang-tidy --------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "UnhandledSelfAssignmentCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace bugprone { + +void UnhandledSelfAssignmentCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // We don't care about deleted, default or implicit operator implementations. + const auto IsUserDefined = cxxMethodDecl( + isDefinition(), unless(anyOf(isDeleted(), isImplicit(), isDefaulted()))); + + // We don't need to worry when a copy assignment operator gets the other + // object by value. + const auto HasReferenceParam = + cxxMethodDecl(hasParameter(0, parmVarDecl(hasType(referenceType())))); + + // Self-check: Code compares something with 'this' pointer. We don't check + // whether it is actually the parameter what we compare. + const auto HasNoSelfCheck = cxxMethodDecl(unless(hasDescendant( + binaryOperator(anyOf(hasOperatorName("=="), hasOperatorName("!=")), + has(ignoringParenCasts(cxxThisExpr())))))); + + // Both copy-and-swap and copy-and-move method creates a copy first and + // assign it to 'this' with swap or move. + // In the non-template case, we can search for the copy constructor call. + const auto HasNonTemplateSelfCopy = cxxMethodDecl( + ofClass(cxxRecordDecl(unless(hasAncestor(classTemplateDecl())))), + hasDescendant(cxxConstructExpr(hasDeclaration(cxxConstructorDecl( + isCopyConstructor(), ofClass(equalsBoundNode("class"))))))); + + // In the template case, we need to handle two separate cases: 1) a local + // variable is created with the copy, 2) copy is created only as a temporary + // object. + const auto HasTemplateSelfCopy = cxxMethodDecl( + ofClass(cxxRecordDecl(hasAncestor(classTemplateDecl()))), + anyOf(hasDescendant( + varDecl(hasType(cxxRecordDecl(equalsBoundNode("class"))), + hasDescendant(parenListExpr()))), + hasDescendant(cxxUnresolvedConstructExpr(hasDescendant(declRefExpr( + hasType(cxxRecordDecl(equalsBoundNode("class"))))))))); + + // If inside the copy assignment operator another assignment operator is + // called on 'this' we assume that self-check might be handled inside + // this nested operator. + const auto HasNoNestedSelfAssign = + cxxMethodDecl(unless(hasDescendant(cxxMemberCallExpr(callee(cxxMethodDecl( + hasName("operator="), ofClass(equalsBoundNode("class")))))))); + + // Matcher for standard smart pointers. + const auto SmartPointerType = qualType(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(classTemplateSpecializationDecl( + hasAnyName("::std::shared_ptr", "::std::unique_ptr", + "::std::weak_ptr", "::std::auto_ptr"), + templateArgumentCountIs(1)))))); + + // We will warn only if the class has a pointer or a C array field which + // probably causes a problem during self-assignment (e.g. first resetting the + // pointer member, then trying to access the object pointed by the pointer, or + // memcpy overlapping arrays). + const auto ThisHasSuspiciousField = cxxMethodDecl(ofClass(cxxRecordDecl( + has(fieldDecl(anyOf(hasType(pointerType()), hasType(SmartPointerType), + hasType(arrayType()))))))); + + Finder->addMatcher( + cxxMethodDecl(ofClass(cxxRecordDecl().bind("class")), + isCopyAssignmentOperator(), IsUserDefined, + HasReferenceParam, HasNoSelfCheck, + unless(HasNonTemplateSelfCopy), unless(HasTemplateSelfCopy), + HasNoNestedSelfAssign, ThisHasSuspiciousField) + .bind("copyAssignmentOperator"), + this); +} + +void UnhandledSelfAssignmentCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = + Result.Nodes.getNodeAs("copyAssignmentOperator"); + diag(MatchedDecl->getLocation(), + "operator=() does not handle self-assignment properly"); +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/clang-tidy/bugprone/UnhandledSelfAssignmentCheck.h b/clang-tools-extra/clang-tidy/bugprone/UnhandledSelfAssignmentCheck.h new file mode 100644 index 0000000000000..1747246143552 --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/UnhandledSelfAssignmentCheck.h @@ -0,0 +1,36 @@ +//===--- UnhandledSelfAssignmentCheck.h - clang-tidy ------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNHANDLEDSELFASSIGNMENTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNHANDLEDSELFASSIGNMENTCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Finds user-defined copy assignment operators which do not protect the code +/// against self-assignment either by checking self-assignment explicitly or +/// using the copy-and-swap or the copy-and-move method. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-unhandled-self-assignment.html +class UnhandledSelfAssignmentCheck : public ClangTidyCheck { +public: + UnhandledSelfAssignmentCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNHANDLEDSELFASSIGNMENTCHECK_H diff --git a/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.cpp b/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.cpp index bde460cac8c4d..c098268196a0a 100644 --- a/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.cpp +++ b/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.cpp @@ -10,7 +10,7 @@ namespace clang { namespace tidy { -namespace llvm { +namespace llvm_check { LLVMHeaderGuardCheck::LLVMHeaderGuardCheck(StringRef Name, ClangTidyContext *Context) @@ -49,6 +49,6 @@ std::string LLVMHeaderGuardCheck::getHeaderGuard(StringRef Filename, return StringRef(Guard).upper(); } -} // namespace llvm +} // namespace llvm_check } // namespace tidy } // namespace clang diff --git a/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.h b/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.h index 46021e28993d7..d759a638cc2ed 100644 --- a/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.h +++ b/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.h @@ -6,14 +6,14 @@ // //===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADER_GUARD_CHECK_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADER_GUARD_CHECK_H +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADERGUARDCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADERGUARDCHECK_H #include "../utils/HeaderGuard.h" namespace clang { namespace tidy { -namespace llvm { +namespace llvm_check { /// Finds and fixes header guards that do not adhere to LLVM style. /// For the user-facing documentation see: @@ -32,8 +32,8 @@ class LLVMHeaderGuardCheck : public utils::HeaderGuardCheck { std::string getHeaderGuard(StringRef Filename, StringRef OldGuard) override; }; -} // namespace llvm +} // namespace llvm_check } // namespace tidy } // namespace clang -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADER_GUARD_CHECK_H +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADERGUARDCHECK_H diff --git a/clang-tools-extra/clang-tidy/llvm/IncludeOrderCheck.cpp b/clang-tools-extra/clang-tidy/llvm/IncludeOrderCheck.cpp index 9c3ab0b783f91..a894c5815fe5f 100644 --- a/clang-tools-extra/clang-tidy/llvm/IncludeOrderCheck.cpp +++ b/clang-tools-extra/clang-tidy/llvm/IncludeOrderCheck.cpp @@ -15,7 +15,7 @@ namespace clang { namespace tidy { -namespace llvm { +namespace llvm_check { namespace { class IncludeOrderPPCallbacks : public PPCallbacks { @@ -176,6 +176,6 @@ void IncludeOrderPPCallbacks::EndOfMainFile() { IncludeDirectives.clear(); } -} // namespace llvm +} // namespace llvm_check } // namespace tidy } // namespace clang diff --git a/clang-tools-extra/clang-tidy/llvm/IncludeOrderCheck.h b/clang-tools-extra/clang-tidy/llvm/IncludeOrderCheck.h index b95c3800907de..930fa90be31f2 100644 --- a/clang-tools-extra/clang-tidy/llvm/IncludeOrderCheck.h +++ b/clang-tools-extra/clang-tidy/llvm/IncludeOrderCheck.h @@ -6,14 +6,14 @@ // //===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_INCLUDE_ORDER_CHECK_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_INCLUDE_ORDER_CHECK_H +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_INCLUDEORDERCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_INCLUDEORDERCHECK_H #include "../ClangTidyCheck.h" namespace clang { namespace tidy { -namespace llvm { +namespace llvm_check { /// Checks the correct order of `#includes`. /// @@ -26,8 +26,8 @@ class IncludeOrderCheck : public ClangTidyCheck { Preprocessor *ModuleExpanderPP) override; }; -} // namespace llvm +} // namespace llvm_check } // namespace tidy } // namespace clang -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_INCLUDE_ORDER_CHECK_H +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_INCLUDEORDERCHECK_H diff --git a/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp b/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp index fb7b52a3815ed..f9954b5e20176 100644 --- a/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp @@ -17,7 +17,7 @@ namespace clang { namespace tidy { -namespace llvm { +namespace llvm_check { class LLVMModule : public ClangTidyModule { public: @@ -36,7 +36,7 @@ class LLVMModule : public ClangTidyModule { static ClangTidyModuleRegistry::Add X("llvm-module", "Adds LLVM lint checks."); -} // namespace llvm +} // namespace llvm_check // This anchor is used to force the linker to link in the generated object file // and thus register the LLVMModule. diff --git a/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.cpp b/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.cpp index 252ef3bf21798..560d24f010694 100644 --- a/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.cpp +++ b/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.cpp @@ -19,7 +19,7 @@ AST_MATCHER(Expr, isMacroID) { return Node.getExprLoc().isMacroID(); } } // namespace ast_matchers namespace tidy { -namespace llvm { +namespace llvm_check { void PreferIsaOrDynCastInConditionalsCheck::registerMatchers( MatchFinder *Finder) { @@ -130,6 +130,6 @@ void PreferIsaOrDynCastInConditionalsCheck::check( } } -} // namespace llvm +} // namespace llvm_check } // namespace tidy } // namespace clang diff --git a/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.h b/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.h index 354693c1f69ae..f35cf6615d5a2 100644 --- a/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.h +++ b/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.h @@ -6,14 +6,14 @@ // //===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_AVOIDCASTINCONDITIONALCHECK_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_AVOIDCASTINCONDITIONALCHECK_H +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_PREFERISAORDYNCASTINCONDITIONALSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_PREFERISAORDYNCASTINCONDITIONALSCHECK_H #include "../ClangTidyCheck.h" namespace clang { namespace tidy { -namespace llvm { +namespace llvm_check { /// \brief Looks at conditionals and finds and replaces cases of ``cast<>``, which will /// assert rather than return a null pointer, and ``dyn_cast<>`` where @@ -57,8 +57,8 @@ class PreferIsaOrDynCastInConditionalsCheck : public ClangTidyCheck { void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; -} // namespace llvm +} // namespace llvm_check } // namespace tidy } // namespace clang -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_AVOIDCASTINCONDITIONALCHECK_H +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_PREFERISAORDYNCASTINCONDITIONALSCHECK_H diff --git a/clang-tools-extra/clang-tidy/llvm/TwineLocalCheck.cpp b/clang-tools-extra/clang-tidy/llvm/TwineLocalCheck.cpp index 2fddf6d277df0..63a9314a49e5e 100644 --- a/clang-tools-extra/clang-tidy/llvm/TwineLocalCheck.cpp +++ b/clang-tools-extra/clang-tidy/llvm/TwineLocalCheck.cpp @@ -15,7 +15,7 @@ using namespace clang::ast_matchers; namespace clang { namespace tidy { -namespace llvm { +namespace llvm_check { void TwineLocalCheck::registerMatchers(MatchFinder *Finder) { auto TwineType = @@ -60,6 +60,6 @@ void TwineLocalCheck::check(const MatchFinder::MatchResult &Result) { } } -} // namespace llvm +} // namespace llvm_check } // namespace tidy } // namespace clang diff --git a/clang-tools-extra/clang-tidy/llvm/TwineLocalCheck.h b/clang-tools-extra/clang-tidy/llvm/TwineLocalCheck.h index 0cb655a88ce97..e6356263480af 100644 --- a/clang-tools-extra/clang-tidy/llvm/TwineLocalCheck.h +++ b/clang-tools-extra/clang-tidy/llvm/TwineLocalCheck.h @@ -6,14 +6,14 @@ // //===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_TWINE_LOCAL_CHECK_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_TWINE_LOCAL_CHECK_H +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_TWINELOCALCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_TWINELOCALCHECK_H #include "../ClangTidyCheck.h" namespace clang { namespace tidy { -namespace llvm { +namespace llvm_check { /// Looks for local `Twine` variables which are prone to use after frees and /// should be generally avoided. @@ -25,8 +25,8 @@ class TwineLocalCheck : public ClangTidyCheck { void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; -} // namespace llvm +} // namespace llvm_check } // namespace tidy } // namespace clang -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_TWINE_LOCAL_CHECK_H +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_TWINELOCALCHECK_H diff --git a/clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp b/clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp index b7077a1225f19..7b1f7eafe1ebf 100644 --- a/clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp @@ -20,7 +20,10 @@ namespace misc { ThrowByValueCatchByReferenceCheck::ThrowByValueCatchByReferenceCheck( StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), - CheckAnonymousTemporaries(Options.get("CheckThrowTemporaries", true)) {} + CheckAnonymousTemporaries(Options.get("CheckThrowTemporaries", true)), + WarnOnLargeObject(Options.get("WarnOnLargeObject", false)), + // Cannot access `ASTContext` from here so set it to an extremal value. + MaxSize(Options.get("MaxSize", std::numeric_limits::max())) {} void ThrowByValueCatchByReferenceCheck::registerMatchers(MatchFinder *Finder) { // This is a C++ only check thus we register the matchers only for C++ @@ -150,8 +153,19 @@ void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations( // If it's not a pointer and not a reference then it must be caught "by // value". In this case we should emit a diagnosis message unless the type // is trivial. - if (!caughtType.isTrivialType(context)) + if (!caughtType.isTrivialType(context)) { diag(varDecl->getBeginLoc(), diagMsgCatchReference); + } else if (WarnOnLargeObject) { + // If the type is trivial, then catching it by reference is not dangerous. + // However, catching large objects by value decreases the performance. + + // We can now access `ASTContext` so if `MaxSize` is an extremal value + // then set it to the size of `size_t`. + if (MaxSize == std::numeric_limits::max()) + MaxSize = context.getTypeSize(context.getSizeType()); + if (context.getTypeSize(caughtType) > MaxSize) + diag(varDecl->getBeginLoc(), diagMsgCatchReference); + } } } diff --git a/clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.h b/clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.h index b3c8935026fb2..39025e02b89dd 100644 --- a/clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.h +++ b/clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.h @@ -41,6 +41,8 @@ class ThrowByValueCatchByReferenceCheck : public ClangTidyCheck { bool isCatchVariable(const DeclRefExpr *declRefExpr); bool isFunctionOrCatchVar(const DeclRefExpr *declRefExpr); const bool CheckAnonymousTemporaries; + const bool WarnOnLargeObject; + uint64_t MaxSize; // No `const` because we have to set it in two steps. }; } // namespace misc diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt index bb5c0aebb6614..36193f0a6d167 100644 --- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt @@ -30,6 +30,7 @@ add_clang_library(clangTidyModernizeModule UseNoexceptCheck.cpp UseNullptrCheck.cpp UseOverrideCheck.cpp + UseTrailingReturnTypeCheck.cpp UseTransparentFunctorsCheck.cpp UseUncaughtExceptionsCheck.cpp UseUsingCheck.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp index f9e941c20169a..0e59a8848d8e8 100644 --- a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp @@ -791,11 +791,6 @@ bool LoopConvertCheck::isConvertible(ASTContext *Context, 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. diff --git a/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp b/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp index 5a152c8c320c5..179a09745e87d 100644 --- a/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp @@ -278,7 +278,7 @@ bool MakeSmartPtrCheck::replaceNew(DiagnosticBuilder &Diag, return false; std::string ArraySizeExpr; - if (const auto* ArraySize = New->getArraySize()) { + if (const auto* ArraySize = New->getArraySize().getValueOr(nullptr)) { ArraySizeExpr = Lexer::getSourceText(CharSourceRange::getTokenRange( ArraySize->getSourceRange()), SM, getLangOpts()) @@ -292,12 +292,13 @@ bool MakeSmartPtrCheck::replaceNew(DiagnosticBuilder &Diag, // Foo{1} => false auto HasListIntializedArgument = [](const CXXConstructExpr *CE) { for (const auto *Arg : CE->arguments()) { + Arg = Arg->IgnoreImplicit(); + if (isa(Arg) || isa(Arg)) return true; // Check whether we implicitly construct a class from a // std::initializer_list. - if (const auto *ImplicitCE = - dyn_cast(Arg->IgnoreImplicit())) { + if (const auto *ImplicitCE = dyn_cast(Arg)) { if (ImplicitCE->isStdInitListInitialization()) return true; } diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp index 45ecdf93a725a..6280f9c991e8a 100644 --- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -35,6 +35,7 @@ #include "UseNoexceptCheck.h" #include "UseNullptrCheck.h" #include "UseOverrideCheck.h" +#include "UseTrailingReturnTypeCheck.h" #include "UseTransparentFunctorsCheck.h" #include "UseUncaughtExceptionsCheck.h" #include "UseUsingCheck.h" @@ -87,6 +88,8 @@ class ModernizeModule : public ClangTidyModule { CheckFactories.registerCheck("modernize-use-noexcept"); CheckFactories.registerCheck("modernize-use-nullptr"); CheckFactories.registerCheck("modernize-use-override"); + CheckFactories.registerCheck( + "modernize-use-trailing-return-type"); CheckFactories.registerCheck( "modernize-use-transparent-functors"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp new file mode 100644 index 0000000000000..4dc53dd7b278f --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp @@ -0,0 +1,478 @@ +//===--- UseTrailingReturnTypeCheck.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 "UseTrailingReturnTypeCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/FixIt.h" + +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { +namespace { +struct UnqualNameVisitor : public RecursiveASTVisitor { +public: + UnqualNameVisitor(const FunctionDecl &F) : F(F) {} + + bool Collision = false; + + bool shouldWalkTypesOfTypeLocs() const { return false; } + + bool VisitUnqualName(StringRef UnqualName) { + // Check for collisions with function arguments. + for (ParmVarDecl *Param : F.parameters()) + if (const IdentifierInfo *Ident = Param->getIdentifier()) + if (Ident->getName() == UnqualName) { + Collision = true; + return true; + } + return false; + } + + bool TraverseTypeLoc(TypeLoc TL, bool Elaborated = false) { + if (TL.isNull()) + return true; + + if (!Elaborated) { + switch (TL.getTypeLocClass()) { + case TypeLoc::Record: + if (VisitUnqualName( + TL.getAs().getTypePtr()->getDecl()->getName())) + return false; + break; + case TypeLoc::Enum: + if (VisitUnqualName( + TL.getAs().getTypePtr()->getDecl()->getName())) + return false; + break; + case TypeLoc::TemplateSpecialization: + if (VisitUnqualName(TL.getAs() + .getTypePtr() + ->getTemplateName() + .getAsTemplateDecl() + ->getName())) + return false; + break; + default: + break; + } + } + + return RecursiveASTVisitor::TraverseTypeLoc(TL); + } + + // Replace the base method in order to call ower own + // TraverseTypeLoc(). + bool TraverseQualifiedTypeLoc(QualifiedTypeLoc TL) { + return TraverseTypeLoc(TL.getUnqualifiedLoc()); + } + + // Replace the base version to inform TraverseTypeLoc that the type is + // elaborated. + bool TraverseElaboratedTypeLoc(ElaboratedTypeLoc TL) { + if (TL.getQualifierLoc() && + !TraverseNestedNameSpecifierLoc(TL.getQualifierLoc())) + return false; + return TraverseTypeLoc(TL.getNamedTypeLoc(), true); + } + + bool VisitDeclRefExpr(DeclRefExpr *S) { + DeclarationName Name = S->getNameInfo().getName(); + return S->getQualifierLoc() || !Name.isIdentifier() || + !VisitUnqualName(Name.getAsIdentifierInfo()->getName()); + } + +private: + const FunctionDecl &F; +}; +} // namespace + +constexpr llvm::StringLiteral Message = + "use a trailing return type for this function"; + +static SourceLocation expandIfMacroId(SourceLocation Loc, + const SourceManager &SM) { + if (Loc.isMacroID()) + Loc = expandIfMacroId(SM.getImmediateExpansionRange(Loc).getBegin(), SM); + assert(!Loc.isMacroID() && + "SourceLocation must not be a macro ID after recursive expansion"); + return Loc; +} + +SourceLocation UseTrailingReturnTypeCheck::findTrailingReturnTypeSourceLocation( + const FunctionDecl &F, const FunctionTypeLoc &FTL, const ASTContext &Ctx, + const SourceManager &SM, const LangOptions &LangOpts) { + // We start with the location of the closing parenthesis. + SourceRange ExceptionSpecRange = F.getExceptionSpecSourceRange(); + if (ExceptionSpecRange.isValid()) + return Lexer::getLocForEndOfToken(ExceptionSpecRange.getEnd(), 0, SM, + LangOpts); + + // If the function argument list ends inside of a macro, it is dangerous to + // start lexing from here - bail out. + SourceLocation ClosingParen = FTL.getRParenLoc(); + if (ClosingParen.isMacroID()) + return {}; + + SourceLocation Result = + Lexer::getLocForEndOfToken(ClosingParen, 0, SM, LangOpts); + + // Skip subsequent CV and ref qualifiers. + std::pair Loc = SM.getDecomposedLoc(Result); + StringRef File = SM.getBufferData(Loc.first); + const char *TokenBegin = File.data() + Loc.second; + Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(), + TokenBegin, File.end()); + Token T; + while (!Lexer.LexFromRawLexer(T)) { + if (T.is(tok::raw_identifier)) { + IdentifierInfo &Info = Ctx.Idents.get( + StringRef(SM.getCharacterData(T.getLocation()), T.getLength())); + T.setIdentifierInfo(&Info); + T.setKind(Info.getTokenID()); + } + + if (T.isOneOf(tok::amp, tok::ampamp, tok::kw_const, tok::kw_volatile, + tok::kw_restrict)) { + Result = T.getEndLoc(); + continue; + } + break; + } + return Result; +} + +static bool IsCVR(Token T) { + return T.isOneOf(tok::kw_const, tok::kw_volatile, tok::kw_restrict); +} + +static bool IsSpecifier(Token T) { + return T.isOneOf(tok::kw_constexpr, tok::kw_inline, tok::kw_extern, + tok::kw_static, tok::kw_friend, tok::kw_virtual); +} + +static llvm::Optional +classifyToken(const FunctionDecl &F, Preprocessor &PP, Token Tok) { + ClassifiedToken CT; + CT.T = Tok; + CT.isQualifier = true; + CT.isSpecifier = true; + bool ContainsQualifiers = false; + bool ContainsSpecifiers = false; + bool ContainsSomethingElse = false; + + Token End; + End.setKind(tok::eof); + SmallVector Stream{Tok, End}; + + // FIXME: do not report these token to Preprocessor.TokenWatcher. + PP.EnterTokenStream(Stream, false, /*IsReinject=*/false); + while (true) { + Token T; + PP.Lex(T); + if (T.is(tok::eof)) + break; + + bool Qual = IsCVR(T); + bool Spec = IsSpecifier(T); + CT.isQualifier &= Qual; + CT.isSpecifier &= Spec; + ContainsQualifiers |= Qual; + ContainsSpecifiers |= Spec; + ContainsSomethingElse |= !Qual && !Spec; + } + + // If the Token/Macro contains more than one type of tokens, we would need + // to split the macro in order to move parts to the trailing return type. + if (ContainsQualifiers + ContainsSpecifiers + ContainsSomethingElse > 1) + return llvm::None; + + return CT; +} + +llvm::Optional> +UseTrailingReturnTypeCheck::classifyTokensBeforeFunctionName( + const FunctionDecl &F, const ASTContext &Ctx, const SourceManager &SM, + const LangOptions &LangOpts) { + SourceLocation BeginF = expandIfMacroId(F.getBeginLoc(), SM); + SourceLocation BeginNameF = expandIfMacroId(F.getLocation(), SM); + + // Create tokens for everything before the name of the function. + std::pair Loc = SM.getDecomposedLoc(BeginF); + StringRef File = SM.getBufferData(Loc.first); + const char *TokenBegin = File.data() + Loc.second; + Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(), + TokenBegin, File.end()); + Token T; + SmallVector ClassifiedTokens; + while (!Lexer.LexFromRawLexer(T) && + SM.isBeforeInTranslationUnit(T.getLocation(), BeginNameF)) { + if (T.is(tok::raw_identifier)) { + IdentifierInfo &Info = Ctx.Idents.get( + StringRef(SM.getCharacterData(T.getLocation()), T.getLength())); + + if (Info.hasMacroDefinition()) { + const MacroInfo *MI = PP->getMacroInfo(&Info); + if (!MI || MI->isFunctionLike()) { + // Cannot handle function style macros. + diag(F.getLocation(), Message); + return llvm::None; + } + } + + T.setIdentifierInfo(&Info); + T.setKind(Info.getTokenID()); + } + + if (llvm::Optional CT = classifyToken(F, *PP, T)) + ClassifiedTokens.push_back(*CT); + else { + diag(F.getLocation(), Message); + return llvm::None; + } + } + + return ClassifiedTokens; +} + +static bool hasAnyNestedLocalQualifiers(QualType Type) { + bool Result = Type.hasLocalQualifiers(); + if (Type->isPointerType()) + Result = Result || hasAnyNestedLocalQualifiers( + Type->castAs()->getPointeeType()); + if (Type->isReferenceType()) + Result = Result || hasAnyNestedLocalQualifiers( + Type->castAs()->getPointeeType()); + return Result; +} + +SourceRange UseTrailingReturnTypeCheck::findReturnTypeAndCVSourceRange( + const FunctionDecl &F, const ASTContext &Ctx, const SourceManager &SM, + const LangOptions &LangOpts) { + + // We start with the range of the return type and expand to neighboring + // qualifiers (const, volatile and restrict). + SourceRange ReturnTypeRange = F.getReturnTypeSourceRange(); + if (ReturnTypeRange.isInvalid()) { + // Happens if e.g. clang cannot resolve all includes and the return type is + // unknown. + diag(F.getLocation(), Message); + return {}; + } + + // If the return type has no local qualifiers, it's source range is accurate. + if (!hasAnyNestedLocalQualifiers(F.getReturnType())) + return ReturnTypeRange; + + // Include qualifiers to the left and right of the return type. + llvm::Optional> MaybeTokens = + classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts); + if (!MaybeTokens) + return {}; + const SmallVector &Tokens = *MaybeTokens; + + ReturnTypeRange.setBegin(expandIfMacroId(ReturnTypeRange.getBegin(), SM)); + ReturnTypeRange.setEnd(expandIfMacroId(ReturnTypeRange.getEnd(), SM)); + + bool ExtendedLeft = false; + for (size_t I = 0; I < Tokens.size(); I++) { + // If we found the beginning of the return type, include left qualifiers. + if (!SM.isBeforeInTranslationUnit(Tokens[I].T.getLocation(), + ReturnTypeRange.getBegin()) && + !ExtendedLeft) { + assert(I <= size_t(std::numeric_limits::max()) && + "Integer overflow detected"); + for (int J = static_cast(I) - 1; J >= 0 && Tokens[J].isQualifier; + J--) + ReturnTypeRange.setBegin(Tokens[J].T.getLocation()); + ExtendedLeft = true; + } + // If we found the end of the return type, include right qualifiers. + if (SM.isBeforeInTranslationUnit(ReturnTypeRange.getEnd(), + Tokens[I].T.getLocation())) { + for (size_t J = I; J < Tokens.size() && Tokens[J].isQualifier; J++) + ReturnTypeRange.setEnd(Tokens[J].T.getLocation()); + break; + } + } + + assert(!ReturnTypeRange.getBegin().isMacroID() && + "Return type source range begin must not be a macro"); + assert(!ReturnTypeRange.getEnd().isMacroID() && + "Return type source range end must not be a macro"); + return ReturnTypeRange; +} + +bool UseTrailingReturnTypeCheck::keepSpecifiers( + std::string &ReturnType, std::string &Auto, SourceRange ReturnTypeCVRange, + const FunctionDecl &F, const FriendDecl *Fr, const ASTContext &Ctx, + const SourceManager &SM, const LangOptions &LangOpts) { + // Check if there are specifiers inside the return type. E.g. unsigned + // inline int. + const auto *M = dyn_cast(&F); + if (!F.isConstexpr() && !F.isInlineSpecified() && + F.getStorageClass() != SC_Extern && F.getStorageClass() != SC_Static && + !Fr && !(M && M->isVirtualAsWritten())) + return true; + + // Tokenize return type. If it contains macros which contain a mix of + // qualifiers, specifiers and types, give up. + llvm::Optional> MaybeTokens = + classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts); + if (!MaybeTokens) + return false; + + // Find specifiers, remove them from the return type, add them to 'auto'. + unsigned int ReturnTypeBeginOffset = + SM.getDecomposedLoc(ReturnTypeCVRange.getBegin()).second; + size_t InitialAutoLength = Auto.size(); + unsigned int DeletedChars = 0; + for (ClassifiedToken CT : *MaybeTokens) { + if (SM.isBeforeInTranslationUnit(CT.T.getLocation(), + ReturnTypeCVRange.getBegin()) || + SM.isBeforeInTranslationUnit(ReturnTypeCVRange.getEnd(), + CT.T.getLocation())) + continue; + if (!CT.isSpecifier) + continue; + + // Add the token to 'auto' and remove it from the return type, including + // any whitespace following the token. + unsigned int TOffset = SM.getDecomposedLoc(CT.T.getLocation()).second; + assert(TOffset >= ReturnTypeBeginOffset && + "Token location must be after the beginning of the return type"); + unsigned int TOffsetInRT = TOffset - ReturnTypeBeginOffset - DeletedChars; + unsigned int TLengthWithWS = CT.T.getLength(); + while (TOffsetInRT + TLengthWithWS < ReturnType.size() && + std::isspace(ReturnType[TOffsetInRT + TLengthWithWS])) + TLengthWithWS++; + std::string Specifier = ReturnType.substr(TOffsetInRT, TLengthWithWS); + if (!std::isspace(Specifier.back())) + Specifier.push_back(' '); + Auto.insert(Auto.size() - InitialAutoLength, Specifier); + ReturnType.erase(TOffsetInRT, TLengthWithWS); + DeletedChars += TLengthWithWS; + } + + return true; +} + +void UseTrailingReturnTypeCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + + auto F = functionDecl(unless(anyOf(hasTrailingReturn(), returns(voidType()), + returns(autoType()), cxxConversionDecl(), + cxxMethodDecl(isImplicit())))) + .bind("Func"); + + Finder->addMatcher(F, this); + Finder->addMatcher(friendDecl(hasDescendant(F)).bind("Friend"), this); +} + +void UseTrailingReturnTypeCheck::registerPPCallbacks( + const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { + this->PP = PP; +} + +void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) { + assert(PP && "Expected registerPPCallbacks() to have been called before so " + "preprocessor is available"); + + const auto *F = Result.Nodes.getNodeAs("Func"); + const auto *Fr = Result.Nodes.getNodeAs("Friend"); + assert(F && "Matcher is expected to find only FunctionDecls"); + + if (F->getLocation().isInvalid()) + return; + + // TODO: implement those + if (F->getDeclaredReturnType()->isFunctionPointerType() || + F->getDeclaredReturnType()->isMemberFunctionPointerType() || + F->getDeclaredReturnType()->isMemberPointerType() || + F->getDeclaredReturnType()->getAs() != nullptr) { + diag(F->getLocation(), Message); + return; + } + + const ASTContext &Ctx = *Result.Context; + const SourceManager &SM = *Result.SourceManager; + const LangOptions &LangOpts = getLangOpts(); + + const TypeSourceInfo *TSI = F->getTypeSourceInfo(); + if (!TSI) + return; + + FunctionTypeLoc FTL = + TSI->getTypeLoc().IgnoreParens().getAs(); + if (!FTL) { + // FIXME: This may happen if we have __attribute__((...)) on the function. + // We abort for now. Remove this when the function type location gets + // available in clang. + diag(F->getLocation(), Message); + return; + } + + SourceLocation InsertionLoc = + findTrailingReturnTypeSourceLocation(*F, FTL, Ctx, SM, LangOpts); + if (InsertionLoc.isInvalid()) { + diag(F->getLocation(), Message); + return; + } + + // Using the declared return type via F->getDeclaredReturnType().getAsString() + // discards user formatting and order of const, volatile, type, whitespace, + // space before & ... . + SourceRange ReturnTypeCVRange = + findReturnTypeAndCVSourceRange(*F, Ctx, SM, LangOpts); + if (ReturnTypeCVRange.isInvalid()) + return; + + // Check if unqualified names in the return type conflict with other entities + // after the rewrite. + // FIXME: this could be done better, by performing a lookup of all + // unqualified names in the return type in the scope of the function. If the + // lookup finds a different entity than the original entity identified by the + // name, then we can either not perform a rewrite or explicitely qualify the + // entity. Such entities could be function parameter names, (inherited) class + // members, template parameters, etc. + UnqualNameVisitor UNV{*F}; + UNV.TraverseTypeLoc(FTL.getReturnLoc()); + if (UNV.Collision) { + diag(F->getLocation(), Message); + return; + } + + SourceLocation ReturnTypeEnd = + Lexer::getLocForEndOfToken(ReturnTypeCVRange.getEnd(), 0, SM, LangOpts); + StringRef CharAfterReturnType = Lexer::getSourceText( + CharSourceRange::getCharRange(ReturnTypeEnd, + ReturnTypeEnd.getLocWithOffset(1)), + SM, LangOpts); + bool NeedSpaceAfterAuto = + CharAfterReturnType.empty() || !std::isspace(CharAfterReturnType[0]); + + std::string Auto = NeedSpaceAfterAuto ? "auto " : "auto"; + std::string ReturnType = tooling::fixit::getText(ReturnTypeCVRange, Ctx); + keepSpecifiers(ReturnType, Auto, ReturnTypeCVRange, *F, Fr, Ctx, SM, + LangOpts); + + diag(F->getLocation(), Message) + << FixItHint::CreateReplacement(ReturnTypeCVRange, Auto) + << FixItHint::CreateInsertion(InsertionLoc, " -> " + ReturnType); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.h b/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.h new file mode 100644 index 0000000000000..565000d41a7fc --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.h @@ -0,0 +1,62 @@ +//===--- UseTrailingReturnTypeCheck.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_USETRAILINGRETURNTYPECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USETRAILINGRETURNTYPECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +struct ClassifiedToken { + Token T; + bool isQualifier; + bool isSpecifier; +}; + +/// Rewrites function signatures to use a trailing return type. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-use-trailing-type-return.html +class UseTrailingReturnTypeCheck : public ClangTidyCheck { +public: + UseTrailingReturnTypeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, + Preprocessor *ModuleExpanderPP) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + Preprocessor *PP = nullptr; + + SourceLocation findTrailingReturnTypeSourceLocation( + const FunctionDecl &F, const FunctionTypeLoc &FTL, const ASTContext &Ctx, + const SourceManager &SM, const LangOptions &LangOpts); + llvm::Optional> + classifyTokensBeforeFunctionName(const FunctionDecl &F, const ASTContext &Ctx, + const SourceManager &SM, + const LangOptions &LangOpts); + SourceRange findReturnTypeAndCVSourceRange(const FunctionDecl &F, + const ASTContext &Ctx, + const SourceManager &SM, + const LangOptions &LangOpts); + bool keepSpecifiers(std::string &ReturnType, std::string &Auto, + SourceRange ReturnTypeCVRange, const FunctionDecl &F, + const FriendDecl *Fr, const ASTContext &Ctx, + const SourceManager &SM, const LangOptions &LangOpts); +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USETRAILINGRETURNTYPECHECK_H diff --git a/clang-tools-extra/clang-tidy/openmp/ExceptionEscapeCheck.cpp b/clang-tools-extra/clang-tidy/openmp/ExceptionEscapeCheck.cpp index 218437a657b22..c3894a6c81ac7 100644 --- a/clang-tools-extra/clang-tidy/openmp/ExceptionEscapeCheck.cpp +++ b/clang-tools-extra/clang-tidy/openmp/ExceptionEscapeCheck.cpp @@ -73,7 +73,7 @@ void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) { // FIXME: We should provide more information about the exact location where // the exception is thrown, maybe the full path the exception escapes. - diag(Directive->getBeginLoc(), + diag(StructuredBlock->getBeginLoc(), "an exception thrown inside of the OpenMP '%0' region is not caught in " "that same region") << getOpenMPDirectiveName(Directive->getDirectiveKind()); diff --git a/clang-tools-extra/clang-tidy/readability/RedundantDeclarationCheck.cpp b/clang-tools-extra/clang-tidy/readability/RedundantDeclarationCheck.cpp index ff3809a2ff114..ab2e15b072376 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantDeclarationCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/RedundantDeclarationCheck.cpp @@ -17,6 +17,10 @@ namespace clang { namespace tidy { namespace readability { +AST_MATCHER(FunctionDecl, doesDeclarationForceExternallyVisibleDefinition) { + return Node.doesDeclarationForceExternallyVisibleDefinition(); +} + RedundantDeclarationCheck::RedundantDeclarationCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), @@ -25,8 +29,10 @@ RedundantDeclarationCheck::RedundantDeclarationCheck(StringRef Name, void RedundantDeclarationCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( namedDecl(anyOf(varDecl(unless(isDefinition())), - functionDecl(unless(anyOf(isDefinition(), isDefaulted(), - hasParent(friendDecl())))))) + functionDecl(unless(anyOf( + isDefinition(), isDefaulted(), + doesDeclarationForceExternallyVisibleDefinition(), + hasParent(friendDecl())))))) .bind("Decl"), this); } diff --git a/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp b/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp index ef88d44f205a7..38a6324f4013b 100644 --- a/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp @@ -44,7 +44,7 @@ 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 IfAssignVarId[] = "if-assign-var"; const char CompoundReturnId[] = "compound-return"; const char CompoundBoolId[] = "compound-bool"; const char CompoundNotBoolId[] = "compound-bool-not"; @@ -360,7 +360,7 @@ void SimplifyBooleanExprCheck::reportBinOp( const auto *LHS = Op->getLHS()->IgnoreParenImpCasts(); const auto *RHS = Op->getRHS()->IgnoreParenImpCasts(); - const CXXBoolLiteralExpr *Bool = nullptr; + const CXXBoolLiteralExpr *Bool; const Expr *Other = nullptr; if ((Bool = dyn_cast(LHS))) Other = RHS; @@ -459,29 +459,28 @@ void SimplifyBooleanExprCheck::matchIfReturnsBool(MatchFinder *Finder, 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 VarAssign = declRefExpr(hasDeclaration(decl().bind(IfAssignVarId))); + auto VarRef = declRefExpr(hasDeclaration(equalsBoundNode(IfAssignVarId))); + auto MemAssign = memberExpr(hasDeclaration(decl().bind(IfAssignVarId))); + auto MemRef = memberExpr(hasDeclaration(equalsBoundNode(IfAssignVarId))); + auto SimpleThen = + binaryOperator(hasOperatorName("="), hasLHS(anyOf(VarAssign, MemAssign)), + 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 SimpleElse = + binaryOperator(hasOperatorName("="), hasLHS(anyOf(VarRef, MemRef)), + hasRHS(cxxBoolLiteral(equals(!Value)))); auto Else = anyOf(SimpleElse, compoundStmt(statementCountIs(1), hasAnySubstatement(SimpleElse))); if (ChainedConditionalAssignment) + Finder->addMatcher(ifStmt(hasThen(Then), hasElse(Else)).bind(Id), this); + else Finder->addMatcher( - ifStmt(isExpansionInMainFile(), hasThen(Then), hasElse(Else)).bind(Id), + ifStmt(unless(hasParent(ifStmt())), 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, diff --git a/clang-tools-extra/clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp b/clang-tools-extra/clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp index 8458fe3c2f024..e7d70f06bdb40 100644 --- a/clang-tools-extra/clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp @@ -67,6 +67,7 @@ void StaticAccessedThroughInstanceCheck::check( const ASTContext *AstContext = Result.Context; PrintingPolicy PrintingPolicyWithSupressedTag(AstContext->getLangOpts()); PrintingPolicyWithSupressedTag.SuppressTagKeyword = true; + PrintingPolicyWithSupressedTag.SuppressUnwrittenScope = true; std::string BaseTypeName = BaseType.getAsString(PrintingPolicyWithSupressedTag); diff --git a/clang-tools-extra/clang-tidy/rename_check.py b/clang-tools-extra/clang-tidy/rename_check.py index 4eb3d740376bd..d5f24073496c5 100755 --- a/clang-tools-extra/clang-tidy/rename_check.py +++ b/clang-tools-extra/clang-tidy/rename_check.py @@ -14,6 +14,18 @@ import re +def replaceInFileRegex(fileName, sFrom, sTo): + if sFrom == sTo: + return + txt = None + with open(fileName, "r") as f: + txt = f.read() + + txt = re.sub(sFrom, sTo, txt) + print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName)) + with open(fileName, "w") as f: + f.write(txt) + def replaceInFile(fileName, sFrom, sTo): if sFrom == sTo: return @@ -203,6 +215,8 @@ def main(): clang_tidy_path = os.path.dirname(__file__) header_guard_variants = [ + (args.old_check_name.replace('-', '_')).upper() + '_CHECK', + (old_module + '_' + check_name_camel).upper(), (old_module + '_' + new_check_name_camel).upper(), args.old_check_name.replace('-', '_').upper()] header_guard_new = (new_module + '_' + new_check_name_camel).upper() @@ -210,18 +224,19 @@ def main(): old_module_path = os.path.join(clang_tidy_path, old_module) new_module_path = os.path.join(clang_tidy_path, new_module) - # Remove the check from the old module. - cmake_lists = os.path.join(old_module_path, 'CMakeLists.txt') - check_found = deleteMatchingLines(cmake_lists, '\\b' + check_name_camel) - if not check_found: - print("Check name '%s' not found in %s. Exiting." % + if (args.old_check_name != args.new_check_name): + # Remove the check from the old module. + cmake_lists = os.path.join(old_module_path, 'CMakeLists.txt') + check_found = deleteMatchingLines(cmake_lists, '\\b' + check_name_camel) + if not check_found: + print("Check name '%s' not found in %s. Exiting." % (check_name_camel, cmake_lists)) - return 1 + return 1 - modulecpp = filter( - lambda p: p.lower() == old_module.lower() + 'tidymodule.cpp', - os.listdir(old_module_path))[0] - deleteMatchingLines(os.path.join(old_module_path, modulecpp), + modulecpp = filter( + lambda p: p.lower() == old_module.lower() + 'tidymodule.cpp', + os.listdir(old_module_path))[0] + deleteMatchingLines(os.path.join(old_module_path, modulecpp), '\\b' + check_name_camel + '|\\b' + args.old_check_name) for filename in getListOfFiles(clang_tidy_path): @@ -249,14 +264,21 @@ def main(): new_module + '/' + new_check_name_camel) replaceInFile(filename, check_name_camel, new_check_name_camel) - if old_module != new_module: + if old_module != new_module or new_module == 'llvm': + if new_module == 'llvm': + new_namespace = new_module + '_check' + else: + new_namespace = new_module check_implementation_files = glob.glob( os.path.join(old_module_path, new_check_name_camel + '*')) for filename in check_implementation_files: # Move check implementation to the directory of the new module. filename = fileRename(filename, old_module_path, new_module_path) - replaceInFile(filename, 'namespace ' + old_module, - 'namespace ' + new_module) + replaceInFileRegex(filename, 'namespace ' + old_module + '[^ \n]*', + 'namespace ' + new_namespace) + + if (args.old_check_name == args.new_check_name): + return # Add check to the new module. adapt_cmake(new_module_path, new_check_name_camel) diff --git a/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py b/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py index 648c17eadd1bd..1eb1352957548 100755 --- a/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py +++ b/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py @@ -84,9 +84,6 @@ def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path, 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: @@ -170,10 +167,10 @@ def run_tidy(args, tmpdir, build_path, queue, lock, failed_files): if proc.returncode != 0: failed_files.append(name) with lock: - sys.stdout.write(' '.join(invocation) + '\n' + output.decode('utf-8') + '\n') + sys.stdout.write(' '.join(invocation) + '\n' + output.decode('utf-8')) if len(err) > 0: sys.stdout.flush() - sys.stderr.write(err.decode('utf-8') + '\n') + sys.stderr.write(err.decode('utf-8')) queue.task_done() @@ -245,7 +242,12 @@ def main(): if args.checks: invocation.append('-checks=' + args.checks) invocation.append('-') - subprocess.check_call(invocation) + if args.quiet: + # Even with -quiet we still want to check if we can call clang-tidy. + with open(os.devnull, 'w') as dev_null: + subprocess.check_call(invocation, stdout=dev_null) + else: + subprocess.check_call(invocation) except: print("Unable to run clang-tidy.", file=sys.stderr) sys.exit(1) diff --git a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt index 68ab7a10f88a4..e093215b8e8e8 100644 --- a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt @@ -13,6 +13,7 @@ add_clang_library(clangTidyUtils LexerUtils.cpp NamespaceAliaser.cpp OptionsUtils.cpp + TransformerClangTidyCheck.cpp TypeTraits.cpp UsingInserter.cpp @@ -22,4 +23,5 @@ add_clang_library(clangTidyUtils clangBasic clangLex clangTidy + clangToolingRefactor ) diff --git a/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp b/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp new file mode 100644 index 0000000000000..f142de4644184 --- /dev/null +++ b/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp @@ -0,0 +1,63 @@ +//===---------- TransformerClangTidyCheck.cpp - clang-tidy ----------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "TransformerClangTidyCheck.h" + +namespace clang { +namespace tidy { +namespace utils { +using tooling::RewriteRule; + +void TransformerClangTidyCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + Finder->addDynamicMatcher(tooling::detail::buildMatcher(Rule), this); +} + +void TransformerClangTidyCheck::check( + const ast_matchers::MatchFinder::MatchResult &Result) { + if (Result.Context->getDiagnostics().hasErrorOccurred()) + return; + + // Verify the existence and validity of the AST node that roots this rule. + const ast_matchers::BoundNodes::IDToNodeMap &NodesMap = Result.Nodes.getMap(); + auto Root = NodesMap.find(RewriteRule::RootID); + assert(Root != NodesMap.end() && "Transformation failed: missing root node."); + SourceLocation RootLoc = Result.SourceManager->getExpansionLoc( + Root->second.getSourceRange().getBegin()); + assert(RootLoc.isValid() && "Invalid location for Root node of match."); + + RewriteRule::Case Case = tooling::detail::findSelectedCase(Result, Rule); + Expected> Transformations = + tooling::detail::translateEdits(Result, Case.Edits); + if (!Transformations) { + llvm::errs() << "Rewrite failed: " + << llvm::toString(Transformations.takeError()) << "\n"; + return; + } + + // No rewrite applied, but no error encountered either. + if (Transformations->empty()) + return; + + StringRef Message = "no explanation"; + if (Case.Explanation) { + if (Expected E = Case.Explanation(Result)) + Message = *E; + else + llvm::errs() << "Error in explanation: " << llvm::toString(E.takeError()) + << "\n"; + } + DiagnosticBuilder Diag = diag(RootLoc, Message); + for (const auto &T : *Transformations) { + Diag << FixItHint::CreateReplacement(T.Range, T.Replacement); + } +} + +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.h b/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.h new file mode 100644 index 0000000000000..6d0f86795bfdc --- /dev/null +++ b/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.h @@ -0,0 +1,49 @@ +//===---------- TransformerClangTidyCheck.h - clang-tidy ------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_TRANSFORMER_CLANG_TIDY_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_TRANSFORMER_CLANG_TIDY_CHECK_H + +#include "../ClangTidy.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/Refactoring/Transformer.h" +#include +#include + +namespace clang { +namespace tidy { +namespace utils { + +// A base class for defining a ClangTidy check based on a `RewriteRule`. +// +// For example, given a rule `MyCheckAsRewriteRule`, one can define a tidy check +// as follows: +// +// class MyCheck : public TransformerClangTidyCheck { +// public: +// MyCheck(StringRef Name, ClangTidyContext *Context) +// : TransformerClangTidyCheck(MyCheckAsRewriteRule, Name, Context) {} +// }; +class TransformerClangTidyCheck : public ClangTidyCheck { +public: + TransformerClangTidyCheck(tooling::RewriteRule R, StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), Rule(std::move(R)) {} + + void registerMatchers(ast_matchers::MatchFinder *Finder) final; + void check(const ast_matchers::MatchFinder::MatchResult &Result) final; + +private: + tooling::RewriteRule Rule; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_TRANSFORMER_CLANG_TIDY_CHECK_H diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt index 1eb444eac5a39..0a10b6d0d2039 100644 --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -50,6 +50,7 @@ add_clang_library(clangDaemon FileDistance.cpp FS.cpp FSProvider.cpp + FormattedString.cpp FuzzyMatch.cpp GlobalCompilationDatabase.cpp Headers.cpp @@ -89,6 +90,7 @@ add_clang_library(clangDaemon index/dex/PostingList.cpp index/dex/Trigram.cpp + refactor/Rename.cpp refactor/Tweak.cpp LINK_LIBS diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index 44357df72457c..6bc8499730f55 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -823,10 +823,13 @@ void ClangdLSPServer::onGoToDeclaration( std::move(Reply))); } -void ClangdLSPServer::onSwitchSourceHeader(const TextDocumentIdentifier &Params, - Callback Reply) { - llvm::Optional Result = Server->switchSourceHeader(Params.uri.file()); - Reply(Result ? URI::createFile(*Result).toString() : ""); +void ClangdLSPServer::onSwitchSourceHeader( + const TextDocumentIdentifier &Params, + Callback> Reply) { + if (auto Result = Server->switchSourceHeader(Params.uri.file())) + Reply(URIForFile::canonicalize(*Result, Params.uri.file())); + else + Reply(llvm::None); } void ClangdLSPServer::onDocumentHighlight( diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h index 171ba075696ad..f0b10a2f89667 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -85,7 +85,7 @@ class ClangdLSPServer : private DiagnosticsConsumer { Callback>); void onReference(const ReferenceParams &, Callback>); void onSwitchSourceHeader(const TextDocumentIdentifier &, - Callback); + Callback>); void onDocumentHighlight(const TextDocumentPositionParams &, Callback>); void onFileEvent(const DidChangeWatchedFilesParams &); diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp index de6083f132ff5..449c0c980991d 100644 --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -18,6 +18,7 @@ #include "index/CanonicalIncludes.h" #include "index/FileIndex.h" #include "index/Merge.h" +#include "refactor/Rename.h" #include "refactor/Tweak.h" #include "clang/Format/Format.h" #include "clang/Frontend/CompilerInstance.h" @@ -25,8 +26,6 @@ #include "clang/Lex/Preprocessor.h" #include "clang/Tooling/CompilationDatabase.h" #include "clang/Tooling/Core/Replacement.h" -#include "clang/Tooling/Refactoring/RefactoringResultConsumer.h" -#include "clang/Tooling/Refactoring/Rename/RenamingAction.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/ScopeExit.h" @@ -44,38 +43,6 @@ namespace clang { namespace clangd { namespace { -// Expand a DiagnosticError to make it print-friendly (print the detailed -// message, rather than "clang diagnostic"). -llvm::Error expandDiagnostics(llvm::Error Err, DiagnosticsEngine &DE) { - if (auto Diag = DiagnosticError::take(Err)) { - llvm::cantFail(std::move(Err)); - SmallVector DiagMessage; - Diag->second.EmitToString(DE, DiagMessage); - return llvm::make_error(DiagMessage, - llvm::inconvertibleErrorCode()); - } - return Err; -} - -class RefactoringResultCollector final - : public tooling::RefactoringResultConsumer { -public: - void handleError(llvm::Error Err) override { - assert(!Result.hasValue()); - Result = std::move(Err); - } - - // Using the handle(SymbolOccurrences) from parent class. - using tooling::RefactoringResultConsumer::handle; - - void handle(tooling::AtomicChanges SourceReplacements) override { - assert(!Result.hasValue()); - Result = std::move(SourceReplacements); - } - - llvm::Optional> Result; -}; - // Update the FileIndex with new ASTs and plumb the diagnostics responses. struct UpdateIndexCallbacks : public ParsingCallbacks { UpdateIndexCallbacks(FileIndex *FIndex, DiagnosticsConsumer &DiagConsumer) @@ -123,7 +90,7 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB, DynamicIdx(Opts.BuildDynamicSymbolIndex ? new FileIndex(Opts.HeavyweightDynamicSymbolIndex) : nullptr), - ClangTidyOptProvider(Opts.ClangTidyOptProvider), + GetClangTidyOptions(Opts.GetClangTidyOptions), SuggestMissingIncludes(Opts.SuggestMissingIncludes), WorkspaceRoot(Opts.WorkspaceRoot), // Pass a callback into `WorkScheduler` to extract symbols from a newly @@ -159,15 +126,18 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB, void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents, WantDiagnostics WantDiags) { + auto FS = FSProvider.getFileSystem(); + ParseOptions Opts; Opts.ClangTidyOpts = tidy::ClangTidyOptions::getDefaults(); - if (ClangTidyOptProvider) - Opts.ClangTidyOpts = ClangTidyOptProvider->getOptions(File); + // FIXME: call tidy options builder on the worker thread, it can do IO. + if (GetClangTidyOptions) + Opts.ClangTidyOpts = GetClangTidyOptions(*FS, File); Opts.SuggestMissingIncludes = SuggestMissingIncludes; // Compile command is set asynchronously during update, as it can be slow. ParseInputs Inputs; - Inputs.FS = FSProvider.getFileSystem(); + Inputs.FS = FS; Inputs.Contents = Contents; Inputs.Opts = std::move(Opts); Inputs.Index = Index; @@ -230,10 +200,12 @@ void ClangdServer::codeComplete(PathRef File, Position Pos, }; // We use a potentially-stale preamble because latency is critical here. - WorkScheduler.runWithPreamble("CodeComplete", File, - Opts.AllowFallback ? TUScheduler::StaleOrAbsent - : TUScheduler::Stale, - Bind(Task, File.str(), std::move(CB))); + WorkScheduler.runWithPreamble( + "CodeComplete", File, + (Opts.RunParser == CodeCompleteOptions::AlwaysParse) + ? TUScheduler::Stale + : TUScheduler::StaleOrAbsent, + Bind(Task, File.str(), std::move(CB))); } void ClangdServer::signatureHelp(PathRef File, Position Pos, @@ -299,47 +271,13 @@ void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName, llvm::Expected InpAST) { if (!InpAST) return CB(InpAST.takeError()); - auto &AST = InpAST->AST; - - RefactoringResultCollector ResultCollector; - const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); - SourceLocation SourceLocationBeg = - clangd::getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID()); - tooling::RefactoringRuleContext Context( - AST.getASTContext().getSourceManager()); - Context.setASTContext(AST.getASTContext()); - auto Rename = clang::tooling::RenameOccurrences::initiate( - Context, SourceRange(SourceLocationBeg), NewName); - if (!Rename) - return CB(expandDiagnostics(Rename.takeError(), - AST.getASTContext().getDiagnostics())); - - Rename->invoke(ResultCollector, Context); - - assert(ResultCollector.Result.hasValue()); - if (!ResultCollector.Result.getValue()) - return CB(expandDiagnostics(ResultCollector.Result->takeError(), - AST.getASTContext().getDiagnostics())); - - std::vector Replacements; - for (const tooling::AtomicChange &Change : ResultCollector.Result->get()) { - tooling::Replacements ChangeReps = Change.getReplacements(); - for (const auto &Rep : ChangeReps) { - // FIXME: Right now we only support renaming the main file, so we - // drop replacements not for the main file. In the future, we might - // consider to support: - // * rename in any included header - // * rename only in the "main" header - // * provide an error if there are symbols we won't rename (e.g. - // std::vector) - // * rename globally in project - // * rename in open files - if (Rep.getFilePath() == File) - Replacements.push_back( - replacementToEdit(InpAST->Inputs.Contents, Rep)); - } - } - return CB(std::move(Replacements)); + auto Changes = renameWithinFile(InpAST->AST, File, Pos, NewName); + if (!Changes) + return CB(Changes.takeError()); + std::vector Edits; + for (const auto &Rep : *Changes) + Edits.push_back(replacementToEdit(InpAST->Inputs.Contents, Rep)); + return CB(std::move(Edits)); }; WorkScheduler.runWithAST( @@ -496,20 +434,16 @@ llvm::Expected ClangdServer::formatCode(llvm::StringRef Code, PathRef File, llvm::ArrayRef Ranges) { // Call clang-format. - auto FS = FSProvider.getFileSystem(); - auto Style = format::getStyle(format::DefaultFormatStyle, File, - format::DefaultFallbackStyle, Code, FS.get()); - if (!Style) - return Style.takeError(); - + format::FormatStyle Style = + getFormatStyleForFile(File, Code, FSProvider.getFileSystem().get()); tooling::Replacements IncludeReplaces = - format::sortIncludes(*Style, Code, Ranges, File); + format::sortIncludes(Style, Code, Ranges, File); auto Changed = tooling::applyAllReplacements(Code, IncludeReplaces); if (!Changed) return Changed.takeError(); return IncludeReplaces.merge(format::reformat( - Style.get(), *Changed, + Style, *Changed, tooling::calculateRangesAfterReplacements(IncludeReplaces, Ranges), File)); } diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h index b5360a7a42b57..1038982dc7a4a 100644 --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -50,6 +50,12 @@ class DiagnosticsConsumer { virtual void onFileUpdated(PathRef File, const TUStatus &Status){}; }; +/// When set, used by ClangdServer to get clang-tidy options for each particular +/// file. Must be thread-safe. We use this instead of ClangTidyOptionsProvider +/// to allow reading tidy configs from the VFS used for parsing. +using ClangTidyOptionsBuilder = std::function; + /// Manages a collection of source files and derived data (ASTs, indexes), /// and provides language-aware features such as code completion. /// @@ -94,12 +100,12 @@ class ClangdServer { /// If set, use this index to augment code completion results. SymbolIndex *StaticIndex = nullptr; - /// If set, enable clang-tidy in clangd, used to get clang-tidy + /// If set, enable clang-tidy in clangd and use to it get clang-tidy /// configurations for a particular file. /// Clangd supports only a small subset of ClangTidyOptions, these options /// (Checks, CheckOptions) are about which clang-tidy checks will be /// enabled. - tidy::ClangTidyOptionsProvider *ClangTidyOptProvider = nullptr; + ClangTidyOptionsBuilder GetClangTidyOptions; /// Clangd's workspace root. Relevant for "workspace" operations not bound /// to a particular file. @@ -278,8 +284,8 @@ class ClangdServer { // Storage for merged views of the various indexes. std::vector> MergedIdx; - // The provider used to provide a clang-tidy option for a specific file. - tidy::ClangTidyOptionsProvider *ClangTidyOptProvider = nullptr; + // When set, provides clang-tidy options for a specific file. + ClangTidyOptionsBuilder GetClangTidyOptions; // If this is true, suggest include insertion fixes for diagnostic errors that // can be caused by missing includes (e.g. member access in incomplete type). diff --git a/clang-tools-extra/clangd/ClangdUnit.cpp b/clang-tools-extra/clangd/ClangdUnit.cpp index c8c413300540e..31476d99152fd 100644 --- a/clang-tools-extra/clangd/ClangdUnit.cpp +++ b/clang-tools-extra/clangd/ClangdUnit.cpp @@ -113,12 +113,12 @@ class CollectMainFileMacros : public PPCallbacks { } void EndOfMainFile() { - for (const auto& Entry : MainFileMacros) + for (const auto &Entry : MainFileMacros) Out->push_back(Entry.getKey()); llvm::sort(*Out); } - private: +private: const SourceManager &SM; bool InMainFile = true; llvm::StringSet<> MainFileMacros; @@ -332,6 +332,38 @@ ParsedAST::build(std::unique_ptr CI, CTContext->setASTContext(&Clang->getASTContext()); CTContext->setCurrentFile(MainInput.getFile()); CTFactories.createChecks(CTContext.getPointer(), CTChecks); + ASTDiags.setLevelAdjuster([&CTContext](DiagnosticsEngine::Level DiagLevel, + const clang::Diagnostic &Info) { + if (CTContext) { + std::string CheckName = CTContext->getCheckName(Info.getID()); + bool IsClangTidyDiag = !CheckName.empty(); + if (IsClangTidyDiag) { + // Check for warning-as-error. + // We deliberately let this take precedence over suppression comments + // to match clang-tidy's behaviour. + if (DiagLevel == DiagnosticsEngine::Warning && + CTContext->treatAsError(CheckName)) { + return DiagnosticsEngine::Error; + } + + // Check for suppression comment. Skip the check for diagnostics not + // in the main file, because we don't want that function to query the + // source buffer for preamble files. For the same reason, we ask + // ShouldSuppressDiagnostic not to follow macro expansions, since + // those might take us into a preamble file as well. + bool IsInsideMainFile = + Info.hasSourceManager() && + Info.getSourceManager().isWrittenInMainFile( + Info.getSourceManager().getFileLoc(Info.getLocation())); + if (IsInsideMainFile && tidy::ShouldSuppressDiagnostic( + DiagLevel, Info, *CTContext, + /* CheckMacroExpansion = */ false)) { + return DiagnosticsEngine::Ignored; + } + } + } + return DiagLevel; + }); Preprocessor *PP = &Clang->getPreprocessor(); for (const auto &Check : CTChecks) { // FIXME: the PP callbacks skip the entire preamble. @@ -613,8 +645,8 @@ buildAST(PathRef FileName, std::unique_ptr Invocation, std::move(VFS), Inputs.Index, Inputs.Opts); } -SourceLocation getBeginningOfIdentifier(ParsedAST &Unit, const Position &Pos, - const FileID FID) { +SourceLocation getBeginningOfIdentifier(const ParsedAST &Unit, + const Position &Pos, const FileID FID) { const ASTContext &AST = Unit.getASTContext(); const SourceManager &SourceMgr = AST.getSourceManager(); auto Offset = positionToOffset(SourceMgr.getBufferData(FID), Pos); diff --git a/clang-tools-extra/clangd/ClangdUnit.h b/clang-tools-extra/clangd/ClangdUnit.h index d5d2998177b94..042bb0d6a036f 100644 --- a/clang-tools-extra/clangd/ClangdUnit.h +++ b/clang-tools-extra/clangd/ClangdUnit.h @@ -162,8 +162,8 @@ buildAST(PathRef FileName, std::unique_ptr Invocation, /// Get the beginning SourceLocation at a specified \p Pos. /// May be invalid if Pos is, or if there's no identifier. -SourceLocation getBeginningOfIdentifier(ParsedAST &Unit, const Position &Pos, - const FileID FID); +SourceLocation getBeginningOfIdentifier(const ParsedAST &Unit, + const Position &Pos, const FileID FID); /// For testing/debugging purposes. Note that this method deserializes all /// unserialized Decls, so use with care. diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index 77e0a8d5e7892..7f811c31de5bc 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -54,7 +54,9 @@ #include "llvm/ADT/None.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/Debug.h" #include "llvm/Support/Error.h" #include "llvm/Support/Format.h" #include "llvm/Support/FormatVariadic.h" @@ -1215,6 +1217,7 @@ class CodeCompleteFlow { llvm::Optional PreferredType; // Initialized once Sema runs. // Whether to query symbols from any scope. Initialized once Sema runs. bool AllScopes = false; + llvm::StringSet<> ContextWords; // Include-insertion and proximity scoring rely on the include structure. // This is available after Sema has run. llvm::Optional Inserter; // Available during runWithSema. @@ -1237,6 +1240,7 @@ class CodeCompleteFlow { trace::Span Tracer("CodeCompleteFlow"); HeuristicPrefix = guessCompletionPrefix(SemaCCInput.Contents, SemaCCInput.Offset); + populateContextWords(SemaCCInput.Contents); if (Opts.Index && SpecFuzzyFind && SpecFuzzyFind->CachedReq.hasValue()) { assert(!SpecFuzzyFind->Result.valid()); SpecReq = speculativeFuzzyFindRequestForCompletion( @@ -1323,6 +1327,7 @@ class CodeCompleteFlow { trace::Span Tracer("CodeCompleteWithoutSema"); // Fill in fields normally set by runWithSema() HeuristicPrefix = guessCompletionPrefix(Content, Offset); + populateContextWords(Content); CCContextKind = CodeCompletionContext::CCC_Recovery; Filter = FuzzyMatcher(HeuristicPrefix.Name); auto Pos = offsetToPosition(Content, Offset); @@ -1375,11 +1380,30 @@ class CodeCompleteFlow { CodeCompleteResult Output = toCodeCompleteResult(mergeResults( /*SemaResults=*/{}, IndexResults, IdentifierResults)); + Output.RanParser = false; logResults(Output, Tracer); return Output; } private: + void populateContextWords(llvm::StringRef Content) { + // Take last 3 lines before the completion point. + unsigned RangeEnd = HeuristicPrefix.Qualifier.begin() - Content.data(), + RangeBegin = RangeEnd; + for (size_t I = 0; I < 3 && RangeBegin > 0; ++I) { + auto PrevNL = Content.rfind('\n', RangeBegin - 1); + if (PrevNL == StringRef::npos) { + RangeBegin = 0; + break; + } + RangeBegin = PrevNL + 1; + } + + ContextWords = collectWords(Content.slice(RangeBegin, RangeEnd)); + dlog("Completion context words: {0}", + llvm::join(ContextWords.keys(), ", ")); + } + // This is called by run() once Sema code completion is done, but before the // Sema data structures are torn down. It does all the real work. CodeCompleteResult runWithSema() { @@ -1563,12 +1587,14 @@ class CodeCompleteFlow { SymbolQualitySignals Quality; SymbolRelevanceSignals Relevance; Relevance.Context = CCContextKind; + Relevance.Name = Bundle.front().Name; Relevance.Query = SymbolRelevanceSignals::CodeComplete; Relevance.FileProximityMatch = FileProximity.getPointer(); if (ScopeProximity) Relevance.ScopeProximityMatch = ScopeProximity.getPointer(); if (PreferredType) Relevance.HadContextType = true; + Relevance.ContextWords = &ContextWords; auto &First = Bundle.front(); if (auto FuzzyScore = fuzzyScore(First)) @@ -1712,9 +1738,10 @@ codeComplete(PathRef FileName, const tooling::CompileCommand &Command, auto Flow = CodeCompleteFlow( FileName, Preamble ? Preamble->Includes : IncludeStructure(), SpecFuzzyFind, Opts); - return Preamble ? std::move(Flow).run( - {FileName, Command, Preamble, Contents, *Offset, VFS}) - : std::move(Flow).runWithoutSema(Contents, *Offset, VFS); + return (!Preamble || Opts.RunParser == CodeCompleteOptions::NeverParse) + ? std::move(Flow).runWithoutSema(Contents, *Offset, VFS) + : std::move(Flow).run( + {FileName, Command, Preamble, Contents, *Offset, VFS}); } SignatureHelp signatureHelp(PathRef FileName, diff --git a/clang-tools-extra/clangd/CodeComplete.h b/clang-tools-extra/clangd/CodeComplete.h index 9d43107a01ea6..58728285d96a3 100644 --- a/clang-tools-extra/clangd/CodeComplete.h +++ b/clang-tools-extra/clangd/CodeComplete.h @@ -115,11 +115,18 @@ struct CodeCompleteOptions { /// Such completions can insert scope qualifiers. bool AllScopes = false; - /// Whether to allow falling back to code completion without compiling files - /// (using identifiers in the current file and symbol indexes), when file - /// cannot be built (e.g. missing compile command), or the build is not ready - /// (e.g. preamble is still being built). - bool AllowFallback = false; + /// Whether to use the clang parser, or fallback to text-based completion + /// (using identifiers in the current file and symbol indexes). + enum CodeCompletionParse { + /// Block until we can run the parser (e.g. preamble is built). + /// Return an error if this fails. + AlwaysParse, + /// Run the parser if inputs (preamble) are ready. + /// Otherwise, use text-based completion. + ParseIfReady, + /// Always use text-based completion. + NeverParse, + } RunParser = ParseIfReady; }; // Semi-structured representation of a code-complete suggestion for our C++ API. @@ -207,6 +214,9 @@ struct CodeCompleteResult { std::vector Completions; bool HasMore = false; CodeCompletionContext::Kind Context = CodeCompletionContext::CCC_Other; + // Usually the source will be parsed with a real C++ parser. + // But heuristics may be used instead if e.g. the preamble is not ready. + bool RanParser = true; }; raw_ostream &operator<<(raw_ostream &, const CodeCompleteResult &); diff --git a/clang-tools-extra/clangd/Diagnostics.cpp b/clang-tools-extra/clangd/Diagnostics.cpp index c004fa3283d8e..5f42841db7717 100644 --- a/clang-tools-extra/clangd/Diagnostics.cpp +++ b/clang-tools-extra/clangd/Diagnostics.cpp @@ -514,6 +514,15 @@ void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, // Handle the new main diagnostic. flushLastDiag(); + if (Adjuster) { + DiagLevel = Adjuster(DiagLevel, Info); + if (DiagLevel == DiagnosticsEngine::Ignored) { + LastPrimaryDiagnosticWasSuppressed = true; + return; + } + } + LastPrimaryDiagnosticWasSuppressed = false; + LastDiag = Diag(); LastDiag->ID = Info.getID(); FillDiagBase(*LastDiag); @@ -528,6 +537,13 @@ void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, } } else { // Handle a note to an existing diagnostic. + + // If a diagnostic was suppressed due to the suppression filter, + // also suppress notes associated with it. + if (LastPrimaryDiagnosticWasSuppressed) { + return; + } + if (!LastDiag) { assert(false && "Adding a note without main diagnostic"); IgnoreDiagnostics::log(DiagLevel, Info); diff --git a/clang-tools-extra/clangd/Diagnostics.h b/clang-tools-extra/clangd/Diagnostics.h index a0ab7c664fd29..b6cfe895504c7 100644 --- a/clang-tools-extra/clangd/Diagnostics.h +++ b/clang-tools-extra/clangd/Diagnostics.h @@ -128,17 +128,25 @@ class StoreDiags : public DiagnosticConsumer { using DiagFixer = std::function(DiagnosticsEngine::Level, const clang::Diagnostic &)>; + using LevelAdjuster = std::function; /// If set, possibly adds fixes for diagnostics using \p Fixer. void contributeFixes(DiagFixer Fixer) { this->Fixer = Fixer; } + /// If set, this allows the client of this class to adjust the level of + /// diagnostics, such as promoting warnings to errors, or ignoring + /// diagnostics. + void setLevelAdjuster(LevelAdjuster Adjuster) { this->Adjuster = Adjuster; } private: void flushLastDiag(); DiagFixer Fixer = nullptr; + LevelAdjuster Adjuster = nullptr; std::vector Output; llvm::Optional LangOpts; llvm::Optional LastDiag; llvm::DenseSet IncludeLinesWithErrors; + bool LastPrimaryDiagnosticWasSuppressed = false; }; } // namespace clangd diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp index ca89bae155c9f..b2bd166279e27 100644 --- a/clang-tools-extra/clangd/FindSymbols.cpp +++ b/clang-tools-extra/clangd/FindSymbols.cpp @@ -100,6 +100,7 @@ getWorkspaceSymbols(llvm::StringRef Query, int Limit, SymbolQualitySignals Quality; Quality.merge(Sym); SymbolRelevanceSignals Relevance; + Relevance.Name = Sym.Name; Relevance.Query = SymbolRelevanceSignals::Generic; if (auto NameMatch = Filter.match(Sym.Name)) Relevance.NameMatch = *NameMatch; diff --git a/clang-tools-extra/clangd/FormattedString.cpp b/clang-tools-extra/clangd/FormattedString.cpp new file mode 100644 index 0000000000000..3ae1a3c6fa8a7 --- /dev/null +++ b/clang-tools-extra/clangd/FormattedString.cpp @@ -0,0 +1,173 @@ +//===--- FormattedString.cpp --------------------------------*- C++-*------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "FormattedString.h" +#include "clang/Basic/CharInfo.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/ErrorHandling.h" +#include +#include + +namespace clang { +namespace clangd { + +namespace { +/// Escape a markdown text block. Ensures the punctuation will not introduce +/// any of the markdown constructs. +static std::string renderText(llvm::StringRef Input) { + // Escaping ASCII punctiation ensures we can't start a markdown construct. + constexpr llvm::StringLiteral Punctuation = + R"txt(!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)txt"; + + std::string R; + for (size_t From = 0; From < Input.size();) { + size_t Next = Input.find_first_of(Punctuation, From); + R += Input.substr(From, Next - From); + if (Next == llvm::StringRef::npos) + break; + R += "\\"; + R += Input[Next]; + + From = Next + 1; + } + return R; +} + +/// Renders \p Input as an inline block of code in markdown. The returned value +/// is surrounded by backticks and the inner contents are properly escaped. +static std::string renderInlineBlock(llvm::StringRef Input) { + std::string R; + // Double all backticks to make sure we don't close the inline block early. + for (size_t From = 0; From < Input.size();) { + size_t Next = Input.find("`", From); + R += Input.substr(From, Next - From); + if (Next == llvm::StringRef::npos) + break; + R += "``"; // double the found backtick. + + From = Next + 1; + } + // If results starts with a backtick, add spaces on both sides. The spaces + // are ignored by markdown renderers. + if (llvm::StringRef(R).startswith("`") || llvm::StringRef(R).endswith("`")) + return "` " + std::move(R) + " `"; + // Markdown render should ignore first and last space if both are there. We + // add an extra pair of spaces in that case to make sure we render what the + // user intended. + if (llvm::StringRef(R).startswith(" ") && llvm::StringRef(R).endswith(" ")) + return "` " + std::move(R) + " `"; + return "`" + std::move(R) + "`"; +} +/// Render \p Input as markdown code block with a specified \p Language. The +/// result is surrounded by >= 3 backticks. Although markdown also allows to use +/// '~' for code blocks, they are never used. +static std::string renderCodeBlock(llvm::StringRef Input, + llvm::StringRef Language) { + // Count the maximum number of consecutive backticks in \p Input. We need to + // start and end the code block with more. + unsigned MaxBackticks = 0; + unsigned Backticks = 0; + for (char C : Input) { + if (C == '`') { + ++Backticks; + continue; + } + MaxBackticks = std::max(MaxBackticks, Backticks); + Backticks = 0; + } + MaxBackticks = std::max(Backticks, MaxBackticks); + // Use the corresponding number of backticks to start and end a code block. + std::string BlockMarker(/*Repeat=*/std::max(3u, MaxBackticks + 1), '`'); + return BlockMarker + Language.str() + "\n" + Input.str() + "\n" + BlockMarker; +} + +} // namespace + +void FormattedString::appendText(std::string Text) { + // We merge consecutive blocks of text to simplify the overall structure. + if (Chunks.empty() || Chunks.back().Kind != ChunkKind::PlainText) { + Chunk C; + C.Kind = ChunkKind::PlainText; + Chunks.push_back(C); + } + // FIXME: ensure there is a whitespace between the chunks. + Chunks.back().Contents += Text; +} + +void FormattedString::appendCodeBlock(std::string Code, std::string Language) { + Chunk C; + C.Kind = ChunkKind::CodeBlock; + C.Contents = std::move(Code); + C.Language = std::move(Language); + Chunks.push_back(std::move(C)); +} + +void FormattedString::appendInlineCode(std::string Code) { + Chunk C; + C.Kind = ChunkKind::InlineCodeBlock; + C.Contents = std::move(Code); + Chunks.push_back(std::move(C)); +} + +std::string FormattedString::renderAsMarkdown() const { + std::string R; + for (const auto &C : Chunks) { + switch (C.Kind) { + case ChunkKind::PlainText: + R += renderText(C.Contents); + continue; + case ChunkKind::InlineCodeBlock: + // Make sure we don't glue two backticks together. + if (llvm::StringRef(R).endswith("`")) + R += " "; + R += renderInlineBlock(C.Contents); + continue; + case ChunkKind::CodeBlock: + if (!R.empty() && !llvm::StringRef(R).endswith("\n")) + R += "\n"; + R += renderCodeBlock(C.Contents, C.Language); + R += "\n"; + continue; + } + llvm_unreachable("unhanlded ChunkKind"); + } + return R; +} + +std::string FormattedString::renderAsPlainText() const { + std::string R; + auto EnsureWhitespace = [&]() { + if (R.empty() || isWhitespace(R.back())) + return; + R += " "; + }; + for (const auto &C : Chunks) { + switch (C.Kind) { + case ChunkKind::PlainText: + EnsureWhitespace(); + R += C.Contents; + continue; + case ChunkKind::InlineCodeBlock: + EnsureWhitespace(); + R += C.Contents; + continue; + case ChunkKind::CodeBlock: + if (!R.empty()) + R += "\n\n"; + R += C.Contents; + if (!llvm::StringRef(C.Contents).endswith("\n")) + R += "\n"; + continue; + } + llvm_unreachable("unhanlded ChunkKind"); + } + while (!R.empty() && isWhitespace(R.back())) + R.pop_back(); + return R; +} +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/FormattedString.h b/clang-tools-extra/clangd/FormattedString.h new file mode 100644 index 0000000000000..f20c19af85680 --- /dev/null +++ b/clang-tools-extra/clangd/FormattedString.h @@ -0,0 +1,57 @@ +//===--- FormattedString.h ----------------------------------*- C++-*------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// A simple intermediate representation of formatted text that could be +// converted to plaintext or markdown. +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMATTEDSTRING_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMATTEDSTRING_H + +#include +#include + +namespace clang { +namespace clangd { + +/// A structured string representation that could be converted to markdown or +/// plaintext upon requrest. +class FormattedString { +public: + /// Append plain text to the end of the string. + void appendText(std::string Text); + /// Append a block of C++ code. This translates to a ``` block in markdown. + /// In a plain text representation, the code block will be surrounded by + /// newlines. + void appendCodeBlock(std::string Code, std::string Language = "cpp"); + /// Append an inline block of C++ code. This translates to the ` block in + /// markdown. + void appendInlineCode(std::string Code); + + std::string renderAsMarkdown() const; + std::string renderAsPlainText() const; + +private: + enum class ChunkKind { + PlainText, /// A plain text paragraph. + CodeBlock, /// A block of code. + InlineCodeBlock, /// An inline block of code. + }; + struct Chunk { + ChunkKind Kind = ChunkKind::PlainText; + std::string Contents; + /// Language for code block chunks. Ignored for other chunks. + std::string Language; + }; + std::vector Chunks; +}; + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index 9a8d4ac74f0a6..957e2f3da444c 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -206,11 +206,10 @@ struct TextEdit { /// The string to be inserted. For delete operations use an /// empty string. std::string newText; - - bool operator==(const TextEdit &rhs) const { - return newText == rhs.newText && range == rhs.range; - } }; +inline bool operator==(const TextEdit &L, const TextEdit &R) { + return std::tie(L.newText, L.range) == std::tie(R.newText, R.range); +} bool fromJSON(const llvm::json::Value &, TextEdit &); llvm::json::Value toJSON(const TextEdit &); llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TextEdit &); @@ -294,7 +293,7 @@ using CompletionItemKindBitset = std::bitset; bool fromJSON(const llvm::json::Value &, CompletionItemKindBitset &); CompletionItemKind adjustKindToCapability(CompletionItemKind Kind, - CompletionItemKindBitset &supportedCompletionItemKinds); + CompletionItemKindBitset &SupportedCompletionItemKinds); /// A symbol kind. enum class SymbolKind { @@ -352,7 +351,7 @@ enum class OffsetEncoding { }; llvm::json::Value toJSON(const OffsetEncoding &); bool fromJSON(const llvm::json::Value &, OffsetEncoding &); -llvm::raw_ostream &operator<<(llvm::raw_ostream &, OffsetEncoding OS); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, OffsetEncoding); // This struct doesn't mirror LSP! // The protocol defines deeply nested structures for client capabilities. diff --git a/clang-tools-extra/clangd/Quality.cpp b/clang-tools-extra/clangd/Quality.cpp index 124ae4c1091d2..6307006c21ea2 100644 --- a/clang-tools-extra/clangd/Quality.cpp +++ b/clang-tools-extra/clangd/Quality.cpp @@ -336,6 +336,15 @@ static float scopeBoost(ScopeDistance &Distance, return std::max(0.65, 2.0 * std::pow(0.6, D / 2.0)); } +static llvm::Optional +wordMatching(llvm::StringRef Name, const llvm::StringSet<> *ContextWords) { + if (ContextWords) + for (const auto& Word : ContextWords->keys()) + if (Name.contains_lower(Word)) + return Word; + return llvm::None; +} + float SymbolRelevanceSignals::evaluate() const { float Score = 1; @@ -357,6 +366,9 @@ float SymbolRelevanceSignals::evaluate() const { Score *= SemaSaysInScope ? 2.0 : scopeBoost(*ScopeProximityMatch, SymbolScope); + if (wordMatching(Name, ContextWords)) + Score *= 1.5; + // Symbols like local variables may only be referenced within their scope. // Conversely if we're in that scope, it's likely we'll reference them. if (Query == CodeComplete) { @@ -413,7 +425,12 @@ float SymbolRelevanceSignals::evaluate() const { llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const SymbolRelevanceSignals &S) { OS << llvm::formatv("=== Symbol relevance: {0}\n", S.evaluate()); + OS << llvm::formatv("\tName: {0}\n", S.Name); OS << llvm::formatv("\tName match: {0}\n", S.NameMatch); + if (S.ContextWords) + OS << llvm::formatv( + "\tMatching context word: {0}\n", + wordMatching(S.Name, S.ContextWords).getValueOr("")); OS << llvm::formatv("\tForbidden: {0}\n", S.Forbidden); OS << llvm::formatv("\tNeedsFixIts: {0}\n", S.NeedsFixIts); OS << llvm::formatv("\tIsInstanceMember: {0}\n", S.IsInstanceMember); diff --git a/clang-tools-extra/clangd/Quality.h b/clang-tools-extra/clangd/Quality.h index 5eea7dbca0fb5..b358a919556e7 100644 --- a/clang-tools-extra/clangd/Quality.h +++ b/clang-tools-extra/clangd/Quality.h @@ -32,13 +32,14 @@ #include "clang/Sema/CodeCompleteConsumer.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSet.h" #include #include #include namespace llvm { class raw_ostream; -} +} // namespace llvm namespace clang { class CodeCompletionResult; @@ -84,8 +85,12 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &, /// Attributes of a symbol-query pair that affect how much we like it. struct SymbolRelevanceSignals { + /// The name of the symbol (for ContextWords). Must be explicitly assigned. + llvm::StringRef Name; /// 0-1+ fuzzy-match score for unqualified name. Must be explicitly assigned. float NameMatch = 1; + /// Lowercase words relevant to the context (e.g. near the completion point). + llvm::StringSet<>* ContextWords = nullptr; bool Forbidden = false; // Unavailable (e.g const) or inaccessible (private). /// Whether fixits needs to be applied for that completion or not. bool NeedsFixIts = false; diff --git a/clang-tools-extra/clangd/SourceCode.cpp b/clang-tools-extra/clangd/SourceCode.cpp index 1999fcd326144..6c52ca6c01c99 100644 --- a/clang-tools-extra/clangd/SourceCode.cpp +++ b/clang-tools-extra/clangd/SourceCode.cpp @@ -8,6 +8,7 @@ #include "SourceCode.h" #include "Context.h" +#include "FuzzyMatch.h" #include "Logger.h" #include "Protocol.h" #include "clang/AST/ASTContext.h" @@ -18,6 +19,7 @@ #include "llvm/ADT/None.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/Compiler.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Support/ErrorHandling.h" @@ -602,5 +604,43 @@ std::vector visibleNamespaces(llvm::StringRef Code, return Found; } +llvm::StringSet<> collectWords(llvm::StringRef Content) { + // We assume short words are not significant. + // We may want to consider other stopwords, e.g. language keywords. + // (A very naive implementation showed no benefit, but lexing might do better) + static constexpr int MinWordLength = 4; + + std::vector Roles(Content.size()); + calculateRoles(Content, Roles); + + llvm::StringSet<> Result; + llvm::SmallString<256> Word; + auto Flush = [&] { + if (Word.size() >= MinWordLength) { + for (char &C : Word) + C = llvm::toLower(C); + Result.insert(Word); + } + Word.clear(); + }; + for (unsigned I = 0; I < Content.size(); ++I) { + switch (Roles[I]) { + case Head: + Flush(); + LLVM_FALLTHROUGH; + case Tail: + Word.push_back(Content[I]); + break; + case Unknown: + case Separator: + Flush(); + break; + } + } + Flush(); + + return Result; +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/SourceCode.h b/clang-tools-extra/clangd/SourceCode.h index 9dd11dc85a731..85db9458589cd 100644 --- a/clang-tools-extra/clangd/SourceCode.h +++ b/clang-tools-extra/clangd/SourceCode.h @@ -20,6 +20,7 @@ #include "clang/Basic/SourceManager.h" #include "clang/Format/Format.h" #include "clang/Tooling/Core/Replacement.h" +#include "llvm/ADT/StringSet.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/SHA1.h" @@ -147,6 +148,11 @@ llvm::Optional getCanonicalPath(const FileEntry *F, bool isRangeConsecutive(const Range &Left, const Range &Right); +/// Choose the clang-format style we should apply to a certain file. +/// This will usually use FS to look for .clang-format directories. +/// FIXME: should we be caching the .clang-format file search? +/// This uses format::DefaultFormatStyle and format::DefaultFallbackStyle, +/// though the latter may have been overridden in main()! format::FormatStyle getFormatStyleForFile(llvm::StringRef File, llvm::StringRef Content, llvm::vfs::FileSystem *FS); @@ -160,6 +166,13 @@ cleanupAndFormat(StringRef Code, const tooling::Replacements &Replaces, llvm::StringMap collectIdentifiers(llvm::StringRef Content, const format::FormatStyle &Style); +/// Collects words from the source code. +/// Unlike collectIdentifiers: +/// - also finds text in comments: +/// - splits text into words +/// - drops stopwords like "get" and "for" +llvm::StringSet<> collectWords(llvm::StringRef Content); + /// Heuristically determine namespaces visible at a point, without parsing Code. /// This considers using-directives and enclosing namespace-declarations that /// are visible (and not obfuscated) in the file itself (not headers). diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index 6f47618fb0884..c51631ad1934b 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -346,24 +346,27 @@ std::vector locateSymbolAt(ParsedAST &AST, Position Pos, Index->lookup(QueryRequest, [&](const Symbol &Sym) { auto &R = Result[ResultIndex.lookup(Sym.ID)]; - // Special case: if the AST yielded a definition, then it may not be - // the right *declaration*. Prefer the one from the index. if (R.Definition) { // from AST + // Special case: if the AST yielded a definition, then it may not be + // the right *declaration*. Prefer the one from the index. if (auto Loc = toLSPLocation(Sym.CanonicalDeclaration, *MainFilePath)) R.PreferredDeclaration = *Loc; + + // We might still prefer the definition from the index, e.g. for + // generated symbols. + if (auto Loc = toLSPLocation( + getPreferredLocation(*R.Definition, Sym.Definition, Scratch), + *MainFilePath)) + R.Definition = *Loc; } else { R.Definition = toLSPLocation(Sym.Definition, *MainFilePath); - if (Sym.CanonicalDeclaration) { - // Use merge logic to choose AST or index declaration. - // We only do this for declarations as definitions from AST - // is generally preferred (e.g. definitions in main file). - if (auto Loc = toLSPLocation( - getPreferredLocation(R.PreferredDeclaration, - Sym.CanonicalDeclaration, Scratch), - *MainFilePath)) - R.PreferredDeclaration = *Loc; - } + // Use merge logic to choose AST or index declaration. + if (auto Loc = toLSPLocation( + getPreferredLocation(R.PreferredDeclaration, + Sym.CanonicalDeclaration, Scratch), + *MainFilePath)) + R.PreferredDeclaration = *Loc; } }); } diff --git a/clang-tools-extra/clangd/clients/clangd-vscode/package-lock.json b/clang-tools-extra/clangd/clients/clangd-vscode/package-lock.json index 90685912f1995..47373624481ae 100644 --- a/clang-tools-extra/clangd/clients/clangd-vscode/package-lock.json +++ b/clang-tools-extra/clangd/clients/clangd-vscode/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-clangd", - "version": "0.0.10", + "version": "0.0.12", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -489,9 +489,9 @@ "dev": true }, "fstream": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -1401,9 +1401,9 @@ }, "dependencies": { "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -1413,15 +1413,6 @@ "once": "^1.3.0", "path-is-absolute": "^1.0.0" } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } } } }, @@ -1547,13 +1538,13 @@ } }, "tar": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", "dev": true, "requires": { "block-stream": "*", - "fstream": "^1.0.2", + "fstream": "^1.0.12", "inherits": "2" } }, diff --git a/clang-tools-extra/clangd/index/Background.cpp b/clang-tools-extra/clangd/index/Background.cpp index 0746a3583718a..02cd11d138985 100644 --- a/clang-tools-extra/clangd/index/Background.cpp +++ b/clang-tools-extra/clangd/index/Background.cpp @@ -148,19 +148,20 @@ BackgroundIndex::BackgroundIndex( })) { assert(ThreadPoolSize > 0 && "Thread pool size can't be zero."); assert(this->IndexStorageFactory && "Storage factory can not be null!"); - while (ThreadPoolSize--) - ThreadPool.emplace_back([this] { run(); }); + for (unsigned I = 0; I < ThreadPoolSize; ++I) { + ThreadPool.runAsync("background-worker-" + llvm::Twine(I + 1), + [this] { run(); }); + } if (BuildIndexPeriodMs > 0) { log("BackgroundIndex: build symbol index periodically every {0} ms.", BuildIndexPeriodMs); - ThreadPool.emplace_back([this] { buildIndex(); }); + ThreadPool.runAsync("background-index-builder", [this] { buildIndex(); }); } } BackgroundIndex::~BackgroundIndex() { stop(); - for (auto &Thread : ThreadPool) - Thread.join(); + ThreadPool.wait(); } void BackgroundIndex::stop() { @@ -355,7 +356,8 @@ void BackgroundIndex::update(llvm::StringRef MainFile, IndexFileIn Index, // This can override a newer version that is added in another thread, if // this thread sees the older version but finishes later. This should be // rare in practice. - IndexedSymbols.update(Path, std::move(SS), std::move(RS)); + IndexedSymbols.update(Path, std::move(SS), std::move(RS), + Path == MainFile); } } } @@ -477,7 +479,8 @@ BackgroundIndex::loadShard(const tooling::CompileCommand &Cmd, struct ShardInfo { std::string AbsolutePath; std::unique_ptr Shard; - FileDigest Digest; + FileDigest Digest = {}; + bool CountReferences = false; }; std::vector IntermediateSymbols; // Make sure we don't have duplicate elements in the queue. Keys are absolute @@ -538,6 +541,7 @@ BackgroundIndex::loadShard(const tooling::CompileCommand &Cmd, SI.AbsolutePath = CurDependency.Path; SI.Shard = std::move(Shard); SI.Digest = I.getValue().Digest; + SI.CountReferences = I.getValue().IsTU; IntermediateSymbols.push_back(std::move(SI)); // Check if the source needs re-indexing. // Get the digest, skip it if file doesn't exist. @@ -567,7 +571,8 @@ BackgroundIndex::loadShard(const tooling::CompileCommand &Cmd, ? llvm::make_unique(std::move(*SI.Shard->Refs)) : nullptr; IndexedFileDigests[SI.AbsolutePath] = SI.Digest; - IndexedSymbols.update(SI.AbsolutePath, std::move(SS), std::move(RS)); + IndexedSymbols.update(SI.AbsolutePath, std::move(SS), std::move(RS), + SI.CountReferences); } } diff --git a/clang-tools-extra/clangd/index/Background.h b/clang-tools-extra/clangd/index/Background.h index 2132e579db43d..cc530d5ac58d1 100644 --- a/clang-tools-extra/clangd/index/Background.h +++ b/clang-tools-extra/clangd/index/Background.h @@ -146,7 +146,7 @@ class BackgroundIndex : public SwapIndex { std::condition_variable QueueCV; bool ShouldStop = false; std::deque> Queue; - std::vector ThreadPool; // FIXME: Abstract this away. + AsyncTaskRunner ThreadPool; GlobalCompilationDatabase::CommandChanged::Subscription CommandsChanged; }; diff --git a/clang-tools-extra/clangd/index/FileIndex.cpp b/clang-tools-extra/clangd/index/FileIndex.cpp index 7eca85e22e008..49746ae926bbd 100644 --- a/clang-tools-extra/clangd/index/FileIndex.cpp +++ b/clang-tools-extra/clangd/index/FileIndex.cpp @@ -90,28 +90,36 @@ SymbolSlab indexHeaderSymbols(ASTContext &AST, std::shared_ptr PP, } void FileSymbols::update(PathRef Path, std::unique_ptr Symbols, - std::unique_ptr Refs) { + std::unique_ptr Refs, bool CountReferences) { std::lock_guard Lock(Mutex); if (!Symbols) FileToSymbols.erase(Path); else FileToSymbols[Path] = std::move(Symbols); - if (!Refs) + if (!Refs) { FileToRefs.erase(Path); - else - FileToRefs[Path] = std::move(Refs); + return; + } + RefSlabAndCountReferences Item; + Item.CountReferences = CountReferences; + Item.Slab = std::move(Refs); + FileToRefs[Path] = std::move(Item); } std::unique_ptr FileSymbols::buildIndex(IndexType Type, DuplicateHandling DuplicateHandle) { std::vector> SymbolSlabs; std::vector> RefSlabs; + std::vector MainFileRefs; { std::lock_guard Lock(Mutex); for (const auto &FileAndSymbols : FileToSymbols) SymbolSlabs.push_back(FileAndSymbols.second); - for (const auto &FileAndRefs : FileToRefs) - RefSlabs.push_back(FileAndRefs.second); + for (const auto &FileAndRefs : FileToRefs) { + RefSlabs.push_back(FileAndRefs.second.Slab); + if (FileAndRefs.second.CountReferences) + MainFileRefs.push_back(RefSlabs.back().get()); + } } std::vector AllSymbols; std::vector SymsStorage; @@ -120,25 +128,37 @@ FileSymbols::buildIndex(IndexType Type, DuplicateHandling DuplicateHandle) { llvm::DenseMap Merged; for (const auto &Slab : SymbolSlabs) { for (const auto &Sym : *Slab) { + assert(Sym.References == 0 && + "Symbol with non-zero references sent to FileSymbols"); auto I = Merged.try_emplace(Sym.ID, Sym); if (!I.second) I.first->second = mergeSymbol(I.first->second, Sym); } } + for (const RefSlab *Refs : MainFileRefs) + for (const auto &Sym : *Refs) { + auto It = Merged.find(Sym.first); + // This might happen while background-index is still running. + if (It == Merged.end()) + continue; + It->getSecond().References += Sym.second.size(); + } SymsStorage.reserve(Merged.size()); for (auto &Sym : Merged) { SymsStorage.push_back(std::move(Sym.second)); AllSymbols.push_back(&SymsStorage.back()); } - // FIXME: aggregate symbol reference count based on references. break; } case DuplicateHandling::PickOne: { llvm::DenseSet AddedSymbols; for (const auto &Slab : SymbolSlabs) - for (const auto &Sym : *Slab) + for (const auto &Sym : *Slab) { + assert(Sym.References == 0 && + "Symbol with non-zero references sent to FileSymbols"); if (AddedSymbols.insert(Sym.ID).second) AllSymbols.push_back(&Sym); + } break; } } @@ -201,9 +221,9 @@ void FileIndex::updatePreamble(PathRef Path, ASTContext &AST, std::shared_ptr PP, const CanonicalIncludes &Includes) { auto Symbols = indexHeaderSymbols(AST, std::move(PP), Includes); - PreambleSymbols.update(Path, - llvm::make_unique(std::move(Symbols)), - llvm::make_unique()); + PreambleSymbols.update( + Path, llvm::make_unique(std::move(Symbols)), + llvm::make_unique(), /*CountReferences=*/false); PreambleIndex.reset( PreambleSymbols.buildIndex(UseDex ? IndexType::Heavy : IndexType::Light, DuplicateHandling::PickOne)); @@ -213,7 +233,8 @@ void FileIndex::updateMain(PathRef Path, ParsedAST &AST) { auto Contents = indexMainDecls(AST); MainFileSymbols.update( Path, llvm::make_unique(std::move(Contents.first)), - llvm::make_unique(std::move(Contents.second))); + llvm::make_unique(std::move(Contents.second)), + /*CountReferences=*/true); MainFileIndex.reset( MainFileSymbols.buildIndex(IndexType::Light, DuplicateHandling::PickOne)); } diff --git a/clang-tools-extra/clangd/index/FileIndex.h b/clang-tools-extra/clangd/index/FileIndex.h index d9bee8885d194..87abd0089c0ff 100644 --- a/clang-tools-extra/clangd/index/FileIndex.h +++ b/clang-tools-extra/clangd/index/FileIndex.h @@ -60,21 +60,29 @@ class FileSymbols { public: /// Updates all symbols and refs in a file. /// If either is nullptr, corresponding data for \p Path will be removed. + /// If CountReferences is true, \p Refs will be used for counting References + /// during merging. void update(PathRef Path, std::unique_ptr Slab, - std::unique_ptr Refs); + std::unique_ptr Refs, bool CountReferences); - // The index keeps the symbols alive. + /// The index keeps the symbols alive. + /// Will count Symbol::References based on number of references in the main + /// files, while building the index with DuplicateHandling::Merge option. std::unique_ptr buildIndex(IndexType, DuplicateHandling DuplicateHandle = DuplicateHandling::PickOne); private: + struct RefSlabAndCountReferences { + std::shared_ptr Slab; + bool CountReferences = false; + }; mutable std::mutex Mutex; /// Stores the latest symbol snapshots for all active files. llvm::StringMap> FileToSymbols; /// Stores the latest ref snapshots for all active files. - llvm::StringMap> FileToRefs; + llvm::StringMap FileToRefs; }; /// This manages symbols from files and an in-memory index on all symbols. diff --git a/clang-tools-extra/clangd/index/IndexAction.cpp b/clang-tools-extra/clangd/index/IndexAction.cpp index 120f6b0bae5ad..c4f553bfb61db 100644 --- a/clang-tools-extra/clangd/index/IndexAction.cpp +++ b/clang-tools-extra/clangd/index/IndexAction.cpp @@ -186,7 +186,6 @@ std::unique_ptr createStaticIndexingAction( IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; Opts.CollectIncludePath = true; - Opts.CountReferences = true; if (Opts.Origin == SymbolOrigin::Unknown) Opts.Origin = SymbolOrigin::Static; Opts.StoreAllDocumentation = false; diff --git a/clang-tools-extra/clangd/index/Serialization.cpp b/clang-tools-extra/clangd/index/Serialization.cpp index 62eb3fcdf3752..12993eb631ca6 100644 --- a/clang-tools-extra/clangd/index/Serialization.cpp +++ b/clang-tools-extra/clangd/index/Serialization.cpp @@ -370,7 +370,7 @@ readRefs(Reader &Data, llvm::ArrayRef Strings) { // The current versioning scheme is simple - non-current versions are rejected. // If you make a breaking change, bump this version number to invalidate stored // data. Later we may want to support some backward compatibility. -constexpr static uint32_t Version = 9; +constexpr static uint32_t Version = 10; llvm::Expected readRIFF(llvm::StringRef Data) { auto RIFF = riff::readFile(Data); diff --git a/clang-tools-extra/clangd/index/Symbol.cpp b/clang-tools-extra/clangd/index/Symbol.cpp index cc4ce3468ee5a..137f90df51978 100644 --- a/clang-tools-extra/clangd/index/Symbol.cpp +++ b/clang-tools-extra/clangd/index/Symbol.cpp @@ -48,27 +48,23 @@ static void own(Symbol &S, llvm::UniqueStringSaver &Strings) { } void SymbolSlab::Builder::insert(const Symbol &S) { - auto R = SymbolIndex.try_emplace(S.ID, Symbols.size()); - if (R.second) { - Symbols.push_back(S); - own(Symbols.back(), UniqueStrings); - } else { - auto &Copy = Symbols[R.first->second] = S; - own(Copy, UniqueStrings); - } + own(Symbols[S.ID] = S, UniqueStrings); } SymbolSlab SymbolSlab::Builder::build() && { - Symbols = {Symbols.begin(), Symbols.end()}; // Force shrink-to-fit. - // Sort symbols so the slab can binary search over them. - llvm::sort(Symbols, + // Sort symbols into vector so the slab can binary search over them. + std::vector SortedSymbols; + SortedSymbols.reserve(Symbols.size()); + for (auto &Entry : Symbols) + SortedSymbols.push_back(std::move(Entry.second)); + llvm::sort(SortedSymbols, [](const Symbol &L, const Symbol &R) { return L.ID < R.ID; }); // We may have unused strings from overwritten symbols. Build a new arena. llvm::BumpPtrAllocator NewArena; llvm::UniqueStringSaver Strings(NewArena); - for (auto &S : Symbols) + for (auto &S : SortedSymbols) own(S, Strings); - return SymbolSlab(std::move(NewArena), std::move(Symbols)); + return SymbolSlab(std::move(NewArena), std::move(SortedSymbols)); } } // namespace clangd diff --git a/clang-tools-extra/clangd/index/Symbol.h b/clang-tools-extra/clangd/index/Symbol.h index c79c0dc8dce1c..65ca82698d485 100644 --- a/clang-tools-extra/clangd/index/Symbol.h +++ b/clang-tools-extra/clangd/index/Symbol.h @@ -204,10 +204,13 @@ class SymbolSlab { /// This is a deep copy: underlying strings will be owned by the slab. void insert(const Symbol &S); - /// Returns the symbol with an ID, if it exists. Valid until next insert(). + /// Removes the symbol with an ID, if it exists. + void erase(const SymbolID &ID) { Symbols.erase(ID); } + + /// Returns the symbol with an ID, if it exists. Valid until insert/remove. const Symbol *find(const SymbolID &ID) { - auto I = SymbolIndex.find(ID); - return I == SymbolIndex.end() ? nullptr : &Symbols[I->second]; + auto I = Symbols.find(ID); + return I == Symbols.end() ? nullptr : &I->second; } /// Consumes the builder to finalize the slab. @@ -217,9 +220,8 @@ class SymbolSlab { llvm::BumpPtrAllocator Arena; /// Intern table for strings. Contents are on the arena. llvm::UniqueStringSaver UniqueStrings; - std::vector Symbols; /// Values are indices into Symbols vector. - llvm::DenseMap SymbolIndex; + llvm::DenseMap Symbols; }; private: diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index b9f321727d3d5..af1938dafa8ab 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -352,9 +352,8 @@ bool SymbolCollector::handleMacroOccurence(const IdentifierInfo *Name, const auto &SM = PP->getSourceManager(); auto DefLoc = MI->getDefinitionLoc(); - // Header guards are not interesting in index. Builtin macros don't have - // useful locations and are not needed for code completions. - if (MI->isUsedForHeaderGuard() || MI->isBuiltinMacro()) + // Builtin macros don't have useful locations and aren't needed in completion. + if (MI->isBuiltinMacro()) return true; // Skip main-file symbols if we are not collecting them. @@ -408,22 +407,25 @@ bool SymbolCollector::handleMacroOccurence(const IdentifierInfo *Name, std::string Signature; std::string SnippetSuffix; getSignature(*CCS, &Signature, &SnippetSuffix); - - std::string Include; - if (Opts.CollectIncludePath && shouldCollectIncludePath(S.SymInfo.Kind)) { - if (auto Header = getIncludeHeader( - Name->getName(), SM.getDecomposedExpansionLoc(DefLoc).first)) - Include = std::move(*Header); - } S.Signature = Signature; S.CompletionSnippetSuffix = SnippetSuffix; - if (!Include.empty()) - S.IncludeHeaders.emplace_back(Include, 1); + IndexedMacros.insert(Name); + setIncludeLocation(S, DefLoc); Symbols.insert(S); return true; } +void SymbolCollector::setIncludeLocation(const Symbol &S, + SourceLocation Loc) { + if (Opts.CollectIncludePath) + if (shouldCollectIncludePath(S.SymInfo.Kind)) + // Use the expansion location to get the #include header since this is + // where the symbol is exposed. + IncludeFiles[S.ID] = + PP->getSourceManager().getDecomposedExpansionLoc(Loc).first; +} + void SymbolCollector::finish() { // At the end of the TU, add 1 to the refcount of all referenced symbols. auto IncRef = [this](const SymbolID &ID) { @@ -440,6 +442,14 @@ void SymbolCollector::finish() { } if (Opts.CollectMacro) { assert(PP); + // First, drop header guards. We can't identify these until EOF. + for (const IdentifierInfo *II : IndexedMacros) { + if (const auto *MI = PP->getMacroDefinition(II).getMacroInfo()) + if (auto ID = getSymbolID(*II, MI, PP->getSourceManager())) + if (MI->isUsedForHeaderGuard()) + Symbols.erase(*ID); + } + // Now increment refcounts. for (const IdentifierInfo *II : ReferencedMacros) { if (const auto *MI = PP->getMacroDefinition(II).getMacroInfo()) if (auto ID = getSymbolID(*II, MI, PP->getSourceManager())) @@ -447,6 +457,21 @@ void SymbolCollector::finish() { } } + // Fill in IncludeHeaders. + // We delay this until end of TU so header guards are all resolved. + // Symbols in slabs aren' mutable, so insert() has to walk all the strings :-( + llvm::SmallString<256> QName; + for (const auto &Entry : IncludeFiles) + if (const Symbol *S = Symbols.find(Entry.first)) { + QName = S->Scope; + QName.append(S->Name); + if (auto Header = getIncludeHeader(QName, Entry.second)) { + Symbol NewSym = *S; + NewSym.IncludeHeaders.push_back({*Header, 1}); + Symbols.insert(NewSym); + } + } + const auto &SM = ASTCtx->getSourceManager(); llvm::DenseMap URICache; auto GetURI = [&](FileID FID) -> llvm::Optional { @@ -464,7 +489,7 @@ void SymbolCollector::finish() { } return Found->second; }; - + // Populate Refs slab from DeclRefs. if (auto MainFileURI = GetURI(SM.getMainFileID())) { for (const auto &It : DeclRefs) { if (auto ID = getSymbolID(It.first)) { @@ -492,6 +517,7 @@ void SymbolCollector::finish() { DeclRefs.clear(); FilesToIndexCache.clear(); HeaderIsSelfContainedCache.clear(); + IncludeFiles.clear(); } const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND, SymbolID ID, @@ -556,17 +582,6 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND, SymbolID ID, std::string ReturnType = getReturnType(*CCS); S.ReturnType = ReturnType; - std::string Include; - if (Opts.CollectIncludePath && shouldCollectIncludePath(S.SymInfo.Kind)) { - // Use the expansion location to get the #include header since this is - // where the symbol is exposed. - if (auto Header = getIncludeHeader( - QName, SM.getDecomposedExpansionLoc(ND.getLocation()).first)) - Include = std::move(*Header); - } - if (!Include.empty()) - S.IncludeHeaders.emplace_back(Include, 1); - llvm::Optional TypeStorage; if (S.Flags & Symbol::IndexedForCodeCompletion) { TypeStorage = OpaqueType::fromCompletionResult(*ASTCtx, SymbolCompletion); @@ -575,6 +590,7 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND, SymbolID ID, } Symbols.insert(S); + setIncludeLocation(S, ND.getLocation()); return Symbols.find(S.ID); } diff --git a/clang-tools-extra/clangd/index/SymbolCollector.h b/clang-tools-extra/clangd/index/SymbolCollector.h index 689d4a4d9f05b..f746002bbd3e3 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.h +++ b/clang-tools-extra/clangd/index/SymbolCollector.h @@ -125,6 +125,12 @@ class SymbolCollector : public index::IndexDataConsumer { // All Symbols collected from the AST. SymbolSlab::Builder Symbols; + // File IDs for Symbol.IncludeHeaders. + // The final spelling is calculated in finish(). + llvm::DenseMap IncludeFiles; + void setIncludeLocation(const Symbol &S, SourceLocation); + // Indexed macros, to be erased if they turned out to be include guards. + llvm::DenseSet IndexedMacros; // All refs collected from the AST. // Only symbols declared in preamble (from #include) and referenced from the // main file will be included. diff --git a/clang-tools-extra/clangd/indexer/IndexerMain.cpp b/clang-tools-extra/clangd/indexer/IndexerMain.cpp index d012825101bfd..530c8891a607e 100644 --- a/clang-tools-extra/clangd/indexer/IndexerMain.cpp +++ b/clang-tools-extra/clangd/indexer/IndexerMain.cpp @@ -41,6 +41,7 @@ class IndexActionFactory : public tooling::FrontendActionFactory { clang::FrontendAction *create() override { SymbolCollector::Options Opts; + Opts.CountReferences = true; return createStaticIndexingAction( Opts, [&](SymbolSlab S) { diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp new file mode 100644 index 0000000000000..98d6fd488bb39 --- /dev/null +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -0,0 +1,81 @@ +#include "refactor/Rename.h" +#include "clang/Tooling/Refactoring/RefactoringResultConsumer.h" +#include "clang/Tooling/Refactoring/Rename/RenamingAction.h" + +namespace clang { +namespace clangd { +namespace { + +class RefactoringResultCollector final + : public tooling::RefactoringResultConsumer { +public: + void handleError(llvm::Error Err) override { + assert(!Result.hasValue()); + Result = std::move(Err); + } + + // Using the handle(SymbolOccurrences) from parent class. + using tooling::RefactoringResultConsumer::handle; + + void handle(tooling::AtomicChanges SourceReplacements) override { + assert(!Result.hasValue()); + Result = std::move(SourceReplacements); + } + + llvm::Optional> Result; +}; + +// Expand a DiagnosticError to make it print-friendly (print the detailed +// message, rather than "clang diagnostic"). +llvm::Error expandDiagnostics(llvm::Error Err, DiagnosticsEngine &DE) { + if (auto Diag = DiagnosticError::take(Err)) { + llvm::cantFail(std::move(Err)); + SmallVector DiagMessage; + Diag->second.EmitToString(DE, DiagMessage); + return llvm::make_error(DiagMessage, + llvm::inconvertibleErrorCode()); + } + return Err; +} + +} // namespace + +llvm::Expected +renameWithinFile(ParsedAST &AST, llvm::StringRef File, Position Pos, + llvm::StringRef NewName) { + RefactoringResultCollector ResultCollector; + ASTContext &ASTCtx = AST.getASTContext(); + const SourceManager &SourceMgr = ASTCtx.getSourceManager(); + SourceLocation SourceLocationBeg = + clangd::getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID()); + tooling::RefactoringRuleContext Context(ASTCtx.getSourceManager()); + Context.setASTContext(ASTCtx); + auto Rename = clang::tooling::RenameOccurrences::initiate( + Context, SourceRange(SourceLocationBeg), NewName); + if (!Rename) + return expandDiagnostics(Rename.takeError(), ASTCtx.getDiagnostics()); + + Rename->invoke(ResultCollector, Context); + + assert(ResultCollector.Result.hasValue()); + if (!ResultCollector.Result.getValue()) + return expandDiagnostics(ResultCollector.Result->takeError(), + ASTCtx.getDiagnostics()); + + tooling::Replacements FilteredChanges; + // Right now we only support renaming the main file, so we + // drop replacements not for the main file. In the future, we might + // also support rename with wider scope. + // Rename sometimes returns duplicate edits (which is a bug). A side-effect of + // adding them to a single Replacements object is these are deduplicated. + for (const tooling::AtomicChange &Change : ResultCollector.Result->get()) { + for (const auto &Rep : Change.getReplacements()) { + if (Rep.getFilePath() == File) + cantFail(FilteredChanges.add(Rep)); + } + } + return FilteredChanges; +} + +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/refactor/Rename.h b/clang-tools-extra/clangd/refactor/Rename.h new file mode 100644 index 0000000000000..8119cd0b65f25 --- /dev/null +++ b/clang-tools-extra/clangd/refactor/Rename.h @@ -0,0 +1,24 @@ +//===--- Rename.h - Symbol-rename refactorings -------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ClangdUnit.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace clangd { + +/// Renames all occurrences of the symbol at \p Pos to \p NewName. +/// Occurrences outside the current file are not modified. +llvm::Expected renameWithinFile(ParsedAST &AST, + llvm::StringRef File, + Position Pos, + llvm::StringRef NewName); + +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt b/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt index 35528d87d9bf8..837853139689a 100644 --- a/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt +++ b/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt @@ -12,6 +12,7 @@ set(LLVM_LINK_COMPONENTS # $ to a list of sources, see # clangd/tool/CMakeLists.txt for an example. add_clang_library(clangDaemonTweaks OBJECT + RawStringLiteral.cpp SwapIfBranches.cpp LINK_LIBS diff --git a/clang-tools-extra/clangd/refactor/tweaks/RawStringLiteral.cpp b/clang-tools-extra/clangd/refactor/tweaks/RawStringLiteral.cpp new file mode 100644 index 0000000000000..e3eaba501922e --- /dev/null +++ b/clang-tools-extra/clangd/refactor/tweaks/RawStringLiteral.cpp @@ -0,0 +1,103 @@ +//===--- RawStringLiteral.cpp ------------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "ClangdUnit.h" +#include "Logger.h" +#include "SourceCode.h" +#include "refactor/Tweak.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/Stmt.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/iterator_range.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace clangd { +namespace { +/// Converts a string literal to a raw string. +/// Before: +/// printf("\"a\"\nb"); +/// ^^^^^^^^^ +/// After: +/// printf(R"("a" +/// b)"); +class RawStringLiteral : public Tweak { +public: + const char *id() const override final; + + bool prepare(const Selection &Inputs) override; + Expected apply(const Selection &Inputs) override; + std::string title() const override; + +private: + const clang::StringLiteral *Str = nullptr; +}; + +REGISTER_TWEAK(RawStringLiteral) + +static bool isNormalString(const StringLiteral &Str, SourceLocation Cursor, + SourceManager &SM) { + // All chunks must be normal ASCII strings, not u8"..." etc. + if (!Str.isAscii()) + return false; + SourceLocation LastTokenBeforeCursor; + for (auto I = Str.tokloc_begin(), E = Str.tokloc_end(); I != E; ++I) { + if (I->isMacroID()) // No tokens in the string may be macro expansions. + return false; + if (SM.isBeforeInTranslationUnit(*I, Cursor) || *I == Cursor) + LastTokenBeforeCursor = *I; + } + // Token we care about must be a normal "string": not raw, u8, etc. + const char* Data = SM.getCharacterData(LastTokenBeforeCursor); + return Data && *Data == '"'; +} + +static bool needsRaw(llvm::StringRef Content) { + return Content.find_first_of("\"\n\t") != StringRef::npos; +} + +static bool canBeRaw(llvm::StringRef Content) { + for (char C : Content) + if (!llvm::isPrint(C) && C != '\n' && C != '\t') + return false; + return !Content.contains(")\""); +} + +bool RawStringLiteral::prepare(const Selection &Inputs) { + const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor(); + if (!N) + return false; + Str = dyn_cast_or_null(N->ASTNode.get()); + return Str && + isNormalString(*Str, Inputs.Cursor, + Inputs.AST.getASTContext().getSourceManager()) && + needsRaw(Str->getBytes()) && canBeRaw(Str->getBytes()); +} + +Expected +RawStringLiteral::apply(const Selection &Inputs) { + return tooling::Replacements( + tooling::Replacement(Inputs.AST.getASTContext().getSourceManager(), Str, + ("R\"(" + Str->getBytes() + ")\"").str(), + Inputs.AST.getASTContext().getLangOpts())); +} + +std::string RawStringLiteral::title() const { return "Convert to raw string"; } + +} // namespace +} // namespace clangd +} // namespace clang + diff --git a/clang-tools-extra/clangd/test/completion-auto-trigger.test b/clang-tools-extra/clangd/test/completion-auto-trigger.test index db3cc537ad255..96830285f0452 100644 --- a/clang-tools-extra/clangd/test/completion-auto-trigger.test +++ b/clang-tools-extra/clangd/test/completion-auto-trigger.test @@ -23,7 +23,7 @@ # CHECK-NEXT: "insertTextFormat": 1, # CHECK-NEXT: "kind": 5, # CHECK-NEXT: "label": " size", -# CHECK-NEXT: "sortText": "3eacccccsize", +# CHECK-NEXT: "sortText": "{{.*}}size", # CHECK-NEXT: "textEdit": { # CHECK-NEXT: "newText": "size", # CHECK-NEXT: "range": { @@ -45,7 +45,7 @@ # CHECK-NEXT: "insertTextFormat": 1, # CHECK-NEXT: "kind": 10, # CHECK-NEXT: "label": " default_capacity", -# CHECK-NEXT: "sortText": "3fd70a3ddefault_capacity", +# CHECK-NEXT: "sortText": "{{.*}}default_capacity", # CHECK-NEXT: "textEdit": { # CHECK-NEXT: "newText": "default_capacity", # CHECK-NEXT: "range": { @@ -84,7 +84,7 @@ # CHECK-NEXT: "insertTextFormat": 1, # CHECK-NEXT: "kind": 6, # CHECK-NEXT: "label": " ns_member", -# CHECK-NEXT: "sortText": "3f2cccccns_member", +# CHECK-NEXT: "sortText": "{{.*}}ns_member", # CHECK-NEXT: "textEdit": { # CHECK-NEXT: "newText": "ns_member", # CHECK-NEXT: "range": { diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp index eca937ef2468f..44ca8a3363a29 100644 --- a/clang-tools-extra/clangd/tool/ClangdMain.cpp +++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp @@ -16,7 +16,9 @@ #include "index/Background.h" #include "index/Serialization.h" #include "clang/Basic/Version.h" +#include "clang/Format/Format.h" #include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" @@ -26,6 +28,7 @@ #include #include #include +#include #include #include @@ -116,7 +119,7 @@ static llvm::cl::opt PCHStorage( static llvm::cl::opt LimitResults( "limit-results", llvm::cl::desc("Limit the number of results returned by clangd. " - "0 means no limit."), + "0 means no limit. (default=100)"), llvm::cl::init(100)); static llvm::cl::opt RunSynchronously( @@ -231,6 +234,12 @@ static llvm::cl::opt EnableClangTidy( llvm::cl::desc("Enable clang-tidy diagnostics."), llvm::cl::init(true)); +static llvm::cl::opt + FallbackStyle("fallback-style", + llvm::cl::desc("clang-format style to apply by default when " + "no .clang-format file is found"), + llvm::cl::init(clang::format::DefaultFallbackStyle)); + static llvm::cl::opt SuggestMissingIncludes( "suggest-missing-includes", llvm::cl::desc("Attempts to fix diagnostic errors caused by missing " @@ -247,13 +256,18 @@ static llvm::cl::opt ForceOffsetEncoding( "Offsets are in UTF-16 code units")), llvm::cl::init(OffsetEncoding::UnsupportedEncoding)); -static llvm::cl::opt AllowFallbackCompletion( - "allow-fallback-completion", - llvm::cl::desc( - "Allow falling back to code completion without compiling files (using " - "identifiers and symbol indexes), when file cannot be built or the " - "build is not ready."), - llvm::cl::init(false)); +static llvm::cl::opt + CodeCompletionParse( + "completion-parse", + llvm::cl::desc("Whether the clang-parser is used for code-completion"), + llvm::cl::values(clEnumValN(CodeCompleteOptions::AlwaysParse, "always", + "Block until the parser can be used"), + clEnumValN(CodeCompleteOptions::ParseIfReady, "auto", + "Use text-based completion if the parser " + "is not ready"), + clEnumValN(CodeCompleteOptions::NeverParse, "never", + "Always used text-based completion")), + llvm::cl::init(CodeCompleteOptions().RunParser), llvm::cl::Hidden); namespace { @@ -352,6 +366,8 @@ int main(int argc, char *argv[]) { llvm::errs() << "Ignoring -j because -run-synchronously is set.\n"; WorkerThreadsCount = 0; } + if (FallbackStyle.getNumOccurrences()) + clang::format::DefaultFallbackStyle = FallbackStyle.c_str(); // Validate command line arguments. llvm::Optional InputMirrorStream; @@ -464,7 +480,7 @@ int main(int argc, char *argv[]) { CCOpts.SpeculativeIndexRequest = Opts.StaticIndex; CCOpts.EnableFunctionArgSnippets = EnableFunctionArgSnippets; CCOpts.AllScopes = AllScopesCompletion; - CCOpts.AllowFallback = AllowFallbackCompletion; + CCOpts.RunParser = CodeCompletionParse; RealFileSystemProvider FSProvider; // Initialize and run ClangdLSPServer. @@ -487,7 +503,9 @@ int main(int argc, char *argv[]) { } // Create an empty clang-tidy option. - std::unique_ptr ClangTidyOptProvider; + std::mutex ClangTidyOptMu; + std::unique_ptr + ClangTidyOptProvider; /*GUARDED_BY(ClangTidyOptMu)*/ if (EnableClangTidy) { auto OverrideClangTidyOptions = tidy::ClangTidyOptions::getDefaults(); OverrideClangTidyOptions.Checks = ClangTidyChecks; @@ -496,7 +514,13 @@ int main(int argc, char *argv[]) { /* Default */ tidy::ClangTidyOptions::getDefaults(), /* Override */ OverrideClangTidyOptions, FSProvider.getFileSystem()); } - Opts.ClangTidyOptProvider = ClangTidyOptProvider.get(); + Opts.GetClangTidyOptions = [&](llvm::vfs::FileSystem &, + llvm::StringRef File) { + // This function must be thread-safe and tidy option providers are not. + std::lock_guard Lock(ClangTidyOptMu); + // FIXME: use the FS provided to the function. + return ClangTidyOptProvider->getOptions(File); + }; Opts.SuggestMissingIncludes = SuggestMissingIncludes; llvm::Optional OffsetEncodingFromFlag; if (ForceOffsetEncoding != OffsetEncoding::UnsupportedEncoding) diff --git a/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp b/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp index 91eb6dec28cc6..df98ee8e5070c 100644 --- a/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp +++ b/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp @@ -7,12 +7,12 @@ #include "gtest/gtest.h" #include -using testing::_; -using testing::AllOf; -using testing::Contains; -using testing::ElementsAre; -using testing::Not; -using testing::UnorderedElementsAre; +using ::testing::_; +using ::testing::AllOf; +using ::testing::Contains; +using ::testing::ElementsAre; +using ::testing::Not; +using ::testing::UnorderedElementsAre; namespace clang { namespace clangd { @@ -23,15 +23,16 @@ MATCHER(Declared, "") { } MATCHER(Defined, "") { return !StringRef(arg.Definition.FileURI).empty(); } MATCHER_P(FileURI, F, "") { return StringRef(arg.Location.FileURI) == F; } -testing::Matcher -RefsAre(std::vector> Matchers) { - return ElementsAre(testing::Pair(_, UnorderedElementsAreArray(Matchers))); +::testing::Matcher +RefsAre(std::vector<::testing::Matcher> Matchers) { + return ElementsAre(::testing::Pair(_, UnorderedElementsAreArray(Matchers))); } // URI cannot be empty since it references keys in the IncludeGraph. MATCHER(EmptyIncludeNode, "") { return !arg.IsTU && !arg.URI.empty() && arg.Digest == FileDigest{{0}} && arg.DirectIncludes.empty(); } +MATCHER_P(NumReferences, N, "") { return arg.References == N; } class MemoryShardStorage : public BackgroundIndexStorage { mutable std::mutex StorageMu; @@ -112,6 +113,9 @@ TEST_F(BackgroundIndexTest, IndexTwoFiles) { #include "A.h" void f_b() { (void)common; + (void)common; + (void)common; + (void)common; })cpp"; llvm::StringMap Storage; size_t CacheHits = 0; @@ -127,20 +131,25 @@ TEST_F(BackgroundIndexTest, IndexTwoFiles) { CDB.setCompileCommand(testPath("root/A.cc"), Cmd); ASSERT_TRUE(Idx.blockUntilIdleForTest()); - EXPECT_THAT( - runFuzzyFind(Idx, ""), - UnorderedElementsAre(Named("common"), Named("A_CC"), Named("g"), - AllOf(Named("f_b"), Declared(), Not(Defined())))); + EXPECT_THAT(runFuzzyFind(Idx, ""), + UnorderedElementsAre(AllOf(Named("common"), NumReferences(1U)), + AllOf(Named("A_CC"), NumReferences(0U)), + AllOf(Named("g"), NumReferences(0U)), + AllOf(Named("f_b"), Declared(), + Not(Defined()), NumReferences(0U)))); Cmd.Filename = testPath("root/B.cc"); Cmd.CommandLine = {"clang++", Cmd.Filename}; - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); + CDB.setCompileCommand(testPath("root/B.cc"), Cmd); ASSERT_TRUE(Idx.blockUntilIdleForTest()); // B_CC is dropped as we don't collect symbols from A.h in this compilation. EXPECT_THAT(runFuzzyFind(Idx, ""), - UnorderedElementsAre(Named("common"), Named("A_CC"), Named("g"), - AllOf(Named("f_b"), Declared(), Defined()))); + UnorderedElementsAre(AllOf(Named("common"), NumReferences(5U)), + AllOf(Named("A_CC"), NumReferences(0U)), + AllOf(Named("g"), NumReferences(0U)), + AllOf(Named("f_b"), Declared(), Defined(), + NumReferences(1U)))); auto Syms = runFuzzyFind(Idx, "common"); EXPECT_THAT(Syms, UnorderedElementsAre(Named("common"))); @@ -148,6 +157,9 @@ TEST_F(BackgroundIndexTest, IndexTwoFiles) { EXPECT_THAT(getRefs(Idx, Common.ID), RefsAre({FileURI("unittest:///root/A.h"), FileURI("unittest:///root/A.cc"), + FileURI("unittest:///root/B.cc"), + FileURI("unittest:///root/B.cc"), + FileURI("unittest:///root/B.cc"), FileURI("unittest:///root/B.cc")})); } diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt index 96b1d3605a684..262da25663189 100644 --- a/clang-tools-extra/clangd/unittests/CMakeLists.txt +++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -38,6 +38,7 @@ add_unittest(ClangdUnitTests ClangdTests FileDistanceTests.cpp FileIndexTests.cpp FindSymbolsTests.cpp + FormattedStringTests.cpp FSTests.cpp FunctionTests.cpp FuzzyMatchTests.cpp @@ -48,6 +49,7 @@ add_unittest(ClangdUnitTests ClangdTests JSONTransportTests.cpp PrintASTTests.cpp QualityTests.cpp + RenameTests.cpp RIFFTests.cpp SelectionTests.cpp SerializationTests.cpp diff --git a/clang-tools-extra/clangd/unittests/ClangdTests.cpp b/clang-tools-extra/clangd/unittests/ClangdTests.cpp index 5d98bdc251c36..ac4ff6ed077a7 100644 --- a/clang-tools-extra/clangd/unittests/ClangdTests.cpp +++ b/clang-tools-extra/clangd/unittests/ClangdTests.cpp @@ -9,6 +9,7 @@ #include "Annotations.h" #include "ClangdLSPServer.h" #include "ClangdServer.h" +#include "CodeComplete.h" #include "GlobalCompilationDatabase.h" #include "Matchers.h" #include "SyncAPI.h" @@ -95,11 +96,8 @@ class MultipleErrorCheckingDiagConsumer : public DiagnosticsConsumer { std::vector> filesWithDiags() const { std::vector> Result; std::lock_guard Lock(Mutex); - - for (const auto &it : LastDiagsHadError) { - Result.emplace_back(it.first(), it.second); - } - + for (const auto &It : LastDiagsHadError) + Result.emplace_back(It.first(), It.second); return Result; } @@ -1075,7 +1073,7 @@ TEST_F(ClangdVFSTest, FallbackWhenPreambleIsNotReady) { FS.Files[FooCpp] = FooCpp; auto Opts = clangd::CodeCompleteOptions(); - Opts.AllowFallback = true; + Opts.RunParser = CodeCompleteOptions::ParseIfReady; // This will make compile command broken and preamble absent. CDB.ExtraClangFlags = {"yolo.cc"}; @@ -1092,11 +1090,17 @@ TEST_F(ClangdVFSTest, FallbackWhenPreambleIsNotReady) { CDB.ExtraClangFlags = {"-std=c++11"}; Server.addDocument(FooCpp, Code.code()); ASSERT_TRUE(Server.blockUntilIdleForTest()); - EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Code.point(), - clangd::CodeCompleteOptions())) - .Completions, - ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"), - Field(&CodeCompletion::Scope, "ns::")))); + EXPECT_THAT( + cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts)).Completions, + ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"), + Field(&CodeCompletion::Scope, "ns::")))); + + // Now force identifier-based completion. + Opts.RunParser = CodeCompleteOptions::NeverParse; + EXPECT_THAT( + cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts)).Completions, + ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"), + Field(&CodeCompletion::Scope, "")))); } TEST_F(ClangdVFSTest, FallbackWhenWaitingForCompileCommand) { @@ -1143,7 +1147,7 @@ TEST_F(ClangdVFSTest, FallbackWhenWaitingForCompileCommand) { // hasn't been scheduled. std::this_thread::sleep_for(std::chrono::milliseconds(10)); auto Opts = clangd::CodeCompleteOptions(); - Opts.AllowFallback = true; + Opts.RunParser = CodeCompleteOptions::ParseIfReady; auto Res = cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts)); EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery); diff --git a/clang-tools-extra/clangd/unittests/ClangdUnitTests.cpp b/clang-tools-extra/clangd/unittests/ClangdUnitTests.cpp index dd3fc6dd58b79..2c239ce76acd6 100644 --- a/clang-tools-extra/clangd/unittests/ClangdUnitTests.cpp +++ b/clang-tools-extra/clangd/unittests/ClangdUnitTests.cpp @@ -18,7 +18,7 @@ namespace clang { namespace clangd { namespace { -using testing::ElementsAre; +using ::testing::ElementsAre; TEST(ClangdUnitTest, GetBeginningOfIdentifier) { std::string Preamble = R"cpp( diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index c0ec8dd1a3737..e584597e7a90c 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -174,6 +174,7 @@ struct ClassWithMembers { int BBB(); int CCC(); }; + int main() { ClassWithMembers().^ } )cpp", /*IndexSymbols=*/{}, Opts); @@ -234,6 +235,7 @@ void TestAfterDotCompletion(clangd::CodeCompleteOptions Opts) { )cpp", {cls("IndexClass"), var("index_var"), func("index_func")}, Opts); + EXPECT_TRUE(Results.RanParser); // Class members. The only items that must be present in after-dot // completion. EXPECT_THAT(Results.Completions, @@ -283,6 +285,7 @@ void TestGlobalScopeCompletion(clangd::CodeCompleteOptions Opts) { )cpp", {cls("IndexClass"), var("index_var"), func("index_func")}, Opts); + EXPECT_TRUE(Results.RanParser); // Class members. Should never be present in global completions. EXPECT_THAT(Results.Completions, Not(AnyOf(Has("method"), Has("method()"), Has("field")))); @@ -324,7 +327,7 @@ TEST(CompletionTest, CompletionOptions) { } } -TEST(CompletionTest, Priorities) { +TEST(CompletionTest, Accessible) { auto Internal = completions(R"cpp( class Foo { public: void pub(); @@ -334,7 +337,7 @@ TEST(CompletionTest, Priorities) { void Foo::pub() { this->^ } )cpp"); EXPECT_THAT(Internal.Completions, - HasSubsequence(Named("priv"), Named("prot"), Named("pub"))); + AllOf(Has("priv"), Has("prot"), Has("pub"))); auto External = completions(R"cpp( class Foo { @@ -502,6 +505,21 @@ TEST(CompletionTest, ReferencesAffectRanking) { HasSubsequence(Named("absl"), Named("absb"))); } +TEST(CompletionTest, ContextWords) { + auto Results = completions(R"cpp( + enum class Color { RED, YELLOW, BLUE }; + + // (blank lines so the definition above isn't "context") + + // "It was a yellow car," he said. "Big yellow car, new." + auto Finish = Color::^ + )cpp"); + // Yellow would normally sort last (alphabetic). + // But the recent mention shuold bump it up. + ASSERT_THAT(Results.Completions, + HasSubsequence(Named("YELLOW"), Named("BLUE"))); +} + TEST(CompletionTest, GlobalQualified) { auto Results = completions( R"cpp( @@ -2443,6 +2461,7 @@ TEST(NoCompileCompletionTest, Basic) { ^ } )cpp"); + EXPECT_FALSE(Results.RanParser); EXPECT_THAT(Results.Completions, UnorderedElementsAre(Named("void"), Named("func"), Named("int"), Named("xyz"), Named("abc"))); diff --git a/clang-tools-extra/clangd/unittests/DexTests.cpp b/clang-tools-extra/clangd/unittests/DexTests.cpp index da744f11d1eed..11aedec5ad62c 100644 --- a/clang-tools-extra/clangd/unittests/DexTests.cpp +++ b/clang-tools-extra/clangd/unittests/DexTests.cpp @@ -352,16 +352,16 @@ TEST(DexIterators, Optimizations) { // Search token tests. //===----------------------------------------------------------------------===// -testing::Matcher> +::testing::Matcher> tokensAre(std::initializer_list Strings, Token::Kind Kind) { std::vector Tokens; for (const auto &TokenData : Strings) { Tokens.push_back(Token(Kind, TokenData)); } - return testing::UnorderedElementsAreArray(Tokens); + return ::testing::UnorderedElementsAreArray(Tokens); } -testing::Matcher> +::testing::Matcher> trigramsAre(std::initializer_list Trigrams) { return tokensAre(Trigrams, Token::Kind::Trigram); } diff --git a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp index ed12897e08df2..9d0492aa84d20 100644 --- a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp +++ b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp @@ -27,23 +27,24 @@ namespace clang { namespace clangd { namespace { -using testing::_; -using testing::ElementsAre; -using testing::Field; -using testing::IsEmpty; -using testing::Pair; -using testing::UnorderedElementsAre; - -testing::Matcher WithFix(testing::Matcher FixMatcher) { +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::Field; +using ::testing::IsEmpty; +using ::testing::Pair; +using ::testing::UnorderedElementsAre; + +::testing::Matcher WithFix(::testing::Matcher FixMatcher) { return Field(&Diag::Fixes, ElementsAre(FixMatcher)); } -testing::Matcher WithFix(testing::Matcher FixMatcher1, - testing::Matcher FixMatcher2) { +::testing::Matcher WithFix(::testing::Matcher FixMatcher1, + ::testing::Matcher FixMatcher2) { return Field(&Diag::Fixes, UnorderedElementsAre(FixMatcher1, FixMatcher2)); } -testing::Matcher WithNote(testing::Matcher NoteMatcher) { +::testing::Matcher +WithNote(::testing::Matcher NoteMatcher) { return Field(&Diag::Notes, ElementsAre(NoteMatcher)); } @@ -54,7 +55,7 @@ MATCHER_P2(Diag, Range, Message, MATCHER_P3(Fix, Range, Replacement, Message, "Fix " + llvm::to_string(Range) + " => " + - testing::PrintToString(Replacement) + " = [" + Message + "]") { + ::testing::PrintToString(Replacement) + " = [" + Message + "]") { return arg.Message == Message && arg.Edits.size() == 1 && arg.Edits[0].range == Range && arg.Edits[0].newText == Replacement; } @@ -72,6 +73,7 @@ MATCHER_P(EqualToLSPDiag, LSPDiag, MATCHER_P(DiagSource, S, "") { return arg.Source == S; } MATCHER_P(DiagName, N, "") { return arg.Name == N; } +MATCHER_P(DiagSeverity, S, "") { return arg.Severity == S; } MATCHER_P(EqualToFix, Fix, "LSP fix " + llvm::to_string(Fix)) { if (arg.Message != Fix.Message) @@ -159,7 +161,7 @@ TEST(DiagnosticsTest, DiagnosticPreamble) { auto TU = TestTU::withCode(Test.code()); EXPECT_THAT(TU.build().getDiagnostics(), - ElementsAre(testing::AllOf( + ElementsAre(::testing::AllOf( Diag(Test.range(), "'not-found.h' file not found"), DiagSource(Diag::Clang), DiagName("pp_file_not_found")))); } @@ -171,6 +173,8 @@ TEST(DiagnosticsTest, ClangTidy) { #define $macrodef[[SQUARE]](X) (X)*(X) int main() { return $doubled[[sizeof]](sizeof(int)); + } + int square() { int y = 4; return SQUARE($macroarg[[++]]y); } @@ -204,6 +208,64 @@ TEST(DiagnosticsTest, ClangTidy) { "multiple unsequenced modifications to 'y'"))); } +TEST(DiagnosticTest, ClangTidySuppressionComment) { + Annotations Main(R"cpp( + int main() { + int i = 3; + double d = 8 / i; // NOLINT + // NOLINTNEXTLINE + double e = 8 / i; + double f = [[8]] / i; + } + )cpp"); + TestTU TU = TestTU::withCode(Main.code()); + TU.ClangTidyChecks = "bugprone-integer-division"; + EXPECT_THAT( + TU.build().getDiagnostics(), + UnorderedElementsAre(::testing::AllOf( + Diag(Main.range(), "result of integer division used in a floating " + "point context; possible loss of precision"), + DiagSource(Diag::ClangTidy), DiagName("bugprone-integer-division")))); +} + +TEST(DiagnosticTest, ClangTidyWarningAsError) { + Annotations Main(R"cpp( + int main() { + int i = 3; + double f = [[8]] / i; + } + )cpp"); + TestTU TU = TestTU::withCode(Main.code()); + TU.ClangTidyChecks = "bugprone-integer-division"; + TU.ClangTidyWarningsAsErrors = "bugprone-integer-division"; + EXPECT_THAT( + TU.build().getDiagnostics(), + UnorderedElementsAre(::testing::AllOf( + Diag(Main.range(), "result of integer division used in a floating " + "point context; possible loss of precision"), + DiagSource(Diag::ClangTidy), DiagName("bugprone-integer-division"), + DiagSeverity(DiagnosticsEngine::Error)))); +} + +TEST(DiagnosticTest, ClangTidyWarningAsErrorTrumpsSuppressionComment) { + Annotations Main(R"cpp( + int main() { + int i = 3; + double f = [[8]] / i; // NOLINT + } + )cpp"); + TestTU TU = TestTU::withCode(Main.code()); + TU.ClangTidyChecks = "bugprone-integer-division"; + TU.ClangTidyWarningsAsErrors = "bugprone-integer-division"; + EXPECT_THAT( + TU.build().getDiagnostics(), + UnorderedElementsAre(::testing::AllOf( + Diag(Main.range(), "result of integer division used in a floating " + "point context; possible loss of precision"), + DiagSource(Diag::ClangTidy), DiagName("bugprone-integer-division"), + DiagSeverity(DiagnosticsEngine::Error)))); +} + TEST(DiagnosticsTest, Preprocessor) { // This looks like a preamble, but there's an #else in the middle! // Check that: @@ -764,6 +826,7 @@ TEST(DiagsInHeaders, OnlyErrorOrFatal) { "a type specifier for all declarations"), WithNote(Diag(Header.range(), "error occurred here"))))); } + } // namespace } // namespace clangd diff --git a/clang-tools-extra/clangd/unittests/FileIndexTests.cpp b/clang-tools-extra/clangd/unittests/FileIndexTests.cpp index 4cf589c7d8b42..781f5313fbf59 100644 --- a/clang-tools-extra/clangd/unittests/FileIndexTests.cpp +++ b/clang-tools-extra/clangd/unittests/FileIndexTests.cpp @@ -23,13 +23,13 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; -using testing::AllOf; -using testing::Contains; -using testing::ElementsAre; -using testing::IsEmpty; -using testing::Pair; -using testing::UnorderedElementsAre; +using ::testing::_; +using ::testing::AllOf; +using ::testing::Contains; +using ::testing::ElementsAre; +using ::testing::IsEmpty; +using ::testing::Pair; +using ::testing::UnorderedElementsAre; MATCHER_P(RefRange, Range, "") { return std::make_tuple(arg.Location.Start.line(), arg.Location.Start.column(), @@ -45,13 +45,14 @@ MATCHER_P(DefURI, U, "") { return llvm::StringRef(arg.Definition.FileURI) == U; } MATCHER_P(QName, N, "") { return (arg.Scope + arg.Name).str() == N; } +MATCHER_P(NumReferences, N, "") { return arg.References == N; } namespace clang { namespace clangd { namespace { -testing::Matcher -RefsAre(std::vector> Matchers) { - return ElementsAre(testing::Pair(_, UnorderedElementsAreArray(Matchers))); +::testing::Matcher +RefsAre(std::vector<::testing::Matcher> Matchers) { + return ElementsAre(::testing::Pair(_, UnorderedElementsAreArray(Matchers))); } Symbol symbol(llvm::StringRef ID) { @@ -81,7 +82,7 @@ TEST(FileSymbolsTest, UpdateAndGet) { FileSymbols FS; EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), ""), IsEmpty()); - FS.update("f1", numSlab(1, 3), refSlab(SymbolID("1"), "f1.cc")); + FS.update("f1", numSlab(1, 3), refSlab(SymbolID("1"), "f1.cc"), false); EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), ""), UnorderedElementsAre(QName("1"), QName("2"), QName("3"))); EXPECT_THAT(getRefs(*FS.buildIndex(IndexType::Light), SymbolID("1")), @@ -90,8 +91,8 @@ TEST(FileSymbolsTest, UpdateAndGet) { TEST(FileSymbolsTest, Overlap) { FileSymbols FS; - FS.update("f1", numSlab(1, 3), nullptr); - FS.update("f2", numSlab(3, 5), nullptr); + FS.update("f1", numSlab(1, 3), nullptr, false); + FS.update("f2", numSlab(3, 5), nullptr, false); for (auto Type : {IndexType::Light, IndexType::Heavy}) EXPECT_THAT(runFuzzyFind(*FS.buildIndex(Type), ""), UnorderedElementsAre(QName("1"), QName("2"), QName("3"), @@ -110,8 +111,8 @@ TEST(FileSymbolsTest, MergeOverlap) { auto X2 = symbol("x"); X2.Definition.FileURI = "file:///x2"; - FS.update("f1", OneSymboSlab(X1), nullptr); - FS.update("f2", OneSymboSlab(X2), nullptr); + FS.update("f1", OneSymboSlab(X1), nullptr, false); + FS.update("f2", OneSymboSlab(X2), nullptr, false); for (auto Type : {IndexType::Light, IndexType::Heavy}) EXPECT_THAT( runFuzzyFind(*FS.buildIndex(Type, DuplicateHandling::Merge), "x"), @@ -123,14 +124,14 @@ TEST(FileSymbolsTest, SnapshotAliveAfterRemove) { FileSymbols FS; SymbolID ID("1"); - FS.update("f1", numSlab(1, 3), refSlab(ID, "f1.cc")); + FS.update("f1", numSlab(1, 3), refSlab(ID, "f1.cc"), false); auto Symbols = FS.buildIndex(IndexType::Light); EXPECT_THAT(runFuzzyFind(*Symbols, ""), UnorderedElementsAre(QName("1"), QName("2"), QName("3"))); EXPECT_THAT(getRefs(*Symbols, ID), RefsAre({FileURI("f1.cc")})); - FS.update("f1", nullptr, nullptr); + FS.update("f1", nullptr, nullptr, false); auto Empty = FS.buildIndex(IndexType::Light); EXPECT_THAT(runFuzzyFind(*Empty, ""), IsEmpty()); EXPECT_THAT(getRefs(*Empty, ID), ElementsAre()); @@ -366,6 +367,33 @@ TEST(FileIndexTest, ReferencesInMainFileWithPreamble) { RefsAre({RefRange(Main.range())})); } +TEST(FileSymbolsTest, CountReferencesNoRefSlabs) { + FileSymbols FS; + FS.update("f1", numSlab(1, 3), nullptr, true); + FS.update("f2", numSlab(1, 3), nullptr, false); + EXPECT_THAT( + runFuzzyFind(*FS.buildIndex(IndexType::Light, DuplicateHandling::Merge), + ""), + UnorderedElementsAre(AllOf(QName("1"), NumReferences(0u)), + AllOf(QName("2"), NumReferences(0u)), + AllOf(QName("3"), NumReferences(0u)))); +} + +TEST(FileSymbolsTest, CountReferencesWithRefSlabs) { + FileSymbols FS; + FS.update("f1cpp", numSlab(1, 3), refSlab(SymbolID("1"), "f1.cpp"), true); + FS.update("f1h", numSlab(1, 3), refSlab(SymbolID("1"), "f1.h"), false); + FS.update("f2cpp", numSlab(1, 3), refSlab(SymbolID("2"), "f2.cpp"), true); + FS.update("f2h", numSlab(1, 3), refSlab(SymbolID("2"), "f2.h"), false); + FS.update("f3cpp", numSlab(1, 3), refSlab(SymbolID("3"), "f3.cpp"), true); + FS.update("f3h", numSlab(1, 3), refSlab(SymbolID("3"), "f3.h"), false); + EXPECT_THAT( + runFuzzyFind(*FS.buildIndex(IndexType::Light, DuplicateHandling::Merge), + ""), + UnorderedElementsAre(AllOf(QName("1"), NumReferences(1u)), + AllOf(QName("2"), NumReferences(1u)), + AllOf(QName("3"), NumReferences(1u)))); +} } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp index edb6248979e25..f311237173192 100644 --- a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp @@ -44,7 +44,7 @@ MATCHER_P(SymRange, Range, "") { return arg.location.range == Range; } // GMock helpers for matching DocumentSymbol. MATCHER_P(SymNameRange, Range, "") { return arg.selectionRange == Range; } template -testing::Matcher Children(ChildMatchers... ChildrenM) { +::testing::Matcher Children(ChildMatchers... ChildrenM) { return Field(&DocumentSymbol::children, ElementsAre(ChildrenM...)); } @@ -573,7 +573,7 @@ TEST_F(DocumentSymbolsTest, Namespaces) { )cpp"); EXPECT_THAT( getSymbols(FilePath), - ElementsAreArray>( + ElementsAreArray<::testing::Matcher>( {AllOf(WithName("ans1"), Children(AllOf(WithName("ai1"), Children()), AllOf(WithName("ans2"), Children(WithName("ai2"))))), diff --git a/clang-tools-extra/clangd/unittests/FormattedStringTests.cpp b/clang-tools-extra/clangd/unittests/FormattedStringTests.cpp new file mode 100644 index 0000000000000..2159de9b65aa8 --- /dev/null +++ b/clang-tools-extra/clangd/unittests/FormattedStringTests.cpp @@ -0,0 +1,156 @@ +//===-- FormattedStringTests.cpp ------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "FormattedString.h" +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/StringRef.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +TEST(FormattedString, Basic) { + FormattedString S; + EXPECT_EQ(S.renderAsPlainText(), ""); + EXPECT_EQ(S.renderAsMarkdown(), ""); + + S.appendText("foobar"); + EXPECT_EQ(S.renderAsPlainText(), "foobar"); + EXPECT_EQ(S.renderAsMarkdown(), "foobar"); + + S = FormattedString(); + S.appendInlineCode("foobar"); + EXPECT_EQ(S.renderAsPlainText(), "foobar"); + EXPECT_EQ(S.renderAsMarkdown(), "`foobar`"); + + S = FormattedString(); + S.appendCodeBlock("foobar"); + EXPECT_EQ(S.renderAsPlainText(), "foobar"); + EXPECT_EQ(S.renderAsMarkdown(), "```cpp\n" + "foobar\n" + "```\n"); +} + +TEST(FormattedString, CodeBlocks) { + FormattedString S; + S.appendCodeBlock("foobar"); + S.appendCodeBlock("bazqux", "javascript"); + + EXPECT_EQ(S.renderAsPlainText(), "foobar\n\n\nbazqux"); + std::string ExpectedMarkdown = R"md(```cpp +foobar +``` +```javascript +bazqux +``` +)md"; + EXPECT_EQ(S.renderAsMarkdown(), ExpectedMarkdown); + + S = FormattedString(); + S.appendInlineCode("foobar"); + S.appendInlineCode("bazqux"); + EXPECT_EQ(S.renderAsPlainText(), "foobar bazqux"); + EXPECT_EQ(S.renderAsMarkdown(), "`foobar` `bazqux`"); + + S = FormattedString(); + S.appendText("foo"); + S.appendInlineCode("bar"); + S.appendText("baz"); + + EXPECT_EQ(S.renderAsPlainText(), "foo bar baz"); + EXPECT_EQ(S.renderAsMarkdown(), "foo`bar`baz"); +} + +TEST(FormattedString, Escaping) { + // Check some ASCII punctuation + FormattedString S; + S.appendText("*!`"); + EXPECT_EQ(S.renderAsMarkdown(), "\\*\\!\\`"); + + // Check all ASCII punctuation. + S = FormattedString(); + std::string Punctuation = R"txt(!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)txt"; + // Same text, with each character escaped. + std::string EscapedPunctuation; + EscapedPunctuation.reserve(2 * Punctuation.size()); + for (char C : Punctuation) + EscapedPunctuation += std::string("\\") + C; + S.appendText(Punctuation); + EXPECT_EQ(S.renderAsMarkdown(), EscapedPunctuation); + + // In code blocks we don't need to escape ASCII punctuation. + S = FormattedString(); + S.appendInlineCode("* foo !+ bar * baz"); + EXPECT_EQ(S.renderAsMarkdown(), "`* foo !+ bar * baz`"); + S = FormattedString(); + S.appendCodeBlock("#define FOO\n* foo !+ bar * baz"); + EXPECT_EQ(S.renderAsMarkdown(), "```cpp\n" + "#define FOO\n* foo !+ bar * baz\n" + "```\n"); + + // But we have to escape the backticks. + S = FormattedString(); + S.appendInlineCode("foo`bar`baz"); + EXPECT_EQ(S.renderAsMarkdown(), "`foo``bar``baz`"); + + S = FormattedString(); + S.appendCodeBlock("foo`bar`baz"); + EXPECT_EQ(S.renderAsMarkdown(), "```cpp\n" + "foo`bar`baz\n" + "```\n"); + + // Inline code blocks starting or ending with backticks should add spaces. + S = FormattedString(); + S.appendInlineCode("`foo"); + EXPECT_EQ(S.renderAsMarkdown(), "` ``foo `"); + S = FormattedString(); + S.appendInlineCode("foo`"); + EXPECT_EQ(S.renderAsMarkdown(), "` foo`` `"); + S = FormattedString(); + S.appendInlineCode("`foo`"); + EXPECT_EQ(S.renderAsMarkdown(), "` ``foo`` `"); + + // Should also add extra spaces if the block stars and ends with spaces. + S = FormattedString(); + S.appendInlineCode(" foo "); + EXPECT_EQ(S.renderAsMarkdown(), "` foo `"); + S = FormattedString(); + S.appendInlineCode("foo "); + EXPECT_EQ(S.renderAsMarkdown(), "`foo `"); + S = FormattedString(); + S.appendInlineCode(" foo"); + EXPECT_EQ(S.renderAsMarkdown(), "` foo`"); + + // Code blocks might need more than 3 backticks. + S = FormattedString(); + S.appendCodeBlock("foobarbaz `\nqux"); + EXPECT_EQ(S.renderAsMarkdown(), "```cpp\n" + "foobarbaz `\nqux\n" + "```\n"); + S = FormattedString(); + S.appendCodeBlock("foobarbaz ``\nqux"); + EXPECT_EQ(S.renderAsMarkdown(), "```cpp\n" + "foobarbaz ``\nqux\n" + "```\n"); + S = FormattedString(); + S.appendCodeBlock("foobarbaz ```\nqux"); + EXPECT_EQ(S.renderAsMarkdown(), "````cpp\n" + "foobarbaz ```\nqux\n" + "````\n"); + S = FormattedString(); + S.appendCodeBlock("foobarbaz ` `` ``` ```` `\nqux"); + EXPECT_EQ(S.renderAsMarkdown(), "`````cpp\n" + "foobarbaz ` `` ``` ```` `\nqux\n" + "`````\n"); +} + +} // namespace +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/unittests/FuzzyMatchTests.cpp b/clang-tools-extra/clangd/unittests/FuzzyMatchTests.cpp index 6d5d88c0ed895..33d202b411a12 100644 --- a/clang-tools-extra/clangd/unittests/FuzzyMatchTests.cpp +++ b/clang-tools-extra/clangd/unittests/FuzzyMatchTests.cpp @@ -15,7 +15,7 @@ namespace clang { namespace clangd { namespace { -using testing::Not; +using ::testing::Not; struct ExpectedMatch { // Annotations are optional, and will not be asserted if absent. @@ -43,7 +43,7 @@ struct ExpectedMatch { llvm::Optional Annotated; }; -struct MatchesMatcher : public testing::MatcherInterface { +struct MatchesMatcher : public ::testing::MatcherInterface { ExpectedMatch Candidate; llvm::Optional Score; MatchesMatcher(ExpectedMatch Candidate, llvm::Optional Score) @@ -56,7 +56,7 @@ struct MatchesMatcher : public testing::MatcherInterface { } bool MatchAndExplain(llvm::StringRef Pattern, - testing::MatchResultListener *L) const override { + ::testing::MatchResultListener *L) const override { std::unique_ptr OS( L->stream() ? (llvm::raw_ostream *)(new llvm::raw_os_ostream(*L->stream())) @@ -65,15 +65,15 @@ struct MatchesMatcher : public testing::MatcherInterface { auto Result = Matcher.match(Candidate.Word); auto AnnotatedMatch = Matcher.dumpLast(*OS << "\n"); return Result && Candidate.accepts(AnnotatedMatch) && - (!Score || testing::Value(*Result, testing::FloatEq(*Score))); + (!Score || ::testing::Value(*Result, ::testing::FloatEq(*Score))); } }; // Accepts patterns that match a given word, optionally requiring a score. // Dumps the debug tables on match failure. -testing::Matcher matches(llvm::StringRef M, - llvm::Optional Score = {}) { - return testing::MakeMatcher(new MatchesMatcher(M, Score)); +::testing::Matcher matches(llvm::StringRef M, + llvm::Optional Score = {}) { + return ::testing::MakeMatcher(new MatchesMatcher(M, Score)); } TEST(FuzzyMatch, Matches) { @@ -179,7 +179,7 @@ TEST(FuzzyMatch, Matches) { EXPECT_THAT("std", Not(matches("pthread_condattr_setpshared"))); } -struct RankMatcher : public testing::MatcherInterface { +struct RankMatcher : public ::testing::MatcherInterface { std::vector RankedStrings; RankMatcher(std::initializer_list RankedStrings) : RankedStrings(RankedStrings) {} @@ -193,7 +193,7 @@ struct RankMatcher : public testing::MatcherInterface { } bool MatchAndExplain(llvm::StringRef Pattern, - testing::MatchResultListener *L) const override { + ::testing::MatchResultListener *L) const override { std::unique_ptr OS( L->stream() ? (llvm::raw_ostream *)(new llvm::raw_os_ostream(*L->stream())) @@ -236,8 +236,8 @@ struct RankMatcher : public testing::MatcherInterface { // Accepts patterns that match all the strings and rank them in the given order. // Dumps the debug tables on match failure. template -testing::Matcher ranks(T... RankedStrings) { - return testing::MakeMatcher( +::testing::Matcher ranks(T... RankedStrings) { + return ::testing::MakeMatcher( new RankMatcher{ExpectedMatch(RankedStrings)...}); } diff --git a/clang-tools-extra/clangd/unittests/IndexActionTests.cpp b/clang-tools-extra/clangd/unittests/IndexActionTests.cpp index a7a9a56e879c4..6adc8cc1fe573 100644 --- a/clang-tools-extra/clangd/unittests/IndexActionTests.cpp +++ b/clang-tools-extra/clangd/unittests/IndexActionTests.cpp @@ -32,12 +32,12 @@ MATCHER_P(HasDigest, Digest, "") { return arg.Digest == Digest; } MATCHER_P(HasName, Name, "") { return arg.Name == Name; } MATCHER(HasSameURI, "") { - llvm::StringRef URI = testing::get<0>(arg); - const std::string &Path = testing::get<1>(arg); + llvm::StringRef URI = ::testing::get<0>(arg); + const std::string &Path = ::testing::get<1>(arg); return toUri(Path) == URI; } -testing::Matcher +::testing::Matcher IncludesAre(const std::vector &Includes) { return ::testing::Field(&IncludeGraphNode::DirectIncludes, UnorderedPointwise(HasSameURI(), Includes)); diff --git a/clang-tools-extra/clangd/unittests/IndexTests.cpp b/clang-tools-extra/clangd/unittests/IndexTests.cpp index 2f67654e935a8..8d8c8da771562 100644 --- a/clang-tools-extra/clangd/unittests/IndexTests.cpp +++ b/clang-tools-extra/clangd/unittests/IndexTests.cpp @@ -18,14 +18,14 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; -using testing::AllOf; -using testing::AnyOf; -using testing::ElementsAre; -using testing::IsEmpty; -using testing::Pair; -using testing::Pointee; -using testing::UnorderedElementsAre; +using ::testing::_; +using ::testing::AllOf; +using ::testing::AnyOf; +using ::testing::ElementsAre; +using ::testing::IsEmpty; +using ::testing::Pair; +using ::testing::Pointee; +using ::testing::UnorderedElementsAre; namespace clang { namespace clangd { diff --git a/clang-tools-extra/clangd/unittests/PrintASTTests.cpp b/clang-tools-extra/clangd/unittests/PrintASTTests.cpp index acd77f52f1723..9715a2ad0988a 100644 --- a/clang-tools-extra/clangd/unittests/PrintASTTests.cpp +++ b/clang-tools-extra/clangd/unittests/PrintASTTests.cpp @@ -21,13 +21,13 @@ namespace clang { namespace clangd { namespace { -using testing::ElementsAreArray; +using ::testing::ElementsAreArray; struct Case { const char *AnnotatedCode; std::vector Expected; }; -class ASTUtils : public testing::Test, +class ASTUtils : public ::testing::Test, public ::testing::WithParamInterface {}; TEST_P(ASTUtils, PrintTemplateArgs) { @@ -55,7 +55,7 @@ TEST_P(ASTUtils, PrintTemplateArgs) { } INSTANTIATE_TEST_CASE_P(ASTUtilsTests, ASTUtils, - testing::ValuesIn(std::vector({ + ::testing::ValuesIn(std::vector({ { R"cpp( template class Bar {}; @@ -96,7 +96,7 @@ INSTANTIATE_TEST_CASE_P(ASTUtilsTests, ASTUtils, struct Bar { friend class Foo; }; template <> struct ^Foo {};)cpp", {""}}, - }))); + })),); } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/QualityTests.cpp b/clang-tools-extra/clangd/unittests/QualityTests.cpp index b797a48f7c79e..0c739d7d2477d 100644 --- a/clang-tools-extra/clangd/unittests/QualityTests.cpp +++ b/clang-tools-extra/clangd/unittests/QualityTests.cpp @@ -292,6 +292,16 @@ TEST(QualityTests, SymbolRelevanceSignalsSanity) { SymbolRelevanceSignals InBaseClass; InBaseClass.InBaseClass = true; EXPECT_LT(InBaseClass.evaluate(), Default.evaluate()); + + llvm::StringSet<> Words = {"one", "two", "three"}; + SymbolRelevanceSignals WithoutMatchingWord; + WithoutMatchingWord.ContextWords = &Words; + WithoutMatchingWord.Name = "four"; + EXPECT_EQ(WithoutMatchingWord.evaluate(), Default.evaluate()); + SymbolRelevanceSignals WithMatchingWord; + WithMatchingWord.ContextWords = &Words; + WithMatchingWord.Name = "TheTwoTowers"; + EXPECT_GT(WithMatchingWord.evaluate(), Default.evaluate()); } TEST(QualityTests, ScopeProximity) { diff --git a/clang-tools-extra/clangd/unittests/RenameTests.cpp b/clang-tools-extra/clangd/unittests/RenameTests.cpp new file mode 100644 index 0000000000000..f7148c13f4a6d --- /dev/null +++ b/clang-tools-extra/clangd/unittests/RenameTests.cpp @@ -0,0 +1,92 @@ +//===-- RenameTests.cpp -----------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Annotations.h" +#include "TestFS.h" +#include "TestTU.h" +#include "refactor/Rename.h" +#include "clang/Tooling/Core/Replacement.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +TEST(RenameTest, SingleFile) { + struct Test { + const char* Before; + const char* After; + } Tests[] = { + // Rename function. + { + R"cpp( + void foo() { + fo^o(); + } + )cpp", + R"cpp( + void abcde() { + abcde(); + } + )cpp", + }, + // Rename type. + { + R"cpp( + struct foo{}; + foo test() { + f^oo x; + return x; + } + )cpp", + R"cpp( + struct abcde{}; + abcde test() { + abcde x; + return x; + } + )cpp", + }, + // Rename variable. + { + R"cpp( + void bar() { + if (auto ^foo = 5) { + foo = 3; + } + } + )cpp", + R"cpp( + void bar() { + if (auto abcde = 5) { + abcde = 3; + } + } + )cpp", + }, + }; + for (const Test &T : Tests) { + Annotations Code(T.Before); + auto TU = TestTU::withCode(Code.code()); + TU.HeaderCode = "void foo();"; // outside main file, will not be touched. + auto AST = TU.build(); + auto RenameResult = + renameWithinFile(AST, testPath(TU.Filename), Code.point(), "abcde"); + ASSERT_TRUE(bool(RenameResult)) << RenameResult.takeError(); + auto ApplyResult = + tooling::applyAllReplacements(Code.code(), *RenameResult); + ASSERT_TRUE(bool(ApplyResult)) << ApplyResult.takeError(); + + EXPECT_EQ(T.After, *ApplyResult) << T.Before; + } +} + +} // namespace +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/unittests/SerializationTests.cpp b/clang-tools-extra/clangd/unittests/SerializationTests.cpp index c85920821a8e8..792da77031039 100644 --- a/clang-tools-extra/clangd/unittests/SerializationTests.cpp +++ b/clang-tools-extra/clangd/unittests/SerializationTests.cpp @@ -13,11 +13,11 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; -using testing::AllOf; -using testing::Pair; -using testing::UnorderedElementsAre; -using testing::UnorderedElementsAreArray; +using ::testing::_; +using ::testing::AllOf; +using ::testing::Pair; +using ::testing::UnorderedElementsAre; +using ::testing::UnorderedElementsAreArray; namespace clang { namespace clangd { @@ -135,7 +135,7 @@ TEST(SerializationTest, YAMLConversions) { EXPECT_THAT( *ParsedYAML->Refs, UnorderedElementsAre(Pair(cantFail(SymbolID::fromStr("057557CEBF6E6B2D")), - testing::SizeIs(1)))); + ::testing::SizeIs(1)))); auto Ref1 = ParsedYAML->Refs->begin()->second.front(); EXPECT_EQ(Ref1.Kind, RefKind::Reference); EXPECT_EQ(StringRef(Ref1.Location.FileURI), "file:///path/foo.cc"); diff --git a/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp b/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp index e9f4c00d35a95..9ca6fa1ad952d 100644 --- a/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp +++ b/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp @@ -22,6 +22,7 @@ namespace { using llvm::Failed; using llvm::HasValue; +using ::testing::UnorderedElementsAreArray; MATCHER_P2(Pos, Line, Col, "") { return arg.line == int(Line) && arg.character == int(Col); @@ -322,6 +323,19 @@ TEST(SourceCodeTests, CollectIdentifiers) { EXPECT_EQ(IDs["foo"], 2u); } +TEST(SourceCodeTests, CollectWords) { + auto Words = collectWords(R"cpp( + #define FIZZ_BUZZ + // this is a comment + std::string getSomeText() { return "magic word"; } + )cpp"); + std::set ActualWords(Words.keys().begin(), Words.keys().end()); + std::set ExpectedWords = {"define", "fizz", "buzz", "this", + "comment", "string", "some", "text", + "return", "magic", "word"}; + EXPECT_EQ(ActualWords, ExpectedWords); +} + TEST(SourceCodeTests, VisibleNamespaces) { std::vector>> Cases = { { diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 52de7b0e86e65..d372b1d672280 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -20,6 +20,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/VirtualFileSystem.h" +#include "gmock/gmock-matchers.h" #include "gmock/gmock-more-matchers.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -31,16 +32,16 @@ namespace clang { namespace clangd { namespace { -using testing::_; -using testing::AllOf; -using testing::Contains; -using testing::ElementsAre; -using testing::Field; -using testing::IsEmpty; -using testing::Not; -using testing::Pair; -using testing::UnorderedElementsAre; -using testing::UnorderedElementsAreArray; +using ::testing::_; +using ::testing::AllOf; +using ::testing::Contains; +using ::testing::Each; +using ::testing::ElementsAre; +using ::testing::Field; +using ::testing::Not; +using ::testing::Pair; +using ::testing::UnorderedElementsAre; +using ::testing::UnorderedElementsAreArray; // GMock helpers for matching Symbol. MATCHER_P(Labeled, Label, "") { @@ -95,16 +96,16 @@ MATCHER(VisibleOutsideFile, "") { return static_cast(arg.Flags & Symbol::VisibleOutsideFile); } MATCHER(RefRange, "") { - const Ref &Pos = testing::get<0>(arg); - const Range &Range = testing::get<1>(arg); + const Ref &Pos = ::testing::get<0>(arg); + const Range &Range = ::testing::get<1>(arg); return std::make_tuple(Pos.Location.Start.line(), Pos.Location.Start.column(), Pos.Location.End.line(), Pos.Location.End.column()) == std::make_tuple(Range.start.line, Range.start.character, Range.end.line, Range.end.character); } -testing::Matcher &> +::testing::Matcher &> HaveRanges(const std::vector Ranges) { - return testing::UnorderedPointwise(RefRange(), Ranges); + return ::testing::UnorderedPointwise(RefRange(), Ranges); } class ShouldCollectSymbolTest : public ::testing::Test { @@ -120,8 +121,8 @@ class ShouldCollectSymbolTest : public ::testing::Test { // build() must have been called. bool shouldCollect(llvm::StringRef Name, bool Qualified = true) { assert(AST.hasValue()); - const NamedDecl& ND = Qualified ? findDecl(*AST, Name) - : findUnqualifiedDecl(*AST, Name); + const NamedDecl &ND = + Qualified ? findDecl(*AST, Name) : findUnqualifiedDecl(*AST, Name); ASTContext& Ctx = AST->getASTContext(); const SourceManager& SM = Ctx.getSourceManager(); bool MainFile = SM.isWrittenInMainFile(SM.getExpansionLoc(ND.getBeginLoc())); @@ -654,19 +655,15 @@ TEST_F(SymbolCollectorTest, References) { )"; CollectorOpts.CountReferences = true; runSymbolCollector(Header, Main); - EXPECT_THAT(Symbols, - UnorderedElementsAreArray( - {AllOf(QName("W"), RefCount(1)), - AllOf(QName("X"), RefCount(1)), - AllOf(QName("Y"), RefCount(0)), - AllOf(QName("Z"), RefCount(0)), - AllOf(QName("y"), RefCount(0)), - AllOf(QName("z"), RefCount(0)), - AllOf(QName("x"), RefCount(0)), - AllOf(QName("w"), RefCount(0)), - AllOf(QName("w2"), RefCount(0)), - AllOf(QName("V"), RefCount(1)), - AllOf(QName("v"), RefCount(0))})); + EXPECT_THAT( + Symbols, + UnorderedElementsAreArray( + {AllOf(QName("W"), RefCount(1)), AllOf(QName("X"), RefCount(1)), + AllOf(QName("Y"), RefCount(0)), AllOf(QName("Z"), RefCount(0)), + AllOf(QName("y"), RefCount(0)), AllOf(QName("z"), RefCount(0)), + AllOf(QName("x"), RefCount(0)), AllOf(QName("w"), RefCount(0)), + AllOf(QName("w2"), RefCount(0)), AllOf(QName("V"), RefCount(1)), + AllOf(QName("v"), RefCount(0))})); } TEST_F(SymbolCollectorTest, SymbolRelativeNoFallback) { @@ -1028,6 +1025,26 @@ TEST_F(SymbolCollectorTest, IncFileInNonHeader) { Not(IncludeHeader())))); } +// Features that depend on header-guards are fragile. Header guards are only +// recognized when the file ends, so we have to defer checking for them. +TEST_F(SymbolCollectorTest, HeaderGuardDetected) { + CollectorOpts.CollectIncludePath = true; + CollectorOpts.CollectMacro = true; + runSymbolCollector(R"cpp( + #ifndef HEADER_GUARD_ + #define HEADER_GUARD_ + + // Symbols are seen before the header guard is complete. + #define MACRO + int decl(); + + #endif // Header guard is recognized here. + )cpp", + ""); + EXPECT_THAT(Symbols, Not(Contains(QName("HEADER_GUARD_")))); + EXPECT_THAT(Symbols, Each(IncludeHeader())); +} + TEST_F(SymbolCollectorTest, NonModularHeader) { auto TU = TestTU::withHeaderCode("int x();"); EXPECT_THAT(TU.headerSymbols(), ElementsAre(IncludeHeader())); diff --git a/clang-tools-extra/clangd/unittests/SymbolInfoTests.cpp b/clang-tools-extra/clangd/unittests/SymbolInfoTests.cpp index e0a9ecdc1f8a7..d8d404326ca65 100644 --- a/clang-tools-extra/clangd/unittests/SymbolInfoTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolInfoTests.cpp @@ -24,7 +24,7 @@ namespace clang { namespace clangd { namespace { -using testing::ElementsAreArray; +using ::testing::ElementsAreArray; auto CreateExpectedSymbolDetails = [](const std::string &name, const std::string &container, diff --git a/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp b/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp index a7d032ccc5741..1b51bd78a3fa9 100644 --- a/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp +++ b/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp @@ -673,10 +673,14 @@ TEST_F(TUSchedulerTests, TUStatus) { AllStatus.push_back(Status); } - std::vector AllStatus; + std::vector allStatus() { + std::lock_guard Lock(Mutex); + return AllStatus; + } private: std::mutex Mutex; + std::vector AllStatus; } CaptureTUStatus; MockFSProvider FS; MockCompilationDatabase CDB; @@ -693,7 +697,7 @@ TEST_F(TUSchedulerTests, TUStatus) { ASSERT_TRUE(Server.blockUntilIdleForTest()); - EXPECT_THAT(CaptureTUStatus.AllStatus, + EXPECT_THAT(CaptureTUStatus.allStatus(), ElementsAre( // Statuses of "Update" action. TUState(TUAction::RunningAction, "Update"), diff --git a/clang-tools-extra/clangd/unittests/TestTU.cpp b/clang-tools-extra/clangd/unittests/TestTU.cpp index 8f48eab537e06..cccb71c39a502 100644 --- a/clang-tools-extra/clangd/unittests/TestTU.cpp +++ b/clang-tools-extra/clangd/unittests/TestTU.cpp @@ -48,6 +48,7 @@ ParsedAST TestTU::build() const { Inputs.FS = buildTestFS(Files); Inputs.Opts = ParseOptions(); Inputs.Opts.ClangTidyOpts.Checks = ClangTidyChecks; + Inputs.Opts.ClangTidyOpts.WarningsAsErrors = ClangTidyWarningsAsErrors; Inputs.Index = ExternalIndex; if (Inputs.Index) Inputs.Opts.SuggestMissingIncludes = true; diff --git a/clang-tools-extra/clangd/unittests/TestTU.h b/clang-tools-extra/clangd/unittests/TestTU.h index 6ac4c86a59b42..e1f9bf855bec1 100644 --- a/clang-tools-extra/clangd/unittests/TestTU.h +++ b/clang-tools-extra/clangd/unittests/TestTU.h @@ -57,6 +57,7 @@ struct TestTU { std::vector ExtraArgs; llvm::Optional ClangTidyChecks; + llvm::Optional ClangTidyWarningsAsErrors; // Index to use when building AST. const SymbolIndex *ExternalIndex = nullptr; diff --git a/clang-tools-extra/clangd/unittests/TweakTests.cpp b/clang-tools-extra/clangd/unittests/TweakTests.cpp index baa60292e3dfa..66806b3d52e99 100644 --- a/clang-tools-extra/clangd/unittests/TweakTests.cpp +++ b/clang-tools-extra/clangd/unittests/TweakTests.cpp @@ -105,9 +105,10 @@ llvm::Expected apply(StringRef ID, llvm::StringRef Input) { } void checkTransform(llvm::StringRef ID, llvm::StringRef Input, - llvm::StringRef Output) { - EXPECT_THAT_EXPECTED(apply(ID, Input), HasValue(Output)) - << "action id is" << ID; + std::string Output) { + auto Result = apply(ID, Input); + ASSERT_TRUE(bool(Result)) << llvm::toString(Result.takeError()) << Input; + EXPECT_EQ(Output, std::string(*Result)) << Input; } TEST(TweakTest, SwapIfBranches) { @@ -185,6 +186,37 @@ TEST(TweakTest, SwapIfBranches) { )cpp"); } +TEST(TweakTest, RawStringLiteral) { + llvm::StringLiteral ID = "RawStringLiteral"; + + checkAvailable(ID, R"cpp( + const char *A = ^"^f^o^o^\^n^"; + const char *B = R"(multi )" ^"token " "str\ning"; + )cpp"); + + checkNotAvailable(ID, R"cpp( + const char *A = ^"f^o^o^o^"; // no chars need escaping + const char *B = R"(multi )" ^"token " u8"str\ning"; // not all ascii + const char *C = ^R^"^(^multi )" "token " "str\ning"; // chunk is raw + const char *D = ^"token\n" __FILE__; // chunk is macro expansion + const char *E = ^"a\r\n"; // contains forbidden escape character + )cpp"); + + const char *Input = R"cpp( + const char *X = R"(multi +token)" "\nst^ring\n" "literal"; + } + )cpp"; + const char *Output = R"cpp( + const char *X = R"(multi +token +string +literal)"; + } + )cpp"; + checkTransform(ID, Input, Output); +} + } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp index 79307e28da4ac..9fcb94a958f72 100644 --- a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp +++ b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp @@ -27,21 +27,21 @@ namespace clang { namespace clangd { namespace { -using testing::AllOf; -using testing::ElementsAre; -using testing::Eq; -using testing::Field; -using testing::IsEmpty; -using testing::Matcher; -using testing::Pointee; -using testing::UnorderedElementsAreArray; +using ::testing::AllOf; +using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::Field; +using ::testing::IsEmpty; +using ::testing::Matcher; +using ::testing::Pointee; +using ::testing::UnorderedElementsAreArray; // GMock helpers for matching TypeHierarchyItem. MATCHER_P(WithName, N, "") { return arg.name == N; } MATCHER_P(WithKind, Kind, "") { return arg.kind == Kind; } MATCHER_P(SelectionRangeIs, R, "") { return arg.selectionRange == R; } template -testing::Matcher Parents(ParentMatchers... ParentsM) { +::testing::Matcher Parents(ParentMatchers... ParentsM) { return Field(&TypeHierarchyItem::parents, HasValue(ElementsAre(ParentsM...))); } diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp index a9fb5898435d1..77fa042c5277b 100644 --- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp @@ -25,10 +25,10 @@ namespace clang { namespace clangd { namespace { -using testing::ElementsAre; -using testing::IsEmpty; -using testing::Matcher; -using testing::UnorderedElementsAreArray; +using ::testing::ElementsAre; +using ::testing::IsEmpty; +using ::testing::Matcher; +using ::testing::UnorderedElementsAreArray; class IgnoreDiagnostics : public DiagnosticsConsumer { void onDiagnosticsReady(PathRef File, @@ -120,7 +120,7 @@ MATCHER_P3(Sym, Name, Decl, DefOrNone, "") { } return true; } -testing::Matcher Sym(std::string Name, Range Decl) { +::testing::Matcher Sym(std::string Name, Range Decl) { return Sym(Name, Decl, llvm::None); } MATCHER_P(Sym, Name, "") { return arg.Name == Name; } @@ -186,7 +186,8 @@ TEST(LocateSymbol, WithIndex) { TEST(LocateSymbol, WithIndexPreferredLocation) { Annotations SymbolHeader(R"cpp( - class $[[Proto]] {}; + class $p[[Proto]] {}; + void $f[[func]]() {}; )cpp"); TestTU TU; TU.HeaderCode = SymbolHeader.code(); @@ -195,13 +196,25 @@ TEST(LocateSymbol, WithIndexPreferredLocation) { Annotations Test(R"cpp(// only declaration in AST. // Shift to make range different. - class [[Proto]]; - P^roto* create(); + class Proto; + void func() {} + P$p^roto* create() { + fu$f^nc(); + return nullptr; + } )cpp"); auto AST = TestTU::withCode(Test.code()).build(); - auto Locs = clangd::locateSymbolAt(AST, Test.point(), Index.get()); - EXPECT_THAT(Locs, ElementsAre(Sym("Proto", SymbolHeader.range()))); + { + auto Locs = clangd::locateSymbolAt(AST, Test.point("p"), Index.get()); + auto CodeGenLoc = SymbolHeader.range("p"); + EXPECT_THAT(Locs, ElementsAre(Sym("Proto", CodeGenLoc, CodeGenLoc))); + } + { + auto Locs = clangd::locateSymbolAt(AST, Test.point("f"), Index.get()); + auto CodeGenLoc = SymbolHeader.range("f"); + EXPECT_THAT(Locs, ElementsAre(Sym("func", CodeGenLoc, CodeGenLoc))); + } } TEST(LocateSymbol, All) { diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index bb18c6ed057b8..f1ee2fe44e9aa 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -101,6 +101,20 @@ Improvements to clang-tidy Finds and fixes ``absl::Time`` subtraction expressions to do subtraction in the Time domain instead of the numeric domain. +- New :doc:`bugprone-unhandled-self-assignment + ` check. + + Finds user-defined copy assignment operators which do not protect the code + against self-assignment either by checking self-assignment explicitly or + using the copy-and-swap or the copy-and-move method. + +- New :doc:`bugprone-branch-clone + ` check. + + Checks for repeated branches in ``if/else if/else`` chains, consecutive + repeated branches in ``switch`` statements and indentical true and false + branches in conditional operators. + - New :doc:`google-readability-avoid-underscore-in-googletest-name ` check. @@ -151,6 +165,19 @@ Improvements to clang-tidy finds and replaces cases that match the pattern ``var && isa(var)``, where ``var`` is evaluated twice. +- New :doc:`modernize-use-trailing-return-type + ` check. + + Rewrites function signatures to use a trailing return type. + +- The :doc:`misc-throw-by-value-catch-by-reference + ` now supports + `WarnOnLargeObject` and `MaxSize` options to warn on any large trivial + object caught by value. + +Improvements to include-fixer +----------------------------- + - New :doc:`openmp-exception-escape ` check. diff --git a/clang-tools-extra/docs/clang-tidy/Integrations.rst b/clang-tools-extra/docs/clang-tidy/Integrations.rst index ba08bf7fc5f37..81da199decf20 100644 --- a/clang-tools-extra/docs/clang-tidy/Integrations.rst +++ b/clang-tools-extra/docs/clang-tidy/Integrations.rst @@ -34,7 +34,7 @@ well-known :program:`clang-tidy` integrations in detail. +--------------------------------------+------------------------+---------------------------------+--------------------------+-----------------------------------------+--------------------------+ |Qt Creator IDE | \+\ | \+\ | \-\ | \-\ | \+\ | +--------------------------------------+------------------------+---------------------------------+--------------------------+-----------------------------------------+--------------------------+ -|ReSharper C++ for Visual Studio | \+\ | \+\ | \-\ | \+\ | \-\ | +|ReSharper C++ for Visual Studio | \+\ | \+\ | \-\ | \+\ | \+\ | +--------------------------------------+------------------------+---------------------------------+--------------------------+-----------------------------------------+--------------------------+ |Syntastic for Vim | \+\ | \-\ | \-\ | \-\ | \+\ | +--------------------------------------+------------------------+---------------------------------+--------------------------+-----------------------------------------+--------------------------+ diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone-branch-clone.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone-branch-clone.rst new file mode 100644 index 0000000000000..b4c07b4d1a787 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone-branch-clone.rst @@ -0,0 +1,90 @@ +.. title:: clang-tidy - bugprone-branch-clone + +bugprone-branch-clone +===================== + +Checks for repeated branches in ``if/else if/else`` chains, consecutive +repeated branches in ``switch`` statements and indentical true and false +branches in conditional operators. + +.. code-block:: c++ + + if (test_value(x)) { + y++; + do_something(x, y); + } else { + y++; + do_something(x, y); + } + +In this simple example (which could arise e.g. as a copy-paste error) the +``then`` and ``else`` branches are identical and the code is equivalent the +following shorter and cleaner code: + +.. code-block:: c++ + + test_value(x); // can be omitted unless it has side effects + y++; + do_something(x, y); + + +If this is the inteded behavior, then there is no reason to use a conditional +statement; otherwise the issue can be solved by fixing the branch that is +handled incorrectly. + +The check also detects repeated branches in longer ``if/else if/else`` chains +where it would be even harder to notice the problem. + +In ``switch`` statements the check only reports repeated branches when they are +consecutive, because it is relatively common that the ``case:`` labels have +some natural ordering and rearranging them would decrease the readability of +the code. For example: + +.. code-block:: c++ + + switch (ch) { + case 'a': + return 10; + case 'A': + return 10; + case 'b': + return 11; + case 'B': + return 11; + default: + return 10; + } + +Here the check reports that the ``'a'`` and ``'A'`` branches are identical +(and that the ``'b'`` and ``'B'`` branches are also identical), but does not +report that the ``default:`` branch is also idenical to the first two branches. +If this is indeed the correct behavior, then it could be implemented as: + +.. code-block:: c++ + + switch (ch) { + case 'a': + case 'A': + return 10; + case 'b': + case 'B': + return 11; + default: + return 10; + } + +Here the check does not warn for the repeated ``return 10;``, which is good if +we want to preserve that ``'a'`` is before ``'b'`` and ``default:`` is the last +branch. + +Finally, the check also examines conditional operators and reports code like: + +.. code-block:: c++ + + return test_value(x) ? x : x; + +Unlike if statements, the check does not detect chains of conditional +operators. + +Note: This check also reports situations where branches become identical only +after preprocession. diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone-inaccurate-erase.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone-inaccurate-erase.rst index 7df47b03e63ed..b2dcdd59a600c 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/bugprone-inaccurate-erase.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone-inaccurate-erase.rst @@ -11,3 +11,19 @@ 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. + +For example, the following code erases only one element: + +.. code-block:: c++ + + std::vector xs; + ... + xs.erase(std::remove(xs.begin(), xs.end(), 10)); + +Call the two-argument overload of ``erase()`` to remove the subrange: + +.. code-block:: c++ + + std::vector xs; + ... + xs.erase(std::remove(xs.begin(), xs.end(), 10), xs.end()); diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone-unhandled-self-assignment.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone-unhandled-self-assignment.rst new file mode 100644 index 0000000000000..64412ba049437 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone-unhandled-self-assignment.rst @@ -0,0 +1,116 @@ +.. title:: clang-tidy - bugprone-unhandled-self-assignment + +bugprone-unhandled-self-assignment +================================== + +Finds user-defined copy assignment operators which do not protect the code +against self-assignment either by checking self-assignment explicitly or +using the copy-and-swap or the copy-and-move method. + +This check now searches only those classes which have any pointer or C array field +to avoid false positives. In case of a pointer or a C array, it's likely that self-copy +assignment breaks the object if the copy assignment operator was not written with care. + +See also: +`OOP54-CPP. Gracefully handle self-copy assignment +`_ + +A copy assignment operator must prevent that self-copy assignment ruins the +object state. A typical use case is when the class has a pointer field +and the copy assignment operator first releases the pointed object and +then tries to assign it: + +.. code-block:: c++ + + class T { + int* p; + + public: + T(const T &rhs) : p(rhs.p ? new int(*rhs.p) : nullptr) {} + ~T() { delete p; } + + // ... + + T& operator=(const T &rhs) { + delete p; + p = new int(*rhs.p); + return *this; + } + }; + +There are two common C++ patterns to avoid this problem. The first is +the self-assignment check: + +.. code-block:: c++ + + class T { + int* p; + + public: + T(const T &rhs) : p(rhs.p ? new int(*rhs.p) : nullptr) {} + ~T() { delete p; } + + // ... + + T& operator=(const T &rhs) { + if(this == &rhs) + return *this; + + delete p; + p = new int(*rhs.p); + return *this; + } + }; + +The second one is the copy-and-swap method when we create a temporary copy +(using the copy constructor) and then swap this temporary object with ``this``: + +.. code-block:: c++ + + class T { + int* p; + + public: + T(const T &rhs) : p(rhs.p ? new int(*rhs.p) : nullptr) {} + ~T() { delete p; } + + // ... + + void swap(T &rhs) { + using std::swap; + swap(p, rhs.p); + } + + T& operator=(const T &rhs) { + T(rhs).swap(*this); + return *this; + } + }; + +There is a third pattern which is less common. Let's call it the copy-and-move method +when we create a temporary copy (using the copy constructor) and then move this +temporary object into ``this`` (needs a move assignment operator): + +.. code-block:: c++ + + class T { + int* p; + + public: + T(const T &rhs) : p(rhs.p ? new int(*rhs.p) : nullptr) {} + ~T() { delete p; } + + // ... + + T& operator=(const T &rhs) { + T t = rhs; + *this = std::move(t); + return *this; + } + + T& operator=(T &&rhs) { + p = rhs.p; + rhs.p = nullptr; + return *this; + } + }; diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index fc28cf618356f..74bf2a87e190a 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -38,6 +38,7 @@ Clang-Tidy Checks bugprone-argument-comment bugprone-assert-side-effect bugprone-bool-pointer-implicit-conversion + bugprone-branch-clone bugprone-copy-constructor-init bugprone-dangling-handle bugprone-exception-escape @@ -71,6 +72,7 @@ Clang-Tidy Checks bugprone-too-small-loop-variable bugprone-undefined-memory-manipulation bugprone-undelegated-constructor + bugprone-unhandled-self-assignment bugprone-unused-raii bugprone-unused-return-value bugprone-use-after-move @@ -219,6 +221,7 @@ Clang-Tidy Checks modernize-use-noexcept modernize-use-nullptr modernize-use-override + modernize-use-trailing-return-type modernize-use-transparent-functors modernize-use-uncaught-exceptions modernize-use-using diff --git a/clang-tools-extra/docs/clang-tidy/checks/misc-throw-by-value-catch-by-reference.rst b/clang-tools-extra/docs/clang-tidy/checks/misc-throw-by-value-catch-by-reference.rst index 91287d862ffe1..bbe681a4fbf18 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/misc-throw-by-value-catch-by-reference.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/misc-throw-by-value-catch-by-reference.rst @@ -32,3 +32,18 @@ Options `_. Default is `1`. +.. option:: WarnOnLargeObject + + Also warns for any large, trivial object caught by value. Catching a large + object by value is not dangerous but affects the performance negatively. The + maximum size of an object allowed to be caught without warning can be set + using the `MaxSize` option. + Default is `0`. + +.. option:: MaxSize + + Determines the maximum size of an object allowed to be caught without + warning. Only applicable if `WarnOnLargeObject` is set to `1`. If option is + set by the user to `std::numeric_limits::max()` then it reverts to + the default value. + Default is the size of `size_t`. diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize-loop-convert.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize-loop-convert.rst index bad574f6e90a4..82b27bbe90200 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize-loop-convert.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize-loop-convert.rst @@ -253,3 +253,15 @@ below ends up being performed at the `safe` level. flag = true; } } + +OpenMP +^^^^^^ + +As range-based for loops are only available since OpenMP 5, this check should +not been used on code with a compatibility requirements of OpenMP prior to +version 5. It is **intentional** that this check does not make any attempts to +exclude incorrect diagnostics on OpenMP for loops prior to OpenMP 5. + +To prevent this check to be applied (and to break) OpenMP for loops but still be +applied to non-OpenMP for loops the usage of ``NOLINT`` (see +:ref:`clang-tidy-nolint`) on the specific for loops is recommended. diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize-use-trailing-return-type.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize-use-trailing-return-type.rst new file mode 100644 index 0000000000000..241071c4489d0 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize-use-trailing-return-type.rst @@ -0,0 +1,68 @@ +.. title:: clang-tidy - modernize-use-trailing-return-type + +modernize-use-trailing-return-type +================================== + +Rewrites function signatures to use a trailing return type +(introduced in C++11). This transformation is purely stylistic. +The return type before the function name is replaced by ``auto`` +and inserted after the function parameter list (and qualifiers). + +Example +------- + +.. code-block:: c++ + + int f1(); + inline int f2(int arg) noexcept; + virtual float f3() const && = delete; + +transforms to: + +.. code-block:: c++ + + auto f1() -> int; + inline auto f2(int arg) -> int noexcept; + virtual auto f3() const && -> float = delete; + +Known Limitations +----------------- + +The following categories of return types cannot be rewritten currently: +* function pointers +* member function pointers +* member pointers +* decltype, when it is the top level expression + +Unqualified names in the return type might erroneously refer to different entities after the rewrite. +Preventing such errors requires a full lookup of all unqualified names present in the return type in the scope of the trailing return type location. +This location includes e.g. function parameter names and members of the enclosing class (including all inherited classes). +Such a lookup is currently not implemented. + +Given the following piece of code + +.. code-block:: c++ + + struct Object { long long value; }; + Object f(unsigned Object) { return {Object * 2}; } + class CC { + int Object; + struct Object m(); + }; + Object CC::m() { return {0}; } + +a careless rewrite would produce the following output: + +.. code-block:: c++ + + struct Object { long long value; }; + auto f(unsigned Object) -> Object { return {Object * 2}; } // error + class CC { + int Object; + auto m() -> struct Object; + }; + auto CC::m() -> Object { return {0}; } // error + +This code fails to compile because the Object in the context of f refers to the equally named function parameter. +Similarly, the Object in the context of m refers to the equally named class member. +The check can currently only detect a clash with a function parameter name. diff --git a/clang-tools-extra/docs/clang-tidy/index.rst b/clang-tools-extra/docs/clang-tidy/index.rst index 1b5af60d9675d..dc186b6994d9c 100644 --- a/clang-tools-extra/docs/clang-tidy/index.rst +++ b/clang-tools-extra/docs/clang-tidy/index.rst @@ -258,6 +258,8 @@ An overview of all the command-line options: value: 'some value' ... +.. _clang-tidy-nolint: + Suppressing Undesired Diagnostics ================================= diff --git a/clang-tools-extra/test/clang-tidy/abseil-duration-unnecessary-conversion.cpp b/clang-tools-extra/test/clang-tidy/abseil-duration-unnecessary-conversion.cpp index d32837f80ee80..886722eb3e963 100644 --- a/clang-tools-extra/test/clang-tidy/abseil-duration-unnecessary-conversion.cpp +++ b/clang-tools-extra/test/clang-tidy/abseil-duration-unnecessary-conversion.cpp @@ -1,4 +1,5 @@ -// RUN: %check_clang_tidy %s abseil-duration-unnecessary-conversion %t -- -- -I%S/Inputs +// RUN: %check_clang_tidy -std=c++11,c++14 %s abseil-duration-unnecessary-conversion %t -- -- -I %S/Inputs +// FIXME: Fix the checker to work in C++17 mode. #include "absl/time/time.h" diff --git a/clang-tools-extra/test/clang-tidy/abseil-faster-strsplit-delimiter.cpp b/clang-tools-extra/test/clang-tidy/abseil-faster-strsplit-delimiter.cpp index 5895e45a9c7ea..3e73d3f36cb11 100644 --- a/clang-tools-extra/test/clang-tidy/abseil-faster-strsplit-delimiter.cpp +++ b/clang-tools-extra/test/clang-tidy/abseil-faster-strsplit-delimiter.cpp @@ -1,4 +1,5 @@ -// RUN: %check_clang_tidy %s abseil-faster-strsplit-delimiter %t +// RUN: %check_clang_tidy -std=c++11,c++14 %s abseil-faster-strsplit-delimiter %t +// FIXME: Fix the checker to work in C++17 mode. namespace absl { diff --git a/clang-tools-extra/test/clang-tidy/abseil-str-cat-append.cpp b/clang-tools-extra/test/clang-tidy/abseil-str-cat-append.cpp index 5ecb284df4b33..9a2585da5b277 100644 --- a/clang-tools-extra/test/clang-tidy/abseil-str-cat-append.cpp +++ b/clang-tools-extra/test/clang-tidy/abseil-str-cat-append.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s abseil-str-cat-append %t -- -- -I%S -std=c++11 +// RUN: %check_clang_tidy %s abseil-str-cat-append %t typedef unsigned __INT16_TYPE__ char16; typedef unsigned __INT32_TYPE__ char32; diff --git a/clang-tools-extra/test/clang-tidy/abseil-string-find-startswith.cpp b/clang-tools-extra/test/clang-tidy/abseil-string-find-startswith.cpp index 7d5fd73cb2b4d..89365bfa1f815 100644 --- a/clang-tools-extra/test/clang-tidy/abseil-string-find-startswith.cpp +++ b/clang-tools-extra/test/clang-tidy/abseil-string-find-startswith.cpp @@ -1,6 +1,5 @@ // RUN: %check_clang_tidy %s abseil-string-find-startswith %t -- \ -// RUN: -config="{CheckOptions: [{key: 'abseil-string-find-startswith.StringLikeClasses', value: '::std::basic_string;::basic_string'}]}" \ -// RUN: -- -std=c++11 +// RUN: -config="{CheckOptions: [{key: 'abseil-string-find-startswith.StringLikeClasses', value: '::std::basic_string;::basic_string'}]}" namespace std { template class allocator {}; diff --git a/clang-tools-extra/test/clang-tidy/abseil-time-subtraction.cpp b/clang-tools-extra/test/clang-tidy/abseil-time-subtraction.cpp index 6f5d4b45af976..4cc738c2af2a2 100644 --- a/clang-tools-extra/test/clang-tidy/abseil-time-subtraction.cpp +++ b/clang-tools-extra/test/clang-tidy/abseil-time-subtraction.cpp @@ -1,4 +1,5 @@ -// RUN: %check_clang_tidy %s abseil-time-subtraction %t -- -- -I%S/Inputs +// RUN: %check_clang_tidy -std=c++11,c++14 %s abseil-time-subtraction %t -- -- -I %S/Inputs +// FIXME: Fix the checker to work in C++17 mode. #include "absl/time/time.h" diff --git a/clang-tools-extra/test/clang-tidy/abseil-upgrade-duration-conversions.cpp b/clang-tools-extra/test/clang-tidy/abseil-upgrade-duration-conversions.cpp index fed0f8bd773d1..54d683ae24014 100644 --- a/clang-tools-extra/test/clang-tidy/abseil-upgrade-duration-conversions.cpp +++ b/clang-tools-extra/test/clang-tidy/abseil-upgrade-duration-conversions.cpp @@ -1,4 +1,5 @@ -// RUN: %check_clang_tidy %s abseil-upgrade-duration-conversions %t -- -- -I%S/Inputs +// RUN: %check_clang_tidy -std=c++11,c++14 %s abseil-upgrade-duration-conversions %t -- -- -I%S/Inputs +// FIXME: Fix the checker to work in C++17 mode. using int64_t = long long; diff --git a/clang-tools-extra/test/clang-tidy/bugprone-branch-clone.cpp b/clang-tools-extra/test/clang-tidy/bugprone-branch-clone.cpp new file mode 100644 index 0000000000000..d8b8870b9b4a7 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/bugprone-branch-clone.cpp @@ -0,0 +1,1026 @@ +// RUN: %check_clang_tidy %s bugprone-branch-clone %t + +void test_basic1(int in, int &out) { + if (in > 77) +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here + out++; + + out++; +} + +void test_basic2(int in, int &out) { + if (in > 77) { +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + } + else { +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here + out++; + } + + out++; +} + +void test_basic3(int in, int &out) { + if (in > 77) { +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + } + else +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here + out++; + + out++; +} + +void test_basic4(int in, int &out) { + if (in > 77) { + out--; + } + else { + out++; + } +} + +void test_basic5(int in, int &out) { + if (in > 77) { + out++; + } + else { + out++; + out++; + } +} + +void test_basic6(int in, int &out) { + if (in > 77) { + out++; + } + else { + out++, out++; + } +} + +void test_basic7(int in, int &out) { + if (in > 77) { + out++; + out++; + } + else + out++; + + out++; +} + +void test_basic8(int in, int &out) { + if (in > 77) { +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + out++; + } else { +// CHECK-MESSAGES: :[[@LINE-1]]:5: note: else branch starts here + out++; + out++; + } + + if (in % 2) + out++; +} + +void test_basic9(int in, int &out) { + if (in > 77) { +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + if (in % 2) + out++; + else + out--; + } else { +// CHECK-MESSAGES: :[[@LINE-1]]:5: note: else branch starts here + if (in % 2) + out++; + else + out--; + } +} + +// If we remove the braces from the previous example, the check recognizes it +// as an `else if`. +void test_basic10(int in, int &out) { + if (in > 77) + if (in % 2) + out++; + else + out--; + else + if (in % 2) + out++; + else + out--; + +} + +void test_basic11(int in, int &out) { + if (in > 77) { +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + if (in % 2) + out++; + else + out--; + if (in % 3) + out++; + else + out--; + } else { +// CHECK-MESSAGES: :[[@LINE-1]]:5: note: else branch starts here + if (in % 2) + out++; + else + out--; + if (in % 3) + out++; + else + out--; + } +} + +void test_basic12(int in, int &out) { + if (in > 77) { +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + } else { +// CHECK-MESSAGES: :[[@LINE-1]]:5: note: else branch starts here + } +} + +void test_basic13(int in, int &out) { + if (in > 77) { + // Empty compound statement is not identical to null statement. + } else; +} + +// We use a comparison that ignores redundant parentheses: +void test_basic14(int in, int &out) { + if (in > 77) + out += 2; + else + (out) += (2); +} + +void test_basic15(int in, int &out) { + if (in > 77) + ((out += 2)); + else + out += 2; +} + +// ..but does not apply additional simplifications: +void test_basic16(int in, int &out) { + if (in > 77) + out += 2; + else + out += 1 + 1; +} + +// ..and does not forget important parentheses: +int test_basic17(int a, int b, int c, int mode) { + if (mode>8) + return (a + b) * c; + else + return a + b * c; +} + +//=========--------------------==========// + +#define PASTE_CODE(x) x + +void test_macro1(int in, int &out) { + PASTE_CODE( + if (in > 77) +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:5: note: else branch starts here + out++; + ) + + out--; +} + +void test_macro2(int in, int &out) { + PASTE_CODE( + if (in > 77) +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + ) + else +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here + out++; +} + +void test_macro3(int in, int &out) { + if (in > 77) +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + PASTE_CODE( + else +// CHECK-MESSAGES: :[[@LINE-1]]:5: note: else branch starts here + out++; + ) +} + +void test_macro4(int in, int &out) { + if (in > 77) +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here + PASTE_CODE( + out++; + ) +} + +void test_macro5(int in, int &out) { + PASTE_CODE(if) (in > 77) +// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + PASTE_CODE(else) +// CHECK-MESSAGES: :[[@LINE-1]]:14: note: else branch starts here + out++; +} + +#define OTHERWISE_INCREASE else out++ + +void test_macro6(int in, int &out) { + if (in > 77) + out++; +// CHECK-MESSAGES: :[[@LINE-2]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + OTHERWISE_INCREASE; +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here +// CHECK-MESSAGES: :[[@LINE-8]]:28: note: expanded from macro 'OTHERWISE_INCREASE' +} + +#define COND_INCR(a, b, c) \ + do { \ + if ((a)) \ + (b)++; \ + else \ + (c)++; \ + } while (0) + +void test_macro7(int in, int &out1, int &out2) { + COND_INCR(in, out1, out1); +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE-9]]:5: note: expanded from macro 'COND_INCR' +// CHECK-MESSAGES: :[[@LINE-3]]:3: note: else branch starts here +// CHECK-MESSAGES: :[[@LINE-9]]:5: note: expanded from macro 'COND_INCR' +} + +void test_macro8(int in, int &out1, int &out2) { + COND_INCR(in, out1, out2); +} + +void test_macro9(int in, int &out1, int &out2) { + COND_INCR(in, out2, out2); +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE-21]]:5: note: expanded from macro 'COND_INCR' +// CHECK-MESSAGES: :[[@LINE-3]]:3: note: else branch starts here +// CHECK-MESSAGES: :[[@LINE-21]]:5: note: expanded from macro 'COND_INCR' +} + +#define CONCAT(a, b) a##b + +void test_macro10(int in, int &out) { + CONCAT(i, f) (in > 77) +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + CONCAT(el, se) +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here + out++; +} + +#define PROBLEM (-1) + +int test_macro11(int count) { + if (!count) + return PROBLEM; + else if (count == 13) + return -1; + else + return count * 2; +} + +#define IF if ( +#define THEN ) { +#define ELSE } else { +#define END } + +void test_macro12(int in, int &out) { + IF in > 77 THEN +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE-8]]:12: note: expanded from macro 'IF' + out++; + out++; + ELSE +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here +// CHECK-MESSAGES: :[[@LINE-11]]:16: note: expanded from macro 'ELSE' + out++; + out++; + END +} + +// A hack for implementing a switch with no fallthrough: +#define SWITCH(x) switch (x) { +#define CASE(x) break; case (x): +#define DEFAULT break; default: + +void test_macro13(int in, int &out) { + SWITCH(in) +// CHECK-MESSAGES: :[[@LINE+1]]:5: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + CASE(1) + out++; + out++; + CASE(2) + out++; + out++; + CASE(3) + out++; + out++; +// CHECK-MESSAGES: :[[@LINE-15]]:24: note: expanded from macro 'CASE' +// CHECK-MESSAGES: :[[@LINE+1]]:9: note: last of these clones ends here + CASE(4) + out++; + CASE(5) +// CHECK-MESSAGES: :[[@LINE+1]]:5: warning: switch has 2 consecutive identical branches [bugprone-branch-clone] + CASE(6) + out--; + CASE(7) + out--; +// CHECK-MESSAGES: :[[@LINE-25]]:24: note: expanded from macro 'CASE' +// CHECK-MESSAGES: :[[@LINE+2]]:9: note: last of these clones ends here +// CHECK-MESSAGES: :[[@LINE+1]]:5: warning: switch has 2 consecutive identical branches [bugprone-branch-clone] + CASE(8) + out++; + out++; + CASE(9) + out++; + out++; +// CHECK-MESSAGES: :[[@LINE-34]]:24: note: expanded from macro 'CASE' +// CHECK-MESSAGES: :[[@LINE+2]]:12: note: last of these clones ends here +// CHECK-MESSAGES: :[[@LINE+1]]:5: warning: switch has 2 consecutive identical branches [bugprone-branch-clone] + DEFAULT + out--; + out--; + CASE(10) + out--; + out--; +// CHECK-MESSAGES: :[[@LINE-42]]:24: note: expanded from macro 'DEFAULT' +// CHECK-MESSAGES: :[[@LINE+1]]:9: note: last of these clones ends here + CASE(12) + out++; + CASE(13) + out++; + END +} + +//=========--------------------==========// + +void test_chain1(int in, int &out) { + if (in > 77) +// CHECK-MESSAGES: :[[@LINE+1]]:5: warning: repeated branch in conditional chain [bugprone-branch-clone] + out++; +// CHECK-MESSAGES: :[[@LINE-1]]:10: note: end of the original + else if (in > 55) +// CHECK-MESSAGES: :[[@LINE+1]]:5: note: clone 1 starts here + out++; + + out++; +} + +void test_chain2(int in, int &out) { + if (in > 77) +// CHECK-MESSAGES: :[[@LINE+1]]:5: warning: repeated branch in conditional chain [bugprone-branch-clone] + out++; +// CHECK-MESSAGES: :[[@LINE-1]]:10: note: end of the original + else if (in > 55) +// CHECK-MESSAGES: :[[@LINE+1]]:5: note: clone 1 starts here + out++; + else if (in > 42) + out--; + else if (in > 28) +// CHECK-MESSAGES: :[[@LINE+1]]:5: note: clone 2 starts here + out++; + else if (in > 12) { + out++; + out *= 7; + } else if (in > 7) { +// CHECK-MESSAGES: :[[@LINE-1]]:22: note: clone 3 starts here + out++; + } +} + +void test_chain3(int in, int &out) { + if (in > 77) { +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: repeated branch in conditional chain [bugprone-branch-clone] + out++; + out++; +// CHECK-MESSAGES: :[[@LINE+1]]:4: note: end of the original + } else if (in > 55) { +// CHECK-MESSAGES: :[[@LINE-1]]:23: note: clone 1 starts here + out++; + out++; + } else if (in > 42) + out--; + else if (in > 28) { +// CHECK-MESSAGES: :[[@LINE-1]]:21: note: clone 2 starts here + out++; + out++; + } else if (in > 12) { + out++; + out++; + out++; + out *= 7; + } else if (in > 7) { +// CHECK-MESSAGES: :[[@LINE-1]]:22: note: clone 3 starts here + out++; + out++; + } +} + +// In this chain there are two clone families; notice that the checker +// describes all branches of the first one before mentioning the second one. +void test_chain4(int in, int &out) { + if (in > 77) { +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: repeated branch in conditional chain [bugprone-branch-clone] + out++; + out++; +// CHECK-MESSAGES: :[[@LINE+1]]:4: note: end of the original + } else if (in > 55) { +// CHECK-MESSAGES: :[[@LINE-1]]:23: note: clone 1 starts here +// CHECK-MESSAGES: :[[@LINE+8]]:21: note: clone 2 starts here +// CHECK-MESSAGES: :[[@LINE+15]]:22: note: clone 3 starts here + out++; + out++; + } else if (in > 42) +// CHECK-MESSAGES: :[[@LINE+1]]:5: warning: repeated branch in conditional chain [bugprone-branch-clone] + out--; +// CHECK-MESSAGES: :[[@LINE-1]]:10: note: end of the original + else if (in > 28) { + out++; + out++; + } else if (in > 12) { + out++; + out++; + out++; + out *= 7; + } else if (in > 7) { + out++; + out++; + } else if (in > -3) { +// CHECK-MESSAGES: :[[@LINE-1]]:23: note: clone 1 starts here + out--; + } +} + +void test_chain5(int in, int &out) { + if (in > 77) +// CHECK-MESSAGES: :[[@LINE+1]]:5: warning: repeated branch in conditional chain [bugprone-branch-clone] + out++; +// CHECK-MESSAGES: :[[@LINE-1]]:10: note: end of the original + else if (in > 55) +// CHECK-MESSAGES: :[[@LINE+1]]:5: note: clone 1 starts here + out++; + else if (in > 42) + out--; + else if (in > 28) +// CHECK-MESSAGES: :[[@LINE+1]]:5: note: clone 2 starts here + out++; + else if (in > 12) { + out++; + out *= 7; + } else { +// CHECK-MESSAGES: :[[@LINE-1]]:10: note: clone 3 starts here + out++; + } +} + +void test_chain6(int in, int &out) { + if (in > 77) { +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: repeated branch in conditional chain [bugprone-branch-clone] + out++; + out++; +// CHECK-MESSAGES: :[[@LINE+1]]:4: note: end of the original + } else if (in > 55) { +// CHECK-MESSAGES: :[[@LINE-1]]:23: note: clone 1 starts here + out++; + out++; + } else if (in > 42) + out--; + else if (in > 28) { +// CHECK-MESSAGES: :[[@LINE-1]]:21: note: clone 2 starts here + out++; + out++; + } else if (in > 12) { + out++; + out++; + out++; + out *= 7; + } else { +// CHECK-MESSAGES: :[[@LINE-1]]:10: note: clone 3 starts here + out++; + out++; + } +} + +void test_nested(int a, int b, int c, int &out) { + if (a > 5) { +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE+27]]:5: note: else branch starts here + if (b > 5) { +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: repeated branch in conditional chain [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE+9]]:6: note: end of the original +// CHECK-MESSAGES: :[[@LINE+8]]:24: note: clone 1 starts here +// CHECK-MESSAGES: :[[@LINE+14]]:12: note: clone 2 starts here + if (c > 5) +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:7: note: else branch starts here + out++; + } else if (b > 15) { + if (c > 5) +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:7: note: else branch starts here + out++; + } else { + if (c > 5) +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:7: note: else branch starts here + out++; + } + } else { + if (b > 5) { +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: repeated branch in conditional chain [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE+9]]:6: note: end of the original +// CHECK-MESSAGES: :[[@LINE+8]]:24: note: clone 1 starts here +// CHECK-MESSAGES: :[[@LINE+14]]:12: note: clone 2 starts here + if (c > 5) +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:7: note: else branch starts here + out++; + } else if (b > 15) { + if (c > 5) +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:7: note: else branch starts here + out++; + } else { + if (c > 5) +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:7: note: else branch starts here + out++; + } + } +} + +//=========--------------------==========// + +template +void test_template_not_instantiated(const T &t) { + int a; + if (t) +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + a++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here + a++; +} + +template +void test_template_instantiated(const T &t) { + int a; + if (t) +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + a++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here + a++; +} + +template void test_template_instantiated(const int &t); + +template +void test_template2(T t, int a) { + if (a) { + T b(0); + a += b; + } else { + int b(0); + a += b; + } +} + +template void test_template2(int t, int a); + +template +void test_template3(T t, int a) { + if (a) { + T b(0); + a += b; + } else { + int b(0); + a += b; + } +} + +template void test_template3(short t, int a); + +template +void test_template_two_instances(T t, int &a) { + if (a) { +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + a += int(t); + } else { +// CHECK-MESSAGES: :[[@LINE-1]]:5: note: else branch starts here + a += int(t); + } +} + +template void test_template_two_instances(short t, int &a); +template void test_template_two_instances(long t, int &a); + +class C { + int member; + void inline_method(int arg) { + if (arg) +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: if with identical then and else branches [bugprone-branch-clone] + member = 3; + else +// CHECK-MESSAGES: :[[@LINE-1]]:5: note: else branch starts here + member = 3; + } + int other_method(); +}; + +int C::other_method() { + if (member) { +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + return 8; + } else { +// CHECK-MESSAGES: :[[@LINE-1]]:5: note: else branch starts here + return 8; + } +} + +//=========--------------------==========// + +int simple_switch(char ch) { + switch (ch) { +// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: switch has 2 consecutive identical branches [bugprone-branch-clone] + case 'a': + return 10; + case 'A': + return 10; +// CHECK-MESSAGES: :[[@LINE-1]]:14: note: last of these clones ends here +// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: switch has 2 consecutive identical branches [bugprone-branch-clone] + case 'b': + return 11; + case 'B': + return 11; +// CHECK-MESSAGES: :[[@LINE-1]]:14: note: last of these clones ends here +// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: switch has 2 consecutive identical branches [bugprone-branch-clone] + case 'c': + return 10; + case 'C': + return 10; +// CHECK-MESSAGES: :[[@LINE-1]]:14: note: last of these clones ends here + default: + return 0; + } +} + +int long_sequence_switch(char ch) { + switch (ch) { +// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: switch has 7 consecutive identical branches [bugprone-branch-clone] + case 'a': + return 10; + case 'A': + return 10; + case 'b': + return 10; + case 'B': + return 10; + case 'c': + return 10; + case 'C': + return 10; + default: + return 10; +// CHECK-MESSAGES: :[[@LINE-1]]:14: note: last of these clones ends here + } +} + +int nested_switch(int a, int b, int c) { + switch (a) { +// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE+114]]:6: note: last of these clones ends here + case 1: + switch (b) { +// CHECK-MESSAGES: :[[@LINE+2]]:5: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE+33]]:8: note: last of these clones ends here + case 1: + switch (c) { +// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + case 1: + return 42; + case 2: + return 42; + default: + return 42; +// CHECK-MESSAGES: :[[@LINE-1]]:18: note: last of these clones ends here + } + case 2: + switch (c) { +// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + case 1: + return 42; + case 2: + return 42; + default: + return 42; +// CHECK-MESSAGES: :[[@LINE-1]]:18: note: last of these clones ends here + } + default: + switch (c) { +// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + case 1: + return 42; + case 2: + return 42; + default: + return 42; +// CHECK-MESSAGES: :[[@LINE-1]]:18: note: last of these clones ends here + } + } + case 2: + switch (b) { +// CHECK-MESSAGES: :[[@LINE+2]]:5: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE+33]]:8: note: last of these clones ends here + case 1: + switch (c) { +// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + case 1: + return 42; + case 2: + return 42; + default: + return 42; +// CHECK-MESSAGES: :[[@LINE-1]]:18: note: last of these clones ends here + } + case 2: + switch (c) { +// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + case 1: + return 42; + case 2: + return 42; + default: + return 42; +// CHECK-MESSAGES: :[[@LINE-1]]:18: note: last of these clones ends here + } + default: + switch (c) { +// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + case 1: + return 42; + case 2: + return 42; + default: + return 42; +// CHECK-MESSAGES: :[[@LINE-1]]:18: note: last of these clones ends here + } + } + default: + switch (b) { +// CHECK-MESSAGES: :[[@LINE+2]]:5: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE+33]]:8: note: last of these clones ends here + case 1: + switch (c) { +// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + case 1: + return 42; + case 2: + return 42; + default: + return 42; +// CHECK-MESSAGES: :[[@LINE-1]]:18: note: last of these clones ends here + } + case 2: + switch (c) { +// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + case 1: + return 42; + case 2: + return 42; + default: + return 42; +// CHECK-MESSAGES: :[[@LINE-1]]:18: note: last of these clones ends here + } + default: + switch (c) { +// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + case 1: + return 42; + case 2: + return 42; + default: + return 42; +// CHECK-MESSAGES: :[[@LINE-1]]:18: note: last of these clones ends here + } + } + } +} + +//=========--------------------==========// + +// This should not produce warnings, as in switch statements we only report +// identical branches when they are consecutive. Also note that a branch +// terminated by a break is different from a branch terminated by the end of +// the switch statement. +int interleaved_cases(int a, int b) { + switch (a) { + case 3: + case 4: + b = 2; + break; + case 5: + b = 3; + break; + case 6: + b = 2; + break; + case 7: + if (b % 2) { + b++; + } else { + b++; + break; + } + b = 2; + break; + case 8: + b = 2; + case 9: + b = 3; + break; + default: + b = 3; + } + return b; +} + + +// A case: or default: is only considered to be the start of a branch if it is a direct child of the CompoundStmt forming the body of the switch +int buried_cases(int foo) { + switch (foo) { + { + case 36: + return 8; + default: + return 8; + } + } +} + +// Here the `case 7:` is a child statement of the GotoLabelStmt, so the checker +// thinks that it is part of the `case 9:` branch. While this result is +// counterintuitve, mixing goto labels and switch statements in this fashion is +// pretty rare, so it does not deserve a special case in the checker code. +int decorated_cases(int z) { + if (!(z % 777)) { + goto lucky; + } + switch (z) { +// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: switch has 2 consecutive identical branches [bugprone-branch-clone] + case 1: + case 2: + case 3: + z++; + break; + case 4: + case 5: + z++; + break; +// CHECK-MESSAGES: :[[@LINE-1]]:10: note: last of these clones ends here + case 9: + z++; + break; + lucky: + case 7: + z += 3; + z *= 2; + break; + case 92: + z += 3; + z *= 2; + break; + default: + z++; + } + return z + 92; +} + +// The child of the switch statement is not neccessarily a compound statement, +// do not crash in this unusual case. +char no_real_body(int in, int &out) { + switch (in) + case 42: + return 'A'; + + if (in > 77) +// CHECK-MESSAGES: :[[@LINE+1]]:5: warning: repeated branch in conditional chain [bugprone-branch-clone] + out++; +// CHECK-MESSAGES: :[[@LINE-1]]:10: note: end of the original + else if (in > 55) +// CHECK-MESSAGES: :[[@LINE+1]]:5: note: clone 1 starts here + out++; + else if (in > 34) +// CHECK-MESSAGES: :[[@LINE+1]]:5: note: clone 2 starts here + out++; + + return '|'; +} + +// Duff's device [https://en.wikipedia.org/wiki/Duff's_device] +// The check does not try to distinguish branches in this sort of convoluted +// code, but it should avoid crashing. +void send(short *to, short *from, int count) +{ + int n = (count + 7) / 8; + switch (count % 8) { + case 0: do { *to = *from++; + case 7: *to = *from++; + case 6: *to = *from++; + case 5: *to = *from++; + case 4: *to = *from++; + case 3: *to = *from++; + case 2: *to = *from++; + case 1: *to = *from++; + } while (--n > 0); + } +} + +//=========--------------------==========// + +void ternary1(bool b, int &x) { +// CHECK-MESSAGES: :[[@LINE+1]]:6: warning: conditional operator with identical true and false expressions [bugprone-branch-clone] + (b ? x : x) = 42; +} + +int ternary2(bool b, int x) { +// CHECK-MESSAGES: :[[@LINE+1]]:12: warning: conditional operator with identical true and false expressions [bugprone-branch-clone] + return b ? 42 : 42; +} + +int ternary3(bool b, int x) { + return b ? 42 : 43; +} + +int ternary4(bool b, int x) { + return b ? true ? 45 : 44 : false ? 45 : 44; +} + +// We do not detect chains of conditional operators. +int ternary5(bool b1, bool b2, int x) { + return b1 ? 42 : b2 ? 43 : 42; +} + +#define SWITCH_WITH_LBRACE(b) switch (b) { +#define SEMICOLON_CASE_COLON(b) \ + ; \ + case b: +int d, e; +void dontCrash() { + SWITCH_WITH_LBRACE(d) +// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: switch has 2 consecutive identical branches [bugprone-branch-clone] + SEMICOLON_CASE_COLON(1) + e++; + e++; + SEMICOLON_CASE_COLON(2) + e++; + e++; + // CHECK-MESSAGES: :[[@LINE-11]]:3: note: expanded from macro 'SEMICOLON_CASE_COLON' + // CHECK-MESSAGES: :[[@LINE+1]]:23: note: last of these clones ends here + SEMICOLON_CASE_COLON(3); + } +} diff --git a/clang-tools-extra/test/clang-tidy/bugprone-dangling-handle.cpp b/clang-tools-extra/test/clang-tidy/bugprone-dangling-handle.cpp index d11a2f2d65031..d3caf73b88c14 100644 --- a/clang-tools-extra/test/clang-tidy/bugprone-dangling-handle.cpp +++ b/clang-tools-extra/test/clang-tidy/bugprone-dangling-handle.cpp @@ -1,8 +1,8 @@ -// RUN: %check_clang_tidy %s bugprone-dangling-handle %t -- \ +// RUN: %check_clang_tidy -std=c++11,c++14 %s bugprone-dangling-handle %t -- \ // RUN: -config="{CheckOptions: \ // RUN: [{key: bugprone-dangling-handle.HandleClasses, \ -// RUN: value: 'std::basic_string_view; ::llvm::StringRef;'}]}" \ -// RUN: -- -std=c++11 +// RUN: value: 'std::basic_string_view; ::llvm::StringRef;'}]}" +// FIXME: Fix the checker to work in C++17 mode. namespace std { diff --git a/clang-tools-extra/test/clang-tidy/bugprone-exception-escape.cpp b/clang-tools-extra/test/clang-tidy/bugprone-exception-escape.cpp index eba14bfcf0354..ebb44f84f67cc 100644 --- a/clang-tools-extra/test/clang-tidy/bugprone-exception-escape.cpp +++ b/clang-tools-extra/test/clang-tidy/bugprone-exception-escape.cpp @@ -1,4 +1,9 @@ -// RUN: %check_clang_tidy %s bugprone-exception-escape %t -- -extra-arg=-std=c++11 -extra-arg=-fexceptions -config="{CheckOptions: [{key: bugprone-exception-escape.IgnoredExceptions, value: 'ignored1,ignored2'}, {key: bugprone-exception-escape.FunctionsThatShouldNotThrow, value: 'enabled1,enabled2,enabled3'}]}" -- +// RUN: %check_clang_tidy -std=c++11,c++14 %s bugprone-exception-escape %t -- \ +// RUN: -config="{CheckOptions: [ \ +// RUN: {key: bugprone-exception-escape.IgnoredExceptions, value: 'ignored1,ignored2'}, \ +// RUN: {key: bugprone-exception-escape.FunctionsThatShouldNotThrow, value: 'enabled1,enabled2,enabled3'} \ +// RUN: ]}" \ +// RUN: -- -fexceptions struct throwing_destructor { ~throwing_destructor() { diff --git a/clang-tools-extra/test/clang-tidy/bugprone-forwarding-reference-overload.cpp b/clang-tools-extra/test/clang-tidy/bugprone-forwarding-reference-overload.cpp index cb842e3e8ae13..e5d0ba395d143 100644 --- a/clang-tools-extra/test/clang-tidy/bugprone-forwarding-reference-overload.cpp +++ b/clang-tools-extra/test/clang-tidy/bugprone-forwarding-reference-overload.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s bugprone-forwarding-reference-overload %t -- -- -std=c++14 +// RUN: %check_clang_tidy %s bugprone-forwarding-reference-overload %t namespace std { template struct enable_if { typedef T type; }; diff --git a/clang-tools-extra/test/clang-tidy/bugprone-inaccurate-erase.cpp b/clang-tools-extra/test/clang-tidy/bugprone-inaccurate-erase.cpp index d29fa9cdd4e9b..eff57eaabf7ac 100644 --- a/clang-tools-extra/test/clang-tidy/bugprone-inaccurate-erase.cpp +++ b/clang-tools-extra/test/clang-tidy/bugprone-inaccurate-erase.cpp @@ -1,4 +1,5 @@ -// RUN: %check_clang_tidy %s bugprone-inaccurate-erase %t +// RUN: %check_clang_tidy -std=c++11,c++14 %s bugprone-inaccurate-erase %t +// FIXME: Fix the checker to work in C++17 mode. namespace std { template struct vec_iterator { diff --git a/clang-tools-extra/test/clang-tidy/bugprone-move-forwarding-reference.cpp b/clang-tools-extra/test/clang-tidy/bugprone-move-forwarding-reference.cpp index befca292de812..68eeb126b5dfa 100644 --- a/clang-tools-extra/test/clang-tidy/bugprone-move-forwarding-reference.cpp +++ b/clang-tools-extra/test/clang-tidy/bugprone-move-forwarding-reference.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s bugprone-move-forwarding-reference %t -- -- -std=c++14 -fno-delayed-template-parsing +// RUN: %check_clang_tidy -std=c++14-or-later %s bugprone-move-forwarding-reference %t -- -- -fno-delayed-template-parsing namespace std { template struct remove_reference; diff --git a/clang-tools-extra/test/clang-tidy/bugprone-sizeof-container.cpp b/clang-tools-extra/test/clang-tidy/bugprone-sizeof-container.cpp index 27798d6c39825..b45183de895a2 100644 --- a/clang-tools-extra/test/clang-tidy/bugprone-sizeof-container.cpp +++ b/clang-tools-extra/test/clang-tidy/bugprone-sizeof-container.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s bugprone-sizeof-container %t -- -- -std=c++11 -target x86_64-unknown-unknown +// RUN: %check_clang_tidy %s bugprone-sizeof-container %t -- -- -target x86_64-unknown-unknown namespace std { diff --git a/clang-tools-extra/test/clang-tidy/bugprone-sizeof-expression.cpp b/clang-tools-extra/test/clang-tidy/bugprone-sizeof-expression.cpp index 683ad083007d8..b82beccc794a1 100644 --- a/clang-tools-extra/test/clang-tidy/bugprone-sizeof-expression.cpp +++ b/clang-tools-extra/test/clang-tidy/bugprone-sizeof-expression.cpp @@ -193,11 +193,15 @@ int Test5() { Array10* ptr; }; typedef const MyStruct TMyStruct; + typedef const MyStruct *PMyStruct; + typedef TMyStruct *PMyStruct2; static TMyStruct kGlocalMyStruct = {}; static TMyStruct volatile * kGlocalMyStructPtr = &kGlocalMyStruct; MyStruct S; + PMyStruct PS; + PMyStruct2 PS2; Array10 A10; int sum = 0; @@ -225,12 +229,49 @@ int Test5() { // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: suspicious usage of 'sizeof(A*)'; pointer to aggregate sum += sizeof(&S); // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: suspicious usage of 'sizeof(A*)'; pointer to aggregate + sum += sizeof(MyStruct*); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: suspicious usage of 'sizeof(A*)'; pointer to aggregate + sum += sizeof(PMyStruct); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: suspicious usage of 'sizeof(A*)'; pointer to aggregate + sum += sizeof(PS); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: suspicious usage of 'sizeof(A*)'; pointer to aggregate + sum += sizeof(PS2); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: suspicious usage of 'sizeof(A*)'; pointer to aggregate sum += sizeof(&A10); // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: suspicious usage of 'sizeof(A*)'; pointer to aggregate return sum; } +int Test6() { + int sum = 0; + + struct S A = AsStruct(), B = AsStruct(); + struct S *P = &A, *Q = &B; + sum += sizeof(struct S) == P - Q; + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: suspicious usage of 'sizeof(...)' in pointer arithmetic + sum += 5 * sizeof(S) != P - Q; + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: suspicious usage of 'sizeof(...)' in pointer arithmetic + sum += sizeof(S) < P - Q; + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: suspicious usage of 'sizeof(...)' in pointer arithmetic + sum += 5 * sizeof(S) <= P - Q; + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: suspicious usage of 'sizeof(...)' in pointer arithmetic + sum += 5 * sizeof(*P) >= P - Q; + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: suspicious usage of 'sizeof(...)' in pointer arithmetic + sum += Q - P > 3 * sizeof(*P); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: suspicious usage of 'sizeof(...)' in pointer arithmetic + sum += sizeof(S) + (P - Q); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: suspicious usage of 'sizeof(...)' in pointer arithmetic + sum += 5 * sizeof(S) - (P - Q); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: suspicious usage of 'sizeof(...)' in pointer arithmetic + sum += (P - Q) / sizeof(S); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: suspicious usage of 'sizeof(...)' in pointer arithmetic + sum += (P - Q) / sizeof(*Q); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: suspicious usage of 'sizeof(...)' in pointer arithmetic + + return sum; +} + int ValidExpressions() { int A[] = {1, 2, 3, 4}; static const char str[] = "hello"; diff --git a/clang-tools-extra/test/clang-tidy/bugprone-unhandled-self-assignment.cpp b/clang-tools-extra/test/clang-tidy/bugprone-unhandled-self-assignment.cpp new file mode 100644 index 0000000000000..c2385d039b49b --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/bugprone-unhandled-self-assignment.cpp @@ -0,0 +1,579 @@ +// RUN: %check_clang_tidy %s bugprone-unhandled-self-assignment %t + +namespace std { + +template +void swap(T x, T y) { +} + +template +T &&move(T x) { +} + +template +class unique_ptr { +}; + +template +class shared_ptr { +}; + +template +class weak_ptr { +}; + +template +class auto_ptr { +}; + +} // namespace std + +void assert(int expression){}; + +/////////////////////////////////////////////////////////////////// +/// Test cases correctly caught by the check. + +class PtrField { +public: + PtrField &operator=(const PtrField &object); + +private: + int *p; +}; + +PtrField &PtrField::operator=(const PtrField &object) { + // CHECK-MESSAGES: [[@LINE-1]]:21: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment] + // ... + return *this; +} + +// Class with an inline operator definition. +class InlineDefinition { +public: + InlineDefinition &operator=(const InlineDefinition &object) { + // CHECK-MESSAGES: [[@LINE-1]]:21: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment] + // ... + return *this; + } + +private: + int *p; +}; + +class UniquePtrField { +public: + UniquePtrField &operator=(const UniquePtrField &object) { + // CHECK-MESSAGES: [[@LINE-1]]:19: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment] + // ... + return *this; + } + +private: + std::unique_ptr p; +}; + +class SharedPtrField { +public: + SharedPtrField &operator=(const SharedPtrField &object) { + // CHECK-MESSAGES: [[@LINE-1]]:19: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment] + // ... + return *this; + } + +private: + std::shared_ptr p; +}; + +class WeakPtrField { +public: + WeakPtrField &operator=(const WeakPtrField &object) { + // CHECK-MESSAGES: [[@LINE-1]]:17: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment] + // ... + return *this; + } + +private: + std::weak_ptr p; +}; + +class AutoPtrField { +public: + AutoPtrField &operator=(const AutoPtrField &object) { + // CHECK-MESSAGES: [[@LINE-1]]:17: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment] + // ... + return *this; + } + +private: + std::auto_ptr p; +}; + +// Class with C array field. +class CArrayField { +public: + CArrayField &operator=(const CArrayField &object) { + // CHECK-MESSAGES: [[@LINE-1]]:16: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment] + // ... + return *this; + } + +private: + int array[256]; +}; + +// Make sure to not ignore cases when the operator definition calls +// a copy constructor of another class. +class CopyConstruct { +public: + CopyConstruct &operator=(const CopyConstruct &object) { + // CHECK-MESSAGES: [[@LINE-1]]:18: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment] + WeakPtrField a; + WeakPtrField b(a); + // ... + return *this; + } + +private: + int *p; +}; + +// Make sure to not ignore cases when the operator definition calls +// a copy assignment operator of another class. +class AssignOperator { +public: + AssignOperator &operator=(const AssignOperator &object) { + // CHECK-MESSAGES: [[@LINE-1]]:19: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment] + a.operator=(object.a); + // ... + return *this; + } + +private: + int *p; + WeakPtrField a; +}; + +class NotSelfCheck { +public: + NotSelfCheck &operator=(const NotSelfCheck &object) { + // CHECK-MESSAGES: [[@LINE-1]]:17: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment] + if (&object == this->doSomething()) { + // ... + } + return *this; + } + + void *doSomething() { + return p; + } + +private: + int *p; +}; + +template +class TemplatePtrField { +public: + TemplatePtrField &operator=(const TemplatePtrField &object) { + // CHECK-MESSAGES: [[@LINE-1]]:24: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment] + // ... + return *this; + } + +private: + T *p; +}; + +template +class TemplateCArrayField { +public: + TemplateCArrayField &operator=(const TemplateCArrayField &object) { + // CHECK-MESSAGES: [[@LINE-1]]:27: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment] + // ... + return *this; + } + +private: + T p[256]; +}; + +// Other template class's constructor is called inside a declaration. +template +class WrongTemplateCopyAndMove { +public: + WrongTemplateCopyAndMove &operator=(const WrongTemplateCopyAndMove &object) { + // CHECK-MESSAGES: [[@LINE-1]]:32: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment] + TemplatePtrField temp; + TemplatePtrField temp2(temp); + return *this; + } + +private: + T *p; +}; + +/////////////////////////////////////////////////////////////////// +/// Test cases correctly ignored by the check. + +// Self-assignment is checked using the equality operator. +class SelfCheck1 { +public: + SelfCheck1 &operator=(const SelfCheck1 &object) { + if (this == &object) + return *this; + // ... + return *this; + } + +private: + int *p; +}; + +class SelfCheck2 { +public: + SelfCheck2 &operator=(const SelfCheck2 &object) { + if (&object == this) + return *this; + // ... + return *this; + } + +private: + int *p; +}; + +// Self-assignment is checked using the inequality operator. +class SelfCheck3 { +public: + SelfCheck3 &operator=(const SelfCheck3 &object) { + if (this != &object) { + // ... + } + return *this; + } + +private: + int *p; +}; + +class SelfCheck4 { +public: + SelfCheck4 &operator=(const SelfCheck4 &object) { + if (&object != this) { + // ... + } + return *this; + } + +private: + int *p; +}; + +template +class TemplateSelfCheck { +public: + TemplateSelfCheck &operator=(const TemplateSelfCheck &object) { + if (&object != this) { + // ... + } + return *this; + } + +private: + T *p; +}; + +// There is no warning if the copy assignment operator gets the object by value. +class PassedByValue { +public: + PassedByValue &operator=(PassedByValue object) { + // ... + return *this; + } + +private: + int *p; +}; + +// User-defined swap method calling std::swap inside. +class CopyAndSwap1 { +public: + CopyAndSwap1 &operator=(const CopyAndSwap1 &object) { + CopyAndSwap1 temp(object); + doSwap(temp); + return *this; + } + +private: + int *p; + + void doSwap(CopyAndSwap1 &object) { + using std::swap; + swap(p, object.p); + } +}; + +// User-defined swap method used with passed-by-value parameter. +class CopyAndSwap2 { +public: + CopyAndSwap2 &operator=(CopyAndSwap2 object) { + doSwap(object); + return *this; + } + +private: + int *p; + + void doSwap(CopyAndSwap2 &object) { + using std::swap; + swap(p, object.p); + } +}; + +// Copy-and-swap method is used but without creating a separate method for it. +class CopyAndSwap3 { +public: + CopyAndSwap3 &operator=(const CopyAndSwap3 &object) { + CopyAndSwap3 temp(object); + std::swap(p, temp.p); + return *this; + } + +private: + int *p; +}; + +template +class TemplateCopyAndSwap { +public: + TemplateCopyAndSwap &operator=(const TemplateCopyAndSwap &object) { + TemplateCopyAndSwap temp(object); + std::swap(p, temp.p); + return *this; + } + +private: + T *p; +}; + +// Move semantics is used on a temporary copy of the object. +class CopyAndMove1 { +public: + CopyAndMove1 &operator=(const CopyAndMove1 &object) { + CopyAndMove1 temp(object); + *this = std::move(temp); + return *this; + } + +private: + int *p; +}; + +// There is no local variable for the temporary copy. +class CopyAndMove2 { +public: + CopyAndMove2 &operator=(const CopyAndMove2 &object) { + *this = std::move(CopyAndMove2(object)); + return *this; + } + +private: + int *p; +}; + +template +class TemplateCopyAndMove { +public: + TemplateCopyAndMove &operator=(const TemplateCopyAndMove &object) { + TemplateCopyAndMove temp(object); + *this = std::move(temp); + return *this; + } + +private: + T *p; +}; + +// There is no local variable for the temporary copy. +template +class TemplateCopyAndMove2 { +public: + TemplateCopyAndMove2 &operator=(const TemplateCopyAndMove2 &object) { + *this = std::move(TemplateCopyAndMove2(object)); + return *this; + } + +private: + T *p; +}; + +// We should not catch move assignment operators. +class MoveAssignOperator { +public: + MoveAssignOperator &operator=(MoveAssignOperator &&object) { + // ... + return *this; + } + +private: + int *p; +}; + +// We ignore copy assignment operators without user-defined implementation. +class DefaultOperator { +public: + DefaultOperator &operator=(const DefaultOperator &object) = default; + +private: + int *p; +}; + +class DeletedOperator { +public: + DeletedOperator &operator=(const DefaultOperator &object) = delete; + +private: + int *p; +}; + +class ImplicitOperator { +private: + int *p; +}; + +// Check ignores those classes which has no any pointer or array field. +class TrivialFields { +public: + TrivialFields &operator=(const TrivialFields &object) { + // ... + return *this; + } + +private: + int m; + float f; + double d; + bool b; +}; + +// There is no warning when the class calls another assignment operator on 'this' +// inside the copy assignment operator's definition. +class AssignIsForwarded { +public: + AssignIsForwarded &operator=(const AssignIsForwarded &object) { + operator=(object.p); + return *this; + } + + AssignIsForwarded &operator=(int *pp) { + if (p != pp) { + delete p; + p = new int(*pp); + } + return *this; + } + +private: + int *p; +}; + +// Assertion is a valid way to say that self-assignment is not expected to happen. +class AssertGuard { +public: + AssertGuard &operator=(const AssertGuard &object) { + assert(this != &object); + // ... + return *this; + } + +private: + int *p; +}; + +// Make sure we don't catch this operator=() as a copy assignment operator. +// Note that RHS has swapped template arguments. +template +class NotACopyAssignmentOperator { + Ty *Ptr1; + Uy *Ptr2; + +public: + NotACopyAssignmentOperator& operator=(const NotACopyAssignmentOperator &RHS) { + Ptr1 = RHS.getUy(); + Ptr2 = RHS.getTy(); + return *this; + } + + Ty *getTy() const { return Ptr1; } + Uy *getUy() const { return Ptr2; } +}; + +/////////////////////////////////////////////////////////////////// +/// Test cases which should be caught by the check. + +// TODO: handle custom pointers. +template +class custom_ptr { +}; + +class CustomPtrField { +public: + CustomPtrField &operator=(const CustomPtrField &object) { + // ... + return *this; + } + +private: + custom_ptr p; +}; + +///////////////////////////////////////////////////////////////////////////////////////////////////// +/// False positives: These are self-assignment safe, but they don't use any of the three patterns. + +class ArrayCopy { +public: + ArrayCopy &operator=(const ArrayCopy &object) { + // CHECK-MESSAGES: [[@LINE-1]]:14: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment] + for (int i = 0; i < 256; i++) + array[i] = object.array[i]; + return *this; + } + +private: + int array[256]; +}; + +class GetterSetter { +public: + GetterSetter &operator=(const GetterSetter &object) { + // CHECK-MESSAGES: [[@LINE-1]]:17: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment] + setValue(object.getValue()); + return *this; + } + + int *getValue() const { return value; } + + void setValue(int *newPtr) { + int *pTmp(newPtr ? new int(*newPtr) : nullptr); + std::swap(value, pTmp); + delete pTmp; + } + +private: + int *value; +}; + +class CustomSelfCheck { +public: + CustomSelfCheck &operator=(const CustomSelfCheck &object) { + // CHECK-MESSAGES: [[@LINE-1]]:20: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment] + if (index != object.index) { + // ... + } + return *this; + } + +private: + int *value; + int index; +}; diff --git a/clang-tools-extra/test/clang-tidy/cert-err34-c.cpp b/clang-tools-extra/test/clang-tidy/cert-err34-c.cpp index dde7dc1cab210..1c358bc3fb54c 100644 --- a/clang-tools-extra/test/clang-tidy/cert-err34-c.cpp +++ b/clang-tools-extra/test/clang-tidy/cert-err34-c.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s cert-err34-c %t -- -- -std=c++11 +// RUN: %check_clang_tidy %s cert-err34-c %t typedef void * FILE; diff --git a/clang-tools-extra/test/clang-tidy/cert-msc51-cpp.cpp b/clang-tools-extra/test/clang-tidy/cert-msc51-cpp.cpp index 8a8d778e2eae6..4aec74b1ed740 100644 --- a/clang-tools-extra/test/clang-tidy/cert-msc51-cpp.cpp +++ b/clang-tools-extra/test/clang-tidy/cert-msc51-cpp.cpp @@ -1,4 +1,5 @@ -// RUN: %check_clang_tidy %s cert-msc51-cpp %t -- -config="{CheckOptions: [{key: cert-msc51-cpp.DisallowedSeedTypes, value: 'some_type,time_t'}]}" -- -std=c++11 +// RUN: %check_clang_tidy %s cert-msc51-cpp %t -- \ +// RUN: -config="{CheckOptions: [{key: cert-msc51-cpp.DisallowedSeedTypes, value: 'some_type,time_t'}]}" namespace std { diff --git a/clang-tools-extra/test/clang-tidy/cert-oop11-cpp.cpp b/clang-tools-extra/test/clang-tidy/cert-oop11-cpp.cpp index 650d6ec311de5..f7f3a64867598 100644 --- a/clang-tools-extra/test/clang-tidy/cert-oop11-cpp.cpp +++ b/clang-tools-extra/test/clang-tidy/cert-oop11-cpp.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s cert-oop11-cpp %t -- -- -std=c++11 +// RUN: %check_clang_tidy %s cert-oop11-cpp %t struct B { B(B&&) noexcept = default; diff --git a/clang-tools-extra/test/clang-tidy/cert-setlongjmp.cpp b/clang-tools-extra/test/clang-tidy/cert-setlongjmp.cpp index 1bf5444ffcc15..88d1db35f6b6a 100644 --- a/clang-tools-extra/test/clang-tidy/cert-setlongjmp.cpp +++ b/clang-tools-extra/test/clang-tidy/cert-setlongjmp.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s cert-err52-cpp %t -- -- -std=c++11 +// RUN: %check_clang_tidy %s cert-err52-cpp %t typedef void *jmp_buf; extern int __setjmpimpl(jmp_buf); diff --git a/clang-tools-extra/test/clang-tidy/cert-throw-exception-type.cpp b/clang-tools-extra/test/clang-tidy/cert-throw-exception-type.cpp index 2ff9be5df5d93..bc0ecc808b295 100644 --- a/clang-tools-extra/test/clang-tidy/cert-throw-exception-type.cpp +++ b/clang-tools-extra/test/clang-tidy/cert-throw-exception-type.cpp @@ -1,4 +1,6 @@ -// RUN: %check_clang_tidy %s cert-err60-cpp %t -- -- -std=c++11 -fcxx-exceptions +// RUN: %check_clang_tidy -std=c++11,c++14 %s cert-err60-cpp %t -- -- -fcxx-exceptions +// FIXME: Split off parts of this test that rely on dynamic exeption +// specifications, and run this test in all language modes. struct S {}; struct T : S {}; diff --git a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py index 5d808f409c279..aa6304baef813 100755 --- a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py +++ b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py @@ -38,29 +38,15 @@ def write_file(file_name, text): f.write(text) f.truncate() -def csv(string): - return string.split(',') - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('-expect-clang-tidy-error', action='store_true') - parser.add_argument('-resource-dir') - parser.add_argument('-assume-filename') - parser.add_argument('input_file_name') - parser.add_argument('check_name') - parser.add_argument('temp_file_name') - parser.add_argument('-check-suffix', '-check-suffixes', - default=[''], type=csv, - help="comma-separated list of FileCheck suffixes") - - args, extra_args = parser.parse_known_args() +def run_test_once(args, extra_args): resource_dir = args.resource_dir assume_file_name = args.assume_filename input_file_name = args.input_file_name check_name = args.check_name temp_file_name = args.temp_file_name expect_clang_tidy_error = args.expect_clang_tidy_error + std = args.std file_name_with_extension = assume_file_name or input_file_name _, extension = os.path.splitext(file_name_with_extension) @@ -69,20 +55,31 @@ def main(): temp_file_name = temp_file_name + extension clang_tidy_extra_args = extra_args - if len(clang_tidy_extra_args) == 0: - clang_tidy_extra_args = ['--'] - if extension in ['.cpp', '.hpp', '.mm']: - clang_tidy_extra_args.append('--std=c++11') - if extension in ['.m', '.mm']: - clang_tidy_extra_args.extend( - ['-fobjc-abi-version=2', '-fobjc-arc']) + clang_extra_args = [] + if '--' in extra_args: + i = clang_tidy_extra_args.index('--') + clang_extra_args = clang_tidy_extra_args[i + 1:] + clang_tidy_extra_args = clang_tidy_extra_args[:i] + + # If the test does not specify a formatting style, force "none"; otherwise + # autodetection logic can discover a ".clang-tidy" file that is not related to + # the test. + if not any( + [arg.startswith('-format-style=') for arg in clang_tidy_extra_args]): + clang_tidy_extra_args.append('-format-style=none') + + if extension in ['.m', '.mm']: + clang_extra_args = ['-fobjc-abi-version=2', '-fobjc-arc'] + clang_extra_args + + if extension in ['.cpp', '.hpp', '.mm']: + clang_extra_args.append('-std=' + std) # Tests should not rely on STL being available, and instead provide mock # implementations of relevant APIs. - clang_tidy_extra_args.append('-nostdinc++') + clang_extra_args.append('-nostdinc++') if resource_dir is not None: - clang_tidy_extra_args.append('-resource-dir=%s' % resource_dir) + clang_extra_args.append('-resource-dir=%s' % resource_dir) with open(input_file_name, 'r') as input_file: input_text = input_file.read() @@ -138,7 +135,7 @@ def main(): write_file(original_file_name, cleaned_test) args = ['clang-tidy', temp_file_name, '-fix', '--checks=-*,' + check_name] + \ - clang_tidy_extra_args + clang_tidy_extra_args + ['--'] + clang_extra_args if expect_clang_tidy_error: args.insert(0, 'not') print('Running ' + repr(args) + '...') @@ -203,5 +200,49 @@ def main(): print('FileCheck failed:\n' + e.output.decode()) raise + +def expand_std(std): + if std == 'c++98-or-later': + return ['c++98', 'c++11', 'c++14', 'c++17', 'c++2a'] + if std == 'c++11-or-later': + return ['c++11', 'c++14', 'c++17', 'c++2a'] + if std == 'c++14-or-later': + return ['c++14', 'c++17', 'c++2a'] + if std == 'c++17-or-later': + return ['c++17', 'c++2a'] + if std == 'c++2a-or-later': + return ['c++2a'] + return [std] + + +def csv(string): + return string.split(',') + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('-expect-clang-tidy-error', action='store_true') + parser.add_argument('-resource-dir') + parser.add_argument('-assume-filename') + parser.add_argument('input_file_name') + parser.add_argument('check_name') + parser.add_argument('temp_file_name') + parser.add_argument( + '-check-suffix', + '-check-suffixes', + default=[''], + type=csv, + help='comma-separated list of FileCheck suffixes') + parser.add_argument('-std', type=csv, default=['c++11-or-later']) + + args, extra_args = parser.parse_known_args() + + abbreviated_stds = args.std + for abbreviated_std in abbreviated_stds: + for std in expand_std(abbreviated_std): + args.std = std + run_test_once(args, extra_args) + + if __name__ == '__main__': main() diff --git a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-owning-memory-legacy-functions.cpp b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-owning-memory-legacy-functions.cpp index 5785e9f7d0cda..d5bfcbc31c907 100644 --- a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-owning-memory-legacy-functions.cpp +++ b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-owning-memory-legacy-functions.cpp @@ -2,7 +2,7 @@ // RUN: -config='{CheckOptions: \ // RUN: [{key: cppcoreguidelines-owning-memory.LegacyResourceProducers, value: "::malloc;::aligned_alloc;::realloc;::calloc;::fopen;::freopen;::tmpfile"}, \ // RUN: {key: cppcoreguidelines-owning-memory.LegacyResourceConsumers, value: "::free;::realloc;::freopen;::fclose"}]}' \ -// RUN: -- -std=c++11 -nostdlib -nostdinc++ +// RUN: -- -nostdlib -nostdinc++ namespace gsl { template diff --git a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-bounds-constant-array-index-c++03.cpp b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-bounds-constant-array-index-c++03.cpp index ad9fcd97daa4f..ad1cc5ab897d9 100644 --- a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-bounds-constant-array-index-c++03.cpp +++ b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-bounds-constant-array-index-c++03.cpp @@ -1,4 +1,4 @@ -// RUN: clang-tidy %s -checks=-*,cppcoreguidelines-pro-bounds-constant-array-index -- -std=c++03 | count 0 +// RUN: %check_clang_tidy -std=c++98-or-later %s cppcoreguidelines-pro-bounds-constant-array-index %t // Note: this test expects no diagnostics, but FileCheck cannot handle that, // hence the use of | count 0. @@ -6,6 +6,7 @@ template struct B { int get() { // The next line used to crash the check (in C++03 mode only). return x[index]; + // CHECK-FIXES: return x[index]; } int x[3]; }; diff --git a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-bounds-constant-array-index-gslheader.cpp b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-bounds-constant-array-index-gslheader.cpp index a88a2d9e9ce82..71b957d84740b 100644 --- a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-bounds-constant-array-index-gslheader.cpp +++ b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-bounds-constant-array-index-gslheader.cpp @@ -1,4 +1,5 @@ -// RUN: %check_clang_tidy %s cppcoreguidelines-pro-bounds-constant-array-index %t -- -config='{CheckOptions: [{key: cppcoreguidelines-pro-bounds-constant-array-index.GslHeader, value: "dir1/gslheader.h"}]}' -- -std=c++11 +// RUN: %check_clang_tidy %s cppcoreguidelines-pro-bounds-constant-array-index %t -- \ +// RUN: -config='{CheckOptions: [{key: cppcoreguidelines-pro-bounds-constant-array-index.GslHeader, value: "dir1/gslheader.h"}]}' // CHECK-FIXES: #include "dir1/gslheader.h" typedef __SIZE_TYPE__ size_t; diff --git a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-bounds-pointer-arithmetic-pr36489.cpp b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-bounds-pointer-arithmetic-pr36489.cpp index 47109522667be..faab2a1ccf0e1 100644 --- a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-bounds-pointer-arithmetic-pr36489.cpp +++ b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-bounds-pointer-arithmetic-pr36489.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s cppcoreguidelines-pro-bounds-pointer-arithmetic %t -- -- -std=c++14 +// RUN: %check_clang_tidy -std=c++14-or-later %s cppcoreguidelines-pro-bounds-pointer-arithmetic %t // Fix PR36489 and detect auto-deduced value correctly. char *getPtr(); diff --git a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-type-member-init-cxx2a.cpp b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-type-member-init-cxx2a.cpp index d3e3ade3861a3..3b384c24b848a 100644 --- a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-type-member-init-cxx2a.cpp +++ b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-type-member-init-cxx2a.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s cppcoreguidelines-pro-type-member-init %t -- -- -std=c++2a -fno-delayed-template-parsing +// RUN: %check_clang_tidy -std=c++2a %s cppcoreguidelines-pro-type-member-init %t -- -- -fno-delayed-template-parsing struct PositiveBitfieldMember { PositiveBitfieldMember() {} diff --git a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-type-member-init-cxx98.cpp b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-type-member-init-cxx98.cpp index db97bb456864c..6e47123568f45 100644 --- a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-type-member-init-cxx98.cpp +++ b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-type-member-init-cxx98.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s cppcoreguidelines-pro-type-member-init %t -- -- -std=c++98 -fno-delayed-template-parsing +// RUN: %check_clang_tidy -std=c++98 %s cppcoreguidelines-pro-type-member-init %t -- -- -fno-delayed-template-parsing struct PositiveFieldBeforeConstructor { int F; diff --git a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-type-member-init.cpp b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-type-member-init.cpp index 86f094de8dec1..7daf9dfeaeb44 100644 --- a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-type-member-init.cpp +++ b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-pro-type-member-init.cpp @@ -1,4 +1,5 @@ -// RUN: %check_clang_tidy %s cppcoreguidelines-pro-type-member-init %t -- -- -std=c++11 -fno-delayed-template-parsing +// RUN: %check_clang_tidy -std=c++11,c++14,c++17 %s cppcoreguidelines-pro-type-member-init %t -- -- -fno-delayed-template-parsing +// FIXME: Fix the checker to work in C++2a mode. struct PositiveFieldBeforeConstructor { int F; diff --git a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-special-member-functions-cxx-03.cpp b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-special-member-functions-cxx-03.cpp index 8fe5e429dbfa2..10f3955846d71 100644 --- a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-special-member-functions-cxx-03.cpp +++ b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-special-member-functions-cxx-03.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s cppcoreguidelines-special-member-functions %t -- -- -std=c++03 +// RUN: %check_clang_tidy -std=c++98 %s cppcoreguidelines-special-member-functions %t class DefinesDestructor { ~DefinesDestructor(); diff --git a/clang-tools-extra/test/clang-tidy/expand-modular-headers-ppcallbacks.cpp b/clang-tools-extra/test/clang-tidy/expand-modular-headers-ppcallbacks.cpp index d191922de27eb..c9b05517d210c 100644 --- a/clang-tools-extra/test/clang-tidy/expand-modular-headers-ppcallbacks.cpp +++ b/clang-tools-extra/test/clang-tidy/expand-modular-headers-ppcallbacks.cpp @@ -2,25 +2,47 @@ // RUN: rm -rf %t // RUN: mkdir %t // RUN: cp %S/Inputs/expand-modular-headers-ppcallbacks/* %t/ -// RUN: %check_clang_tidy %s readability-identifier-naming %t/without-modules -- \ +// RUN: %check_clang_tidy -std=c++11 %s readability-identifier-naming %t/without-modules -- \ // RUN: -config="CheckOptions: [{ \ // RUN: key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE }]" \ // RUN: -header-filter=.* \ -// RUN: -- -x c++ -std=c++11 -I%t/ +// RUN: -- -I %t/ +// +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: cp %S/Inputs/expand-modular-headers-ppcallbacks/* %t/ +// RUN: %check_clang_tidy -std=c++17 %s readability-identifier-naming %t/without-modules -- \ +// RUN: -config="CheckOptions: [{ \ +// RUN: key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE }]" \ +// RUN: -header-filter=.* \ +// RUN: -- -I %t/ // // Run clang-tidy on a file with modular includes: // // RUN: rm -rf %t // RUN: mkdir %t // RUN: cp %S/Inputs/expand-modular-headers-ppcallbacks/* %t/ -// RUN: %check_clang_tidy %s readability-identifier-naming %t/with-modules -- \ +// RUN: %check_clang_tidy -std=c++11 %s readability-identifier-naming %t/with-modules -- \ +// RUN: -config="CheckOptions: [{ \ +// RUN: key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE }]" \ +// RUN: -header-filter=.* \ +// RUN: -- -I %t/ \ +// RUN: -fmodules -fimplicit-modules -fno-implicit-module-maps \ +// RUN: -fmodule-map-file=%t/module.modulemap \ +// RUN: -fmodules-cache-path=%t/module-cache/ +// +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: cp %S/Inputs/expand-modular-headers-ppcallbacks/* %t/ +// RUN: %check_clang_tidy -std=c++17 %s readability-identifier-naming %t/with-modules -- \ // RUN: -config="CheckOptions: [{ \ // RUN: key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE }]" \ // RUN: -header-filter=.* \ -// RUN: -- -x c++ -std=c++11 -I%t/ \ +// RUN: -- -I %t/ \ // RUN: -fmodules -fimplicit-modules -fno-implicit-module-maps \ // RUN: -fmodule-map-file=%t/module.modulemap \ // RUN: -fmodules-cache-path=%t/module-cache/ +// FIXME: Make the test work in all language modes. #include "c.h" // CHECK-MESSAGES: a.h:1:9: warning: invalid case style for macro definition 'a' [readability-identifier-naming] diff --git a/clang-tools-extra/test/clang-tidy/fuchsia-restrict-system-includes-all.cpp b/clang-tools-extra/test/clang-tidy/fuchsia-restrict-system-includes-all.cpp index 21f4b68b9c8d7..d521c1358dba6 100644 --- a/clang-tools-extra/test/clang-tidy/fuchsia-restrict-system-includes-all.cpp +++ b/clang-tools-extra/test/clang-tidy/fuchsia-restrict-system-includes-all.cpp @@ -1,6 +1,6 @@ // RUN: %check_clang_tidy %s fuchsia-restrict-system-includes %t \ -// RUN: -- -config="{CheckOptions: [{key: fuchsia-restrict-system-includes.Includes, value: ''}]}" \ -// RUN: -- -std=c++11 -I %S/Inputs/fuchsia-restrict-system-includes -isystem %S/Inputs/fuchsia-restrict-system-includes/system +// RUN: -- -config="{CheckOptions: [{key: fuchsia-restrict-system-includes.Includes, value: ''}]}" \ +// RUN: -- -I %S/Inputs/fuchsia-restrict-system-includes -isystem %S/Inputs/fuchsia-restrict-system-includes/system #include // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: system include cstdlib.h not allowed diff --git a/clang-tools-extra/test/clang-tidy/fuchsia-restrict-system-includes-glob.cpp b/clang-tools-extra/test/clang-tidy/fuchsia-restrict-system-includes-glob.cpp index 334990dd84dd5..104f3689246c0 100644 --- a/clang-tools-extra/test/clang-tidy/fuchsia-restrict-system-includes-glob.cpp +++ b/clang-tools-extra/test/clang-tidy/fuchsia-restrict-system-includes-glob.cpp @@ -1,6 +1,6 @@ // RUN: %check_clang_tidy %s fuchsia-restrict-system-includes %t \ -// RUN: -- -config="{CheckOptions: [{key: fuchsia-restrict-system-includes.Includes, value: 'cstd*'}]}" \ -// RUN: -- -std=c++11 -I %S/Inputs/fuchsia-restrict-system-includes -isystem %S/Inputs/fuchsia-restrict-system-includes/system +// RUN: -- -config="{CheckOptions: [{key: fuchsia-restrict-system-includes.Includes, value: 'cstd*'}]}" \ +// RUN: -- -I %S/Inputs/fuchsia-restrict-system-includes -isystem %S/Inputs/fuchsia-restrict-system-includes/system #include #include diff --git a/clang-tools-extra/test/clang-tidy/fuchsia-restrict-system-includes-headers.cpp b/clang-tools-extra/test/clang-tidy/fuchsia-restrict-system-includes-headers.cpp index f392eacc9a976..b9c00e2a105af 100644 --- a/clang-tools-extra/test/clang-tidy/fuchsia-restrict-system-includes-headers.cpp +++ b/clang-tools-extra/test/clang-tidy/fuchsia-restrict-system-includes-headers.cpp @@ -1,12 +1,13 @@ // RUN: rm -rf %T/Headers // RUN: mkdir %T/Headers // RUN: cp -r %S/Inputs/fuchsia-restrict-system-includes %T/Headers/fuchsia-restrict-system-includes -// RUN: %check_clang_tidy %s fuchsia-restrict-system-includes %t \ -// RUN: -- -config="{CheckOptions: [{key: fuchsia-restrict-system-includes.Includes, value: 'transitive.h,s.h'}]}" \ +// RUN: %check_clang_tidy -std=c++11 %s fuchsia-restrict-system-includes %t \ +// RUN: -- -config="{CheckOptions: [{key: fuchsia-restrict-system-includes.Includes, value: 'transitive.h,s.h'}]}" \ // RUN: -system-headers -header-filter=.* \ -// RUN: -- -std=c++11 -I %T/Headers/fuchsia-restrict-system-includes -isystem %T/Headers/fuchsia-restrict-system-includes/system +// RUN: -- -I %T/Headers/fuchsia-restrict-system-includes -isystem %T/Headers/fuchsia-restrict-system-includes/system // RUN: FileCheck -input-file=%T/Headers/fuchsia-restrict-system-includes/transitive2.h %s -check-prefix=CHECK-FIXES // RUN: rm -rf %T/Headers +// FIXME: Make the test work in all language modes. // transitive.h includes and #include diff --git a/clang-tools-extra/test/clang-tidy/fuchsia-restrict-system-includes.cpp b/clang-tools-extra/test/clang-tidy/fuchsia-restrict-system-includes.cpp index e2ba71060c38e..c36640822b444 100644 --- a/clang-tools-extra/test/clang-tidy/fuchsia-restrict-system-includes.cpp +++ b/clang-tools-extra/test/clang-tidy/fuchsia-restrict-system-includes.cpp @@ -1,6 +1,6 @@ // RUN: %check_clang_tidy %s fuchsia-restrict-system-includes %t \ -// RUN: -- -config="{CheckOptions: [{key: fuchsia-restrict-system-includes.Includes, value: 's.h'}]}" \ -// RUN: -- -std=c++11 -I %S/Inputs/fuchsia-restrict-system-includes -isystem %S/Inputs/fuchsia-restrict-system-includes/system +// RUN: -- -config="{CheckOptions: [{key: fuchsia-restrict-system-includes.Includes, value: 's.h'}]}" \ +// RUN: -- -I %S/Inputs/fuchsia-restrict-system-includes -isystem %S/Inputs/fuchsia-restrict-system-includes/system #include "a.h" diff --git a/clang-tools-extra/test/clang-tidy/google-readability-casting.cpp b/clang-tools-extra/test/clang-tidy/google-readability-casting.cpp index d36fef6fc94ec..ffec0f89dabcb 100644 --- a/clang-tools-extra/test/clang-tidy/google-readability-casting.cpp +++ b/clang-tools-extra/test/clang-tidy/google-readability-casting.cpp @@ -1,4 +1,5 @@ -// RUN: %check_clang_tidy %s google-readability-casting %t +// RUN: %check_clang_tidy -std=c++11,c++14 %s google-readability-casting %t +// FIXME: Fix the checker to work in C++17 mode. bool g() { return false; } diff --git a/clang-tools-extra/test/clang-tidy/google-runtime-int-std.cpp b/clang-tools-extra/test/clang-tidy/google-runtime-int-std.cpp index c5d3112075cf0..e5cdb3ec048e2 100644 --- a/clang-tools-extra/test/clang-tidy/google-runtime-int-std.cpp +++ b/clang-tools-extra/test/clang-tidy/google-runtime-int-std.cpp @@ -3,7 +3,7 @@ // RUN: {key: google-runtime-int.UnsignedTypePrefix, value: "std::uint"}, \ // RUN: {key: google-runtime-int.SignedTypePrefix, value: "std::int"}, \ // RUN: {key: google-runtime-int.TypeSuffix, value: "_t"}, \ -// RUN: ]}' -- -std=c++11 +// RUN: ]}' long a(); // CHECK-MESSAGES: [[@LINE-1]]:1: warning: consider replacing 'long' with 'std::int{{..}}_t' diff --git a/clang-tools-extra/test/clang-tidy/google-runtime-references.cpp b/clang-tools-extra/test/clang-tidy/google-runtime-references.cpp index b9f84b6295586..2abd5afc75eaa 100644 --- a/clang-tools-extra/test/clang-tidy/google-runtime-references.cpp +++ b/clang-tools-extra/test/clang-tidy/google-runtime-references.cpp @@ -1,8 +1,7 @@ // RUN: %check_clang_tidy %s google-runtime-references %t -- \ -// RUN: -extra-arg="-std=c++11" \ // RUN: -config="{CheckOptions: \ // RUN: [{key: google-runtime-references.WhiteListTypes, \ -// RUN: value: 'whitelist::A; whitelist::B'}]}" -- +// RUN: value: 'whitelist::A; whitelist::B'}]}" int a; int &b = a; diff --git a/clang-tools-extra/test/clang-tidy/hicpp-signed-bitwise-standard-types.cpp b/clang-tools-extra/test/clang-tidy/hicpp-signed-bitwise-standard-types.cpp index c85fe20ca77f1..b88b266ff4bbe 100644 --- a/clang-tools-extra/test/clang-tidy/hicpp-signed-bitwise-standard-types.cpp +++ b/clang-tools-extra/test/clang-tidy/hicpp-signed-bitwise-standard-types.cpp @@ -1,4 +1,5 @@ // RUN: clang-tidy %s -checks='-*,hicpp-signed-bitwise' -- -std=c++11 +// FIXME: Make the test work in all language modes. #include "hicpp-signed-bitwise-standard-types.h" diff --git a/clang-tools-extra/test/clang-tidy/hicpp-signed-bitwise.cpp b/clang-tools-extra/test/clang-tidy/hicpp-signed-bitwise.cpp index 97a24b6bca935..58cc911d108ea 100644 --- a/clang-tools-extra/test/clang-tidy/hicpp-signed-bitwise.cpp +++ b/clang-tools-extra/test/clang-tidy/hicpp-signed-bitwise.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s hicpp-signed-bitwise %t -- -- -std=c++11 --target=x86_64-linux +// RUN: %check_clang_tidy %s hicpp-signed-bitwise %t -- -- --target=x86_64-linux // These could cause false positives and should not be considered. struct StreamClass { diff --git a/clang-tools-extra/test/clang-tidy/misc-new-delete-overloads-sized-dealloc.cpp b/clang-tools-extra/test/clang-tidy/misc-new-delete-overloads-sized-dealloc.cpp index 2ba60e46fa9d0..69867882797cd 100644 --- a/clang-tools-extra/test/clang-tidy/misc-new-delete-overloads-sized-dealloc.cpp +++ b/clang-tools-extra/test/clang-tidy/misc-new-delete-overloads-sized-dealloc.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s misc-new-delete-overloads %t -- -- -std=c++14 -fsized-deallocation +// RUN: %check_clang_tidy %s misc-new-delete-overloads %t -- -- -fsized-deallocation typedef decltype(sizeof(int)) size_t; diff --git a/clang-tools-extra/test/clang-tidy/misc-new-delete-overloads.cpp b/clang-tools-extra/test/clang-tidy/misc-new-delete-overloads.cpp index 3e60537ef65d8..78f021144b2e1 100644 --- a/clang-tools-extra/test/clang-tidy/misc-new-delete-overloads.cpp +++ b/clang-tools-extra/test/clang-tidy/misc-new-delete-overloads.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s misc-new-delete-overloads %t -- -- -std=c++14 +// RUN: %check_clang_tidy %s misc-new-delete-overloads %t typedef decltype(sizeof(int)) size_t; diff --git a/clang-tools-extra/test/clang-tidy/misc-redundant-expression.cpp b/clang-tools-extra/test/clang-tidy/misc-redundant-expression.cpp index 3454318148d2e..becec875c0940 100644 --- a/clang-tools-extra/test/clang-tidy/misc-redundant-expression.cpp +++ b/clang-tools-extra/test/clang-tidy/misc-redundant-expression.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s misc-redundant-expression %t -- -- -std=c++11 +// RUN: %check_clang_tidy %s misc-redundant-expression %t typedef __INT64_TYPE__ I64; diff --git a/clang-tools-extra/test/clang-tidy/misc-throw-by-value-catch-by-reference.cpp b/clang-tools-extra/test/clang-tidy/misc-throw-by-value-catch-by-reference.cpp index 120b07eecc71e..269ef90f38fb8 100644 --- a/clang-tools-extra/test/clang-tidy/misc-throw-by-value-catch-by-reference.cpp +++ b/clang-tools-extra/test/clang-tidy/misc-throw-by-value-catch-by-reference.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s misc-throw-by-value-catch-by-reference %t -- -- -std=c++11 -fcxx-exceptions +// RUN: %check_clang_tidy %s misc-throw-by-value-catch-by-reference %t -- -- -fcxx-exceptions class logic_error { diff --git a/clang-tools-extra/test/clang-tidy/misc-unconventional-assign-operator-cxx17.cpp b/clang-tools-extra/test/clang-tidy/misc-unconventional-assign-operator-cxx17.cpp index ba1a685842ef7..d6e9ebe49a977 100644 --- a/clang-tools-extra/test/clang-tidy/misc-unconventional-assign-operator-cxx17.cpp +++ b/clang-tools-extra/test/clang-tidy/misc-unconventional-assign-operator-cxx17.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s misc-unconventional-assign-operator %t -- -- -std=c++17 -fno-delayed-template-parsing +// RUN: %check_clang_tidy -std=c++14-or-later %s misc-unconventional-assign-operator %t -- -- -fno-delayed-template-parsing struct BadModifier { BadModifier& operator=(const BadModifier&) const; diff --git a/clang-tools-extra/test/clang-tidy/misc-unconventional-assign-operator.cpp b/clang-tools-extra/test/clang-tidy/misc-unconventional-assign-operator.cpp index 4bd9cb2f2d6bb..a0a37c0ff1c7a 100644 --- a/clang-tools-extra/test/clang-tidy/misc-unconventional-assign-operator.cpp +++ b/clang-tools-extra/test/clang-tidy/misc-unconventional-assign-operator.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s misc-unconventional-assign-operator %t -- -- -std=c++11 -isystem %S/Inputs/Headers -fno-delayed-template-parsing +// RUN: %check_clang_tidy %s misc-unconventional-assign-operator %t -- -- -isystem %S/Inputs/Headers -fno-delayed-template-parsing namespace std { template diff --git a/clang-tools-extra/test/clang-tidy/misc-unused-parameters.cpp b/clang-tools-extra/test/clang-tidy/misc-unused-parameters.cpp index ec1ee2d01194b..119eff67318ea 100644 --- a/clang-tools-extra/test/clang-tidy/misc-unused-parameters.cpp +++ b/clang-tools-extra/test/clang-tidy/misc-unused-parameters.cpp @@ -1,7 +1,8 @@ // RUN: echo "static void staticFunctionHeader(int i) {;}" > %T/header.h // RUN: echo "static void staticFunctionHeader(int /*i*/) {;}" > %T/header-fixed.h -// RUN: %check_clang_tidy %s misc-unused-parameters %t -- -header-filter='.*' -- -std=c++11 -fno-delayed-template-parsing +// RUN: %check_clang_tidy -std=c++11 %s misc-unused-parameters %t -- -header-filter='.*' -- -fno-delayed-template-parsing // RUN: diff %T/header.h %T/header-fixed.h +// FIXME: Make the test work in all language modes. #include "header.h" // CHECK-MESSAGES: header.h:1:38: warning diff --git a/clang-tools-extra/test/clang-tidy/modernize-avoid-bind.cpp b/clang-tools-extra/test/clang-tidy/modernize-avoid-bind.cpp index 721801bef11d0..fa60cdc2c9d08 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-avoid-bind.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-avoid-bind.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-avoid-bind %t -- -- -std=c++14 +// RUN: %check_clang_tidy -std=c++14-or-later %s modernize-avoid-bind %t namespace std { inline namespace impl { diff --git a/clang-tools-extra/test/clang-tidy/modernize-concat-nested-namespaces.cpp b/clang-tools-extra/test/clang-tidy/modernize-concat-nested-namespaces.cpp index 22a92f6016dea..dcde32d0f0690 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-concat-nested-namespaces.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-concat-nested-namespaces.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-concat-nested-namespaces %t -- -- -std=c++17 +// RUN: %check_clang_tidy -std=c++17-or-later %s modernize-concat-nested-namespaces %t namespace n1 {} diff --git a/clang-tools-extra/test/clang-tidy/modernize-deprecated-headers-cxx03.cpp b/clang-tools-extra/test/clang-tidy/modernize-deprecated-headers-cxx03.cpp index c604ba4fcc289..4511f46d57b33 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-deprecated-headers-cxx03.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-deprecated-headers-cxx03.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-deprecated-headers %t -- -extra-arg-before=-isystem%S/Inputs/modernize-deprecated-headers -- -std=c++03 -v +// RUN: %check_clang_tidy -std=c++98 %s modernize-deprecated-headers %t -- -extra-arg-before=-isystem%S/Inputs/modernize-deprecated-headers #include // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: inclusion of deprecated C++ header 'assert.h'; consider using 'cassert' instead [modernize-deprecated-headers] diff --git a/clang-tools-extra/test/clang-tidy/modernize-deprecated-headers-cxx11.cpp b/clang-tools-extra/test/clang-tidy/modernize-deprecated-headers-cxx11.cpp index 366f04538dea6..c3374a9123bd1 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-deprecated-headers-cxx11.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-deprecated-headers-cxx11.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-deprecated-headers %t -- -extra-arg-before=-isystem%S/Inputs/modernize-deprecated-headers -- -std=c++11 -v +// RUN: %check_clang_tidy -std=c++11-or-later %s modernize-deprecated-headers %t -- -extra-arg-before=-isystem%S/Inputs/modernize-deprecated-headers #include // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: inclusion of deprecated C++ header 'assert.h'; consider using 'cassert' instead [modernize-deprecated-headers] diff --git a/clang-tools-extra/test/clang-tidy/modernize-loop-convert-basic.cpp b/clang-tools-extra/test/clang-tidy/modernize-loop-convert-basic.cpp index def7c4bb2d207..06b1b739632f0 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-loop-convert-basic.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-loop-convert-basic.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-loop-convert %t -- -- -std=c++11 -I %S/Inputs/modernize-loop-convert +// RUN: %check_clang_tidy %s modernize-loop-convert %t -- -- -I %S/Inputs/modernize-loop-convert #include "structures.h" @@ -369,7 +369,7 @@ void f() { // CHECK-FIXES: for (auto Val_int_ptr : Val_int_ptrs) } - // This container uses an iterator where the derefence type is a typedef of + // This container uses an iterator where the dereference type is a typedef of // a reference type. Make sure non-const auto & is still used. A failure here // means canonical types aren't being tested. { @@ -431,19 +431,22 @@ void different_type() { // CHECK-FIXES: for (auto P : *Ps) // CHECK-FIXES-NEXT: printf("s has value %d\n", P.X); - // V.begin() returns a user-defined type 'iterator' which, since it's - // different from const_iterator, disqualifies these loops from - // transformation. dependent V; for (dependent::const_iterator It = V.begin(), E = V.end(); It != E; ++It) { printf("Fibonacci number is %d\n", *It); } + // CHECK-MESSAGES: :[[@LINE-4]]:3: warning: use range-based for loop instead + // CHECK-FIXES: for (int It : V) + // CHECK-FIXES-NEXT: printf("Fibonacci number is %d\n", It); for (dependent::const_iterator It(V.begin()), E = V.end(); It != E; ++It) { printf("Fibonacci number is %d\n", *It); } + // CHECK-MESSAGES: :[[@LINE-4]]:3: warning: use range-based for loop instead + // CHECK-FIXES: for (int It : V) + // CHECK-FIXES-NEXT: printf("Fibonacci number is %d\n", It); } // Tests to ensure that an implicit 'this' is picked up as the container. diff --git a/clang-tools-extra/test/clang-tidy/modernize-loop-convert-camelback.cpp b/clang-tools-extra/test/clang-tidy/modernize-loop-convert-camelback.cpp index 50044de666500..42c492f58b5a5 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-loop-convert-camelback.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-loop-convert-camelback.cpp @@ -1,6 +1,6 @@ // RUN: %check_clang_tidy %s modernize-loop-convert %t -- \ // RUN: -config="{CheckOptions: [{key: modernize-loop-convert.NamingStyle, value: 'camelBack'}]}" \ -// RUN: -- -std=c++11 -I %S/Inputs/modernize-loop-convert +// RUN: -- -I %S/Inputs/modernize-loop-convert #include "structures.h" diff --git a/clang-tools-extra/test/clang-tidy/modernize-loop-convert-const.cpp b/clang-tools-extra/test/clang-tidy/modernize-loop-convert-const.cpp index 806b6c11f9c22..fd3bddedd5675 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-loop-convert-const.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-loop-convert-const.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-loop-convert %t -- -- -std=c++11 +// RUN: %check_clang_tidy %s modernize-loop-convert %t struct Str { Str() = default; diff --git a/clang-tools-extra/test/clang-tidy/modernize-loop-convert-extra.cpp b/clang-tools-extra/test/clang-tidy/modernize-loop-convert-extra.cpp index b46ff25c2a3eb..d57c335d922bc 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-loop-convert-extra.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-loop-convert-extra.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-loop-convert %t -- -- -std=c++11 -I %S/Inputs/modernize-loop-convert +// RUN: %check_clang_tidy %s modernize-loop-convert %t -- -- -I %S/Inputs/modernize-loop-convert #include "structures.h" @@ -776,17 +776,20 @@ void different_type() { // CHECK-FIXES: for (auto P : *Ps) // CHECK-FIXES-NEXT: printf("s has value %d\n", P.X); - // V.begin() returns a user-defined type 'iterator' which, since it's - // different from const_iterator, disqualifies these loops from - // transformation. dependent V; for (dependent::const_iterator It = V.begin(); It != V.end(); ++It) { printf("Fibonacci number is %d\n", *It); } + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: use range-based for loop instead + // CHECK-FIXES: for (int It : V) + // CHECK-FIXES-NEXT: printf("Fibonacci number is %d\n", It); for (dependent::const_iterator It(V.begin()); It != V.end(); ++It) { printf("Fibonacci number is %d\n", *It); } + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: use range-based for loop instead + // CHECK-FIXES: for (int It : V) + // CHECK-FIXES-NEXT: printf("Fibonacci number is %d\n", It); } } // namespace SingleIterator @@ -991,18 +994,26 @@ void iterators() { // CHECK-FIXES: for (int & I : Dep) // CHECK-FIXES-NEXT: auto H2 = [&]() { int R = I + 2; }; - // FIXME: It doesn't work with const iterators. for (dependent::const_iterator I = Dep.begin(), E = Dep.end(); I != E; ++I) auto H3 = [I]() { int R = *I; }; + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: use range-based for loop instead + // CHECK-FIXES: for (int I : Dep) + // CHECK-FIXES-NEXT: auto H3 = [&I]() { int R = I; }; for (dependent::const_iterator I = Dep.begin(), E = Dep.end(); I != E; ++I) auto H4 = [&]() { int R = *I + 1; }; + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: use range-based for loop instead + // CHECK-FIXES: for (int I : Dep) + // CHECK-FIXES-NEXT: auto H4 = [&]() { int R = I + 1; }; for (dependent::const_iterator I = Dep.begin(), E = Dep.end(); I != E; ++I) auto H5 = [=]() { int R = *I; }; + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: use range-based for loop instead + // CHECK-FIXES: for (int R : Dep) + // CHECK-FIXES-NEXT: auto H5 = [=]() { }; } void captureByValue() { diff --git a/clang-tools-extra/test/clang-tidy/modernize-loop-convert-lowercase.cpp b/clang-tools-extra/test/clang-tidy/modernize-loop-convert-lowercase.cpp index c7bb22132ef01..1389376b2e4d7 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-loop-convert-lowercase.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-loop-convert-lowercase.cpp @@ -1,6 +1,6 @@ // RUN: %check_clang_tidy %s modernize-loop-convert %t -- \ // RUN: -config="{CheckOptions: [{key: modernize-loop-convert.NamingStyle, value: 'lower_case'}]}" \ -// RUN: -- -std=c++11 -I %S/Inputs/modernize-loop-convert +// RUN: -- -I %S/Inputs/modernize-loop-convert #include "structures.h" diff --git a/clang-tools-extra/test/clang-tidy/modernize-loop-convert-negative.cpp b/clang-tools-extra/test/clang-tidy/modernize-loop-convert-negative.cpp index c038437298ec3..607baaed5dd38 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-loop-convert-negative.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-loop-convert-negative.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-loop-convert %t -- -- -std=c++11 -I %S/Inputs/modernize-loop-convert +// RUN: %check_clang_tidy %s modernize-loop-convert %t -- -- -I %S/Inputs/modernize-loop-convert #include "structures.h" diff --git a/clang-tools-extra/test/clang-tidy/modernize-loop-convert-uppercase.cpp b/clang-tools-extra/test/clang-tidy/modernize-loop-convert-uppercase.cpp index 4d1d5c2fd590e..09e5f45ed0402 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-loop-convert-uppercase.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-loop-convert-uppercase.cpp @@ -1,6 +1,6 @@ // RUN: %check_clang_tidy %s modernize-loop-convert %t -- \ // RUN: -config="{CheckOptions: [{key: modernize-loop-convert.NamingStyle, value: 'UPPER_CASE'}]}" \ -// RUN: -- -std=c++11 -I %S/Inputs/modernize-loop-convert +// RUN: -- -I %S/Inputs/modernize-loop-convert #include "structures.h" diff --git a/clang-tools-extra/test/clang-tidy/modernize-make-shared-header.cpp b/clang-tools-extra/test/clang-tidy/modernize-make-shared-header.cpp index 21b07ee449647..8f293312f0d6b 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-make-shared-header.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-make-shared-header.cpp @@ -5,7 +5,7 @@ // RUN: {key: modernize-make-shared.MakeSmartPtrFunctionHeader, \ // RUN: value: 'make_shared_util.h'} \ // RUN: ]}" \ -// RUN: -- -std=c++11 -I%S/Inputs/modernize-smart-ptr +// RUN: -- -I %S/Inputs/modernize-smart-ptr #include "shared_ptr.h" // CHECK-FIXES: #include "make_shared_util.h" diff --git a/clang-tools-extra/test/clang-tidy/modernize-make-shared.cpp b/clang-tools-extra/test/clang-tidy/modernize-make-shared.cpp index 49012dc1858ec..e046801e24212 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-make-shared.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-make-shared.cpp @@ -1,5 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-make-shared %t -- -- -std=c++11 \ -// RUN: -I%S/Inputs/modernize-smart-ptr +// RUN: %check_clang_tidy %s modernize-make-shared %t -- -- -I %S/Inputs/modernize-smart-ptr #include "shared_ptr.h" // CHECK-FIXES: #include diff --git a/clang-tools-extra/test/clang-tidy/modernize-make-unique-cxx11.cpp b/clang-tools-extra/test/clang-tidy/modernize-make-unique-cxx11.cpp index 89beb0813cb42..9cd58b4198fc7 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-make-unique-cxx11.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-make-unique-cxx11.cpp @@ -1,5 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-make-unique %t -- -- -std=c++11 \ -// RUN: -I%S/Inputs/modernize-smart-ptr +// RUN: %check_clang_tidy -std=c++11 %s modernize-make-unique %t -- -- -I %S/Inputs/modernize-smart-ptr #include "unique_ptr.h" // CHECK-FIXES: #include "unique_ptr.h" diff --git a/clang-tools-extra/test/clang-tidy/modernize-make-unique-cxx14.cpp b/clang-tools-extra/test/clang-tidy/modernize-make-unique-cxx14.cpp index cd35f751bb439..96173710d7fd3 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-make-unique-cxx14.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-make-unique-cxx14.cpp @@ -1,5 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-make-unique %t -- -- -std=c++14 \ -// RUN: -I%S/Inputs/modernize-smart-ptr +// RUN: %check_clang_tidy -std=c++14-or-later %s modernize-make-unique %t -- -- -I %S/Inputs/modernize-smart-ptr #include "unique_ptr.h" // CHECK-FIXES: #include diff --git a/clang-tools-extra/test/clang-tidy/modernize-make-unique-header.cpp b/clang-tools-extra/test/clang-tidy/modernize-make-unique-header.cpp index e6c2a613d9f3f..88d82d6b38c7c 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-make-unique-header.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-make-unique-header.cpp @@ -5,7 +5,7 @@ // RUN: {key: modernize-make-unique.MakeSmartPtrFunctionHeader, \ // RUN: value: 'make_unique_util.h'} \ // RUN: ]}" \ -// RUN: -- -std=c++11 -I%S/Inputs/modernize-smart-ptr +// RUN: -- -I %S/Inputs/modernize-smart-ptr #include "unique_ptr.h" // CHECK-FIXES: #include "make_unique_util.h" diff --git a/clang-tools-extra/test/clang-tidy/modernize-make-unique-macros.cpp b/clang-tools-extra/test/clang-tidy/modernize-make-unique-macros.cpp index 117a45cb6d4f7..3a7da91126051 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-make-unique-macros.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-make-unique-macros.cpp @@ -1,6 +1,6 @@ -// RUN: %check_clang_tidy %s modernize-make-unique %t -- \ +// RUN: %check_clang_tidy -std=c++14-or-later %s modernize-make-unique %t -- \ // RUN: -config="{CheckOptions: [{key: modernize-make-unique.IgnoreMacros, value: 0}]}" \ -// RUN: -- -std=c++14 -I%S/Inputs/modernize-smart-ptr +// RUN: -- -I %S/Inputs/modernize-smart-ptr #include "unique_ptr.h" diff --git a/clang-tools-extra/test/clang-tidy/modernize-make-unique.cpp b/clang-tools-extra/test/clang-tidy/modernize-make-unique.cpp index 65246da7a2399..3b7a3de43ac5f 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-make-unique.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-make-unique.cpp @@ -1,5 +1,5 @@ -// RUN: %check_clang_tidy %s modernize-make-unique %t -- -- -std=c++14 \ -// RUN: -I%S/Inputs/modernize-smart-ptr +// RUN: %check_clang_tidy -std=c++14 %s modernize-make-unique %t -- -- -I %S/Inputs/modernize-smart-ptr +// FIXME: Fix the checker to work in C++17 mode. #include "unique_ptr.h" #include "initializer_list.h" @@ -273,6 +273,14 @@ void initialization(int T, Base b) { // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use std::make_unique instead // CHECK-FIXES: std::make_unique(APair{T, 1}); + // Check aggregate init with intermediate temporaries. + std::unique_ptr PAggrTemp = std::unique_ptr(new APair({T, 1})); + // CHECK-MESSAGES: :[[@LINE-1]]:38: warning: use std::make_unique instead + // CHECK-FIXES: std::unique_ptr PAggrTemp = std::unique_ptr(new APair({T, 1})); + PAggrTemp.reset(new APair({T, 1})); + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use std::make_unique instead + // CHECK-FIXES: PAggrTemp.reset(new APair({T, 1})); + // Test different kinds of initialization of the pointee, when the unique_ptr // is initialized with braces. diff --git a/clang-tools-extra/test/clang-tidy/modernize-pass-by-value-header.cpp b/clang-tools-extra/test/clang-tidy/modernize-pass-by-value-header.cpp index 95ad623dca2b7..992ad27c61fdf 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-pass-by-value-header.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-pass-by-value-header.cpp @@ -1,6 +1,7 @@ // RUN: cp %S/Inputs/modernize-pass-by-value/header.h %T/pass-by-value-header.h // RUN: clang-tidy %s -checks='-*,modernize-pass-by-value' -header-filter='.*' -fix -- -std=c++11 -I %T | FileCheck %s -check-prefix=CHECK-MESSAGES -implicit-check-not="{{warning|error}}:" // RUN: FileCheck -input-file=%T/pass-by-value-header.h %s -check-prefix=CHECK-FIXES +// FIXME: Make the test work in all language modes. #include "pass-by-value-header.h" // CHECK-MESSAGES: :8:5: warning: pass by value and use std::move [modernize-pass-by-value] diff --git a/clang-tools-extra/test/clang-tidy/modernize-pass-by-value-macro-header.cpp b/clang-tools-extra/test/clang-tidy/modernize-pass-by-value-macro-header.cpp index 543b7e4651da3..5bfe680f58353 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-pass-by-value-macro-header.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-pass-by-value-macro-header.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-pass-by-value %t -- -- -std=c++11 -isystem %S/Inputs/Headers +// RUN: %check_clang_tidy %s modernize-pass-by-value %t -- -- -isystem %S/Inputs/Headers // CHECK-FIXES: #include diff --git a/clang-tools-extra/test/clang-tidy/modernize-pass-by-value.cpp b/clang-tools-extra/test/clang-tidy/modernize-pass-by-value.cpp index 87e22ba170cdb..6bf68ca0b6ab2 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-pass-by-value.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-pass-by-value.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-pass-by-value %t -- -- -std=c++11 -fno-delayed-template-parsing +// RUN: %check_clang_tidy %s modernize-pass-by-value %t -- -- -fno-delayed-template-parsing namespace { // POD types are trivially move constructible. diff --git a/clang-tools-extra/test/clang-tidy/modernize-raw-string-literal-delimiter.cpp b/clang-tools-extra/test/clang-tidy/modernize-raw-string-literal-delimiter.cpp index 1dd3e1321aa1b..2507a6f69745f 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-raw-string-literal-delimiter.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-raw-string-literal-delimiter.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-raw-string-literal %t -- -config='{CheckOptions: [{key: "modernize-raw-string-literal.DelimiterStem", value: "str"}, {key: modernize-raw-string-literal.ReplaceShorterLiterals, value: 1}]}' -- -std=c++11 +// RUN: %check_clang_tidy %s modernize-raw-string-literal %t -- -config='{CheckOptions: [{key: "modernize-raw-string-literal.DelimiterStem", value: "str"}, {key: modernize-raw-string-literal.ReplaceShorterLiterals, value: 1}]}' char const *const ContainsSentinel{"who\\ops)\""}; // CHECK-MESSAGES: :[[@LINE-1]]:36: warning: {{.*}} can be written as a raw string literal diff --git a/clang-tools-extra/test/clang-tidy/modernize-raw-string-literal.cpp b/clang-tools-extra/test/clang-tidy/modernize-raw-string-literal.cpp index b6d499513bf22..0fb0db1018150 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-raw-string-literal.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-raw-string-literal.cpp @@ -1,4 +1,5 @@ -// RUN: %check_clang_tidy %s modernize-raw-string-literal %t -- -config="{CheckOptions: [{key: modernize-raw-string-literal.ReplaceShorterLiterals, value: 1}]}" -- -std=c++11 +// RUN: %check_clang_tidy -std=c++11,c++14,c++17 %s modernize-raw-string-literal %t -- -config="{CheckOptions: [{key: modernize-raw-string-literal.ReplaceShorterLiterals, value: 1}]}" +// FIXME: Fix the checker to work in C++2a mode. char const *const BackSlash("goink\\frob"); // CHECK-MESSAGES: :[[@LINE-1]]:29: warning: escaped string literal can be written as a raw string literal [modernize-raw-string-literal] diff --git a/clang-tools-extra/test/clang-tidy/modernize-replace-auto-ptr.cpp b/clang-tools-extra/test/clang-tidy/modernize-replace-auto-ptr.cpp index 13405b2a206a9..c54ba1c75c69b 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-replace-auto-ptr.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-replace-auto-ptr.cpp @@ -1,5 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-replace-auto-ptr %t -- -- \ -// RUN: -std=c++11 -I %S/Inputs/modernize-replace-auto-ptr +// RUN: %check_clang_tidy %s modernize-replace-auto-ptr %t -- -- -I %S/Inputs/modernize-replace-auto-ptr // CHECK-FIXES: #include diff --git a/clang-tools-extra/test/clang-tidy/modernize-replace-random-shuffle.cpp b/clang-tools-extra/test/clang-tidy/modernize-replace-random-shuffle.cpp index 019471e2a3ebe..f96a60b377873 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-replace-random-shuffle.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-replace-random-shuffle.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-replace-random-shuffle %t -- -- -std=c++11 +// RUN: %check_clang_tidy %s modernize-replace-random-shuffle %t //CHECK-FIXES: #include diff --git a/clang-tools-extra/test/clang-tidy/modernize-return-braced-init-list.cpp b/clang-tools-extra/test/clang-tidy/modernize-return-braced-init-list.cpp index 49a41ae18cf8b..29d81c6df1f02 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-return-braced-init-list.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-return-braced-init-list.cpp @@ -1,4 +1,5 @@ -// RUN: %check_clang_tidy %s modernize-return-braced-init-list %t -- -- -std=c++14 +// RUN: %check_clang_tidy -std=c++14 %s modernize-return-braced-init-list %t +// FIXME: Fix the checker to work in C++17 mode. namespace std { typedef decltype(sizeof(int)) size_t; diff --git a/clang-tools-extra/test/clang-tidy/modernize-unary-static-assert.cpp b/clang-tools-extra/test/clang-tidy/modernize-unary-static-assert.cpp index fd39d5be527f8..97ad94834cd0e 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-unary-static-assert.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-unary-static-assert.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-unary-static-assert %t -- -- -std=c++1z +// RUN: %check_clang_tidy -std=c++17-or-later %s modernize-unary-static-assert %t #define FOO static_assert(sizeof(a) <= 15, ""); #define MSG "" diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-auto-cast-remove-stars.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-auto-cast-remove-stars.cpp index 97b87b8421a9b..5fbc243c66a39 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-auto-cast-remove-stars.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-auto-cast-remove-stars.cpp @@ -1,6 +1,6 @@ // RUN: %check_clang_tidy %s modernize-use-auto %t -- \ // RUN: -config="{CheckOptions: [{key: modernize-use-auto.RemoveStars, value: '1'} , {key: modernize-use-auto.MinTypeNameLength, value: '0'}]}" \ -// RUN: -- -std=c++11 -frtti +// RUN: -- -frtti struct A { virtual ~A() {} diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-auto-cast.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-auto-cast.cpp index e07c9cbf6003d..47c12471fec69 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-auto-cast.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-auto-cast.cpp @@ -1,6 +1,6 @@ // RUN: %check_clang_tidy %s modernize-use-auto %t -- \ // RUN: -config="{CheckOptions: [{key: modernize-use-auto.MinTypeNameLength, value: '0'}]}" \ -// RUN: -- -std=c++11 -I %S/Inputs/modernize-use-auto -frtti +// RUN: -- -I %S/Inputs/modernize-use-auto -frtti struct A { virtual ~A() {} diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-auto-iterator.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-auto-iterator.cpp index 98117fc544b14..9690972a97ce9 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-auto-iterator.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-auto-iterator.cpp @@ -1,5 +1,5 @@ -// RUN: %check_clang_tidy %s modernize-use-auto %t -- -- \ -// RUN: -std=c++11 -I %S/Inputs/modernize-use-auto +// RUN: %check_clang_tidy -std=c++11,c++14 %s modernize-use-auto %t -- -- -I %S/Inputs/modernize-use-auto +// FIXME: Fix the checker to work in C++17 mode. #include "containers.h" diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-auto-min-type-name-length.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-auto-min-type-name-length.cpp index 1cd91582b970f..dd79ad416ed86 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-auto-min-type-name-length.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-auto-min-type-name-length.cpp @@ -1,7 +1,7 @@ -// RUN: %check_clang_tidy -check-suffix=0-0 %s modernize-use-auto %t -- -config="{CheckOptions: [{key: modernize-use-auto.RemoveStars, value: 0}, {key: modernize-use-auto.MinTypeNameLength, value: 0}]}" -- --std=c++11 -frtti -// RUN: %check_clang_tidy -check-suffix=0-8 %s modernize-use-auto %t -- -config="{CheckOptions: [{key: modernize-use-auto.RemoveStars, value: 0}, {key: modernize-use-auto.MinTypeNameLength, value: 8}]}" -- --std=c++11 -frtti -// RUN: %check_clang_tidy -check-suffix=1-0 %s modernize-use-auto %t -- -config="{CheckOptions: [{key: modernize-use-auto.RemoveStars, value: 1}, {key: modernize-use-auto.MinTypeNameLength, value: 0}]}" -- --std=c++11 -frtti -// RUN: %check_clang_tidy -check-suffix=1-8 %s modernize-use-auto %t -- -config="{CheckOptions: [{key: modernize-use-auto.RemoveStars, value: 1}, {key: modernize-use-auto.MinTypeNameLength, value: 8}]}" -- --std=c++11 -frtti +// RUN: %check_clang_tidy -check-suffix=0-0 %s modernize-use-auto %t -- -config="{CheckOptions: [{key: modernize-use-auto.RemoveStars, value: 0}, {key: modernize-use-auto.MinTypeNameLength, value: 0}]}" -- -frtti +// RUN: %check_clang_tidy -check-suffix=0-8 %s modernize-use-auto %t -- -config="{CheckOptions: [{key: modernize-use-auto.RemoveStars, value: 0}, {key: modernize-use-auto.MinTypeNameLength, value: 8}]}" -- -frtti +// RUN: %check_clang_tidy -check-suffix=1-0 %s modernize-use-auto %t -- -config="{CheckOptions: [{key: modernize-use-auto.RemoveStars, value: 1}, {key: modernize-use-auto.MinTypeNameLength, value: 0}]}" -- -frtti +// RUN: %check_clang_tidy -check-suffix=1-8 %s modernize-use-auto %t -- -config="{CheckOptions: [{key: modernize-use-auto.RemoveStars, value: 1}, {key: modernize-use-auto.MinTypeNameLength, value: 8}]}" -- -frtti template extern T foo(); template struct P { explicit P(T t) : t_(t) {} T t_;}; diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-auto-new-remove-stars.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-auto-new-remove-stars.cpp index 00e3c89519412..3eb1bf9d76978 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-auto-new-remove-stars.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-auto-new-remove-stars.cpp @@ -1,6 +1,5 @@ // RUN: %check_clang_tidy %s modernize-use-auto %t -- \ -// RUN: -config="{CheckOptions: [{key: modernize-use-auto.RemoveStars, value: '1'}, {key: modernize-use-auto.MinTypeNameLength, value: '0'}]}" \ -// RUN: -- -std=c++11 +// RUN: -config="{CheckOptions: [{key: modernize-use-auto.RemoveStars, value: '1'}, {key: modernize-use-auto.MinTypeNameLength, value: '0'}]}" class MyType {}; diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-auto-new.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-auto-new.cpp index 3c872c986bcbe..c9a438971cfd3 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-auto-new.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-auto-new.cpp @@ -1,6 +1,6 @@ // RUN: %check_clang_tidy %s modernize-use-auto %t -- \ // RUN: -config="{CheckOptions: [{key: modernize-use-auto.MinTypeNameLength, value: '0'}]}" \ -// RUN: -- -std=c++11 -frtti +// RUN: -- -frtti class MyType {}; diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-bool-literals-ignore-macros.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-bool-literals-ignore-macros.cpp index cf8f72c976449..fc0ec47186a33 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-bool-literals-ignore-macros.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-bool-literals-ignore-macros.cpp @@ -1,8 +1,7 @@ // RUN: %check_clang_tidy %s modernize-use-bool-literals %t -- \ // RUN: -config="{CheckOptions: \ // RUN: [{key: modernize-use-bool-literals.IgnoreMacros, \ -// RUN: value: 1}]}" \ -// RUN: -- -std=c++11 +// RUN: value: 1}]}" bool IntToTrue = 1; // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: converting integer literal to bool, use bool literal instead [modernize-use-bool-literals] diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-bool-literals.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-bool-literals.cpp index f2ab9ce438ae1..a6b66be92a07e 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-bool-literals.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-bool-literals.cpp @@ -1,8 +1,7 @@ // RUN: %check_clang_tidy %s modernize-use-bool-literals %t -- \ // RUN: -config="{CheckOptions: \ // RUN: [{key: modernize-use-bool-literals.IgnoreMacros, \ -// RUN: value: 0}]}" \ -// RUN: -- -std=c++11 +// RUN: value: 0}]}" bool IntToTrue = 1; // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: converting integer literal to bool, use bool literal instead [modernize-use-bool-literals] diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-default-member-init-assignment.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-default-member-init-assignment.cpp index b98055c9f44ad..f27a95bcc30c3 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-default-member-init-assignment.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-default-member-init-assignment.cpp @@ -1,5 +1,5 @@ // RUN: %check_clang_tidy %s modernize-use-default-member-init %t -- \ -// RUN: -config="{CheckOptions: [{key: modernize-use-default-member-init.UseAssignment, value: 1}]}" -- -std=c++11 +// RUN: -config="{CheckOptions: [{key: modernize-use-default-member-init.UseAssignment, value: 1}]}" struct S { }; diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-default-member-init-bitfield.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-default-member-init-bitfield.cpp index 1ff08f97d416b..2eaf8f8a62e8f 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-default-member-init-bitfield.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-default-member-init-bitfield.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-use-default-member-init %t -- -- -std=c++2a +// RUN: %check_clang_tidy -std=c++2a-or-later %s modernize-use-default-member-init %t struct PositiveBitField { diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-default-member-init-macros.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-default-member-init-macros.cpp index 5b366b57d1109..62be60c976806 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-default-member-init-macros.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-default-member-init-macros.cpp @@ -1,6 +1,5 @@ // RUN: %check_clang_tidy %s modernize-use-default-member-init %t -- \ -// RUN: -config="{CheckOptions: [{key: modernize-use-default-member-init.IgnoreMacros, value: 0}]}" \ -// RUN: -- -std=c++11 +// RUN: -config="{CheckOptions: [{key: modernize-use-default-member-init.IgnoreMacros, value: 0}]}" #define MACRO() \ struct S { \ diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-default-member-init.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-default-member-init.cpp index 825bfa0bfbeed..5af9855835ca5 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-default-member-init.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-default-member-init.cpp @@ -1,4 +1,5 @@ -// RUN: %check_clang_tidy %s modernize-use-default-member-init %t -- -- -std=c++11 +// RUN: %check_clang_tidy -std=c++11,c++14,c++17 %s modernize-use-default-member-init %t +// FIXME: Fix the checker to work in C++2a mode. struct S { }; diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-emplace-ignore-implicit-constructors.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-emplace-ignore-implicit-constructors.cpp index 2f6b37f3e9fd9..538b641c03c8f 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-emplace-ignore-implicit-constructors.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-emplace-ignore-implicit-constructors.cpp @@ -2,7 +2,7 @@ // RUN: -config="{CheckOptions: \ // RUN: [{key: modernize-use-emplace.IgnoreImplicitConstructors, \ // RUN: value: 1}] \ -// RUN: }" -- -std=c++11 +// RUN: }" namespace std { template diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-emplace.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-emplace.cpp index c03d0241e201e..4e74d49d33fb7 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-emplace.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-emplace.cpp @@ -6,7 +6,7 @@ // RUN: value: '::std::pair; std::tuple; ::test::Single'}, \ // RUN: {key: modernize-use-emplace.TupleMakeFunctions, \ // RUN: value: '::std::make_pair; ::std::make_tuple; ::test::MakeSingle'}] \ -// RUN: }" -- -std=c++11 +// RUN: }" namespace std { template diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-equals-default-copy.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-equals-default-copy.cpp index 39a864d6651ab..8aa80d8a957f0 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-equals-default-copy.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-equals-default-copy.cpp @@ -1,6 +1,6 @@ // RUN: %check_clang_tidy %s modernize-use-equals-default %t -- \ // RUN: -config="{CheckOptions: [{key: modernize-use-equals-default.IgnoreMacros, value: 0}]}" \ -// RUN: -- -std=c++11 -fno-delayed-template-parsing -fexceptions +// RUN: -- -fno-delayed-template-parsing -fexceptions // Out of line definition. struct OL { diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-equals-default-delayed.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-equals-default-delayed.cpp index 953d88a9b79de..f39efd8452f79 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-equals-default-delayed.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-equals-default-delayed.cpp @@ -1,6 +1,7 @@ // RUN: clang-tidy %s -checks=-*,modernize-use-equals-default -- -std=c++11 -fdelayed-template-parsing -fexceptions | count 0 // Note: this test expects no diagnostics, but FileCheck cannot handle that, // hence the use of | count 0. +// FIXME: Make the test work in all language modes. template struct S { diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-equals-default-macros.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-equals-default-macros.cpp index 2277a862a22c9..a660f81e2935e 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-equals-default-macros.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-equals-default-macros.cpp @@ -1,6 +1,5 @@ // RUN: %check_clang_tidy %s modernize-use-equals-default %t -- \ -// RUN: -config="{CheckOptions: [{key: modernize-use-equals-default.IgnoreMacros, value: 0}]}" \ -// RUN: -- -std=c++11 +// RUN: -config="{CheckOptions: [{key: modernize-use-equals-default.IgnoreMacros, value: 0}]}" #define STRUCT_WITH_DEFAULT(_base, _type) \ struct _type { \ diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-equals-default.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-equals-default.cpp index 8fa15ff65a555..c214f6a573946 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-equals-default.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-equals-default.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-use-equals-default %t -- -- -std=c++11 -fno-delayed-template-parsing -fexceptions +// RUN: %check_clang_tidy %s modernize-use-equals-default %t -- -- -fno-delayed-template-parsing -fexceptions // Out of line definition. class OL { diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-equals-delete-macros.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-equals-delete-macros.cpp index 1b5a89cca4e78..f20804f1e598c 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-equals-delete-macros.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-equals-delete-macros.cpp @@ -1,6 +1,5 @@ // RUN: %check_clang_tidy %s modernize-use-equals-delete %t -- \ -// RUN: -config="{CheckOptions: [{key: modernize-use-equals-delete.IgnoreMacros, value: 0}]}" \ -// RUN: -- -std=c++11 +// RUN: -config="{CheckOptions: [{key: modernize-use-equals-delete.IgnoreMacros, value: 0}]}" #define MACRO(type) void operator=(type const &) class C { diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-clang-unused.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-clang-unused.cpp index 0951e6118d4a2..0d29a03227494 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-clang-unused.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-clang-unused.cpp @@ -1,6 +1,5 @@ // RUN: %check_clang_tidy %s modernize-use-nodiscard %t -- \ -// RUN: -config="{CheckOptions: [{key: modernize-use-nodiscard.ReplacementString, value: '[[clang::warn_unused_result]]'}]}" \ -// RUN: -- -std=c++11 +// RUN: -config="{CheckOptions: [{key: modernize-use-nodiscard.ReplacementString, value: '[[clang::warn_unused_result]]'}]}" class Foo { diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-cxx11.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-cxx11.cpp index cbf0cead0559c..8c75043a06414 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-cxx11.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-cxx11.cpp @@ -1,6 +1,5 @@ // RUN: %check_clang_tidy %s modernize-use-nodiscard %t -- \ -// RUN: -config="{CheckOptions: [{key: modernize-use-nodiscard.ReplacementString, value: '__attribute__((warn_unused_result))'}]}" \ -// RUN: -- -std=c++11 +// RUN: -config="{CheckOptions: [{key: modernize-use-nodiscard.ReplacementString, value: '__attribute__((warn_unused_result))'}]}" class Foo { diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-gcc-unused.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-gcc-unused.cpp index 54b808ed726b6..7ddcb7aff380c 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-gcc-unused.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-gcc-unused.cpp @@ -1,6 +1,5 @@ // RUN: %check_clang_tidy %s modernize-use-nodiscard %t -- \ -// RUN: -config="{CheckOptions: [{key: modernize-use-nodiscard.ReplacementString, value: '[[gcc::warn_unused_result]]'}]}" \ -// RUN: -- -std=c++11 +// RUN: -config="{CheckOptions: [{key: modernize-use-nodiscard.ReplacementString, value: '[[gcc::warn_unused_result]]'}]}" class Foo { diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-no-macro-inscope-cxx11.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-no-macro-inscope-cxx11.cpp index 0564a1336031d..5ac821ad466fe 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-no-macro-inscope-cxx11.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-no-macro-inscope-cxx11.cpp @@ -1,5 +1,5 @@ // RUN: %check_clang_tidy %s modernize-use-nodiscard %t -- \ -// RUN: -config="{CheckOptions: [{key: modernize-use-nodiscard.ReplacementString, value: 'CUSTOM_NO_DISCARD'}]}" -- -std=c++11 +// RUN: -config="{CheckOptions: [{key: modernize-use-nodiscard.ReplacementString, value: 'CUSTOM_NO_DISCARD'}]}" // As if the macro was not defined. // #define CUSTOM_NO_DISCARD __attribute_((warn_unused_result)) diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-no-macro.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-no-macro.cpp index 7898b6ede3433..578df3665f89c 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-no-macro.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-nodiscard-no-macro.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-use-nodiscard %t -- -- -std=c++17 +// RUN: %check_clang_tidy -std=c++17-or-later %s modernize-use-nodiscard %t class Foo { diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-noexcept-macro.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-noexcept-macro.cpp index 3948b66000f90..77b59cf51dfa3 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-noexcept-macro.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-noexcept-macro.cpp @@ -1,6 +1,8 @@ -// RUN: %check_clang_tidy %s modernize-use-noexcept %t -- \ +// RUN: %check_clang_tidy -std=c++11,c++14 %s modernize-use-noexcept %t -- \ // RUN: -config="{CheckOptions: [{key: modernize-use-noexcept.ReplacementString, value: 'NOEXCEPT'}]}" \ -// RUN: -- -std=c++11 -fexceptions +// RUN: -- -fexceptions +// This test is not run in C++17 or later because dynamic exception +// specifications were removed in C++17. // Example definition of NOEXCEPT -- simplified test to see if noexcept is supported. #if (__has_feature(cxx_noexcept)) diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-noexcept-opt.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-noexcept-opt.cpp index 85a83c81d0728..92c1387d64d66 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-noexcept-opt.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-noexcept-opt.cpp @@ -1,6 +1,8 @@ -// RUN: %check_clang_tidy %s modernize-use-noexcept %t -- \ +// RUN: %check_clang_tidy -std=c++11,c++14 %s modernize-use-noexcept %t -- \ // RUN: -config="{CheckOptions: [{key: modernize-use-noexcept.UseNoexceptFalse, value: 0}]}" \ -// RUN: -- -std=c++11 -fexceptions +// RUN: -- -fexceptions +// This test is not run in C++17 or later because dynamic exception +// specifications were removed in C++17. class A {}; class B {}; diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-noexcept.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-noexcept.cpp index 58c764ab90f90..863a4ed9a6fc0 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-noexcept.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-noexcept.cpp @@ -1,5 +1,6 @@ -// RUN: %check_clang_tidy %s modernize-use-noexcept %t -- \ -// RUN: -- -std=c++11 -fexceptions +// RUN: %check_clang_tidy -std=c++11,c++14 %s modernize-use-noexcept %t -- -- -fexceptions +// This test is not run in C++17 or later because dynamic exception +// specifications were removed in C++17. class A {}; class B {}; diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-nullptr-basic.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-nullptr-basic.cpp index 7a953a70c7779..5fd823b754e62 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-nullptr-basic.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-nullptr-basic.cpp @@ -1,8 +1,9 @@ -// RUN: %check_clang_tidy %s modernize-use-nullptr %t -- -- \ -// RUN: -std=c++98 -Wno-non-literal-null-conversion +// RUN: %check_clang_tidy -std=c++98 %s modernize-use-nullptr %t -- -- -Wno-non-literal-null-conversion // // Some parts of the test (e.g. assignment of `const int` to `int *`) fail in // C++11, so we need to run the test in C++98 mode. +// +// FIXME: Make the test work in all language modes. const unsigned int g_null = 0; #define NULL 0 diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-nullptr.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-nullptr.cpp index 878c11473c92a..d7b78ac9c3f64 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-nullptr.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-nullptr.cpp @@ -1,6 +1,5 @@ // RUN: %check_clang_tidy %s modernize-use-nullptr %t -- \ -// RUN: -config="{CheckOptions: [{key: modernize-use-nullptr.NullMacros, value: 'MY_NULL,NULL'}]}" \ -// RUN: -- -std=c++11 +// RUN: -config="{CheckOptions: [{key: modernize-use-nullptr.NullMacros, value: 'MY_NULL,NULL'}]}" #define NULL 0 diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-override-cxx98.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-override-cxx98.cpp index bc162d63a60a4..aaa9d35cdef57 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-override-cxx98.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-override-cxx98.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-use-override %t -- -- -std=c++98 +// RUN: %check_clang_tidy -std=c++98 %s modernize-use-override %t struct Base { virtual ~Base() {} diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-override-ms.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-override-ms.cpp index 0cee91708375f..e7c00895860c2 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-override-ms.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-override-ms.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-use-override %t -- -- -fms-extensions -std=c++11 +// RUN: %check_clang_tidy %s modernize-use-override %t -- -- -fms-extensions // This test is designed to test ms-extension __declspec(dllexport) attributes. #define EXPORT __declspec(dllexport) diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-override-no-destructors.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-override-no-destructors.cpp index eaadb07b947b3..aa8ee42eb84f0 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-override-no-destructors.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-override-no-destructors.cpp @@ -1,6 +1,5 @@ // RUN: %check_clang_tidy %s modernize-use-override %t -- \ -// RUN: -config="{CheckOptions: [{key: modernize-use-override.IgnoreDestructors, value: 1}]}" \ -// RUN: -- -std=c++11 +// RUN: -config="{CheckOptions: [{key: modernize-use-override.IgnoreDestructors, value: 1}]}" struct Base { virtual ~Base(); diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-override-with-macro.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-override-with-macro.cpp index ad682f1559136..37013adaff808 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-override-with-macro.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-override-with-macro.cpp @@ -1,6 +1,5 @@ // RUN: %check_clang_tidy %s modernize-use-override %t -- \ -// RUN: -config="{CheckOptions: [{key: modernize-use-override.OverrideSpelling, value: 'OVERRIDE'},{key: modernize-use-override.FinalSpelling, value: 'FINAL'}]}" \ -// RUN: -- -std=c++11 +// RUN: -config="{CheckOptions: [{key: modernize-use-override.OverrideSpelling, value: 'OVERRIDE'},{key: modernize-use-override.FinalSpelling, value: 'FINAL'}]}" #define ABSTRACT = 0 diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-override-with-no-macro-inscope.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-override-with-no-macro-inscope.cpp index 97b71053d333f..f0cbdc5c2b179 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-override-with-no-macro-inscope.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-override-with-no-macro-inscope.cpp @@ -1,6 +1,5 @@ // RUN: %check_clang_tidy %s modernize-use-override %t -- \ -// RUN: -config="{CheckOptions: [{key: modernize-use-override.OverrideSpelling, value: 'CUSTOM_OVERRIDE'},{key: modernize-use-override.FinalSpelling, value: 'CUSTOM_FINAL'}]}" \ -// RUN: -- -std=c++11 +// RUN: -config="{CheckOptions: [{key: modernize-use-override.OverrideSpelling, value: 'CUSTOM_OVERRIDE'},{key: modernize-use-override.FinalSpelling, value: 'CUSTOM_FINAL'}]}" // As if the macro was not defined. //#define CUSTOM_OVERRIDE override diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-override.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-override.cpp index 24aafe5c4698c..8e4e113e4f431 100644 --- a/clang-tools-extra/test/clang-tidy/modernize-use-override.cpp +++ b/clang-tools-extra/test/clang-tidy/modernize-use-override.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s modernize-use-override %t -- -- -std=c++11 -fexceptions +// RUN: %check_clang_tidy %s modernize-use-override %t -- -- -fexceptions #define ABSTRACT = 0 diff --git a/clang-tools-extra/test/clang-tidy/modernize-use-trailing-return-type.cpp b/clang-tools-extra/test/clang-tidy/modernize-use-trailing-return-type.cpp new file mode 100644 index 0000000000000..15b3e27470728 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/modernize-use-trailing-return-type.cpp @@ -0,0 +1,565 @@ +// RUN: %check_clang_tidy -std=c++14,c++17 %s modernize-use-trailing-return-type %t -- -- -fdeclspec -fexceptions +// FIXME: Fix the checker to work in C++2a mode, it is performing a +// use-of-uninitialized-value. + +namespace std { + template + class vector; + + template + class array; + + class string; + + template + auto declval() -> T; +} + +// +// Functions +// + +int f(); +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a trailing return type for this function [modernize-use-trailing-return-type] +// CHECK-FIXES: {{^}}auto f() -> int;{{$}} +int ((f))(); +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use a trailing return type for this function [modernize-use-trailing-return-type] +// CHECK-FIXES: {{^}}auto ((f))() -> int;{{$}} +int f(int); +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a trailing return type for this function [modernize-use-trailing-return-type] +// CHECK-FIXES: {{^}}auto f(int) -> int;{{$}} +int f(int arg); +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a trailing return type for this function [modernize-use-trailing-return-type] +// CHECK-FIXES: {{^}}auto f(int arg) -> int;{{$}} +int f(int arg1, int arg2, int arg3); +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a trailing return type for this function [modernize-use-trailing-return-type] +// CHECK-FIXES: {{^}}auto f(int arg1, int arg2, int arg3) -> int;{{$}} +int f(int arg1, int arg2, int arg3, ...); +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a trailing return type for this function [modernize-use-trailing-return-type] +// CHECK-FIXES: {{^}}auto f(int arg1, int arg2, int arg3, ...) -> int;{{$}} +template int f(T t); +// CHECK-MESSAGES: :[[@LINE-1]]:27: warning: use a trailing return type for this function [modernize-use-trailing-return-type] +// CHECK-FIXES: {{^}}template auto f(T t) -> int;{{$}} + +// +// Functions with formatting +// + +int a1() { return 42; } +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a trailing return type for this function [modernize-use-trailing-return-type] +// CHECK-FIXES: {{^}}auto a1() -> int { return 42; }{{$}} +int a2() { + return 42; +} +// CHECK-MESSAGES: :[[@LINE-3]]:5: warning: use a trailing return type for this function [modernize-use-trailing-return-type] +// CHECK-FIXES: {{^}}auto a2() -> int {{{$}} +int a3() +{ + return 42; +} +// CHECK-MESSAGES: :[[@LINE-4]]:5: warning: use a trailing return type for this function [modernize-use-trailing-return-type] +// CHECK-FIXES: {{^}}auto a3() -> int{{$}} +int a4(int arg ) ; +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a trailing return type for this function [modernize-use-trailing-return-type] +// CHECK-FIXES: {{^}}auto a4(int arg ) -> int ;{{$}} +int a5 +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a trailing return type for this function [modernize-use-trailing-return-type] +// CHECK-FIXES: {{^}}auto a5{{$}} +(int arg); +// CHECK-FIXES: {{^}}(int arg) -> int;{{$}} +const +int +* +a7 +// CHECK-MESSAGES: :[[@LINE-1]]:1: warning: use a trailing return type for this function [modernize-use-trailing-return-type] +() +// CHECK-FIXES: {{^}}() -> const{{$}} +// CHECK-FIXES: {{^}}int{{$}} +// CHECK-FIXES: {{^}}*{{$}} +; + +int*a7(int arg); +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a trailing return type for this function [modernize-use-trailing-return-type] +// CHECK-FIXES: {{^}}auto a7(int arg) -> int*;{{$}} +template