compile.c #1

  • //
  • guest/
  • matt_armstrong/
  • jam/
  • fixes/
  • compile.c
  • View
  • Commits
  • Open Download .zip Download (17 KB)
/*
 * Copyright 1993, 1995 Christopher Seiwald.
 *
 * This file is part of Jam - see jam.c for Copyright information.
 */

# include "jam.h"

# include "lists.h"
# include "parse.h"
# include "compile.h"
# include "variable.h"
# include "rules.h"
# include "newstr.h"
# include "make.h"
# include "search.h"

/*
 * compile.c - compile parsed jam statements
 *
 * External routines:
 *
 *	compile_foreach() - compile the "for x in y" statement
 *	compile_if() - compile 'if' rule
 *	compile_include() - support for 'include' - call include() on file
 *	compile_local() - declare (and set) local variables
 *	compile_null() - do nothing -- a stub for parsing
 *	compile_rule() - compile a single user defined rule
 *	compile_rules() - compile a chain of rules
 *	compile_set() - compile the "set variable" statement
 *	compile_setcomp() - support for `rule` - save parse tree 
 *	compile_setexec() - support for `actions` - save execution string 
 *	compile_settings() - compile the "on =" (set variable on exec) statement
 *	compile_switch() - compile 'switch' rule
 *
 * Internal routines:
 *
 *	debug_compile() - printf with indent to show rule expansion.
 *
 *	evaluate_if() - evaluate if to determine which leg to compile
 *
 *	builtin_depends() - DEPENDS/INCLUDES rule
 *	builtin_echo() - ECHO rule
 *	builtin_exit() - EXIT rule
 *	builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
 *
 * 02/03/94 (seiwald) -	Changed trace output to read "setting" instead of 
 *			the awkward sounding "settings".
 * 04/12/94 (seiwald) - Combined build_depends() with build_includes().
 * 04/12/94 (seiwald) - actionlist() now just appends a single action.
 * 04/13/94 (seiwald) - added shorthand L0 for null list pointer
 * 05/13/94 (seiwald) - include files are now bound as targets, and thus
 *			can make use of $(SEARCH)
 * 08/23/94 (seiwald) - Support for '+=' (append to variable)
 * 12/20/94 (seiwald) - NOTIME renamed NOTFILE.
 * 01/22/95 (seiwald) - Exit rule.
 * 02/02/95 (seiwald) - Always rule; LEAVES rule.
 * 02/14/95 (seiwald) - NoUpdate rule.
 */

static void debug_compile();

static int evaluate_if();

static void builtin_depends();
static void builtin_echo();
static void builtin_exit();
static void builtin_flags();

int glob();



/*
 * compile_builtin() - define builtin rules
 */

# define P0 (PARSE *)0
# define C0 (char *)0

void
compile_builtins()
{
    bindrule( "Always" )->procedure = 
    bindrule( "ALWAYS" )->procedure = 
	parse_make( builtin_flags, P0, P0, C0, C0, L0, L0, T_FLAG_TOUCHED );

    bindrule( "Depends" )->procedure = 
    bindrule( "DEPENDS" )->procedure = 
	parse_make( builtin_depends, P0, P0, C0, C0, L0, L0, T_DEPS_DEPENDS );

    bindrule( "Echo" )->procedure = 
    bindrule( "ECHO" )->procedure = 
	parse_make( builtin_echo, P0, P0, C0, C0, L0, L0, 0 );

    bindrule( "Exit" )->procedure = 
    bindrule( "EXIT" )->procedure = 
	parse_make( builtin_exit, P0, P0, C0, C0, L0, L0, 0 );

    bindrule( "Includes" )->procedure = 
    bindrule( "INCLUDES" )->procedure = 
	parse_make( builtin_depends, P0, P0, C0, C0, L0, L0, T_DEPS_INCLUDES );

    bindrule( "Leaves" )->procedure = 
    bindrule( "LEAVES" )->procedure = 
	parse_make( builtin_flags, P0, P0, C0, C0, L0, L0, T_FLAG_LEAVES );

    bindrule( "NoCare" )->procedure = 
    bindrule( "NOCARE" )->procedure = 
	parse_make( builtin_flags, P0, P0, C0, C0, L0, L0, T_FLAG_NOCARE );

    bindrule( "NOTIME" )->procedure = 
    bindrule( "NotFile" )->procedure = 
    bindrule( "NOTFILE" )->procedure = 
	parse_make( builtin_flags, P0, P0, C0, C0, L0, L0, T_FLAG_NOTFILE );

    bindrule( "NoUpdate" )->procedure = 
    bindrule( "NOUPDATE" )->procedure = 
	parse_make( builtin_flags, P0, P0, C0, C0, L0, L0, T_FLAG_NOUPDATE );

    bindrule( "Temporary" )->procedure = 
    bindrule( "TEMPORARY" )->procedure = 
	parse_make( builtin_flags, P0, P0, C0, C0, L0, L0, T_FLAG_TEMP );
}

