src/usr.bin/make/var.c

1532 lines
37 KiB
C
Raw Normal View History

2024-06-18 04:24:39 +00:00
/* $OpenBSD: var.c,v 1.107 2024/06/18 02:11:04 millert Exp $ */
/* $NetBSD: var.c,v 1.18 1997/03/18 19:24:46 christos Exp $ */
/*
* Copyright (c) 1999,2000,2007 Marc Espie.
*
* Extensive code modifications for the OpenBSD project.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OPENBSD
* PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) 1988, 1989, 1990, 1993
* The Regents of the University of California. All rights reserved.
* Copyright (c) 1989 by Berkeley Softworks
* All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Adam de Boor.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ohash.h>
#include "defines.h"
#include "buf.h"
#include "cmd_exec.h"
#include "stats.h"
#include "pathnames.h"
#include "varmodifiers.h"
#include "var.h"
#include "varname.h"
#include "error.h"
#include "str.h"
#include "var_int.h"
#include "memory.h"
#include "symtable.h"
#include "gnode.h"
#include "dump.h"
#include "lowparse.h"
/*
* This is a harmless return value for Var_Parse that can be used by Var_Subst
* to determine if there was an error in parsing -- easier than returning
* a flag, as things outside this module don't give a hoot.
*/
char var_Error[] = "";
GNode *current_node = NULL;
/*
* Similar to var_Error, but returned when the 'err' flag for Var_Parse is
* set false. Why not just use a constant? Well, gcc likes to condense
* identical string instances...
*/
static char varNoError[] = "";
bool errorIsOkay;
static bool checkEnvFirst; /* true if environment should be searched for
* variables before the global context */
/* do we need to recompute varname_list */
static bool varname_list_changed = true;
void
Var_setCheckEnvFirst(bool yes)
{
checkEnvFirst = yes;
}
/*
* The rules for variable look-up are complicated.
*
* - Dynamic variables like $@ and $* are special. They always pertain to
* a given variable. In this implementation of make, it is an error to
* try to affect them manually. They are stored in a local symtable directly
* inside the gnode.
*
* Global variables can be obtained:
* - from the command line
* - from the environment
* - from the Makefile proper.
* All of these are stored in a hash global_variables.
*
* Variables set on the command line override Makefile contents, are
* passed to submakes (see Var_AddCmdLine), and are also exported to the
* environment.
*
* Without -e (!checkEnvFirst), make will see variables set in the
* Makefile, and default to the environment otherwise.
*
* With -e (checkEnvFirst), make will see the environment first, and that
* will override anything that's set in the Makefile (but not set on
* the command line).
*
* The SHELL variable is very special: it is never obtained from the
* environment, and never passed to the environment.
*/
/* definitions pertaining to dynamic variables */
/* full names of dynamic variables */
static char *varnames[] = {
TARGET,
PREFIX,
ARCHIVE,
MEMBER,
IMPSRC,
OODATE,
ALLSRC,
FTARGET,
DTARGET,
FPREFIX,
DPREFIX,
FARCHIVE,
DARCHIVE,
FMEMBER,
DMEMBER,
FIMPSRC,
DIMPSRC
};
static bool xtlist[] = {
false, /* GLOBAL_INDEX */
true, /* $@ */
false, /* $* */
false, /* $! */
true, /* $% */
true, /* $< */
false, /* $? */
false, /* $> */
true, /* ${@F} */
true, /* ${@D} */
false, /* ${*F} */
false, /* ${*D} */
false, /* ${!F} */
false, /* ${!D} */
true, /* ${%F} */
true, /* ${%D} */
true, /* ${<F} */
true, /* ${<D} */
};
/* so that we can access tlist[-1] */
static bool *tlist = xtlist+1;
/* hashed names of dynamic variables */
#include "varhashconsts.h"
/* extended indices for System V stuff */
#define FTARGET_INDEX 7
#define DTARGET_INDEX 8
#define FPREFIX_INDEX 9
#define DPREFIX_INDEX 10
#define FARCHIVE_INDEX 11
#define DARCHIVE_INDEX 12
#define FMEMBER_INDEX 13
#define DMEMBER_INDEX 14
#define FIMPSRC_INDEX 15
#define DIMPSRC_INDEX 16
#define GLOBAL_INDEX -1
#define EXTENDED2SIMPLE(i) (((i)-LOCAL_SIZE)/2)
#define IS_EXTENDED_F(i) ((i)%2 == 1)
static struct ohash global_variables;
typedef struct Var_ {
BUFFER val; /* the variable value */
unsigned int flags; /* miscellaneous status flags */
#define VAR_IN_USE 1 /* Variable's value currently being used. */
/* (Used to avoid recursion) */
#define VAR_DUMMY 2 /* Variable is currently just a name */
/* In particular: BUFFER is invalid */
#define VAR_FROM_CMD 4 /* Special source: command line */
#define VAR_FROM_ENV 8 /* Special source: environment */
#define VAR_SEEN_ENV 16 /* No need to go look up environment again */
#define VAR_IS_SHELL 32 /* Magic behavior */
#define VAR_IS_NAMES 1024 /* Very expensive, only defined when needed */
/* XXX there are also some flag values which are part of the visible API
* and thus defined inside var.h, don't forget to look there if you want
* to define some new flags !
*/
#define POISONS (POISON_NORMAL | POISON_EMPTY | POISON_NOT_DEFINED)
/* Defined in var.h */
char name[1]; /* the variable's name */
} Var;
/* for GNU make compatibility */
#define VARNAME_LIST ".VARIABLES"
static struct ohash_info var_info = {
offsetof(Var, name),
NULL,
hash_calloc, hash_free, element_alloc
};
static int classify_var(const char *, const char **, uint32_t *);
static Var *find_global_var(const char *, const char *, uint32_t);
static Var *find_global_var_without_env(const char *, const char *, uint32_t);
static void fill_from_env(Var *);
static Var *create_var(const char *, const char *);
static void var_set_initial_value(Var *, const char *);
static void var_set_value(Var *, const char *);
static char *var_get_value(Var *);
static void var_exec_cmd(Var *);
static void varname_list_retrieve(Var *);
static void var_append_value(Var *, const char *);
static void poison_check(Var *);
static void var_set_append(const char *, const char *, const char *, int, bool);
static void set_magic_shell_variable(void);
static void delete_var(Var *);
static void print_var(Var *);
static const char *find_rparen(const char *);
static const char *find_ket(const char *);
typedef const char * (*find_t)(const char *);
static find_t find_pos(int);
static void push_used(Var *);
static void pop_used(Var *);
static char *get_expanded_value(const char *, const char *, int, uint32_t,
SymTable *, bool, bool *);
static bool parse_base_variable_name(const char **, struct Name *, SymTable *);
/* Variable lookup function: return idx for dynamic variable, or
* GLOBAL_INDEX if name is not dynamic. Set up *pk for further use.
*/
static int
classify_var(const char *name, const char **enamePtr, uint32_t *pk)
{
size_t len;
*pk = ohash_interval(name, enamePtr);
len = *enamePtr - name;
/* substitute short version for long local name */
switch (*pk % MAGICSLOTS1) { /* MAGICSLOTS should be the */
case K_LONGALLSRC % MAGICSLOTS1:/* smallest constant yielding */
/* distinct case values */
if (*pk == K_LONGALLSRC && len == strlen(LONGALLSRC) &&
strncmp(name, LONGALLSRC, len) == 0)
return ALLSRC_INDEX;
break;
case K_LONGARCHIVE % MAGICSLOTS1:
if (*pk == K_LONGARCHIVE && len == strlen(LONGARCHIVE) &&
strncmp(name, LONGARCHIVE, len) == 0)
return ARCHIVE_INDEX;
break;
case K_LONGIMPSRC % MAGICSLOTS1:
if (*pk == K_LONGIMPSRC && len == strlen(LONGIMPSRC) &&
strncmp(name, LONGIMPSRC, len) == 0)
return IMPSRC_INDEX;
break;
case K_LONGMEMBER % MAGICSLOTS1:
if (*pk == K_LONGMEMBER && len == strlen(LONGMEMBER) &&
strncmp(name, LONGMEMBER, len) == 0)
return MEMBER_INDEX;
break;
case K_LONGOODATE % MAGICSLOTS1:
if (*pk == K_LONGOODATE && len == strlen(LONGOODATE) &&
strncmp(name, LONGOODATE, len) == 0)
return OODATE_INDEX;
break;
case K_LONGPREFIX % MAGICSLOTS1:
if (*pk == K_LONGPREFIX && len == strlen(LONGPREFIX) &&
strncmp(name, LONGPREFIX, len) == 0)
return PREFIX_INDEX;
break;
case K_LONGTARGET % MAGICSLOTS1:
if (*pk == K_LONGTARGET && len == strlen(LONGTARGET) &&
strncmp(name, LONGTARGET, len) == 0)
return TARGET_INDEX;
break;
case K_TARGET % MAGICSLOTS1:
if (name[0] == TARGET[0] && len == 1)
return TARGET_INDEX;
break;
case K_OODATE % MAGICSLOTS1:
if (name[0] == OODATE[0] && len == 1)
return OODATE_INDEX;
break;
case K_ALLSRC % MAGICSLOTS1:
if (name[0] == ALLSRC[0] && len == 1)
return ALLSRC_INDEX;
break;
case K_IMPSRC % MAGICSLOTS1:
if (name[0] == IMPSRC[0] && len == 1)
return IMPSRC_INDEX;
break;
case K_PREFIX % MAGICSLOTS1:
if (name[0] == PREFIX[0] && len == 1)
return PREFIX_INDEX;
break;
case K_ARCHIVE % MAGICSLOTS1:
if (name[0] == ARCHIVE[0] && len == 1)
return ARCHIVE_INDEX;
break;
case K_MEMBER % MAGICSLOTS1:
if (name[0] == MEMBER[0] && len == 1)
return MEMBER_INDEX;
break;
case K_FTARGET % MAGICSLOTS1:
if (name[0] == FTARGET[0] && name[1] == FTARGET[1] && len == 2)
return FTARGET_INDEX;
break;
case K_DTARGET % MAGICSLOTS1:
if (name[0] == DTARGET[0] && name[1] == DTARGET[1] && len == 2)
return DTARGET_INDEX;
break;
case K_FPREFIX % MAGICSLOTS1:
if (name[0] == FPREFIX[0] && name[1] == FPREFIX[1] && len == 2)
return FPREFIX_INDEX;
break;
case K_DPREFIX % MAGICSLOTS1:
if (name[0] == DPREFIX[0] && name[1] == DPREFIX[1] && len == 2)
return DPREFIX_INDEX;
break;
case K_FARCHIVE % MAGICSLOTS1:
if (name[0] == FARCHIVE[0] && name[1] == FARCHIVE[1] &&
len == 2)
return FARCHIVE_INDEX;
break;
case K_DARCHIVE % MAGICSLOTS1:
if (name[0] == DARCHIVE[0] && name[1] == DARCHIVE[1] &&
len == 2)
return DARCHIVE_INDEX;
break;
case K_FMEMBER % MAGICSLOTS1:
if (name[0] == FMEMBER[0] && name[1] == FMEMBER[1] && len == 2)
return FMEMBER_INDEX;
break;
case K_DMEMBER % MAGICSLOTS1:
if (name[0] == DMEMBER[0] && name[1] == DMEMBER[1] && len == 2)
return DMEMBER_INDEX;
break;
case K_FIMPSRC % MAGICSLOTS1:
if (name[0] == FIMPSRC[0] && name[1] == FIMPSRC[1] && len == 2)
return FIMPSRC_INDEX;
break;
case K_DIMPSRC % MAGICSLOTS1:
if (name[0] == DIMPSRC[0] && name[1] == DIMPSRC[1] && len == 2)
return DIMPSRC_INDEX;
break;
default:
break;
}
return GLOBAL_INDEX;
}
/***
*** Internal handling of variables.
***/
/* Create a new variable, does not initialize anything except the name.
* in particular, buffer is invalid, and flag value is invalid. Accordingly,
* must either:
* - set flags to VAR_DUMMY
* - set flags to !VAR_DUMMY, and initialize buffer, for instance with
* var_set_initial_value().
*/
static Var *
create_var(const char *name, const char *ename)
{
return ohash_create_entry(&var_info, name, &ename);
}
/* Initial version of var_set_value(), to be called after create_var().
*/
static void
var_set_initial_value(Var *v, const char *val)
{
size_t len;
len = strlen(val);
Buf_Init(&(v->val), len+1);
Buf_AddChars(&(v->val), len, val);
varname_list_changed = true;
}
/* Normal version of var_set_value(), to be called after variable is fully
* initialized.
*/
static void
var_set_value(Var *v, const char *val)
{
if ((v->flags & VAR_DUMMY) == 0) {
Buf_Reset(&(v->val));
Buf_AddString(&(v->val), val);
} else {
var_set_initial_value(v, val);
v->flags &= ~VAR_DUMMY;
}
}
static char *
var_get_value(Var *v)
{
if (v->flags & VAR_IS_NAMES)
varname_list_retrieve(v);
else if (v->flags & VAR_EXEC_LATER)
var_exec_cmd(v);
return Buf_Retrieve(&(v->val));
}
/* Add to a variable, insert a separating space if the variable was already
* defined.
*/
static void
var_append_value(Var *v, const char *val)
{
if ((v->flags & VAR_DUMMY) == 0) {
Buf_AddSpace(&(v->val));
Buf_AddString(&(v->val), val);
} else {
var_set_initial_value(v, val);
v->flags &= ~VAR_DUMMY;
}
}
/* Delete a variable and all the space associated with it.
*/
static void
delete_var(Var *v)
{
if ((v->flags & VAR_DUMMY) == 0)
Buf_Destroy(&(v->val));
free(v);
}
/***
*** Dynamic variable handling.
***/
/* create empty symtable.
* XXX: to save space, dynamic variables may be NULL pointers.
*/
void
SymTable_Init(SymTable *ctxt)
{
static SymTable sym_template;
memcpy(ctxt, &sym_template, sizeof(*ctxt));
}
/***
*** Global variable handling.
***/
/* Create a new global var if necessary, and set it up correctly.
* Do not take environment into account.
*/
static Var *
find_global_var_without_env(const char *name, const char *ename, uint32_t k)
{
unsigned int slot;
Var *v;
slot = ohash_lookup_interval(&global_variables, name, ename, k);
v = ohash_find(&global_variables, slot);
if (v == NULL) {
v = create_var(name, ename);
v->flags = VAR_DUMMY;
ohash_insert(&global_variables, slot, v);
}
return v;
}
/* Helper for find_global_var(): grab environment value if needed.
*/
static void
fill_from_env(Var *v)
{
char *env;
env = getenv(v->name);
if (env == NULL)
v->flags |= VAR_SEEN_ENV;
else {
var_set_value(v, env);
v->flags |= VAR_FROM_ENV | VAR_SEEN_ENV;
}
#ifdef STATS_VAR_LOOKUP
STAT_VAR_FROM_ENV++;
#endif
}
/* Find global var, and obtain its value from the environment if needed.
*/
static Var *
find_global_var(const char *name, const char *ename, uint32_t k)
{
Var *v;
v = find_global_var_without_env(name, ename, k);
if ((v->flags & VAR_SEEN_ENV) == 0)
if ((checkEnvFirst && (v->flags & VAR_FROM_CMD) == 0) ||
(v->flags & VAR_DUMMY) != 0)
fill_from_env(v);
return v;
}
/* mark variable with special flags, in a given setup.
*/
void
Var_Mark(const char *name, const char *ename, unsigned int type)
{
Var *v;
uint32_t k;
int idx;
idx = classify_var(name, &ename, &k);
if (idx != GLOBAL_INDEX) {
Parse_Error(PARSE_FATAL,
"Trying to poison dynamic variable $%s",
varnames[idx]);
return;
}
v = find_global_var(name, ename, k);
v->flags |= type;
/* POISON_NORMAL is not lazy: if the variable already exists in
* the Makefile, then it's a mistake.
*/
if (v->flags & POISON_NORMAL) {
if (v->flags & VAR_DUMMY)
return;
if (v->flags & VAR_FROM_ENV)
return;
Parse_Error(PARSE_FATAL,
"Poisoned variable %s is already set\n", v->name);
}
}
/* Check if there's any reason not to use this variable.
*/
static void
poison_check(Var *v)
{
if (v->flags & POISON_NORMAL) {
Parse_Error(PARSE_FATAL,
"Poisoned variable %s has been referenced\n", v->name);
return;
}
if (v->flags & VAR_DUMMY) {
Parse_Error(PARSE_FATAL,
"Poisoned variable %s is not defined\n", v->name);
return;
}
if (v->flags & POISON_EMPTY)
if (strcmp(var_get_value(v), "") == 0)
Parse_Error(PARSE_FATAL,
"Poisoned variable %s is empty\n", v->name);
}
/* Delete global variable.
*/
void
Var_Deletei(const char *name, const char *ename)
{
Var *v;
uint32_t k;
unsigned int slot;
int idx;
idx = classify_var(name, &ename, &k);
if (idx != GLOBAL_INDEX) {
Parse_Error(PARSE_FATAL,
"Trying to delete dynamic variable $%s", varnames[idx]);
return;
}
slot = ohash_lookup_interval(&global_variables, name, ename, k);
v = ohash_find(&global_variables, slot);
if (v == NULL)
return;
if (checkEnvFirst && (v->flags & VAR_FROM_ENV))
return;
if (v->flags & VAR_FROM_CMD)
return;
ohash_remove(&global_variables, slot);
delete_var(v);
varname_list_changed = true;
}
/* Set or add a global variable, either to VAR_CMD or VAR_GLOBAL.
*/
static void
var_set_append(const char *name, const char *ename, const char *val, int ctxt,
bool append)
{
Var *v;
uint32_t k;
int idx;
idx = classify_var(name, &ename, &k);
if (idx != GLOBAL_INDEX) {
Parse_Error(PARSE_FATAL, "Trying to %s dynamic variable $%s",
append ? "append to" : "set", varnames[idx]);
return;
}
v = find_global_var(name, ename, k);
if (v->flags & POISON_NORMAL)
Parse_Error(PARSE_FATAL, "Trying to %s poisoned variable %s\n",
append ? "append to" : "set", v->name);
/* so can we write to it ? */
if (ctxt == VAR_CMD) { /* always for command line */
(append ? var_append_value : var_set_value)(v, val);
v->flags |= VAR_FROM_CMD;
if ((v->flags & VAR_IS_SHELL) == 0) {
/* Any variables given on the command line are
* automatically exported to the environment,
* except for SHELL (as per POSIX standard).
*/
esetenv(v->name, val);
}
if (DEBUG(VAR))
printf("command:%s = %s\n", v->name, var_get_value(v));
} else if ((v->flags & VAR_FROM_CMD) == 0 &&
(!checkEnvFirst || (v->flags & VAR_FROM_ENV) == 0)) {
(append ? var_append_value : var_set_value)(v, val);
if (DEBUG(VAR))
printf("global:%s = %s\n", v->name, var_get_value(v));
} else if (DEBUG(VAR))
printf("overridden:%s = %s\n", v->name, var_get_value(v));
}
void
Var_Seti_with_ctxt(const char *name, const char *ename, const char *val,
int ctxt)
{
var_set_append(name, ename, val, ctxt, false);
}
void
Var_Appendi_with_ctxt(const char *name, const char *ename, const char *val,
int ctxt)
{
var_set_append(name, ename, val, ctxt, true);
}
static void
var_exec_cmd(Var *v)
{
char *arg = Buf_Retrieve(&(v->val));
char *err;
char *res1;
res1 = Cmd_Exec(arg, &err);
if (err)
Parse_Error(PARSE_WARNING, err, arg);
var_set_value(v, res1);
free(res1);
v->flags &= ~VAR_EXEC_LATER;
}
static void
varname_list_retrieve(Var *v)
{
unsigned int i;
void *e;
bool first = true;
if (!varname_list_changed)
return;
for (e = ohash_first(&global_variables, &i); e != NULL;
e = ohash_next(&global_variables, &i)) {
Var *v2 = e;
if (v2->flags & VAR_DUMMY)
continue;
if (first)
var_set_value(v, v2->name);
else
var_append_value(v, v2->name);
first = false;
}
varname_list_changed = false;
}
/* XXX different semantics for Var_Valuei() and Var_Definedi():
* references to poisoned value variables will error out in Var_Valuei(),
* but not in Var_Definedi(), so the following construct works:
* .poison BINDIR
* BINDIR ?= /usr/bin
*/
char *
Var_Valuei(const char *name, const char *ename)
{
Var *v;
uint32_t k;
int idx;
idx = classify_var(name, &ename, &k);
if (idx != GLOBAL_INDEX) {
Parse_Error(PARSE_FATAL,
"Trying to get value of dynamic variable $%s",
varnames[idx]);
return NULL;
}
v = find_global_var(name, ename, k);
if (v->flags & POISONS)
poison_check(v);
if ((v->flags & VAR_DUMMY) == 0)
return var_get_value(v);
else
return NULL;
}
bool
Var_Definedi(const char *name, const char *ename)
{
Var *v;
uint32_t k;
int idx;
idx = classify_var(name, &ename, &k);
/* We don't bother writing an error message for dynamic variables,
* these will be caught when getting set later, usually.
*/
if (idx == GLOBAL_INDEX) {
v = find_global_var(name, ename, k);
if (v->flags & POISON_NORMAL)
poison_check(v);
if ((v->flags & VAR_DUMMY) == 0)
return true;
}
return false;
}
/***
*** Substitution functions, handling both global and dynamic variables.
***/
/* All the scanning functions needed to account for all the forms of
* variable names that exist:
* $A, ${AB}, $(ABC), ${A:mod}, $(A:mod)
*/
static const char *
find_rparen(const char *p)
{
while (*p != '$' && *p != '\0' && *p != ')' && *p != ':')
p++;
return p;
}
static const char *
find_ket(const char *p)
{
while (*p != '$' && *p != '\0' && *p != '}' && *p != ':')
p++;
return p;
}
/* Figure out what kind of name we're looking for from a start character.
*/
static find_t
find_pos(int c)
{
switch(c) {
case '(':
return find_rparen;
case '{':
return find_ket;
default:
Parse_Error(PARSE_FATAL,
"Wrong character in variable spec %c (can't happen)", c);
return find_rparen;
}
}
static bool
parse_base_variable_name(const char **pstr, struct Name *name, SymTable *ctxt)
{
const char *str = *pstr;
const char *tstr;
bool has_modifier = false;
switch(str[1]) {
case '(':
case '{':
/* Find eventual modifiers in the variable */
tstr = VarName_Get(str+2, name, ctxt, false, find_pos(str[1]));
if (*tstr == '\0')
Parse_Error(PARSE_FATAL, "Unterminated variable spec in %s", *pstr);
else if (*tstr == ':')
has_modifier = true;
else
tstr++;
break;
default:
name->s = str+1;
name->e = str+2;
name->tofree = false;
tstr = str + 2;
break;
}
*pstr = tstr;
return has_modifier;
}
bool
Var_ParseSkip(const char **pstr, SymTable *ctxt)
{
const char *str = *pstr;
struct Name name;
bool result;
bool has_modifier;
const char *tstr = str;
if (str[1] == 0) {
*pstr = str+1;
return false;
}
has_modifier = parse_base_variable_name(&tstr, &name, ctxt);
VarName_Free(&name);
result = true;
if (has_modifier) {
bool freePtr = false;
char *s = VarModifiers_Apply(NULL, NULL, ctxt, true, &freePtr,
&tstr, str[1]);
if (s == var_Error)
result = false;
if (freePtr)
free(s);
}
*pstr = tstr;
return result;
}
/* As of now, Var_ParseBuffer is just a wrapper around Var_Parse. For
* speed, it may be better to revisit the implementation to do things
* directly. */
bool
Var_ParseBuffer(Buffer buf, const char *str, SymTable *ctxt, bool err,
size_t *lengthPtr)
{
char *result;
bool freeIt;
result = Var_Parse(str, ctxt, err, lengthPtr, &freeIt);
if (result == var_Error)
return false;
Buf_AddString(buf, result);
if (freeIt)
free(result);
return true;
}
/* Helper function for Var_Parse: still recursive, but we tag what variables
* we expand for better error messages.
*/
#define MAX_DEPTH 350
static Var *call_trace[MAX_DEPTH];
static int current_depth = 0;
static void
push_used(Var *v)
{
if (v->flags & VAR_IN_USE) {
int i;
fprintf(stderr, "Problem with variable expansion chain: ");
for (i = 0;
i < (current_depth > MAX_DEPTH ? MAX_DEPTH : current_depth);
i++)
fprintf(stderr, "%s -> ", call_trace[i]->name);
fprintf(stderr, "%s\n", v->name);
Fatal("\tVariable %s is recursive.", v->name);
/*NOTREACHED*/
}
v->flags |= VAR_IN_USE;
if (current_depth < MAX_DEPTH)
call_trace[current_depth] = v;
current_depth++;
}
static void
pop_used(Var *v)
{
v->flags &= ~VAR_IN_USE;
current_depth--;
}
static char *
get_expanded_value(const char *name, const char *ename, int idx, uint32_t k,
SymTable *ctxt, bool err, bool *freePtr)
{
char *val;
/* Before doing any modification, we have to make sure the
* value has been fully expanded. If it looks like recursion
* might be necessary (there's a dollar sign somewhere in
* the variable's value) we just call Var_Subst to do any
* other substitutions that are necessary. Note that the
* value returned by Var_Subst will have been dynamically
* allocated, so it will need freeing when we return.
*/
if (idx == GLOBAL_INDEX) {
Var *v = find_global_var(name, ename, k);
if (v == NULL)
return NULL;
if ((v->flags & POISONS) != 0)
poison_check(v);
if ((v->flags & VAR_DUMMY) != 0)
return NULL;
val = var_get_value(v);
if (strchr(val, '$') != NULL) {
push_used(v);
val = Var_Subst(val, ctxt, err);
pop_used(v);
*freePtr = true;
}
} else {
if (ctxt != NULL) {
if (idx < LOCAL_SIZE)
val = ctxt->locals[idx];
else
val = ctxt->locals[EXTENDED2SIMPLE(idx)];
} else
val = NULL;
if (val == NULL)
return NULL;
if (idx >= LOCAL_SIZE) {
if (IS_EXTENDED_F(idx))
val = Var_GetTail(val);
else
val = Var_GetHead(val);
*freePtr = true;
}
}
return val;
}
#define ERRMSG1 "Using $< in a non-suffix rule context is a GNUmake idiom "
#define ERRMSG2 "Using undefined dynamic variable $%s "
static void
bad_dynamic_variable(int idx)
{
Location origin;
Parse_FillLocation(&origin);
if (idx >= LOCAL_SIZE)
idx = EXTENDED2SIMPLE(idx);
switch(idx) {
case IMPSRC_INDEX:
if (origin.fname)
Fatal(ERRMSG1 "(%s:%lu)",
origin.fname, origin.lineno);
else if (current_node)
Fatal(ERRMSG1 "(prereq of %s)", current_node->name);
else
Fatal(ERRMSG1 "(?)");
break;
default:
if (origin.fname)
Error(ERRMSG2 "(%s:%lu)", varnames[idx],
origin.fname, origin.lineno);
else if (current_node)
Error(ERRMSG2 "(prereq of %s)", varnames[idx],
current_node->name);
else
Error(ERRMSG2 "(?)", varnames[idx]);
break;
}
}
char *
Var_Parse(const char *str, /* The string to parse */
SymTable *ctxt, /* The context for the variable */
bool err, /* true if undefined variables are an error */
size_t *lengthPtr, /* OUT: The length of the specification */
bool *freePtr) /* OUT: true if caller should free result */
{
const char *tstr;
struct Name name;
char *val;
uint32_t k;
int idx;
bool has_modifier;
*freePtr = false;
tstr = str;
if (str[1] == 0) {
*lengthPtr = 1;
*freePtr = false;
return err ? var_Error : varNoError;
}
has_modifier = parse_base_variable_name(&tstr, &name, ctxt);
idx = classify_var(name.s, &name.e, &k);
val = get_expanded_value(name.s, name.e, idx, k, ctxt, err, freePtr);
if (has_modifier) {
val = VarModifiers_Apply(val, &name, ctxt, err, freePtr,
&tstr, str[1]);
}
if (val == NULL) {
val = err ? var_Error : varNoError;
/* If it comes from a dynamic source, and it doesn't have
* a local context, copy the spec instead.
* Specifically, this make allows constructs like:
* target.o: $*.c
* Absence of a context means "parsing". But these can't
* be expanded during parsing, to be consistent with the
* way .SUFFIXES work.
* .SUFFIXES may be added/reset/removed during parsing,
* but in the end, the final list is what's considered for
* handling targets. So those dynamic variables must be
* handled lazily too.
*/
if (idx != GLOBAL_INDEX) {
if (ctxt == NULL) {
*freePtr = true;
val = Str_dupi(str, tstr);
} else {
bad_dynamic_variable(idx);
}
}
}
VarName_Free(&name);
*lengthPtr = tstr - str;
return val;
}
char *
Var_Subst(const char *str, /* the string in which to substitute */
SymTable *ctxt, /* the context wherein to find variables */
bool undefErr) /* true if undefineds are an error */
{
BUFFER buf; /* Buffer for forming things */
static bool errorReported;
Buf_Init(&buf, MAKE_BSIZE);
errorReported = false;
for (;;) {
char *val; /* Value to substitute for a variable */
size_t length; /* Length of the variable invocation */
bool doFree; /* Set true if val should be freed */
const char *cp;
/* copy uninteresting stuff */
for (cp = str; *str != '\0' && *str != '$'; str++)
;
Buf_Addi(&buf, cp, str);
if (*str == '\0')
break;
if (str[1] == '$') {
/* A $ may be escaped with another $. */
Buf_AddChar(&buf, '$');
str += 2;
continue;
}
val = Var_Parse(str, ctxt, undefErr, &length, &doFree);
/* When we come down here, val should either point to the
* value of this variable, suitably modified, or be NULL.
* Length should be the total length of the potential
* variable invocation (from $ to end character...) */
if (val == var_Error || val == varNoError) {
/* If errors are not an issue, skip over the variable
* and continue with the substitution. Otherwise, store
* the dollar sign and advance str so we continue with
* the string... */
if (errorIsOkay)
str += length;
else if (undefErr) {
/* If variable is undefined, complain and
* skip the variable name. The complaint
* will stop us from doing anything when
* the file is parsed. */
if (!errorReported)
Parse_Error(PARSE_FATAL,
"Undefined variable \"%.*s\"",
(int)length, str);
str += length;
errorReported = true;
} else {
Buf_AddChar(&buf, *str);
str++;
}
} else {
/* We've now got a variable structure to store in.
* But first, advance the string pointer. */
str += length;
/* Copy all the characters from the variable value
* straight into the new string. */
Buf_AddString(&buf, val);
if (doFree)
free(val);
}
}
return Buf_Retrieve(&buf);
}
/* Very quick version of the variable scanner that just looks for target
* variables, and never ever errors out
*/
bool
Var_Check_for_target(const char *str)
{
bool seen_target = false;
for (;;) {
const char *tstr;
uint32_t k;
int idx;
bool has_modifier;
struct Name name;
/* skip over uninteresting stuff */
for (; *str != '\0' && *str != '$'; str++)
;
if (*str == '\0')
break;
if (str[1] == '$') {
/* A $ may be escaped with another $. */
str += 2;
continue;
}
tstr = str;
has_modifier = parse_base_variable_name(&tstr, &name, NULL);
idx = classify_var(name.s, &name.e, &k);
if (has_modifier) {
bool doFree = false;
char *val = VarModifiers_Apply(NULL, NULL, NULL, false,
&doFree, &tstr, str[1]);
if (doFree)
free(val);
}
if (tlist[idx])
seen_target = true;
VarName_Free(&name);
str = tstr;
}
return seen_target;
}
static BUFFER subst_buffer;
/* we would like to subst on intervals, but it's complicated, so we cheat
* by storing the interval in a static buffer.
*/
char *
Var_Substi(const char *str, const char *estr, SymTable *ctxt, bool undefErr)
{
/* delimited string: no need to copy */
if (estr == NULL || *estr == '\0')
return Var_Subst(str, ctxt, undefErr);
Buf_Reset(&subst_buffer);
Buf_Addi(&subst_buffer, str, estr);
return Var_Subst(Buf_Retrieve(&subst_buffer), ctxt, undefErr);
}
/***
*** Supplementary support for .for loops.
***/
struct LoopVar
{
Var old; /* keep old variable value (before the loop) */
Var *me; /* the variable we're dealing with */
};
struct LoopVar *
Var_NewLoopVar(const char *name, const char *ename)
{
struct LoopVar *l;
uint32_t k;
l = emalloc(sizeof(struct LoopVar));
/* we obtain a new variable quickly, make a snapshot of its old
* value, and make sure the environment cannot touch us.
*/
/* XXX: should we avoid dynamic variables ? */
k = ohash_interval(name, &ename);
l->me = find_global_var_without_env(name, ename, k);
l->old = *(l->me);
l->me->flags = VAR_SEEN_ENV | VAR_DUMMY;
return l;
}
char *
Var_LoopVarName(struct LoopVar *v)
{
return v->me->name;
}
void
Var_DeleteLoopVar(struct LoopVar *l)
{
if ((l->me->flags & VAR_DUMMY) == 0)
Buf_Destroy(&(l->me->val));
*(l->me) = l->old;
free(l);
}
void
Var_SubstVar(Buffer buf, /* To store result */
const char *str, /* The string in which to substitute */
struct LoopVar *l, /* Handle */
const char *val) /* Its value */
{
const char *var = l->me->name;
var_set_value(l->me, val);
for (;;) {
const char *start;
/* Copy uninteresting stuff */
for (start = str; *str != '\0' && *str != '$'; str++)
;
Buf_Addi(buf, start, str);
start = str;
if (*str++ == '\0')
break;
str++;
/* and escaped dollars */
if (start[1] == '$') {
Buf_Addi(buf, start, start+2);
continue;
}
/* Simple variable, if it's not us, copy. */
if (start[1] != '(' && start[1] != '{') {
if (start[1] != *var || var[1] != '\0') {
Buf_AddChars(buf, 2, start);
continue;
}
} else {
const char *p;
char paren = start[1];
/* Find the end of the variable specification. */
p = find_pos(paren)(str);
/* A variable inside the variable. We don't know how to
* expand the external variable at this point, so we
* try again with the nested variable. */
if (*p == '$') {
Buf_Addi(buf, start, p);
str = p;
continue;
}
if (strncmp(var, str, p - str) != 0 ||
var[p - str] != '\0') {
/* Not the variable we want to expand. */
Buf_Addi(buf, start, p);
str = p;
continue;
}
if (*p == ':') {
bool doFree; /* should val be freed ? */
char *newval;
struct Name name;
doFree = false;
name.s = var;
name.e = var + (p-str);
/* val won't be freed since !doFree, but
* VarModifiers_Apply doesn't know that,
* hence the cast. */
newval = VarModifiers_Apply((char *)val,
&name, NULL, false, &doFree, &p, paren);
Buf_AddString(buf, newval);
if (doFree)
free(newval);
str = p;
continue;
} else
str = p+1;
}
Buf_AddString(buf, val);
}
}
/***
*** Odds and ends
***/
static void
2024-06-18 04:24:39 +00:00
set_magic_shell_variable(void)
{
const char *name = "SHELL";
const char *ename = NULL;
uint32_t k;
Var *v;
k = ohash_interval(name, &ename);
v = find_global_var_without_env(name, ename, k);
var_set_value(v, _PATH_BSHELL);
/* XXX the environment shall never affect it */
v->flags = VAR_IS_SHELL | VAR_SEEN_ENV;
}
static void
2024-06-18 04:24:39 +00:00
set_magic_name_list_variable(void)
{
const char *name = VARNAME_LIST;
const char *ename = NULL;
uint32_t k;
Var *v;
k = ohash_interval(name, &ename);
v = find_global_var_without_env(name, ename, k);
/* XXX We need to set a "dummy" value because that variable can't be
* VAR_DUMMY, since we wouldn't hit var_get_value otherwise.
*/
var_set_initial_value(v, "");
v->flags = VAR_IS_NAMES;
}
/*
* Var_Init
* Initialize the module
*/
void
Var_Init(void)
{
ohash_init(&global_variables, 10, &var_info);
set_magic_shell_variable();
set_magic_name_list_variable();
errorIsOkay = true;
Var_setCheckEnvFirst(false);
VarModifiers_Init();
Buf_Init(&subst_buffer, MAKE_BSIZE);
}
static const char *interpret(int);
static const char *
interpret(int f)
{
if (f & VAR_DUMMY)
return "(D)";
return "";
}
static void
print_var(Var *v)
{
printf("%-16s%s = %s\n", v->name, interpret(v->flags),
(v->flags & VAR_DUMMY) == 0 ? var_get_value(v) : "(none)");
}
void
Var_Dump(void)
{
Var **t;
unsigned int i;
const char *banner;
bool first = true;
t = sort_ohash_by_name(&global_variables);
/* somewhat dirty, but does the trick */
#define LOOP(mask, value, do_stuff) \
for (i = 0; t[i] != NULL; i++) \
if ((t[i]->flags & (mask)) == (value)) { \
if (banner) { \
if (first) \
first = false; \
else \
putchar('\n'); \
fputs(banner, stdout); \
banner = NULL; \
} \
do_stuff; \
}
banner = "#variables from command line:\n";
LOOP(VAR_FROM_CMD | VAR_DUMMY, VAR_FROM_CMD, print_var(t[i]));
banner = "#global variables:\n";
LOOP(VAR_FROM_ENV| VAR_FROM_CMD | VAR_DUMMY, 0, print_var(t[i]));
banner = "#variables from env:\n";
LOOP(VAR_FROM_ENV|VAR_DUMMY, VAR_FROM_ENV, print_var(t[i]));
banner = "#variable name seen, but not defined:";
LOOP(VAR_DUMMY|POISONS, VAR_DUMMY, printf(" %s", t[i]->name));
#undef LOOP
printf("\n\n");
for (i = 0; t[i] != NULL; i++)
switch(t[i]->flags & POISONS) {
case POISON_NORMAL:
printf(".poison %s\n", t[i]->name);
break;
case POISON_EMPTY:
printf(".poison empty(%s)\n", t[i]->name);
break;
case POISON_NOT_DEFINED:
printf(".poison !defined(%s)\n", t[i]->name);
break;
default:
break;
}
free(t);
printf("\n");
}
static const char *quotable = " \t\n\\'\"";
/* POSIX says that variable assignments passed on the command line should be
* propagated to sub makes through MAKEFLAGS.
*/
void
Var_AddCmdline(const char *name)
{
Var *v;
unsigned int i;
BUFFER buf;
char *s;
Buf_Init(&buf, MAKE_BSIZE);
for (v = ohash_first(&global_variables, &i); v != NULL;
v = ohash_next(&global_variables, &i)) {
/* This is not as expensive as it looks: this function is
* called before parsing Makefiles, so there are just a
* few non cmdling variables in there.
*/
if (!(v->flags & VAR_FROM_CMD)) {
continue;
}
/* We assume variable names don't need quoting */
Buf_AddString(&buf, v->name);
Buf_AddChar(&buf, '=');
for (s = var_get_value(v); *s != '\0'; s++) {
if (strchr(quotable, *s))
Buf_AddChar(&buf, '\\');
Buf_AddChar(&buf, *s);
}
Buf_AddSpace(&buf);
}
Var_Append(name, Buf_Retrieve(&buf));
Buf_Destroy(&buf);
}