Skip to content

Audit: portability & correctness bugs (hardcoded paths, CMake, BigInt, isIncomplete, version detection) #4

Closed

Description

@sergiorandria

Summary

Automated audit found multiple portability, correctness and maintainability bugs that should be fixed before enabling branch protection / release. Most are triggered on CI (ubuntu-24.04, LLVM 22 apt.llvm.org) or on a fresh clone away from /home/sergio.

1. CMakeLists.txt:23,38 – hardcoded clang-cpp / LLVM search – CI failure 🔴

Location: CMakeLists.txt:23 find_library(CLANG_CPP_LIB clang-cpp HINTS /usr/lib) and :38 find_library(LLVM_SINGLE_LIB LLVM-22 HINTS /usr/lib /usr/lib/llvm)

Evidence:

  • CI log: Found LLVM 22.1.8 at /usr/lib/llvm-22/lib/cmake/llvm → CMake Error at CMakeLists.txt:25 (message): clang-cpp not found
  • libclang-cpp22-dev on apt.llvm.org/noble installs:
    /usr/lib/llvm-22/lib/libclang-cpp.so -> libclang-cpp.so.22.1
    /usr/lib/x86_64-linux-gnu/libclang-cpp.so.22.1 (versioned)
    /usr/lib/x86_64-linux-gnu/libclang-cpp22.so (dev)
    
    Unversioned libclang-cpp.so only exists at /usr/lib/llvm-22/lib, not in default system search. Similarly libLLVM-22.so lives at /usr/lib/llvm-22/lib/libLLVM-22.so.
  • Arch layout (/usr/lib/libclang-cpp.so, /usr/include) works locally, so bug only visible on CI.

Expected: Support both layouts:

find_library(CLANG_CPP_LIB NAMES clang-cpp clang-cpp-22
  HINTS /usr/lib /usr/lib/x86_64-linux-gnu /usr/lib/llvm-22/lib ${LLVM_LIBRARY_DIRS} ${LLVM_DIR}/../..)
find_path(CLANG_INCLUDE_DIRS NAMES clang/Basic/Version.h
  HINTS /usr/include /usr/lib/llvm-22/include ${LLVM_INCLUDE_DIRS})
find_library(LLVM_SINGLE_LIB NAMES LLVM-22 LLVM
  HINTS /usr/lib /usr/lib/x86_64-linux-gnu /usr/lib/llvm-22/lib ${LLVM_LIBRARY_DIRS})

2. src/interpreter/interpreter.cpp:54,57 – hardcoded absolute paths 🔴

compilerArgsStorage_.push_back("/usr/lib/clang/22");
compilerArgsStorage_.push_back("/home/sergio/Project/cpp-repl/include");
  • /usr/lib/clang/22 is the Arch path. On Ubuntu the resource dir is /usr/lib/llvm-22/lib/clang/22 or discovered via clang::CompilerInstance/llvm-config --prefix. Hardcoding breaks CI and any other install prefix.
  • /home/sergio/Project/cpp-repl/include makes a fresh git clone elsewhere fail to find cpp-repl/fix_np_headers.hpp (force-included at :59). Repro: clone to /tmp, build – builtin headers break. Should be derived from PROJECT_SOURCE_DIR/CMAKE_CURRENT_SOURCE_DIR or installed prefix, or via target_include_directories.

Same issue in src/repl.cpp:27-28 (legacy Repl):

std::vector<const char *> args = {"-std=c++17", "-O0", "-resource-dir", "/usr/lib/clang/22"};

3. Dead / non-portable Numpy-C-API hints 🔴

src/interpreter/interpreter.cpp:55,548,672,716 contains explanations and error hints referencing a local checkout:

Use -I /home/sergio/Project/Numpy-C-API/include and #include "np/np.hpp"
Try: cpp-repl -std=c++23 -I /home/sergio/Project/Numpy-C-API/include

These paths leak a developer's home dir into user-facing diagnostics and should be generic (/path/to/Numpy-C-API/include or just suggest -I <path>).

4. include/cpp-repl/fix_proxy.hpp:10 – unconditional external include 🔴

#include "np/detail/proxy.hpp"

If this header were ever force-included, it fails unless Numpy-C-API is installed. File is currently dead (only fix_np_headers.hpp is -included) but still presents maintenance risk and breaks clang-tidy/cppcheck if enabled. Either remove or guard with #if __has_include.

5. src/interpreter/interpreter.cpp:266-329 – preprocessBigIntLiterals regex incomplete (functional bug) 🟡

Repro:

./build/cpp-repl --no-interactive -e 'bigint g = 12345678901234567890123456789012345678901234567890;'
# In file included from <<< inputs >>>:1:
# error: integer literal is too large

Expected: auto-wrap to bigint("..."). The regex only handles bigint|cpp_int|... \s+ \w+ \s*=\s* DIGITS ; but fails for:

  • const bigint, constexpr, static qualifiers before type
  • multi-declaration or template types like const std::vector<cpp_int>, though less common
  • The local uncommitted diff already adds handling but still misses auto literals used as function args, e.g., foo(123456789...)
    Should expand to handle qualifiers and report correct type extraction, or better rely on boost::multiprecision::cpp_int's user-defined literal if available.

6. src/repl/session.cpp:20-36 and src/interpreter/interpreter.cpp:824-843 – naive isIncomplete 🟡

