EH clobbered by longjmp again

Jim Wilson wilson@chestnut.cygnus.com
Fri Sep 5 19:53:00 GMT 1997


	It seems to me that flow has not been taught that the builtin
	setjmp uses the non-local goto support, saving everything to
	the stack at appropriate places, and therefore cannot have 
	things clobbered out from underneath it.

It is true that there is missing info in flow.  Flow should know that it
is possible for control to go from calls to the longjmp/catch labels.
This missing info means that the flow graph is incorrect, and this can
cause incorrect code or a compiler crash.

I am not sure what you mean by the non-local support can't have things
clobbered out from underneath it.  It doesn't save/restore any more state
than setjmp does, so random allocatable registers can still be clobbered.
However, if we contruct the flow graph correctly, than we can be sure that
local variables won't be allocated to any register where is might be clobbered,
and there is code in the non-local goto support to do this.

For setjmp/longjmp, we don't construct a correct flow graph, but rather we
note those variables live at the time of the setjmp, and ensure that they
get allocated to the stack.  The builtin setjmp/longjmp code is trying to
use this same scheme, but it won't work.  We should construct a correct
flow graph, as is done for the non-local goto support, and once we have the
correct flow graph then the builtin setjmp support won't need the setjmp
hacks which are enabled by the NOTE_INSN_SETJMP note.  It might be possible
to completely get rid of this note in the builtin setjmp case.  That would
solve a lot of problems.

	It also does as Jim suggested this evening (and I've thought ought
	to be done for a while), which is to get rid of that stupid __dummy()
	call.  I fixed things up for Alpha, but I don't know how to make the
	MIPS assembler do the right thing.

We probably also want to get rid of the definition for _dummy which is in
libgcc2.c.

One problem with getting rid of the call is that we don't know what targets
will be broken by this.  We know that alpha and mips (irix5) need changes
to make this work.  Jeff Law suggested that the PA port would need similar
changes.  There might be others.

There is no easy way to get this value reloaded on the mips.  It is at
a variable offset from the FP/SP.  You would need to create an fake hard
register holding the address, which then got eliminated at reload time to
be fp/sp+offset, much like the existing return address pointer ($rap) stuff
works.

	(Back to the warning problem for a moment, note that it is reg 68 in
	block 7 that flow is complaining about, but that it knows that control
	cannot go from the catches back into 7, 8 or 9.)

It doesn't matter where control goes after the catch.

Lets start with setjmp/longjmp.  The ISO C standard says that if you
have a automatic variable which is set before the setjmp, is modified between
the setjmp and longjmp call, and then used after the longjmp, its value
is indeterminate after the longjmp call.  Gcc gives a warning for this case to
alert the programmer.  Here is an example, which gives the warning when
compiled with -O -Wall.

#include <stdio.h>
#include <setjmp.h>

int g = 1;

int
main()
{
  jmp_buf tmp;
  int i = 10;
  int j;

  if (! (j = setjmp (tmp)))
    {
      if (g)
	i = 11;
      else
	i = 12;
      longjmp (tmp, i);
    }

  printf ("i = %d\n", i);
  printf ("j = %d\n", j);
  return 0;
}

This is roughly equivalent to the following C++ example (I hope):

#include <stdio.h>

int g = 1;

int
main()
{
  int i = 10;
  try
    {
      if (g)
	i = 11;
      else
	i = 12;
      throw i;
    }
  catch (int j)
    {
      printf ("i = %d\n", i);
      printf ("j = %d\n", j);
      exit (0);
    }
  return 0;
}

If we use exact ISO C setjmp/longjmp semantics for the try/throw, then
the variable `i' as used in the catch clause has an indeterminate value, for
exactly the same reasons as it does in the above example.  This is why gcc
gives a warning for it.

What does C++ say about this case?  Does `i' have a well defined value in
the catch clause, and if so, which value is it?  I need to know the answer
to this question in order to decide how to fix the problem.

But just ignoring that, there is a serious problem here.  The code that
emits the warning is over simplistic.  It does not verify all of the
conditions that are necessary to get indeterminate results.  As a result
it gives warnings in many cases where there is obviously no problem.
For instance, it will give a warning for this example:

#include <stdio.h>
#include <setjmp.h>

int g = 1;

int
main()
{
  jmp_buf tmp;
  int i = 10;
  int j;

  if (! (j = setjmp (tmp)))
    {
      printf ("i = %d\n", i);
      if (g)
	i = 11;
      else
	i = 12;
      longjmp (tmp, i);
    }

  printf ("j = %d\n", j);
  return 0;
}

The over simplistic checks used seems to be why the message contains the
word `might', since it will occasionally give false warnings.  While this
was good enough for setjmp, which is rarely used, this is not OK for
try/catch, because they can be used frequently.  This difference in
frequency of use means that the false warnings are a serious annoyance
for C++ users.

If values can never be clobbered by a try/throw, then we could perhaps just
disable this warning when builtin setjmp/longjmp are involved, and just
make sure that gcc always emits correct code.  It should already be doing
the right thing if local variables should have the same value they had
just before the throw.  One way to disable this warning is to get rid
of the NOTE_INSN_SETJMP note as I mentioned at the top.

If there are cases where this warning is still useful for C++, then we
can modify the warning to make more sense (e.g. variable clobbered by try/throw
instead of saying setjmp), and then tighten the conditions checked by the
warning code so that we don't get any of the false warnings.

Jim



More information about the Gcc mailing list