setjmp + goto = error handling bliss

Oh yes, it’s a post about setjmp. Loved by few, feared by most — setjmp/longjmp is still the best way to write maintainable error handling code in some programs!

Consider a line-based assembler or compiler where we want to deal with each line as it comes in from a file. We don’t want to stop compiling on the first line with an error, but flag errors and keep going. As soon as we find an error in a line, we want to abort processing that line and go on with the next one.

The traditional way to solve this is to put a setjmp handler inside the line loop and be prepared to deal with a line going bad. But calling setjmp is expensive as it moves a lot of data from CPU registers to memory (depending on the architecture of course).

Here’s an interesting way to structure the input loop instead which gives all the benefits of setjmp/longjmp but only incurs a cost when there is an error:

static jmp_buf error_jmp;

static int process_file(FILE* input)
{
	volatile int error_count = 0;
retry:
	if (0 != setjmp(error_jmp)) {
		++error_count;
		goto retry;
	}

	while (NULL != fgets(line_buf, sizeof line_buf, input)) {
		/* process line, calling arbitrary functions */
	}

	return error_count;
}

Here’s how it works. We re-establish the setjmp point at the retry symbol rather than inside the line processing loop where we want to get actual work done. Whenever there is an error, the error handling function calls longjmp and we end up back in the if statement following the setjmp, which bumps the error count and resumes by resetting the jump buffer and then running the line loop again!

To put it all together, here’s how you could design an error reporting function that terminates the processing of a line and resumes with the next one:

static void parse_error(const char *fmt, ...)
{
	char buf[1024];
	va_list args;

	va_start(args, fmt);
	vsnprintf(buf, sizeof buf, fmt, args);
	buf[(sizeof buf)-1] = 0;
	fprintf(stderr, "error: %s\n", buf);
	va_end(args);

	longjmp(error_jmp, 1);
}

You can then write pretty complex handlers in vanilla C without worrying about how to get out of a tight spot when you encounter a parse error:

	if (!foo_bar(data))
		parse_error("expected foo bar to be true");

If you don’t like the static jmp_buf, that can of course be solved too by passing around more state in your processing code. If you do that, you can keep it on the stack inside the outer function which makes the code thread-safe as well.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s