Neither limit had any coverage: a renderer that emits more than 4K and a program that introduces more than 4096 run-time names are both past anything the corpus does, so the truncation and the abort were code that had never executed. dev_limits.c drives them directly — they are C entry points with no Flan spelling, and flan_dev.c is compiled into every build — one process per mode, because the name table never shrinks and the overflow case aborts. The cap case pins the length, the ellipsis, a byte from before the cut, the generation moving exactly once, and the flag being cleared so a short value after a truncated one does not inherit its ellipsis. The registry case pins that 4096 fit and the next one stops the process with its reason. Dropping result_full and moving the slot check by one were both planted and watched fail.
108 lines
3.9 KiB
C
108 lines
3.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);
|
|
const char *flan_dev_result_get(uint64_t *gen, uint64_t *len);
|
|
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 = flan_dev_result_get(&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 = flan_dev_result_get(&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 = flan_dev_result_get(&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;
|
|
}
|