Counts {, (, [ without stripping strings/comments. Example:

cpp> std::string s = "{";
...>

incorrectly considered incomplete (open brace in string). Python REPL correctly handles this via tokenization. Should reuse stripCommentsAndStrings logic from VersionDetector or use clang lexing.

7. src/interpreter/interpreter.cpp:233-257,162-179 – reinitWithCurrentOptions/ensureVersion replays history unsafely 🟡

  • reinitWithCurrentOptions and ensureVersion clear variables_/history_ then replay via eval(h, e) without handling re-entrancy: if replayed code triggers another ensureVersion upgrade, recursion could double-reinit.
  • variables_ tracking via regex parseDeclaration is lossy (misses std::string, templates, auto, etc.) and can silently corrupt undo state. undo() rebuilds from history but skips failed parseDeclaration, leaving stale entries.

8. src/utils/version_detector.cpp:88-111 – overly broad version detection 🟡

detect checks stripped.find("import") via contains("import ") etc., causing false positives on variable names like int important = 0; → triggers C++23 upgrade. Same for export, format, source_location. Should use containsWord.

9. Hardcoded version string 🟡

src/cli/cli.cpp:126-127 and README.md hardcode LLVM 22.1.3 / clang 22.1.8. CI now uses 22.1.8 for both. Should query LLVM_VERSION_MAJOR/PACKAGE_VERSION at cmake time and embed via configure_file.

10. Build: CMakeLists.txt missing dynamic resource-dir / include handling & HAS_READLINE 🟠

  • No clang -print-resource-dir detection; should use execute_process(clang -print-resource-dir) or ClangConfig.
  • Latest main adds READLINE detection at bottom but interpreter doesn't use it consistently (history handling).

Repro steps

cmake -B build -G Ninja -DLLVM_DIR=/usr/lib/llvm-22/lib/cmake/llvm  # CI
# fails at clang-cpp not found before fix
./build/cpp-repl --no-interactive -e 'bigint g = 1234567890123456789012345678901234567890;'
# error: integer literal is too large
git clone elsewhere && cmake -B build && ./build/cpp-repl --no-interactive -e 'int x=1;'
# fails to find fix_np_headers.hpp due to hardcoded /home/sergio path

Suggested priority


Generated by local audit (grep, llvm-config, gh, smoke tests) on e6e94b7 + unstaged changes. Happy to PR the CMake fix above.

Activity

  1. sergiorandria commented on Sep 1, 2026

    @sergiorandria
    OwnerAuthor

    Update: Fixed root cause of the bigint JIT failure reported in follow-up:

    JIT session error: Symbols not found: [ _ZN5boost15throw_exception... ]
    error: Failed to materialize symbols: { (main, { x }) }
    

    Root cause: CMakeLists.txt:13,145 forced -fno-exceptions -fno-rtti (CMAKE_CXX_FLAGS_DEBUG + target_compile_options).
    llvm-config --cxxflags indeed contains -fno-exceptions, but LLVMConfig.cmake: LLVM_ENABLE_RTTI=ON and boost::multiprecision::cpp_int (header-only) requires exceptions. With -fno-exceptions, BOOST_NO_EXCEPTIONS is defined (clang.hpp checks !__has_feature(cxx_exceptions)), so boost::throw_exception(std::exception&, source_location&) is declared as user-defined (not throw-based) and never defined in the host binary. At runtime the JIT tries to resolve cpp_int_backend::do_assign_string, eval_add, throw_exception etc. from the host and fails → whole incremental module fails → x never materialized → second x*x also fails.

    Repro: bigint x = cpp_int("238493...392"); x * x → first module fails, second reports x not found.

    Fix applied: CMakeLists.txt

    -set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -fno-exceptions -fno-rtti")
    +set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g")
    - target_compile_options(cpp-repl PRIVATE -fno-exceptions -fno-rtti)
    + # Boost needs exceptions/RTTI; LLVM RTTI is ON, so keep them enabled

    Rebuilt with -G Ninja (clean) and verified:

    ./build/cpp-repl --no-interactive -e 'bigint x = cpp_int("238493849384938493840293840293849384203984029384938209348029384023984392");' -e 'x * x'
    # → 56879316194445727196646582565376800156063760889790521039319906429201930134203754126777792792075488572897490478806717944981858347324307059609664
    ./build/cpp-repl --no-interactive -e 'cpp_int a = cpp_int("123456789012345678901234567890"); a * cpp_int("2")'  # ok
    for f in examples/*.cpp; do ./build/cpp-repl --no-interactive "$f"; done  # all ok

    Also verified earlier fix for #1 (hardcoded clang-cpp search) still present: find_library(CLANG_CPP_LIB NAMES clang-cpp clang-cpp-22 HINTS /usr/lib /usr/lib/x86_64-linux-gnu /usr/lib/llvm-22/lib ${LLVM_LIBRARY_DIRS} ${LLVM_DIR}/../..) etc.

    If you prefer to keep -fno-exceptions for LLVM-sensitive TUs, alternative is to provide a stub:

    namespace boost { void throw_exception(std::exception const& e, boost::source_location const& loc){ std::fprintf(stderr,"boost::throw_exception: %s\n",e.what()); std::abort(); } }

    but enabling exceptions is simpler and matches LLVM_ENABLE_RTTI=ON.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions