mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-06 13:30:00 +00:00
Add FreeBSD/POSIX lfind and lsearch for the libc PRX, with explicit guards against null arguments, zero element size, and span arithmetic that would overflow size_t. lsearch validates the existing search range before any comparator call, then separately checks the append capacity so a matched element never requires room for an extra one. Extend guest_search to cover first-match on duplicate keys, zero-count no-op, byte-exact appended payload, zero-count seed, and that the overflow guard fires without touching element memory or invoking the callback. Document the exports in docs/dev/debt/libc.md. Co-Authored-By: Claude Opus 4.7 <[email protected]>
122 lines
5.8 KiB
C++
122 lines
5.8 KiB
C++
#include "prx/libc/include/Api.hpp"
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include <array>
|
|
#include <cstdlib>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
#include <cstring>
|
|
#include <cstdio>
|
|
#include <source_location>
|
|
using Comparator = int (APS5_VABI *)(const void*, const void*);
|
|
|
|
static void Require(bool value, std::source_location location = std::source_location::current()) {
|
|
if (!value) { std::fprintf(stderr, "Guest search failed at line %u\n", location.line()); std::abort(); }
|
|
}
|
|
struct Record { int key; char payload[13]; };
|
|
static int calls = 0;
|
|
static int APS5_VABI Compare(const void* key, const void* record) {
|
|
++calls;
|
|
const int left = *static_cast<const int*>(key);
|
|
const int right = static_cast<const Record*>(record)->key;
|
|
return (left > right) - (left < right);
|
|
}
|
|
int main() {
|
|
const std::array<Record, 6> records{{{1, "one"}, {3, "three"}, {5, "five"},
|
|
{5, "duplicate"}, {7, "seven"}, {9, "nine"}}};
|
|
std::array<unsigned char, sizeof(records)> before{};
|
|
std::memcpy(before.data(), records.data(), sizeof(records));
|
|
for (int key = 0; key <= 10; ++key) {
|
|
calls = 0;
|
|
const auto* found = static_cast<Record*>(bsearch_nid_postfix(&key, records.data(), records.size(), sizeof(Record), Compare));
|
|
Require(calls <= 3);
|
|
if (key % 2) Require(found && found->key == key && found >= records.data() && found < records.data() + records.size());
|
|
else Require(!found);
|
|
}
|
|
Require(std::memcmp(before.data(), records.data(), sizeof(records)) == 0);
|
|
calls = 0;
|
|
int key = 1;
|
|
Require(bsearch_nid_postfix(&key, nullptr, 0, sizeof(Record), Compare) == nullptr && calls == 0);
|
|
Require(bsearch_nid_postfix(&key, records.data(), 1, sizeof(Record), Compare) == records.data());
|
|
key = 2;
|
|
Require(!bsearch_nid_postfix(&key, records.data(), 1, sizeof(Record), Compare));
|
|
bool rejected = false;
|
|
try { bsearch_nid_postfix(&key, records.data(), std::numeric_limits<std::size_t>::max(), sizeof(Record), Compare); }
|
|
catch (const std::overflow_error&) { rejected = true; }
|
|
Require(rejected);
|
|
|
|
{
|
|
std::array<Record, 6> table = records;
|
|
std::size_t nel = table.size();
|
|
calls = 0;
|
|
int probe = 5;
|
|
auto* hit = static_cast<Record*>(lfind_nid_postfix(&probe, table.data(), &nel, sizeof(Record), Compare));
|
|
// Duplicate key=5 lives at indexes 2 and 3; lfind must return the first.
|
|
Require(hit && hit == table.data() + 2 && calls == 3);
|
|
Require(nel == table.size());
|
|
Require(std::strcmp(hit->payload, "five") == 0);
|
|
probe = 4;
|
|
calls = 0;
|
|
Require(lfind_nid_postfix(&probe, table.data(), &nel, sizeof(Record), Compare) == nullptr);
|
|
Require(calls == static_cast<int>(table.size()));
|
|
std::size_t empty = 0;
|
|
calls = 0;
|
|
Require(lfind_nid_postfix(&probe, table.data(), &empty, sizeof(Record), Compare) == nullptr);
|
|
Require(calls == 0);
|
|
}
|
|
|
|
{
|
|
std::array<Record, 8> buffer{};
|
|
for (std::size_t index = 0; index < records.size(); ++index) buffer[index] = records[index];
|
|
const auto before_lsearch = buffer;
|
|
std::size_t nel = records.size();
|
|
Record present{5, "already"};
|
|
calls = 0;
|
|
auto* found = static_cast<Record*>(lsearch_nid_postfix(&present.key, buffer.data(), &nel, sizeof(Record), Compare));
|
|
// Match on duplicate key=5 returns the first occurrence and leaves memory intact.
|
|
Require(found == buffer.data() + 2 && nel == records.size() && calls == 3);
|
|
Require(std::memcmp(buffer.data(), before_lsearch.data(), sizeof(buffer)) == 0);
|
|
Record fresh{4, "appended"};
|
|
calls = 0;
|
|
auto* appended = static_cast<Record*>(lsearch_nid_postfix(&fresh.key, buffer.data(), &nel, sizeof(Record), Compare));
|
|
Require(appended == buffer.data() + records.size());
|
|
Require(appended->key == 4 && nel == records.size() + 1);
|
|
Require(calls == static_cast<int>(records.size()));
|
|
// Full byte copy of the key, not just its first field.
|
|
Require(std::memcmp(appended, &fresh, sizeof(Record)) == 0);
|
|
std::size_t zero = 0;
|
|
Record first{42, "first"};
|
|
calls = 0;
|
|
auto* seeded = static_cast<Record*>(lsearch_nid_postfix(&first.key, buffer.data() + 7, &zero, sizeof(Record), Compare));
|
|
Require(seeded == buffer.data() + 7 && zero == 1 && calls == 0);
|
|
Require(std::memcmp(seeded, &first, sizeof(Record)) == 0);
|
|
}
|
|
|
|
{
|
|
bool caught = false;
|
|
std::size_t nel = 1;
|
|
try { lfind_nid_postfix(nullptr, records.data(), &nel, sizeof(Record), Compare); }
|
|
catch (const std::invalid_argument&) { caught = true; }
|
|
Require(caught);
|
|
caught = false;
|
|
try { lsearch_nid_postfix(&key, nullptr, &nel, sizeof(Record), Compare); }
|
|
catch (const std::invalid_argument&) { caught = true; }
|
|
Require(caught);
|
|
caught = false;
|
|
try { lfind_nid_postfix(&key, records.data(), nullptr, sizeof(Record), Compare); }
|
|
catch (const std::invalid_argument&) { caught = true; }
|
|
Require(caught);
|
|
// Overflow guard must fire before the comparator or any element memory is touched.
|
|
std::size_t huge = std::numeric_limits<std::size_t>::max();
|
|
calls = 0;
|
|
caught = false;
|
|
try { lfind_nid_postfix(&key, records.data(), &huge, sizeof(Record), Compare); }
|
|
catch (const std::overflow_error&) { caught = true; }
|
|
Require(caught && calls == 0 && huge == std::numeric_limits<std::size_t>::max());
|
|
caught = false;
|
|
Record scratch{};
|
|
try { lsearch_nid_postfix(&key, &scratch, &huge, sizeof(Record), Compare); }
|
|
catch (const std::overflow_error&) { caught = true; }
|
|
Require(caught && calls == 0 && huge == std::numeric_limits<std::size_t>::max());
|
|
}
|
|
}
|