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, rawdepth;
188
189 depth = 0;
190 rawdepth = 0;
191 for (i = 0; i < n; i++) {
192 if (s[i] == '$' && i + 1 < n && (s[i + 1] == '(' || s[i + 1] == '{')) {
193 depth++;
194 i++;
195 continue;
196 }
197 if (s[i] == '(') {
198 rawdepth++;
199 continue;
200 }
201 if (s[i] == ')' && rawdepth > 0) {
202 rawdepth--;
203 continue;
204 }
205 if ((s[i] == ')' || s[i] == '}') && depth > 0) {
206 depth--;
207 continue;
208 }
209 if (s[i] == ',' && depth == 0 && rawdepth == 0)
210 return (ptrdiff_t)i;
211 }
212 return -1;
213}
214
215static size_t
216findclose(const char *s, size_t i, size_t n, char close)
217{
218 size_t j, inner;
219 char open;
220
221 j = i + 2;
222 inner = 1;
223 open = close == ')' ? '(' : '{';
224 while (j < n && inner) {
225 if (s[j] == '$' && j + 1 < n && (s[j + 1] == '(' || s[j + 1] == '{')) {
226 inner++;
227 j += 2;
228 continue;
229 }
230 if (s[j] == open) {
231 inner++;
232 j++;
233 continue;
234 }
235 if (s[j] == close)
236 inner--;
237 j++;
238 }
239 return inner ? 0 : j;
240}
241
242static char *expandvarref(struct EvalCtx *ctx, const char *s, size_t n);
243static char *expandref(struct EvalCtx *ctx, const char *s, size_t n);
244
245static char *
246autoprereqsval(const struct StrList *prereqs, char kind)
247{
248 if (kind == '+')
249 return joinstrs(prereqs, " ");
250
251 {
252 size_t i;
253 struct StrList uniq;
254 char *joined;
255
256 memset(&uniq, 0, sizeof(uniq));
257 for (i = 0; i < prereqs->n; i++) {
258 if (!hasword(&uniq, prereqs->v[i]))
259 addstr(&uniq, prereqs->v[i]);
260 }
261 joined = joinstrs(&uniq, " ");
262 freestrs(&uniq);
263 return joined;
264 }
265}
266
267static char *
268expandname(struct EvalCtx *ctx, const char *s)
269{
270 size_t i, j, n;
271 char close;
272 char *val;
273 struct Buf out;
274
275 n = strlen(s);
276 bufinit(&out, n + 1);
277 for (i = 0; i < n; i++) {
278 if (s[i] != '$' || i + 1 >= n) {
279 bufappendc(&out, s[i]);
280 continue;
281 }
282 if (s[i + 1] == '$') {
283 bufappendc(&out, '$');
284 i++;
285 continue;
286 }
287 if (s[i + 1] == '@' && ctx->auto_target) {
288 bufappend(&out, ctx->auto_target);
289 i++;
290 continue;
291 }
292 if ((s[i + 1] == '<') && ctx->auto_prereqs) {
293 if (ctx->auto_prereqs->n > 0)
294 bufappend(&out, ctx->auto_prereqs->v[0]);
295 i++;
296 continue;
297 }
298 if ((s[i + 1] == '^' || s[i + 1] == '+' || s[i + 1] == '?') &&
299 ctx->auto_prereqs) {
300 char *joined;
301
302 joined = autoprereqsval(ctx->auto_prereqs, s[i + 1]);
303 bufappend(&out, joined);
304 free(joined);
305 i++;
306 continue;
307 }
308 if (s[i + 1] == '*' && ctx->auto_stem) {
309 bufappend(&out, ctx->auto_stem);
310 i++;
311 continue;
312 }
313 if (s[i + 1] != '(' && s[i + 1] != '{') {
314 val = expandvarref(ctx, s + i + 1, 1);
315 bufappend(&out, val);
316 free(val);
317 i++;
318 continue;
319 }
320 close = s[i + 1] == '(' ? ')' : '}';
321 j = findclose(s, i, n, close);
322 if (!j) {
323 bufappendn(&out, s + i, n - i);
324 break;
325 }
326 val = expandref(ctx, s + i + 2, j - i - 3);
327 bufappend(&out, val);
328 free(val);
329 i = j - 1;
330 }
331 return bufdone(&out);
332}
333
334static char *
335expandvarref(struct EvalCtx *ctx, const char *s, size_t n)
336{
337 char *name, *raw, *val;
338 struct Var *v;
339
340 raw = xstrndup(s, n);
341 name = expandname(ctx, raw);
342 free(raw);
343
344 if (n > 0 && ctx->call) {
345 size_t i;
346 int numeric;
347
348 numeric = 1;
349 for (i = 0; name[i]; i++) {
350 if (!isdigit((unsigned char)name[i])) {
351 numeric = 0;
352 break;
353 }
354 }
355 if (numeric) {
356 unsigned long idx;
357 char *end;
358
359 idx = strtoul(name, &end, 10);
360 if (*end == 0 && idx < ctx->call->nargs) {
361 val = expandstr(ctx, ctx->call->args[idx]);
362 free(name);
363 return val;
364 }
365 }
366 }
367
368 v = findvar(ctx->env, name);
369 free(name);
370 if (!v)
371 return xstrdup("");
372 if (v->simple)
373 return xstrdup(v->val);
374 /* expansion could be $(eval ...) and replace this var, freeing its
375 * current value so keep the input alive for the whole expansion */
376 raw = xstrdup(v->val);
377 val = expandstr(ctx, raw);
378 free(raw);
379 return val;
380}
381
382static char *
383expandsubstref(struct EvalCtx *ctx, const char *s, size_t colon, size_t eq, size_t n)
384{
385 char *name, *from, *to, *val;
386 struct Var *v;
387 char *base;
388 int auto_name;
389
390 {
391 char *nameraw, *toraw, *fromraw;
392
393 nameraw = xstrndup(s, colon);
394 name = expandname(ctx, nameraw);
395 free(nameraw);
396 fromraw = xstrndup(s + colon + 1, eq - colon - 1);
397 toraw = xstrndup(s + eq + 1, n - eq - 1);
398 from = expandstr(ctx, fromraw);
399 to = expandstr(ctx, toraw);
400 free(fromraw);
401 free(toraw);
402 v = findvar(ctx->env, name);
403 }
404 base = 0;
405 auto_name = 0;
406 if (v) {
407 char *rawval;
408
409 rawval = xstrdup(v->val);
410 base = expandstr(ctx, rawval);
411 free(rawval);
412 } else if (name[0] && name[1] == 0) {
413 /* we handle some substitution refs on automatic vars like $(@:.o=.c). */
414 switch (name[0]) {
415 case '@':
416 auto_name = 1;
417 if (ctx->auto_target)
418 base = xstrdup(ctx->auto_target);
419 break;
420 case '<':
421 auto_name = 1;
422 if (ctx->auto_prereqs && ctx->auto_prereqs->n > 0)
423 base = xstrdup(ctx->auto_prereqs->v[0]);
424 break;
425 case '^':
426 case '+':
427 case '?':
428 auto_name = 1;
429 if (ctx->auto_prereqs)
430 base = autoprereqsval(ctx->auto_prereqs, name[0]);
431 break;
432 case '*':
433 auto_name = 1;
434 if (ctx->auto_stem)
435 base = xstrdup(ctx->auto_stem);
436 break;
437 }
438 }
439 free(name);
440
441 if (base) {
442 if (ctx->mode == MODE_POSIX_2008 && (strchr(from, '%') || strchr(to, '%'))) {
443 evalerr(ctx,
444 "pattern macros in substitution references are not valid in POSIX 2008",
445 0);
446 free(base);
447 free(from);
448 free(to);
449 return xstrdup("");
450 }
451 val = substval(base, from, to);
452 free(base);
453 } else if (auto_name) {
454 char *inner;
455
456 inner = xstrndup(s, n);
457 val = cat3("$(", inner, ")");
458 free(inner);
459 } else {
460 val = xstrdup("");
461 }
462 free(from);
463 free(to);
464 return val;
465}
466
467typedef char *(*fn1_t)(const char *);
468typedef char *(*fn2_t)(const char *, const char *);
469typedef char *(*fn3_t)(const char *, const char *, const char *);
470typedef char *(*ctxfn_t)(struct EvalCtx *, const char *);
471
472enum FuncMode {
473 FNEXP1,
474 FNEXP2,
475 FNEXP3,
476 FNCTX,
477};
478
479/* posix2008 and posix2024 both have no builtin functions, gnu has all of them */
480
481struct func {
482 const char *name;
483 enum FuncMode mode;
484 union {
485 fn1_t f1;
486 fn2_t f2;
487 fn3_t f3;
488 ctxfn_t ctx;
489 } fn;
490};
491
492static const char *const allfuncs[] = {
493 "abspath",
494 "addprefix",
495 "addsuffix",
496 "and",
497 "basename",
498 "call",
499 "dir",
500 "error",
501 "eval",
502 "file",
503 "filter",
504 "filter-out",
505 "findstring",
506 "firstword",
507 "flavor",
508 "foreach",
509 "guile",
510 "if",
511 "info",
512 "intcmp",
513 "join",
514 "lastword",
515 "let",
516 "notdir",
517 "or",
518 "origin",
519 "patsubst",
520 "realpath",
521 "shell",
522 "sort",
523 "strip",
524 "subst",
525 "suffix",
526 "value",
527 "warning",
528 "wildcard",
529 "word",
530 "wordlist",
531 "words",
532 0,
533};
534
535static const struct func funcs[] = {
536 {"wildcard", FNEXP1, {.f1 = fnwildcard}},
537 {"shell", FNEXP1, {.f1 = fnshell}},
538 {"sort", FNEXP1, {.f1 = fnsort}},
539 {"info", FNCTX, {.ctx = fninfo}},
540 {"origin", FNCTX, {.ctx = fnorigin}},
541 {"notdir", FNEXP1, {.f1 = fnnotdir}},
542 {"dir", FNEXP1, {.f1 = fndir}},
543 {"basename", FNEXP1, {.f1 = fnbasename}},
544 {"filter-out", FNEXP2, {.f2 = fnfilterout}},
545 {"filter", FNEXP2, {.f2 = fnfilter}},
546 {"findstring", FNEXP2, {.f2 = fnfindstring}},
547 {"addprefix", FNEXP2, {.f2 = fnaddprefix}},
548 {"addsuffix", FNEXP2, {.f2 = fnaddsuffix}},
549 {"join", FNEXP2, {.f2 = fnjoin}},
550 {"strip", FNEXP1, {.f1 = fnstrip}},
551 {"subst", FNEXP3, {.f3 = fnsubst}},
552 {"patsubst", FNEXP3, {.f3 = fnpatsubst}},
553 {"if", FNEXP3, {.f3 = fnif}},
554 {"or", FNCTX, {.ctx = fnor}},
555 {"call", FNCTX, {.ctx = fncall}},
556 {"foreach", FNCTX, {.ctx = fnforeach}},
557 {"eval", FNCTX, {.ctx = fneval}},
558 {"value", FNCTX, {.ctx = fnvalue}},
559 {"words", FNEXP1, {.f1 = fnwords}},
560 {"word", FNEXP2, {.f2 = fnword}},
561 {"wordlist", FNEXP3, {.f3 = fnwordlist}},
562 {"firstword", FNEXP1, {.f1 = fnfirstword}},
563 {"lastword", FNEXP1, {.f1 = fnlastword}},
564 {"realpath", FNEXP1, {.f1 = fnrealpath}},
565 {"abspath", FNEXP1, {.f1 = fnabspath}},
566 {0, FNEXP1, {.f1 = 0}},
567};
568
569static char *
570funcref(struct EvalCtx *ctx, const char *s, size_t n)
571{
572 size_t i, namelen, start;
573 const struct func *f;
574 char *val;
575
576 if (ctx->mode != MODE_GNU)
577 return 0;
578 for (i = 0; funcs[i].name; i++) {
579 f = &funcs[i];
580 namelen = strlen(f->name);
581 if (n < namelen || memcmp(s, f->name, namelen) != 0)
582 continue;
583 start = namelen;
584 while (start < n && isspace((unsigned char)s[start]))
585 start++;
586 if (f->mode == FNCTX) {
587 char *args;
588
589 args = xstrndup(s + start, n - start);
590 val = f->fn.ctx(ctx, args);
591 free(args);
592 } else if (f->mode == FNEXP1) {
593 char *raw, *exp;
594
595 raw = xstrndup(s + start, n - start);
596 exp = expandstr(ctx, raw);
597 free(raw);
598 val = f->fn.f1(exp);
599 free(exp);
600 } else if (f->mode == FNEXP2) {
601 char *args, *lhs_raw, *rhs_raw, *lhs_exp, *rhs_exp, *detail;
602 ptrdiff_t comma;
603
604 args = xstrndup(s + start, n - start);
605 comma = findargcomma(args, strlen(args));
606 if (comma < 0) {
607 detail = cat3("$(", f->name, ")");
608 evalerr(ctx, "malformed function arguments", detail);
609 free(detail);
610 free(args);
611 return xstrdup("");
612 }
613 lhs_raw = xstrndup(args, (size_t)comma);
614 rhs_raw = xstrdup(args + comma + 1);
615 free(args);
616 lhs_exp = expandstr(ctx, lhs_raw);
617 rhs_exp = expandstr(ctx, rhs_raw);
618 free(lhs_raw);
619 free(rhs_raw);
620 val = f->fn.f2(lhs_exp, rhs_exp);
621 free(lhs_exp);
622 free(rhs_exp);
623 } else {
624 char *args, *a1r, *a2r, *a3r, *a1e, *a2e, *a3e, *detail;
625 ptrdiff_t c1, c2;
626 size_t rest;
627
628 args = xstrndup(s + start, n - start);
629 c1 = findargcomma(args, strlen(args));
630 if (c1 < 0) {
631 detail = cat3("$(", f->name, ")");
632 evalerr(ctx, "malformed function arguments", detail);
633 free(detail);
634 free(args);
635 return xstrdup("");
636 }
637 a1r = xstrndup(args, (size_t)c1);
638 rest = (size_t)c1 + 1;
639 c2 = findargcomma(args + rest, strlen(args + rest));
640 if (c2 < 0) {
641 a2r = xstrdup(args + rest);
642 a3r = xstrdup("");
643 } else {
644 a2r = xstrndup(args + rest, (size_t)c2);
645 a3r = xstrdup(args + rest + (size_t)c2 + 1);
646 }
647 free(args);
648 a1e = expandstr(ctx, a1r);
649 a2e = expandstr(ctx, a2r);
650 a3e = expandstr(ctx, a3r);
651 free(a1r);
652 free(a2r);
653 free(a3r);
654 val = f->fn.f3(a1e, a2e, a3e);
655 free(a1e);
656 free(a2e);
657 free(a3e);
658 }
659 return val;
660 }
661 return 0;
662}
663
664static int
665isfunc(const char *s, size_t n)
666{
667 size_t i;
668
669 for (i = 0; allfuncs[i]; i++) {
670 if (strlen(allfuncs[i]) == n && strncmp(allfuncs[i], s, n) == 0)
671 return 1;
672 }
673 return 0;
674}
675
676static char *
677expandref(struct EvalCtx *ctx, const char *s, size_t n)
678{
679 size_t colon, eq, i;
680 char *val;
681
682 if (isplainvar(s, n))
683 return expandvarref(ctx, s, n);
684 if (issubstref(s, n, &colon, &eq))
685 return expandsubstref(ctx, s, colon, eq, n);
686 val = funcref(ctx, s, n);
687 if (val)
688 return val;
689 for (i = 0; i < n && !isspace((unsigned char)s[i]); i++)
690 ;
691 if (i < n && isfunc(s, i)) {
692 char *detail;
693
694 detail = xstrndup(s, n);
695 if (ctx->mode != MODE_GNU)
696 evalerr(ctx, "functions are only valid in GNU", detail);
697 else
698 evalerr(ctx, "i don't know how to handle that yet", detail);
699 free(detail);
700 return xstrdup("");
701 }
702 return expandvarref(ctx, s, n);
703}
704
705char *
706expandstr(struct EvalCtx *ctx, const char *s)
707{
708 size_t i, j, n;
709 char close;
710 char *val;
711 struct Buf out;
712
713 n = strlen(s);
714 bufinit(&out, n + 1);
715 for (i = 0; i < n; i++) {
716 if (s[i] == '$' && i + 1 < n && s[i + 1] == '$') {
717 bufappendc(&out, '$');
718 i++;
719 continue;
720 }
721 if (s[i] != '$' || i + 1 >= n) {
722 bufappendc(&out, s[i]);
723 continue;
724 }
725 if (s[i + 1] != '(' && s[i + 1] != '{') {
726 if (s[i + 1] == '@' && ctx->auto_target) {
727 bufappend(&out, ctx->auto_target);
728 i++;
729 continue;
730 }
731 if (s[i + 1] == '<' && ctx->auto_prereqs) {
732 if (ctx->auto_prereqs->n > 0)
733 bufappend(&out, ctx->auto_prereqs->v[0]);
734 i++;
735 continue;
736 }
737 if ((s[i + 1] == '^' || s[i + 1] == '+' || s[i + 1] == '?') &&
738 ctx->auto_prereqs) {
739 char *joined;
740
741 joined = autoprereqsval(ctx->auto_prereqs, s[i + 1]);
742 bufappend(&out, joined);
743 free(joined);
744 i++;
745 continue;
746 }
747 if (s[i + 1] == '*' && ctx->auto_stem) {
748 bufappend(&out, ctx->auto_stem);
749 i++;
750 continue;
751 }
752 /* leave unresolved automatic vars for later translation */
753 if (s[i + 1] == '@' || s[i + 1] == '<' || s[i + 1] == '^' ||
754 s[i + 1] == '+' || s[i + 1] == '?' || s[i + 1] == '*' ||
755 s[i + 1] == '%') {
756 bufappendc(&out, s[i]);
757 continue;
758 }
759 /* some single char variable like $x */
760 val = expandvarref(ctx, s + i + 1, 1);
761 bufappend(&out, val);
762 free(val);
763 i++;
764 continue;
765 }
766 close = s[i + 1] == '(' ? ')' : '}';
767 j = findclose(s, i, n, close);
768 if (!j) {
769 bufappendn(&out, s + i, n - i);
770 break;
771 }
772 val = expandref(ctx, s + i + 2, j - i - 3);
773 bufappend(&out, val);
774 free(val);
775 i = j - 1;
776 }
777 return bufdone(&out);
778}