#include #include #include #include #include #include #include #include #include #include #include namespace { struct TempDir { std::filesystem::path path; TempDir() : path(std::filesystem::temp_directory_path() / ("fold-swift-frontend-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()))) { std::filesystem::create_directories(path); } ~TempDir() { std::filesystem::remove_all(path); } }; struct SwiftScanResult { int exit_code = 0; nlohmann::json report; std::string log; }; void writeFile(const std::filesystem::path& path, const std::string& content) { std::ofstream(path) << content; } void writeExecutable(const std::filesystem::path& path, const std::string& content) { writeFile(path, content); std::filesystem::permissions(path, std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | std::filesystem::perms::owner_exec, std::filesystem::perm_options::replace); } std::string readFile(const std::filesystem::path& path) { std::ifstream in(path); std::ostringstream out; out << in.rdbuf(); return out.str(); } std::string shellQuote(const std::filesystem::path& path) { std::string out = "'"; for (char c : path.string()) { if (c == '\'') out += "'\\''"; else out += c; } out += "'"; return out; } std::filesystem::path locateFoldBinary() { if (const char* env = std::getenv("FOLD_BINARY")) { if (*env && std::filesystem::exists(env)) return std::filesystem::canonical(env); } auto cwd = std::filesystem::current_path(); for (const auto& rel : {"../../src/cli/fold", "../../../src/cli/fold", "../src/cli/fold"}) { auto candidate = cwd / rel; if (std::filesystem::exists(candidate)) return std::filesystem::canonical(candidate); } return {}; } std::filesystem::path locateFoldSwiftBinary() { if (const char* env = std::getenv("FOLD_SWIFT_BIN")) { if (*env && std::filesystem::exists(env)) return std::filesystem::canonical(env); } auto cwd = std::filesystem::current_path(); for (const auto& rel : {"../../tools/fold-swift/fold-swift", "../../../tools/fold-swift/fold-swift", "../tools/fold-swift/fold-swift"}) { auto candidate = cwd / rel; if (std::filesystem::exists(candidate)) return std::filesystem::canonical(candidate); } return {}; } bool swiftcAvailable() { return std::system("command -v swiftc >/dev/null 2>&1") == 0; } std::optional runSwiftScan(const std::string& source_text, const std::string& detectors = {}, const std::map& env = {}) { const auto fold_bin = locateFoldBinary(); const auto fold_swift_bin = env.contains("FOLD_SWIFT_BIN") ? std::filesystem::path(env.at("FOLD_SWIFT_BIN")) : locateFoldSwiftBinary(); const bool need_swiftc = !env.contains("FOLD_SWIFT_BIN"); if (fold_bin.empty() || fold_swift_bin.empty() || (need_swiftc && !swiftcAvailable())) { WARN("fold, fold-swift, or swiftc missing; skipping Swift frontend integration test"); return std::nullopt; } TempDir dir; const auto source = dir.path / "input.swift"; writeFile(source, source_text); const auto output = dir.path / "report.json"; const auto log = dir.path / "scan.log"; std::string cmd = "FOLD_SWIFT_BIN=" + shellQuote(fold_swift_bin) + " "; for (const auto& [key, value] : env) if (key != "FOLD_SWIFT_BIN") cmd += key + "=" + shellQuote(value) + " "; cmd += shellQuote(fold_bin) + " scan --mode=swift "; if (!detectors.empty()) cmd += "--detectors " + detectors + " "; cmd += shellQuote(source) + " --format sarif --output " + shellQuote(output) + " > " + shellQuote(log) + " 2>&1"; SwiftScanResult result; result.exit_code = std::system(cmd.c_str()); result.log = readFile(log); if (std::filesystem::exists(output)) result.report = nlohmann::json::parse(std::ifstream(output)); return result; } const nlohmann::json& scanResults(const SwiftScanResult& scan) { return scan.report.at("runs").at(0).at("results"); } // Swift source that lowers to SIL with an unsafe-pointer load and an // unchecked bit cast, exercised by both the structured and fallback paths. const char* kUnsafeSwift = R"swift( func rawLoad(_ p: UnsafePointer) -> UInt8 { p.pointee } class Box {} func castIt(_ b: Box) -> UnsafeRawPointer { unsafeBitCast(b, to: UnsafeRawPointer.self) } )swift"; } // namespace TEST_CASE("swift frontend respects detector selection", "[swift][frontend]") { auto scan = runSwiftScan(kUnsafeSwift, "div-by-zero"); if (!scan) return; REQUIRE(scan->exit_code == 0); // no selected detector emitted findings REQUIRE(scanResults(*scan).empty()); } TEST_CASE("swift frontend emits Swift-specific rule IDs", "[swift][frontend]") { auto scan = runSwiftScan(kUnsafeSwift); if (!scan) return; REQUIRE(scan->exit_code != 0); // exit 1: actionable findings const auto& results = scanResults(*scan); REQUIRE(!results.empty()); bool saw_load = false; bool saw_cast = false; for (const auto& result : results) { const auto rule = result.at("ruleId").get(); // Regression: Swift findings must never be relabeled as Rust rules // (the swift backend previously mapped every kind to Rust*). REQUIRE(rule.find("fold.rust-") != 0); REQUIRE(result.at("properties").value("backend", std::string{}) == "swift"); saw_load = saw_load || rule == "fold.swift-unsafe-load"; saw_cast = saw_cast || rule == "fold.swift-unsafe-cast"; } REQUIRE(saw_load); REQUIRE(saw_cast); } TEST_CASE("swift frontend honors the swift-security detector group", "[swift][frontend]") { auto scan = runSwiftScan(kUnsafeSwift, "swift-security"); if (!scan) return; REQUIRE(scan->exit_code != 0); REQUIRE(!scanResults(*scan).empty()); } TEST_CASE("swift frontend surfaces compiler failures in the log", "[swift][frontend]") { auto scan = runSwiftScan("func broken( {\n", {}); if (!scan) return; REQUIRE(scan->exit_code != 0); REQUIRE(scan->log.find("swiftc") != std::string::npos); } TEST_CASE("swift frontend fails closed on nonzero fake frontend exit", "[swift][frontend]") { TempDir dir; const auto fake = dir.path / "fake-fold-swift.sh"; writeExecutable(fake, "#!/bin/sh\n" "printf '%s\\n' '{\"kind\":\"error\",\"data\":{\"message\":\"synthetic " "swift frontend failure\"}}'\n" "printf '%s\\n' " "'{\"kind\":\"function\",\"data\":{\"qualified_name\":\"demo\",\"source_" "file\":\"input.swift\",\"start_line\":1,\"end_line\":1}}'\n" "exit 9\n"); auto scan = runSwiftScan("func demo() {}\n", {}, {{"FOLD_SWIFT_BIN", fake.string()}}); if (!scan) return; REQUIRE(scan->exit_code != 0); REQUIRE(scan->log.find("scan failed:") != std::string::npos); REQUIRE(scan->log.find("synthetic swift frontend failure") != std::string::npos); REQUIRE(scan->log.find("exit status 9") != std::string::npos); } TEST_CASE("swift frontend fails closed on truncated fake frontend output", "[swift][frontend]") { TempDir dir; const auto fake = dir.path / "fake-fold-swift-truncate.sh"; writeExecutable(fake, "#!/bin/sh\n" "printf '%s\\n' '{\"kind\":\"error\",\"data\":{\"message\":\"synthetic " "swift truncation\"}}'\n" "i=0\n" "while [ \"$i\" -lt 1000 ]; do\n" " printf '%s\\n' " "'{\"kind\":\"function\",\"data\":{\"qualified_name\":\"demo\",\"source_" "file\":\"input.swift\",\"start_line\":1,\"end_line\":1}}'\n" " i=$((i + 1))\n" "done\n"); auto scan = runSwiftScan( "func demo() {}\n", {}, {{"FOLD_SWIFT_BIN", fake.string()}, {"FOLD_FRONTEND_SUBPROCESS_MAX_OUTPUT_BYTES", "128"}}); if (!scan) return; REQUIRE(scan->exit_code != 0); REQUIRE(scan->log.find("scan failed:") != std::string::npos); REQUIRE(scan->log.find("output truncated") != std::string::npos); REQUIRE(scan->log.find("synthetic swift truncation") != std::string::npos); } TEST_CASE("swift frontend does not execute shell metacharacters in paths", "[swift][frontend]") { const auto fold_bin = locateFoldBinary(); const auto fold_swift_bin = locateFoldSwiftBinary(); if (fold_bin.empty() || fold_swift_bin.empty() || !swiftcAvailable()) { WARN("fold, fold-swift, or swiftc missing; skipping Swift frontend integration test"); return; } TempDir dir; // A filename that would inject a shell command if any layer of the // scan pipeline shelled out unescaped. The sentinel must never appear. const auto sentinel = dir.path / "pwned"; const auto evil = dir.path / ("evil'; touch " + sentinel.string() + "; echo '.swift"); writeFile(evil, "func f(_ p: UnsafePointer) -> UInt8 { p.pointee }\n"); const auto output = dir.path / "report.json"; const auto log = dir.path / "scan.log"; std::string cmd = "FOLD_SWIFT_BIN=" + shellQuote(fold_swift_bin) + " " + shellQuote(fold_bin) + " scan --mode=swift " + shellQuote(evil) + " --format sarif --output " + shellQuote(output) + " > " + shellQuote(log) + " 2>&1"; std::system(cmd.c_str()); REQUIRE(!std::filesystem::exists(sentinel)); }