mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-11-07 04:36:52 -10:00
Obtained from https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-3.0.9.tar.gz SHA1: a390f016a7a5ff419c9eef9e2b2c01fe86c11971 - SHA256: 32310a33bb97425ebc0707cb5af59f9dc05d10282dcd4db628a45ed904993dc6 - SHA512: 30776d31f860910286b96fbf60a19b5a6d1e73cb6a1077a019d763ae6e5ed1e8d81bc3649978c83f40e5e90ae779f7e530c513b700c8e73bb6399883dd6ef7ab - Sponsored by: The FreeBSD Foundation
554 lines
12 KiB
C
554 lines
12 KiB
C
/*
|
|
* 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 bool
|
|
pkgconf_bytecode_read_op(const uint8_t *p, size_t remaining,
|
|
const pkgconf_bytecode_op_t **op, size_t *op_size)
|
|
{
|
|
if (p == NULL || remaining < sizeof(**op))
|
|
return false;
|
|
|
|
*op = (const pkgconf_bytecode_op_t *) p;
|
|
if ((size_t) (*op)->size > remaining - sizeof(**op))
|
|
return false;
|
|
|
|
*op_size = sizeof(**op) + (size_t) (*op)->size;
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
|
|
if (bc->len != 0 && bc->base == NULL)
|
|
return false;
|
|
|
|
const uint8_t *p = bc->base;
|
|
size_t remaining = 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 (remaining != 0)
|
|
{
|
|
const pkgconf_bytecode_op_t *op;
|
|
size_t op_size;
|
|
|
|
if (!pkgconf_bytecode_read_op(p, remaining, &op, &op_size))
|
|
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 += op_size;
|
|
remaining -= op_size;
|
|
}
|
|
|
|
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 = buf->base != NULL ? (size_t)(buf->end - buf->base) : 0;
|
|
}
|
|
|
|
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;
|
|
size_t remaining;
|
|
size_t klen;
|
|
|
|
if (buf == NULL || key == NULL)
|
|
return false;
|
|
|
|
klen = strlen(key);
|
|
p = (const uint8_t *) buf->base;
|
|
remaining = pkgconf_buffer_len(buf);
|
|
|
|
while (remaining != 0)
|
|
{
|
|
const pkgconf_bytecode_op_t *op;
|
|
size_t op_size;
|
|
|
|
if (!pkgconf_bytecode_read_op(p, remaining, &op, &op_size))
|
|
return false;
|
|
|
|
if (op->tag == PKGCONF_BYTECODE_OP_VAR)
|
|
{
|
|
if (op->size == (uint32_t) klen && memcmp(op->data, key, klen) == 0)
|
|
return true;
|
|
}
|
|
|
|
p += op_size;
|
|
remaining -= 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)
|
|
{
|
|
if (out == NULL || rhs == NULL || key == NULL || prev == NULL)
|
|
return false;
|
|
|
|
const uint8_t *p = (const uint8_t *) rhs->base;
|
|
size_t remaining = pkgconf_buffer_len(rhs);
|
|
|
|
while (remaining != 0)
|
|
{
|
|
const pkgconf_bytecode_op_t *op;
|
|
size_t op_size;
|
|
|
|
if (!pkgconf_bytecode_read_op(p, remaining, &op, &op_size))
|
|
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, op_size))
|
|
return false;
|
|
}
|
|
|
|
p += op_size;
|
|
remaining -= op_size;
|
|
}
|
|
|
|
return true;
|
|
}
|