diff --git a/CMakeLists.txt b/CMakeLists.txt index 16551a10..e66f3f00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -132,6 +132,32 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug") clangLex clangSema clangSerialization + clangTidy + clangTidyUtils + # ALL_CLANG_TIDY_CHECKS + clangTidyAndroidModule + clangTidyAbseilModule + clangTidyAlteraModule + clangTidyBoostModule + clangTidyBugproneModule + clangTidyCERTModule + clangTidyConcurrencyModule + clangTidyCppCoreGuidelinesModule + clangTidyDarwinModule + clangTidyFuchsiaModule + clangTidyGoogleModule + clangTidyHICPPModule + clangTidyLinuxKernelModule + clangTidyLLVMModule + clangTidyLLVMLibcModule + clangTidyMiscModule + clangTidyModernizeModule + clangTidyObjCModule + clangTidyOpenMPModule + clangTidyPerformanceModule + clangTidyPortabilityModule + clangTidyReadabilityModule + clangTidyZirconModule clangTooling clangToolingCore clangToolingInclusions @@ -147,6 +173,7 @@ target_compile_options(clice-core PUBLIC ${CLICE_CXX_FLAGS}) target_link_options(clice-core PUBLIC ${CLICE_LINKER_FLAGS}) target_include_directories(clice-core PUBLIC "${CMAKE_SOURCE_DIR}/include") +target_include_directories(clice-core PRIVATE "${CMAKE_SOURCE_DIR}/src/Compiler/generated/") target_link_libraries(clice-core PUBLIC uv_a tomlplusplus::tomlplusplus llvm-libs) # clice executable diff --git a/include/Compiler/Compilation.h b/include/Compiler/Compilation.h index ed95d99f..e25406cc 100644 --- a/include/Compiler/Compilation.h +++ b/include/Compiler/Compilation.h @@ -15,6 +15,9 @@ struct CompilationParams { /// The kind of this compilation. CompilationUnit::Kind kind; + /// Whether to run clang-tidy. + bool clang_tidy = false; + /// Output file path. llvm::SmallString<128> output_file; diff --git a/include/Compiler/Diagnostic.h b/include/Compiler/Diagnostic.h index 2bea73dc..b28219bc 100644 --- a/include/Compiler/Diagnostic.h +++ b/include/Compiler/Diagnostic.h @@ -52,6 +52,8 @@ struct DiagnosticID { bool is_unused() const; }; +class DiagnosticCollector {}; + struct Diagnostic { /// The diagnostic id. DiagnosticID id; @@ -66,7 +68,8 @@ struct Diagnostic { /// The error message of this diagnostic. std::string message; - static clang::DiagnosticConsumer* create(std::shared_ptr> diagnostics); + static std::pair + create(std::shared_ptr> diagnostics); }; } // namespace clice diff --git a/include/Compiler/Tidy.h b/include/Compiler/Tidy.h new file mode 100644 index 00000000..2ade4f79 --- /dev/null +++ b/include/Compiler/Tidy.h @@ -0,0 +1,24 @@ +#pragma once + +#include "llvm/ADT/StringRef.h" + +#include + +namespace clang { +class CompilerInstance; +} + +namespace clice::tidy { + +bool isRegisteredTidyCheck(llvm::StringRef check); +std::optional isFastTidyCheck(llvm::StringRef check); + +struct TidyParams {}; + +class ClangTidyChecker; + +/// Run clang-tidy on the given file. +std::unique_ptr configure(clang::CompilerInstance& instance, + const TidyParams& params); + +} // namespace clice::tidy diff --git a/include/Server/Config.h b/include/Server/Config.h index 63b22f6d..51a35641 100644 --- a/include/Server/Config.h +++ b/include/Server/Config.h @@ -5,6 +5,7 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringMap.h" namespace clice::config { @@ -30,6 +31,13 @@ struct IndexOptions { std::string dir = "${workspace}/.clice/index"; }; +/// Configures what clang-tidy checks to run and options to use with them. +struct ClangTidyOptions { + // A comma-separated list of globs specify which clang-tidy checks to run. + std::string checks; + llvm::StringMap check_options; +}; + struct Rule { std::string pattern; std::vector append; @@ -44,6 +52,7 @@ extern llvm::StringRef binary; extern llvm::StringRef llvm_version; extern llvm::StringRef workspace; +extern const ClangTidyOptions& clang_tidy; extern const ServerOptions& server; extern const CacheOptions& cache; extern const IndexOptions& index; diff --git a/src/Compiler/Compilation.cpp b/src/Compiler/Compilation.cpp index bf6da8fd..b3b813b7 100644 --- a/src/Compiler/Compilation.cpp +++ b/src/Compiler/Compilation.cpp @@ -1,11 +1,15 @@ +#include "Support/Logger.h" #include "CompilationUnitImpl.h" #include "Compiler/Command.h" #include "Compiler/Compilation.h" #include "Compiler/Diagnostic.h" +#include "Compiler/Tidy.h" #include "clang/Lex/PreprocessorOptions.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Frontend/MultiplexConsumer.h" +#include "TidyImpl.h" + namespace clice { namespace { @@ -63,6 +67,56 @@ class ProxyASTConsumer final : public clang::MultiplexConsumer { std::shared_ptr stop; }; +template +bool isTemplateSpecializationKind(const clang::NamedDecl* D, + clang::TemplateSpecializationKind Kind) { + if(const auto* TD = dyn_cast(D)) + return TD->getTemplateSpecializationKind() == Kind; + return false; +} + +bool isTemplateSpecializationKind(const clang::NamedDecl* D, + clang::TemplateSpecializationKind Kind) { + return isTemplateSpecializationKind(D, Kind) || + isTemplateSpecializationKind(D, Kind) || + isTemplateSpecializationKind(D, Kind); +} + +bool isImplicitTemplateInstantiation(const clang::NamedDecl* D) { + return isTemplateSpecializationKind(D, clang::TSK_ImplicitInstantiation); +} + +class DeclTrackingASTConsumer : public clang::ASTConsumer { +public: + DeclTrackingASTConsumer(std::vector& TopLevelDecls) : + TopLevelDecls(TopLevelDecls) {} + + bool HandleTopLevelDecl(clang::DeclGroupRef DG) override { + for(clang::Decl* D: DG) { + + TopLevelDecls.push_back(D); + } + return true; + } + +private: + std::vector& TopLevelDecls; +}; + +bool isClangdTopLevelDecl(const clang::Decl* D) { + auto& SM = D->getASTContext().getSourceManager(); + if(!isInsideMainFile(D->getLocation(), SM)) + return false; + if(const clang::NamedDecl* ND = dyn_cast(D)) + if(isImplicitTemplateInstantiation(ND)) + return false; + + // ObjCMethodDecl are not actually top-level decls. + if(isa(D)) + return false; + return true; +} + class ProxyAction final : public clang::WrapperFrontendAction { public: ProxyAction(std::unique_ptr action, @@ -80,7 +134,6 @@ class ProxyAction final : public clang::WrapperFrontendAction { std::move(stop)); } - /// Make this public. using clang::WrapperFrontendAction::EndSourceFile; private: @@ -154,10 +207,11 @@ CompilationResult run_clang(CompilationParams& params, const AfterExecute& after_execute = no_hook) { auto diagnostics = params.diagnostics ? params.diagnostics : std::make_shared>(); + auto [collector, diagnostic_client] = Diagnostic::create(diagnostics); auto diagnostic_engine = clang::CompilerInstance::createDiagnostics(*params.vfs, new clang::DiagnosticOptions(), - Diagnostic::create(diagnostics)); + diagnostic_client); auto invocation = create_invocation(params, diagnostic_engine); if(!invocation) { @@ -189,7 +243,7 @@ CompilationResult run_clang(CompilationParams& params, auto action = std::make_unique( std::make_unique(), /// We only collect top level declarations for parse main file. - params.kind == CompilationUnit::Content ? &top_level_decls : nullptr, + (params.clang_tidy || params.kind == CompilationUnit::Content) ? &top_level_decls : nullptr, params.stop); if(!action->BeginSourceFile(*instance, instance->getFrontendOpts().Inputs[0])) { @@ -197,7 +251,14 @@ CompilationResult run_clang(CompilationParams& params, } auto& pp = instance->getPreprocessor(); - /// FIXME: clang-tidy, include-fixer, etc? + /// FIXME: include-fixer, etc? + + /// Setup clang-tidy + std::unique_ptr checker; + if(params.clang_tidy) { + tidy::TidyParams params; + checker = tidy::configure(*instance, params); + } /// `BeginSourceFile` may create new preprocessor, so all operations related to preprocessor /// should be done after `BeginSourceFile`. @@ -235,6 +296,25 @@ CompilationResult run_clang(CompilationParams& params, token_buffer = std::move(*token_collector).consume(); } + // Must be called before EndSourceFile because the ast context can be destroyed later. + if(checker) { + auto clangd_top_level_decls = top_level_decls; + std::erase_if(clangd_top_level_decls, + [](auto decl) { return !isClangdTopLevelDecl(decl); }); + log::info("Clangd top level decls: {} of {}", + clangd_top_level_decls.size(), + top_level_decls.size()); + // AST traversals should exclude the preamble, to avoid performance cliffs. + // TODO: is it okay to affect the unit-level traversal scope here? + instance->getASTContext().setTraversalScope(clangd_top_level_decls); + checker->CTFinder.matchAST(instance->getASTContext()); + } + + // XXX: This is messy: clang-tidy checks flush some diagnostics at EOF. + // However Action->EndSourceFile() would destroy the ASTContext! + // So just inform the preprocessor of EOF, while keeping everything alive. + pp.EndSourceFile(); + /// FIXME: getDependencies currently return ArrayRef, which actually results in /// extra copy. It would be great to avoid this copy. @@ -244,7 +324,7 @@ CompilationResult run_clang(CompilationParams& params, } auto impl = new CompilationUnit::Impl{ - .interested = pp.getSourceManager().getMainFileID(), + .interested = instance->getSourceManager().getMainFileID(), .src_mgr = instance->getSourceManager(), .action = std::move(action), .instance = std::move(instance), diff --git a/src/Compiler/CompilationUnit.cpp b/src/Compiler/CompilationUnit.cpp index fa17d298..c4833f4c 100644 --- a/src/Compiler/CompilationUnit.cpp +++ b/src/Compiler/CompilationUnit.cpp @@ -6,6 +6,11 @@ namespace clice { CompilationUnit::~CompilationUnit() { if(impl && impl->action) { + auto instance = impl->instance.get(); + // We already notified the PP of end-of-file earlier, so detach it first. + // We must keep it alive until after EndSourceFile(), Sema relies on this. + auto PP = instance->getPreprocessorPtr(); + instance->setPreprocessor(nullptr); // Detach so we don't send EOF again. impl->action->EndSourceFile(); } diff --git a/src/Compiler/Diagnostic.cpp b/src/Compiler/Diagnostic.cpp index 56abba50..308450df 100644 --- a/src/Compiler/Diagnostic.cpp +++ b/src/Compiler/Diagnostic.cpp @@ -194,9 +194,9 @@ auto diagnostic_range(const clang::Diagnostic& diagnostic, const clang::LangOpti }; } -class DiagnosticCollector : public clang::DiagnosticConsumer { +class DiagnosticCollectorImpl : public clang::DiagnosticConsumer, public DiagnosticCollector { public: - DiagnosticCollector(std::shared_ptr> diagnostics) : + DiagnosticCollectorImpl(std::shared_ptr> diagnostics) : diagnostics(diagnostics) {} void BeginSourceFile(const clang::LangOptions& Opts, const clang::Preprocessor* PP) override { @@ -241,9 +241,10 @@ class DiagnosticCollector : public clang::DiagnosticConsumer { clang::SourceManager* src_mgr; }; -clang::DiagnosticConsumer* +std::pair Diagnostic::create(std::shared_ptr> diagnostics) { - return new DiagnosticCollector(diagnostics); + auto collector = new DiagnosticCollectorImpl(diagnostics); + return std::pair{collector, collector}; } } // namespace clice diff --git a/src/Compiler/Tidy.cpp b/src/Compiler/Tidy.cpp new file mode 100644 index 00000000..595f09ab --- /dev/null +++ b/src/Compiler/Tidy.cpp @@ -0,0 +1,521 @@ +//===--- Run 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 +// +//===----------------------------------------------------------------------===// + +/// Partial code is copied from clangd. See: +/// https://github.com/llvm/llvm-project//blob/0865ecc5150b9a55ba1f9e30b6d463a66ac362a6/clang-tools-extra/clangd/ParsedAST.cpp#L547 +/// https://github.com/llvm/llvm-project//blob/0865ecc5150b9a55ba1f9e30b6d463a66ac362a6/clang-tools-extra/clangd/TidyProvider.cpp + +#include "Support/Logger.h" +#include "clang-tidy/ClangTidyModuleRegistry.h" +#include "clang-tidy/ClangTidyOptions.h" +#include "clang-tidy/ClangTidyCheck.h" +#include "clang-tidy/ClangTidyDiagnosticConsumer.h" + +#include "llvm/ADT/StringSet.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/Process.h" + +#include "clang/Frontend/CompilerInstance.h" +#include + +#include "Compiler/Diagnostic.h" +#include "Compiler/Tidy.h" + +#include "TidyImpl.h" + +// Force the linker to link in Clang-tidy modules. +// clangd doesn't support the static analyzer. +#define CLANG_TIDY_DISABLE_STATIC_ANALYZER_CHECKS +#include "clang-tidy/ClangTidyForceLinker.h" + +namespace clice::tidy { + +using namespace clang::tidy; + +bool isRegisteredTidyCheck(llvm::StringRef check) { + assert(!check.empty()); + assert(!check.contains('*') && !check.contains(',') && + "isRegisteredCheck doesn't support globs"); + assert(check.ltrim().front() != '-'); + + const static llvm::StringSet all_checks = [] { + llvm::StringSet result; + tidy::ClangTidyCheckFactories factories; + for(tidy::ClangTidyModuleRegistry::entry entry: tidy::ClangTidyModuleRegistry::entries()) + entry.instantiate()->addCheckFactories(factories); + for(const auto& factory: factories) + result.insert(factory.getKey()); + return result; + }(); + + return all_checks.contains(check); +} + +std::optional isFastTidyCheck(llvm::StringRef check) { + static auto& fast = *new llvm::StringMap{ +#define FAST(CHECK, TIME) {#CHECK, true}, +#define SLOW(CHECK, TIME) {#CHECK, false}, +// todo: move me to llvm toolchain headers. +#include "TidyFastChecks.inc" + }; + if(auto it = fast.find(check); it != fast.end()) + return it->second; + return std::nullopt; +} + +tidy::ClangTidyCheckFactories filterFastTidyChecks(const tidy::ClangTidyCheckFactories& All) { + tidy::ClangTidyCheckFactories Fast; + for(const auto& Factory: All) { + if(isFastTidyCheck(Factory.getKey()).value_or(false)) + Fast.registerCheckFactory(Factory.first(), Factory.second); + } + return Fast; +} + +tidy::ClangTidyOptions createTidyOptions() { + // getDefaults instantiates all check factories, which are registered at link + // time. So cache the results once. + const static auto* DefaultOpts = [] { + auto* Opts = new tidy::ClangTidyOptions; + *Opts = tidy::ClangTidyOptions::getDefaults(); + Opts->Checks->clear(); + return Opts; + }(); + // These default checks are chosen for: + // - low false-positive rate + // - providing a lot of value + // - being reasonably efficient + const static std::string DefaultChecks = llvm::join_items(",", + "readability-misleading-indentation", + "readability-deleted-default", + "bugprone-integer-division", + "bugprone-sizeof-expression", + "bugprone-suspicious-missing-comma", + "bugprone-unused-raii", + "bugprone-unused-return-value", + "misc-unused-using-decls", + "misc-unused-alias-decls", + "misc-definitions-in-headers"); + const static std::string BadChecks = + llvm::join_items(",", + // We want this list to start with a separator to + // simplify appending in the lambda. So including an + // empty string here will force that. + "", + // include-cleaner is directly integrated in IncludeCleaner.cpp + "-misc-include-cleaner", + + // ----- False Positives ----- + + // Check relies on seeing ifndef/define/endif directives, + // clangd doesn't replay those when using a preamble. + "-llvm-header-guard", + "-modernize-macro-to-enum", + + // ----- Crashing Checks ----- + + // Check can choke on invalid (intermediate) c++ + // code, which is often the case when clangd + // tries to build an AST. + "-bugprone-use-after-move", + // Alias for bugprone-use-after-move. + "-hicpp-invalid-access-moved", + // Check uses dataflow analysis, which might hang/crash unexpectedly on + // incomplete code. + "-bugprone-unchecked-optional-access"); + + tidy::ClangTidyOptions opts = *DefaultOpts; + + opts.Checks->clear(); + // clang::clangd::provideEnvironment + if(std::optional user = llvm::sys::Process::GetEnv("USER")) + opts.User = user; + // TODO: Providers.push_back(provideClangTidyFiles(TFS)); Filename + // TODO: if(EnableConfig) Providers.push_back(provideClangdConfig()); + // clang::clangd::provideDefaultChecks + if(!opts.Checks || opts.Checks->empty()) + opts.Checks = DefaultChecks; + // clang::clangd::disableUnusableChecks + if(opts.Checks && !opts.Checks->empty()) + opts.Checks->append(BadChecks); + return opts; +} + +// Filter for clang diagnostics groups enabled by CTOptions.Checks. +// +// These are check names like clang-diagnostics-unused. +// Note that unlike -Wunused, clang-diagnostics-unused does not imply +// subcategories like clang-diagnostics-unused-function. +// +// This is used to determine which diagnostics can be enabled by ExtraArgs in +// the clang-tidy configuration. +class TidyDiagnosticGroups { + // Whether all diagnostic groups are enabled by default. + // True if we've seen clang-diagnostic-*. + bool Default = false; + // Set of diag::Group whose enablement != Default. + // If Default is false, this is foo where we've seen clang-diagnostic-foo. + llvm::DenseSet Exceptions; + +public: + TidyDiagnosticGroups(llvm::StringRef Checks) { + constexpr llvm::StringLiteral CDPrefix = "clang-diagnostic-"; + + llvm::StringRef Check; + while(!Checks.empty()) { + std::tie(Check, Checks) = Checks.split(','); + Check = Check.trim(); + + if(Check.empty()) + continue; + + bool Enable = !Check.consume_front("-"); + bool Glob = Check.consume_back("*"); + if(Glob) { + // Is this clang-diagnostic-*, or *, or so? + // (We ignore all other types of globs). + if(CDPrefix.starts_with(Check)) { + Default = Enable; + Exceptions.clear(); + } + continue; + } + + // In "*,clang-diagnostic-foo", the latter is a no-op. + if(Default == Enable) + continue; + // The only non-glob entries we care about are clang-diagnostic-foo. + if(!Check.consume_front(CDPrefix)) + continue; + + if(auto Group = clang::DiagnosticIDs::getGroupForWarningOption(Check)) + Exceptions.insert(static_cast(*Group)); + } + } + + bool operator() (clang::diag::Group GroupID) const { + return Exceptions.contains(static_cast(GroupID)) ? !Default : Default; + } +}; + +// Find -W and -Wno- options in ExtraArgs and apply them to Diags. +// +// This is used to handle ExtraArgs in clang-tidy configuration. +// We don't use clang's standard handling of this as we want slightly different +// behavior (e.g. we want to exclude these from -Wno-error). +void applyWarningOptions(llvm::ArrayRef ExtraArgs, + llvm::function_ref EnabledGroups, + clang::DiagnosticsEngine& Diags) { + for(llvm::StringRef Group: ExtraArgs) { + // Only handle args that are of the form -W[no-]. + // Other flags are possible but rare and deliberately out of scope. + llvm::SmallVector Members; + if(!Group.consume_front("-W") || Group.empty()) + continue; + bool Enable = !Group.consume_front("no-"); + if(Diags.getDiagnosticIDs()->getDiagnosticsInGroup(clang::diag::Flavor::WarningOrError, + Group, + Members)) + continue; + + // Upgrade (or downgrade) the severity of each diagnostic in the group. + // If -Werror is on, newly added warnings will be treated as errors. + // We don't want this, so keep track of them to fix afterwards. + bool NeedsWerrorExclusion = false; + for(clang::diag::kind ID: Members) { + if(Enable) { + if(Diags.getDiagnosticLevel(ID, clang::SourceLocation()) < + clang::DiagnosticsEngine::Warning) { + auto Group = Diags.getDiagnosticIDs()->getGroupForDiag(ID); + if(!Group || !EnabledGroups(*Group)) + continue; + Diags.setSeverity(ID, clang::diag::Severity::Warning, clang::SourceLocation()); + if(Diags.getWarningsAsErrors()) + NeedsWerrorExclusion = true; + } + } else { + Diags.setSeverity(ID, clang::diag::Severity::Ignored, clang::SourceLocation()); + } + } + if(NeedsWerrorExclusion) { + // FIXME: there's no API to suppress -Werror for single diagnostics. + // In some cases with sub-groups, we may end up erroneously + // downgrading diagnostics that were -Werror in the compile command. + Diags.setDiagnosticGroupWarningAsError(Group, false); + } + } +} + +ClangTidyChecker::ClangTidyChecker(std::unique_ptr provider) : + context(std::move(provider)) {} + +clang::DiagnosticsEngine::Level + ClangTidyChecker::adjustLevel(clang::DiagnosticsEngine::Level DiagLevel, + const clang::Diagnostic& Info) { + if(!checks.empty()) { + std::string CheckName = context.getCheckName(Info.getID()); + bool IsClangTidyDiag = !CheckName.empty(); + if(IsClangTidyDiag) { + // 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 to avoid I/O. + // We let suppression comments take precedence over warning-as-error + // to match clang-tidy's behaviour. + bool IsInsideMainFile = Info.hasSourceManager() && + isInsideMainFile(Info.getLocation(), Info.getSourceManager()); + llvm::SmallVector TidySuppressedErrors; + if(IsInsideMainFile && context.shouldSuppressDiagnostic(DiagLevel, + Info, + TidySuppressedErrors, + /*AllowIO=*/false, + /*EnableNolintBlocks=*/true)) { + // FIXME: should we expose the suppression error (invalid use of + // NOLINT comments)? + return clang::DiagnosticsEngine::Ignored; + } + if(!context.getOptions().SystemHeaders.value_or(false) && Info.hasSourceManager() && + Info.getSourceManager().isInSystemMacro(Info.getLocation())) + return clang::DiagnosticsEngine::Ignored; + + // Check for warning-as-error. + if(DiagLevel == clang::DiagnosticsEngine::Warning && context.treatAsError(CheckName)) { + return clang::DiagnosticsEngine::Error; + } + } + } + return DiagLevel; +} + +void ClangTidyChecker::adjustDiag(Diagnostic& Diag) { + std::string TidyDiag = context.getCheckName(Diag.id.value); + if(!TidyDiag.empty()) { + // TODO: using a global string saver. + static llvm::BumpPtrAllocator Allocator; + static llvm::StringSaver Saver(Allocator); + Diag.id.name = Saver.save(TidyDiag); + Diag.id.source = DiagnosticSource::ClangTidy; + // clang-tidy bakes the name into diagnostic messages. Strip it out. + // It would be much nicer to make clang-tidy not do this. + auto CleanMessage = [&](std::string& Msg) { + llvm::StringRef Rest(Msg); + if(Rest.consume_back("]") && Rest.consume_back(Diag.id.name) && Rest.consume_back(" [")) + Msg.resize(Rest.size()); + }; + CleanMessage(Diag.message); + // todo: where is clice notes and fixes? + // for(auto& Note: Diag.Notes) + // CleanMessage(Note.Message); + // for(auto& Fix: Diag.Fixes) + // CleanMessage(Fix.Message); + } +} + +std::unique_ptr configure(clang::CompilerInstance& instance, + const TidyParams& params) { + auto& input = instance.getFrontendOpts().Inputs[0]; + + if(!input.isFile()) { + return nullptr; + } + auto FileName = input.getFile(); + log::info("Tidy configure file: {}", FileName); + + tidy::ClangTidyOptions ClangTidyOpts = createTidyOptions(); + if(ClangTidyOpts.Checks) { + log::info("Tidy configure checks: {}", *ClangTidyOpts.Checks); + } + + { + // If clang-tidy is configured to emit clang warnings, we should too. + // + // Such clang-tidy configuration consists of two parts: + // - ExtraArgs: ["-Wfoo"] causes clang to produce the warnings + // - Checks: "clang-diagnostic-foo" prevents clang-tidy filtering them out + // + // In clang-tidy, diagnostics are emitted if they pass both checks. + // When groups contain subgroups, -Wparent includes the child, but + // clang-diagnostic-parent does not. + // + // We *don't* want to change the compile command directly. This can have + // too many unexpected effects: breaking the command, interactions with + // -- and -Werror, etc. Besides, we've already parsed the command. + // Instead we parse the -W flags and handle them directly. + // + // Similarly, we don't want to use Checks to filter clang diagnostics after + // they are generated, as this spreads clang-tidy emulation everywhere. + // Instead, we just use these to filter which extra diagnostics we enable. + auto& Diags = instance.getDiagnostics(); + TidyDiagnosticGroups TidyGroups(ClangTidyOpts.Checks ? *ClangTidyOpts.Checks + : llvm::StringRef()); + if(ClangTidyOpts.ExtraArgsBefore) + applyWarningOptions(*ClangTidyOpts.ExtraArgsBefore, TidyGroups, Diags); + if(ClangTidyOpts.ExtraArgs) + applyWarningOptions(*ClangTidyOpts.ExtraArgs, TidyGroups, Diags); + } + + /// No need to run clang-tidy or IncludeFixerif we are not going to surface + /// diagnostics. + const static auto* AllCTFactories = [] { + auto* CTFactories = new tidy::ClangTidyCheckFactories; + for(const auto& E: tidy::ClangTidyModuleRegistry::entries()) + E.instantiate()->addCheckFactories(*CTFactories); + return CTFactories; + }(); + tidy::ClangTidyCheckFactories FastFactories = filterFastTidyChecks(*AllCTFactories); + std::unique_ptr checker = std::make_unique( + std::make_unique(tidy::ClangTidyGlobalOptions(), + ClangTidyOpts)); + + checker->context.setDiagnosticsEngine(&instance.getDiagnostics()); + checker->context.setASTContext(&instance.getASTContext()); + // tood: is it always FileName to check? + checker->context.setCurrentFile(FileName); + checker->context.setSelfContainedDiags(true); + checker->checks = FastFactories.createChecksForLanguage(&checker->context); + log::info("Tidy configure checks: {}", checker->checks.size()); + clang::Preprocessor* PP = &instance.getPreprocessor(); + for(const auto& Check: checker->checks) { + Check->registerPPCallbacks(instance.getSourceManager(), PP, PP); + Check->registerMatchers(&checker->CTFinder); + } + + return checker; +} + +#if 0 + +TidyProvider addTidyChecks(llvm::StringRef Checks, llvm::StringRef WarningsAsErrors) { + return [Checks = std::string(Checks), + WarningsAsErrors = std::string(WarningsAsErrors)](tidy::ClangTidyOptions& Opts, + llvm::StringRef) { + mergeCheckList(Opts.Checks, Checks); + mergeCheckList(Opts.WarningsAsErrors, WarningsAsErrors); + }; +} + +TidyProvider disableUnusableChecks(llvm::ArrayRef ExtraBadChecks) { + constexpr llvm::StringLiteral Separator(","); + const static std::string BadChecks = + llvm::join_items(Separator, + // We want this list to start with a separator to + // simplify appending in the lambda. So including an + // empty string here will force that. + "", + // include-cleaner is directly integrated in IncludeCleaner.cpp + "-misc-include-cleaner", + + // ----- False Positives ----- + + // Check relies on seeing ifndef/define/endif directives, + // clangd doesn't replay those when using a preamble. + "-llvm-header-guard", + "-modernize-macro-to-enum", + + // ----- Crashing Checks ----- + + // Check can choke on invalid (intermediate) c++ + // code, which is often the case when clangd + // tries to build an AST. + "-bugprone-use-after-move", + // Alias for bugprone-use-after-move. + "-hicpp-invalid-access-moved", + // Check uses dataflow analysis, which might hang/crash unexpectedly on + // incomplete code. + "-bugprone-unchecked-optional-access"); + + size_t Size = BadChecks.size(); + for(const std::string& Str: ExtraBadChecks) { + if(Str.empty()) + continue; + Size += Separator.size(); + if(LLVM_LIKELY(Str.front() != '-')) + ++Size; + Size += Str.size(); + } + std::string DisableGlob; + DisableGlob.reserve(Size); + DisableGlob += BadChecks; + for(const std::string& Str: ExtraBadChecks) { + if(Str.empty()) + continue; + DisableGlob += Separator; + if(LLVM_LIKELY(Str.front() != '-')) + DisableGlob.push_back('-'); + DisableGlob += Str; + } + + return [DisableList(std::move(DisableGlob))](tidy::ClangTidyOptions& Opts, llvm::StringRef) { + if(Opts.Checks && !Opts.Checks->empty()) + Opts.Checks->append(DisableList); + }; +} + +TidyProvider provideEnvironment() { + const static std::optional User = [] { + std::optional Ret = llvm::sys::Process::GetEnv("USER"); +#ifdef _WIN32 + if(!Ret) + return llvm::sys::Process::GetEnv("USERNAME"); +#endif + return Ret; + }(); + + if(User) + return [](tidy::ClangTidyOptions& Opts, llvm::StringRef) { + Opts.User = User; + }; + // FIXME: Once function_ref and unique_function operator= operators handle + // null values, this can return null. + return [](tidy::ClangTidyOptions&, llvm::StringRef) { + }; +} + +TidyProvider provideClangdConfig() { + return [](tidy::ClangTidyOptions& Opts, llvm::StringRef) { + const auto& CurTidyConfig = Config::current().Diagnostics.ClangTidy; + if(!CurTidyConfig.Checks.empty()) + mergeCheckList(Opts.Checks, CurTidyConfig.Checks); + + for(const auto& CheckOption: CurTidyConfig.CheckOptions) + Opts.CheckOptions.insert_or_assign( + CheckOption.getKey(), + tidy::ClangTidyOptions::ClangTidyValue(CheckOption.getValue(), 10000U)); + }; +} + +TidyProvider combine(std::vector Providers) { + // FIXME: Once function_ref and unique_function operator= operators handle + // null values, we should filter out any Providers that are null. Right now we + // have to ensure we dont pass any providers that are null. + return + [Providers(std::move(Providers))](tidy::ClangTidyOptions& Opts, llvm::StringRef Filename) { + for(const auto& Provider: Providers) + Provider(Opts, Filename); + }; +} + +tidy::ClangTidyOptions getTidyOptionsForFile(TidyProviderRef Provider, llvm::StringRef Filename) { + /// getDefaults instantiates all check factories, which are registered at link + /// time. So cache the results once. + const static auto* DefaultOpts = [] { + auto* Opts = new tidy::ClangTidyOptions; + *Opts = tidy::ClangTidyOptions::getDefaults(); + Opts->Checks->clear(); + return Opts; + }(); + auto Opts = *DefaultOpts; + if(Provider) + Provider(Opts, Filename); + return Opts; +} + +#endif + +} // namespace clice::tidy diff --git a/src/Compiler/TidyFastChecks.inc b/src/Compiler/TidyFastChecks.inc new file mode 100644 index 00000000..de1a0256 --- /dev/null +++ b/src/Compiler/TidyFastChecks.inc @@ -0,0 +1,442 @@ +// This file is generated, do not edit it directly! +// Deltas are percentage regression in parsing clang/lib/Sema/Sema.cpp +#ifndef FAST +#define FAST(CHECK, DELTA) +#endif +#ifndef SLOW +#define SLOW(CHECK, DELTA) +#endif + +FAST(abseil-cleanup-ctad, -2.0) +FAST(abseil-duration-addition, 0.0) +FAST(abseil-duration-comparison, -1.0) +FAST(abseil-duration-conversion-cast, -1.0) +FAST(abseil-duration-division, 0.0) +FAST(abseil-duration-factory-float, 2.0) +FAST(abseil-duration-factory-scale, 1.0) +FAST(abseil-duration-subtraction, -1.0) +FAST(abseil-duration-unnecessary-conversion, -0.0) +FAST(abseil-faster-strsplit-delimiter, 3.0) +FAST(abseil-no-internal-dependencies, 1.0) +FAST(abseil-no-namespace, -0.0) +FAST(abseil-redundant-strcat-calls, 1.0) +FAST(abseil-str-cat-append, -0.0) +FAST(abseil-string-find-startswith, -1.0) +FAST(abseil-string-find-str-contains, 4.0) +FAST(abseil-time-comparison, -1.0) +FAST(abseil-time-subtraction, 1.0) +FAST(abseil-upgrade-duration-conversions, 2.0) +SLOW(altera-id-dependent-backward-branch, 13.0) +FAST(altera-kernel-name-restriction, 4.0) +FAST(altera-single-work-item-barrier, 1.0) +FAST(altera-struct-pack-align, -0.0) +FAST(altera-unroll-loops, 2.0) +FAST(android-cloexec-accept, 0.0) +FAST(android-cloexec-accept4, 1.0) +FAST(android-cloexec-creat, 1.0) +FAST(android-cloexec-dup, 0.0) +FAST(android-cloexec-epoll-create, 2.0) +FAST(android-cloexec-epoll-create1, 0.0) +FAST(android-cloexec-fopen, -1.0) +FAST(android-cloexec-inotify-init, 2.0) +FAST(android-cloexec-inotify-init1, -0.0) +FAST(android-cloexec-memfd-create, -1.0) +FAST(android-cloexec-open, 1.0) +FAST(android-cloexec-pipe, -0.0) +FAST(android-cloexec-pipe2, 0.0) +FAST(android-cloexec-socket, 1.0) +FAST(android-comparison-in-temp-failure-retry, 1.0) +FAST(boost-use-ranges, 2.0) +FAST(boost-use-to-string, 2.0) +FAST(bugprone-argument-comment, 4.0) +FAST(bugprone-assert-side-effect, 1.0) +FAST(bugprone-assignment-in-if-condition, 2.0) +FAST(bugprone-bad-signal-to-kill-thread, 1.0) +FAST(bugprone-bool-pointer-implicit-conversion, 0.0) +FAST(bugprone-branch-clone, 1.0) +FAST(bugprone-casting-through-void, 1.0) +FAST(bugprone-chained-comparison, 1.0) +FAST(bugprone-compare-pointer-to-member-virtual-function, -0.0) +FAST(bugprone-copy-constructor-init, 1.0) +FAST(bugprone-crtp-constructor-accessibility, 0.0) +FAST(bugprone-dangling-handle, -0.0) +FAST(bugprone-dynamic-static-initializers, 0.0) +FAST(bugprone-easily-swappable-parameters, 2.0) +FAST(bugprone-empty-catch, 1.0) +FAST(bugprone-exception-escape, 0.0) +FAST(bugprone-fold-init-type, 1.0) +FAST(bugprone-forward-declaration-namespace, 0.0) +FAST(bugprone-forwarding-reference-overload, -1.0) +FAST(bugprone-implicit-widening-of-multiplication-result, 2.0) +FAST(bugprone-inaccurate-erase, -0.0) +FAST(bugprone-inc-dec-in-conditions, 3.0) +FAST(bugprone-incorrect-enable-if, -1.0) +FAST(bugprone-incorrect-roundings, 1.0) +FAST(bugprone-infinite-loop, 1.0) +FAST(bugprone-integer-division, -0.0) +FAST(bugprone-lambda-function-name, 0.0) +FAST(bugprone-macro-parentheses, 1.0) +FAST(bugprone-macro-repeated-side-effects, 1.0) +FAST(bugprone-misplaced-operator-in-strlen-in-alloc, 0.0) +FAST(bugprone-misplaced-pointer-arithmetic-in-alloc, -0.0) +FAST(bugprone-misplaced-widening-cast, -1.0) +FAST(bugprone-move-forwarding-reference, -1.0) +FAST(bugprone-multi-level-implicit-pointer-conversion, -1.0) +FAST(bugprone-multiple-new-in-one-expression, 0.0) +FAST(bugprone-multiple-statement-macro, 2.0) +FAST(bugprone-narrowing-conversions, 2.0) +FAST(bugprone-no-escape, 1.0) +FAST(bugprone-non-zero-enum-to-bool-conversion, 0.0) +FAST(bugprone-not-null-terminated-result, 0.0) +FAST(bugprone-optional-value-conversion, 1.0) +FAST(bugprone-parent-virtual-call, 1.0) +FAST(bugprone-pointer-arithmetic-on-polymorphic-object, 0.0) +FAST(bugprone-posix-return, -0.0) +FAST(bugprone-redundant-branch-condition, -0.0) +FAST(bugprone-reserved-identifier, -1.0) +FAST(bugprone-return-const-ref-from-parameter, -2.0) +FAST(bugprone-shared-ptr-array-mismatch, 0.0) +FAST(bugprone-signal-handler, -1.0) +FAST(bugprone-signed-char-misuse, -2.0) +FAST(bugprone-sizeof-container, -1.0) +FAST(bugprone-sizeof-expression, 1.0) +FAST(bugprone-spuriously-wake-up-functions, 1.0) +FAST(bugprone-standalone-empty, 7.0) +FAST(bugprone-string-constructor, 3.0) +FAST(bugprone-string-integer-assignment, -0.0) +FAST(bugprone-string-literal-with-embedded-nul, 1.0) +FAST(bugprone-stringview-nullptr, 4.0) +FAST(bugprone-suspicious-enum-usage, 2.0) +FAST(bugprone-suspicious-include, 0.0) +FAST(bugprone-suspicious-memory-comparison, 0.0) +FAST(bugprone-suspicious-memset-usage, 0.0) +FAST(bugprone-suspicious-missing-comma, -2.0) +FAST(bugprone-suspicious-realloc-usage, -0.0) +FAST(bugprone-suspicious-semicolon, 6.0) +FAST(bugprone-suspicious-string-compare, 1.0) +FAST(bugprone-suspicious-stringview-data-usage, 1.0) +FAST(bugprone-swapped-arguments, 1.0) +FAST(bugprone-switch-missing-default-case, 2.0) +FAST(bugprone-terminating-continue, -1.0) +FAST(bugprone-throw-keyword-missing, 0.0) +FAST(bugprone-too-small-loop-variable, 0.0) +FAST(bugprone-unchecked-optional-access, 2.0) +FAST(bugprone-undefined-memory-manipulation, 1.0) +FAST(bugprone-undelegated-constructor, 1.0) +FAST(bugprone-unhandled-exception-at-new, -1.0) +FAST(bugprone-unhandled-self-assignment, 0.0) +FAST(bugprone-unique-ptr-array-mismatch, 0.0) +FAST(bugprone-unsafe-functions, 1.0) +FAST(bugprone-unused-local-non-trivial-variable, -1.0) +FAST(bugprone-unused-raii, 1.0) +FAST(bugprone-unused-return-value, 4.0) +FAST(bugprone-use-after-move, 4.0) +FAST(bugprone-virtual-near-miss, 0.0) +FAST(cert-con36-c, 1.0) +FAST(cert-con54-cpp, 2.0) +FAST(cert-ctr56-cpp, 0.0) +FAST(cert-dcl03-c, 0.0) +FAST(cert-dcl16-c, 1.0) +FAST(cert-dcl37-c, 1.0) +FAST(cert-dcl50-cpp, -1.0) +FAST(cert-dcl51-cpp, -1.0) +FAST(cert-dcl54-cpp, 0.0) +FAST(cert-dcl58-cpp, -0.0) +FAST(cert-dcl59-cpp, 1.0) +FAST(cert-env33-c, 1.0) +FAST(cert-err09-cpp, -0.0) +FAST(cert-err33-c, 4.0) +FAST(cert-err34-c, -1.0) +FAST(cert-err52-cpp, -1.0) +FAST(cert-err58-cpp, -0.0) +FAST(cert-err60-cpp, -0.0) +FAST(cert-err61-cpp, 2.0) +FAST(cert-exp42-c, 1.0) +FAST(cert-fio38-c, 1.0) +FAST(cert-flp30-c, 3.0) +FAST(cert-flp37-c, 1.0) +FAST(cert-int09-c, -1.0) +FAST(cert-mem57-cpp, 0.0) +FAST(cert-msc24-c, 0.0) +FAST(cert-msc30-c, 0.0) +FAST(cert-msc32-c, -0.0) +FAST(cert-msc33-c, 2.0) +FAST(cert-msc50-cpp, -0.0) +FAST(cert-msc51-cpp, 2.0) +FAST(cert-msc54-cpp, -0.0) +FAST(cert-oop11-cpp, -0.0) +FAST(cert-oop54-cpp, 2.0) +FAST(cert-oop57-cpp, -0.0) +FAST(cert-oop58-cpp, 0.0) +FAST(cert-pos44-c, 2.0) +FAST(cert-pos47-c, 0.0) +FAST(cert-sig30-c, 1.0) +FAST(cert-str34-c, 2.0) +FAST(concurrency-mt-unsafe, 3.0) +FAST(concurrency-thread-canceltype-asynchronous, 1.0) +FAST(cppcoreguidelines-avoid-c-arrays, 0.0) +FAST(cppcoreguidelines-avoid-capturing-lambda-coroutines, -1.0) +FAST(cppcoreguidelines-avoid-const-or-ref-data-members, -2.0) +FAST(cppcoreguidelines-avoid-do-while, -1.0) +FAST(cppcoreguidelines-avoid-goto, -1.0) +FAST(cppcoreguidelines-avoid-magic-numbers, -2.0) +FAST(cppcoreguidelines-avoid-non-const-global-variables, -0.0) +FAST(cppcoreguidelines-avoid-reference-coroutine-parameters, -0.0) +FAST(cppcoreguidelines-c-copy-assignment-signature, 1.0) +FAST(cppcoreguidelines-explicit-virtual-functions, 0.0) +FAST(cppcoreguidelines-init-variables, 1.0) +FAST(cppcoreguidelines-interfaces-global-init, 1.0) +FAST(cppcoreguidelines-macro-to-enum, 0.0) +FAST(cppcoreguidelines-macro-usage, -0.0) +FAST(cppcoreguidelines-misleading-capture-default-by-value, -1.0) +FAST(cppcoreguidelines-missing-std-forward, 0.0) +FAST(cppcoreguidelines-narrowing-conversions, 2.0) +FAST(cppcoreguidelines-no-malloc, -1.0) +FAST(cppcoreguidelines-no-suspend-with-lock, 1.0) +FAST(cppcoreguidelines-noexcept-destructor, -0.0) +FAST(cppcoreguidelines-noexcept-move-operations, 2.0) +FAST(cppcoreguidelines-noexcept-swap, -2.0) +FAST(cppcoreguidelines-non-private-member-variables-in-classes, 1.0) +FAST(cppcoreguidelines-owning-memory, 3.0) +FAST(cppcoreguidelines-prefer-member-initializer, 2.0) +FAST(cppcoreguidelines-pro-bounds-array-to-pointer-decay, 2.0) +FAST(cppcoreguidelines-pro-bounds-constant-array-index, 1.0) +FAST(cppcoreguidelines-pro-bounds-pointer-arithmetic, 0.0) +FAST(cppcoreguidelines-pro-type-const-cast, -1.0) +FAST(cppcoreguidelines-pro-type-cstyle-cast, 2.0) +FAST(cppcoreguidelines-pro-type-member-init, 1.0) +FAST(cppcoreguidelines-pro-type-reinterpret-cast, -1.0) +FAST(cppcoreguidelines-pro-type-static-cast-downcast, 0.0) +FAST(cppcoreguidelines-pro-type-union-access, 0.0) +FAST(cppcoreguidelines-pro-type-vararg, -1.0) +FAST(cppcoreguidelines-rvalue-reference-param-not-moved, 1.0) +FAST(cppcoreguidelines-slicing, 1.0) +FAST(cppcoreguidelines-special-member-functions, -1.0) +FAST(cppcoreguidelines-use-default-member-init, 0.0) +FAST(cppcoreguidelines-virtual-class-destructor, -0.0) +FAST(darwin-avoid-spinlock, 2.0) +FAST(darwin-dispatch-once-nonstatic, 0.0) +FAST(fuchsia-default-arguments-calls, 1.0) +FAST(fuchsia-default-arguments-declarations, -0.0) +FAST(fuchsia-header-anon-namespaces, -0.0) +FAST(fuchsia-multiple-inheritance, 0.0) +FAST(fuchsia-overloaded-operator, 4.0) +FAST(fuchsia-statically-constructed-objects, -0.0) +FAST(fuchsia-trailing-return, 1.0) +FAST(fuchsia-virtual-inheritance, 1.0) +FAST(google-build-explicit-make-pair, 3.0) +FAST(google-build-namespaces, -1.0) +FAST(google-build-using-namespace, -0.0) +FAST(google-default-arguments, 0.0) +FAST(google-explicit-constructor, 2.0) +FAST(google-global-names-in-headers, 0.0) +FAST(google-objc-avoid-nsobject-new, 1.0) +FAST(google-objc-avoid-throwing-exception, -0.0) +FAST(google-objc-function-naming, -1.0) +FAST(google-objc-global-variable-declaration, 0.0) +FAST(google-readability-avoid-underscore-in-googletest-name, 1.0) +FAST(google-readability-braces-around-statements, 0.0) +FAST(google-readability-casting, -0.0) +FAST(google-readability-function-size, 3.0) +FAST(google-readability-namespace-comments, -0.0) +FAST(google-readability-todo, 1.0) +FAST(google-runtime-int, 0.0) +FAST(google-runtime-operator, 0.0) +FAST(google-upgrade-googletest-case, 1.0) +FAST(hicpp-avoid-c-arrays, 1.0) +FAST(hicpp-avoid-goto, -0.0) +FAST(hicpp-braces-around-statements, 0.0) +FAST(hicpp-deprecated-headers, 1.0) +FAST(hicpp-exception-baseclass, -1.0) +FAST(hicpp-explicit-conversions, 1.0) +FAST(hicpp-function-size, 1.0) +FAST(hicpp-ignored-remove-result, 3.0) +FAST(hicpp-invalid-access-moved, 4.0) +FAST(hicpp-member-init, 2.0) +FAST(hicpp-move-const-arg, 3.0) +FAST(hicpp-multiway-paths-covered, 0.0) +FAST(hicpp-named-parameter, 2.0) +FAST(hicpp-new-delete-operators, -0.0) +FAST(hicpp-no-array-decay, 4.0) +FAST(hicpp-no-assembler, 1.0) +FAST(hicpp-no-malloc, 2.0) +FAST(hicpp-noexcept-move, -0.0) +FAST(hicpp-signed-bitwise, -1.0) +FAST(hicpp-special-member-functions, -2.0) +FAST(hicpp-static-assert, 4.0) +FAST(hicpp-undelegated-constructor, 6.0) +FAST(hicpp-uppercase-literal-suffix, 5.0) +FAST(hicpp-use-auto, 0.0) +FAST(hicpp-use-emplace, 3.0) +FAST(hicpp-use-equals-default, 2.0) +FAST(hicpp-use-equals-delete, 1.0) +FAST(hicpp-use-noexcept, -0.0) +FAST(hicpp-use-nullptr, 1.0) +FAST(hicpp-use-override, -1.0) +FAST(hicpp-vararg, 0.0) +FAST(linuxkernel-must-check-errs, -0.0) +FAST(llvm-else-after-return, -1.0) +FAST(llvm-header-guard, 3.0) +FAST(llvm-include-order, 0.0) +FAST(llvm-namespace-comment, 0.0) +FAST(llvm-prefer-isa-or-dyn-cast-in-conditionals, 3.0) +FAST(llvm-prefer-register-over-unsigned, -0.0) +FAST(llvm-qualified-auto, 4.0) +FAST(llvm-twine-local, -0.0) +FAST(llvmlibc-callee-namespace, -0.0) +FAST(llvmlibc-implementation-in-namespace, 1.0) +FAST(llvmlibc-inline-function-decl, 3.0) +FAST(llvmlibc-restrict-system-libc-headers, 0.0) +FAST(misc-confusable-identifiers, -1.0) +SLOW(misc-const-correctness, 67.0) +FAST(misc-coroutine-hostile-raii, 1.0) +FAST(misc-definitions-in-headers, -1.0) +SLOW(misc-header-include-cycle, 10.0) +FAST(misc-include-cleaner, 5.0) +FAST(misc-misleading-bidirectional, 1.0) +FAST(misc-misleading-identifier, 3.0) +FAST(misc-misplaced-const, -2.0) +FAST(misc-new-delete-overloads, 1.0) +FAST(misc-no-recursion, 0.0) +FAST(misc-non-copyable-objects, 0.0) +FAST(misc-non-private-member-variables-in-classes, -1.0) +FAST(misc-redundant-expression, 1.0) +FAST(misc-static-assert, 3.0) +FAST(misc-throw-by-value-catch-by-reference, -0.0) +FAST(misc-unconventional-assign-operator, 1.0) +FAST(misc-uniqueptr-reset-release, 2.0) +FAST(misc-unused-alias-decls, 2.0) +FAST(misc-unused-parameters, 3.0) +FAST(misc-unused-using-decls, 1.0) +FAST(misc-use-anonymous-namespace, 1.0) +FAST(misc-use-internal-linkage, 1.0) +FAST(modernize-avoid-bind, 1.0) +FAST(modernize-avoid-c-arrays, 2.0) +FAST(modernize-concat-nested-namespaces, -0.0) +FAST(modernize-deprecated-headers, 0.0) +FAST(modernize-deprecated-ios-base-aliases, 0.0) +FAST(modernize-loop-convert, 2.0) +FAST(modernize-macro-to-enum, 0.0) +FAST(modernize-make-shared, 2.0) +FAST(modernize-make-unique, 1.0) +FAST(modernize-min-max-use-initializer-list, 1.0) +FAST(modernize-pass-by-value, 0.0) +FAST(modernize-raw-string-literal, 2.0) +FAST(modernize-redundant-void-arg, 1.0) +FAST(modernize-replace-auto-ptr, 0.0) +FAST(modernize-replace-disallow-copy-and-assign-macro, -0.0) +FAST(modernize-replace-random-shuffle, 1.0) +FAST(modernize-return-braced-init-list, 1.0) +FAST(modernize-shrink-to-fit, 1.0) +FAST(modernize-type-traits, 1.0) +FAST(modernize-unary-static-assert, 1.0) +FAST(modernize-use-auto, 0.0) +FAST(modernize-use-bool-literals, 1.0) +FAST(modernize-use-constraints, 1.0) +FAST(modernize-use-default-member-init, -0.0) +FAST(modernize-use-designated-initializers, 1.0) +FAST(modernize-use-emplace, 2.0) +FAST(modernize-use-equals-default, 1.0) +FAST(modernize-use-equals-delete, 2.0) +FAST(modernize-use-nodiscard, -2.0) +FAST(modernize-use-noexcept, -2.0) +FAST(modernize-use-nullptr, 1.0) +FAST(modernize-use-override, 0.0) +FAST(modernize-use-ranges, 0.0) +FAST(modernize-use-starts-ends-with, 0.0) +FAST(modernize-use-std-format, -1.0) +FAST(modernize-use-std-numbers, 0.0) +FAST(modernize-use-std-print, -0.0) +FAST(modernize-use-trailing-return-type, 3.0) +FAST(modernize-use-transparent-functors, 0.0) +FAST(modernize-use-uncaught-exceptions, 0.0) +FAST(modernize-use-using, 1.0) +FAST(objc-assert-equals, 1.0) +FAST(objc-avoid-nserror-init, -0.0) +FAST(objc-dealloc-in-category, 0.0) +FAST(objc-forbidden-subclassing, 2.0) +FAST(objc-missing-hash, -0.0) +FAST(objc-nsdate-formatter, 0.0) +FAST(objc-nsinvocation-argument-lifetime, 0.0) +FAST(objc-property-declaration, -0.0) +FAST(objc-super-self, -2.0) +FAST(openmp-exception-escape, -1.0) +FAST(openmp-use-default-none, 2.0) +FAST(performance-avoid-endl, 2.0) +FAST(performance-enum-size, -1.0) +FAST(performance-faster-string-find, 1.0) +FAST(performance-for-range-copy, 1.0) +FAST(performance-implicit-conversion-in-loop, 0.0) +FAST(performance-inefficient-algorithm, 1.0) +FAST(performance-inefficient-string-concatenation, 1.0) +FAST(performance-inefficient-vector-operation, -0.0) +FAST(performance-move-const-arg, 2.0) +FAST(performance-move-constructor-init, 2.0) +FAST(performance-no-automatic-move, 2.0) +FAST(performance-no-int-to-ptr, 0.0) +FAST(performance-noexcept-destructor, -2.0) +FAST(performance-noexcept-move-constructor, 1.0) +FAST(performance-noexcept-swap, -2.0) +FAST(performance-trivially-destructible, 3.0) +FAST(performance-type-promotion-in-math-fn, 2.0) +FAST(performance-unnecessary-copy-initialization, 2.0) +FAST(performance-unnecessary-value-param, 2.0) +FAST(portability-restrict-system-includes, 1.0) +FAST(portability-simd-intrinsics, 1.0) +FAST(portability-std-allocator-const, 3.0) +FAST(readability-avoid-const-params-in-decls, -0.0) +FAST(readability-avoid-nested-conditional-operator, -1.0) +FAST(readability-avoid-return-with-void-value, 0.0) +FAST(readability-avoid-unconditional-preprocessor-if, -1.0) +FAST(readability-braces-around-statements, 1.0) +FAST(readability-const-return-type, -1.0) +FAST(readability-container-contains, 3.0) +FAST(readability-container-data-pointer, -1.0) +SLOW(readability-container-size-empty, 13.0) +FAST(readability-convert-member-functions-to-static, 4.0) +FAST(readability-delete-null-pointer, -1.0) +FAST(readability-duplicate-include, 2.0) +FAST(readability-else-after-return, 0.0) +FAST(readability-enum-initial-value, 0.0) +FAST(readability-function-cognitive-complexity, 0.0) +FAST(readability-function-size, 0.0) +FAST(readability-identifier-length, 2.0) +FAST(readability-identifier-naming, 1.0) +FAST(readability-implicit-bool-conversion, 3.0) +FAST(readability-inconsistent-declaration-parameter-name, -0.0) +FAST(readability-isolate-declaration, 0.0) +FAST(readability-magic-numbers, 4.0) +FAST(readability-make-member-function-const, 1.0) +FAST(readability-math-missing-parentheses, 1.0) +FAST(readability-misleading-indentation, 1.0) +FAST(readability-misplaced-array-index, 0.0) +FAST(readability-named-parameter, -0.0) +FAST(readability-non-const-parameter, 2.0) +FAST(readability-operators-representation, 0.0) +FAST(readability-qualified-auto, 0.0) +FAST(readability-redundant-access-specifiers, 1.0) +FAST(readability-redundant-casting, -0.0) +FAST(readability-redundant-control-flow, 1.0) +FAST(readability-redundant-declaration, 1.0) +FAST(readability-redundant-function-ptr-dereference, 0.0) +FAST(readability-redundant-inline-specifier, 0.0) +FAST(readability-redundant-member-init, 1.0) +FAST(readability-redundant-preprocessor, -0.0) +FAST(readability-redundant-smartptr-get, 4.0) +FAST(readability-redundant-string-cstr, 1.0) +FAST(readability-redundant-string-init, -0.0) +FAST(readability-reference-to-constructed-temporary, 3.0) +FAST(readability-simplify-boolean-expr, -0.0) +FAST(readability-simplify-subscript-expr, 1.0) +FAST(readability-static-accessed-through-instance, 0.0) +FAST(readability-static-definition-in-anonymous-namespace, 0.0) +FAST(readability-string-compare, -0.0) +FAST(readability-suspicious-call-argument, -1.0) +FAST(readability-uniqueptr-delete-release, 3.0) +FAST(readability-uppercase-literal-suffix, 0.0) +FAST(readability-use-anyofallof, 2.0) +FAST(readability-use-std-min-max, -1.0) +FAST(zircon-temporary-objects, 1.0) + +#undef FAST +#undef SLOW diff --git a/src/Compiler/TidyImpl.h b/src/Compiler/TidyImpl.h new file mode 100644 index 00000000..c95e4255 --- /dev/null +++ b/src/Compiler/TidyImpl.h @@ -0,0 +1,41 @@ +#pragma once + +#include "clang-tidy/ClangTidyModuleRegistry.h" +#include "clang-tidy/ClangTidyOptions.h" +#include "clang-tidy/ClangTidyCheck.h" + +#include "Compiler/Diagnostic.h" +#include "Compiler/Tidy.h" + +namespace clice::tidy { + +using namespace clang::tidy; + +class ClangTidyChecker { + +public: + ClangTidyContext context; + /// Create instances of checks that are enabled for the current Language. + std::vector> checks; + clang::ast_matchers::MatchFinder CTFinder; + + ClangTidyChecker(std::unique_ptr provider); + + clang::DiagnosticsEngine::Level adjustLevel(clang::DiagnosticsEngine::Level DiagLevel, + const clang::Diagnostic& Info); + + void adjustDiag(Diagnostic& Diag); +}; + +} // namespace clice::tidy + +namespace clice { + +inline bool isInsideMainFile(clang::SourceLocation Loc, const clang::SourceManager& SM) { + if(!Loc.isValid()) + return false; + clang::FileID FID = SM.getFileID(SM.getExpansionLoc(Loc)); + return FID == SM.getMainFileID() || FID == SM.getPreambleFileID(); +} + +} // namespace clice diff --git a/src/Compiler/generated/clang-tidy-config.h b/src/Compiler/generated/clang-tidy-config.h new file mode 100644 index 00000000..6f589ed2 --- /dev/null +++ b/src/Compiler/generated/clang-tidy-config.h @@ -0,0 +1,10 @@ +/* This generated file is for internal use. Do not include it from headers. */ + +#ifdef CLANG_TIDY_CONFIG_H +#error clang-tidy-config.h can only be included once +#else +#define CLANG_TIDY_CONFIG_H + +#define CLANG_TIDY_ENABLE_STATIC_ANALYZER 0 + +#endif diff --git a/src/Server/Config.cpp b/src/Server/Config.cpp index 96de7048..0feea351 100644 --- a/src/Server/Config.cpp +++ b/src/Server/Config.cpp @@ -28,6 +28,7 @@ struct Config { ServerOptions server; CacheOptions cache; IndexOptions index; + ClangTidyOptions clang_tidy; std::vector rules; }; @@ -37,6 +38,7 @@ static Config config = {}; const ServerOptions& server = config.server; const CacheOptions& cache = config.cache; const IndexOptions& index = config.index; +const ClangTidyOptions& clang_tidy = config.clang_tidy; llvm::ArrayRef rules = config.rules; template @@ -60,6 +62,14 @@ static void parse(Object& object, auto&& value) { parse(object.back(), item); } } + } else if constexpr(std::is_same_v>) { + if(auto v = value.as_table()) { + for(auto& [key, value]: *v) { + if(auto str_v = value.as_string()) { + object.insert({llvm::StringRef(key), str_v->get()}); + } + } + } } else if constexpr(refl::reflectable_struct) { if(auto table = value.as_table()) { refl::foreach(object, [&](std::string_view key, auto& member) { diff --git a/src/Server/Document.cpp b/src/Server/Document.cpp index edf36c41..835ca202 100644 --- a/src/Server/Document.cpp +++ b/src/Server/Document.cpp @@ -313,6 +313,7 @@ async::Task<> Server::build_ast(std::string path, std::string content) { params.pch = {pch->path, pch->preamble.size()}; file->diagnostics->clear(); params.diagnostics = file->diagnostics; + params.clang_tidy = config::server.clang_tidy; /// Check result auto ast = co_await async::submit([&] { return compile(params); }); @@ -325,12 +326,6 @@ async::Task<> Server::build_ast(std::string path, std::string content) { co_return; } - /// Run Clang-Tidy - if(config::server.clang_tidy) { - log::warn( - "clang-tidy is not fully supported yet. Tracked in https://github.com/clice-project/clice/issues/90."); - } - /// Send diagnostics auto diagnostics = co_await async::submit( [&, kind = this->kind] { return feature::diagnostics(kind, mapping, *ast); }); diff --git a/tests/data/clang_tidy/main.cpp b/tests/data/clang_tidy/main.cpp index ff682901..950316c3 100644 --- a/tests/data/clang_tidy/main.cpp +++ b/tests/data/clang_tidy/main.cpp @@ -1,6 +1,25 @@ #include +// Tests bugprone-integer-division int main() { - std::cout << "Hello World!" << std::endl; + float floatFunc(float); + int intFunc(int); + double d; + int i = 42; + + // Warn, floating-point values expected. + d = 32 * 8 / (2 + i); + d = 8 * floatFunc(1 + 7 / 2); + d = i / (1 << 4); + + // OK, no integer division. + d = 32 * 8.0 / (2 + i); + d = 8 * floatFunc(1 + 7.0 / 2); + d = (double)i / (1 << 4); + + // OK, there are signs of deliberateness. + d = 1 << (i / 2); + d = 9 + intFunc(6 * i / 32); + d = (int)(i / 32) - 8; return 0; } diff --git a/tests/unit/Compiler/Tidy.cpp b/tests/unit/Compiler/Tidy.cpp new file mode 100644 index 00000000..3576f13e --- /dev/null +++ b/tests/unit/Compiler/Tidy.cpp @@ -0,0 +1,21 @@ +#include "Test/Test.h" +#include "Compiler/Tidy.h" + +namespace clice::testing { + +namespace { + +suite<"ClangTidy"> clang_tidy = [] { + test("isFastTidyCheck") = [] { + expect(that % tidy::isFastTidyCheck("readability-misleading-indentation")); + expect(that % tidy::isFastTidyCheck("bugprone-unused-return-value")); + + // clangd/unittests/TidyProviderTests.cpp + expect(that % tidy::isFastTidyCheck("misc-const-correctness") == false); + expect(that % tidy::isFastTidyCheck("bugprone-suspicious-include") == true); + expect(that % tidy::isFastTidyCheck("replay-preamble-check") == std::nullopt); + }; +}; + +} // namespace +} // namespace clice::testing diff --git a/xmake.lua b/xmake.lua index a007a8bb..8b2780df 100644 --- a/xmake.lua +++ b/xmake.lua @@ -51,6 +51,7 @@ target("clice-core") set_kind("$(kind)") add_files("src/**.cpp|Driver/*.cpp") add_includedirs("include", {public = true}) + add_includedirs("src/Compiler/generated", {public = false}) add_packages("libuv", "toml++", {public = true}) @@ -72,6 +73,32 @@ target("clice-core") "clangLex", "clangSema", "clangSerialization", + "clangTidy", + "clangTidyUtils", + -- ALL_CLANG_TIDY_CHECKS + "clangTidyAndroidModule", + "clangTidyAbseilModule", + "clangTidyAlteraModule", + "clangTidyBoostModule", + "clangTidyBugproneModule", + "clangTidyCERTModule", + "clangTidyConcurrencyModule", + "clangTidyCppCoreGuidelinesModule", + "clangTidyDarwinModule", + "clangTidyFuchsiaModule", + "clangTidyGoogleModule", + "clangTidyHICPPModule", + "clangTidyLinuxKernelModule", + "clangTidyLLVMModule", + "clangTidyLLVMLibcModule", + "clangTidyMiscModule", + "clangTidyModernizeModule", + "clangTidyObjCModule", + "clangTidyOpenMPModule", + "clangTidyPerformanceModule", + "clangTidyPortabilityModule", + "clangTidyReadabilityModule", + "clangTidyZirconModule", "clangTooling", "clangToolingCore", "clangToolingInclusions",