/* 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 #include #include #include 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; }