/*
 * compile_foreach() - compile the "for x in y" statement
 *
 * Compile_foreach() resets the given variable name to each specified
 * value, executing the commands enclosed in braces for each iteration.
 *
 *	parse->string	index variable
 *	parse->left	rule to compile
 *	parse->llist	variable values
 */

void
compile_foreach( parse, args )
PARSE		*parse;
LOL		*args;
{
	LIST	*nv = var_list( parse->llist, args );
	LIST	*l;

	/* Call var_set to reset $(parse->string) for each val. */

	for( l = nv; l; l = list_next( l ) )
	{
	    LIST *val = list_new( L0, copystr( l->string ) );

	    var_set( parse->string, val, VAR_SET );

	    (*parse->left->func)( parse->left, args );
	}

	list_free( nv );
}

/*
 * compile_if() - compile 'if' rule
 *
 *	parse->left		condition tree
 *	parse->right->left	then tree
 *	parse->right->right	else tree
 */

void
compile_if( parse, args )
PARSE		*parse;
LOL		*args;
{
	if( evaluate_if( parse->left, args ) )
	{
	    (*parse->right->left->func)( parse->right->left, args );
	}
	else
	{
	    (*parse->right->right->func)( parse->right->right, args );
	}
}

/*
 * evaluate_if() - evaluate if to determine which leg to compile
 *
 * Returns:
 *	!0	if expression true - compile 'then' clause
 *	0	if expression false - compile 'else' clause
 */

static int
evaluate_if( parse, args )
PARSE		*parse;
LOL		*args;
{
	int	status;

	if( parse->num <= COND_OR )
	{
	    /* Handle one of the logical operators */

	    switch( parse->num )
	    {
	    case COND_NOT:
		status = !evaluate_if( parse->left, args );
		break;

	    case COND_AND:
		status = evaluate_if( parse->left, args ) &&
			 evaluate_if( parse->right, args );
		break;

	    case COND_OR:
		status = evaluate_if( parse->left, args ) ||
			 evaluate_if( parse->right, args );
		break;

	    default:
		status = 0;	/* can't happen */
	    }
	}
	else
	{
	    /* Handle one of the comparison operators */
	    /* Expand targets and sources */

	    LIST *nt, *ns;
	    nt = var_list( parse->llist, args );
	    ns = var_list( parse->rlist, args );

	    /* "a in b" make sure each of a is equal to something in b. */
	    /* Otherwise, step through pairwise comparison. */

	    if( parse->num == COND_IN )
	    {
		LIST *s, *t;

		/* Try each t until failure. */

		for( status = 1, t = nt; status && t; t = list_next( t ) )
		{
		    int stat1;

		    /* Try each s until success */

		    for( stat1 = 0, s = ns; !stat1 && s; s = list_next( s ) )
			stat1 = !strcmp( t->string, s->string );

		    status = stat1;
		}
	    }
	    else
	    {
		LIST *s = ns, *t = nt;

		status = 0;

		while( !status && ( t || s ) )
		{
		    char *st = t ? t->string : "";
		    char *ss = s ? s->string : "";

		    status = strcmp( st, ss );

		    t = t ? list_next( t ) : t;
		    s = s ? list_next( s ) : s;
		}
	    }

	    switch( parse->num )
	    {
	    case COND_EXISTS:	status = status > 0 ; break;
	    case COND_EQUALS:	status = !status; break;
	    case COND_NOTEQ:	status = status != 0; break;
	    case COND_LESS:	status = status < 0; break;
	    case COND_LESSEQ:	status = status <= 0; break;
	    case COND_MORE:	status = status > 0; break;
	    case COND_MOREEQ:	status = status >= 0; break;
	    case COND_IN:	/* status = status */ break;
	    }

	    if( DEBUG_IF )
	    {
		debug_compile( 0, "if" );
		list_print( nt );
		printf( "(%d)", status );
		list_print( ns );
		printf( "\n" );
	    }

	    list_free( nt );
	    list_free( ns );

	}

	return status;
}

/*
 * compile_include() - support for 'include' - call include() on file
 *
 * 	parse->llist	list of files to include (can only do 1)
 */

