1 /*
2 * bytecode.c
3 * variable expansion bytecode evaluator
4 *
5 * SPDX-License-Identifier: pkgconf
6 *
7 * Copyright (c) 2026 pkgconf authors (see AUTHORS).
8 *
9 * Permission to use, copy, modify, and/or distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
12 *
13 * This software is provided 'as is' and without any warranty, express or
14 * implied. In no event shall the authors be liable for any damages arising
15 * from the use of this software.
16 */
17
18 #include <libpkgconf/stdinc.h>
19 #include <libpkgconf/libpkgconf.h>
20
21 /*
22 * !doc
23 *
24 * libpkgconf `bytecode` module
25 * ============================
26 *
27 * The libpkgconf `bytecode` module contains the functions related to
28 * evaluating variable expansion bytecode.
29 */
30
31 #define PKGCONF_EVAL_MAX_OUTPUT (PKGCONF_BUFSIZE - 1)
32 #define PKGCONF_EVAL_MAX_ITERATIONS (512)
33
34 static bool
pkgconf_bytecode_eval_append_slice(pkgconf_bytecode_eval_ctx_t * ctx,pkgconf_buffer_t * out,const char * p,size_t n)35 pkgconf_bytecode_eval_append_slice(pkgconf_bytecode_eval_ctx_t *ctx, pkgconf_buffer_t *out, const char *p, size_t n)
36 {
37 size_t cur = pkgconf_buffer_len(out);
38 if (cur >= PKGCONF_EVAL_MAX_OUTPUT)
39 return false;
40
41 if (n > PKGCONF_EVAL_MAX_OUTPUT - cur)
42 {
43 pkgconf_warn(ctx->client, "warning: truncating very long variable to 64KB\n");
44
45 n = PKGCONF_EVAL_MAX_OUTPUT - cur;
46 if (!pkgconf_buffer_append_slice(out, p, n))
47 pkgconf_error(ctx->client, "pkgconf_bytecode_eval_append_slice: failed to append to slice");
48
49 return false;
50 }
51
52 return pkgconf_buffer_append_slice(out, p, n);
53 }
54
55 static bool
pkgconf_bytecode_eval_append(pkgconf_bytecode_eval_ctx_t * ctx,pkgconf_buffer_t * out,const char * s)56 pkgconf_bytecode_eval_append(pkgconf_bytecode_eval_ctx_t *ctx, pkgconf_buffer_t *out, const char *s)
57 {
58 if (s == NULL || *s == '\0')
59 return true;
60
61 return pkgconf_bytecode_eval_append_slice(ctx, out, s, strlen(s));
62 }
63
64 static bool
65 pkgconf_bytecode_eval_internal(pkgconf_bytecode_eval_ctx_t *ctx, const pkgconf_bytecode_t *bc, pkgconf_buffer_t *out, bool *saw_sysroot);
66
67 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)68 pkgconf_bytecode_eval_scan(const pkgconf_list_t *vars, const char *name, size_t nlen, unsigned int require_flags, unsigned int forbid_flags)
69 {
70 const pkgconf_node_t *node;
71
72 PKGCONF_FOREACH_LIST_ENTRY(vars->head, node)
73 {
74 pkgconf_variable_t *v = node->data;
75
76 if ((v->flags & require_flags) != require_flags)
77 continue;
78
79 if ((v->flags & forbid_flags) != 0)
80 continue;
81
82 if (pkgconf_str_eq_slice(v->key, name, nlen))
83 return v;
84 }
85
86 return NULL;
87 }
88
89 pkgconf_variable_t *
pkgconf_bytecode_eval_lookup_var(pkgconf_bytecode_eval_ctx_t * ctx,const char * name,size_t nlen)90 pkgconf_bytecode_eval_lookup_var(pkgconf_bytecode_eval_ctx_t *ctx, const char *name, size_t nlen)
91 {
92 pkgconf_variable_t *v;
93
94 if ((v = pkgconf_bytecode_eval_scan(&ctx->client->global_vars, name, nlen, PKGCONF_VARIABLEF_OVERRIDE, 0)) != NULL)
95 return v;
96
97 if (ctx->vars != NULL && (v = pkgconf_bytecode_eval_scan(ctx->vars, name, nlen, 0, 0)) != NULL)
98 return v;
99
100 if ((v = pkgconf_bytecode_eval_scan(&ctx->client->global_vars, name, nlen, 0, PKGCONF_VARIABLEF_OVERRIDE)) != NULL)
101 return v;
102
103 return NULL;
104 }
105
106 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)107 pkgconf_bytecode_eval_var(pkgconf_bytecode_eval_ctx_t *ctx, const char *name, size_t nlen, pkgconf_buffer_t *out, bool *saw_sysroot)
108 {
109 pkgconf_variable_t *v;
110
111 v = pkgconf_bytecode_eval_lookup_var(ctx, name, nlen);
112 if (v == NULL)
113 return true;
114
115 if (v->expanding)
116 return false;
117
118 v->expanding = true;
119
120 bool inner_saw = false;
121 bool ok = pkgconf_bytecode_eval_internal(ctx, &v->bc, out, &inner_saw);
122
123 v->expanding = false;
124
125 if (!ok)
126 return false;
127
128 if (saw_sysroot != NULL)
129 *saw_sysroot |= inner_saw;
130
131 return true;
132 }
133
134 static bool
pkgconf_bytecode_eval_internal(pkgconf_bytecode_eval_ctx_t * ctx,const pkgconf_bytecode_t * bc,pkgconf_buffer_t * out,bool * saw_sysroot)135 pkgconf_bytecode_eval_internal(pkgconf_bytecode_eval_ctx_t *ctx, const pkgconf_bytecode_t *bc, pkgconf_buffer_t *out, bool *saw_sysroot)
136 {
137 (void) ctx;
138
139 if (bc == NULL || out == NULL)
140 return false;
141
142 const uint8_t *p = bc->base;
143 const uint8_t *end = bc->base + bc->len;
144
145 if (++ctx->expansions > PKGCONF_EVAL_MAX_ITERATIONS)
146 {
147 pkgconf_warn(ctx->client,
148 "warning: bytecode program exceeds iteration limit (" SIZE_FMT_SPECIFIER ")\n",
149 ctx->expansions - 1);
150 return false;
151 }
152
153 while (p < end)
154 {
155 const pkgconf_bytecode_op_t *op =
156 (const pkgconf_bytecode_op_t *)p;
157
158 if ((const uint8_t *)op + sizeof(*op) > end)
159 return false;
160
161 if ((const uint8_t *)op + sizeof(*op) + op->size > end)
162 return false;
163
164 switch (op->tag)
165 {
166 case PKGCONF_BYTECODE_OP_TEXT:
167 /* this only fails due to truncation */
168 if (!pkgconf_bytecode_eval_append_slice(ctx, out, op->data, op->size))
169 return false;
170 break;
171
172 case PKGCONF_BYTECODE_OP_VAR:
173 if (!pkgconf_bytecode_eval_var(ctx, op->data, op->size, out, saw_sysroot))
174 return false;
175 break;
176
177 case PKGCONF_BYTECODE_OP_SYSROOT:
178 if (saw_sysroot != NULL)
179 *saw_sysroot = true;
180 if (!pkgconf_bytecode_eval_append(ctx, out, pkgconf_buffer_str_or_empty(&ctx->sysroot)))
181 return false;
182 break;
183
184 default:
185 /* reserved/unimplemented */
186 return false;
187 }
188
189 p = (const uint8_t *)pkgconf_bytecode_op_next(op);
190 }
191
192 return true;
193 }
194
195 static bool
pkgconf_bytecode_eval_ctx_init(pkgconf_bytecode_eval_ctx_t * ctx,const pkgconf_client_t * client,const pkgconf_list_t * vars)196 pkgconf_bytecode_eval_ctx_init(pkgconf_bytecode_eval_ctx_t *ctx, const pkgconf_client_t *client, const pkgconf_list_t *vars)
197 {
198 memset(ctx, 0, sizeof(*ctx));
199
200 ctx->client = client;
201 ctx->vars = vars;
202
203 const char *raw = pkgconf_client_get_sysroot_dir(client);
204
205 /* disabled sysroot cases */
206 if (raw == NULL || *raw == '\0')
207 return true;
208
209 if (raw[0] == '.' && raw[1] == '\0')
210 return true;
211
212 if (raw[0] == '/' && raw[1] == '\0')
213 return true;
214
215 if (!pkgconf_buffer_append(&ctx->sysroot, raw))
216 return false;
217
218 while (pkgconf_buffer_len(&ctx->sysroot) > 1 && ctx->sysroot.end[-1] == '/')
219 {
220 if (!pkgconf_buffer_trim_byte(&ctx->sysroot))
221 return false;
222 }
223
224 /* if normalization yields "/", disable by making buffer empty */
225 if (pkgconf_buffer_len(&ctx->sysroot) == 1 && ctx->sysroot.base[0] == '/')
226 {
227 if (!pkgconf_buffer_trim_byte(&ctx->sysroot))
228 return false;
229 }
230
231 return true;
232 }
233
234 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)235 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)
236 {
237 bool ret;
238
239 if (client == NULL || bc == NULL || out == NULL)
240 return false;
241
242 pkgconf_bytecode_eval_ctx_t ctx;
243 if (!pkgconf_bytecode_eval_ctx_init(&ctx, client, vars))
244 return false;
245
246 if (saw_sysroot != NULL)
247 *saw_sysroot = false;
248
249 ret = pkgconf_bytecode_eval_internal(&ctx, bc, out, saw_sysroot);
250
251 pkgconf_buffer_finalize(&ctx.sysroot);
252
253 return ret;
254 }
255
256 bool
pkgconf_bytecode_emit(pkgconf_buffer_t * buf,enum pkgconf_bytecode_op tag,const void * data,uint32_t size)257 pkgconf_bytecode_emit(pkgconf_buffer_t *buf, enum pkgconf_bytecode_op tag, const void *data, uint32_t size)
258 {
259 pkgconf_bytecode_op_t op = {
260 .tag = tag,
261 .size = size,
262 };
263
264 if (!pkgconf_buffer_append_slice(buf, (const char *) &op, sizeof(op)))
265 return false;
266
267 if (size != 0)
268 {
269 if (!pkgconf_buffer_append_slice(buf, (const char *) data, (size_t) size))
270 return false;
271 }
272
273 return true;
274 }
275
276 bool
pkgconf_bytecode_emit_text(pkgconf_buffer_t * buf,const char * p,size_t n)277 pkgconf_bytecode_emit_text(pkgconf_buffer_t *buf, const char *p, size_t n)
278 {
279 if (p == NULL || n == 0)
280 return true;
281
282 return pkgconf_bytecode_emit(buf, PKGCONF_BYTECODE_OP_TEXT, p, (uint32_t) n);
283 }
284
285 bool
pkgconf_bytecode_emit_var(pkgconf_buffer_t * buf,const char * name,size_t nlen)286 pkgconf_bytecode_emit_var(pkgconf_buffer_t *buf, const char *name, size_t nlen)
287 {
288 if (name == NULL || nlen == 0)
289 return true;
290
291 return pkgconf_bytecode_emit(buf, PKGCONF_BYTECODE_OP_VAR, name, (uint32_t) nlen);
292 }
293
294 bool
pkgconf_bytecode_emit_sysroot(pkgconf_buffer_t * buf)295 pkgconf_bytecode_emit_sysroot(pkgconf_buffer_t *buf)
296 {
297 return pkgconf_bytecode_emit(buf, PKGCONF_BYTECODE_OP_SYSROOT, NULL, 0);
298 }
299
300 void
pkgconf_bytecode_from_buffer(pkgconf_bytecode_t * bc,const pkgconf_buffer_t * buf)301 pkgconf_bytecode_from_buffer(pkgconf_bytecode_t *bc, const pkgconf_buffer_t *buf)
302 {
303 bc->base = (const uint8_t *)buf->base;
304 bc->len = (size_t)(buf->end - buf->base);
305 }
306
307 bool
pkgconf_bytecode_compile(pkgconf_buffer_t * out,const char * value)308 pkgconf_bytecode_compile(pkgconf_buffer_t *out, const char *value)
309 {
310 const char *p, *text_start;
311
312 if (out == NULL || value == NULL)
313 return false;
314
315 p = value;
316 text_start = value;
317
318 for (; *p != '\0'; p++)
319 {
320 const char *name, *q;
321
322 if (*p != '$')
323 continue;
324
325 /* $$ escapes to a literal $ */
326 if (p[1] == '$')
327 {
328 if (p > text_start)
329 {
330 if (!pkgconf_bytecode_emit_text(out, text_start, (size_t)(p - text_start)))
331 return false;
332 }
333
334 if (!pkgconf_bytecode_emit_text(out, "$", 1))
335 return false;
336
337 p++;
338 text_start = p + 1;
339 continue;
340 }
341
342 if (p[1] != '{')
343 continue;
344
345 if (p > text_start)
346 {
347 if (!pkgconf_bytecode_emit_text(out, text_start, (size_t)(p - text_start)))
348 return false;
349 }
350
351 name = p + 2;
352 q = name;
353
354 for (; *q != '\0' && *q != '}'; q++)
355 ;
356
357 /* make sure a variable expansion ends with } */
358 if (*q != '}')
359 {
360 text_start = p;
361 continue;
362 }
363
364 /* if this is not a valid variable, emit it as text */
365 size_t nlen = (size_t)(q - name);
366 if (nlen == 0 || nlen >= PKGCONF_ITEM_SIZE)
367 {
368 if (!pkgconf_bytecode_emit_text(out, p, (size_t)((q + 1) - p)))
369 return false;
370
371 p = q;
372 text_start = p + 1;
373 continue;
374 }
375
376 /* we need to special-case ${pc_sysrootdir} and emit OP_SYSROOT instead... */
377 if (nlen == strlen("pc_sysrootdir") && !memcmp(name, "pc_sysrootdir", nlen))
378 {
379 if (!pkgconf_bytecode_emit_sysroot(out))
380 return false;
381 }
382 else
383 {
384 if (!pkgconf_bytecode_emit_var(out, name, nlen))
385 return false;
386 }
387
388 p = q;
389 text_start = p + 1;
390 }
391
392 if (p > text_start)
393 {
394 if (!pkgconf_bytecode_emit_text(out, text_start, (size_t)(p - text_start)))
395 return false;
396 }
397
398 return true;
399 }
400
401 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)402 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)
403 {
404 pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER;
405 pkgconf_bytecode_t bc;
406 bool ret = false;
407
408 if (!pkgconf_bytecode_compile(&bcbuf, input))
409 {
410 pkgconf_buffer_finalize(&bcbuf);
411 return false;
412 }
413
414 pkgconf_bytecode_from_buffer(&bc, &bcbuf);
415
416 ret = pkgconf_bytecode_eval(client, vars, &bc, out, saw_sysroot);
417
418 pkgconf_buffer_finalize(&bcbuf);
419
420 return ret;
421 }
422
423 char *
pkgconf_bytecode_eval_str(const pkgconf_client_t * client,const pkgconf_list_t * vars,const char * input,bool * saw_sysroot)424 pkgconf_bytecode_eval_str(const pkgconf_client_t *client, const pkgconf_list_t *vars, const char *input, bool *saw_sysroot)
425 {
426 pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
427
428 if (!pkgconf_bytecode_eval_str_to_buf(client, vars, input, saw_sysroot, &out))
429 {
430 if (pkgconf_buffer_len(&out) > 0)
431 return pkgconf_buffer_freeze(&out);
432
433 pkgconf_buffer_finalize(&out);
434 return NULL;
435 }
436
437 if (pkgconf_buffer_len(&out) == 0)
438 {
439 pkgconf_buffer_finalize(&out);
440 return strdup("");
441 }
442
443 return pkgconf_buffer_freeze(&out);
444 }
445
446 bool
pkgconf_bytecode_references_var(const pkgconf_buffer_t * buf,const char * key)447 pkgconf_bytecode_references_var(const pkgconf_buffer_t *buf, const char *key)
448 {
449 const uint8_t *p, *end;
450 size_t klen;
451
452 if (buf == NULL || key == NULL)
453 return false;
454
455 klen = strlen(key);
456 p = (uint8_t *) buf->base;
457 end = (uint8_t *) buf->end;
458
459 while (p < end)
460 {
461 const pkgconf_bytecode_op_t *op = (const pkgconf_bytecode_op_t *)p;
462
463 if (p + sizeof(*op) > end)
464 return false;
465
466 if (p + sizeof(*op) + op->size > end)
467 return false;
468
469 if (op->tag == PKGCONF_BYTECODE_OP_VAR)
470 {
471 if (op->size == (uint32_t) klen && memcmp(op->data, key, klen) == 0)
472 return true;
473 }
474
475 p += sizeof(*op) + op->size;
476 }
477
478 return false;
479 }
480
481 static bool
pkgconf_bytecode_op_is_selfref(const pkgconf_bytecode_op_t * op,const char * key)482 pkgconf_bytecode_op_is_selfref(const pkgconf_bytecode_op_t *op, const char *key)
483 {
484 const size_t klen = strlen(key);
485
486 if (op->tag != PKGCONF_BYTECODE_OP_VAR)
487 return false;
488
489 if (op->size != (uint32_t) klen)
490 return false;
491
492 return memcmp(op->data, key, klen) == 0;
493 }
494
495 static bool
pkgconf_bytecode_append_stream(pkgconf_buffer_t * dst,const pkgconf_buffer_t * bcbuf)496 pkgconf_bytecode_append_stream(pkgconf_buffer_t *dst, const pkgconf_buffer_t *bcbuf)
497 {
498 if (dst == NULL || bcbuf == NULL || pkgconf_buffer_str(bcbuf) == NULL)
499 return true;
500
501 return pkgconf_buffer_append_slice(dst, pkgconf_buffer_str(bcbuf), pkgconf_buffer_len(bcbuf));
502 }
503
504 bool
pkgconf_bytecode_rewrite_selfrefs(pkgconf_buffer_t * out,const pkgconf_buffer_t * rhs,const char * key,const pkgconf_buffer_t * prev)505 pkgconf_bytecode_rewrite_selfrefs(pkgconf_buffer_t *out, const pkgconf_buffer_t *rhs, const char *key, const pkgconf_buffer_t *prev)
506 {
507 const uint8_t *p = (uint8_t *) rhs->base;
508 const uint8_t *end = (uint8_t *) rhs->end;
509
510 while (p < end)
511 {
512 const pkgconf_bytecode_op_t *op = (const pkgconf_bytecode_op_t *)p;
513
514 if (p + sizeof(*op) > end)
515 return false;
516
517 if (p + sizeof(*op) + op->size > end)
518 return false;
519
520 if (pkgconf_bytecode_op_is_selfref(op, key))
521 {
522 if (!pkgconf_bytecode_append_stream(out, prev))
523 return false;
524 }
525 else
526 {
527 if (!pkgconf_buffer_append_slice(out, (const char *) op, sizeof(*op) + op->size))
528 return false;
529 }
530
531 p += sizeof(*op) + op->size;
532 }
533
534 return true;
535 }
536