fold/tests/integration/test_swift_frontend.cpp
trvon b0b51f1e71
feat: converge quality and scan interfaces
- fail closed on MCP input and compile database policy
- add shared scan catalogs, finding queries, and tiered gates
2026-07-29 00:57:33 -04:00

270 lines
10 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include <chrono>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <map>
#include <nlohmann/json.hpp>
#include <optional>
#include <set>
#include <sstream>
#include <string>
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<SwiftScanResult> runSwiftScan(const std::string& source_text,
const std::string& detectors = {},
const std::map<std::string, std::string>& 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>) -> 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<std::string>();
// 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>) -> 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));
}