void
compile_include( parse, args )
PARSE		*parse;
LOL		*args;
{
	LIST	*nt = var_list( parse->llist, args );

	if( DEBUG_COMPILE )
	{
	    debug_compile( 0, "include" );
	    list_print( nt );
	    printf( "\n" );
	}

	if( nt )
	{
	    TARGET *t = bindtarget( nt->string );

	    /* Bind the include file under the influence of */
	    /* "on-target" variables.  Though they are targets, */
	    /* include files are not built with make(). */

	    pushsettings( t->settings );
	    t->boundname = search( t->name, &t->time );
	    popsettings( t->settings );

	    parse_file( t->boundname );
	}

	list_free( nt );
}

/*
 * compile_local() - declare (and set) local variables
 *
 *	parse->llist	list of variables
 *	parse->rlist	list of values
 *	parse->left	rules to execute
 */

void
compile_local( parse, args )
PARSE		*parse;
LOL		*args;
{
	LIST *l;
	SETTINGS *s = 0;
	LIST	*nt = var_list( parse->llist, args );
	LIST	*ns = var_list( parse->rlist, args );

	if( DEBUG_COMPILE )
	{
	    debug_compile( 0, "local" );
	    list_print( nt );
	    printf( " = " );
	    list_print( ns );
	    printf( "\n" );
	}

	/* Initial value is ns */

	for( l = nt; l; l = list_next( l ) )
	    s = addsettings( s, 0, l->string, list_copy( (LIST*)0, ns ) );

	list_free( ns );
	list_free( nt );

	/* Note that callees of the current context get this "local" */
	/* variable, making it not so much local as layered. */

	pushsettings( s );
	(*parse->left->func)( parse->left, args );
	popsettings( s );

	freesettings( s );
}

/*
 * compile_null() - do nothing -- a stub for parsing
 */

void
compile_null( parse, args )
PARSE		*parse;
LOL		*args;
{
}

/*
 * compile_rule() - compile a single user defined rule
 *
 *	parse->string	name of user defined rule
 *	parse->left	parameters (list of lists) to rule, recursing left
 */

void
compile_rule( parse, args )
PARSE		*parse;
LOL		*args;
{
	RULE	*rule = bindrule( parse->string );
	LOL	nargs[1];
	PARSE	*p;

	/* Build up the list of arg lists */

	lol_init( nargs );

	for( p = parse->left; p; p = p->left )
	    lol_add( nargs, var_list( p->llist, args ) );

	if( DEBUG_COMPILE )
	{
	    debug_compile( 1, parse->string );
	    lol_print( nargs );
	    printf( "\n" );
	}

	/* Check traditional targets $(<) and sources $(>) */

	if( !lol_get( nargs, 0 ) )
	    printf( "warning: no targets on rule %s %s\n",
		    rule->name, parse->llist ? parse->llist->string : "" );

	if( !rule->actions && !rule->procedure )
	    printf( "warning: unknown rule %s\n", rule->name );

	/* If this rule will be executed for updating the targets */
	/* then construct the action for make(). */

	if( rule->actions )
	{
	    TARGETS	*t;
	    ACTION	*action;

	    /* The action is associated with this instance of this rule */

	    action = (ACTION *)malloc( sizeof( ACTION ) );
	    memset( (char *)action, '\0', sizeof( *action ) );

	    action->rule = rule;
	    action->targets = targetlist( (TARGETS *)0, lol_get( nargs, 0 ) );
	    action->sources = targetlist( (TARGETS *)0, lol_get( nargs, 1 ) );

	    /* Append this action to the actions of each target */

	    for( t = action->targets; t; t = t->next )
		t->target->actions = actionlist( t->target->actions, action );
	}

	/* Now recursively compile any parse tree associated with this rule */

	if( rule->procedure )
	    (*rule->procedure->func)( rule->procedure, nargs );

	lol_free( nargs );

	if( DEBUG_COMPILE )
	    debug_compile( -1, 0 );
}

/*
 * compile_rules() - compile a chain of rules
 *
 *	parse->left	more compile_rules() by left-recursion
 *	parse->right	single rule
 */

void
compile_rules( parse, args )
PARSE		*parse;
LOL		*args;
{
	(*parse->left->func)( parse->left, args );
	(*parse->right->func)( parse->right, args );
}

/*
 * compile_set() - compile the "set variable" statement
 *
 *	parse->llist	variable names
 *	parse->rlist	variable values
 *	parse->num	ASSIGN_SET/APPEND/DEFAULT
 */

