[gnu.utils.bug] Bug in GNU Make - Improper handling of nested variable references

marc@sun-valley.mit.edu (Marc Ullman) (07/06/89)

			B U G    R E P O R T
			
	Program: GNU Make
	Version: 3.48
	Computer: Sun 3
	Operating System: SunOS 4.0.1
	Reported By: Marc Ullman (marc@sun-valley.stanford.edu)
	Date: July 5, 1989

gnumake has a bug with nested variable references when text strings contain
parentheses "(",")" and/or braces "{", "}" as the following example makefile
shows:

	SOURCES = src1.c src2.c src3.c
	LIB_NAME = testlib.a
	LIB_OBJS = ${SOURCES:%.c=$(LIB_NAME)(%.o)}

	all :
		@echo "$(LIB_OBJS)"
		
running gnumake on this file produces the following result:

        testlib.a(src1.o testlib.a(src2.o testlib.a(src3.o)
                        ^                ^
                        |                |
                        Missing parentheses

The bug results from an incorrect assumption in the function variable_expand()
in the file variable.c.  A fragment of this function is shown below:


	    /* Is there a variable reference inside the parens or braces?
	       If so, expand it before expanding the entire reference.  */

	    p1 = index (beg, closeparen);
	    if (p1 != 0)
	      p1 = lindex (beg, p1, '$');
	    if (p1 != 0)
	      {
		/* BEG now points past the opening paren or brace.
		   Count parens or braces until it is matched.  */
		int count = 0;
		for (p = beg; *p != '\0'; ++p)
		  {
		    if (*p == openparen)
		      ++count;
		    else if (*p == closeparen && --count < 0)
		      break;
		  }
		/* If count is >= 0, there were unmatched opening parens
		   or braces, so we go to the simple case of a variable name
		   such as `$($(a)'.  */
		if (count < 0)
		  {
		    char *name = expand_argument (beg, p);
		    p1 = concat ("$(", name, ")");
		    free (name);
		    name = expand_argument (p1, p1 + strlen (p1));
		    o = variable_buffer_output (o, name, strlen (name));
		    free (name);
		    break;
		  }
	      }
	      
The line 
	p1 = concat ("$(", name, ")");
	
blindly restores parentheses around the previously evaluated expression
rather than restoring the appropriate delimiters depending on the current
state of the variables "openparen" and "closeparen".  A possible fix is shown below:

	if (count < 0)
	  {
	    char var_start_str[3] = {'$', openparen, '\0' };  /* NEW */
	    char var_end_str[2] = {closeparen, '\0'};	      /* NEW */
	    char *name = expand_argument (beg, p);
	    p1 = concat (var_start_str, name, var_end_str); /* REVISED */
	    free (name);
	    name = expand_argument (p1, p1 + strlen (p1));
	    o = variable_buffer_output (o, name, strlen (name));
	    free (name);
	    break;
	 }

With this revision, the sample makefile shown above yields the desirerd result:

	testlib.a(src1.o) testlib.a(src2.o) testlib.a(src3.o)