Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/rusty-std-book.md
Original file line number Diff line number Diff line change
Expand Up @@ -2388,6 +2388,26 @@ Closed since last revision:
forward-decl approach impossible (the merge is the right
resolution). Test: `docs/vec_port/vec_extract_if_test.cpp`.

Root cause audit (TODO-005): commit `0680e4c` correctly isolated
the crash to a value-type mismatch, not sizeof/alignment or vtable
shape. The generated `ExtractIf<T, F, A>` body came from
`vec_port.vec.extract_if.cppm`, where `rusty::Vec<T, A>` resolves to
the hand-written compatibility alias in `include/rusty/vec.hpp`
(`VecLegacy`, fields `data_`, `size_`, `capacity_`). The object
passed by `Vec<T, A>::extract_if` is the transpiled module-local
`vec_port::Vec<T, A>` (`RawVec<T, A> buf` plus `len_field`), so the
old reinterpret-cast made `ExtractIf::new_` call `VecLegacy::set_len`
through the wrong layout. `drain` masked the same class of bug on
full-range tests because its destructor could short-circuit before
touching the parent vec; partial drain and `extract_if` both need the
real parent layout. The viable shape is therefore the `8a8154f`
merge: inject the aux-module content into `vec.cppm` under the same
C++20 module attachment and rewrite `rusty::Vec` to the local
transpiled `Vec`. This is now pinned by
`tests/vec_port/run_extract_if_check.sh`, which rebuilds the port
from rustc sources and runs `tests/vec_port/vec_extract_if_test.cpp`
under ASAN.

