From 5c67eb053d1e4a81f8c2d73d04240a0987a9222f Mon Sep 17 00:00:00 2001 From: pgoodman Date: Fri, 17 Apr 2026 22:15:17 -0400 Subject: [PATCH 1/3] Fix fragment hash instability for typedef declarations Skip TypeForDeclaration() type printing for TypedefNameDecls during fragment hashing. The typedef body tokens are already captured in the fragment's token range. The type printer can produce different output across TUs depending on how much type sugar Clang preserved, causing the same typedef to hash differently and create duplicate fragments. Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/Index/Hash.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/bin/Index/Hash.cpp b/bin/Index/Hash.cpp index 8f4ee977d..cd936e0fa 100644 --- a/bin/Index/Hash.cpp +++ b/bin/Index/Hash.cpp @@ -379,8 +379,14 @@ void HashVisitor::VisitDecl(const pasta::Decl &decl) { AccumulateTokenData(ss, pasta::PrintedTokenRange::Create(vd->Type(), pp)); } else if (auto td = pasta::TypeDecl::From(decl)) { - if (auto ty = td->TypeForDeclaration()) { - AccumulateTokenData(ss, pasta::PrintedTokenRange::Create(ty.value(), pp)); + // Skip TypeForDeclaration() for TypedefNameDecls: the typedef body tokens + // are already in the fragment's token range, and the type printer can + // produce different output across TUs depending on how much type sugar + // Clang preserved, causing hash instability. + if (!pasta::TypedefNameDecl::From(decl)) { + if (auto ty = td->TypeForDeclaration()) { + AccumulateTokenData(ss, pasta::PrintedTokenRange::Create(ty.value(), pp)); + } } } @@ -403,10 +409,6 @@ static std::string HashNestedFragment( HashVisitor visitor(ss, em, true); visitor.Accept(decl); - // std::cerr << "\n-----------------\n"; - // std::cerr << ss.str() << '\n'; - // Dump(decl); - return ss.str(); } From d18e707d1759255c3b077430b80d182acd739132 Mon Sep 17 00:00:00 2001 From: pgoodman Date: Sun, 19 Apr 2026 17:00:31 -0400 Subject: [PATCH 2/3] Add configurable argument filter for mx-index compiler flag stripping Replace the hardcoded if/else chain in GetCompilerInfo() with a data-driven argument filter loaded from share/multiplier/unsupported_args.cfg. The config file uses a simple text format (exact/prefix/contains match types with optional following-argument counts) and is installed alongside the mx-index binary. At startup, mx-index locates the default config relative to its own executable path (/share/multiplier/unsupported_args.cfg) via llvm::sys::fs::getMainExecutable. Users can supply additional patterns with --extra_arg_patterns to augment the defaults. The default config includes all previously hardcoded patterns plus comprehensive coverage of GCC-specific flags not supported by Clang (--param, -fplugin, -fipa-*, -mrecord-mcount, kernel-common x86 flags, etc.) and flags removed from recent Clang versions (pass manager flags, -fmodules-ts, -fcoroutines-ts, etc.). Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/Index/ArgumentFilter.cpp | 116 ++++++++++ bin/Index/ArgumentFilter.h | 63 ++++++ bin/Index/CMakeLists.txt | 29 +++ bin/Index/Importer.cpp | 112 ++++------ bin/Index/Importer.h | 4 +- bin/Index/Main.cpp | 53 ++++- share/multiplier/unsupported_args.cfg | 298 ++++++++++++++++++++++++++ 7 files changed, 598 insertions(+), 77 deletions(-) create mode 100644 bin/Index/ArgumentFilter.cpp create mode 100644 bin/Index/ArgumentFilter.h create mode 100644 share/multiplier/unsupported_args.cfg diff --git a/bin/Index/ArgumentFilter.cpp b/bin/Index/ArgumentFilter.cpp new file mode 100644 index 000000000..22aec2394 --- /dev/null +++ b/bin/Index/ArgumentFilter.cpp @@ -0,0 +1,116 @@ +// Copyright (c) 2022-present, Trail of Bits, Inc. +// +// This source code is licensed in accordance with the terms specified in +// the LICENSE file found in the root directory of this source tree. + +#include "ArgumentFilter.h" + +#include +#include +#include + +namespace indexer { + +std::optional +ArgumentFilter::LoadFromFile(const std::filesystem::path &path) { + std::ifstream file(path); + if (!file.is_open()) { + return "Could not open argument filter config: " + path.string(); + } + + std::string line; + unsigned line_num = 0; + + while (std::getline(file, line)) { + ++line_num; + + // Strip leading whitespace. + auto start = line.find_first_not_of(" \t"); + if (start == std::string::npos) { + continue; // Empty line. + } + + // Skip comments. + if (line[start] == '#') { + continue; + } + + // Parse: [] + std::istringstream ss(line.substr(start)); + std::string match_str; + std::string pattern; + std::string num_str; + + ss >> match_str >> pattern; + if (match_str.empty() || pattern.empty()) { + return path.string() + ":" + std::to_string(line_num) + + ": expected ' []'"; + } + + MatchType match; + if (match_str == "exact") { + match = MatchType::kExact; + } else if (match_str == "prefix") { + match = MatchType::kPrefix; + } else if (match_str == "contains") { + match = MatchType::kContains; + } else { + return path.string() + ":" + std::to_string(line_num) + + ": unknown match type '" + match_str + + "'; expected 'exact', 'prefix', or 'contains'"; + } + + int num_following = 0; + ss >> num_str; + if (!num_str.empty()) { + bool lenient = false; + std::string_view digits = num_str; + + // ~N means lenient: skip following args only if they don't look like + // flags (don't start with '-'). + if (digits.front() == '~') { + lenient = true; + digits.remove_prefix(1); + } + + auto [ptr, ec] = std::from_chars( + digits.data(), digits.data() + digits.size(), num_following); + if (ec != std::errc{} || ptr != digits.data() + digits.size()) { + return path.string() + ":" + std::to_string(line_num) + + ": invalid num_values '" + num_str + "'"; + } + + if (lenient) { + num_following = -num_following; + } + } + + rules.push_back({match, std::move(pattern), num_following}); + } + + return std::nullopt; +} + +std::optional +ArgumentFilter::ShouldSkip(std::string_view arg) const { + for (const Rule &rule : rules) { + bool matched = false; + switch (rule.match) { + case MatchType::kExact: + matched = (arg == rule.pattern); + break; + case MatchType::kPrefix: + matched = arg.starts_with(rule.pattern); + break; + case MatchType::kContains: + matched = (arg.find(rule.pattern) != std::string_view::npos); + break; + } + if (matched) { + return rule.num_following; + } + } + return std::nullopt; +} + +} // namespace indexer diff --git a/bin/Index/ArgumentFilter.h b/bin/Index/ArgumentFilter.h new file mode 100644 index 000000000..492c91fc5 --- /dev/null +++ b/bin/Index/ArgumentFilter.h @@ -0,0 +1,63 @@ +// Copyright (c) 2022-present, Trail of Bits, Inc. +// +// This source code is licensed in accordance with the terms specified in +// the LICENSE file found in the root directory of this source tree. + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace indexer { + +// Filters compiler arguments based on pattern rules loaded from a config file. +// +// Each rule specifies a match type (exact, prefix, or contains), a pattern +// string, and how many following arguments to also skip. Rules are checked in +// order; the first match wins. +class ArgumentFilter { + public: + ArgumentFilter(void) = default; + + // Load rules from a config file, appending them to any existing rules. + // Returns an error message on failure, or std::nullopt on success. + std::optional LoadFromFile(const std::filesystem::path &path); + + // Check if an argument should be skipped. Returns the number of following + // arguments to also skip (0 means skip only this argument), or std::nullopt + // if the argument should be kept. + // + // When the returned value is negative, its absolute value is the number of + // following arguments to skip, but only if they don't start with '-'. This + // handles malformed compilation databases where a "value" slot actually + // contains the next flag. + std::optional ShouldSkip(std::string_view arg) const; + + // Returns true if no rules have been loaded. + bool Empty(void) const { return rules.empty(); } + + private: + enum class MatchType : uint8_t { + kExact, + kPrefix, + kContains, + }; + + struct Rule { + MatchType match; + std::string pattern; + + // Positive: unconditionally skip this many following args. + // Negative: skip |n| following args only if they don't start with '-'. + // Zero: skip only the matched argument itself. + int num_following; + }; + + std::vector rules; +}; + +} // namespace indexer diff --git a/bin/Index/CMakeLists.txt b/bin/Index/CMakeLists.txt index 83ed3048b..64f3ec23c 100644 --- a/bin/Index/CMakeLists.txt +++ b/bin/Index/CMakeLists.txt @@ -10,6 +10,8 @@ set(exe_name "mx-index") add_executable("${exe_name}" "Action.cpp" "Action.h" + "ArgumentFilter.cpp" + "ArgumentFilter.h" "BuildPendingFragment.cpp" "Context.cpp" "Context.h" @@ -96,6 +98,11 @@ target_include_directories("${exe_name}" "$" ) +target_compile_definitions("${exe_name}" + PRIVATE + "MX_SHARE_DIR=\"${MX_INSTALL_SHARE_DIR}\"" +) + target_compile_options("${exe_name}" PRIVATE "-Wno-unknown-warning-option" @@ -122,6 +129,21 @@ set_target_properties("${exe_name}" find_and_link_llvm_dependencies("${exe_name}") +# Copy the default argument filter config into the build tree so that mx-index +# can find it when run from the build directory (the binary sits in +# //, and it looks for ..//multiplier/). +set(MX_ARG_FILTER_SRC "${PROJECT_SOURCE_DIR}/share/multiplier/unsupported_args.cfg") +set(MX_ARG_FILTER_DST "${PROJECT_BINARY_DIR}/${MX_INSTALL_SHARE_DIR}/multiplier/unsupported_args.cfg") +add_custom_command( + OUTPUT "${MX_ARG_FILTER_DST}" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${MX_ARG_FILTER_SRC}" "${MX_ARG_FILTER_DST}" + DEPENDS "${MX_ARG_FILTER_SRC}" + COMMENT "Copying unsupported_args.cfg to build tree" +) +add_custom_target("${exe_name}-arg-filter" ALL DEPENDS "${MX_ARG_FILTER_DST}") +add_dependencies("${exe_name}" "${exe_name}-arg-filter") + if(MX_ENABLE_INSTALL AND NOT MX_ENABLE_BOOTSTRAP) install( TARGETS @@ -132,4 +154,11 @@ if(MX_ENABLE_INSTALL AND NOT MX_ENABLE_BOOTSTRAP) DESTINATION "${CMAKE_INSTALL_BINDIR}" ) + + install( + FILES + "${MX_ARG_FILTER_SRC}" + DESTINATION + "${MX_INSTALL_SHARE_DIR}/multiplier" + ) endif() diff --git a/bin/Index/Importer.cpp b/bin/Index/Importer.cpp index 25fdbe5a0..7a4feff6d 100644 --- a/bin/Index/Importer.cpp +++ b/bin/Index/Importer.cpp @@ -46,6 +46,7 @@ #pragma clang diagnostic pop #include "Action.h" +#include "ArgumentFilter.h" #include "Context.h" #include "Executor.h" #include "IndexCompileJob.h" @@ -208,6 +209,7 @@ class BuildCommandAction final : public Action { std::shared_ptr fs; const Command &command; GlobalIndexingState &ctx; + const ArgumentFilter &arg_filter; void RunWithCompiler(pasta::CompileCommand cmd, pasta::Compiler cc); @@ -219,11 +221,13 @@ class BuildCommandAction final : public Action { virtual ~BuildCommandAction(void) = default; inline BuildCommandAction(pasta::FileManager &fm_, const Command &command_, - GlobalIndexingState &ctx_) + GlobalIndexingState &ctx_, + const ArgumentFilter &arg_filter_) : fm(fm_), fs(fm.FileSystem()), command(command_), - ctx(ctx_) {} + ctx(ctx_), + arg_filter(arg_filter_) {} void Run(void) final; }; @@ -233,13 +237,16 @@ class BuildCommandAction final : public Action { std::variant BuildCommandAction::GetCompilerInfo(void) { std::vector new_args; - bool skip = false; - bool skip_internal_option = false; bool has_output = false; bool next_is_language = false; std::string_view inferred_lang = "c"; std::string_view specified_lang = "c"; bool specifies_language = false; + + // Number of following arguments to skip. Positive means unconditional; + // negative means skip only if the argument doesn't look like a flag. + int skip_following = 0; + for (const char *arg_ : command.vec) { std::string_view arg(arg_); @@ -258,16 +265,22 @@ BuildCommandAction::GetCompilerInfo(void) { continue; } - if (skip) { - skip = false; + // Handle skipping of following arguments from a previous match. + if (skip_following > 0) { + --skip_following; + continue; + } else if (skip_following < 0) { + ++skip_following; // NOTE(pag): Have observed things like the following in the Linux kernel: // // ... -main-file-name -mrelocation-model ... // + // In lenient mode, only skip if the argument doesn't look like a flag. if (arg.front() != '-' && 1u < arg.size()) { continue; } + // Looks like a flag; fall through to process it normally. } // Try to detect C++ code. @@ -298,7 +311,7 @@ BuildCommandAction::GetCompilerInfo(void) { } else if (arg_str.ends_with(".s")) { inferred_lang = "asm"; - + } else if (arg_str.ends_with(".ll") || arg_str.ends_with(".ir")) { inferred_lang = "ir"; @@ -308,71 +321,16 @@ BuildCommandAction::GetCompilerInfo(void) { } } - if (skip_internal_option) { - skip_internal_option = false; + // Check the argument filter for patterns to elide. + if (auto n = arg_filter.ShouldSkip(arg)) { + skip_following = *n; continue; } - // Drop things like `-Wall`, `-Werror, `-fsanitize=..`, etc. - if (arg.starts_with("-W") || - arg.starts_with("-pedantic") || - arg.starts_with("-ftrivial-auto-var-init=") || - arg.starts_with("-fpatchable-function-entry=") || - arg.starts_with("-fpatchable-function-entry-offset=") || - arg.starts_with("-fstrict-flex-arrays=") || - arg.starts_with("-mfunction-return=") || - arg.starts_with("-fsanitize=") || - arg.starts_with("-fcoverage-compilation-dir=") || - arg.starts_with("-fcrash-diagnostics-dir=") || - arg == "-fskip-odr-check-in-gmf" || - arg == "-pic-is-pie" || - arg == "-mindirect-branch-cs-prefix" || - arg == "-Wno-cast-function-type-strict" || - arg == "-Wno-c++11-narrowing-const-reference" || - arg == "-Wno-thread-safety-reference-return") { - continue; // Skip the argument. - - // Keep the argument. - } else if (arg.starts_with("-Wno-")) { - - // Drop these, and the following argument. - } else if (arg == "-Xclang" || arg == "-mllvm") { - skip_internal_option = true; - continue; - - // Drop these, and the following argument. - } else if (arg == "-dependency-file" || - arg == "-diagnostic-log-file" || - arg == "-header-include-file" || - arg == "-stack-usage-file" || - arg == "-mrelocation-model" || - arg == "-pic-level" || - arg == "-main-file-name" || - arg == "-MT" || - arg == "-MQ") { - skip = true; - continue; - - // If it specifies some file, e.g. `-frandomize-layout-seed-file=...` or - // `-fprofile-remapping-file=`, or ..., then drop it. - } else if (strstr(arg_, "-file=") /* NOTE(pag): find anywhere */ || - arg.starts_with("-dependent-lib=") || - arg.starts_with("-stats-file=") || - arg.starts_with("-fprofile-list=") || - arg.starts_with("-fxray-always-instrument=") || - arg.starts_with("-fxray-never-instrument=") || - arg.starts_with("-fxray-attr-list=") || - arg.starts_with("-tsan-compound-read-before-write=") || - arg.starts_with("-tsan-distinguish-volatile=") || - arg.starts_with("-treat") || - arg.starts_with("-split-threshold-for-reg-with-hint=") || - arg.starts_with("-instcombine-lower-dbg-declare=")) { - continue; - // Output file, `-o `, `--output `. - } else if (arg == "-o" || arg == "--output") { + if (arg == "-o" || arg == "--output") { has_output = true; - skip = true; + skip_following = 1; continue; // `--output=` @@ -410,8 +368,9 @@ BuildCommandAction::GetCompilerInfo(void) { continue; // Something like `"-DFOO=bar"` or `'-DFOO=bar'`. - } else if ((arg.front() == '\'' || arg.front() == '"') && arg[1] == '-' && - arg.back() == arg.front()) { + } else if (arg.size() >= 3 && + (arg.front() == '\'' || arg.front() == '"') && + arg[1] == '-' && arg.back() == arg.front()) { continue; } @@ -730,22 +689,26 @@ struct Importer::PrivateData { std::filesystem::path cwd; pasta::FileManager fm; GlobalIndexingState &ctx; + const ArgumentFilter &arg_filter; inline PrivateData(std::filesystem::path cwd_, const pasta::FileManager &fm_, - GlobalIndexingState &ctx_) + GlobalIndexingState &ctx_, + const ArgumentFilter &arg_filter_) : cwd(std::move(cwd_)), fm(fm_), - ctx(ctx_) {} + ctx(ctx_), + arg_filter(arg_filter_) {} }; Importer::~Importer(void) {} Importer::Importer(std::filesystem::path cwd_, const pasta::FileManager &fm, - GlobalIndexingState &ctx) + GlobalIndexingState &ctx, + const ArgumentFilter &arg_filter) : d(std::make_unique( - std::move(cwd_), fm, ctx)) {} + std::move(cwd_), fm, ctx, arg_filter)) {} bool Importer::ImportBlightCompileCommand(llvm::json::Object &o) { ProgressBarWork progress_tracker(d->ctx.command_progress); @@ -986,7 +949,8 @@ void Importer::Import(const ExecutorOptions &options) { } for (const Command &cmd : commands) { - per_path_exe.EmplaceAction(d->fm, cmd, d->ctx); + per_path_exe.EmplaceAction(d->fm, cmd, d->ctx, + d->arg_filter); } per_path_exe.Start(); diff --git a/bin/Index/Importer.h b/bin/Index/Importer.h index b998eb772..8f6228fc6 100644 --- a/bin/Index/Importer.h +++ b/bin/Index/Importer.h @@ -18,6 +18,7 @@ class FileManager; } namespace indexer { +class ArgumentFilter; class GlobalIndexingState; class EnvVariableMap : public std::unordered_map {}; struct ExecutorOptions; @@ -35,7 +36,8 @@ class Importer { explicit Importer(std::filesystem::path cwd_, const pasta::FileManager &fm, - GlobalIndexingState &context); + GlobalIndexingState &context, + const ArgumentFilter &arg_filter); bool ImportBlightCompileCommand(llvm::json::Object &o); bool ImportCMakeCompileCommand(llvm::json::Object &o, diff --git a/bin/Index/Main.cpp b/bin/Index/Main.cpp index 64f04198f..2bb494c3e 100644 --- a/bin/Index/Main.cpp +++ b/bin/Index/Main.cpp @@ -28,12 +28,13 @@ #pragma clang diagnostic ignored "-Wshadow" #pragma clang diagnostic ignored "-Wcast-align" #include -//#include +#include #include #include #include #pragma clang diagnostic pop +#include "ArgumentFilter.h" #include "Context.h" #include "IdStore.h" #include "Importer.h" @@ -73,8 +74,16 @@ DEFINE_string(workspace, "mx-workspace", DEFINE_bool(fork_mode, false, "Use --fork_mode if running inside docker"); DEFINE_bool(reproc_mode, false, "Use --reproc_mode to use reproc library"); +DEFINE_string(extra_arg_patterns, "", + "Path to an additional argument filter config file. Patterns in " + "this file augment the default unsupported_args.cfg that ships " + "with mx-index. See that file for the format."); + namespace { +// Used as an address anchor for llvm::sys::fs::getMainExecutable. +static int gExeAnchor = 0; + std::unique_ptr ReadFileBuffer(const std::string file_name) { if (!file_name.empty()) { @@ -165,6 +174,7 @@ int main(int argc, char *argv[], char *envp[]) { << " [--num_indexer_workers N]\n" << " [--num_command_workers N]\n" << " [--env PATH_TO_COPIED_ENV_VARS]\n" + << " [--extra_arg_patterns PATH_TO_EXTRA_PATTERNS]\n" << " [--show_progress]\n" << " --fork_mode\n" << " --reproc_mode\n" @@ -280,8 +290,47 @@ int main(int argc, char *argv[], char *envp[]) { return EXIT_FAILURE; } + // Load the argument filter from the default config file that ships alongside + // the mx-index binary, then optionally augment with a user-provided file. + indexer::ArgumentFilter arg_filter; + + { + // Locate the default config relative to the executable: + // /bin/mx-index -> //multiplier/unsupported_args.cfg + auto exe_path = llvm::sys::fs::getMainExecutable( + argv[0], &gExeAnchor); + if (!exe_path.empty()) { + auto default_cfg = std::filesystem::path(exe_path).parent_path() + / ".." / MX_SHARE_DIR / "multiplier" + / "unsupported_args.cfg"; + std::error_code ec; + default_cfg = std::filesystem::canonical(default_cfg, ec); + if (!ec) { + if (auto err = arg_filter.LoadFromFile(default_cfg)) { + LOG(WARNING) << *err; + } + } else { + LOG(WARNING) + << "Could not find default argument filter config at " + << default_cfg.string() << ": " << ec.message(); + } + } + + if (!FLAGS_extra_arg_patterns.empty()) { + if (auto err = arg_filter.LoadFromFile(FLAGS_extra_arg_patterns)) { + std::cerr << *err << "\n"; + return EXIT_FAILURE; + } + } + + if (arg_filter.Empty()) { + LOG(WARNING) << "No argument filter patterns loaded; unsupported " + "compiler arguments will not be stripped"; + } + } + llvm::LLVMContext llvm_context; - indexer::Importer importer(path.parent_path(), fm, context); + indexer::Importer importer(path.parent_path(), fm, context, arg_filter); // Parse the target, be it a compile commands JSON database or a binary // with embedded commands. diff --git a/share/multiplier/unsupported_args.cfg b/share/multiplier/unsupported_args.cfg new file mode 100644 index 000000000..1afa9be9a --- /dev/null +++ b/share/multiplier/unsupported_args.cfg @@ -0,0 +1,298 @@ +# Compiler argument patterns for mx-index to elide from compilation commands. +# +# When mx-index re-invokes the original compiler to discover system include +# paths and version information, arguments matching these patterns are stripped +# from the command line. This prevents the compiler invocation from failing due +# to unsupported or irrelevant flags. +# +# Format: +# +# [] +# +# match: +# exact - argument must equal exactly +# prefix - argument must start with +# contains - argument must contain as a substring +# +# num_values (how many subsequent arguments to also skip): +# (omitted or 0) - only skip the matched argument itself +# N - also skip the next N arguments unconditionally +# ~N - also skip the next N arguments, but only if they don't +# start with '-'. This handles malformed compilation +# databases (e.g. the Linux kernel) where a "value" may +# actually be the next flag: +# -main-file-name -mrelocation-model ... +# +# Lines starting with '#' are comments. Empty lines are ignored. +# The first matching rule wins; subsequent rules are not checked. +# +# You can provide additional patterns via --extra_arg_patterns . + +# --------------------------------------------------------------------------- +# Warning and diagnostic flags +# --------------------------------------------------------------------------- + +prefix -W +prefix -pedantic + +# --------------------------------------------------------------------------- +# Sanitizer and coverage flags +# --------------------------------------------------------------------------- + +prefix -fsanitize= +prefix -fcoverage-compilation-dir= +prefix -fcrash-diagnostics-dir= + +# --------------------------------------------------------------------------- +# Hardening / instrumentation flags +# --------------------------------------------------------------------------- + +prefix -ftrivial-auto-var-init= +prefix -fpatchable-function-entry= +prefix -fpatchable-function-entry-offset= +prefix -fstrict-flex-arrays= +prefix -mfunction-return= +exact -fskip-odr-check-in-gmf +exact -pic-is-pie +exact -mindirect-branch-cs-prefix + +# --------------------------------------------------------------------------- +# Internal compiler options (flag + one value) +# --------------------------------------------------------------------------- +# These always take exactly one following argument, even if that argument +# looks like a flag (e.g. -Xclang -emit-llvm). + +exact -Xclang 1 +exact -mllvm 1 + +# --------------------------------------------------------------------------- +# Internal compiler options (flag + one value, lenient) +# --------------------------------------------------------------------------- +# These expect one following argument, but in some compilation databases the +# "value" is actually the next flag. The ~1 modifier skips the following +# argument only when it does not start with '-'. + +exact -dependency-file ~1 +exact -diagnostic-log-file ~1 +exact -header-include-file ~1 +exact -stack-usage-file ~1 +exact -mrelocation-model ~1 +exact -pic-level ~1 +exact -main-file-name ~1 +exact -MT ~1 +exact -MQ ~1 + +# --------------------------------------------------------------------------- +# File-path options (matched by substring) +# --------------------------------------------------------------------------- +# Catches a broad range of -f*-file=, -f*-seed-file=, etc. + +contains -file= + +# --------------------------------------------------------------------------- +# Remaining options with embedded values (prefix=value form) +# --------------------------------------------------------------------------- + +prefix -dependent-lib= +prefix -stats-file= +prefix -fprofile-list= +prefix -fxray-always-instrument= +prefix -fxray-never-instrument= +prefix -fxray-attr-list= +prefix -tsan-compound-read-before-write= +prefix -tsan-distinguish-volatile= +prefix -treat +prefix -split-threshold-for-reg-with-hint= +prefix -instcombine-lower-dbg-declare= + +# =========================================================================== +# +# GCC-specific flags not recognized by Clang +# +# These appear in compile_commands.json from GCC-built projects (e.g. the +# Linux kernel) and cause "unknown argument" errors in Clang. +# +# =========================================================================== + +# --------------------------------------------------------------------------- +# GCC --param tuning knobs (entire family is GCC-only) +# --------------------------------------------------------------------------- +# --param=name=value or --param name=value + +prefix --param= +exact --param 1 + +# --------------------------------------------------------------------------- +# GCC spec files (entire family is GCC-only) +# --------------------------------------------------------------------------- + +prefix -specs= +exact -specs 1 +prefix --specs= +exact --specs 1 + +# --------------------------------------------------------------------------- +# GCC plugin system (incompatible with Clang's plugin system) +# --------------------------------------------------------------------------- + +prefix -fplugin= +prefix -fplugin-arg- + +# --------------------------------------------------------------------------- +# GCC interprocedural analysis passes (entire family is GCC-only) +# --------------------------------------------------------------------------- + +prefix -fipa- +prefix -fno-ipa- + +# --------------------------------------------------------------------------- +# GCC dump / optimization-info flags (entire family is GCC-only) +# --------------------------------------------------------------------------- + +prefix -fdump-rtl- +prefix -fdump-tree- +prefix -fdump-ipa- +prefix -fopt-info + +# --------------------------------------------------------------------------- +# GCC optimization flags with no Clang equivalent +# --------------------------------------------------------------------------- +# NOTE: -ftree-vectorize and -ftree-slp-vectorize ARE valid Clang aliases +# (for -fvectorize and -fslp-vectorize), so we cannot blanket-strip -ftree-*. + +exact -fno-var-tracking +exact -fno-var-tracking-assignments +exact -fconserve-stack +exact -fno-allow-store-data-races +exact -fallow-store-data-races +exact -fno-code-hoisting +exact -fno-tree-loop-im +exact -fno-tree-loop-ivcanon +exact -fno-tree-loop-distribute-patterns +exact -fno-tree-loop-distribution +exact -fno-tree-switch-conversion +exact -fno-tree-tail-merge +exact -fno-tree-pre +exact -fno-tree-dse +exact -fno-partial-inlining +exact -fno-inline-functions-called-once +exact -fno-hoist-adjacent-loads +exact -fno-guess-branch-probability +exact -fno-tracer +exact -fno-caller-saves +exact -fno-defer-pop +exact -fno-function-cse +exact -fno-peephole2 +exact -fno-gcse +exact -fno-gcse-lm +exact -fno-gcse-sm +exact -fno-reorder-blocks +exact -fno-reorder-blocks-and-partition +exact -fno-reorder-functions +exact -fno-toplevel-reorder +exact -fno-unit-at-a-time +exact -fno-live-range-shrinkage +exact -fno-devirtualize-speculatively +exact -fno-enforce-eh-specs +exact -fweb +exact -fno-web +exact -frename-registers +exact -fno-rename-registers +exact -fno-strength-reduce + +# GCC Graphite (polyhedral) loop optimization flags. +exact -fgraphite +exact -fgraphite-identity +exact -floop-nest-optimize +exact -floop-parallelize-all + +# --------------------------------------------------------------------------- +# GCC scheduler flags (no Clang equivalent) +# --------------------------------------------------------------------------- + +exact -fno-schedule-insns +exact -fno-schedule-insns2 +exact -fno-sched-pressure +exact -fsched-pressure +exact -fsched2-use-superblocks +exact -fsched-stalled-insns +prefix -fsched-stalled-insns= +exact -fsched-stalled-insns-dep +prefix -fsched-stalled-insns-dep= + +# --------------------------------------------------------------------------- +# GCC alignment flags (extended syntax not supported by Clang) +# --------------------------------------------------------------------------- +# NOTE: -falign-functions[=N] and -falign-loops=N ARE functional in Clang, +# so we only strip the forms that Clang does not support. + +prefix -falign-jumps +prefix -falign-labels + +# --------------------------------------------------------------------------- +# GCC LTO flags with no Clang equivalent +# --------------------------------------------------------------------------- + +prefix -flto-partition= +exact -fuse-linker-plugin +exact -fno-use-linker-plugin + +# --------------------------------------------------------------------------- +# GCC profile flags with no Clang equivalent +# --------------------------------------------------------------------------- + +exact -fprofile-correction +prefix -fprofile-dir= + +# --------------------------------------------------------------------------- +# GCC-specific machine flags (x86) +# --------------------------------------------------------------------------- +# These are common in Linux kernel compile_commands.json. + +exact -mrecord-mcount +exact -mnop-mcount +exact -mfentry +exact -mno-fp-ret-in-387 +exact -mno-80387 +exact -maccumulate-outgoing-args +exact -mskip-rax-setup +prefix -mpreferred-stack-boundary= +exact -mno-avx256-split-unaligned-load +exact -mno-avx256-split-unaligned-store +exact -mindirect-branch-register +prefix -mindirect-branch= +prefix -mharden-sls= + +# --------------------------------------------------------------------------- +# GCC diagnostic generation flags +# --------------------------------------------------------------------------- + +exact -fdiagnostics-generate-patch +prefix -ftrack-macro-expansion + +# =========================================================================== +# +# Removed or deprecated Clang flags +# +# These were accepted by older Clang versions but cause errors in newer ones. +# +# =========================================================================== + +# Removed in Clang 16 (old/new pass manager flags). +exact -fexperimental-new-pass-manager +exact -fno-experimental-new-pass-manager +exact -flegacy-pass-manager +exact -fno-legacy-pass-manager +exact -analyzer-store ~1 +exact -analyzer-opt-analyze-nested-blocks + +# Removed in Clang 17. +exact -fmodules-ts +exact -fcoroutines-ts + +# Removed in Clang 18. +exact -enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-from-clang + +# Removed in Clang 21. +exact -frelaxed-template-template-args +exact -fno-relaxed-template-template-args From b53aefd81d0b30ac65aee2bc404465bb5cca37b0 Mon Sep 17 00:00:00 2001 From: pgoodman Date: Sun, 19 Apr 2026 17:23:27 -0400 Subject: [PATCH 3/3] Fix dependency cycle when build and source trees overlap Guard the unsupported_args.cfg copy command with a path comparison so that it is skipped when PROJECT_BINARY_DIR == PROJECT_SOURCE_DIR (or the resolved paths otherwise collide). This avoids the ninja error "dependency cycle: share/multiplier/unsupported_args.cfg -> ..." in CI builds that reuse the source tree as the build tree. Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/Index/CMakeLists.txt | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/bin/Index/CMakeLists.txt b/bin/Index/CMakeLists.txt index 64f3ec23c..2c0e9367c 100644 --- a/bin/Index/CMakeLists.txt +++ b/bin/Index/CMakeLists.txt @@ -134,15 +134,21 @@ find_and_link_llvm_dependencies("${exe_name}") # //, and it looks for ..//multiplier/). set(MX_ARG_FILTER_SRC "${PROJECT_SOURCE_DIR}/share/multiplier/unsupported_args.cfg") set(MX_ARG_FILTER_DST "${PROJECT_BINARY_DIR}/${MX_INSTALL_SHARE_DIR}/multiplier/unsupported_args.cfg") -add_custom_command( - OUTPUT "${MX_ARG_FILTER_DST}" - COMMAND "${CMAKE_COMMAND}" -E copy_if_different - "${MX_ARG_FILTER_SRC}" "${MX_ARG_FILTER_DST}" - DEPENDS "${MX_ARG_FILTER_SRC}" - COMMENT "Copying unsupported_args.cfg to build tree" -) -add_custom_target("${exe_name}-arg-filter" ALL DEPENDS "${MX_ARG_FILTER_DST}") -add_dependencies("${exe_name}" "${exe_name}-arg-filter") + +# Skip the copy when source and build trees overlap (avoids a dependency cycle). +cmake_path(ABSOLUTE_PATH MX_ARG_FILTER_SRC NORMALIZE OUTPUT_VARIABLE _src_abs) +cmake_path(ABSOLUTE_PATH MX_ARG_FILTER_DST NORMALIZE OUTPUT_VARIABLE _dst_abs) +if(NOT _src_abs STREQUAL _dst_abs) + add_custom_command( + OUTPUT "${MX_ARG_FILTER_DST}" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${MX_ARG_FILTER_SRC}" "${MX_ARG_FILTER_DST}" + DEPENDS "${MX_ARG_FILTER_SRC}" + COMMENT "Copying unsupported_args.cfg to build tree" + ) + add_custom_target("${exe_name}-arg-filter" ALL DEPENDS "${MX_ARG_FILTER_DST}") + add_dependencies("${exe_name}" "${exe_name}-arg-filter") +endif() if(MX_ENABLE_INSTALL AND NOT MX_ENABLE_BOOTSTRAP) install(