void
compile_set( parse, args )
PARSE		*parse;
LOL		*args;
{
	LIST	*nt = var_list( parse->llist, args );
	LIST	*ns = var_list( parse->rlist, args );
	LIST	*l;
	int	setflag;
	char	*trace;

	switch( parse->num )
	{
	case ASSIGN_SET:	setflag = VAR_SET; trace = "="; break;
	case ASSIGN_APPEND:	setflag = VAR_APPEND; trace = "+="; break;
	case ASSIGN_DEFAULT:	setflag = VAR_DEFAULT; trace = "?="; break;
	default:		setflag = VAR_SET; trace = ""; break;
	}

	if( DEBUG_COMPILE )
	{
	    debug_compile( 0, "set" );
	    list_print( nt );
	    printf( " %s ", trace );
	    list_print( ns );
	    printf( "\n" );
	}

	/* Call var_set to set variable */
	/* var_set keeps ns, so need to copy it */
	/* avoid copy if just setting one variable. */

	for( l = nt; l; l = list_next( l ) )
	    var_set( l->string, 
		list_next( l ) ? list_copy( (LIST*)0, ns ) : ns,
		setflag );

	if( !nt )
	    list_free( ns );

	list_free( nt );
}

/*
 * compile_setcomp() - support for `rule` - save parse tree 
 *
 *	parse->string	rule name
 *	parse->left	rules for rule
 */

void
compile_setcomp( parse, args )
PARSE		*parse;
LOL		*args;
{
	RULE	*rule = bindrule( parse->string );

	/* Free old one, if present */

	if( rule->procedure )
	    parse_free( rule->procedure );

	rule->procedure = parse->left;

	/* we now own this parse tree */
	/* don't let parse_free() release it */

	parse->left = 0;	
}

/*
 * compile_setexec() - support for `actions` - save execution string 
 *
 *	parse->string	rule name
 *	parse->string1	OS command string
 *	parse->num	flags
 *	parse->llist	`bind` variables
 *
 * Note that the parse flags (as defined in compile.h) are transfered
 * directly to the rule flags (as defined in rules.h).
 */

void
compile_setexec( parse, args )
PARSE		*parse;
LOL		*args;
{
	RULE	*rule = bindrule( parse->string );
	
	/* Free old one, if present */

	if( rule->actions )
	{
	    freestr( rule->actions );
	    list_free( rule->bindlist );
	}

	rule->actions = copystr( parse->string1 );
	rule->bindlist = list_copy( L0, parse->llist );
	rule->flags |= parse->num; /* XXX translate this properly */
}

/*
 * compile_settings() - compile the "on =" (set variable on exec) statement
 *
 *	parse->llist		target names
 *	parse->left->llist	variable names
 *	parse->left->rlist	variable values
 *	parse->num		ASSIGN_SET/APPEND	
 */

void
compile_settings( parse, args )
PARSE		*parse;
LOL		*args;
{
	LIST	*nt = var_list( parse->left->llist, args );
	LIST	*ns = var_list( parse->left->rlist, args );
	LIST	*targets, *ts;
	int	append = parse->num == ASSIGN_APPEND;

	/* Reset targets */

	targets = var_list( parse->llist, args );

	if( DEBUG_COMPILE )
	{
	    debug_compile( 0, "set" );
	    list_print( nt );
	    printf( "on " );
	    list_print( targets );
	    printf( " %s ", append ? "+=" : "=" );
	    list_print( ns );
	    printf( "\n" );
	}

	/* Call addsettings to save variable setting */
	/* addsettings keeps ns, so need to copy it */
	/* Pass append flag to addsettings() */

	for( ts = targets; ts; ts = list_next( ts ) )
	{
	    TARGET 	*t = bindtarget( ts->string );
	    LIST	*l;

	    for( l = nt; l; l = list_next( l ) )
		t->settings = addsettings( t->settings, append, 
				l->string, list_copy( (LIST*)0, ns ) );
	}

	list_free( ns );
	list_free( nt );
	list_free( targets );
}

/*
 * compile_switch() - compile 'switch' rule
 *
 *	parse->llist	switch value (only 1st used)
 *	parse->left	cases
 *
 *	cases->left	1st case
 *	cases->right	next cases
 *
 *	case->string	argument to match
 *	case->left	parse tree to execute
 */

