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