summaryrefslogtreecommitdiff
path: root/libpkgconf/bytecode.c
diff options
context:
space:
mode:
Diffstat (limited to 'libpkgconf/bytecode.c')
-rw-r--r--libpkgconf/bytecode.c535
1 files changed, 535 insertions, 0 deletions
diff --git a/libpkgconf/bytecode.c b/libpkgconf/bytecode.c
new file mode 100644
index 000000000000..a83b7d8fe72d
--- /dev/null
+++ b/libpkgconf/bytecode.c
@@ -0,0 +1,535 @@
+/*
+ * bytecode.c
+ * variable expansion bytecode evaluator
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2026 pkgconf authors (see AUTHORS).
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#include <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+
+/*
+ * !doc
+ *
+ * libpkgconf `bytecode` module
+ * ============================
+ *
+ * The libpkgconf `bytecode` module contains the functions related to
+ * evaluating variable expansion bytecode.
+ */
+
+#define PKGCONF_EVAL_MAX_OUTPUT (PKGCONF_BUFSIZE - 1)
+#define PKGCONF_EVAL_MAX_ITERATIONS (512)
+
+static bool
+pkgconf_bytecode_eval_append_slice(pkgconf_bytecode_eval_ctx_t *ctx, pkgconf_buffer_t *out, const char *p, size_t n)
+{
+ size_t cur = pkgconf_buffer_len(out);
+ if (cur >= PKGCONF_EVAL_MAX_OUTPUT)
+ return false;
+
+ if (n > PKGCONF_EVAL_MAX_OUTPUT - cur)
+ {
+ pkgconf_warn(ctx->client, "warning: truncating very long variable to 64KB\n");
+
+ n = PKGCONF_EVAL_MAX_OUTPUT - cur;
+ if (!pkgconf_buffer_append_slice(out, p, n))
+ pkgconf_error(ctx->client, "pkgconf_bytecode_eval_append_slice: failed to append to slice");
+
+ return false;
+ }
+
+ return pkgconf_buffer_append_slice(out, p, n);
+}
+
+static bool
+pkgconf_bytecode_eval_append(pkgconf_bytecode_eval_ctx_t *ctx, pkgconf_buffer_t *out, const char *s)
+{
+ if (s == NULL || *s == '\0')
+ return true;
+
+ return pkgconf_bytecode_eval_append_slice(ctx, out, s, strlen(s));
+}
+
+static bool
+pkgconf_bytecode_eval_internal(pkgconf_bytecode_eval_ctx_t *ctx, const pkgconf_bytecode_t *bc, pkgconf_buffer_t *out, bool *saw_sysroot);
+
+static pkgconf_variable_t *
+pkgconf_bytecode_eval_scan(const pkgconf_list_t *vars, const char *name, size_t nlen, unsigned int require_flags, unsigned int forbid_flags)
+{
+ const pkgconf_node_t *node;
+
+ PKGCONF_FOREACH_LIST_ENTRY(vars->head, node)
+ {
+ pkgconf_variable_t *v = node->data;
+
+ if ((v->flags & require_flags) != require_flags)
+ continue;
+
+ if ((v->flags & forbid_flags) != 0)
+ continue;
+
+ if (pkgconf_str_eq_slice(v->key, name, nlen))
+ return v;
+ }
+
+ return NULL;
+}
+
+pkgconf_variable_t *
+pkgconf_bytecode_eval_lookup_var(pkgconf_bytecode_eval_ctx_t *ctx, const char *name, size_t nlen)
+{
+ pkgconf_variable_t *v;
+
+ if ((v = pkgconf_bytecode_eval_scan(&ctx->client->global_vars, name, nlen, PKGCONF_VARIABLEF_OVERRIDE, 0)) != NULL)
+ return v;
+
+ if (ctx->vars != NULL && (v = pkgconf_bytecode_eval_scan(ctx->vars, name, nlen, 0, 0)) != NULL)
+ return v;
+
+ if ((v = pkgconf_bytecode_eval_scan(&ctx->client->global_vars, name, nlen, 0, PKGCONF_VARIABLEF_OVERRIDE)) != NULL)
+ return v;
+
+ return NULL;
+}
+
+static bool
+pkgconf_bytecode_eval_var(pkgconf_bytecode_eval_ctx_t *ctx, const char *name, size_t nlen, pkgconf_buffer_t *out, bool *saw_sysroot)
+{
+ pkgconf_variable_t *v;
+
+ v = pkgconf_bytecode_eval_lookup_var(ctx, name, nlen);
+ if (v == NULL)
+ return true;
+
+ if (v->expanding)
+ return false;
+
+ v->expanding = true;
+
+ bool inner_saw = false;
+ bool ok = pkgconf_bytecode_eval_internal(ctx, &v->bc, out, &inner_saw);
+
+ v->expanding = false;
+
+ if (!ok)
+ return false;
+
+ if (saw_sysroot != NULL)
+ *saw_sysroot |= inner_saw;
+
+ return true;
+}
+
+static bool
+pkgconf_bytecode_eval_internal(pkgconf_bytecode_eval_ctx_t *ctx, const pkgconf_bytecode_t *bc, pkgconf_buffer_t *out, bool *saw_sysroot)
+{
+ (void) ctx;
+
+ if (bc == NULL || out == NULL)
+ return false;
+
+ const uint8_t *p = bc->base;
+ const uint8_t *end = bc->base + bc->len;
+
+ if (++ctx->expansions > PKGCONF_EVAL_MAX_ITERATIONS)
+ {
+ pkgconf_warn(ctx->client,
+ "warning: bytecode program exceeds iteration limit (" SIZE_FMT_SPECIFIER ")\n",
+ ctx->expansions - 1);
+ return false;
+ }
+
+ while (p < end)
+ {
+ const pkgconf_bytecode_op_t *op =
+ (const pkgconf_bytecode_op_t *)p;
+
+ if ((const uint8_t *)op + sizeof(*op) > end)
+ return false;
+
+ if ((const uint8_t *)op + sizeof(*op) + op->size > end)
+ return false;
+
+ switch (op->tag)
+ {
+ case PKGCONF_BYTECODE_OP_TEXT:
+ /* this only fails due to truncation */
+ if (!pkgconf_bytecode_eval_append_slice(ctx, out, op->data, op->size))
+ return false;
+ break;
+
+ case PKGCONF_BYTECODE_OP_VAR:
+ if (!pkgconf_bytecode_eval_var(ctx, op->data, op->size, out, saw_sysroot))
+ return false;
+ break;
+
+ case PKGCONF_BYTECODE_OP_SYSROOT:
+ if (saw_sysroot != NULL)
+ *saw_sysroot = true;
+ if (!pkgconf_bytecode_eval_append(ctx, out, pkgconf_buffer_str_or_empty(&ctx->sysroot)))
+ return false;
+ break;
+
+ default:
+ /* reserved/unimplemented */
+ return false;
+ }
+
+ p = (const uint8_t *)pkgconf_bytecode_op_next(op);
+ }
+
+ return true;
+}
+
+static bool
+pkgconf_bytecode_eval_ctx_init(pkgconf_bytecode_eval_ctx_t *ctx, const pkgconf_client_t *client, const pkgconf_list_t *vars)
+{
+ memset(ctx, 0, sizeof(*ctx));
+
+ ctx->client = client;
+ ctx->vars = vars;
+
+ const char *raw = pkgconf_client_get_sysroot_dir(client);
+
+ /* disabled sysroot cases */
+ if (raw == NULL || *raw == '\0')
+ return true;
+
+ if (raw[0] == '.' && raw[1] == '\0')
+ return true;
+
+ if (raw[0] == '/' && raw[1] == '\0')
+ return true;
+
+ if (!pkgconf_buffer_append(&ctx->sysroot, raw))
+ return false;
+
+ while (pkgconf_buffer_len(&ctx->sysroot) > 1 && ctx->sysroot.end[-1] == '/')
+ {
+ if (!pkgconf_buffer_trim_byte(&ctx->sysroot))
+ return false;
+ }
+
+ /* if normalization yields "/", disable by making buffer empty */
+ if (pkgconf_buffer_len(&ctx->sysroot) == 1 && ctx->sysroot.base[0] == '/')
+ {
+ if (!pkgconf_buffer_trim_byte(&ctx->sysroot))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+pkgconf_bytecode_eval(const pkgconf_client_t *client, const pkgconf_list_t *vars, const pkgconf_bytecode_t *bc, pkgconf_buffer_t *out, bool *saw_sysroot)
+{
+ bool ret;
+
+ if (client == NULL || bc == NULL || out == NULL)
+ return false;
+
+ pkgconf_bytecode_eval_ctx_t ctx;
+ if (!pkgconf_bytecode_eval_ctx_init(&ctx, client, vars))
+ return false;
+
+ if (saw_sysroot != NULL)
+ *saw_sysroot = false;
+
+ ret = pkgconf_bytecode_eval_internal(&ctx, bc, out, saw_sysroot);
+
+ pkgconf_buffer_finalize(&ctx.sysroot);
+
+ return ret;
+}
+
+bool
+pkgconf_bytecode_emit(pkgconf_buffer_t *buf, enum pkgconf_bytecode_op tag, const void *data, uint32_t size)
+{
+ pkgconf_bytecode_op_t op = {
+ .tag = tag,
+ .size = size,
+ };
+
+ if (!pkgconf_buffer_append_slice(buf, (const char *) &op, sizeof(op)))
+ return false;
+
+ if (size != 0)
+ {
+ if (!pkgconf_buffer_append_slice(buf, (const char *) data, (size_t) size))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+pkgconf_bytecode_emit_text(pkgconf_buffer_t *buf, const char *p, size_t n)
+{
+ if (p == NULL || n == 0)
+ return true;
+
+ return pkgconf_bytecode_emit(buf, PKGCONF_BYTECODE_OP_TEXT, p, (uint32_t) n);
+}
+
+bool
+pkgconf_bytecode_emit_var(pkgconf_buffer_t *buf, const char *name, size_t nlen)
+{
+ if (name == NULL || nlen == 0)
+ return true;
+
+ return pkgconf_bytecode_emit(buf, PKGCONF_BYTECODE_OP_VAR, name, (uint32_t) nlen);
+}
+
+bool
+pkgconf_bytecode_emit_sysroot(pkgconf_buffer_t *buf)
+{
+ return pkgconf_bytecode_emit(buf, PKGCONF_BYTECODE_OP_SYSROOT, NULL, 0);
+}
+
+void
+pkgconf_bytecode_from_buffer(pkgconf_bytecode_t *bc, const pkgconf_buffer_t *buf)
+{
+ bc->base = (const uint8_t *)buf->base;
+ bc->len = (size_t)(buf->end - buf->base);
+}
+
+bool
+pkgconf_bytecode_compile(pkgconf_buffer_t *out, const char *value)
+{
+ const char *p, *text_start;
+
+ if (out == NULL || value == NULL)
+ return false;
+
+ p = value;
+ text_start = value;
+
+ for (; *p != '\0'; p++)
+ {
+ const char *name, *q;
+
+ if (*p != '$')
+ continue;
+
+ /* $$ escapes to a literal $ */
+ if (p[1] == '$')
+ {
+ if (p > text_start)
+ {
+ if (!pkgconf_bytecode_emit_text(out, text_start, (size_t)(p - text_start)))
+ return false;
+ }
+
+ if (!pkgconf_bytecode_emit_text(out, "$", 1))
+ return false;
+
+ p++;
+ text_start = p + 1;
+ continue;
+ }
+
+ if (p[1] != '{')
+ continue;
+
+ if (p > text_start)
+ {
+ if (!pkgconf_bytecode_emit_text(out, text_start, (size_t)(p - text_start)))
+ return false;
+ }
+
+ name = p + 2;
+ q = name;
+
+ for (; *q != '\0' && *q != '}'; q++)
+ ;
+
+ /* make sure a variable expansion ends with } */
+ if (*q != '}')
+ {
+ text_start = p;
+ continue;
+ }
+
+ /* if this is not a valid variable, emit it as text */
+ size_t nlen = (size_t)(q - name);
+ if (nlen == 0 || nlen >= PKGCONF_ITEM_SIZE)
+ {
+ if (!pkgconf_bytecode_emit_text(out, p, (size_t)((q + 1) - p)))
+ return false;
+
+ p = q;
+ text_start = p + 1;
+ continue;
+ }
+
+ /* we need to special-case ${pc_sysrootdir} and emit OP_SYSROOT instead... */
+ if (nlen == strlen("pc_sysrootdir") && !memcmp(name, "pc_sysrootdir", nlen))
+ {
+ if (!pkgconf_bytecode_emit_sysroot(out))
+ return false;
+ }
+ else
+ {
+ if (!pkgconf_bytecode_emit_var(out, name, nlen))
+ return false;
+ }
+
+ p = q;
+ text_start = p + 1;
+ }
+
+ if (p > text_start)
+ {
+ if (!pkgconf_bytecode_emit_text(out, text_start, (size_t)(p - text_start)))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+pkgconf_bytecode_eval_str_to_buf(const pkgconf_client_t *client, const pkgconf_list_t *vars, const char *input, bool *saw_sysroot, pkgconf_buffer_t *out)
+{
+ pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_bytecode_t bc;
+ bool ret = false;
+
+ if (!pkgconf_bytecode_compile(&bcbuf, input))
+ {
+ pkgconf_buffer_finalize(&bcbuf);
+ return false;
+ }
+
+ pkgconf_bytecode_from_buffer(&bc, &bcbuf);
+
+ ret = pkgconf_bytecode_eval(client, vars, &bc, out, saw_sysroot);
+
+ pkgconf_buffer_finalize(&bcbuf);
+
+ return ret;
+}
+
+char *
+pkgconf_bytecode_eval_str(const pkgconf_client_t *client, const pkgconf_list_t *vars, const char *input, bool *saw_sysroot)
+{
+ pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
+
+ if (!pkgconf_bytecode_eval_str_to_buf(client, vars, input, saw_sysroot, &out))
+ {
+ if (pkgconf_buffer_len(&out) > 0)
+ return pkgconf_buffer_freeze(&out);
+
+ pkgconf_buffer_finalize(&out);
+ return NULL;
+ }
+
+ if (pkgconf_buffer_len(&out) == 0)
+ {
+ pkgconf_buffer_finalize(&out);
+ return strdup("");
+ }
+
+ return pkgconf_buffer_freeze(&out);
+}
+
+bool
+pkgconf_bytecode_references_var(const pkgconf_buffer_t *buf, const char *key)
+{
+ const uint8_t *p, *end;
+ size_t klen;
+
+ if (buf == NULL || key == NULL)
+ return false;
+
+ klen = strlen(key);
+ p = (uint8_t *) buf->base;
+ end = (uint8_t *) buf->end;
+
+ while (p < end)
+ {
+ const pkgconf_bytecode_op_t *op = (const pkgconf_bytecode_op_t *)p;
+
+ if (p + sizeof(*op) > end)
+ return false;
+
+ if (p + sizeof(*op) + op->size > end)
+ return false;
+
+ if (op->tag == PKGCONF_BYTECODE_OP_VAR)
+ {
+ if (op->size == (uint32_t) klen && memcmp(op->data, key, klen) == 0)
+ return true;
+ }
+
+ p += sizeof(*op) + op->size;
+ }
+
+ return false;
+}
+
+static bool
+pkgconf_bytecode_op_is_selfref(const pkgconf_bytecode_op_t *op, const char *key)
+{
+ const size_t klen = strlen(key);
+
+ if (op->tag != PKGCONF_BYTECODE_OP_VAR)
+ return false;
+
+ if (op->size != (uint32_t) klen)
+ return false;
+
+ return memcmp(op->data, key, klen) == 0;
+}
+
+static bool
+pkgconf_bytecode_append_stream(pkgconf_buffer_t *dst, const pkgconf_buffer_t *bcbuf)
+{
+ if (dst == NULL || bcbuf == NULL || pkgconf_buffer_str(bcbuf) == NULL)
+ return true;
+
+ return pkgconf_buffer_append_slice(dst, pkgconf_buffer_str(bcbuf), pkgconf_buffer_len(bcbuf));
+}
+
+bool
+pkgconf_bytecode_rewrite_selfrefs(pkgconf_buffer_t *out, const pkgconf_buffer_t *rhs, const char *key, const pkgconf_buffer_t *prev)
+{
+ const uint8_t *p = (uint8_t *) rhs->base;
+ const uint8_t *end = (uint8_t *) rhs->end;
+
+ while (p < end)
+ {
+ const pkgconf_bytecode_op_t *op = (const pkgconf_bytecode_op_t *)p;
+
+ if (p + sizeof(*op) > end)
+ return false;
+
+ if (p + sizeof(*op) + op->size > end)
+ return false;
+
+ if (pkgconf_bytecode_op_is_selfref(op, key))
+ {
+ if (!pkgconf_bytecode_append_stream(out, prev))
+ return false;
+ }
+ else
+ {
+ if (!pkgconf_buffer_append_slice(out, (const char *) op, sizeof(*op) + op->size))
+ return false;
+ }
+
+ p += sizeof(*op) + op->size;
+ }
+
+ return true;
+}