summaryrefslogtreecommitdiffstats
path: root/test/radix/terp.c
diff options
context:
space:
mode:
Diffstat (limited to 'test/radix/terp.c')
-rw-r--r--test/radix/terp.c882
1 files changed, 882 insertions, 0 deletions
diff --git a/test/radix/terp.c b/test/radix/terp.c
new file mode 100644
index 0000000000..06aa0aa515
--- /dev/null
+++ b/test/radix/terp.c
@@ -0,0 +1,882 @@
+/*
+ * Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+#include <openssl/ssl.h>
+#include <openssl/quic.h>
+#include <openssl/bio.h>
+#include <openssl/lhash.h>
+#include <openssl/rand.h>
+#include "../testutil.h"
+#include "internal/numbers.h" /* UINT64_C */
+#include "internal/time.h" /* OSSL_TIME */
+
+static const char *cert_file, *key_file;
+
+/*
+ * TERP - Test Executive Script Interpreter
+ * ========================================
+ */
+typedef struct gen_ctx_st GEN_CTX;
+
+typedef void (*script_gen_t)(GEN_CTX *ctx);
+
+typedef struct script_info_st {
+ /* name: A symbolic name, like simple_conn. */
+ const char *name;
+ /* desc: A short, one-line description. */
+ const char *desc;
+ const char *file;
+ int line;
+ /* gen_func: The script generation function. */
+ script_gen_t gen_func;
+} SCRIPT_INFO;
+
+struct gen_ctx_st {
+ SCRIPT_INFO *script_info;
+ const char *cur_file;
+ int error, cur_line;
+ const char *first_error_msg, *first_error_file;
+ int first_error_line;
+
+ uint8_t *build_buf_beg, *build_buf_cur, *build_buf_end;
+};
+
+static int GEN_CTX_init(GEN_CTX *ctx, SCRIPT_INFO *script_info)
+{
+ ctx->script_info = script_info;
+ ctx->error = 0;
+ ctx->cur_file = NULL;
+ ctx->cur_line = 0;
+ ctx->first_error_msg = NULL;
+ ctx->first_error_line = 0;
+ ctx->build_buf_beg = NULL;
+ ctx->build_buf_cur = NULL;
+ ctx->build_buf_end = NULL;
+ return 1;
+}
+
+static void GEN_CTX_cleanup(GEN_CTX *ctx)
+{
+ OPENSSL_free(ctx->build_buf_beg);
+ ctx->build_buf_beg = ctx->build_buf_cur = ctx->build_buf_end = NULL;
+}
+
+typedef struct terp_st TERP;
+
+#define F_RET_SPIN_AGAIN 2
+#define F_RET_SKIP_REST 3
+
+#define F_SPIN_AGAIN() \
+ do { \
+ ok = F_RET_SPIN_AGAIN; \
+ fctx->spin_again = 1; \
+ goto err; \
+ } while (0)
+
+#define F_SKIP_REST() \
+ do { \
+ ok = F_RET_SKIP_REST; \
+ fctx->skip_rest = 1; \
+ goto err; \
+ } while (0)
+
+typedef struct func_ctx_st {
+ TERP *terp;
+
+ /*
+ * Set to 1 inside a user function if the function should spin again.
+ * Cleared automatically after the user function returns.
+ */
+ int spin_again;
+
+ /*
+ * Immediately exit script successfully. Useful for skipping.
+ */
+ int skip_rest;
+} FUNC_CTX;
+
+static ossl_inline int TERP_stk_pop(TERP *terp,
+ void *buf, size_t buf_len);
+
+#define TERP_STK_PUSH(terp, v) \
+ do { \
+ if (!TEST_true(TERP_stk_push((terp), &(v), sizeof(v)))) \
+ goto err; \
+ } while (0)
+
+#define TERP_STK_POP(terp, v) \
+ do { \
+ if (!TEST_true(TERP_stk_pop((terp), &(v), sizeof(v)))) \
+ goto err; \
+ } while (0)
+
+#define TERP_STK_POP2(terp, a, b) \
+ do { \
+ TERP_STK_POP((terp), (b)); \
+ TERP_STK_POP((terp), (a)); \
+ } while (0)
+
+#define F_PUSH(v) TERP_STK_PUSH(fctx->terp, (v))
+#define F_POP(v) TERP_STK_POP (fctx->terp, (v))
+#define F_POP2(a, b) TERP_STK_POP2(fctx->terp, (a), (b))
+
+typedef int (*helper_func_t)(FUNC_CTX *fctx);
+
+#define DEF_FUNC(name) ossl_unused static int name(FUNC_CTX *fctx)
+
+#define DEF_SCRIPT(name, desc) \
+ static void script_gen_##name(GEN_CTX *ctx); \
+ static SCRIPT_INFO script_info_##name = { \
+ #name, desc, __FILE__, __LINE__, \
+ script_gen_##name \
+ }; \
+ static void script_gen_##name(GEN_CTX *ctx)
+
+enum {
+ OPK_INVALID,
+ OPK_END,
+ OPK_PUSH_P,
+ /*
+ * This is exactly like PUSH_P, but the script dumper knows the pointer
+ * points to a static NUL-terminated string and can therefore print it.
+ */
+ OPK_PUSH_PZ,
+ OPK_PUSH_U64,
+ /*
+ * Could use OPK_PUSH_U64 for this but it's annoying to have to avoid using
+ * size_t in case it is a different size.
+ */
+ OPK_PUSH_SIZE,
+ OPK_FUNC,
+ OPK_LABEL
+};
+
+static void *openc_alloc_space(GEN_CTX *ctx, size_t num_bytes);
+
+#define DEF_ENCODER(name, type) \
+ static void name(GEN_CTX *ctx, type v) \
+ { \
+ void *dst = openc_alloc_space(ctx, sizeof(v)); \
+ if (dst == NULL) \
+ return; \
+ \
+ memcpy(dst, &v, sizeof(v)); \
+ }
+
+DEF_ENCODER(openc_u64, uint64_t)
+DEF_ENCODER(openc_size, size_t)
+DEF_ENCODER(openc_p, void *)
+DEF_ENCODER(openc_fp, helper_func_t)
+#define openc_opcode openc_u64
+
+static void opgen_END(GEN_CTX *ctx)
+{
+ openc_opcode(ctx, OPK_END);
+}
+
+static ossl_unused void opgen_PUSH_P(GEN_CTX *ctx, void *p)
+{
+ openc_opcode(ctx, OPK_PUSH_P);
+ openc_p(ctx, p);
+}
+
+static void opgen_PUSH_PZ(GEN_CTX *ctx, void *p)
+{
+ openc_opcode(ctx, OPK_PUSH_PZ);
+ openc_p(ctx, p);
+}
+
+static void opgen_PUSH_U64(GEN_CTX *ctx, uint64_t v)
+{
+ openc_opcode(ctx, OPK_PUSH_U64);
+ openc_u64(ctx, v);
+}
+
+ossl_unused static void opgen_PUSH_SIZE(GEN_CTX *ctx, size_t v)
+{
+ openc_opcode(ctx, OPK_PUSH_SIZE);
+ openc_size(ctx, v);
+}
+
+ossl_unused static void opgen_FUNC(GEN_CTX *ctx, helper_func_t f,
+ const char *f_name)
+{
+ openc_opcode(ctx, OPK_FUNC);
+ openc_fp(ctx, f);
+ openc_p(ctx, (void *)f_name);
+}
+
+ossl_unused static void opgen_LABEL(GEN_CTX *ctx, const char *name)
+{
+ openc_opcode(ctx, OPK_LABEL);
+ openc_p(ctx, (void *)name);
+}
+
+static void opgen_set_line(GEN_CTX *ctx, const char *file, int line)
+{
+ ctx->cur_file = file;
+ ctx->cur_line = line;
+}
+
+static ossl_unused void opgen_fail(GEN_CTX *ctx, const char *msg)
+{
+ if (!ctx->error) {
+ ctx->first_error_file = ctx->cur_file;
+ ctx->first_error_line = ctx->cur_line;
+ ctx->first_error_msg = msg;
+ }
+
+ ctx->error = 1;
+}
+
+#define OPGEN(n) (opgen_set_line(ctx, __FILE__, __LINE__), \
+ opgen_##n)
+#define OP_END() OPGEN(END) (ctx)
+#define OP_PUSH_P(v) OPGEN(PUSH_P) (ctx, (v))
+#define OP_PUSH_PZ(v) OPGEN(PUSH_PZ) (ctx, (v))
+#define OP_PUSH_U64(v) OPGEN(PUSH_U64) (ctx, (v))
+#define OP_PUSH_SIZE(v) OPGEN(PUSH_SIZE) (ctx, (v))
+#define OP_PUSH_BUFP(p, l) (OP_PUSH_P(p), OP_PUSH_SIZE(l))
+#define OP_PUSH_BUF(v) OP_PUSH_BUFP(&(v), sizeof(v))
+#define OP_PUSH_LREF(v) OPGEN(PUSH_LREF)(ctx, (lref))
+#define OP_FUNC(f) OPGEN(FUNC) (ctx, (f), #f)
+#define OP_LABEL(name) OPGEN(LABEL) (ctx, (name))
+#define GEN_FAIL(msg) OPGEN(fail) (ctx, (msg))
+
+static void *openc_alloc_space(GEN_CTX *ctx, size_t num_bytes)
+{
+ void *p;
+ size_t cur_spare, old_size, new_size, off;
+
+ cur_spare = ctx->build_buf_end - ctx->build_buf_cur;
+ if (cur_spare < num_bytes) {
+ off = ctx->build_buf_cur - ctx->build_buf_beg;
+ old_size = ctx->build_buf_end - ctx->build_buf_beg;
+ new_size = (old_size == 0) ? 1024 : old_size * 2;
+ p = OPENSSL_realloc(ctx->build_buf_beg, new_size);
+ if (!TEST_ptr(p))
+ return NULL;
+
+ ctx->build_buf_beg = p;
+ ctx->build_buf_cur = ctx->build_buf_beg + off;
+ ctx->build_buf_end = ctx->build_buf_beg + new_size;
+ }
+
+ p = ctx->build_buf_cur;
+ ctx->build_buf_cur += num_bytes;
+ return p;
+}
+
+/*
+ * Script Interpreter
+ * ============================================================================
+ */
+typedef struct gen_script_st {
+ const uint8_t *buf;
+ size_t buf_len;
+} GEN_SCRIPT;
+
+static int GEN_CTX_finish(GEN_CTX *ctx, GEN_SCRIPT *script)
+{
+ script->buf = ctx->build_buf_beg;
+ script->buf_len = ctx->build_buf_cur - ctx->build_buf_beg;
+ ctx->build_buf_beg = ctx->build_buf_cur = ctx->build_buf_end = NULL;
+ return 1;
+}
+
+static void GEN_SCRIPT_cleanup(GEN_SCRIPT *script)
+{
+ OPENSSL_free((char *)script->buf);
+
+ script->buf = NULL;
+ script->buf_len = 0;
+}
+
+static int GEN_SCRIPT_init(GEN_SCRIPT *gen_script, SCRIPT_INFO *script_info)
+{
+ int ok = 0;
+ GEN_CTX gctx;
+
+ if (!TEST_true(GEN_CTX_init(&gctx, script_info)))
+ return 0;
+
+ script_info->gen_func(&gctx);
+ opgen_END(&gctx);
+
+ if (!TEST_false(gctx.error))
+ goto err;
+
+ if (!TEST_true(GEN_CTX_finish(&gctx, gen_script)))
+ goto err;
+
+ ok = 1;
+err:
+ if (!ok) {
+ if (gctx.error)
+ TEST_error("script generation failed: %s (at %s:%d)",
+ gctx.first_error_msg,
+ gctx.first_error_file,
+ gctx.first_error_line);
+
+ GEN_CTX_cleanup(&gctx);
+ }
+ return ok;
+}
+
+typedef struct srdr_st {
+ const uint8_t *beg, *cur, *end, *save_cur;
+} SRDR;
+
+static void SRDR_init(SRDR *rdr, const uint8_t *buf, size_t buf_len)
+{
+ rdr->beg = rdr->cur = buf;
+ rdr->end = rdr->beg + buf_len;
+ rdr->save_cur = NULL;
+}
+
+static ossl_inline int SRDR_get_operand(SRDR *srdr, void *buf, size_t buf_len)
+{
+ if (!TEST_size_t_ge(srdr->end - srdr->cur, buf_len))
+ return 0; /* malformed script */
+
+ memcpy(buf, srdr->cur, buf_len);
+ srdr->cur += buf_len;
+ return 1;
+}
+
+static ossl_inline void SRDR_save(SRDR *srdr)
+{
+ srdr->save_cur = srdr->cur;
+}
+
+static ossl_inline void SRDR_restore(SRDR *srdr)
+{
+ srdr->cur = srdr->save_cur;
+}
+
+#define GET_OPERAND(srdr, v) \
+ do { \
+ if (!TEST_true(SRDR_get_operand(srdr, &(v), sizeof(v)))) \
+ goto err; \
+ } while (0)
+
+
+static void print_opc(BIO *bio, size_t op_num, size_t offset, const char *name)
+{
+ if (op_num != SIZE_MAX)
+ BIO_printf(bio, "%3zu- %4zx>\t%-8s \t", op_num,
+ offset, name);
+ else
+ BIO_printf(bio, " %4zx>\t%-8s \t",
+ offset, name);
+}
+
+static int SRDR_print_one(SRDR *srdr, BIO *bio, size_t i, int *was_end)
+{
+ int ok = 0;
+ const uint8_t *opc_start;
+ uint64_t opc;
+
+ if (was_end != NULL)
+ *was_end = 0;
+
+ opc_start = srdr->cur;
+ GET_OPERAND(srdr, opc);
+
+#define PRINT_OPC(name) print_opc(bio, i, (size_t)(opc_start - srdr->beg), #name)
+
+ switch (opc) {
+ case OPK_END:
+ PRINT_OPC(END);
+ opc_start = srdr->cur;
+ if (was_end != NULL)
+ *was_end = 1;
+ break;
+ case OPK_PUSH_P:
+ {
+ void *v;
+
+ GET_OPERAND(srdr, v);
+ PRINT_OPC(PUSH_P);
+ BIO_printf(bio, "%20p", v);
+ }
+ break;
+ case OPK_PUSH_PZ:
+ {
+ void *v;
+
+ GET_OPERAND(srdr, v);
+ PRINT_OPC(PUSH_P);
+ if (v != NULL && strlen((const char *)v) == 1)
+ BIO_printf(bio, "%20p (%s)", v, (const char *)v);
+ else
+ BIO_printf(bio, "%20p (\"%s\")", v, (const char *)v);
+ }
+ break;
+ case OPK_PUSH_U64:
+ {
+ uint64_t v;
+
+ GET_OPERAND(srdr, v);
+ PRINT_OPC(PUSH_U64);
+ BIO_printf(bio, "%#20llx (%lld)",
+ (unsigned long long)v, (unsigned long long)v);
+ }
+ break;
+ case OPK_PUSH_SIZE:
+ {
+ size_t v;
+
+ GET_OPERAND(srdr, v);
+ PRINT_OPC(PUSH_SIZE);
+ BIO_printf(bio, "%#20llx (%lld)",
+ (unsigned long long)v, (unsigned long long)v);
+ }
+ break;
+ case OPK_FUNC:
+ {
+ helper_func_t v;
+ void *f_name, *x;
+
+ GET_OPERAND(srdr, v);
+ GET_OPERAND(srdr, f_name);
+
+ PRINT_OPC(FUNC);
+ memcpy(&x, &v, sizeof(x) < sizeof(v) ? sizeof(x) : sizeof(v));
+ BIO_printf(bio, "%s", (const char *)f_name);
+ }
+ break;
+ case OPK_LABEL:
+ {
+ void *l_name;
+
+ GET_OPERAND(srdr, l_name);
+
+ BIO_printf(bio, "\n%s:\n", (const char *)l_name);
+ PRINT_OPC(LABEL);
+ }
+ break;
+ default:
+ TEST_error("unsupported opcode while printing: %llu",
+ (unsigned long long)opc);
+ goto err;
+ }
+
+ ok = 1;
+err:
+ return ok;
+}
+
+static int GEN_SCRIPT_print(GEN_SCRIPT *gen_script, BIO *bio,
+ const SCRIPT_INFO *script_info)
+{
+ int ok = 0;
+ size_t i;
+ SRDR srdr_v, *srdr = &srdr_v;
+ int was_end = 0;
+
+ SRDR_init(srdr, gen_script->buf, gen_script->buf_len);
+
+ if (script_info != NULL) {
+ BIO_printf(bio, "\nGenerated script for '%s':\n",
+ script_info->name);
+ BIO_printf(bio, "\n--GENERATED-------------------------------------"
+ "----------------------\n");
+ BIO_printf(bio, " # NAME:\n # %s\n",
+ script_info->name);
+ BIO_printf(bio, " # SOURCE:\n # %s:%d\n",
+ script_info->file, script_info->line);
+ BIO_printf(bio, " # DESCRIPTION:\n # %s\n", script_info->desc);
+ }
+
+ for (i = 0; !was_end; ++i) {
+ BIO_printf(bio, "\n");
+
+ if (!TEST_true(SRDR_print_one(srdr, bio, i, &was_end)))
+ goto err;
+ }
+
+ if (script_info != NULL) {
+ const unsigned char *opc_start = srdr->cur;
+
+ BIO_printf(bio, "\n");
+ PRINT_OPC(+++);
+ BIO_printf(bio, "\n------------------------------------------------"
+ "----------------------\n\n");
+ }
+
+ ok = 1;
+err:
+ return ok;
+}
+
+static void SCRIPT_INFO_print(SCRIPT_INFO *script_info, BIO *bio, int error,
+ const char *msg)
+{
+ if (error)
+ TEST_error("%s: script '%s' (%s)",
+ msg, script_info->name, script_info->desc);
+ else
+ TEST_info("%s: script '%s' (%s)",
+ msg, script_info->name, script_info->desc);
+}
+
+typedef struct terp_config_st {
+ BIO *debug_bio;
+
+ OSSL_TIME (*now_cb)(void *arg);
+ void *now_cb_arg;
+
+ int (*per_op_cb)(TERP *terp, void *arg);
+ void *per_op_cb_arg;
+
+ OSSL_TIME max_execution_time; /* duration */
+} TERP_CONFIG;
+
+#define TERP_DEFAULT_MAX_EXECUTION_TIME (ossl_ms2time(3000))
+
+struct terp_st {
+ TERP_CONFIG cfg;
+ const SCRIPT_INFO *script_info;
+ const GEN_SCRIPT *gen_script;
+ SRDR srdr;
+ uint8_t *stk_beg, *stk_cur, *stk_end, *stk_save_cur;
+ FUNC_CTX fctx;
+ uint64_t ops_executed;
+ int log_execute;
+ OSSL_TIME start_time, deadline_time;
+};
+
+static int TERP_init(TERP *terp,
+ const TERP_CONFIG *cfg,
+ const SCRIPT_INFO *script_info,
+ const GEN_SCRIPT *gen_script)
+{
+ if (!TEST_true(cfg->now_cb != NULL))
+ return 0;
+
+ terp->cfg = *cfg;
+ terp->script_info = script_info;
+ terp->gen_script = gen_script;
+ terp->fctx.terp = terp;
+ terp->fctx.spin_again = 0;
+ terp->fctx.skip_rest = 0;
+ terp->stk_beg = NULL;
+ terp->stk_cur = NULL;
+ terp->stk_end = NULL;
+ terp->stk_save_cur = NULL;
+ terp->ops_executed = 0;
+ terp->log_execute = 1;
+
+ if (ossl_time_is_zero(terp->cfg.max_execution_time))
+ terp->cfg.max_execution_time = TERP_DEFAULT_MAX_EXECUTION_TIME;
+
+ return 1;
+}
+
+static void TERP_cleanup(TERP *terp)
+{
+ if (terp->script_info == NULL)
+ return;
+
+ OPENSSL_free(terp->stk_beg);
+ terp->stk_beg = terp->stk_cur = terp->stk_end = NULL;
+ terp->script_info = NULL;
+}
+
+static int TERP_stk_ensure_capacity(TERP *terp, size_t spare)
+{
+ uint8_t *p;
+ size_t old_size, new_size, off;
+
+ old_size = terp->stk_end - terp->stk_beg;
+ if (old_size >= spare)
+ return 1;
+
+ off = terp->stk_end - terp->stk_cur;
+ new_size = old_size != 0 ? old_size * 2 : 256;
+ p = OPENSSL_realloc(terp->stk_beg, new_size);
+ if (!TEST_ptr(p))
+ return 0;
+
+ terp->stk_beg = p;
+ terp->stk_end = terp->stk_beg + new_size;
+ terp->stk_cur = terp->stk_end - off;
+ return 1;
+}
+
+static ossl_inline int TERP_stk_push(TERP *terp,
+ const void *buf, size_t buf_len)
+{
+ if (!TEST_true(TERP_stk_ensure_capacity(terp, buf_len)))
+ return 0;
+
+ terp->stk_cur -= buf_len;
+ memcpy(terp->stk_cur, buf, buf_len);
+ return 1;
+}
+
+static ossl_inline int TERP_stk_pop(TERP *terp,
+ void *buf, size_t buf_len)
+{
+ if (!TEST_size_t_ge(terp->stk_end - terp->stk_cur, buf_len)) {
+ asm("int3");
+ return 0;
+ }
+
+ memcpy(buf, terp->stk_cur, buf_len);
+ terp->stk_cur += buf_len;
+ return 1;
+}
+
+static void TERP_print_stack(TERP *terp, BIO *bio, const char *header)
+{
+ test_output_memory(header, terp->stk_cur, terp->stk_end - terp->stk_cur);
+ BIO_printf(bio, " (%zu bytes)\n", terp->stk_end - terp->stk_cur);
+ BIO_printf(bio, "\n");
+}
+
+#define TERP_GET_OPERAND(v) GET_OPERAND(&terp->srdr, (v))
+
+#define TERP_SPIN_AGAIN() \
+ do { \
+ SRDR_restore(&terp->srdr); \
+ terp->stk_cur = terp->stk_save_cur; \
+ ++spin_count; \
+ goto spin_again; \
+ } while (0)
+
+static OSSL_TIME TERP_now(TERP *terp)
+{
+ return terp->cfg.now_cb(terp->cfg.now_cb_arg);
+}
+
+static void TERP_log_spin(TERP *terp, size_t spin_count)
+{
+ if (spin_count > 0)
+ BIO_printf(terp->cfg.debug_bio, " \t\t(span %zu times)\n",
+ spin_count);
+}
+
+static int TERP_execute(TERP *terp)
+{
+ int ok = 0;
+ uint64_t opc;
+ size_t op_num = SIZE_MAX;
+ int in_debug_output = 0;
+ size_t spin_count = 0;
+ BIO *debug_bio = terp->cfg.debug_bio;
+
+ SRDR_init(&terp->srdr, terp->gen_script->buf, terp->gen_script->buf_len);
+
+ terp->start_time = TERP_now(terp);
+ terp->deadline_time = ossl_time_add(terp->start_time,
+ terp->cfg.max_execution_time);
+
+ for (;;) {
+ if (terp->log_execute) {
+ SRDR srdr_copy = terp->srdr;
+
+ if (!in_debug_output) {
+ BIO_printf(debug_bio, "\n--EXECUTION-----------------------------"
+ "------------------------------\n");
+ in_debug_output = 1;
+ }
+
+ TERP_log_spin(terp, spin_count);
+ if (!TEST_true(SRDR_print_one(&srdr_copy, debug_bio, SIZE_MAX, NULL)))
+ goto err;
+
+ BIO_printf(debug_bio, "\n");
+ }
+
+ TERP_GET_OPERAND(opc);
+ ++op_num;
+ SRDR_save(&terp->srdr);
+ terp->stk_save_cur = terp->stk_cur;
+ spin_count = 0;
+
+ ++terp->ops_executed;
+
+spin_again:
+ if (ossl_time_compare(TERP_now(terp), terp->deadline_time) >= 0) {
+ TEST_error("timed out while executing op %zu", op_num);
+ if (terp->log_execute)
+ TERP_log_spin(terp, spin_count);
+ goto err;
+ }
+
+ if (terp->cfg.per_op_cb != NULL)
+ if (!TEST_true(terp->cfg.per_op_cb(terp, terp->cfg.per_op_cb_arg))) {
+ TEST_error("pre-operation processing failed at op %zu", op_num);
+ if (terp->log_execute)
+ TERP_log_spin(terp, spin_count);
+ goto err;
+ }
+
+ switch (opc) {
+ case OPK_END:
+ goto stop;
+ case OPK_PUSH_P:
+ case OPK_PUSH_PZ:
+ {
+ void *v;
+
+ TERP_GET_OPERAND(v);
+ TERP_STK_PUSH(terp, v);
+ }
+ break;
+ case OPK_PUSH_U64:
+ {
+ uint64_t v;
+
+ TERP_GET_OPERAND(v);
+ TERP_STK_PUSH(terp, v);
+ }
+ break;
+ case OPK_PUSH_SIZE:
+ {
+ uint64_t v;
+
+ TERP_GET_OPERAND(v);
+ TERP_STK_PUSH(terp, v);
+ }
+ break;
+ case OPK_LABEL:
+ {
+ const char *l_name;
+
+ TERP_GET_OPERAND(l_name);
+ /* no-op */
+ }
+ break;
+ case OPK_FUNC:
+ {
+ helper_func_t v;
+ const void *f_name;
+ int ret;
+
+ TERP_GET_OPERAND(v);
+ TERP_GET_OPERAND(f_name);
+
+ if (!TEST_true(v != NULL))
+ goto err;
+
+ ret = v(&terp->fctx);
+
+ if (terp->fctx.skip_rest) {
+ if (!TEST_int_eq(ret, F_RET_SKIP_REST))
+ goto err;
+
+ if (terp->log_execute)
+ BIO_printf(terp->cfg.debug_bio, " \t\t(skipping)\n");
+
+ terp->fctx.skip_rest = 0;
+ goto stop;
+ } else if (terp->fctx.spin_again) {
+ if (!TEST_int_eq(ret, F_RET_SPIN_AGAIN))
+ goto err;
+
+ terp->fctx.spin_again = 0;
+ TERP_SPIN_AGAIN();
+ } else {
+ if (!TEST_false(terp->fctx.spin_again))
+ goto err;
+
+ if (ret != 1) {
+ TEST_error("op %zu (FUNC %s) failed with return value %d",
+ op_num, (const char *)f_name, ret);
+ goto err;
+ }
+ }
+ }
+ break;
+ default:
+ TEST_error("unknown opcode: %llu", (unsigned long long)opc);
+ goto err;
+ }
+ }
+
+stop:
+ ok = 1;
+err:
+ if (in_debug_output)
+ BIO_printf(debug_bio, "----------------------------------------"
+ "------------------------------\n");
+
+ if (!ok) {
+ TEST_error("FAILED while executing script: %s at op %zu, error stack:",
+ terp->script_info->name, op_num);
+ ERR_print_errors(terp->cfg.debug_bio);
+ BIO_printf(debug_bio, "\n");
+ } else if (ERR_peek_last_error() != 0) {
+ TEST_info("WARNING: errors on error stack despite success:");
+ ERR_print_errors(terp->cfg.debug_bio);
+ BIO_printf(debug_bio, "\n");
+ }
+
+ return ok;
+}
+
+static int TERP_run(SCRIPT_INFO *script_info, TERP_CONFIG *cfg)
+{
+ int ok = 0, have_terp = 0;
+ TERP terp;
+ GEN_SCRIPT gen_script = {0};
+ BIO *debug_bio = cfg->debug_bio;
+
+ SCRIPT_INFO_print(script_info, debug_bio, /*error=*/0, "generating script");
+
+ /* Generate the script by calling the generator function. */
+ if (!TEST_true(GEN_SCRIPT_init(&gen_script, script_info))) {
+ SCRIPT_INFO_print(script_info, debug_bio, /*error=*/1,
+ "error while generating script");
+ goto err;
+ }
+
+ /* Output the script for debugging purposes. */
+ if (!TEST_true(GEN_SCRIPT_print(&gen_script, debug_bio, script_info))) {
+ SCRIPT_INFO_print(script_info, debug_bio, /*error=*/1,
+ "error while printing script");
+ goto err;
+ }
+
+ /* Execute the script. */
+ if (!TEST_true(TERP_init(&terp, cfg, script_info, &gen_script)))
+ goto err;
+
+ have_terp = 1;
+
+ SCRIPT_INFO_print(script_info, debug_bio, /*error=*/0, "executing script");
+
+ if (!TERP_execute(&terp))
+ goto err;
+
+ if (terp.stk_end - terp.stk_cur != 0) {
+ TEST_error("stack not empty: %zu bytes left",
+ terp.stk_end - terp.stk_cur);
+ goto err;
+ }
+
+ ok = 1;
+err:
+ if (have_terp) {
+ TERP_print_stack(&terp, debug_bio, "Final state of stack");
+ TERP_cleanup(&terp);
+ }
+
+ GEN_SCRIPT_cleanup(&gen_script);
+ BIO_printf(debug_bio, "Stats:\n Ops executed: %16llu\n\n",
+ (unsigned long long)terp.ops_executed);
+ SCRIPT_INFO_print(script_info, debug_bio, /*error=*/!ok,
+ ok ? "completed" : "failed, exiting");
+ return ok;
+}
+
+#define SCRIPT(name) (&script_info_##name)
+#define USE(name) SCRIPT(name),