Skip to content

Commit c80eb45

Browse files
committed
Add exception::traceAsString() and exception::toString()
1 parent b9c71c7 commit c80eb45

File tree

2 files changed

+394
-8
lines changed

2 files changed

+394
-8
lines changed

Zend/zend_default_classes.c

Lines changed: 197 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,17 +123,209 @@ ZEND_METHOD(exception, gettrace)
123123
_default_exception_get_entry(getThis(), "trace", sizeof("trace")-1, return_value TSRMLS_CC);
124124
}
125125

126+
/* {{{ ZEND_METHOD(exception, gettraceasstring) */
127+
#define TRACE_APPEND_CHR(chr) \
128+
*str = (char*)erealloc(*str, *len + 1 + 1); \
129+
(*str)[(*len)++] = chr
130+
131+
#define TRACE_APPEND_STRL(val, vallen) \
132+
{ \
133+
int l = vallen; \
134+
*str = (char*)erealloc(*str, *len + l + 1); \
135+
memcpy((*str) + *len, val, l); \
136+
*len += l; \
137+
}
138+
139+
#define TRACE_APPEND_STR(val) \
140+
TRACE_APPEND_STRL(val, sizeof(val)-1)
141+
142+
#define TRACE_APPEND_KEY(key) \
143+
if (zend_hash_find(ht, key, sizeof(key), (void**)&tmp) == SUCCESS) { \
144+
TRACE_APPEND_STRL(Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp)); \
145+
}
146+
147+
static int _build_trace_args(zval **arg, int num_args, va_list args, zend_hash_key *hash_key)
148+
{
149+
char **str;
150+
int *len;
151+
152+
str = va_arg(args, char**);
153+
len = va_arg(args, int*);
154+
155+
/* the trivial way would be to do:
156+
* conver_to_string_ex(arg);
157+
* append it and kill the now tmp arg.
158+
* but that could cause some E_NOTICE and also damn long lines.
159+
*/
160+
161+
switch (Z_TYPE_PP(arg)) {
162+
case IS_NULL:
163+
TRACE_APPEND_STR("NULL, ");
164+
break;
165+
case IS_STRING:
166+
TRACE_APPEND_CHR('\'');
167+
if (Z_STRLEN_PP(arg) > 15) {
168+
TRACE_APPEND_STRL(Z_STRVAL_PP(arg), 15);
169+
TRACE_APPEND_STR("...', ");
170+
} else {
171+
TRACE_APPEND_STRL(Z_STRVAL_PP(arg), Z_STRLEN_PP(arg));
172+
TRACE_APPEND_STR("', ");
173+
}
174+
break;
175+
case IS_BOOL:
176+
if (Z_LVAL_PP(arg)) {
177+
TRACE_APPEND_STR("true, ");
178+
} else {
179+
TRACE_APPEND_STR("false, ");
180+
}
181+
break;
182+
case IS_RESOURCE:
183+
TRACE_APPEND_STR("Resource id #");
184+
/* break; */
185+
case IS_LONG: {
186+
long lval = Z_LVAL_PP(arg);
187+
char s_tmp[MAX_LENGTH_OF_LONG + 1];
188+
int l_tmp = zend_sprintf(s_tmp, "%ld", lval); /* SAFE */
189+
TRACE_APPEND_STRL(s_tmp, l_tmp);
190+
TRACE_APPEND_STR(", ");
191+
break;
192+
}
193+
case IS_DOUBLE: {
194+
double dval = Z_DVAL_PP(arg);
195+
char *s_tmp;
196+
int l_tmp;
197+
TSRMLS_FETCH();
198+
199+
s_tmp = emalloc(MAX_LENGTH_OF_DOUBLE + EG(precision) + 1);
200+
l_tmp = zend_sprintf(s_tmp, "%.*G", (int) EG(precision), dval); /* SAFE */
201+
TRACE_APPEND_STRL(s_tmp, l_tmp);
202+
/* %G already handles removing trailing zeros from the fractional part, yay */
203+
efree(s_tmp);
204+
TRACE_APPEND_STR(", ");
205+
break;
206+
}
207+
case IS_ARRAY:
208+
TRACE_APPEND_STR("Array, ");
209+
break;
210+
case IS_OBJECT: {
211+
TSRMLS_FETCH();
212+
213+
TRACE_APPEND_STR("Object(");
214+
TRACE_APPEND_STRL(Z_OBJCE_PP(arg)->name, strlen(Z_OBJCE_PP(arg)->name));
215+
TRACE_APPEND_STR("), ");
216+
break;
217+
}
218+
default:
219+
break;
220+
}
221+
return ZEND_HASH_APPLY_KEEP;
222+
}
223+
224+
static int _build_trace_string(zval **frame, int num_args, va_list args, zend_hash_key *hash_key)
225+
{
226+
char *s_tmp, **str;
227+
int *len;
228+
long line;
229+
HashTable *ht = Z_ARRVAL_PP(frame);
230+
zval **file, **tmp;
231+
232+
str = va_arg(args, char**);
233+
len = va_arg(args, int*);
234+
235+
if (zend_hash_find(ht, "file", sizeof("file"), (void**)&file) == SUCCESS) {
236+
if (zend_hash_find(ht, "line", sizeof("line"), (void**)&tmp) == SUCCESS) {
237+
line = Z_LVAL_PP(tmp);
238+
} else {
239+
line = 0;
240+
}
241+
s_tmp = emalloc(Z_STRLEN_PP(file) + MAX_LENGTH_OF_LONG + 2 + 1);
242+
sprintf(s_tmp, "%s(%ld): ", Z_STRVAL_PP(file), line);
243+
TRACE_APPEND_STRL(s_tmp, strlen(s_tmp));
244+
efree(s_tmp);
245+
} else {
246+
TRACE_APPEND_STR("[internal function]: ");
247+
}
248+
TRACE_APPEND_KEY("class");
249+
TRACE_APPEND_KEY("type");
250+
TRACE_APPEND_KEY("function");
251+
TRACE_APPEND_CHR('(');
252+
if (zend_hash_find(ht, "args", sizeof("args"), (void**)&tmp) == SUCCESS) {
253+
zend_hash_apply_with_arguments(Z_ARRVAL_PP(tmp), (apply_func_args_t)_build_trace_args, 2, str, len);
254+
*len -= 2; /* remove last ', ' */
255+
}
256+
TRACE_APPEND_STR(")\n");
257+
return ZEND_HASH_APPLY_KEEP;
258+
}
259+
260+
ZEND_METHOD(exception, gettraceasstring)
261+
{
262+
zval *trace;
263+
char *str = estrdup("");
264+
int len = 0;
265+
266+
trace = zend_read_property(Z_OBJCE_P(getThis()), getThis(), "trace", sizeof("trace")-1, 1 TSRMLS_CC);
267+
zend_hash_apply_with_arguments(Z_ARRVAL_P(trace), (apply_func_args_t)_build_trace_string, 2, &str, &len);
268+
269+
str[len] = '\0';
270+
RETURN_STRINGL(str, len, 0);
271+
}
272+
/* }}} */
273+
274+
ZEND_METHOD(exception, tostring)
275+
{
276+
zval *message, *file, *line, *trace;
277+
char *str;
278+
int len;
279+
zend_fcall_info fci;
280+
zval fname;
281+
282+
message = zend_read_property(Z_OBJCE_P(getThis()), getThis(), "message", sizeof("message")-1, 1 TSRMLS_CC);
283+
file = zend_read_property(Z_OBJCE_P(getThis()), getThis(), "file", sizeof("file")-1, 1 TSRMLS_CC);
284+
line = zend_read_property(Z_OBJCE_P(getThis()), getThis(), "line", sizeof("line")-1, 1 TSRMLS_CC);
285+
286+
ZVAL_STRINGL(&fname, "gettraceasstring", sizeof("gettraceasstring")-1, 0);
287+
288+
fci.size = sizeof(fci);
289+
fci.function_table = &Z_OBJCE_P(getThis())->function_table;
290+
fci.function_name = &fname;
291+
fci.symbol_table = NULL;
292+
fci.object_pp = &getThis();
293+
fci.retval_ptr_ptr = &trace;
294+
fci.param_count = 0;
295+
fci.params = NULL;
296+
fci.no_separation = 1;
297+
298+
zend_call_function(&fci, NULL TSRMLS_CC);
299+
300+
len = 11 + strlen(Z_OBJCE_P(getThis())->name) + 16 + Z_STRLEN_P(message) + 5 + Z_STRLEN_P(file) + 1 + MAX_LENGTH_OF_LONG + 14 + Z_STRLEN_P(trace) + 1;
301+
str = emalloc(len);
302+
sprintf(str, "exception '%s' with message '%s' in %s:%ld\nStack trace:\n%s",
303+
Z_OBJCE_P(getThis())->name, Z_STRVAL_P(message), Z_STRVAL_P(file), Z_LVAL_P(line),
304+
Z_STRVAL_P(trace));
305+
len = strlen(str);
306+
307+
/* We store the result in the private property string so we can access
308+
* the result in uncaught exception handlers without memleaks. */
309+
zend_update_property_string(default_exception_ptr, getThis(), "string", sizeof("string")-1, str TSRMLS_CC);
310+
311+
zval_ptr_dtor(&trace);
312+
313+
RETURN_STRINGL(str, len, 0);
314+
}
315+
126316
/* All functions that may be used in uncaught exception handlers must be final
127317
* and must not throw exceptions. Otherwise we would need a facility to handle
128318
* such exceptions in that handler.
129319
*/
130320
static zend_function_entry default_exception_functions[] = {
131321
ZEND_ME(exception, __construct, NULL, 0)
132-
ZEND_ME(exception, getmessage, NULL, 0) /* non final for now */
322+
ZEND_ME(exception, getmessage, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
133323
ZEND_ME(exception, getcode, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
134324
ZEND_ME(exception, getfile, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
135325
ZEND_ME(exception, getline, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
136326
ZEND_ME(exception, gettrace, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
327+
ZEND_ME(exception, gettraceasstring, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
328+
ZEND_ME(exception, tostring, NULL, 0)
137329
{NULL, NULL, NULL}
138330
};
139331

@@ -146,6 +338,7 @@ static void zend_register_default_exception(TSRMLS_D)
146338
default_exception_ptr->create_object = zend_default_exception_new;
147339

148340
zend_declare_property_string(default_exception_ptr, "message", sizeof("message")-1, "Unknown exception", ZEND_ACC_PROTECTED);
341+
zend_declare_property_string(default_exception_ptr, "string", sizeof("string")-1, "", ZEND_ACC_PRIVATE);
149342
zend_declare_property_long(default_exception_ptr, "code", sizeof("code")-1, 0, ZEND_ACC_PROTECTED);
150343
zend_declare_property_null(default_exception_ptr, "file", sizeof("file")-1, ZEND_ACC_PROTECTED);
151344
zend_declare_property_null(default_exception_ptr, "line", sizeof("line")-1, ZEND_ACC_PROTECTED);
@@ -252,10 +445,10 @@ ZEND_API void zend_exception_error(zval *exception TSRMLS_DC)
252445
if (instanceof_function(Z_OBJCE_P(exception), default_exception_ptr TSRMLS_CC)) {
253446
zval *message = zend_read_property(default_exception_ptr, exception, "message", sizeof("message")-1, 1 TSRMLS_CC);
254447
zval *file = zend_read_property(default_exception_ptr, exception, "file", sizeof("file")-1, 1 TSRMLS_CC);
255-
zval *lineno = zend_read_property(default_exception_ptr, exception, "line", sizeof("line")-1, 1 TSRMLS_CC);
256-
zend_error_va(E_ERROR, Z_STRVAL_P(file), Z_LVAL_P(lineno), "Uncaught exception '%s' with message '%s'", Z_OBJCE_P(exception)->name, Z_STRVAL_P(message));
448+
zval *line = zend_read_property(default_exception_ptr, exception, "line", sizeof("line")-1, 1 TSRMLS_CC);
449+
zend_error_va(E_ERROR, Z_STRVAL_P(file), Z_LVAL_P(line), "Uncaught exception '%s' with message '%s'", Z_OBJCE_P(exception)->name, Z_STRVAL_P(message));
257450
} else {
258-
zend_error(E_ERROR, "Uncaught exception '%s'!", Z_OBJCE_P(exception)->name);
451+
zend_error(E_ERROR, "Uncaught exception '%s'", Z_OBJCE_P(exception)->name);
259452
}
260453
}
261454

0 commit comments

Comments
 (0)