void
compile_switch( parse, args )
PARSE		*parse;
LOL		*args;
{
	LIST	*nt;

	nt = var_list( parse->llist, args );

	if( DEBUG_COMPILE )
	{
	    debug_compile( 0, "switch" );
	    list_print( nt );
	    printf( "\n" );
	}

	/* Step through cases */

	for( parse = parse->left; parse; parse = parse->right )
	{
	    if( !glob( parse->left->string, nt ? nt->string : "" ) )
	    {
		/* Get & exec parse tree for this case */
		parse = parse->left->left;
		(*parse->func)( parse, args );
		break;
	    }
	}

	list_free( nt );
}



/*
 * builtin_depends() - DEPENDS/INCLUDES rule
 *
 * The DEPENDS builtin rule appends each of the listed sources on the 
 * dependency list of each of the listed targets.  It binds both the 
 * targets and sources as TARGETs.
 */

static void
builtin_depends( parse, args )
PARSE		*parse;
LOL		*args;
{
	LIST *targets = lol_get( args, 0 );
	LIST *sources = lol_get( args, 1 );
	int which = parse->num;
	LIST *l;

	for( l = targets; l; l = list_next( l ) )
	{
	    TARGET *t = bindtarget( l->string );
	    t->deps[ which ] = targetlist( t->deps[ which ], sources );
	}
}

/*
 * builtin_echo() - ECHO rule
 *
 * The ECHO builtin rule echoes the targets to the user.  No other 
 * actions are taken.
 */

static void
builtin_echo( parse, args )
PARSE		*parse;
LOL		*args;
{
	list_print( lol_get( args, 0 ) );
	printf( "\n" );
}

/*
 * builtin_exit() - EXIT rule
 *
 * The EXIT builtin rule echoes the targets to the user and exits
 * the program with a failure status.
 */

static void
builtin_exit( parse, args )
PARSE		*parse;
LOL		*args;
{
	list_print( lol_get( args, 0 ) );
	printf( "\n" );
	exit( EXITBAD ); /* yeech */
}

/*
 * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
 *
 * Builtin_flags() marks the target with the appropriate flag, for use
 * by make0().  It binds each target as a TARGET.
 */

static void
builtin_flags( parse, args )
PARSE		*parse;
LOL		*args;
{
	LIST *l = lol_get( args, 0 );

	for( ; l; l = list_next( l ) )
	    bindtarget( l->string )->flags |= parse->num;
}

/*
 * debug_compile() - printf with indent to show rule expansion.
 */

static void
debug_compile( which, s )
int which;
char *s;
{
	static int level = 0;
	static char indent[36] = ">>>>|>>>>|>>>>|>>>>|>>>>|>>>>|>>>>|";
	int i = ((1+level) * 2) % 35;

	if( which >= 0 )
	    printf( "%*.*s ", i, i, indent );

	if( s )
	    printf( "%s ", s );

	level += which;
}
# Change User Description Committed
#5 1270 Matt Armstrong In prep for publishing all my patches to jam.
#4 1089 Matt Armstrong One more integration.
#3 1088 Matt Armstrong Merge in changes from upstream.
#2 293 Matt Armstrong Fix a crash bug in jam by adding reference counting to the
PARSE struct.  The bug occurs when a rule re-defines
itself.  This can only happen by using the include statement,
as follows.

JamfileA:
----------------------------------------------------
rule DoInclude {
    ECHO JamfileA DoInclude $(<) start ;
    include $(<) ;
    ECHO JamfileA DoInclude $(<) end ;
}
DoInclude JambileB ;
----------------------------------------------------

JamfileB:
----------------------------------------------------
rule DoInclude {
    ECHO JamfileB DoInclude ;
}
DoInclude JambileC ;
----------------------------------------------------

With these two jamfiles you can reproduce the crash bug
by running "jam -fJamfileA".  If that doesn't work,
change parse_free() such that it memsets PARSE struct being
freed to something bogus like 0x03 and try again (or use
Purify and watch the free memory read warnings).

The problem is that compile_setcomp() just frees the PARSE
without regard for the possibility that it might be executing.
In the case above, JamfileA's DoInclude is re-defined by
JamfileB while it is still being executed.

The simple fix is to just have compile_setcomp() not free the
PARSE.  This would introduce a memory leak for every re-defined
rule.

My fix introduces reference counting of the PARSE struct.  A
PARSE is allocated with a reference count of 1 and when it
goes to 0 it is freed.  Every place that was a parse_free() is
now a parse_decref().  In addition, compile_rules() increments
the reference count of its PARSE around the calls to its left
and right functions.

Perhaps there a better fix!
#1 289 Matt Armstrong Initial branch.
//guest/perforce_software/jam/src/compile.c
#1 2 laura Add Jam/MR 2.2 source