Two ancillary fixes landed with the merge:
- `slice_ext::range` in `include/rusty/slice.hpp` now also detects
`r.end_value()` on `rusty::range<T>` (in addition to the `.end`
Expand Down
161 changes: 161 additions & 0 deletions tests/vec_port/run_extract_if_check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#!/usr/bin/env bash
# End-to-end ASAN check for the transpiled vec_port::Vec::extract_if path.
#
# This intentionally builds a fresh vec_port module from rustc's alloc sources
# instead of relying on stale /tmp artifacts.
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"

WORK_DIR="${TMPDIR:-/tmp}/rusty-vec-port-extract-if"
KEEP_WORK_DIR=0
CXX="${CXX:-clang++}"

print_usage() {
cat <<EOF
Usage: $(basename "$0") [options]

Options:
--work-dir <dir> Work directory for generated vec_port artifacts
(default: ${WORK_DIR})
--keep-work-dir Keep generated artifacts after a successful run
--help Show this help
EOF
}

while [[ $# -gt 0 ]]; do
case "$1" in
--work-dir)
if [[ $# -lt 2 ]]; then
echo "error: --work-dir requires a value" >&2
exit 2
fi
WORK_DIR="$2"
shift 2
;;
--keep-work-dir)
KEEP_WORK_DIR=1
shift
;;
--help|-h)
print_usage
exit 0
;;
*)
echo "error: unknown option '$1'" >&2
print_usage >&2
exit 2
;;
esac
done

if ! command -v "${CXX}" >/dev/null 2>&1; then
echo "error: CXX compiler '${CXX}' not found; clang++ with C++20 modules is required" >&2
exit 1
fi

SYSROOT="$(rustc --print sysroot)"
RUST_SRC="${SYSROOT}/lib/rustlib/src/rust/library/alloc/src"
if [[ ! -d "${RUST_SRC}/vec" || ! -d "${RUST_SRC}/raw_vec" ]]; then
echo "error: rust-src component not found under ${RUST_SRC}" >&2
echo "hint: rustup component add rust-src" >&2
exit 1
fi

VEC_CRATE="${WORK_DIR}/vec_crate"
CPP_OUT="${WORK_DIR}/cpp_out"
BUILD_DIR="${CPP_OUT}/build"
LOG_DIR="${WORK_DIR}/logs"

run_logged() {
local name="$1"
shift
local log="${LOG_DIR}/${name}.log"
echo "[vec_port] ${name}"
if ! "$@" >"${log}" 2>&1; then
echo "error: ${name} failed; showing last 120 log lines" >&2
tail -n 120 "${log}" >&2 || true
exit 1
fi
}

rm -rf "${WORK_DIR}"
mkdir -p "${VEC_CRATE}/src" "${LOG_DIR}"

cp -r "${RUST_SRC}/vec" "${VEC_CRATE}/src/vec"
cp -r "${RUST_SRC}/raw_vec" "${VEC_CRATE}/src/raw_vec"

run_logged prep \
bash "${REPO_ROOT}/docs/vec_port/prep.sh" \
"${VEC_CRATE}/src/vec" \
"${VEC_CRATE}/src/raw_vec"

cat > "${VEC_CRATE}/Cargo.toml" <<'EOF'
[package]
name = "vec_port"
version = "0.0.1"
edition = "2021"

[lib]
path = "src/lib.rs"
EOF

cat > "${VEC_CRATE}/src/lib.rs" <<'EOF'
#![allow(unused)]
pub mod vec;
pub mod raw_vec;
EOF

run_logged transpile \
cargo run --manifest-path "${REPO_ROOT}/Cargo.toml" -p rusty-cpp-transpiler -- \
--crate "${VEC_CRATE}/Cargo.toml" \
--output-dir "${CPP_OUT}"

run_logged post_transpile_patch \
python3 "${REPO_ROOT}/docs/vec_port/post_transpile_patch.py" "${CPP_OUT}"

if ! grep -q "vec_port: extract_if content merged" "${CPP_OUT}/vec_port.vec.cppm"; then
echo "error: extract_if content was not merged into vec_port.vec.cppm" >&2
exit 1
fi
if ! awk '
/struct ExtractIf \{/ { in_extract_if = 1 }
in_extract_if && /Vec<T, A>& vec;/ { found_local_vec = 1 }
in_extract_if && /^};/ { done = 1; exit }
END { exit (done && found_local_vec) ? 0 : 1 }
' "${CPP_OUT}/vec_port.vec.cppm"; then
echo "error: ExtractIf does not hold the local transpiled Vec<T, A>&" >&2
exit 1
fi

ASAN_FLAGS="-I${REPO_ROOT}/include -std=c++23 -fsanitize=address -fno-omit-frame-pointer"
run_logged cmake_configure \
cmake -B "${BUILD_DIR}" -S "${CPP_OUT}" -G Ninja \
-DCMAKE_CXX_COMPILER="${CXX}" \
-DCMAKE_CXX_FLAGS="${ASAN_FLAGS}" \
-DCMAKE_CXX_STANDARD=23

run_logged cmake_build \
cmake --build "${BUILD_DIR}" -- -j2

cp "${SCRIPT_DIR}/vec_extract_if_test.cpp" "${CPP_OUT}/vec_extract_if_test.cpp"
run_logged compile_extract_if_test \
"${CXX}" -std=c++23 -fsanitize=address -fno-omit-frame-pointer \
-I"${REPO_ROOT}/include" \
-fprebuilt-module-path="${BUILD_DIR}/CMakeFiles/vec_port.dir" \
-x c++ "${CPP_OUT}/vec_extract_if_test.cpp" \
-x none "${BUILD_DIR}/libvec_port.a" \
-o "${CPP_OUT}/vec_extract_if_test_asan"

run_logged run_extract_if_test \
env ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 \
"${CPP_OUT}/vec_extract_if_test_asan"

cat "${LOG_DIR}/run_extract_if_test.log"

if [[ "${KEEP_WORK_DIR}" == "0" ]]; then
rm -rf "${WORK_DIR}"
fi

echo "PASS: vec_port extract_if is ASAN-clean"
46 changes: 46 additions & 0 deletions tests/vec_port/vec_extract_if_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import vec_port;

#include <cstdio>
#include <cstdlib>
#include <rusty/rusty.hpp>

#define CHECK(cond, msg) \
do { \
if (!(cond)) { \
std::printf("FAIL: %s\n", msg); \
std::exit(1); \
} \
} while (0)

int main() {
auto v = Vec<int, rusty::alloc::Global>::new_in(rusty::alloc::Global{});
for (int i = 1; i <= 6; ++i) {
v.push(i);
}

{
auto ei = v.extract_if(rusty::range_from(static_cast<size_t>(0)),
[](int& x) { return x % 2 == 0; });
int count = 0;
int expected[3] = {2, 4, 6};
while (true) {
auto next = ei.next();
if (next.is_none()) {
break;
}
int val = next.unwrap();
CHECK(count < 3, "extract_if yielded too many values");
CHECK(val == expected[count], "extract_if yielded expected value");
++count;
}
CHECK(count == 3, "extract_if extracted exactly three values");
}

auto s = v.as_slice();
CHECK(s.size() == 3, "extract_if left three values behind");
CHECK(s[0] == 1 && s[1] == 3 && s[2] == 5,
"extract_if kept the non-matching values");

std::printf("ALL CHECKS PASSED\n");
return 0;
}
Loading