1#include "internal.h"
2
3#include <ctype.h>
4#include <stddef.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8
9/* $() ${} expansion is handled here */
10
11struct Buf {
12 char *s;
13 size_t len;
14 size_t cap;
15};
16
17static void
18evalerr(struct EvalCtx *ctx, const char *msg, const char *detail)
19{
20 dielikemake(ctx->cur_path, ctx->cur_line, msg, detail);
21 ctx->errors++;
22}
23
24static void
25bufinit(struct Buf *buf, size_t cap)
26{
27 buf->cap = cap ? cap : 1;
28 buf->len = 0;
29 buf->s = xmalloc(buf->cap);
30 buf->s[0] = 0;
31}
32
33static void
34bufgrow(struct Buf *buf, size_t need)
35{
36 while (buf->cap < need)
37 buf->cap *= 2;
38 buf->s = xrealloc(buf->s, buf->cap);
39}
40
41static void
42bufappendn(struct Buf *buf, const char *s, size_t n)
43{
44 if (buf->len + n + 1 > buf->cap)
45 bufgrow(buf, buf->len + n + 1);
46 memcpy(buf->s + buf->len, s, n);
47 buf->len += n;
48 buf->s[buf->len] = 0;
49}
50
51static void
52bufappend(struct Buf *buf, const char *s)
53{
54 bufappendn(buf, s, strlen(s));
55}
56
57static void
58bufappendc(struct Buf *buf, char c)
59{
60 if (buf->len + 2 > buf->cap)
61 bufgrow(buf, buf->len + 2);
62 buf->s[buf->len++] = c;
63 buf->s[buf->len] = 0;
64}
65
66static char *
67bufdone(struct Buf *buf)
68{
69 return buf->s;
70}
71
72static int
73isplainvar(const char *s, size_t n)
74{
75 size_t i;
76
77 if (!n)
78 return 0;
79 for (i = 0; i < n; i++) {
80 if (!(isalnum((unsigned char)s[i]) || s[i] == '_'))
81 return 0;
82 }
83 return 1;
84}
85
86static char *
87substword(const char *word, size_t n, const char *from, const char *to)
88{
89 size_t nfrom, nto;
90 const char *pct;
91 char *out;
92
93 nfrom = strlen(from);
94 nto = strlen(to);
95 pct = strchr(from, '%');
96 if (!pct) {
97 if (n < nfrom || memcmp(word + n - nfrom, from, nfrom) != 0)
98 return xstrndup(word, n);
99 out = xmalloc(n - nfrom + nto + 1);
100 memcpy(out, word, n - nfrom);
101 memcpy(out + n - nfrom, to, nto);
102 out[n - nfrom + nto] = 0;
103 return out;
104 }
105 {
106 size_t pre, suf, stem;
107 const char *tpct;
108
109 pre = (size_t)(pct - from);
110 suf = nfrom - pre - 1;
111 if (n < pre + suf)
112 return xstrndup(word, n);
113 if (memcmp(word, from, pre) != 0 || memcmp(word + n - suf, pct + 1, suf) != 0)
114 return xstrndup(word, n);
115 stem = n - pre - suf;
116 tpct = strchr(to, '%');
117 if (!tpct)
118 return xstrdup(to);
119 {
120 size_t tpre, tsuf;
121
122 tpre = (size_t)(tpct - to);
123 tsuf = nto - tpre - 1;
124 out = xmalloc(tpre + stem + tsuf + 1);
125 memcpy(out, to, tpre);
126 memcpy(out + tpre, word + pre, stem);
127 memcpy(out + tpre + stem, tpct + 1, tsuf);
128 out[tpre + stem + tsuf] = 0;
129 return out;
130 }
131 }
132}
133
134static char *
135substval(const char *val, const char *from, const char *to)
136{
137 size_t i, j, n;
138 char *part;
139 struct Buf out;
140
141 n = strlen(val);
142 bufinit(&out, n + 1);
143 for (i = 0; i < n;) {
144 if (isspace((unsigned char)val[i])) {
145 bufappendc(&out, val[i]);
146 i++;
147 continue;
148 }
149 j = i;
150 while (j < n && !isspace((unsigned char)val[j]))
151 j++;
152 part = substword(val + i, j - i, from, to);
153 bufappend(&out, part);
154 free(part);
155 i = j;
156 }
157 return bufdone(&out);
158}
159
160static int
161issubstref(const char *s, size_t n, size_t *colon, size_t *eq)
162{
163 size_t i;
164
165 for (i = 0; i < n; i++) {
166 if (s[i] == ':') {
167 *colon = i;
168 break;
169 }
170 if (isspace((unsigned char)s[i]))
171 return 0;
172 }
173 if (i == 0 || i >= n)
174 return 0;
175 for (i = *colon + 1; i < n; i++) {
176 if (s[i] == '=') {
177 *eq = i;
178 return i > *colon + 1;
179 }
180 }
181 return 0;
182}
183
184static ptrdiff_t
185findargcomma(const char *s, size_t n)
186{
187 size_t i, depth;
188
189 depth = 0;
190 for (i = 0; i < n; i++) {
191 if (s[i] == '$' && i + 1 < n && (s[i + 1] == '(' || s[i + 1] == '{')) {
192 depth++;
193 i++;
194 continue;
195 }
196 if ((s[i] == ')' || s[i] == '}') && depth > 0) {
197 depth--;
198 continue;
199 }
200 if (s[i] == ',' && depth == 0)
201 return (ptrdiff_t)i;
202 }
203 return -1;
204}
205
206static size_t
207findclose(const char *s, size_t i, size_t n, char close)
208{
209 size_t j, inner;
210
211 j = i + 2;
212 inner = 1;
213 while (j < n && inner) {
214 if (s[j] == '$' && j + 1 < n && (s[j + 1] == '(' || s[j + 1] == '{')) {
215 inner++;
216 j += 2;
217 continue;
218 }
219 if (s[j] == close)
220 inner--;
221 j++;
222 }
223 return inner ? 0 : j;
224}
225
226static char *expandvarref(struct EvalCtx *ctx, const char *s, size_t n);
227static char *expandref(struct EvalCtx *ctx, const char *s, size_t n);
228
229static char *
230autoprereqsval(const struct StrList *prereqs, char kind)
231{
232 if (kind == '+')
233 return joinstrs(prereqs, " ");
234
235 {
236 size_t i;
237 struct StrList uniq;
238 char *joined;
239
240 memset(&uniq, 0, sizeof(uniq));
241 for (i = 0; i < prereqs->n; i++) {
242 if (!hasword(&uniq, prereqs->v[i]))
243 addstr(&uniq, prereqs->v[i]);
244 }
245 joined = joinstrs(&uniq, " ");
246 freestrs(&uniq);
247 return joined;
248 }
249}
250
251static char *
252expandname(struct EvalCtx *ctx, const char *s)
253{
254 size_t i, j, n;
255 char close;
256 char *val;
257 struct Buf out;
258
259 n = strlen(s);
260 bufinit(&out, n + 1);
261 for (i = 0; i < n; i++) {
262 if (s[i] != '$' || i + 1 >= n) {
263 bufappendc(&out, s[i]);
264 continue;
265 }
266 if (s[i + 1] == '$') {
267 bufappendc(&out, '$');
268 i++;
269 continue;
270 }
271 if (s[i + 1] == '@' && ctx->auto_target) {
272 bufappend(&out, ctx->auto_target);
273 i++;
274 continue;
275 }
276 if ((s[i + 1] == '<') && ctx->auto_prereqs) {
277 if (ctx->auto_prereqs->n > 0)
278 bufappend(&out, ctx->auto_prereqs->v[0]);
279 i++;
280 continue;
281 }
282 if ((s[i + 1] == '^' || s[i + 1] == '+' || s[i + 1] == '?') &&
283 ctx->auto_prereqs) {
284 char *joined;
285
286 joined = autoprereqsval(ctx->auto_prereqs, s[i + 1]);
287 bufappend(&out, joined);
288 free(joined);
289 i++;
290 continue;
291 }
292 if (s[i + 1] == '*' && ctx->auto_stem) {
293 bufappend(&out, ctx->auto_stem);
294 i++;
295 continue;
296 }
297 if (s[i + 1] != '(' && s[i + 1] != '{') {
298 val = expandvarref(ctx, s + i + 1, 1);
299 bufappend(&out, val);
300 free(val);
301 i++;
302 continue;
303 }
304 close = s[i + 1] == '(' ? ')' : '}';
305 j = findclose(s, i, n, close);
306 if (!j) {
307 bufappendn(&out, s + i, n - i);
308 break;
309 }
310 val = expandref(ctx, s + i + 2, j - i - 3);
311 bufappend(&out, val);
312 free(val);
313 i = j - 1;
314 }
315 return bufdone(&out);
316}
317
318static char *
319expandvarref(struct EvalCtx *ctx, const char *s, size_t n)
320{
321 char *name, *raw, *val;
322 struct Var *v;
323
324 raw = xstrndup(s, n);
325 name = expandname(ctx, raw);
326 free(raw);
327
328 if (n > 0 && ctx->call) {
329 size_t i;
330 int numeric;
331
332 numeric = 1;
333 for (i = 0; name[i]; i++) {
334 if (!isdigit((unsigned char)name[i])) {
335 numeric = 0;
336 break;
337 }
338 }
339 if (numeric) {
340 unsigned long idx;
341 char *end;
342
343 idx = strtoul(name, &end, 10);
344 if (*end == 0 && idx < ctx->call->nargs) {
345 val = expandstr(ctx, ctx->call->args[idx]);
346 free(name);
347 return val;
348 }
349 }
350 }
351
352 v = findvar(ctx->env, name);
353 free(name);
354 if (!v)
355 return xstrdup("");
356 if (v->simple)
357 return xstrdup(v->val);
358 val = expandstr(ctx, v->val);
359 return val;
360}
361
362static char *
363expandsubstref(struct EvalCtx *ctx, const char *s, size_t colon, size_t eq, size_t n)
364{
365 char *name, *from, *to, *val;
366 struct Var *v;
367 char *base;
368 int auto_name;
369
370 {
371 char *nameraw, *toraw, *fromraw;
372
373 nameraw = xstrndup(s, colon);
374 name = expandname(ctx, nameraw);
375 free(nameraw);
376 fromraw = xstrndup(s + colon + 1, eq - colon - 1);
377 toraw = xstrndup(s + eq + 1, n - eq - 1);
378 from = expandstr(ctx, fromraw);
379 to = expandstr(ctx, toraw);
380 free(fromraw);
381 free(toraw);
382 v = findvar(ctx->env, name);
383 }
384 base = 0;
385 auto_name = 0;
386 if (v) {
387 base = expandstr(ctx, v->val);
388 } else if (name[0] && name[1] == 0) {
389 /* we handle some substitution refs on automatic vars like $(@:.o=.c). */
390 switch (name[0]) {
391 case '@':
392 auto_name = 1;
393 if (ctx->auto_target)
394 base = xstrdup(ctx->auto_target);
395 break;
396 case '<':
397 auto_name = 1;
398 if (ctx->auto_prereqs && ctx->auto_prereqs->n > 0)
399 base = xstrdup(ctx->auto_prereqs->v[0]);
400 break;
401 case '^':
402 case '+':
403 case '?':
404 auto_name = 1;
405 if (ctx->auto_prereqs)
406 base = autoprereqsval(ctx->auto_prereqs, name[0]);
407 break;
408 case '*':
409 auto_name = 1;
410 if (ctx->auto_stem)
411 base = xstrdup(ctx->auto_stem);
412 break;
413 }
414 }
415 free(name);
416
417 if (base) {
418 if (ctx->mode == MODE_POSIX_2008 && (strchr(from, '%') || strchr(to, '%'))) {
419 evalerr(ctx,
420 "pattern macros in substitution references are not valid in POSIX 2008",
421 0);
422 free(base);
423 free(from);
424 free(to);
425 return xstrdup("");
426 }
427 val = substval(base, from, to);
428 free(base);
429 } else if (auto_name) {
430 char *inner;
431
432 inner = xstrndup(s, n);
433 val = cat3("$(", inner, ")");
434 free(inner);
435 } else {
436 val = xstrdup("");
437 }
438 free(from);
439 free(to);
440 return val;
441}
442
443typedef char *(*fn1_t)(const char *);
444typedef char *(*fn2_t)(const char *, const char *);
445typedef char *(*fn3_t)(const char *, const char *, const char *);
446typedef char *(*ctxfn_t)(struct EvalCtx *, const char *);
447
448enum FuncMode {
449 FNEXP1,
450 FNEXP2,
451 FNEXP3,
452 FNCTX,
453};
454
455/* posix2008 and posix2024 both have no builtin functions, gnu has all of them */
456
457struct func {
458 const char *name;
459 enum FuncMode mode;
460 union {
461 fn1_t f1;
462 fn2_t f2;
463 fn3_t f3;
464 ctxfn_t ctx;
465 } fn;
466};
467
468static const char *const allfuncs[] = {
469 "abspath",
470 "addprefix",
471 "addsuffix",
472 "and",
473 "basename",
474 "call",
475 "dir",
476 "error",
477 "eval",
478 "file",
479 "filter",
480 "filter-out",
481 "findstring",
482 "firstword",
483 "flavor",
484 "foreach",
485 "guile",
486 "if",
487 "info",
488 "intcmp",
489 "join",
490 "lastword",
491 "let",
492 "notdir",
493 "or",
494 "origin",
495 "patsubst",
496 "realpath",
497 "shell",
498 "sort",
499 "strip",
500 "subst",
501 "suffix",
502 "value",
503 "warning",
504 "wildcard",
505 "word",
506 "wordlist",
507 "words",
508 0,
509};
510
511static const struct func funcs[] = {
512 {"wildcard", FNEXP1, {.f1 = fnwildcard}},
513 {"shell", FNEXP1, {.f1 = fnshell}},
514 {"sort", FNEXP1, {.f1 = fnsort}},
515 {"info", FNCTX, {.ctx = fninfo}},
516 {"origin", FNCTX, {.ctx = fnorigin}},
517 {"notdir", FNEXP1, {.f1 = fnnotdir}},
518 {"dir", FNEXP1, {.f1 = fndir}},
519 {"basename", FNEXP1, {.f1 = fnbasename}},
520 {"filter-out", FNEXP2, {.f2 = fnfilterout}},
521 {"filter", FNEXP2, {.f2 = fnfilter}},
522 {"findstring", FNEXP2, {.f2 = fnfindstring}},
523 {"addprefix", FNEXP2, {.f2 = fnaddprefix}},
524 {"addsuffix", FNEXP2, {.f2 = fnaddsuffix}},
525 {"join", FNEXP2, {.f2 = fnjoin}},
526 {"strip", FNEXP1, {.f1 = fnstrip}},
527 {"subst", FNEXP3, {.f3 = fnsubst}},
528 {"patsubst", FNEXP3, {.f3 = fnpatsubst}},
529 {"if", FNEXP3, {.f3 = fnif}},
530 {"call", FNCTX, {.ctx = fncall}},
531 {"foreach", FNCTX, {.ctx = fnforeach}},
532 {"eval", FNCTX, {.ctx = fneval}},
533 {"value", FNCTX, {.ctx = fnvalue}},
534 {"words", FNEXP1, {.f1 = fnwords}},
535 {"word", FNEXP2, {.f2 = fnword}},
536 {"wordlist", FNEXP3, {.f3 = fnwordlist}},
537 {"firstword", FNEXP1, {.f1 = fnfirstword}},
538 {"lastword", FNEXP1, {.f1 = fnlastword}},
539 {"realpath", FNEXP1, {.f1 = fnrealpath}},
540 {"abspath", FNEXP1, {.f1 = fnabspath}},
541 {0, FNEXP1, {.f1 = 0}},
542};
543
544static char *
545funcref(struct EvalCtx *ctx, const char *s, size_t n)
546{
547 size_t i, namelen, start;
548 const struct func *f;
549 char *val;
550
551 if (ctx->mode != MODE_GNU)
552 return 0;
553 for (i = 0; funcs[i].name; i++) {
554 f = &funcs[i];
555 namelen = strlen(f->name);
556 if (n < namelen || memcmp(s, f->name, namelen) != 0)
557 continue;
558 start = namelen;
559 while (start < n && isspace((unsigned char)s[start]))
560 start++;
561 if (f->mode == FNCTX) {
562 char *args;
563
564 args = xstrndup(s + start, n - start);
565 val = f->fn.ctx(ctx, args);
566 free(args);
567 } else if (f->mode == FNEXP1) {
568 char *raw, *exp;
569
570 raw = xstrndup(s + start, n - start);
571 exp = expandstr(ctx, raw);
572 free(raw);
573 val = f->fn.f1(exp);
574 free(exp);
575 } else if (f->mode == FNEXP2) {
576 char *args, *lhs_raw, *rhs_raw, *lhs_exp, *rhs_exp, *detail;
577 ptrdiff_t comma;
578
579 args = xstrndup(s + start, n - start);
580 comma = findargcomma(args, strlen(args));
581 if (comma < 0) {
582 detail = cat3("$(", f->name, ")");
583 evalerr(ctx, "malformed function arguments", detail);
584 free(detail);
585 free(args);
586 return xstrdup("");
587 }
588 lhs_raw = xstrndup(args, (size_t)comma);
589 rhs_raw = xstrdup(args + comma + 1);
590 free(args);
591 lhs_exp = expandstr(ctx, lhs_raw);
592 rhs_exp = expandstr(ctx, rhs_raw);
593 free(lhs_raw);
594 free(rhs_raw);
595 val = f->fn.f2(lhs_exp, rhs_exp);
596 free(lhs_exp);
597 free(rhs_exp);
598 } else {
599 char *args, *a1r, *a2r, *a3r, *a1e, *a2e, *a3e, *detail;
600 ptrdiff_t c1, c2;
601 size_t rest;
602
603 args = xstrndup(s + start, n - start);
604 c1 = findargcomma(args, strlen(args));
605 if (c1 < 0) {
606 detail = cat3("$(", f->name, ")");
607 evalerr(ctx, "malformed function arguments", detail);
608 free(detail);
609 free(args);
610 return xstrdup("");
611 }
612 a1r = xstrndup(args, (size_t)c1);
613 rest = (size_t)c1 + 1;
614 c2 = findargcomma(args + rest, strlen(args + rest));
615 if (c2 < 0) {
616 a2r = xstrdup(args + rest);
617 a3r = xstrdup("");
618 } else {
619 a2r = xstrndup(args + rest, (size_t)c2);
620 a3r = xstrdup(args + rest + (size_t)c2 + 1);
621 }
622 free(args);
623 a1e = expandstr(ctx, a1r);
624 a2e = expandstr(ctx, a2r);
625 a3e = expandstr(ctx, a3r);
626 free(a1r);
627 free(a2r);
628 free(a3r);
629 val = f->fn.f3(a1e, a2e, a3e);
630 free(a1e);
631 free(a2e);
632 free(a3e);
633 }
634 return val;
635 }
636 return 0;
637}
638
639static int
640isfunc(const char *s, size_t n)
641{
642 size_t i;
643
644 for (i = 0; allfuncs[i]; i++) {
645 if (strlen(allfuncs[i]) == n && strncmp(allfuncs[i], s, n) == 0)
646 return 1;
647 }
648 return 0;
649}
650
651static char *
652expandref(struct EvalCtx *ctx, const char *s, size_t n)
653{
654 size_t colon, eq, i;
655 char *val;
656
657 if (isplainvar(s, n))
658 return expandvarref(ctx, s, n);
659 if (issubstref(s, n, &colon, &eq))
660 return expandsubstref(ctx, s, colon, eq, n);
661 val = funcref(ctx, s, n);
662 if (val)
663 return val;
664 for (i = 0; i < n && !isspace((unsigned char)s[i]); i++)
665 ;
666 if (i < n && isfunc(s, i)) {
667 char *detail;
668
669 detail = xstrndup(s, n);
670 if (ctx->mode != MODE_GNU)
671 evalerr(ctx, "functions are only valid in GNU", detail);
672 else
673 evalerr(ctx, "i don't know how to handle that yet", detail);
674 free(detail);
675 return xstrdup("");
676 }
677 return expandvarref(ctx, s, n);
678}
679
680char *
681expandstr(struct EvalCtx *ctx, const char *s)
682{
683 size_t i, j, n;
684 char close;
685 char *val;
686 struct Buf out;
687
688 n = strlen(s);
689 bufinit(&out, n + 1);
690 for (i = 0; i < n; i++) {
691 if (s[i] == '$' && i + 1 < n && s[i + 1] == '$') {
692 bufappendc(&out, '$');
693 i++;
694 continue;
695 }
696 if (s[i] != '$' || i + 1 >= n) {
697 bufappendc(&out, s[i]);
698 continue;
699 }
700 if (s[i + 1] != '(' && s[i + 1] != '{') {
701 if (s[i + 1] == '@' && ctx->auto_target) {
702 bufappend(&out, ctx->auto_target);
703 i++;
704 continue;
705 }
706 if (s[i + 1] == '<' && ctx->auto_prereqs) {
707 if (ctx->auto_prereqs->n > 0)
708 bufappend(&out, ctx->auto_prereqs->v[0]);
709 i++;
710 continue;
711 }
712 if ((s[i + 1] == '^' || s[i + 1] == '+' || s[i + 1] == '?') &&
713 ctx->auto_prereqs) {
714 char *joined;
715
716 joined = autoprereqsval(ctx->auto_prereqs, s[i + 1]);
717 bufappend(&out, joined);
718 free(joined);
719 i++;
720 continue;
721 }
722 if (s[i + 1] == '*' && ctx->auto_stem) {
723 bufappend(&out, ctx->auto_stem);
724 i++;
725 continue;
726 }
727 /* leave unresolved automatic vars for later translation */
728 if (s[i + 1] == '@' || s[i + 1] == '<' || s[i + 1] == '^' ||
729 s[i + 1] == '+' || s[i + 1] == '?' || s[i + 1] == '*' ||
730 s[i + 1] == '%') {
731 bufappendc(&out, s[i]);
732 continue;
733 }
734 /* some single char variable like $x */
735 val = expandvarref(ctx, s + i + 1, 1);
736 bufappend(&out, val);
737 free(val);
738 i++;
739 continue;
740 }
741 close = s[i + 1] == '(' ? ')' : '}';
742 j = findclose(s, i, n, close);
743 if (!j) {
744 bufappendn(&out, s + i, n - i);
745 break;
746 }
747 val = expandref(ctx, s + i + 2, j - i - 3);
748 bufappend(&out, val);
749 free(val);
750 i = j - 1;
751 }
752 return bufdone(&out);
753}