fold/tests/unit/test_prediction_match.cpp
2026-07-20 14:53:14 -04:00

171 lines
5.6 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include <vector>
#include "fold/fuzz/prediction_match.hpp"
TEST_CASE("fuzz correlation selects the hazard compatible with the sanitizer crash",
"[fuzz][prediction-match]") {
fold::fuzz::AsanReport report;
report.kind = "heap-buffer-overflow";
report.file = "/tmp/project/target.cpp";
report.line = 42;
report.column = 7;
std::vector<fold::fuzz::FindingPrediction> predictions = {
{"null-deref:/tmp/project/target.cpp:42:7",
"target",
fold::FindingKind::NullDeref,
{"/tmp/project/target.cpp", 42, 7}},
{"oob-access:/tmp/project/target.cpp:42:7",
"target",
fold::FindingKind::OOBAccess,
{"/tmp/project/target.cpp", 42, 7}},
};
auto matched = fold::fuzz::matchSanitizerReport(report, predictions);
REQUIRE(matched == 1);
}
TEST_CASE("fuzz correlation rejects same-basename crashes from another directory",
"[fuzz][prediction-match]") {
fold::fuzz::AsanReport report;
report.kind = "SEGV";
report.file = "/tmp/other/target.cpp";
report.line = 12;
std::vector<fold::fuzz::FindingPrediction> predictions = {
{"null-deref:/tmp/project/target.cpp:12:0",
"target",
fold::FindingKind::NullDeref,
{"/tmp/project/target.cpp", 12, 0}},
};
REQUIRE_FALSE(fold::fuzz::matchSanitizerReport(report, predictions));
}
TEST_CASE("fuzz correlation accepts relative ASan source paths", "[fuzz][prediction-match]") {
fold::fuzz::AsanReport report;
report.kind = "SEGV";
report.file = "fixture.c";
report.line = 6;
report.function = "leaky";
std::vector<fold::fuzz::FindingPrediction> predictions = {
{"null-deref:fixture.c:6:16",
"leaky",
fold::FindingKind::NullDeref,
{"/tmp/project/fixture.c", 6, 16}},
};
REQUIRE(fold::fuzz::matchSanitizerReport(report, predictions) == 0);
}
TEST_CASE("fuzz correlation rejects incompatible concrete failure classes",
"[fuzz][prediction-match]") {
fold::fuzz::AsanReport report;
report.kind = "heap-buffer-overflow";
report.file = "/tmp/project/target.cpp";
report.line = 9;
std::vector<fold::fuzz::FindingPrediction> predictions = {
{"null-deref:/tmp/project/target.cpp:9:0",
"target",
fold::FindingKind::NullDeref,
{"/tmp/project/target.cpp", 9, 0}},
};
REQUIRE_FALSE(fold::fuzz::matchSanitizerReport(report, predictions));
}
TEST_CASE("fuzz correlation accepts a unique compatible function for locationless reports",
"[fuzz][prediction-match]") {
fold::fuzz::AsanReport report;
report.kind = "SEGV";
report.function = "leaky+0x70";
std::vector<fold::fuzz::FindingPrediction> predictions = {
{"null-deref:fixture.c:6:16",
"leaky",
fold::FindingKind::NullDeref,
{"/tmp/project/fixture.c", 6, 16}},
{"oob-access:fixture.c:9:3",
"other",
fold::FindingKind::OOBAccess,
{"/tmp/project/fixture.c", 9, 3}},
};
REQUIRE(fold::fuzz::matchSanitizerReport(report, predictions) == 0);
}
TEST_CASE("fuzz correlation rejects ambiguous locationless reports", "[fuzz][prediction-match]") {
fold::fuzz::AsanReport report;
report.kind = "SEGV";
report.function = "leaky+0x70";
std::vector<fold::fuzz::FindingPrediction> predictions = {
{"null-deref:fixture.c:6:16",
"leaky",
fold::FindingKind::NullDeref,
{"/tmp/project/fixture.c", 6, 16}},
{"null-deref:fixture.c:8:16",
"leaky",
fold::FindingKind::NullDeref,
{"/tmp/project/fixture.c", 8, 16}},
};
REQUIRE_FALSE(fold::fuzz::matchSanitizerReport(report, predictions));
}
TEST_CASE("fuzz correlation accepts duplicate seeds for the same finding",
"[fuzz][prediction-match]") {
fold::fuzz::AsanReport report;
report.kind = "SEGV";
report.function = "leaky+0x70";
std::vector<fold::fuzz::FindingPrediction> predictions = {
{"null-deref:fixture.c:6:16",
"leaky",
fold::FindingKind::NullDeref,
{"/tmp/project/fixture.c", 6, 16}},
{"null-deref:fixture.c:6:16",
"leaky",
fold::FindingKind::NullDeref,
{"/tmp/project/fixture.c", 6, 16}},
};
REQUIRE(fold::fuzz::matchSanitizerReport(report, predictions) == 0);
}
TEST_CASE("fuzz correlation rejects a locationless report from another function",
"[fuzz][prediction-match]") {
fold::fuzz::AsanReport report;
report.kind = "SEGV";
report.function = "leaky_more+0x70";
std::vector<fold::fuzz::FindingPrediction> predictions = {
{"null-deref:fixture.c:6:16",
"leaky",
fold::FindingKind::NullDeref,
{"/tmp/project/fixture.c", 6, 16}},
};
REQUIRE_FALSE(fold::fuzz::matchSanitizerReport(report, predictions));
}
TEST_CASE("unpredicted crash IDs are stable and artifact-specific", "[fuzz][crash-id]") {
fold::fuzz::AsanReport report;
report.kind = "SEGV";
report.function = "parse input+0x70";
const auto first =
fold::fuzz::unpredictedCrashFindingId(report, "/tmp/run/crashes/crash-0123456789abcdef");
const auto repeated =
fold::fuzz::unpredictedCrashFindingId(report, "/tmp/run/crashes/crash-0123456789abcdef");
const auto other =
fold::fuzz::unpredictedCrashFindingId(report, "/tmp/run/crashes/crash-fedcba9876543210");
REQUIRE(first == repeated);
REQUIRE(first != other);
REQUIRE(first == "fuzz-crash:SEGV:parse-input+0x70:crash-0123456789abcdef");
REQUIRE(first.find("::0:0") == std::string::npos);
}