project('fold', ['cpp'], version: '0.1.0', default_options: [ 'buildtype=debugoptimized', 'b_ndebug=if-release', 'warning_level=2', 'cpp_std=c++23', 'cpp_args=-march=native', ], ) cpp = meson.get_compiler('cpp') fold_git_revision = 'unknown' fold_git_dirty = 0 git_program = find_program('git', required: false) if git_program.found() revision_result = run_command( git_program, 'rev-parse', '--short=12', 'HEAD', check: false, ) if revision_result.returncode() == 0 fold_git_revision = revision_result.stdout().strip() endif status_result = run_command( git_program, 'status', '--porcelain', '--untracked-files=normal', check: false, ) if status_result.returncode() == 0 and status_result.stdout().strip() != '' fold_git_dirty = 1 endif endif add_project_arguments( '-DFOLD_PROJECT_VERSION="' + meson.project_version() + '"', '-DFOLD_GIT_REVISION="' + fold_git_revision + '"', '-DFOLD_GIT_DIRTY=' + fold_git_dirty.to_string(), language: 'cpp', ) _fold_include_dir = join_paths(meson.current_source_dir(), 'include') add_project_arguments('-I' + _fold_include_dir, language: 'cpp') if get_option('build-tests') or get_option('enable-dcheck') add_project_arguments('-DFOLD_ENABLE_DCHECK=1', language: 'cpp') message('Fold debug checks: enabled') endif if host_machine.system() == 'darwin' add_global_link_arguments('-Wl,-no_warn_duplicate_libraries', language: 'cpp') endif # --- lightweight deps via Conan-generated pkg-config --- spdlog_dep = dependency('spdlog') cli11_dep = dependency('CLI11') json_dep = dependency('nlohmann_json') sqlite3_dep = dependency('sqlite3') # --- xxhash (Conan pkg-config name: libxxhash) --- # Activates the XXH3 path in src/ir/normalize.cpp (FOLD_HAVE_XXHASH). # Used header-only via XXH_INLINE_ALL, so we only need the include path, # but link the dep anyway so future non-inline usage keeps working. xxhash_dep = dependency('libxxhash', required: false) if not xxhash_dep.found() xxhash_dep = dependency('xxhash', required: false) endif if xxhash_dep.found() add_project_arguments('-DFOLD_HAVE_XXHASH=1', language: 'cpp') message('xxhash: XXH3 hashing enabled') else # normalize.cpp falls back to scalar FNV-1a. warning('xxhash not found - falling back to scalar FNV-1a hashing') endif # --- rapidfuzz-cpp --- # Header-only bit-parallel Levenshtein implementation used by clone scoring. rapidfuzz_dep = dependency('rapidfuzz') # --- Z3 via pkg-config (Homebrew, distro packages, or Conan) --- # Users may need to extend PKG_CONFIG_PATH on macOS: # export PKG_CONFIG_PATH="$(brew --prefix z3)/lib/pkgconfig:$PKG_CONFIG_PATH" z3_dep = dependency('z3', required: true, include_type: 'system') # --- radare2 binary backend --- # Prefer linking against libr_core when available. R2Backend still has a CLI # fallback so binary scans remain usable when building without radare2 dev libs. radare2_dep = dependency('r_core', required: get_option('radare2')) if radare2_dep.found() add_project_arguments('-DFOLD_HAVE_R2_CORE=1', language: 'cpp') message('radare2 binary backend: using libr_core') else message('radare2 binary backend: using r2 CLI fallback') endif # --- Capstone: exhaustive overlapping ROP gadget discovery --- # When enabled, the gadget-discovery router (gadget_source.cpp) does its own # overlapping sweep; otherwise it falls back to the r2 /R search, so this is # fully optional. Capstone is CMake-only (not on wrapdb), so we try the # system/pkg-config copy first, then build the vendored git wrap # (subprojects/capstone.wrap) as a CMake subproject. capstone_dep = dependency('', required: false) # not-found placeholder for dep lists if not get_option('capstone').disabled() capstone_dep = dependency('capstone', required: false) # system / pkg-config first if not capstone_dep.found() cmake = import('cmake') capstone_opts = cmake.subproject_options() capstone_opts.add_cmake_defines({ 'CAPSTONE_BUILD_SHARED_LIBS': false, 'CAPSTONE_BUILD_CSTOOL': false, 'CAPSTONE_INSTALL': false, }) capstone_sub = cmake.subproject('capstone', options: capstone_opts, required: get_option('capstone')) if capstone_sub.found() capstone_dep = capstone_sub.dependency('capstone') endif endif endif if capstone_dep.found() add_project_arguments('-DFOLD_HAVE_CAPSTONE=1', language: 'cpp') message('capstone gadget discovery: enabled') else message('capstone gadget discovery: disabled (r2 /R fallback)') endif # --- Optional analysis-library lanes --- # Only Ghidra is wired today. Souffle remains as the one near-term reserved # fact-engine lane; other research candidates stay docs-only until a prototype # justifies a build surface. if get_option('souffle').enabled() warning('souffle lane requested, but only a vendor manifest exists; no build target is wired yet') endif # --- Ghidra decompiler (vendored SSA P-Code binary frontend) --- # Optional; gated by -Dghidra. When enabled, third_party/ghidra-decompiler # defines ghidra_decompiler_dep + the per-arch .sla + pcode_dump. fold_core does # not link it yet (the lifter/encoder glue lands in later phases). ghidra_enabled = false # .allowed() is true for both `auto` (the default) and `enabled`; only an explicit # -Dghidra=disabled skips it. The decompiler is vendored (third_party), so there is # no external dependency to probe — auto means "build unless told not to". if get_option('ghidra').allowed() # add_project_arguments must precede any target (the subdir declares some). add_project_arguments('-DFOLD_HAVE_GHIDRA=1', language: 'cpp') # Absolute path to the checked-in vendored SLEIGH specs (.ldefs/.pspec/.cspec # plus source-tree .sla copies), so the lifter can locate the complete spec # set at runtime in a dev build. The Meson-generated build-tree .sla targets # are validation artifacts until an install/datadir copy rule exists. add_project_arguments( '-DFOLD_GHIDRA_SPECS_DIR="' + meson.project_source_root() / 'third_party' / 'ghidra-decompiler' / 'specs' + '"', language: 'cpp') ghidra_enabled = true message('ghidra decompiler: enabled (vendored)') else message('ghidra decompiler: disabled (r2-disasm fallback)') endif # Optional Rust/Swift frontend defines — must precede any build target. if get_option('rust-frontend') add_project_arguments('-DFOLD_HAVE_RUST=1', language: 'cpp') endif if get_option('swift-frontend') add_project_arguments('-DFOLD_HAVE_SWIFT=1', language: 'cpp') endif # --- LLVM / Clang via llvm-config --- # The user can override the discovered binary with -Dllvm-config-path=... # or by exporting LLVM_CONFIG before invoking meson. llvm_config_opt = get_option('llvm-config-path') if llvm_config_opt != '' llvm_config = find_program(llvm_config_opt, required: true) else llvm_config = find_program( 'llvm-config', 'llvm-config-22', 'llvm-config-21', 'llvm-config-20', 'llvm-config-19', 'llvm-config-18', 'llvm-config-17', 'llvm-config-16', 'llvm-config-15', 'llvm-config-14', required: true, ) endif llvm_version = run_command(llvm_config, '--version', check: true).stdout().strip() llvm_incdir = run_command(llvm_config, '--includedir', check: true).stdout().strip() llvm_libdir = run_command(llvm_config, '--libdir', check: true).stdout().strip() llvm_bindir = run_command(llvm_config, '--bindir', check: true).stdout().strip() llvm_cxxflags = run_command(llvm_config, '--cxxflags', check: true).stdout().strip().split() message('LLVM @0@ from @1@'.format(llvm_version, llvm_libdir)) # Find llvm-ar so meson/ninja can archive static libraries without # requiring the user to put the LLVM bin directory on PATH. llvm_ar = find_program(llvm_bindir / 'llvm-ar', llvm_bindir / 'llvm-ar-@0@'.format(llvm_version), required: false) if not llvm_ar.found() warning('llvm-ar not found in @0@ — static library archiving may fail on macOS'.format(llvm_bindir)) endif # Pull only the include-related and necessary flags from llvm-config; strip # warning flags and macro definitions that conflict with our own warning # level. We keep -I, -D for system requirements like -DNDEBUG, and -std= if # present (LLVM headers sometimes require C++17+). _keep_prefixes = ['-I', '-D', '-isystem'] _clean_cxxflags = [] foreach f : llvm_cxxflags foreach p : _keep_prefixes if f.startswith(p) _clean_cxxflags += f break endif endforeach endforeach add_project_arguments(_clean_cxxflags, language: 'cpp') # Clang ships in the same prefix as LLVM. Use -idirafter so the path comes # after the SDK and clang's resource-dir / libc++ search dirs — preventing # Homebrew clang's from finding the SDK via -isystem # and crashing the whole build. The cost is that LLVM headers fire on our # warning level; we suppress -Wunused-parameter etc. just below. add_project_arguments('-idirafter', llvm_incdir, language: 'cpp') add_project_arguments( '-Wno-unused-parameter', '-Wno-unknown-warning-option', language: 'cpp', ) if get_option('strict_warnings').enabled() add_project_arguments( '-Wpedantic', '-Wshadow', language: 'cpp' ) endif # Link against the umbrella Clang shared library (covers Tooling, AST, # Frontend, Basic, etc.) and LLVM. This avoids enumerating ~40 static libs # and matches the recommended way to link tools against Clang. libclang_cpp_dep = cpp.find_library( 'clang-cpp', dirs: [llvm_libdir], has_headers: ['clang/Tooling/Tooling.h'], header_args: ['-I' + llvm_incdir], required: true, ) # Prefer the umbrella libLLVM shared lib; fall back to enumerated static libs # if the distro only shipped those. libllvm_dep = cpp.find_library('LLVM', dirs: [llvm_libdir], required: false) if not libllvm_dep.found() libllvm_dep = cpp.find_library( 'LLVM-' + llvm_version.split('.')[0], dirs: [llvm_libdir], required: true, ) endif clang_deps = [libclang_cpp_dep] llvm_dep = libllvm_dep # --- core static library: everything except cli/main --- fold_core_sources = [] # Vendored Ghidra decompiler targets (declared after all add_project_arguments, # before fold_core). Builds the lib + sleigh + .sla + pcode_dump; not yet linked # into fold_core (lifter/encoder glue is a later phase). if ghidra_enabled subdir('third_party/ghidra-decompiler') endif subdir('src/binary') subdir('src/frontend') subdir('src/ir') fold_core_sources += files( 'src/analysis/production_roots.cpp', 'src/analysis/production_reachability.cpp', 'src/config/configuration_profile.cpp', ) subdir('src/symbolic') subdir('src/detect') subdir('src/index') subdir('src/report') subdir('src/confirm') subdir('src/witness') subdir('src/fuzz') subdir('src/mcp') subdir('src/lsp') # cli/scan_runner.cpp and cli/subprocess_fanout.cpp live in src/cli/ but are # part of fold_core (the executable links them too via the dep). Pull them in # before the static_library is finalized. fold_core_sources += files( 'src/cli/clang_shard_tu.cpp', 'src/cli/backend_shard_runner.cpp', 'src/cli/configuration_callsite_guard.cpp', 'src/cli/incremental_fingerprint.cpp', 'src/cli/project_graph_orchestrator.cpp', 'src/cli/production_reachability_findings.cpp', 'src/cli/source_call_graph.cpp', 'src/cli/source_call_graph_contribution.cpp', 'src/cli/translation_unit_fingerprint.cpp', 'src/cli/source_summary_pipeline.cpp', 'src/cli/source_tu_runner.cpp', 'src/cli/tu_quarantine.cpp', 'src/cli/tu_path.cpp', 'src/cli/detector_plan.cpp', 'src/cli/detector_profile_accum.cpp', 'src/cli/finding_evidence.cpp', 'src/cli/detector_registry.cpp', 'src/cli/error_format.cpp', 'src/cli/executable_path.cpp', 'src/cli/fuzz_prep_runner.cpp', 'src/cli/fuzz_run_runner.cpp', 'src/cli/scan_option_catalog.cpp', 'src/cli/scan_preset.cpp', 'src/cli/scan_router.cpp', 'src/cli/scan_runner.cpp', 'src/cli/signal_isolation.cpp', 'src/cli/tu_security_prefilter.cpp', 'src/cli/subprocess_fanout.cpp', ) fold_core = static_library( 'fold-core', fold_core_sources, dependencies: [ spdlog_dep, json_dep, sqlite3_dep, xxhash_dep, rapidfuzz_dep, z3_dep, radare2_dep, capstone_dep, llvm_dep, ] + clang_deps, include_directories: include_directories('include', '.'), install: false, ) fold_core_dep = declare_dependency( link_with: fold_core, include_directories: include_directories('include', '.'), dependencies: [spdlog_dep, json_dep, sqlite3_dep, xxhash_dep, rapidfuzz_dep, z3_dep, radare2_dep, capstone_dep, llvm_dep] + clang_deps, ) # --- Ghidra lifter + dual-stream router (gated) --- # Links fold_core (encoder + observers) with the vendored decompiler. Built only # when -Dghidra=enabled; the Ghidra-header TU (ghidra_lifter.cpp) is compiled at # fold's std with warnings suppressed. Defined after fold_core so fold_core_dep # and ghidra_decompiler_dep (from the third_party subdir) are both in scope. if ghidra_enabled fold_ghidra = static_library( 'fold-ghidra', files('src/binary/ghidra_lifter.cpp', 'src/binary/pcode_router.cpp', 'src/binary/binary_pcode_scan.cpp'), cpp_args: ['-w'], include_directories: [include_directories('include'), ghidra_inc], dependencies: [fold_core_dep, ghidra_decompiler_dep], install: false, ) fold_ghidra_dep = declare_dependency( link_with: fold_ghidra, include_directories: include_directories('include'), dependencies: [fold_core_dep, ghidra_decompiler_dep], ) # Phase-3.5 smoke driver: one function → router → merged findings. pcode_scan_exe = executable( 'pcode_scan', files('tools/pcode_scan.cpp'), dependencies: [fold_ghidra_dep], install: false, ) endif # --- CLI binary --- subdir('src/cli') # --- Swift SIL frontend --- if get_option('swift-frontend') subdir('tools/fold-swift') endif # --- tests --- if get_option('build-tests') # Use the system Catch2 (Homebrew on macOS / distro pkg elsewhere) so the # header and library stay in lock-step. Mixing a Conan-built lib with a # Homebrew header leaks `makeExceptionHappenedString` references that the # Conan lib never built. PKG_CONFIG_PATH must point at the system .pc. catch2_dep = dependency('catch2-with-main', required: true) subdir('tests') endif # --- fold-rust (Rust frontend, requires nightly rustc) --- _fold_rust_manifest = join_paths(meson.project_source_root(), 'tools', 'fold-rust', 'Cargo.toml') _fold_rust_src_bin = join_paths(meson.project_source_root(), 'tools', 'fold-rust', 'target', 'release', 'fold-rust') # custom_target with build_by_default+install so a single `meson compile` # builds all 3 binaries and `meson install` copies them to prefix/bin. if get_option('rust-frontend') _fold_rust = custom_target('fold-rust', output: 'fold-rust', command: ['sh', '-c', 'cd "' + join_paths(meson.project_source_root(), 'tools', 'fold-rust') + '" && ' + 'cargo build --release --manifest-path "' + _fold_rust_manifest + '" && ' + 'cp "' + _fold_rust_src_bin + '" "@OUTPUT@"'], install: true, install_dir: get_option('bindir'), build_by_default: true, build_always_stale: true, ) endif