|
38 | 38 | #include "py/gc.h"
|
39 | 39 | #include "py/mperrno.h"
|
40 | 40 |
|
| 41 | +// Number of items per traceback entry (file, line, block) |
| 42 | +#define TRACEBACK_ENTRY_LEN (3) |
| 43 | + |
| 44 | +// Number of traceback entries to reserve in the emergency exception buffer |
| 45 | +#define EMG_TRACEBACK_ALLOC (2 * TRACEBACK_ENTRY_LEN) |
| 46 | + |
41 | 47 | // Instance of MemoryError exception - needed by mp_malloc_fail
|
42 | 48 | const mp_obj_exception_t mp_const_MemoryError_obj = {{&mp_type_MemoryError}, 0, 0, NULL, (mp_obj_tuple_t*)&mp_const_empty_tuple_obj};
|
43 | 49 |
|
@@ -127,18 +133,51 @@ STATIC void mp_obj_exception_print(const mp_print_t *print, mp_obj_t o_in, mp_pr
|
127 | 133 |
|
128 | 134 | mp_obj_t mp_obj_exception_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
|
129 | 135 | mp_arg_check_num(n_args, n_kw, 0, MP_OBJ_FUN_ARGS_MAX, false);
|
130 |
| - mp_obj_exception_t *o = m_new_obj_var_maybe(mp_obj_exception_t, mp_obj_t, 0); |
131 |
| - if (o == NULL) { |
132 |
| - // Couldn't allocate heap memory; use local data instead. |
133 |
| - o = &MP_STATE_VM(mp_emergency_exception_obj); |
134 |
| - // We can't store any args. |
135 |
| - o->args = (mp_obj_tuple_t*)&mp_const_empty_tuple_obj; |
| 136 | + |
| 137 | + // Try to allocate memory for the exception, with fallback to emergency exception object |
| 138 | + mp_obj_exception_t *o_exc = m_new_obj_maybe(mp_obj_exception_t); |
| 139 | + if (o_exc == NULL) { |
| 140 | + o_exc = &MP_STATE_VM(mp_emergency_exception_obj); |
| 141 | + } |
| 142 | + |
| 143 | + // Populate the exception object |
| 144 | + o_exc->base.type = type; |
| 145 | + o_exc->traceback_data = NULL; |
| 146 | + |
| 147 | + mp_obj_tuple_t *o_tuple; |
| 148 | + if (n_args == 0) { |
| 149 | + // No args, can use the empty tuple straightaway |
| 150 | + o_tuple = (mp_obj_tuple_t*)&mp_const_empty_tuple_obj; |
136 | 151 | } else {
|
137 |
| - o->args = MP_OBJ_TO_PTR(mp_obj_new_tuple(n_args, args)); |
| 152 | + // Try to allocate memory for the tuple containing the args |
| 153 | + o_tuple = m_new_obj_var_maybe(mp_obj_tuple_t, mp_obj_t, n_args); |
| 154 | + |
| 155 | + #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF |
| 156 | + // If we are called by mp_obj_new_exception_msg_varg then it will have |
| 157 | + // reserved room (after the traceback data) for a tuple with 1 element. |
| 158 | + // Otherwise we are free to use the whole buffer after the traceback data. |
| 159 | + if (o_tuple == NULL && mp_emergency_exception_buf_size >= |
| 160 | + EMG_TRACEBACK_ALLOC * sizeof(size_t) + sizeof(mp_obj_tuple_t) + n_args * sizeof(mp_obj_t)) { |
| 161 | + o_tuple = (mp_obj_tuple_t*) |
| 162 | + ((uint8_t*)MP_STATE_VM(mp_emergency_exception_buf) + EMG_TRACEBACK_ALLOC * sizeof(size_t)); |
| 163 | + } |
| 164 | + #endif |
| 165 | + |
| 166 | + if (o_tuple == NULL) { |
| 167 | + // No memory for a tuple, fallback to an empty tuple |
| 168 | + o_tuple = (mp_obj_tuple_t*)&mp_const_empty_tuple_obj; |
| 169 | + } else { |
| 170 | + // Have memory for a tuple so populate it |
| 171 | + o_tuple->base.type = &mp_type_tuple; |
| 172 | + o_tuple->len = n_args; |
| 173 | + memcpy(o_tuple->items, args, n_args * sizeof(mp_obj_t)); |
| 174 | + } |
138 | 175 | }
|
139 |
| - o->base.type = type; |
140 |
| - o->traceback_data = NULL; |
141 |
| - return MP_OBJ_FROM_PTR(o); |
| 176 | + |
| 177 | + // Store the tuple of args in the exception object |
| 178 | + o_exc->args = o_tuple; |
| 179 | + |
| 180 | + return MP_OBJ_FROM_PTR(o_exc); |
142 | 181 | }
|
143 | 182 |
|
144 | 183 | // Get exception "value" - that is, first argument, or None
|
@@ -306,87 +345,95 @@ mp_obj_t mp_obj_new_exception_msg(const mp_obj_type_t *exc_type, const char *msg
|
306 | 345 | return mp_obj_new_exception_msg_varg(exc_type, msg);
|
307 | 346 | }
|
308 | 347 |
|
309 |
| -mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, const char *fmt, ...) { |
310 |
| - // check that the given type is an exception type |
311 |
| - assert(exc_type->make_new == mp_obj_exception_make_new); |
312 |
| - |
313 |
| - // make exception object |
314 |
| - mp_obj_exception_t *o = m_new_obj_var_maybe(mp_obj_exception_t, mp_obj_t, 0); |
315 |
| - if (o == NULL) { |
316 |
| - // Couldn't allocate heap memory; use local data instead. |
317 |
| - // Unfortunately, we won't be able to format the string... |
318 |
| - o = &MP_STATE_VM(mp_emergency_exception_obj); |
319 |
| - o->base.type = exc_type; |
320 |
| - o->traceback_data = NULL; |
321 |
| - o->args = (mp_obj_tuple_t*)&mp_const_empty_tuple_obj; |
322 |
| - |
323 |
| -#if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF |
324 |
| - // If the user has provided a buffer, then we try to create a tuple |
325 |
| - // of length 1, which has a string object and the string data. |
| 348 | +// The following struct and function implement a simple printer that conservatively |
| 349 | +// allocates memory and truncates the output data if no more memory can be obtained. |
| 350 | +// It leaves room for a null byte at the end of the buffer. |
326 | 351 |
|
327 |
| - if (mp_emergency_exception_buf_size > (sizeof(mp_obj_tuple_t) + sizeof(mp_obj_str_t) + sizeof(mp_obj_t))) { |
328 |
| - mp_obj_tuple_t *tuple = (mp_obj_tuple_t *)MP_STATE_VM(mp_emergency_exception_buf); |
329 |
| - mp_obj_str_t *str = (mp_obj_str_t *)&tuple->items[1]; |
330 |
| - |
331 |
| - tuple->base.type = &mp_type_tuple; |
332 |
| - tuple->len = 1; |
333 |
| - tuple->items[0] = MP_OBJ_FROM_PTR(str); |
334 |
| - |
335 |
| - byte *str_data = (byte *)&str[1]; |
336 |
| - size_t max_len = (byte*)MP_STATE_VM(mp_emergency_exception_buf) + mp_emergency_exception_buf_size |
337 |
| - - str_data; |
| 352 | +struct _exc_printer_t { |
| 353 | + bool allow_realloc; |
| 354 | + size_t alloc; |
| 355 | + size_t len; |
| 356 | + byte *buf; |
| 357 | +}; |
338 | 358 |
|
339 |
| - vstr_t vstr; |
340 |
| - vstr_init_fixed_buf(&vstr, max_len, (char *)str_data); |
| 359 | +STATIC void exc_add_strn(void *data, const char *str, size_t len) { |
| 360 | + struct _exc_printer_t *pr = data; |
| 361 | + if (pr->len + len >= pr->alloc) { |
| 362 | + // Not enough room for data plus a null byte so try to grow the buffer |
| 363 | + if (pr->allow_realloc) { |
| 364 | + size_t new_alloc = pr->alloc + len + 16; |
| 365 | + byte *new_buf = m_renew_maybe(byte, pr->buf, pr->alloc, new_alloc, true); |
| 366 | + if (new_buf == NULL) { |
| 367 | + pr->allow_realloc = false; |
| 368 | + len = pr->alloc - pr->len - 1; |
| 369 | + } else { |
| 370 | + pr->alloc = new_alloc; |
| 371 | + pr->buf = new_buf; |
| 372 | + } |
| 373 | + } else { |
| 374 | + len = pr->alloc - pr->len - 1; |
| 375 | + } |
| 376 | + } |
| 377 | + memcpy(pr->buf + pr->len, str, len); |
| 378 | + pr->len += len; |
| 379 | +} |
341 | 380 |
|
342 |
| - va_list ap; |
343 |
| - va_start(ap, fmt); |
344 |
| - vstr_vprintf(&vstr, fmt, ap); |
345 |
| - va_end(ap); |
| 381 | +mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, const char *fmt, ...) { |
| 382 | + assert(fmt != NULL); |
346 | 383 |
|
347 |
| - str->base.type = &mp_type_str; |
348 |
| - str->hash = qstr_compute_hash(str_data, str->len); |
349 |
| - str->len = vstr.len; |
350 |
| - str->data = str_data; |
| 384 | + // Check that the given type is an exception type |
| 385 | + assert(exc_type->make_new == mp_obj_exception_make_new); |
351 | 386 |
|
352 |
| - o->args = tuple; |
| 387 | + // Try to allocate memory for the message |
| 388 | + mp_obj_str_t *o_str = m_new_obj_maybe(mp_obj_str_t); |
| 389 | + size_t o_str_alloc = strlen(fmt) + 1; |
| 390 | + byte *o_str_buf = m_new_maybe(byte, o_str_alloc); |
| 391 | + |
| 392 | + bool used_emg_buf = false; |
| 393 | + #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF |
| 394 | + // If memory allocation failed and there is an emergency buffer then try to use |
| 395 | + // that buffer to store the string object and its data (at least 16 bytes for |
| 396 | + // the string data), reserving room at the start for the traceback and 1-tuple. |
| 397 | + if ((o_str == NULL || o_str_buf == NULL) |
| 398 | + && mp_emergency_exception_buf_size >= EMG_TRACEBACK_ALLOC * sizeof(size_t) |
| 399 | + + sizeof(mp_obj_tuple_t) + sizeof(mp_obj_t) + sizeof(mp_obj_str_t) + 16) { |
| 400 | + used_emg_buf = true; |
| 401 | + o_str = (mp_obj_str_t*)((uint8_t*)MP_STATE_VM(mp_emergency_exception_buf) |
| 402 | + + EMG_TRACEBACK_ALLOC * sizeof(size_t) + sizeof(mp_obj_tuple_t) + sizeof(mp_obj_t)); |
| 403 | + o_str_buf = (byte*)&o_str[1]; |
| 404 | + o_str_alloc = (uint8_t*)MP_STATE_VM(mp_emergency_exception_buf) |
| 405 | + + mp_emergency_exception_buf_size - o_str_buf; |
| 406 | + } |
| 407 | + #endif |
353 | 408 |
|
354 |
| - size_t offset = &str_data[str->len] - (byte*)MP_STATE_VM(mp_emergency_exception_buf); |
355 |
| - offset += sizeof(void *) - 1; |
356 |
| - offset &= ~(sizeof(void *) - 1); |
| 409 | + if (o_str == NULL) { |
| 410 | + // No memory for the string object so create the exception with no args |
| 411 | + return mp_obj_exception_make_new(exc_type, 0, 0, NULL); |
| 412 | + } |
357 | 413 |
|
358 |
| - if ((mp_emergency_exception_buf_size - offset) > (sizeof(o->traceback_data[0]) * 3)) { |
359 |
| - // We have room to store some traceback. |
360 |
| - o->traceback_data = (size_t*)((byte *)MP_STATE_VM(mp_emergency_exception_buf) + offset); |
361 |
| - o->traceback_alloc = ((byte*)MP_STATE_VM(mp_emergency_exception_buf) + mp_emergency_exception_buf_size - (byte *)o->traceback_data) / sizeof(o->traceback_data[0]); |
362 |
| - o->traceback_len = 0; |
363 |
| - } |
364 |
| - } |
365 |
| -#endif // MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF |
| 414 | + if (o_str_buf == NULL) { |
| 415 | + // No memory for the string buffer: assume that the fmt string is in ROM |
| 416 | + // and use that data as the data of the string |
| 417 | + o_str->len = o_str_alloc - 1; // will be equal to strlen(fmt) |
| 418 | + o_str->data = (const byte*)fmt; |
366 | 419 | } else {
|
367 |
| - o->base.type = exc_type; |
368 |
| - o->traceback_data = NULL; |
369 |
| - o->args = MP_OBJ_TO_PTR(mp_obj_new_tuple(1, NULL)); |
370 |
| - |
371 |
| - assert(fmt != NULL); |
372 |
| - { |
373 |
| - if (strchr(fmt, '%') == NULL) { |
374 |
| - // no formatting substitutions, avoid allocating vstr. |
375 |
| - o->args->items[0] = mp_obj_new_str(fmt, strlen(fmt), false); |
376 |
| - } else { |
377 |
| - // render exception message and store as .args[0] |
378 |
| - va_list ap; |
379 |
| - vstr_t vstr; |
380 |
| - vstr_init(&vstr, 16); |
381 |
| - va_start(ap, fmt); |
382 |
| - vstr_vprintf(&vstr, fmt, ap); |
383 |
| - va_end(ap); |
384 |
| - o->args->items[0] = mp_obj_new_str_from_vstr(&mp_type_str, &vstr); |
385 |
| - } |
386 |
| - } |
| 420 | + // We have some memory to format the string |
| 421 | + struct _exc_printer_t exc_pr = {!used_emg_buf, o_str_alloc, 0, o_str_buf}; |
| 422 | + mp_print_t print = {&exc_pr, exc_add_strn}; |
| 423 | + va_list ap; |
| 424 | + va_start(ap, fmt); |
| 425 | + mp_vprintf(&print, fmt, ap); |
| 426 | + va_end(ap); |
| 427 | + exc_pr.buf[exc_pr.len] = '\0'; |
| 428 | + o_str->len = exc_pr.len; |
| 429 | + o_str->data = exc_pr.buf; |
387 | 430 | }
|
388 | 431 |
|
389 |
| - return MP_OBJ_FROM_PTR(o); |
| 432 | + // Create the string object and call mp_obj_exception_make_new to create the exception |
| 433 | + o_str->base.type = &mp_type_str; |
| 434 | + o_str->hash = qstr_compute_hash(o_str->data, o_str->len); |
| 435 | + mp_obj_t arg = MP_OBJ_FROM_PTR(o_str); |
| 436 | + return mp_obj_exception_make_new(exc_type, 1, 0, &arg); |
390 | 437 | }
|
391 | 438 |
|
392 | 439 | // return true if the given object is an exception type
|
@@ -443,24 +490,46 @@ void mp_obj_exception_add_traceback(mp_obj_t self_in, qstr file, size_t line, qs
|
443 | 490 | // if memory allocation fails (eg because gc is locked), just return
|
444 | 491 |
|
445 | 492 | if (self->traceback_data == NULL) {
|
446 |
| - self->traceback_data = m_new_maybe(size_t, 3); |
| 493 | + self->traceback_data = m_new_maybe(size_t, TRACEBACK_ENTRY_LEN); |
447 | 494 | if (self->traceback_data == NULL) {
|
| 495 | + #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF |
| 496 | + if (mp_emergency_exception_buf_size >= EMG_TRACEBACK_ALLOC * sizeof(size_t)) { |
| 497 | + // There is room in the emergency buffer for traceback data |
| 498 | + size_t *tb = (size_t*)MP_STATE_VM(mp_emergency_exception_buf); |
| 499 | + self->traceback_data = tb; |
| 500 | + self->traceback_alloc = EMG_TRACEBACK_ALLOC; |
| 501 | + } else { |
| 502 | + // Can't allocate and no room in emergency buffer |
| 503 | + return; |
| 504 | + } |
| 505 | + #else |
| 506 | + // Can't allocate |
448 | 507 | return;
|
| 508 | + #endif |
| 509 | + } else { |
| 510 | + // Allocated the traceback data on the heap |
| 511 | + self->traceback_alloc = TRACEBACK_ENTRY_LEN; |
449 | 512 | }
|
450 |
| - self->traceback_alloc = 3; |
451 | 513 | self->traceback_len = 0;
|
452 |
| - } else if (self->traceback_len + 3 > self->traceback_alloc) { |
| 514 | + } else if (self->traceback_len + TRACEBACK_ENTRY_LEN > self->traceback_alloc) { |
| 515 | + #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF |
| 516 | + if (self->traceback_data == (size_t*)MP_STATE_VM(mp_emergency_exception_buf)) { |
| 517 | + // Can't resize the emergency buffer |
| 518 | + return; |
| 519 | + } |
| 520 | + #endif |
453 | 521 | // be conservative with growing traceback data
|
454 |
| - size_t *tb_data = m_renew_maybe(size_t, self->traceback_data, self->traceback_alloc, self->traceback_alloc + 3, true); |
| 522 | + size_t *tb_data = m_renew_maybe(size_t, self->traceback_data, self->traceback_alloc, |
| 523 | + self->traceback_alloc + TRACEBACK_ENTRY_LEN, true); |
455 | 524 | if (tb_data == NULL) {
|
456 | 525 | return;
|
457 | 526 | }
|
458 | 527 | self->traceback_data = tb_data;
|
459 |
| - self->traceback_alloc += 3; |
| 528 | + self->traceback_alloc += TRACEBACK_ENTRY_LEN; |
460 | 529 | }
|
461 | 530 |
|
462 | 531 | size_t *tb_data = &self->traceback_data[self->traceback_len];
|
463 |
| - self->traceback_len += 3; |
| 532 | + self->traceback_len += TRACEBACK_ENTRY_LEN; |
464 | 533 | tb_data[0] = file;
|
465 | 534 | tb_data[1] = line;
|
466 | 535 | tb_data[2] = block;
|
|
0 commit comments