diff --git a/spike/A_verbatim_roundtrip/Roundtrip.cpp b/spike/A_verbatim_roundtrip/Roundtrip.cpp index ab87993..d3f3ed4 100644 --- a/spike/A_verbatim_roundtrip/Roundtrip.cpp +++ b/spike/A_verbatim_roundtrip/Roundtrip.cpp @@ -44,7 +44,10 @@ FileResult roundtrip(const std::filesystem::path& input, QPDF q; try { - q.processInputFile(input.string()); + // Keep the input filename alive across processFile: QPDF opens the + // file lazily and may dereference the pointer later. + const std::string input_str = input.string(); + q.processFile(input_str.c_str()); } catch (const std::exception& e) { r.error = std::string("QPDF open failed: ") + e.what(); return r; @@ -62,7 +65,7 @@ FileResult roundtrip(const std::filesystem::path& input, for (auto& page : pages) { QPDFObjectHandle content = page.getAttribute("/Contents", true); if (content.isArray()) { - auto items = content.getArrayAsArray(); + auto items = content.getArrayAsVector(); for (auto& item : items) { if (item.isStream()) { (void)item.getStreamData(); // force decode @@ -81,13 +84,18 @@ FileResult roundtrip(const std::filesystem::path& input, try { QPDFWriter w(q); - w.setOutputFile(output.string()); + // Keep the output filename string alive across the write() call: + // QPDFWriter retains the const char* and dereferences it during + // write(), so a temporary from output.string().c_str() would be a + // use-after-free (caught by ASan during the M0 spike run). + const std::string output_str = output.string(); + w.setOutputFilename(output_str.c_str()); // Preserve as much as possible: write with the same object stream // strategy and linearisation state as the input so byte-identity is a // realistic target. The defaults preserve the structure verbatim where // QPDF can. w.setPreserveEncryption(true); - w.setQDF(false); + w.setQDFMode(false); w.setLinearization(false); w.write(); } catch (const std::exception& e) { diff --git a/spike/CMakeLists.txt b/spike/CMakeLists.txt index a04b5ec..b72cb39 100644 --- a/spike/CMakeLists.txt +++ b/spike/CMakeLists.txt @@ -6,15 +6,40 @@ # independently and its results reported separately by CI. The spikes link only # against the leaf libraries they exercise (QPDF here), not against Qt. -find_package(QPDF CONFIG QUIET) +# Find QPDF. System packages (Debian libqpdf-dev) install the config under the +# multiarch lib dir; vcpkg installs it under its installed tree. Search both. +find_package(QPDF CONFIG QUIET + HINTS /usr/lib/${CMAKE_LIBRARY_ARCHITECTURE} /usr/local /usr) +# Fall back to a pkg-config probe if the CMake config wasn't installed. if(NOT QPDF_FOUND) + find_package(PkgConfig QUIET) + if(PkgConfig_FOUND) + pkg_check_modules(QPDF libqpdf IMPORTED_TARGET) + endif() +endif() + +if(NOT QPDF_FOUND AND NOT TARGET PkgConfig::QPDF) message(STATUS "QPDF not found — Spike A (verbatim round-trip) will not be built. " "Install qpdf (vcpkg, system package, or from source) to enable it.") return() endif() +# The QPDF CMake config exports `qpdf::libqpdf` (system packages, e.g. Debian's +# libqpdf-dev) and/or `QPDF::qpdf` (vcpkg). pkg-config yields PkgConfig::QPDF. +# Accept any of them. +if(TARGET qpdf::libqpdf) + set(_fpe_qpdf_target qpdf::libqpdf) +elseif(TARGET QPDF::qpdf) + set(_fpe_qpdf_target QPDF::qpdf) +elseif(TARGET PkgConfig::QPDF) + set(_fpe_qpdf_target PkgConfig::QPDF) +else() + message(WARNING "QPDF found but no known imported target — Spike A skipped.") + return() +endif() + add_executable(spike_a_verbatim_roundtrip common/SpikeRunner.cpp common/SpikeRunner.h @@ -22,7 +47,8 @@ add_executable(spike_a_verbatim_roundtrip A_verbatim_roundtrip/Roundtrip.cpp A_verbatim_roundtrip/Roundtrip.h ) -target_link_libraries(spike_a_verbatim_roundtrip PRIVATE QPDF::qpdf) +target_include_directories(spike_a_verbatim_roundtrip PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(spike_a_verbatim_roundtrip PRIVATE ${_fpe_qpdf_target}) target_compile_features(spike_a_verbatim_roundtrip PRIVATE cxx_std_20) freepdfeditor_apply_warnings(spike_a_verbatim_roundtrip) freepdfeditor_apply_hardening(spike_a_verbatim_roundtrip) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 71cb71b..ca67eb9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -12,7 +12,10 @@ # and lets CI on all three platforms run ctest without a vcpkg build of gtest. # A later milestone swaps in GTest once there is real C++ logic to assert on. -add_executable(test_spike_runner_contract test_spike_runner_contract.cpp) +add_executable(test_spike_runner_contract test_spike_runner_contract.cpp + ../spike/common/SpikeRunner.cpp) +target_include_directories(test_spike_runner_contract PRIVATE + ${CMAKE_SOURCE_DIR}/spike) target_compile_features(test_spike_runner_contract PRIVATE cxx_std_20) freepdfeditor_apply_warnings(test_spike_runner_contract) freepdfeditor_apply_hardening(test_spike_runner_contract)