1 /*
2  * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301, USA.
18  *
19  * You can also choose to distribute this program under the terms of
20  * the Unmodified Binary Distribution Licence (as given in the file
21  * COPYING.UBDL), provided that you have satisfied its requirements.
22  */
23 
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25 
26 /** @file
27  *
28  * setjmp()/longjmp() tests
29  *
30  */
31 
32 /* Forcibly enable assertions */
33 #undef NDEBUG
34 
35 #include <stddef.h>
36 #include <assert.h>
37 #include <setjmp.h>
38 #include <ipxe/test.h>
39 
40 /** A setjmp()/longjmp() test */
41 struct setjmp_test {
42 	/** Jump buffer */
43 	jmp_buf env;
44 	/** Expected value */
45 	int expected;
46 	/** Test code file */
47 	const char *file;
48 	/** Test code line */
49 	unsigned int line;
50 };
51 
52 /** Expected jump */
53 static struct setjmp_test *jumped;
54 
55 /**
56  * Report a setjmp() test result
57  *
58  * @v test		setjmp()/longjmp() test
59  *
60  * This has to be implemented as a macro since if it were a function
61  * then the context saved by setjmp() would be invalidated when the
62  * function returned.
63  */
64 #define setjmp_ok( test ) do {						\
65 	int value;							\
66 	/* Sanity check */						\
67 	assert ( jumped == NULL );					\
68 	/* Initialise test */						\
69 	(test)->expected = 0;						\
70 	(test)->file = __FILE__;					\
71 	(test)->line = __LINE__;					\
72 	/* Perform setjmp() */						\
73 	value = setjmp ( (test)->env );					\
74 	/* Report setjmp()/longjmp() result */				\
75 	setjmp_return_ok ( (test), value );				\
76 	} while ( 0 )
77 
78 /**
79  * Report a setjmp()/longjmp() test result
80  *
81  * @v test		setjmp()/longjmp() test
82  * @v value		Value returned from setjmp()
83  *
84  * This function ends up reporting results from either setjmp() or
85  * longjmp() tests (since calls to longjmp() will return via the
86  * corresponding setjmp()).  It therefore uses the test code file and
87  * line stored in the test structure, which will represent the line
88  * from which either setjmp() or longjmp() was called.
89  */
setjmp_return_ok(struct setjmp_test * test,int value)90 static void setjmp_return_ok ( struct setjmp_test *test, int value ) {
91 
92 	/* Determine whether this was reached via setjmp() or longjmp() */
93 	if ( value == 0 ) {
94 		/* This is the initial call to setjmp() */
95 		okx ( test->expected == 0, test->file, test->line );
96 		okx ( jumped == NULL, test->file, test->line );
97 	} else {
98 		/* This is reached via a call to longjmp() */
99 		okx ( value == test->expected, test->file, test->line );
100 		okx ( jumped == test, test->file, test->line );
101 	}
102 
103 	/* Clear expected jump */
104 	jumped = NULL;
105 }
106 
107 /**
108  * Report a longjmp() test result
109  *
110  * @v test		setjmp()/longjmp() test
111  * @v file		Test code file
112  * @v line		Test code line
113  */
114 static void __attribute__ (( noreturn ))
longjmp_okx(struct setjmp_test * test,int value,const char * file,unsigned int line)115 longjmp_okx ( struct setjmp_test *test, int value,
116 	      const char *file, unsigned int line ) {
117 
118 	/* Record expected value.  A zero passed to longjmp() should
119 	 * result in setjmp() returning a value of one.
120 	 */
121 	test->expected = ( value ? value : 1 );
122 
123 	/* Record test code file and line */
124 	test->file = file;
125 	test->line = line;
126 
127 	/* Record expected jump */
128 	jumped = test;
129 
130 	/* Perform longjmp().  Should return via setjmp_okx() */
131 	longjmp ( test->env, value );
132 
133 	/* longjmp() should never return */
134 	assert ( 0 );
135 }
136 #define longjmp_ok( test, value ) \
137 	longjmp_okx ( test, value, __FILE__, __LINE__ )
138 
139 /**
140  * Perform setjmp()/longjmp() self-tests
141  *
142  */
setjmp_test_exec(void)143 static void setjmp_test_exec ( void ) {
144 	static struct setjmp_test alpha;
145 	static struct setjmp_test beta;
146 	static int iteration;
147 
148 	/* This is one of the very few situations in which the
149 	 * "for-case" pattern is justified.
150 	 */
151 	for ( iteration = 0 ; iteration < 10 ; iteration++ ) {
152 		DBGC ( jumped, "SETJMP test iteration %d\n", iteration );
153 		switch ( iteration ) {
154 		case 0: setjmp_ok ( &alpha ); break;
155 		case 1: setjmp_ok ( &beta ); break;
156 		case 2:	longjmp_ok ( &alpha, 0 );
157 		case 3: longjmp_ok ( &alpha, 1 );
158 		case 4: longjmp_ok ( &alpha, 2 );
159 		case 5: longjmp_ok ( &beta, 17 );
160 		case 6: longjmp_ok ( &beta, 29 );
161 		case 7: longjmp_ok ( &alpha, -1 );
162 		case 8: longjmp_ok ( &beta, 0 );
163 		case 9: longjmp_ok ( &beta, 42 );
164 		}
165 	}
166 }
167 
168 /** setjmp()/longjmp() self-test */
169 struct self_test setjmp_test __self_test = {
170 	.name = "setjmp",
171 	.exec = setjmp_test_exec,
172 };
173