flan/test/dev_limits.c

132 lines
4.9 KiB
C

/* dev_limits.c — the two fixed-size limits in runtime/flan_dev.c, driven
* directly.
*
* Neither is reachable from a Flan program that behaves. The result buffer
* only truncates when a renderer emits more than 4K, which no fixture does,
* and the name table only fills after 4096 distinct run-time-introduced names,
* which is more forms than the whole corpus has. So both were code nothing had
* ever run: the first thing either would do in anger is either lose the end of
* a value or scribble past the end of a static array, and the ellipsis and the
* abort are the two behaviours that say which.
*
* A C main, for the same reason reload_host.c is one: these are C entry points
* with no Flan spelling, and flan_dev.c is compiled into every build (see
* build.ml), so linking any Flan program brings them along. The .flan this is
* linked against therefore has no [main] of its own.
*
* One mode per run, chosen by argv, because both of them are one-way: the
* name table never shrinks and the overflow case aborts the process.
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void **flan_dev_cell(const char *name);
void flan_dev_result_begin(void);
void flan_dev_emit(const uint8_t *bytes, int64_t len);
void flan_dev_result_end(void);
uint64_t flan_dev_result_cap(void);
int flan_dev_result_read(char *dst, uint64_t cap, uint64_t *gen, uint64_t *len);
/* [flan_dev_result_get] was replaced by a real seqlock, which copies into the
* caller's buffer rather than handing back a pointer into one being written.
* The buffer is sized from [flan_dev_result_cap] rather than from CAP below,
* which is this test's own expectation of the answer: sizing it from the
* number under test would make a wrong cap copy the wrong amount and agree
* with itself. */
static char rbuf[1 << 16];
static const char *result_read(uint64_t *gen, uint64_t *len) {
if (flan_dev_result_cap() > sizeof rbuf) {
printf("result buffer too small for cap %llu\n",
(unsigned long long)flan_dev_result_cap());
exit(2);
}
if (!flan_dev_result_read(rbuf, sizeof rbuf, gen, len)) {
/* Single-threaded here: the writer is this thread, so a failed read means
* the counter was left odd, not that a write was racing. */
printf("result read did not settle\n");
exit(2);
}
return rbuf;
}
void flan_rt_init(int32_t argc, char **argv);
#define CAP 4096
/* Emit more than fits, twice the buffer's worth, and report what came back.
* What is printed is everything a wrong cap gets wrong: the length, the three
* characters that say it was truncated, a byte from the middle to show the
* content up to the cut is the content that was emitted, and the generation
* counter, which is what a reader waits on and must move exactly once. */
static int cap(void) {
static uint8_t a[3000], b[3000];
uint64_t gen0, gen1, len;
const char *r;
memset(a, 'a', sizeof a);
memset(b, 'b', sizeof b);
r = result_read(&gen0, &len);
flan_dev_result_begin();
flan_dev_emit(a, (int64_t)sizeof a);
/* A negative length is not a huge one: the cast to size_t would make it
* about 2^64 and the clamp would then be the only thing between it and a
* memcpy of everything. */
flan_dev_emit(b, -1);
flan_dev_emit(b, (int64_t)sizeof b);
flan_dev_result_end();
r = result_read(&gen1, &len);
printf("len %llu\n", (unsigned long long)len);
printf("tail %.3s\n", len >= 3 ? r + len - 3 : "");
printf("mid %c\n", len > 3500 ? r[3500] : '?');
printf("head %c\n", len > 0 ? r[0] : '?');
printf("gen %llu\n", (unsigned long long)(gen1 - gen0));
/* And a short value after a truncated one: the flag has to be cleared by
* [begin] or every later render ends in an ellipsis it did not earn. */
flan_dev_result_begin();
flan_dev_emit((const uint8_t *)"12", 2);
flan_dev_result_end();
r = result_read(&gen1, &len);
printf("again %.*s\n", (int)len, r);
return 0;
}
/* Fill the name table and then ask for one more. The table is fixed and never
* moves — a module holds the address of a cell for as long as it is loaded —
* so the only honest answer past the end is to stop. */
static int names(void) {
char buf[32];
for (int i = 0; i < CAP; i++) {
snprintf(buf, sizeof buf, "n%d", i);
if (flan_dev_cell(buf) == NULL) {
printf("null cell at %d\n", i);
return 1;
}
}
/* Distinct names, all of them: a table that deduplicated wrongly would not
* be full here and the line below would not abort. */
printf("interned %d\n", CAP);
fflush(stdout);
(void)flan_dev_cell("one-too-many");
printf("survived\n");
return 0;
}
int main(int argc, char **argv) {
flan_rt_init(argc, argv);
if (argc < 2) {
fprintf(stderr, "usage: %s cap|names\n", argv[0]);
return 2;
}
if (strcmp(argv[1], "cap") == 0) return cap();
if (strcmp(argv[1], "names") == 0) return names();
fprintf(stderr, "unknown mode %s\n", argv[1]);
return 2;
}