main shrub/shinobi / src / eval / eval.c
  1#include "shinobi.h"
  2#include "internal.h"
  3#include "posix.h"
  4
  5#include <glob.h>
  6#include <stdio.h>
  7#include <stdlib.h>
  8#include <string.h>
  9
 10/*
 11 * eval does the second pass over the built ast
 12 *   seed builtins like CC and so on
 13 *   apply assignment semantics
 14 *   execute conditionals and flatten the chosen branch
 15 */
 16
 17static char *
 18runshellassign(const char *cmd)
 19{
 20	FILE *fp;
 21	char buf[4096];
 22	char *out;
 23	size_t len, cap, nread, i;
 24
 25	fp = popen(cmd, "r");
 26	if (!fp)
 27		return xstrdup("");
 28
 29	cap = 64;
 30	len = 0;
 31	out = xmalloc(cap);
 32	out[0] = 0;
 33
 34	while ((nread = fread(buf, 1, sizeof(buf), fp)) > 0) {
 35		if (len + nread + 1 > cap) {
 36			while (cap < len + nread + 1)
 37				cap *= 2;
 38			out = xrealloc(out, cap);
 39		}
 40		memcpy(out + len, buf, nread);
 41		len += nread;
 42	}
 43	out[len] = 0;
 44	pclose(fp);
 45	if (len > 0 && out[len - 1] == '\n') {
 46		len--;
 47		if (len > 0 && out[len - 1] == '\r')
 48			len--;
 49		out[len] = 0;
 50	} else if (len > 0 && out[len - 1] == '\r') {
 51		len--;
 52		out[len] = 0;
 53	}
 54
 55	for (i = 0; i < len; i++) {
 56		if (out[i] == '\n' || out[i] == '\r')
 57			out[i] = ' ';
 58	}
 59	return out;
 60}
 61
 62static char *
 63escapedollars(const char *s)
 64{
 65	size_t i, n, ndollar;
 66	char *out;
 67	size_t j;
 68
 69	n = strlen(s);
 70	ndollar = 0;
 71	for (i = 0; i < n; i++) {
 72		if (s[i] == '$')
 73			ndollar++;
 74	}
 75	out = xmalloc(n + ndollar + 1);
 76	j = 0;
 77	for (i = 0; i < n; i++) {
 78		if (s[i] == '$')
 79			out[j++] = '$';
 80		out[j++] = s[i];
 81	}
 82	out[j] = 0;
 83	return out;
 84}
 85
 86static void
 87removeexportname(struct StrList *list, const char *name)
 88{
 89	size_t i;
 90
 91	for (i = 0; i < list->n; i++) {
 92		if (strcmp(list->v[i], name) != 0)
 93			continue;
 94		free(list->v[i]);
 95		memmove(&list->v[i], &list->v[i + 1], (list->n - i - 1) * sizeof(list->v[0]));
 96		list->n--;
 97		return;
 98	}
 99}
