|
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