From 5a5f46fd7e0f28b61dfe2b4a67a979a1c2d61680 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 21 Apr 2026 20:01:52 +0100 Subject: [PATCH 1/3] Run until segfault is hit --- .github/workflows/run-tests.yml | 84 ++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index b75fad03..98c38067 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -39,6 +39,21 @@ jobs: - name: Install Python dependencies run: pip install tomli + - name: Install gdb (Linux) + if: runner.os == 'Linux' + run: sudo apt-get install -y gdb + + - name: Enable core dumps (Linux) + if: runner.os == 'Linux' + run: | + sudo mkdir -p /cores + sudo chmod 1777 /cores + sudo sysctl -w kernel.core_pattern='/cores/core.%e.%p.%t' + sudo sysctl -w kernel.core_uses_pid=1 + sudo systemctl stop apport.service 2>/dev/null || true + sudo systemctl stop apport.socket 2>/dev/null || true + cat /proc/sys/kernel/core_pattern + - name: Configure run: | mkdir build && cd build @@ -50,10 +65,75 @@ jobs: run: ninja working-directory: build - - name: Run unit tests - run: ninja check + - name: Run unit tests (loop until segfault or cap) + working-directory: build + shell: bash + env: + REPRO_MAX_ITERATIONS: "50" + run: | + set +e + ulimit -c unlimited 2>/dev/null || true + shopt -s nullglob + MAX="${REPRO_MAX_ITERATIONS}" + last_rc=0 + for i in $(seq 1 "$MAX"); do + echo "::group::iteration $i / $MAX" + ninja check-unit + last_rc=$? + echo "::endgroup::" + if [[ "$RUNNER_OS" == "Linux" ]]; then + cores=( /cores/core.* ) + if (( ${#cores[@]} > 0 )); then + echo "got a core on iteration $i (check-unit rc=$last_rc)" + printf '%s\n' "${cores[@]}" + exit 1 + fi + fi + if (( last_rc != 0 )); then + echo "iteration $i failed with rc=$last_rc but no core captured - continuing" + fi + done + # Mirror original semantics: fail the step if the last run failed, + # even without a core dump. + exit $last_rc + + - name: Generate backtraces from cores (Linux) + if: failure() && runner.os == 'Linux' + shell: bash + run: | + set -x + ls -la /cores || true + shopt -s nullglob + for core in /cores/core.*; do + echo "=== $core ===" + gdb -batch \ + -ex "set pagination off" \ + -ex "thread apply all bt full" \ + -ex "info registers" \ + -ex "info sharedlibrary" \ + ./cpp2rust/cpp2rust "$core" 2>&1 | tee "${core}.bt.txt" || true + done working-directory: build + - name: Package binary + cores for local gdb (Linux) + if: failure() && runner.os == 'Linux' + shell: bash + run: | + mkdir -p /tmp/repro-bundle + cp -v ./build/cpp2rust/cpp2rust /tmp/repro-bundle/ || true + find ./build -maxdepth 4 -name 'libcc2rs*' -exec cp -v {} /tmp/repro-bundle/ \; || true + cp -v /cores/core.* /tmp/repro-bundle/ 2>/dev/null || true + ls -la /tmp/repro-bundle + + - name: Upload coredump bundle + if: failure() && runner.os == 'Linux' + uses: actions/upload-artifact@v4 + with: + name: segfault-repro-${{ matrix.os }}-${{ matrix.build_type }}-${{ github.run_id }}-${{ github.run_attempt }} + path: /tmp/repro-bundle/** + if-no-files-found: warn + retention-days: 7 + - name: Check rules run: ninja check-rules working-directory: build From e4f9cc914e57e714293c34ea71c6c6248d974591 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 21 Apr 2026 20:20:17 +0100 Subject: [PATCH 2/3] Ignore cores generated by ub tests --- .github/workflows/run-tests.yml | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 98c38067..a04e9f98 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -76,21 +76,42 @@ jobs: shopt -s nullglob MAX="${REPRO_MAX_ITERATIONS}" last_rc=0 + # core_pattern is /cores/core.%e.%p.%t, where %e is the executable + # name truncated to 15 chars (TASK_COMM_LEN-1). UB tests compile to + # Rust binaries named test_ub_ (truncated to test_ub_) + # and may segfault by design - those are expected and get discarded. + # Anything else (cpp2rust itself, or a non-UB test binary) is a real + # crash we want to capture. + is_expected_ub_core() { + local base exe + base=$(basename "$1") + exe=${base#core.} + exe=${exe%%.*} + [[ $exe == test_ub* ]] + } for i in $(seq 1 "$MAX"); do echo "::group::iteration $i / $MAX" ninja check-unit last_rc=$? echo "::endgroup::" if [[ "$RUNNER_OS" == "Linux" ]]; then - cores=( /cores/core.* ) - if (( ${#cores[@]} > 0 )); then - echo "got a core on iteration $i (check-unit rc=$last_rc)" - printf '%s\n' "${cores[@]}" + real_cores=() + for c in /cores/core.*; do + if is_expected_ub_core "$c"; then + echo "ignoring expected UB-test core: $(basename "$c")" + rm -f "$c" + else + real_cores+=( "$c" ) + fi + done + if (( ${#real_cores[@]} > 0 )); then + echo "got a real core on iteration $i (check-unit rc=$last_rc)" + printf '%s\n' "${real_cores[@]}" exit 1 fi fi if (( last_rc != 0 )); then - echo "iteration $i failed with rc=$last_rc but no core captured - continuing" + echo "iteration $i failed with rc=$last_rc but no real core captured - continuing" fi done # Mirror original semantics: fail the step if the last run failed, From 4d259cffb048569c2b478845860f87f01eebf636 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 21 Apr 2026 21:22:14 +0100 Subject: [PATCH 3/3] Use the context of the matcher to print types --- cpp2rust/converter/mapper.cpp | 6 ++++++ cpp2rust/converter/mapper.h | 12 ++++++++++++ cpp2rust/converter/translation_rule.cpp | 1 + 3 files changed, 19 insertions(+) diff --git a/cpp2rust/converter/mapper.cpp b/cpp2rust/converter/mapper.cpp index c3cc0897..f3a4af9a 100644 --- a/cpp2rust/converter/mapper.cpp +++ b/cpp2rust/converter/mapper.cpp @@ -32,6 +32,7 @@ std::unordered_map clang::PrintingPolicy getPrintPolicy() { assert(ctx_); clang::PrintingPolicy policy(ctx_->getLangOpts()); + policy.Bool = true; policy.SuppressTagKeyword = true; policy.SuppressScope = false; policy.FullyQualifiedName = true; @@ -562,6 +563,11 @@ std::string normalizeTranslationRule(std::string rule) { } // namespace +PushASTContext::PushASTContext(clang::ASTContext &ctx) : prev_(ctx_) { + ctx_ = &ctx; +} +PushASTContext::~PushASTContext() { ctx_ = prev_; } + bool Contains(clang::QualType qual_type) { return search(qual_type) != types_.end(); } diff --git a/cpp2rust/converter/mapper.h b/cpp2rust/converter/mapper.h index 14de1808..f2b706ab 100644 --- a/cpp2rust/converter/mapper.h +++ b/cpp2rust/converter/mapper.h @@ -3,6 +3,7 @@ // Copyright (c) 2022-present INESC-ID. // Distributed under the MIT license that can be found in the LICENSE file. +#include #include #include @@ -14,6 +15,17 @@ #include "converter/translation_rule.h" namespace cpp2rust::Mapper { +class PushASTContext { +public: + explicit PushASTContext(clang::ASTContext &ctx); + ~PushASTContext(); + PushASTContext(const PushASTContext &) = delete; + PushASTContext &operator=(const PushASTContext &) = delete; + +private: + clang::ASTContext *prev_; +}; + bool Contains(clang::QualType qual_type); bool Contains(const clang::Expr *expr); diff --git a/cpp2rust/converter/translation_rule.cpp b/cpp2rust/converter/translation_rule.cpp index 748bbab7..be57303a 100644 --- a/cpp2rust/converter/translation_rule.cpp +++ b/cpp2rust/converter/translation_rule.cpp @@ -86,6 +86,7 @@ class Callback : public clang::ast_matchers::MatchFinder::MatchCallback { void run(const clang::ast_matchers::MatchFinder::MatchResult &R) override { assert(sema_); + Mapper::PushASTContext scoped(*R.Context); if (auto var = R.Nodes.getNodeAs("tvar")) { clang::QualType type; if (auto *tdecl = var->getDescribedAliasTemplate()) {