100
101static int
102assignexported(const struct EvalCtx *ctx, const struct AssignNode *in)
103{
104	if (in->exported > 0)
105		return 1;
106	if (in->exported < 0)
107		return 0;
108	if (hasword(&ctx->unexports, in->lhs))
109		return 0;
110	if (hasword(&ctx->exports, in->lhs))
111		return 1;
112	return ctx->export_all;
113}
114
115void
116evalassign(struct EvalCtx *ctx, const struct AssignNode *in)
117{
118	struct Env *env;
119	struct Var *v;
120	char *lhs, *rhs, *joined;
121	enum Origin o;
122	int exported;
123
124	env = ctx->env;
125	o = in->origin ? in->origin : ORIGIN_FILE;
126	exported = assignexported(ctx, in);
127	lhs = expandstr(ctx, in->lhs);
128	switch (in->op) {
129	case ASSIGN_EQ:
130		envsetvar(env, lhs, xstrdup(in->rhs), 0, o, exported);
131		break;
132	case ASSIGN_DCOLON_EQ:
133		if (ctx->mode == MODE_POSIX_2008) {
134			dielikemake(ctx->cur_path, ctx->cur_line, "'::=' is not valid in POSIX 2008", 0);
135			ctx->errors++;
136			break;
137		}
138		envsetvar(env, lhs, expandstr(ctx, in->rhs), 1, o, exported);
139		break;
140	case ASSIGN_COLON_EQ:
141		/* := is a gnu extension. posix uses ::= for simple expansion.
142		 * bsd := is different again, equivalent to posix :::= */
143		if (ctx->mode == MODE_POSIX_2024) {
144			dielikemake(ctx->cur_path, ctx->cur_line, "':=' is not valid in POSIX 2024, use '::='", 0);
145			ctx->errors++;
146			break;
147		}
148		if (ctx->mode == MODE_POSIX_2008) {
149			dielikemake(ctx->cur_path, ctx->cur_line, "':=' is not valid in POSIX 2008", 0);
150			ctx->errors++;
151			break;
152		}
153		envsetvar(env, lhs, expandstr(ctx, in->rhs), 1, o, exported);
154		break;
155	case ASSIGN_COLON3_EQ:
156		if (ctx->mode == MODE_POSIX_2008) {
157			dielikemake(ctx->cur_path, ctx->cur_line, "':::=' is not valid in POSIX 2008", 0);
158			ctx->errors++;
159			break;
160		}
161		/* expand now, escape the result so re-expansion on use gives back the same value */
162		rhs = expandstr(ctx, in->rhs);
163		joined = escapedollars(rhs);
164		free(rhs);
165		envsetvar(env, lhs, joined, 0, o, exported);
166		break;
167	case ASSIGN_QMARK_EQ:
168		if (ctx->mode == MODE_POSIX_2008) {
169			dielikemake(ctx->cur_path, ctx->cur_line, "'?=' is not valid in POSIX 2008", 0);
170			ctx->errors++;
171			break;
172		}
173		if (!findvar(env, lhs))
174			envsetvar(env, lhs, xstrdup(in->rhs), 0, o, exported);
175		break;
176	case ASSIGN_PLUS_EQ:
177		if (ctx->mode == MODE_POSIX_2008) {
178			dielikemake(ctx->cur_path, ctx->cur_line, "'+=' is not valid in POSIX 2008", 0);
179			ctx->errors++;
180			break;
181		}
182		v = findvar(env, lhs);
183		if (!v) {
184			envsetvar(env, lhs, xstrdup(in->rhs), 0, o, in->exported);
185			break;
186		}
187		if ((int)o < (int)v->origin)
188			break;
189		rhs = v->simple ? expandstr(ctx, in->rhs) : xstrdup(in->rhs);
190		joined = cat3(v->val, " ", rhs);
191		free(rhs);
192		free(v->val);
193		v->val = joined;
194		v->origin = o;
195		if (exported)
196			v->exported = 1;
197		break;
198	case ASSIGN_BANG_EQ:
199		if (ctx->mode == MODE_POSIX_2008) {
200			dielikemake(ctx->cur_path, ctx->cur_line, "'!=' is not valid in POSIX 2008", 0);
201			ctx->errors++;
202			break;
203		}
204		rhs = expandstr(ctx, in->rhs);
205		joined = runshellassign(rhs);
206		free(rhs);
207		envsetvar(env, lhs, joined, 1, o, exported);
208		break;
209	}
210	free(lhs);
211}
212
213static void
214evalexport(struct EvalCtx *ctx, const struct ExportNode *exp)
215{
216	struct StrList names;
217	struct Var *v;
218	size_t i, j;
219
220	if (exp->all) {
221		size_t j;
222
223		ctx->export_all = exp->exported;
224		for (i = 0; i < ctx->env->n; i++)
225			ctx->env->v[i].exported = exp->exported;
226		for (j = 0; j < ctx->out->nvars; j++)
227			ctx->out->vars[j].exported = exp->exported;
228		for (j = 0; j < ctx->out->ntvars; j++)
229			ctx->out->tvars[j].exported = exp->exported;
230		return;
231	}
232	memset(&names, 0, sizeof(names));
233	for (i = 0; i < exp->names.n; i++) {
234		char *s;
235
236		s = expandstr(ctx, exp->names.v[i]);
237		splitwords(&names, s, strlen(s));
238		free(s);
239	}
240	for (i = 0; i < names.n; i++) {
241		if (exp->exported) {
242			removeexportname(&ctx->unexports, names.v[i]);
243			if (!hasword(&ctx->exports, names.v[i]))
244				addstr(&ctx->exports, names.v[i]);
245		} else {
246			removeexportname(&ctx->exports, names.v[i]);
247			if (!hasword(&ctx->unexports, names.v[i]))
248				addstr(&ctx->unexports, names.v[i]);
249		}
250		v = findvar(ctx->env, names.v[i]);
251		if (v)
252			v->exported = exp->exported;
253	}
254	for (j = 0; j < ctx->out->nvars; j++) {
255		if (hasword(&ctx->exports, ctx->out->vars[j].lhs))
256			ctx->out->vars[j].exported = 1;
257		if (hasword(&ctx->unexports, ctx->out->vars[j].lhs))
258			ctx->out->vars[j].exported = 0;
259	}
260	for (j = 0; j < ctx->out->ntvars; j++) {
261		if (hasword(&ctx->exports, ctx->out->tvars[j].lhs))
262			ctx->out->tvars[j].exported = 1;
263		if (hasword(&ctx->unexports, ctx->out->tvars[j].lhs))
264			ctx->out->tvars[j].exported = 0;
265	}
266	freestrs(&names);
267}
268
269static int
270testcond(struct EvalCtx *ctx, const struct CondNode *cond)
271{
272	char *a, *b, *name;
273	struct Var *v;
274	int ok;
275
276	if (cond->kind == COND_IFDEF || cond->kind == COND_IFNDEF) {
277		name = expandstr(ctx, cond->arg1);
278		v = findvar(ctx->env, name);
279		ok = v != 0;
280		free(name);
281		if (cond->kind == COND_IFNDEF)
282			ok = !ok;
283		return ok;
284	}
285
286	a = cond->arg1 ? expandstr(ctx, cond->arg1) : xstrdup("");
287	b = cond->arg2 ? expandstr(ctx, cond->arg2) : xstrdup("");
288	ok = strcmp(a, b) == 0;
289	free(a);
290	free(b);
291	if (cond->kind == COND_IFNEQ)
292		ok = !ok;
293	return ok;
294}
295
296static void
297copywords(struct StrList *out, const struct StrList *in, struct EvalCtx *ctx)
298{
299	size_t i;
300
301	memset(out, 0, sizeof(*out));
302	for (i = 0; i < in->n; i++) {
303		char *s;
304
305		s = expandstr(ctx, in->v[i]);
306		splitwords(out, s, strlen(s));
307		free(s);
308	}
309}
310
311static void
312addrulesetassign(struct AssignNode **vec, size_t *n, const struct Node *src, struct EvalCtx *ctx)
313{
314	struct AssignNode *dst;
315
316	*vec = xrealloc(*vec, (*n + 1) * sizeof((*vec)[0]));
317	dst = &(*vec)[(*n)++];
318	memset(dst, 0, sizeof(*dst));
319	dst->lhs = xstrdup(src->data.assign.lhs);
320	dst->op = src->data.assign.op;
321	dst->origin = src->data.assign.origin;
322	dst->exported = assignexported(ctx, &src->data.assign);
323	dst->tspec = src->data.assign.tspec;
324	if (src->data.assign.op == ASSIGN_DCOLON_EQ ||
325	    src->data.assign.op == ASSIGN_COLON_EQ ||
326	    src->data.assign.op == ASSIGN_COLON3_EQ) {
327		char *rhs;
328
329		rhs = expandstr(ctx, src->data.assign.rhs);
330		dst->rhs = escapedollars(rhs);
331		free(rhs);
332	} else if (src->data.assign.op == ASSIGN_BANG_EQ) {
333		dst->rhs = expandstr(ctx, src->data.assign.rhs);
334	} else {
335		dst->rhs = xstrdup(src->data.assign.rhs);
336	}
337	copywords(&dst->targets, &src->data.assign.targets, ctx);
338}
339
340static int
341issufruletargets(const struct RuleNode *rule)
342{
343	size_t i;
344	char *a, *b;
345
346	if (rule->targets.n == 0)
347		return 0;
348	for (i = 0; i < rule->targets.n; i++) {
349		if (issinglesuf(rule->targets.v[i], &a)) {
350			free(a);
351			continue;
352		}
353		if (issuf(rule->targets.v[i], &a, &b)) {
354			free(a);
355			free(b);
356			continue;
357		}
358		return 0;
359	}
360	return 1;
361}
362
363static void
364addrulesetrule(struct RuleSet *out, const struct RuleNode *src, struct EvalCtx *ctx)
365{
366	struct RuleNode *dst;
367
368	out->rules = xrealloc(out->rules, (out->nrules + 1) * sizeof(out->rules[0]));
369	dst = &out->rules[out->nrules++];
370	memset(dst, 0, sizeof(*dst));
371	dst->dcolon = src->dcolon;
372	copywords(&dst->targets, &src->targets, ctx);
373	if (src->target_pattern)
374		dst->target_pattern = expandstr(ctx, src->target_pattern);
375	copywords(&dst->prereqs, &src->prereqs, ctx);
376	copywords(&dst->order_only, &src->order_only, ctx);
377	addrecipes(&dst->recipes, &src->recipes);
378}
379
380static int
381hasglobmeta(const char *s)
382{
383	for (; *s; s++) {
384		if (*s == '*' || *s == '?' || *s == '[')
385			return 1;
386	}
387	return 0;
388}
389
390static int
391evalinclude(struct EvalCtx *ctx, const struct IncludeNode *inc)
392{
393	struct StrList paths;
394	char *exp;
395	size_t i;
396
397	struct RuleNode *makerule;
398
399	makerule = 0;
400
401	exp = expandstr(ctx, inc->path);
402	memset(&paths, 0, sizeof(paths));
403	splitwords(&paths, exp, strlen(exp));
404	free(exp);
405	for (i = 0; i < paths.n; i++) {
406		size_t j, nmatch;
407		char *single;
408		const char *word;
409		glob_t g;
410		int grc;
411
412		memset(&g, 0, sizeof(g));
413		single = 0;
414		word = paths.v[i];
415		nmatch = 0;
416		grc = 0;
417		/* glob in include paths is a gnu extension */
418		if (ctx->mode == MODE_GNU && hasglobmeta(word)) {
419			grc = glob(word, 0, 0, &g);
420			nmatch = (grc == 0 && g.gl_pathc > 0) ? g.gl_pathc : 0;
421		} else {
422			nmatch = 1;
423			single = xstrdup(word);
424		}
425
426		if (nmatch == 0) {
427			if (!inc->optional)
428				fprintf(stderr, "%s:%d: %s: No such file or directory\n",
429				        ctx->cur_path, ctx->cur_line, word);
430			globfree(&g);
431			continue;
432		}
433
434		for (j = 0; j < nmatch; j++) {
435			char *src;
436			const char *path;
437
438			path = single ? single : g.gl_pathv[j];
439			src = readfile(path);
440			if (!src) {
441				size_t r, k;
442
443				makerule = 0;
444				for (r = 0; r < ctx->out->nrules && !makerule; r++) {
445					struct RuleNode *rule = &ctx->out->rules[r];
446
447					for (k = 0; k < rule->targets.n; k++) {
448						if (strcmp(rule->targets.v[k], path) == 0) {
449							makerule = rule;
450							break;
451						}
452					}
453				}
454				if (makerule) {
455					for (k = 0; k < makerule->recipes.n; k++) {
456						char *cmd;
457						int rc;
458
459						cmd = expandstr(ctx, makerule->recipes.v[k].body);
460						rc = system(cmd);
461						free(cmd);
462						if (rc != 0)
463							break;
464					}
465					src = readfile(path);
466				}
467			}
468			if (!src) {
469				if (!inc->optional)
470					fprintf(stderr, "%s:%d: %s: No such file or directory\n",
471					        ctx->cur_path, ctx->cur_line, path);
472				continue;
473			}
474			if (evalsnippet(ctx, path, src) < 0) {
475				free(src);
476				if (single)
477					free(single);
478				globfree(&g);
479				freestrs(&paths);
480				return -1;
481			}
482			free(src);
483		}
484		if (single)
485			free(single);
486		globfree(&g);
487	}
488	freestrs(&paths);
489	return 0;
490}
491
492static int
493isgnutarget(const char *s)
494{
495	/*gnu only special targets that we actually handle*/
496	static const char *const gnutargets[] = {
497		".EXPORT_ALL_VARIABLES",
498		0
499	};
500	size_t i;
501
502	for (i = 0; gnutargets[i]; i++) {
503		if (strcmp(s, gnutargets[i]) == 0)
504			return 1;
505	}
506	return 0;
507}
508
509static int
510evalnodes(const struct NodeList *in, struct RuleSet *out, struct EvalCtx *ctx)
511{
512	size_t i;
513	struct SpecialTargets targets;
514
515	initspecialtargets(&targets);
516
517	for (i = 0; i < in->n; i++) {
518		const struct Node *src;
519
520		src = &in->v[i];
521		ctx->cur_line = src->loc.line0;
522		switch (src->kind) {
523		case NODE_BLANK:
524			break;
525		case NODE_RAW: {
526			char *exp;
527
528			exp = expandstr(ctx, src->data.raw.text);
529			free(exp);
530			break;
531		}
532		case NODE_COMMENT:
533			break;
534		case NODE_INCLUDE:
535			if (evalinclude(ctx, &src->data.include) < 0)
536				return -1;
537			break;
538		case NODE_COND:
539			if (ctx->mode != MODE_GNU) {
540				dielikemake(ctx->cur_path, ctx->cur_line, "conditionals are only valid in GNU", 0);
541				ctx->errors++;
542				break;
543			}
544			if (testcond(ctx, &src->data.cond)) {
545				if (evalnodes(&src->data.cond.thenpart, out, ctx) < 0)
546					return -1;
547			} else {
548				if (evalnodes(&src->data.cond.elsepart, out, ctx) < 0)
549					return -1;
550			}
551			continue;
552		case NODE_ASSIGN:
553			if (ctx->mode != MODE_GNU && src->data.assign.define_block) {
554				dielikemake(ctx->cur_path, ctx->cur_line, "'define'/'endef' are only valid in GNU", 0);
555				ctx->errors++;
556				break;
557			}
558			if (ctx->mode != MODE_GNU && src->data.assign.exported != 0) {
559				dielikemake(ctx->cur_path, ctx->cur_line, "'export'/'unexport' are only valid in GNU", 0);
560				ctx->errors++;
561				break;
562			}
563			updatespecialassign(&targets, src->data.assign.lhs, src->data.assign.rhs);
564			if (src->data.assign.tspec) {
565				addrulesetassign(&out->tvars, &out->ntvars, src, ctx);
566			} else {
567				addrulesetassign(&out->vars, &out->nvars, src, ctx);
568				evalassign(ctx, &src->data.assign);
569			}
570			break;
571		case NODE_EXPORT:
572			if (ctx->mode != MODE_GNU) {
573				dielikemake(ctx->cur_path, ctx->cur_line, "'export'/'unexport' are only valid in GNU", 0);
574				ctx->errors++;
575				break;
576			}
577			evalexport(ctx, &src->data.export);
578			break;
579		case NODE_RULE: {
580			struct StrList exptargets;
581			struct RuleNode tmprule;
582
583			/* expand targets before special target checks so that some pattern like
584			 * "$X.POSIX:" with X undefined correctly resolves to just ".POSIX:" and
585			 * set targets.posix before suffix rule prereq warnings fire
586			 * (this is for gnu/features/suffixrules/t009). */
587			memset(&exptargets, 0, sizeof(exptargets));
588			copywords(&exptargets, &src->data.rule.targets, ctx);
589			tmprule = src->data.rule;
590			tmprule.targets = exptargets;
591			if (ctx->mode != MODE_GNU && tmprule.dcolon) {
592				dielikemake(ctx->cur_path, ctx->cur_line,
593				            "double-colon rules are only valid in GNU", 0);
594				ctx->errors++;
595				freestrs(&exptargets);
596				break;
597			}
598			/* ignore gnu only targets if not in gnu mode */
599			if (ctx->mode != MODE_GNU &&
600			    tmprule.targets.n == 1 &&
601			    isgnutarget(tmprule.targets.v[0])) {
602				freestrs(&exptargets);
603				break;
604			}
605			if (tmprule.targets.n == 1 &&
606			    strcmp(tmprule.targets.v[0], ".EXPORT_ALL_VARIABLES") == 0) {
607				ctx->export_all = 1;
608				freestrs(&exptargets);
609				break;
610			}
611			if (tmprule.targets.n == 1 &&
612			    strcmp(tmprule.targets.v[0], ".DEFAULT") == 0) {
613				freerecipes(&out->defaultrule);
614				addrecipes(&out->defaultrule, &src->data.rule.recipes);
615				freestrs(&exptargets);
616				break;
617			}
618			if (handlespecialrule(&targets, &tmprule)) {
619				freestrs(&exptargets);
620				break;
621			}
622			if (!targets.posix &&
623			    src->data.rule.prereqs.n > 0 &&
624			    !issufrule(&tmprule) &&
625			    issufruletargets(&tmprule))
626				warnlikemake(ctx->cur_path, ctx->cur_line,
627				             "ignoring prerequisites on suffix rule definition");
628			freestrs(&exptargets);
629			addrulesetrule(out, &src->data.rule, ctx);
630			break;
631		}
632		}
633	}
634	if (out)
635		addwords(&out->phony, &targets.phony);
636	if (out)
637		out->export_all = ctx->export_all || targets.export_all;
638	if (out)
639		out->posix = targets.posix;
640	freestrs(&targets.phony);
641	return 0;
642}
643
644int
645evalsnippet(struct EvalCtx *ctx, const char *path, const char *src)
646{
647	struct Ast ast;
648	const char *saved_path;
649	int rc;
650
651	if (parse(path, src, &ast, ctx->mode) < 0)
652		return -1;
653	saved_path = ctx->cur_path;
654	ctx->cur_path = path;
655	rc = evalnodes((const struct NodeList *)&ast, ctx->out, ctx);
656	ctx->cur_path = saved_path;
657	freeast(&ast);
658	return rc;
659}
660
661int
662eval(const char *path, const struct Ast *ast, const struct Ast *pre, int envoverride, enum ShinMode mode, struct RuleSet *out)
663{
664	struct Env env;
665	struct EvalCtx ctx;
666	int rc;
667
668	memset(out, 0, sizeof(*out));
669	out->envoverride = envoverride;
670	memset(&env, 0, sizeof(env));
671	memset(&ctx, 0, sizeof(ctx));
672	seedenv(&env, 0, out->envoverride, mode);
673	ctx.env = &env;
674	ctx.out = out;
675	ctx.cur_path = path;
676	ctx.mode = mode;
677	if (pre) {
678		rc = evalnodes((const struct NodeList *)pre, out, &ctx);
679		if (rc < 0) {
680			freeenv(&env);
681			return rc;
682		}
683	}
684	rc = evalnodes((const struct NodeList *)ast, out, &ctx);
685	freeenv(&env);
686	freestrs(&ctx.exports);
687	freestrs(&ctx.unexports);
688	if (rc == 0 && ctx.errors)
689		return -1;
690	return rc;
691}
692
693static void
694freeassigns(struct AssignNode *v, size_t n)
695{
696	size_t i;
697
698	for (i = 0; i < n; i++) {
699		free(v[i].lhs);
700		free(v[i].rhs);
701		freestrs(&v[i].targets);
702	}
703	free(v);
704}
705
706static void
707freerules(struct RuleNode *v, size_t n)
708{
709	size_t i;
710
711	for (i = 0; i < n; i++) {
712		freestrs(&v[i].targets);
713		free(v[i].target_pattern);
714		freestrs(&v[i].prereqs);
715		freestrs(&v[i].order_only);
716		freerecipes(&v[i].recipes);
717	}
718	free(v);
719}
720
721void
722freeruleset(struct RuleSet *ruleset)
723{
724	if (!ruleset)
725		return;
726	freeassigns(ruleset->vars, ruleset->nvars);
727	freeassigns(ruleset->tvars, ruleset->ntvars);
728	freerules(ruleset->rules, ruleset->nrules);
729	freerecipes(&ruleset->defaultrule);
730	freestrs(&ruleset->phony);
731	memset(ruleset, 0, sizeof(*ruleset));
732}
733
734/*
735 * strings in ast nodes are arena-owned, only free the v arrays and
736 * the submake strings (xmalloc'd separately by parsesubmake)
737 */
738static void
739freenodes(struct NodeList *list)
740{
741	size_t i, j;
742
743	for (i = 0; i < list->n; i++) {
744		switch (list->v[i].kind) {
745		case NODE_COMMENT:
746		case NODE_RAW:
747		case NODE_INCLUDE:
748			break;
749		case NODE_EXPORT:
750			free(list->v[i].data.export.names.v);
751			break;
752		case NODE_ASSIGN:
753			free(list->v[i].data.assign.targets.v);
754			break;
755		case NODE_RULE:
756			free(list->v[i].data.rule.targets.v);
757			free(list->v[i].data.rule.prereqs.v);
758			free(list->v[i].data.rule.order_only.v);
759			for (j = 0; j < list->v[i].data.rule.recipes.n; j++)
760				freesubmake(&list->v[i].data.rule.recipes.v[j].sm);
761			free(list->v[i].data.rule.recipes.v);
762			break;
763		case NODE_COND:
764			freenodes(&list->v[i].data.cond.thenpart);
765			freenodes(&list->v[i].data.cond.elsepart);
766			break;
767		case NODE_BLANK:
768			break;
769		}
770	}
771	free(list->v);
772	list->v = 0;
773	list->n = 0;
774	list->cap = 0;
775}
776
777void
778freeast(struct Ast *ast)
779{
780	freenodes((struct NodeList *)ast);
781	arena_free(&ast->arena);
782}