From 2028a4f6d996d2a46cbc33d0b65fdae284ee71fc Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 3 Jul 2023 13:05:11 -0700 Subject: [PATCH 01/39] gh-106290: Fix edge cases around uops (#106319) - Tweak uops debugging output - Fix the bug from gh-106290 - Rename `SET_IP` to `SAVE_IP` (per /~https://github.com/faster-cpython/ideas/issues/558) - Add a `SAVE_IP` uop at the start of the trace (ditto) - Allow `unbound_local_error`; this gives us uops for `LOAD_FAST_CHECK`, `LOAD_CLOSURE`, and `DELETE_FAST` - Longer traces - Support `STORE_FAST_LOAD_FAST`, `STORE_FAST_STORE_FAST` - Add deps on pycore_uops.h to Makefile(.pre.in) --- Include/internal/pycore_uops.h | 2 +- Makefile.pre.in | 1 + Python/ceval.c | 62 ++--- Python/executor_cases.c.h | 291 +++++++++++++----------- Python/opcode_metadata.h | 6 +- Python/optimizer.c | 132 ++++++----- Tools/cases_generator/generate_cases.py | 14 +- 7 files changed, 274 insertions(+), 234 deletions(-) diff --git a/Include/internal/pycore_uops.h b/Include/internal/pycore_uops.h index 0e88d7e7f4a3bd..5ed275fb857679 100644 --- a/Include/internal/pycore_uops.h +++ b/Include/internal/pycore_uops.h @@ -8,7 +8,7 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif -#define _Py_UOP_MAX_TRACE_LENGTH 16 +#define _Py_UOP_MAX_TRACE_LENGTH 32 typedef struct { int opcode; diff --git a/Makefile.pre.in b/Makefile.pre.in index dcb3b5eb06a1e9..41623bd2f1da7f 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1800,6 +1800,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/internal/pycore_unionobject.h \ $(srcdir)/Include/internal/pycore_unicodeobject.h \ $(srcdir)/Include/internal/pycore_unicodeobject_generated.h \ + $(srcdir)/Include/internal/pycore_uops.h \ $(srcdir)/Include/internal/pycore_warnings.h \ $(srcdir)/Include/internal/pycore_weakref.h \ $(DTRACE_HEADERS) \ diff --git a/Python/ceval.c b/Python/ceval.c index 80ae85c24f0540..6ce1a6a636ed71 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2773,24 +2773,26 @@ void Py_LeaveRecursiveCall(void) _PyInterpreterFrame * _PyUopExecute(_PyExecutorObject *executor, _PyInterpreterFrame *frame, PyObject **stack_pointer) { -#ifdef LLTRACE +#ifdef Py_DEBUG char *uop_debug = Py_GETENV("PYTHONUOPSDEBUG"); int lltrace = 0; if (uop_debug != NULL && *uop_debug >= '0') { lltrace = *uop_debug - '0'; // TODO: Parse an int and all that } - if (lltrace >= 2) { - PyCodeObject *code = _PyFrame_GetCode(frame); - _Py_CODEUNIT *instr = frame->prev_instr + 1; - fprintf(stderr, - "Entering _PyUopExecute for %s (%s:%d) at offset %ld\n", - PyUnicode_AsUTF8(code->co_qualname), - PyUnicode_AsUTF8(code->co_filename), - code->co_firstlineno, - (long)(instr - (_Py_CODEUNIT *)code->co_code_adaptive)); - } +#define DPRINTF(level, ...) \ + if (lltrace >= (level)) { fprintf(stderr, __VA_ARGS__); } +#else +#define DPRINTF(level, ...) #endif + DPRINTF(3, + "Entering _PyUopExecute for %s (%s:%d) at offset %ld\n", + PyUnicode_AsUTF8(_PyFrame_GetCode(frame)->co_qualname), + PyUnicode_AsUTF8(_PyFrame_GetCode(frame)->co_filename), + _PyFrame_GetCode(frame)->co_firstlineno, + (long)(frame->prev_instr + 1 - + (_Py_CODEUNIT *)_PyFrame_GetCode(frame)->co_code_adaptive)); + PyThreadState *tstate = _PyThreadState_GET(); _PyUOpExecutorObject *self = (_PyUOpExecutorObject *)executor; @@ -2803,7 +2805,7 @@ _PyUopExecute(_PyExecutorObject *executor, _PyInterpreterFrame *frame, PyObject } OBJECT_STAT_INC(optimization_traces_executed); - _Py_CODEUNIT *ip_offset = (_Py_CODEUNIT *)_PyFrame_GetCode(frame)->co_code_adaptive - 1; + _Py_CODEUNIT *ip_offset = (_Py_CODEUNIT *)_PyFrame_GetCode(frame)->co_code_adaptive; int pc = 0; int opcode; uint64_t operand; @@ -2812,14 +2814,11 @@ _PyUopExecute(_PyExecutorObject *executor, _PyInterpreterFrame *frame, PyObject opcode = self->trace[pc].opcode; operand = self->trace[pc].operand; oparg = (int)operand; -#ifdef LLTRACE - if (lltrace >= 3) { - const char *opname = opcode < 256 ? _PyOpcode_OpName[opcode] : _PyOpcode_uop_name[opcode]; - int stack_level = (int)(stack_pointer - _PyFrame_Stackbase(frame)); - fprintf(stderr, " uop %s, operand %" PRIu64 ", stack_level %d\n", - opname, operand, stack_level); - } -#endif + DPRINTF(3, + " uop %s, operand %" PRIu64 ", stack_level %d\n", + opcode < 256 ? _PyOpcode_OpName[opcode] : _PyOpcode_uop_name[opcode], + operand, + (int)(stack_pointer - _PyFrame_Stackbase(frame))); pc++; OBJECT_STAT_INC(optimization_uops_executed); switch (opcode) { @@ -2828,7 +2827,7 @@ _PyUopExecute(_PyExecutorObject *executor, _PyInterpreterFrame *frame, PyObject #define ENABLE_SPECIALIZATION 0 #include "executor_cases.c.h" - case SET_IP: + case SAVE_IP: { frame->prev_instr = ip_offset + oparg; break; @@ -2836,6 +2835,7 @@ _PyUopExecute(_PyExecutorObject *executor, _PyInterpreterFrame *frame, PyObject case EXIT_TRACE: { + frame->prev_instr--; // Back up to just before destination _PyFrame_SetStackPointer(frame, stack_pointer); Py_DECREF(self); return frame; @@ -2850,6 +2850,13 @@ _PyUopExecute(_PyExecutorObject *executor, _PyInterpreterFrame *frame, PyObject } } +unbound_local_error: + format_exc_check_arg(tstate, PyExc_UnboundLocalError, + UNBOUNDLOCAL_ERROR_MSG, + PyTuple_GetItem(_PyFrame_GetCode(frame)->co_localsplusnames, oparg) + ); + goto error; + pop_4_error: STACK_SHRINK(1); pop_3_error: @@ -2861,11 +2868,7 @@ _PyUopExecute(_PyExecutorObject *executor, _PyInterpreterFrame *frame, PyObject error: // On ERROR_IF we return NULL as the frame. // The caller recovers the frame from cframe.current_frame. -#ifdef LLTRACE - if (lltrace >= 2) { - fprintf(stderr, "Error: [Opcode %d, operand %" PRIu64 "]\n", opcode, operand); - } -#endif + DPRINTF(2, "Error: [Opcode %d, operand %" PRIu64 "]\n", opcode, operand); _PyFrame_SetStackPointer(frame, stack_pointer); Py_DECREF(self); return NULL; @@ -2873,11 +2876,8 @@ _PyUopExecute(_PyExecutorObject *executor, _PyInterpreterFrame *frame, PyObject deoptimize: // On DEOPT_IF we just repeat the last instruction. // This presumes nothing was popped from the stack (nor pushed). -#ifdef LLTRACE - if (lltrace >= 2) { - fprintf(stderr, "DEOPT: [Opcode %d, operand %" PRIu64 "]\n", opcode, operand); - } -#endif + DPRINTF(2, "DEOPT: [Opcode %d, operand %" PRIu64 "]\n", opcode, operand); + frame->prev_instr--; // Back up to just before destination _PyFrame_SetStackPointer(frame, stack_pointer); Py_DECREF(self); return frame; diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 546b3d9f50ac76..d1e0443db613d2 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -7,13 +7,25 @@ break; } + case LOAD_FAST_CHECK: { + PyObject *value; + #line 182 "Python/bytecodes.c" + value = GETLOCAL(oparg); + if (value == NULL) goto unbound_local_error; + Py_INCREF(value); + #line 17 "Python/executor_cases.c.h" + STACK_GROW(1); + stack_pointer[-1] = value; + break; + } + case LOAD_FAST: { PyObject *value; #line 188 "Python/bytecodes.c" value = GETLOCAL(oparg); assert(value != NULL); Py_INCREF(value); - #line 17 "Python/executor_cases.c.h" + #line 29 "Python/executor_cases.c.h" STACK_GROW(1); stack_pointer[-1] = value; break; @@ -25,7 +37,7 @@ value = GETLOCAL(oparg); // do not use SETLOCAL here, it decrefs the old value GETLOCAL(oparg) = NULL; - #line 29 "Python/executor_cases.c.h" + #line 41 "Python/executor_cases.c.h" STACK_GROW(1); stack_pointer[-1] = value; break; @@ -36,7 +48,7 @@ #line 209 "Python/bytecodes.c" value = GETITEM(FRAME_CO_CONSTS, oparg); Py_INCREF(value); - #line 40 "Python/executor_cases.c.h" + #line 52 "Python/executor_cases.c.h" STACK_GROW(1); stack_pointer[-1] = value; break; @@ -46,7 +58,7 @@ PyObject *value = stack_pointer[-1]; #line 214 "Python/bytecodes.c" SETLOCAL(oparg, value); - #line 50 "Python/executor_cases.c.h" + #line 62 "Python/executor_cases.c.h" STACK_SHRINK(1); break; } @@ -54,7 +66,7 @@ case POP_TOP: { PyObject *value = stack_pointer[-1]; #line 237 "Python/bytecodes.c" - #line 58 "Python/executor_cases.c.h" + #line 70 "Python/executor_cases.c.h" Py_DECREF(value); STACK_SHRINK(1); break; @@ -64,7 +76,7 @@ PyObject *res; #line 241 "Python/bytecodes.c" res = NULL; - #line 68 "Python/executor_cases.c.h" + #line 80 "Python/executor_cases.c.h" STACK_GROW(1); stack_pointer[-1] = res; break; @@ -75,7 +87,7 @@ PyObject *receiver = stack_pointer[-2]; #line 260 "Python/bytecodes.c" Py_DECREF(receiver); - #line 79 "Python/executor_cases.c.h" + #line 91 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = value; break; @@ -86,11 +98,11 @@ PyObject *res; #line 275 "Python/bytecodes.c" res = PyNumber_Negative(value); - #line 90 "Python/executor_cases.c.h" + #line 102 "Python/executor_cases.c.h" Py_DECREF(value); #line 277 "Python/bytecodes.c" if (res == NULL) goto pop_1_error; - #line 94 "Python/executor_cases.c.h" + #line 106 "Python/executor_cases.c.h" stack_pointer[-1] = res; break; } @@ -101,7 +113,7 @@ #line 281 "Python/bytecodes.c" assert(PyBool_Check(value)); res = Py_IsFalse(value) ? Py_True : Py_False; - #line 105 "Python/executor_cases.c.h" + #line 117 "Python/executor_cases.c.h" stack_pointer[-1] = res; break; } @@ -111,7 +123,7 @@ #line 313 "Python/bytecodes.c" DEOPT_IF(!PyBool_Check(value), TO_BOOL); STAT_INC(TO_BOOL, hit); - #line 115 "Python/executor_cases.c.h" + #line 127 "Python/executor_cases.c.h" break; } @@ -126,12 +138,12 @@ res = Py_False; } else { - #line 130 "Python/executor_cases.c.h" + #line 142 "Python/executor_cases.c.h" Py_DECREF(value); #line 326 "Python/bytecodes.c" res = Py_True; } - #line 135 "Python/executor_cases.c.h" + #line 147 "Python/executor_cases.c.h" stack_pointer[-1] = res; break; } @@ -143,7 +155,7 @@ DEOPT_IF(!PyList_CheckExact(value), TO_BOOL); STAT_INC(TO_BOOL, hit); res = Py_SIZE(value) ? Py_True : Py_False; - #line 147 "Python/executor_cases.c.h" + #line 159 "Python/executor_cases.c.h" Py_DECREF(value); stack_pointer[-1] = res; break; @@ -157,7 +169,7 @@ DEOPT_IF(!Py_IsNone(value), TO_BOOL); STAT_INC(TO_BOOL, hit); res = Py_False; - #line 161 "Python/executor_cases.c.h" + #line 173 "Python/executor_cases.c.h" stack_pointer[-1] = res; break; } @@ -174,12 +186,12 @@ } else { assert(Py_SIZE(value)); - #line 178 "Python/executor_cases.c.h" + #line 190 "Python/executor_cases.c.h" Py_DECREF(value); #line 354 "Python/bytecodes.c" res = Py_True; } - #line 183 "Python/executor_cases.c.h" + #line 195 "Python/executor_cases.c.h" stack_pointer[-1] = res; break; } @@ -193,11 +205,11 @@ assert(version); DEOPT_IF(Py_TYPE(value)->tp_version_tag != version, TO_BOOL); STAT_INC(TO_BOOL, hit); - #line 197 "Python/executor_cases.c.h" + #line 209 "Python/executor_cases.c.h" Py_DECREF(value); #line 364 "Python/bytecodes.c" res = Py_True; - #line 201 "Python/executor_cases.c.h" + #line 213 "Python/executor_cases.c.h" stack_pointer[-1] = res; break; } @@ -207,11 +219,11 @@ PyObject *res; #line 368 "Python/bytecodes.c" res = PyNumber_Invert(value); - #line 211 "Python/executor_cases.c.h" + #line 223 "Python/executor_cases.c.h" Py_DECREF(value); #line 370 "Python/bytecodes.c" if (res == NULL) goto pop_1_error; - #line 215 "Python/executor_cases.c.h" + #line 227 "Python/executor_cases.c.h" stack_pointer[-1] = res; break; } @@ -222,7 +234,7 @@ #line 386 "Python/bytecodes.c" DEOPT_IF(!PyLong_CheckExact(left), BINARY_OP); DEOPT_IF(!PyLong_CheckExact(right), BINARY_OP); - #line 226 "Python/executor_cases.c.h" + #line 238 "Python/executor_cases.c.h" break; } @@ -236,7 +248,7 @@ _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); _Py_DECREF_SPECIALIZED(left, (destructor)PyObject_Free); if (res == NULL) goto pop_2_error; - #line 240 "Python/executor_cases.c.h" + #line 252 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = res; break; @@ -252,7 +264,7 @@ _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); _Py_DECREF_SPECIALIZED(left, (destructor)PyObject_Free); if (res == NULL) goto pop_2_error; - #line 256 "Python/executor_cases.c.h" + #line 268 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = res; break; @@ -268,7 +280,7 @@ _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); _Py_DECREF_SPECIALIZED(left, (destructor)PyObject_Free); if (res == NULL) goto pop_2_error; - #line 272 "Python/executor_cases.c.h" + #line 284 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = res; break; @@ -280,7 +292,7 @@ #line 422 "Python/bytecodes.c" DEOPT_IF(!PyFloat_CheckExact(left), BINARY_OP); DEOPT_IF(!PyFloat_CheckExact(right), BINARY_OP); - #line 284 "Python/executor_cases.c.h" + #line 296 "Python/executor_cases.c.h" break; } @@ -294,7 +306,7 @@ ((PyFloatObject *)left)->ob_fval * ((PyFloatObject *)right)->ob_fval; DECREF_INPUTS_AND_REUSE_FLOAT(left, right, dres, res); - #line 298 "Python/executor_cases.c.h" + #line 310 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = res; break; @@ -310,7 +322,7 @@ ((PyFloatObject *)left)->ob_fval + ((PyFloatObject *)right)->ob_fval; DECREF_INPUTS_AND_REUSE_FLOAT(left, right, dres, res); - #line 314 "Python/executor_cases.c.h" + #line 326 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = res; break; @@ -326,7 +338,7 @@ ((PyFloatObject *)left)->ob_fval - ((PyFloatObject *)right)->ob_fval; DECREF_INPUTS_AND_REUSE_FLOAT(left, right, dres, res); - #line 330 "Python/executor_cases.c.h" + #line 342 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = res; break; @@ -338,7 +350,7 @@ #line 458 "Python/bytecodes.c" DEOPT_IF(!PyUnicode_CheckExact(left), BINARY_OP); DEOPT_IF(!PyUnicode_CheckExact(right), BINARY_OP); - #line 342 "Python/executor_cases.c.h" + #line 354 "Python/executor_cases.c.h" break; } @@ -352,7 +364,7 @@ _Py_DECREF_SPECIALIZED(left, _PyUnicode_ExactDealloc); _Py_DECREF_SPECIALIZED(right, _PyUnicode_ExactDealloc); if (res == NULL) goto pop_2_error; - #line 356 "Python/executor_cases.c.h" + #line 368 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = res; break; @@ -376,7 +388,7 @@ } Py_DECREF(container); if (res == NULL) goto pop_3_error; - #line 380 "Python/executor_cases.c.h" + #line 392 "Python/executor_cases.c.h" STACK_SHRINK(2); stack_pointer[-1] = res; break; @@ -400,7 +412,7 @@ Py_DECREF(v); Py_DECREF(container); if (err) goto pop_4_error; - #line 404 "Python/executor_cases.c.h" + #line 416 "Python/executor_cases.c.h" STACK_SHRINK(4); break; } @@ -423,7 +435,7 @@ Py_INCREF(res); _Py_DECREF_SPECIALIZED(sub, (destructor)PyObject_Free); Py_DECREF(list); - #line 427 "Python/executor_cases.c.h" + #line 439 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = res; break; @@ -447,7 +459,7 @@ Py_INCREF(res); _Py_DECREF_SPECIALIZED(sub, (destructor)PyObject_Free); Py_DECREF(tuple); - #line 451 "Python/executor_cases.c.h" + #line 463 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = res; break; @@ -465,14 +477,14 @@ if (!_PyErr_Occurred(tstate)) { _PyErr_SetKeyError(sub); } - #line 469 "Python/executor_cases.c.h" + #line 481 "Python/executor_cases.c.h" Py_DECREF(dict); Py_DECREF(sub); #line 603 "Python/bytecodes.c" if (true) goto pop_2_error; } Py_INCREF(res); // Do this before DECREF'ing dict, sub - #line 476 "Python/executor_cases.c.h" + #line 488 "Python/executor_cases.c.h" Py_DECREF(dict); Py_DECREF(sub); STACK_SHRINK(1); @@ -485,7 +497,7 @@ PyObject *list = stack_pointer[-(2 + (oparg-1))]; #line 635 "Python/bytecodes.c" if (_PyList_AppendTakeRef((PyListObject *)list, v) < 0) goto pop_1_error; - #line 489 "Python/executor_cases.c.h" + #line 501 "Python/executor_cases.c.h" STACK_SHRINK(1); break; } @@ -495,11 +507,11 @@ PyObject *set = stack_pointer[-(2 + (oparg-1))]; #line 639 "Python/bytecodes.c" int err = PySet_Add(set, v); - #line 499 "Python/executor_cases.c.h" + #line 511 "Python/executor_cases.c.h" Py_DECREF(v); #line 641 "Python/bytecodes.c" if (err) goto pop_1_error; - #line 503 "Python/executor_cases.c.h" + #line 515 "Python/executor_cases.c.h" STACK_SHRINK(1); break; } @@ -525,7 +537,7 @@ Py_DECREF(old_value); _Py_DECREF_SPECIALIZED(sub, (destructor)PyObject_Free); Py_DECREF(list); - #line 529 "Python/executor_cases.c.h" + #line 541 "Python/executor_cases.c.h" STACK_SHRINK(3); break; } @@ -540,7 +552,7 @@ int err = _PyDict_SetItem_Take2((PyDictObject *)dict, sub, value); Py_DECREF(dict); if (err) goto pop_3_error; - #line 544 "Python/executor_cases.c.h" + #line 556 "Python/executor_cases.c.h" STACK_SHRINK(3); break; } @@ -551,12 +563,12 @@ #line 697 "Python/bytecodes.c" /* del container[sub] */ int err = PyObject_DelItem(container, sub); - #line 555 "Python/executor_cases.c.h" + #line 567 "Python/executor_cases.c.h" Py_DECREF(container); Py_DECREF(sub); #line 700 "Python/bytecodes.c" if (err) goto pop_2_error; - #line 560 "Python/executor_cases.c.h" + #line 572 "Python/executor_cases.c.h" STACK_SHRINK(2); break; } @@ -567,11 +579,11 @@ #line 704 "Python/bytecodes.c" assert(oparg <= MAX_INTRINSIC_1); res = _PyIntrinsics_UnaryFunctions[oparg](tstate, value); - #line 571 "Python/executor_cases.c.h" + #line 583 "Python/executor_cases.c.h" Py_DECREF(value); #line 707 "Python/bytecodes.c" if (res == NULL) goto pop_1_error; - #line 575 "Python/executor_cases.c.h" + #line 587 "Python/executor_cases.c.h" stack_pointer[-1] = res; break; } @@ -583,12 +595,12 @@ #line 711 "Python/bytecodes.c" assert(oparg <= MAX_INTRINSIC_2); res = _PyIntrinsics_BinaryFunctions[oparg](tstate, value2, value1); - #line 587 "Python/executor_cases.c.h" + #line 599 "Python/executor_cases.c.h" Py_DECREF(value2); Py_DECREF(value1); #line 714 "Python/bytecodes.c" if (res == NULL) goto pop_2_error; - #line 592 "Python/executor_cases.c.h" + #line 604 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = res; break; @@ -610,14 +622,14 @@ "'async for' requires an object with " "__aiter__ method, got %.100s", type->tp_name); - #line 614 "Python/executor_cases.c.h" + #line 626 "Python/executor_cases.c.h" Py_DECREF(obj); #line 832 "Python/bytecodes.c" if (true) goto pop_1_error; } iter = (*getter)(obj); - #line 621 "Python/executor_cases.c.h" + #line 633 "Python/executor_cases.c.h" Py_DECREF(obj); #line 837 "Python/bytecodes.c" if (iter == NULL) goto pop_1_error; @@ -632,7 +644,7 @@ Py_DECREF(iter); if (true) goto pop_1_error; } - #line 636 "Python/executor_cases.c.h" + #line 648 "Python/executor_cases.c.h" stack_pointer[-1] = iter; break; } @@ -683,7 +695,7 @@ Py_DECREF(next_iter); } } - #line 687 "Python/executor_cases.c.h" + #line 699 "Python/executor_cases.c.h" STACK_GROW(1); stack_pointer[-1] = awaitable; break; @@ -699,7 +711,7 @@ format_awaitable_error(tstate, Py_TYPE(iterable), oparg); } - #line 703 "Python/executor_cases.c.h" + #line 715 "Python/executor_cases.c.h" Py_DECREF(iterable); #line 904 "Python/bytecodes.c" @@ -718,7 +730,7 @@ } if (iter == NULL) goto pop_1_error; - #line 722 "Python/executor_cases.c.h" + #line 734 "Python/executor_cases.c.h" stack_pointer[-1] = iter; break; } @@ -728,7 +740,7 @@ #line 1034 "Python/bytecodes.c" _PyErr_StackItem *exc_info = tstate->exc_info; Py_XSETREF(exc_info->exc_value, exc_value); - #line 732 "Python/executor_cases.c.h" + #line 744 "Python/executor_cases.c.h" STACK_SHRINK(1); break; } @@ -737,7 +749,7 @@ PyObject *value; #line 1085 "Python/bytecodes.c" value = Py_NewRef(PyExc_AssertionError); - #line 741 "Python/executor_cases.c.h" + #line 753 "Python/executor_cases.c.h" STACK_GROW(1); stack_pointer[-1] = value; break; @@ -767,7 +779,7 @@ if (true) goto error; } } - #line 771 "Python/executor_cases.c.h" + #line 783 "Python/executor_cases.c.h" STACK_GROW(1); stack_pointer[-1] = bc; break; @@ -782,7 +794,7 @@ if (ns == NULL) { _PyErr_Format(tstate, PyExc_SystemError, "no locals found when storing %R", name); - #line 786 "Python/executor_cases.c.h" + #line 798 "Python/executor_cases.c.h" Py_DECREF(v); #line 1121 "Python/bytecodes.c" if (true) goto pop_1_error; @@ -791,11 +803,11 @@ err = PyDict_SetItem(ns, name, v); else err = PyObject_SetItem(ns, name, v); - #line 795 "Python/executor_cases.c.h" + #line 807 "Python/executor_cases.c.h" Py_DECREF(v); #line 1128 "Python/bytecodes.c" if (err) goto pop_1_error; - #line 799 "Python/executor_cases.c.h" + #line 811 "Python/executor_cases.c.h" STACK_SHRINK(1); break; } @@ -818,7 +830,7 @@ name); goto error; } - #line 822 "Python/executor_cases.c.h" + #line 834 "Python/executor_cases.c.h" break; } @@ -832,7 +844,7 @@ STAT_INC(UNPACK_SEQUENCE, hit); values[0] = Py_NewRef(PyTuple_GET_ITEM(seq, 1)); values[1] = Py_NewRef(PyTuple_GET_ITEM(seq, 0)); - #line 836 "Python/executor_cases.c.h" + #line 848 "Python/executor_cases.c.h" Py_DECREF(seq); STACK_SHRINK(1); STACK_GROW(oparg); @@ -850,7 +862,7 @@ for (int i = oparg; --i >= 0; ) { *values++ = Py_NewRef(items[i]); } - #line 854 "Python/executor_cases.c.h" + #line 866 "Python/executor_cases.c.h" Py_DECREF(seq); STACK_SHRINK(1); STACK_GROW(oparg); @@ -868,7 +880,7 @@ for (int i = oparg; --i >= 0; ) { *values++ = Py_NewRef(items[i]); } - #line 872 "Python/executor_cases.c.h" + #line 884 "Python/executor_cases.c.h" Py_DECREF(seq); STACK_SHRINK(1); STACK_GROW(oparg); @@ -881,11 +893,11 @@ int totalargs = 1 + (oparg & 0xFF) + (oparg >> 8); PyObject **top = stack_pointer + totalargs - 1; int res = unpack_iterable(tstate, seq, oparg & 0xFF, oparg >> 8, top); - #line 885 "Python/executor_cases.c.h" + #line 897 "Python/executor_cases.c.h" Py_DECREF(seq); #line 1211 "Python/bytecodes.c" if (res == 0) goto pop_1_error; - #line 889 "Python/executor_cases.c.h" + #line 901 "Python/executor_cases.c.h" STACK_GROW((oparg & 0xFF) + (oparg >> 8)); break; } @@ -895,11 +907,11 @@ #line 1242 "Python/bytecodes.c" PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); int err = PyObject_SetAttr(owner, name, (PyObject *)NULL); - #line 899 "Python/executor_cases.c.h" + #line 911 "Python/executor_cases.c.h" Py_DECREF(owner); #line 1245 "Python/bytecodes.c" if (err) goto pop_1_error; - #line 903 "Python/executor_cases.c.h" + #line 915 "Python/executor_cases.c.h" STACK_SHRINK(1); break; } @@ -909,11 +921,11 @@ #line 1249 "Python/bytecodes.c" PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); int err = PyDict_SetItem(GLOBALS(), name, v); - #line 913 "Python/executor_cases.c.h" + #line 925 "Python/executor_cases.c.h" Py_DECREF(v); #line 1252 "Python/bytecodes.c" if (err) goto pop_1_error; - #line 917 "Python/executor_cases.c.h" + #line 929 "Python/executor_cases.c.h" STACK_SHRINK(1); break; } @@ -931,7 +943,7 @@ } goto error; } - #line 935 "Python/executor_cases.c.h" + #line 947 "Python/executor_cases.c.h" break; } @@ -945,7 +957,7 @@ if (true) goto error; } Py_INCREF(locals); - #line 949 "Python/executor_cases.c.h" + #line 961 "Python/executor_cases.c.h" STACK_GROW(1); stack_pointer[-1] = locals; break; @@ -1011,11 +1023,20 @@ } } } - #line 1015 "Python/executor_cases.c.h" + #line 1027 "Python/executor_cases.c.h" stack_pointer[-1] = v; break; } + case DELETE_FAST: { + #line 1435 "Python/bytecodes.c" + PyObject *v = GETLOCAL(oparg); + if (v == NULL) goto unbound_local_error; + SETLOCAL(oparg, NULL); + #line 1037 "Python/executor_cases.c.h" + break; + } + case DELETE_DEREF: { #line 1452 "Python/bytecodes.c" PyObject *cell = GETLOCAL(oparg); @@ -1028,7 +1049,7 @@ } PyCell_SET(cell, NULL); Py_DECREF(oldobj); - #line 1032 "Python/executor_cases.c.h" + #line 1053 "Python/executor_cases.c.h" break; } @@ -1070,7 +1091,7 @@ } Py_INCREF(value); } - #line 1074 "Python/executor_cases.c.h" + #line 1095 "Python/executor_cases.c.h" stack_pointer[-1] = value; break; } @@ -1085,7 +1106,7 @@ if (true) goto error; } Py_INCREF(value); - #line 1089 "Python/executor_cases.c.h" + #line 1110 "Python/executor_cases.c.h" STACK_GROW(1); stack_pointer[-1] = value; break; @@ -1098,7 +1119,7 @@ PyObject *oldobj = PyCell_GET(cell); PyCell_SET(cell, v); Py_XDECREF(oldobj); - #line 1102 "Python/executor_cases.c.h" + #line 1123 "Python/executor_cases.c.h" STACK_SHRINK(1); break; } @@ -1115,7 +1136,7 @@ PyObject *o = PyTuple_GET_ITEM(closure, i); frame->localsplus[offset + i] = Py_NewRef(o); } - #line 1119 "Python/executor_cases.c.h" + #line 1140 "Python/executor_cases.c.h" break; } @@ -1124,13 +1145,13 @@ PyObject *str; #line 1532 "Python/bytecodes.c" str = _PyUnicode_JoinArray(&_Py_STR(empty), pieces, oparg); - #line 1128 "Python/executor_cases.c.h" + #line 1149 "Python/executor_cases.c.h" for (int _i = oparg; --_i >= 0;) { Py_DECREF(pieces[_i]); } #line 1534 "Python/bytecodes.c" if (str == NULL) { STACK_SHRINK(oparg); goto error; } - #line 1134 "Python/executor_cases.c.h" + #line 1155 "Python/executor_cases.c.h" STACK_SHRINK(oparg); STACK_GROW(1); stack_pointer[-1] = str; @@ -1143,7 +1164,7 @@ #line 1538 "Python/bytecodes.c" tup = _PyTuple_FromArraySteal(values, oparg); if (tup == NULL) { STACK_SHRINK(oparg); goto error; } - #line 1147 "Python/executor_cases.c.h" + #line 1168 "Python/executor_cases.c.h" STACK_SHRINK(oparg); STACK_GROW(1); stack_pointer[-1] = tup; @@ -1156,7 +1177,7 @@ #line 1543 "Python/bytecodes.c" list = _PyList_FromArraySteal(values, oparg); if (list == NULL) { STACK_SHRINK(oparg); goto error; } - #line 1160 "Python/executor_cases.c.h" + #line 1181 "Python/executor_cases.c.h" STACK_SHRINK(oparg); STACK_GROW(1); stack_pointer[-1] = list; @@ -1177,13 +1198,13 @@ "Value after * must be an iterable, not %.200s", Py_TYPE(iterable)->tp_name); } - #line 1181 "Python/executor_cases.c.h" + #line 1202 "Python/executor_cases.c.h" Py_DECREF(iterable); #line 1559 "Python/bytecodes.c" if (true) goto pop_1_error; } assert(Py_IsNone(none_val)); - #line 1187 "Python/executor_cases.c.h" + #line 1208 "Python/executor_cases.c.h" Py_DECREF(iterable); STACK_SHRINK(1); break; @@ -1194,11 +1215,11 @@ PyObject *set = stack_pointer[-(2 + (oparg-1))]; #line 1566 "Python/bytecodes.c" int err = _PySet_Update(set, iterable); - #line 1198 "Python/executor_cases.c.h" + #line 1219 "Python/executor_cases.c.h" Py_DECREF(iterable); #line 1568 "Python/bytecodes.c" if (err < 0) goto pop_1_error; - #line 1202 "Python/executor_cases.c.h" + #line 1223 "Python/executor_cases.c.h" STACK_SHRINK(1); break; } @@ -1221,7 +1242,7 @@ Py_DECREF(set); if (true) { STACK_SHRINK(oparg); goto error; } } - #line 1225 "Python/executor_cases.c.h" + #line 1246 "Python/executor_cases.c.h" STACK_SHRINK(oparg); STACK_GROW(1); stack_pointer[-1] = set; @@ -1239,13 +1260,13 @@ if (map == NULL) goto error; - #line 1243 "Python/executor_cases.c.h" + #line 1264 "Python/executor_cases.c.h" for (int _i = oparg*2; --_i >= 0;) { Py_DECREF(values[_i]); } #line 1597 "Python/bytecodes.c" if (map == NULL) { STACK_SHRINK(oparg*2); goto error; } - #line 1249 "Python/executor_cases.c.h" + #line 1270 "Python/executor_cases.c.h" STACK_SHRINK(oparg*2); STACK_GROW(1); stack_pointer[-1] = map; @@ -1293,7 +1314,7 @@ Py_DECREF(ann_dict); } } - #line 1297 "Python/executor_cases.c.h" + #line 1318 "Python/executor_cases.c.h" break; } @@ -1311,14 +1332,14 @@ map = _PyDict_FromItems( &PyTuple_GET_ITEM(keys, 0), 1, values, 1, oparg); - #line 1315 "Python/executor_cases.c.h" + #line 1336 "Python/executor_cases.c.h" for (int _i = oparg; --_i >= 0;) { Py_DECREF(values[_i]); } Py_DECREF(keys); #line 1653 "Python/bytecodes.c" if (map == NULL) { STACK_SHRINK(oparg); goto pop_1_error; } - #line 1322 "Python/executor_cases.c.h" + #line 1343 "Python/executor_cases.c.h" STACK_SHRINK(oparg); stack_pointer[-1] = map; break; @@ -1334,12 +1355,12 @@ "'%.200s' object is not a mapping", Py_TYPE(update)->tp_name); } - #line 1338 "Python/executor_cases.c.h" + #line 1359 "Python/executor_cases.c.h" Py_DECREF(update); #line 1665 "Python/bytecodes.c" if (true) goto pop_1_error; } - #line 1343 "Python/executor_cases.c.h" + #line 1364 "Python/executor_cases.c.h" Py_DECREF(update); STACK_SHRINK(1); break; @@ -1352,12 +1373,12 @@ if (_PyDict_MergeEx(dict, update, 2) < 0) { format_kwargs_error(tstate, PEEK(3 + oparg), update); - #line 1356 "Python/executor_cases.c.h" + #line 1377 "Python/executor_cases.c.h" Py_DECREF(update); #line 1676 "Python/bytecodes.c" if (true) goto pop_1_error; } - #line 1361 "Python/executor_cases.c.h" + #line 1382 "Python/executor_cases.c.h" Py_DECREF(update); STACK_SHRINK(1); break; @@ -1372,7 +1393,7 @@ /* dict[key] = value */ // Do not DECREF INPUTS because the function steals the references if (_PyDict_SetItem_Take2((PyDictObject *)dict, key, value) != 0) goto pop_2_error; - #line 1376 "Python/executor_cases.c.h" + #line 1397 "Python/executor_cases.c.h" STACK_SHRINK(2); break; } @@ -1390,13 +1411,13 @@ STAT_INC(LOAD_SUPER_ATTR, hit); PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 2); res = _PySuper_Lookup((PyTypeObject *)class, self, name, NULL); - #line 1394 "Python/executor_cases.c.h" + #line 1415 "Python/executor_cases.c.h" Py_DECREF(global_super); Py_DECREF(class); Py_DECREF(self); #line 1772 "Python/bytecodes.c" if (res == NULL) goto pop_3_error; - #line 1400 "Python/executor_cases.c.h" + #line 1421 "Python/executor_cases.c.h" STACK_SHRINK(2); STACK_GROW(((oparg & 1) ? 1 : 0)); stack_pointer[-1] = res; @@ -1433,7 +1454,7 @@ res = res2; res2 = NULL; } - #line 1437 "Python/executor_cases.c.h" + #line 1458 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = res; stack_pointer[-2] = res2; @@ -1456,7 +1477,7 @@ _Py_DECREF_SPECIALIZED(right, _PyFloat_ExactDealloc); res = (sign_ish & oparg) ? Py_True : Py_False; // It's always a bool, so we don't care about oparg & 16. - #line 1460 "Python/executor_cases.c.h" + #line 1481 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = res; break; @@ -1482,7 +1503,7 @@ _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); res = (sign_ish & oparg) ? Py_True : Py_False; // It's always a bool, so we don't care about oparg & 16. - #line 1486 "Python/executor_cases.c.h" + #line 1507 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = res; break; @@ -1505,7 +1526,7 @@ assert(COMPARISON_NOT_EQUALS + 1 == COMPARISON_EQUALS); res = ((COMPARISON_NOT_EQUALS + eq) & oparg) ? Py_True : Py_False; // It's always a bool, so we don't care about oparg & 16. - #line 1509 "Python/executor_cases.c.h" + #line 1530 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = res; break; @@ -1517,12 +1538,12 @@ PyObject *b; #line 2163 "Python/bytecodes.c" int res = Py_Is(left, right) ^ oparg; - #line 1521 "Python/executor_cases.c.h" + #line 1542 "Python/executor_cases.c.h" Py_DECREF(left); Py_DECREF(right); #line 2165 "Python/bytecodes.c" b = res ? Py_True : Py_False; - #line 1526 "Python/executor_cases.c.h" + #line 1547 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = b; break; @@ -1534,13 +1555,13 @@ PyObject *b; #line 2169 "Python/bytecodes.c" int res = PySequence_Contains(right, left); - #line 1538 "Python/executor_cases.c.h" + #line 1559 "Python/executor_cases.c.h" Py_DECREF(left); Py_DECREF(right); #line 2171 "Python/bytecodes.c" if (res < 0) goto pop_2_error; b = (res ^ oparg) ? Py_True : Py_False; - #line 1544 "Python/executor_cases.c.h" + #line 1565 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = b; break; @@ -1553,7 +1574,7 @@ PyObject *match; #line 2176 "Python/bytecodes.c" if (check_except_star_type_valid(tstate, match_type) < 0) { - #line 1557 "Python/executor_cases.c.h" + #line 1578 "Python/executor_cases.c.h" Py_DECREF(exc_value); Py_DECREF(match_type); #line 2178 "Python/bytecodes.c" @@ -1564,7 +1585,7 @@ rest = NULL; int res = exception_group_match(exc_value, match_type, &match, &rest); - #line 1568 "Python/executor_cases.c.h" + #line 1589 "Python/executor_cases.c.h" Py_DECREF(exc_value); Py_DECREF(match_type); #line 2186 "Python/bytecodes.c" @@ -1576,7 +1597,7 @@ if (!Py_IsNone(match)) { PyErr_SetHandledException(match); } - #line 1580 "Python/executor_cases.c.h" + #line 1601 "Python/executor_cases.c.h" stack_pointer[-1] = match; stack_pointer[-2] = rest; break; @@ -1589,18 +1610,18 @@ #line 2197 "Python/bytecodes.c" assert(PyExceptionInstance_Check(left)); if (check_except_type_valid(tstate, right) < 0) { - #line 1593 "Python/executor_cases.c.h" + #line 1614 "Python/executor_cases.c.h" Py_DECREF(right); #line 2200 "Python/bytecodes.c" if (true) goto pop_1_error; } int res = PyErr_GivenExceptionMatches(left, right); - #line 1600 "Python/executor_cases.c.h" + #line 1621 "Python/executor_cases.c.h" Py_DECREF(right); #line 2205 "Python/bytecodes.c" b = res ? Py_True : Py_False; - #line 1604 "Python/executor_cases.c.h" + #line 1625 "Python/executor_cases.c.h" stack_pointer[-1] = b; break; } @@ -1614,7 +1635,7 @@ if (len_i < 0) goto error; len_o = PyLong_FromSsize_t(len_i); if (len_o == NULL) goto error; - #line 1618 "Python/executor_cases.c.h" + #line 1639 "Python/executor_cases.c.h" STACK_GROW(1); stack_pointer[-1] = len_o; break; @@ -1630,7 +1651,7 @@ // None on failure. assert(PyTuple_CheckExact(names)); attrs = match_class(tstate, subject, type, oparg, names); - #line 1634 "Python/executor_cases.c.h" + #line 1655 "Python/executor_cases.c.h" Py_DECREF(subject); Py_DECREF(type); Py_DECREF(names); @@ -1642,7 +1663,7 @@ if (_PyErr_Occurred(tstate)) goto pop_3_error; attrs = Py_None; // Failure! } - #line 1646 "Python/executor_cases.c.h" + #line 1667 "Python/executor_cases.c.h" STACK_SHRINK(2); stack_pointer[-1] = attrs; break; @@ -1654,7 +1675,7 @@ #line 2327 "Python/bytecodes.c" int match = Py_TYPE(subject)->tp_flags & Py_TPFLAGS_MAPPING; res = match ? Py_True : Py_False; - #line 1658 "Python/executor_cases.c.h" + #line 1679 "Python/executor_cases.c.h" STACK_GROW(1); stack_pointer[-1] = res; break; @@ -1666,7 +1687,7 @@ #line 2332 "Python/bytecodes.c" int match = Py_TYPE(subject)->tp_flags & Py_TPFLAGS_SEQUENCE; res = match ? Py_True : Py_False; - #line 1670 "Python/executor_cases.c.h" + #line 1691 "Python/executor_cases.c.h" STACK_GROW(1); stack_pointer[-1] = res; break; @@ -1680,7 +1701,7 @@ // On successful match, PUSH(values). Otherwise, PUSH(None). values_or_none = match_keys(tstate, subject, keys); if (values_or_none == NULL) goto error; - #line 1684 "Python/executor_cases.c.h" + #line 1705 "Python/executor_cases.c.h" STACK_GROW(1); stack_pointer[-1] = values_or_none; break; @@ -1692,11 +1713,11 @@ #line 2343 "Python/bytecodes.c" /* before: [obj]; after [getiter(obj)] */ iter = PyObject_GetIter(iterable); - #line 1696 "Python/executor_cases.c.h" + #line 1717 "Python/executor_cases.c.h" Py_DECREF(iterable); #line 2346 "Python/bytecodes.c" if (iter == NULL) goto pop_1_error; - #line 1700 "Python/executor_cases.c.h" + #line 1721 "Python/executor_cases.c.h" stack_pointer[-1] = iter; break; } @@ -1727,11 +1748,11 @@ if (iter == NULL) { goto error; } - #line 1731 "Python/executor_cases.c.h" + #line 1752 "Python/executor_cases.c.h" Py_DECREF(iterable); #line 2373 "Python/bytecodes.c" } - #line 1735 "Python/executor_cases.c.h" + #line 1756 "Python/executor_cases.c.h" stack_pointer[-1] = iter; break; } @@ -1762,7 +1783,7 @@ res = PyObject_Vectorcall(exit_func, stack + 1, 3 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL); if (res == NULL) goto error; - #line 1766 "Python/executor_cases.c.h" + #line 1787 "Python/executor_cases.c.h" STACK_GROW(1); stack_pointer[-1] = res; break; @@ -1781,7 +1802,7 @@ } assert(PyExceptionInstance_Check(new_exc)); exc_info->exc_value = Py_NewRef(new_exc); - #line 1785 "Python/executor_cases.c.h" + #line 1806 "Python/executor_cases.c.h" STACK_GROW(1); stack_pointer[-1] = new_exc; stack_pointer[-2] = prev_exc; @@ -1798,7 +1819,7 @@ Py_TYPE(should_be_none)->tp_name); goto error; } - #line 1802 "Python/executor_cases.c.h" + #line 1823 "Python/executor_cases.c.h" STACK_SHRINK(1); break; } @@ -1818,7 +1839,7 @@ func_obj->func_version = ((PyCodeObject *)codeobj)->co_version; func = (PyObject *)func_obj; - #line 1822 "Python/executor_cases.c.h" + #line 1843 "Python/executor_cases.c.h" stack_pointer[-1] = func; break; } @@ -1851,7 +1872,7 @@ default: Py_UNREACHABLE(); } - #line 1855 "Python/executor_cases.c.h" + #line 1876 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = func; break; @@ -1864,13 +1885,13 @@ PyObject *slice; #line 3491 "Python/bytecodes.c" slice = PySlice_New(start, stop, step); - #line 1868 "Python/executor_cases.c.h" + #line 1889 "Python/executor_cases.c.h" Py_DECREF(start); Py_DECREF(stop); Py_XDECREF(step); #line 3493 "Python/bytecodes.c" if (slice == NULL) { STACK_SHRINK(((oparg == 3) ? 1 : 0)); goto pop_2_error; } - #line 1874 "Python/executor_cases.c.h" + #line 1895 "Python/executor_cases.c.h" STACK_SHRINK(((oparg == 3) ? 1 : 0)); STACK_SHRINK(1); stack_pointer[-1] = slice; @@ -1887,7 +1908,7 @@ result = conv_fn(value); Py_DECREF(value); if (result == NULL) goto pop_1_error; - #line 1891 "Python/executor_cases.c.h" + #line 1912 "Python/executor_cases.c.h" stack_pointer[-1] = result; break; } @@ -1906,7 +1927,7 @@ else { res = value; } - #line 1910 "Python/executor_cases.c.h" + #line 1931 "Python/executor_cases.c.h" stack_pointer[-1] = res; break; } @@ -1920,7 +1941,7 @@ Py_DECREF(value); Py_DECREF(fmt_spec); if (res == NULL) goto pop_2_error; - #line 1924 "Python/executor_cases.c.h" + #line 1945 "Python/executor_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = res; break; @@ -1932,7 +1953,7 @@ #line 3526 "Python/bytecodes.c" assert(oparg > 0); top = Py_NewRef(bottom); - #line 1936 "Python/executor_cases.c.h" + #line 1957 "Python/executor_cases.c.h" STACK_GROW(1); stack_pointer[-1] = top; break; @@ -1943,7 +1964,7 @@ PyObject *bottom = stack_pointer[-(2 + (oparg-2))]; #line 3551 "Python/bytecodes.c" assert(oparg >= 2); - #line 1947 "Python/executor_cases.c.h" + #line 1968 "Python/executor_cases.c.h" stack_pointer[-1] = bottom; stack_pointer[-(2 + (oparg-2))] = top; break; diff --git a/Python/opcode_metadata.h b/Python/opcode_metadata.h index 6a42775e091208..ac3d800d8fe0fe 100644 --- a/Python/opcode_metadata.h +++ b/Python/opcode_metadata.h @@ -20,7 +20,7 @@ 0) #define EXIT_TRACE 300 -#define SET_IP 301 +#define SAVE_IP 301 #define _GUARD_BOTH_INT 302 #define _BINARY_OP_MULTIPLY_INT 303 #define _BINARY_OP_ADD_INT 304 @@ -1164,6 +1164,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[512] = { }; const struct opcode_macro_expansion _PyOpcode_macro_expansion[256] = { [NOP] = { .nuops = 1, .uops = { { NOP, 0, 0 } } }, + [LOAD_FAST_CHECK] = { .nuops = 1, .uops = { { LOAD_FAST_CHECK, 0, 0 } } }, [LOAD_FAST] = { .nuops = 1, .uops = { { LOAD_FAST, 0, 0 } } }, [LOAD_FAST_AND_CLEAR] = { .nuops = 1, .uops = { { LOAD_FAST_AND_CLEAR, 0, 0 } } }, [LOAD_CONST] = { .nuops = 1, .uops = { { LOAD_CONST, 0, 0 } } }, @@ -1218,6 +1219,7 @@ const struct opcode_macro_expansion _PyOpcode_macro_expansion[256] = { [LOAD_LOCALS] = { .nuops = 1, .uops = { { _LOAD_LOCALS, 0, 0 } } }, [LOAD_NAME] = { .nuops = 2, .uops = { { _LOAD_LOCALS, 0, 0 }, { _LOAD_FROM_DICT_OR_GLOBALS, 0, 0 } } }, [LOAD_FROM_DICT_OR_GLOBALS] = { .nuops = 1, .uops = { { _LOAD_FROM_DICT_OR_GLOBALS, 0, 0 } } }, + [DELETE_FAST] = { .nuops = 1, .uops = { { DELETE_FAST, 0, 0 } } }, [DELETE_DEREF] = { .nuops = 1, .uops = { { DELETE_DEREF, 0, 0 } } }, [LOAD_FROM_DICT_OR_DEREF] = { .nuops = 1, .uops = { { LOAD_FROM_DICT_OR_DEREF, 0, 0 } } }, [LOAD_DEREF] = { .nuops = 1, .uops = { { LOAD_DEREF, 0, 0 } } }, @@ -1266,7 +1268,7 @@ const struct opcode_macro_expansion _PyOpcode_macro_expansion[256] = { #ifdef Py_DEBUG const char * const _PyOpcode_uop_name[512] = { [300] = "EXIT_TRACE", - [301] = "SET_IP", + [301] = "SAVE_IP", [302] = "_GUARD_BOTH_INT", [303] = "_BINARY_OP_MULTIPLY_INT", [304] = "_BINARY_OP_ADD_INT", diff --git a/Python/optimizer.c b/Python/optimizer.c index b00825ade16e07..32f0b1477d203c 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -282,11 +282,6 @@ PyUnstable_Optimizer_NewCounter(void) ///////////////////// Experimental UOp Optimizer ///////////////////// -#ifdef Py_DEBUG - /* For debugging the interpreter: */ -# define LLTRACE 1 /* Low-level trace feature */ -#endif - static void uop_dealloc(_PyUOpExecutorObject *self) { PyObject_Free(self); @@ -308,60 +303,81 @@ translate_bytecode_to_trace( _PyUOpInstruction *trace, int max_length) { -#ifdef LLTRACE + int trace_length = 0; + +#ifdef Py_DEBUG char *uop_debug = Py_GETENV("PYTHONUOPSDEBUG"); int lltrace = 0; if (uop_debug != NULL && *uop_debug >= '0') { lltrace = *uop_debug - '0'; // TODO: Parse an int and all that } - if (lltrace >= 4) { - fprintf(stderr, - "Optimizing %s (%s:%d) at offset %ld\n", - PyUnicode_AsUTF8(code->co_qualname), - PyUnicode_AsUTF8(code->co_filename), - code->co_firstlineno, - (long)(instr - (_Py_CODEUNIT *)code->co_code_adaptive)); - } -#define ADD_TO_TRACE(OPCODE, OPERAND) \ - if (lltrace >= 2) { \ - const char *opname = (OPCODE) < 256 ? _PyOpcode_OpName[(OPCODE)] : _PyOpcode_uop_name[(OPCODE)]; \ - fprintf(stderr, " ADD_TO_TRACE(%s, %" PRIu64 ")\n", opname, (uint64_t)(OPERAND)); \ - } \ - trace[trace_length].opcode = (OPCODE); \ - trace[trace_length].operand = (OPERAND); \ - trace_length++; +#define DPRINTF(level, ...) \ + if (lltrace >= (level)) { fprintf(stderr, __VA_ARGS__); } #else -#define ADD_TO_TRACE(OPCODE, OPERAND) \ - trace[trace_length].opcode = (OPCODE); \ - trace[trace_length].operand = (OPERAND); \ - trace_length++; +#define DPRINTF(level, ...) #endif - int trace_length = 0; - // Always reserve space for one uop, plus SET_UP, plus EXIT_TRACE - while (trace_length + 3 <= max_length) { +#define ADD_TO_TRACE(OPCODE, OPERAND) \ + DPRINTF(2, \ + " ADD_TO_TRACE(%s, %" PRIu64 ")\n", \ + (OPCODE) < 256 ? _PyOpcode_OpName[(OPCODE)] : _PyOpcode_uop_name[(OPCODE)], \ + (uint64_t)(OPERAND)); \ + assert(trace_length < max_length); \ + trace[trace_length].opcode = (OPCODE); \ + trace[trace_length].operand = (OPERAND); \ + trace_length++; + + DPRINTF(4, + "Optimizing %s (%s:%d) at offset %ld\n", + PyUnicode_AsUTF8(code->co_qualname), + PyUnicode_AsUTF8(code->co_filename), + code->co_firstlineno, + (long)(instr - (_Py_CODEUNIT *)code->co_code_adaptive)); + + for (;;) { + ADD_TO_TRACE(SAVE_IP, (int)(instr - (_Py_CODEUNIT *)code->co_code_adaptive)); int opcode = instr->op.code; uint64_t operand = instr->op.arg; switch (opcode) { case LOAD_FAST_LOAD_FAST: + case STORE_FAST_LOAD_FAST: + case STORE_FAST_STORE_FAST: { - // Reserve space for two uops (+ SETUP + EXIT_TRACE) + // Reserve space for two uops (+ SAVE_IP + EXIT_TRACE) if (trace_length + 4 > max_length) { + DPRINTF(1, "Ran out of space for LOAD_FAST_LOAD_FAST\n"); goto done; } uint64_t oparg1 = operand >> 4; uint64_t oparg2 = operand & 15; - ADD_TO_TRACE(LOAD_FAST, oparg1); - ADD_TO_TRACE(LOAD_FAST, oparg2); + switch (opcode) { + case LOAD_FAST_LOAD_FAST: + ADD_TO_TRACE(LOAD_FAST, oparg1); + ADD_TO_TRACE(LOAD_FAST, oparg2); + break; + case STORE_FAST_LOAD_FAST: + ADD_TO_TRACE(STORE_FAST, oparg1); + ADD_TO_TRACE(LOAD_FAST, oparg2); + break; + case STORE_FAST_STORE_FAST: + ADD_TO_TRACE(STORE_FAST, oparg1); + ADD_TO_TRACE(STORE_FAST, oparg2); + break; + default: + Py_FatalError("Missing case"); + } break; } default: { const struct opcode_macro_expansion *expansion = &_PyOpcode_macro_expansion[opcode]; if (expansion->nuops > 0) { - // Reserve space for nuops (+ SETUP + EXIT_TRACE) + // Reserve space for nuops (+ SAVE_IP + EXIT_TRACE) int nuops = expansion->nuops; if (trace_length + nuops + 2 > max_length) { + DPRINTF(1, + "Ran out of space for %s\n", + opcode < 256 ? _PyOpcode_OpName[opcode] : _PyOpcode_uop_name[opcode]); goto done; } for (int i = 0; i < nuops; i++) { @@ -387,49 +403,45 @@ translate_bytecode_to_trace( Py_FatalError("garbled expansion"); } ADD_TO_TRACE(expansion->uops[i].uop, operand); - assert(expansion->uops[0].size == 0); // TODO } break; } - // fprintf(stderr, "Unsupported opcode %d\n", opcode); - goto done; // Break out of while loop + DPRINTF(2, + "Unsupported opcode %s\n", + opcode < 256 ? _PyOpcode_OpName[opcode] : _PyOpcode_uop_name[opcode]); + goto done; // Break out of loop } } instr++; // Add cache size for opcode instr += _PyOpcode_Caches[_PyOpcode_Deopt[opcode]]; - ADD_TO_TRACE(SET_IP, (int)(instr - (_Py_CODEUNIT *)code->co_code_adaptive)); } + done: - if (trace_length > 0) { + // Skip short traces like SAVE_IP, LOAD_FAST, SAVE_IP, EXIT_TRACE + if (trace_length > 3) { ADD_TO_TRACE(EXIT_TRACE, 0); -#ifdef LLTRACE - if (lltrace >= 1) { - fprintf(stderr, - "Created a trace for %s (%s:%d) at offset %ld -- length %d\n", - PyUnicode_AsUTF8(code->co_qualname), - PyUnicode_AsUTF8(code->co_filename), - code->co_firstlineno, - (long)(instr - (_Py_CODEUNIT *)code->co_code_adaptive), - trace_length); - } -#endif + DPRINTF(1, + "Created a trace for %s (%s:%d) at offset %ld -- length %d\n", + PyUnicode_AsUTF8(code->co_qualname), + PyUnicode_AsUTF8(code->co_filename), + code->co_firstlineno, + (long)(instr - (_Py_CODEUNIT *)code->co_code_adaptive), + trace_length); + return trace_length; } else { -#ifdef LLTRACE - if (lltrace >= 4) { - fprintf(stderr, - "No trace for %s (%s:%d) at offset %ld\n", - PyUnicode_AsUTF8(code->co_qualname), - PyUnicode_AsUTF8(code->co_filename), - code->co_firstlineno, - (long)(instr - (_Py_CODEUNIT *)code->co_code_adaptive)); - } -#endif + DPRINTF(4, + "No trace for %s (%s:%d) at offset %ld\n", + PyUnicode_AsUTF8(code->co_qualname), + PyUnicode_AsUTF8(code->co_filename), + code->co_firstlineno, + (long)(instr - (_Py_CODEUNIT *)code->co_code_adaptive)); } - return trace_length; + return 0; #undef ADD_TO_TRACE +#undef DPRINTF } static int diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index 38be98034e0d83..657dfa93fd537d 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -308,8 +308,7 @@ class ActiveCacheEffect: FORBIDDEN_NAMES_IN_UOPS = ( - "resume_with_error", # Proxy for "goto", which isn't an IDENTIFIER - "unbound_local_error", + "resume_with_error", "kwnames", "next_instr", "oparg1", # Proxy for super-instructions like LOAD_FAST_LOAD_FAST @@ -401,20 +400,25 @@ def __init__(self, inst: parser.InstDef): def is_viable_uop(self) -> bool: """Whether this instruction is viable as a uop.""" if self.always_exits: + # print(f"Skipping {self.name} because it always exits") return False if self.instr_flags.HAS_ARG_FLAG: # If the instruction uses oparg, it cannot use any caches if self.active_caches: + # print(f"Skipping {self.name} because it uses oparg and caches") return False else: # If it doesn't use oparg, it can have one cache entry if len(self.active_caches) > 1: + # print(f"Skipping {self.name} because it has >1 cache entries") return False + res = True for forbidden in FORBIDDEN_NAMES_IN_UOPS: # TODO: Don't check in '#ifdef ENABLE_SPECIALIZATION' regions if variable_used(self.inst, forbidden): - return False - return True + # print(f"Skipping {self.name} because it uses {forbidden}") + res = False + return res def write(self, out: Formatter, tier: Tiers = TIER_ONE) -> None: """Write one instruction, sans prologue and epilogue.""" @@ -1323,7 +1327,7 @@ def add(name: str) -> None: self.out.emit(make_text(name, counter)) counter += 1 add("EXIT_TRACE") - add("SET_IP") + add("SAVE_IP") for instr in self.instrs.values(): if instr.kind == "op" and instr.is_viable_uop(): add(instr.name) From bf06c6825cadeda54a9c0848fa51463a0e0b2cf8 Mon Sep 17 00:00:00 2001 From: Charlie Zhao Date: Tue, 4 Jul 2023 04:10:01 +0800 Subject: [PATCH 02/39] gh-106078: Move `context template` to decimal module global state (#106346) --- Modules/_decimal/_decimal.c | 59 +++++++++++---------- Tools/c-analyzer/cpython/globals-to-fix.tsv | 3 -- 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index da623725003428..6da5095b91018a 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -49,6 +49,14 @@ typedef struct { /* Top level Exception; inherits from ArithmeticError */ PyObject *DecimalException; + + /* Template for creating new thread contexts, calling Context() without + * arguments and initializing the module_context on first access. */ + PyObject *default_context_template; + + /* Basic and extended context templates */ + PyObject *basic_context_template; + PyObject *extended_context_template; } decimal_state; static decimal_state global_state; @@ -147,14 +155,6 @@ static PyDecContextObject *cached_context = NULL; static PyObject *current_context_var = NULL; #endif -/* Template for creating new thread contexts, calling Context() without - * arguments and initializing the module_context on first access. */ -static PyObject *default_context_template = NULL; -/* Basic and extended context templates */ -static PyObject *basic_context_template = NULL; -static PyObject *extended_context_template = NULL; - - /* Error codes for functions that return signals or conditions */ #define DEC_INVALID_SIGNALS (MPD_Max_status+1U) #define DEC_ERR_OCCURRED (DEC_INVALID_SIGNALS<<1) @@ -1272,8 +1272,8 @@ context_new(PyTypeObject *type, PyObject *args UNUSED, PyObject *kwds UNUSED) ctx = CTX(self); - if (default_context_template) { - *ctx = *CTX(default_context_template); + if (state->default_context_template) { + *ctx = *CTX(state->default_context_template); } else { *ctx = dflt_ctx; @@ -1576,7 +1576,7 @@ current_context_from_dict(void) } /* Set up a new thread local context. */ - tl_context = context_copy(default_context_template, NULL); + tl_context = context_copy(state->default_context_template, NULL); if (tl_context == NULL) { return NULL; } @@ -1649,9 +1649,9 @@ PyDec_SetCurrentContext(PyObject *self UNUSED, PyObject *v) /* If the new context is one of the templates, make a copy. * This is the current behavior of decimal.py. */ - if (v == default_context_template || - v == basic_context_template || - v == extended_context_template) { + if (v == state->default_context_template || + v == state->basic_context_template || + v == state->extended_context_template) { v = context_copy(v, NULL); if (v == NULL) { return NULL; @@ -1675,7 +1675,8 @@ PyDec_SetCurrentContext(PyObject *self UNUSED, PyObject *v) static PyObject * init_current_context(void) { - PyObject *tl_context = context_copy(default_context_template, NULL); + decimal_state *state = GLOBAL_STATE(); + PyObject *tl_context = context_copy(state->default_context_template, NULL); if (tl_context == NULL) { return NULL; } @@ -1730,9 +1731,9 @@ PyDec_SetCurrentContext(PyObject *self UNUSED, PyObject *v) /* If the new context is one of the templates, make a copy. * This is the current behavior of decimal.py. */ - if (v == default_context_template || - v == basic_context_template || - v == extended_context_template) { + if (v == state->default_context_template || + v == state->basic_context_template || + v == state->extended_context_template) { v = context_copy(v, NULL); if (v == NULL) { return NULL; @@ -5980,10 +5981,10 @@ PyInit__decimal(void) /* Init default context template first */ - ASSIGN_PTR(default_context_template, + ASSIGN_PTR(state->default_context_template, PyObject_CallObject((PyObject *)state->PyDecContext_Type, NULL)); CHECK_INT(PyModule_AddObject(m, "DefaultContext", - Py_NewRef(default_context_template))); + Py_NewRef(state->default_context_template))); #ifndef WITH_DECIMAL_CONTEXTVAR ASSIGN_PTR(tls_context_key, PyUnicode_FromString("___DECIMAL_CTX__")); @@ -5995,18 +5996,18 @@ PyInit__decimal(void) CHECK_INT(PyModule_AddObject(m, "HAVE_THREADS", Py_NewRef(Py_True))); /* Init basic context template */ - ASSIGN_PTR(basic_context_template, + ASSIGN_PTR(state->basic_context_template, PyObject_CallObject((PyObject *)state->PyDecContext_Type, NULL)); - init_basic_context(basic_context_template); + init_basic_context(state->basic_context_template); CHECK_INT(PyModule_AddObject(m, "BasicContext", - Py_NewRef(basic_context_template))); + Py_NewRef(state->basic_context_template))); /* Init extended context template */ - ASSIGN_PTR(extended_context_template, + ASSIGN_PTR(state->extended_context_template, PyObject_CallObject((PyObject *)state->PyDecContext_Type, NULL)); - init_extended_context(extended_context_template); + init_extended_context(state->extended_context_template); CHECK_INT(PyModule_AddObject(m, "ExtendedContext", - Py_NewRef(extended_context_template))); + Py_NewRef(state->extended_context_template))); /* Init mpd_ssize_t constants */ @@ -6046,14 +6047,14 @@ PyInit__decimal(void) Py_CLEAR(MutableMapping); /* GCOV_NOT_REACHED */ Py_CLEAR(SignalTuple); /* GCOV_NOT_REACHED */ Py_CLEAR(state->DecimalTuple); /* GCOV_NOT_REACHED */ - Py_CLEAR(default_context_template); /* GCOV_NOT_REACHED */ + Py_CLEAR(state->default_context_template); /* GCOV_NOT_REACHED */ #ifndef WITH_DECIMAL_CONTEXTVAR Py_CLEAR(tls_context_key); /* GCOV_NOT_REACHED */ #else Py_CLEAR(current_context_var); /* GCOV_NOT_REACHED */ #endif - Py_CLEAR(basic_context_template); /* GCOV_NOT_REACHED */ - Py_CLEAR(extended_context_template); /* GCOV_NOT_REACHED */ + Py_CLEAR(state->basic_context_template); /* GCOV_NOT_REACHED */ + Py_CLEAR(state->extended_context_template); /* GCOV_NOT_REACHED */ Py_CLEAR(m); /* GCOV_NOT_REACHED */ return NULL; /* GCOV_NOT_REACHED */ diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 8fdc54df2b0722..ad3d9b6513d69c 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -422,10 +422,7 @@ Modules/_datetimemodule.c - us_per_day - Modules/_datetimemodule.c - us_per_week - Modules/_datetimemodule.c - seconds_per_day - Modules/_decimal/_decimal.c - global_state - -Modules/_decimal/_decimal.c - basic_context_template - Modules/_decimal/_decimal.c - current_context_var - -Modules/_decimal/_decimal.c - default_context_template - -Modules/_decimal/_decimal.c - extended_context_template - Modules/_decimal/_decimal.c - round_map - Modules/_decimal/_decimal.c - Rational - Modules/_decimal/_decimal.c - SignalTuple - From 7f4c8121db62a9f72f00f2d9f73381e82f289581 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 3 Jul 2023 22:16:50 +0200 Subject: [PATCH 03/39] gh-106368: Increase Argument Clinic test coverage (#106369) Add tests for 'self' and 'defining_class' converter requirements. --- Lib/test/test_clinic.py | 57 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index fab2d2bb0bdbe4..51d2ac972752fd 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -829,6 +829,63 @@ def test_kwarg_splats_disallowed_in_function_call_annotations(self): out = self.parse_function_should_fail(fn) self.assertEqual(out, expected_error_msg) + def test_self_param_placement(self): + expected_error_msg = ( + "Error on line 0:\n" + "A 'self' parameter, if specified, must be the very first thing " + "in the parameter block.\n" + ) + block = """ + module foo + foo.func + a: int + self: self(type="PyObject *") + """ + out = self.parse_function_should_fail(block) + self.assertEqual(out, expected_error_msg) + + def test_self_param_cannot_be_optional(self): + expected_error_msg = ( + "Error on line 0:\n" + "A 'self' parameter cannot be marked optional.\n" + ) + block = """ + module foo + foo.func + self: self(type="PyObject *") = None + """ + out = self.parse_function_should_fail(block) + self.assertEqual(out, expected_error_msg) + + def test_defining_class_param_placement(self): + expected_error_msg = ( + "Error on line 0:\n" + "A 'defining_class' parameter, if specified, must either be the " + "first thing in the parameter block, or come just after 'self'.\n" + ) + block = """ + module foo + foo.func + self: self(type="PyObject *") + a: int + cls: defining_class + """ + out = self.parse_function_should_fail(block) + self.assertEqual(out, expected_error_msg) + + def test_defining_class_param_cannot_be_optional(self): + expected_error_msg = ( + "Error on line 0:\n" + "A 'defining_class' parameter cannot be marked optional.\n" + ) + block = """ + module foo + foo.func + cls: defining_class(type="PyObject *") = None + """ + out = self.parse_function_should_fail(block) + self.assertEqual(out, expected_error_msg) + def test_unused_param(self): block = self.parse(""" module foo From e5862113dde7a66b08f1ece542a3cfaf0a3d9080 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 3 Jul 2023 21:28:27 +0100 Subject: [PATCH 04/39] GH-104584: Fix ENTER_EXECUTOR (GH-106141) * Check eval-breaker in ENTER_EXECUTOR. * Make sure that frame->prev_instr is set before entering executor. --- Include/internal/pycore_ceval.h | 2 + Python/bytecodes.c | 19 +- Python/ceval.c | 70 +------ Python/ceval_gil.c | 61 +++++- Python/ceval_macros.h | 4 +- Python/executor_cases.c.h | 44 ++--- Python/generated_cases.c.h | 339 ++++++++++++++++---------------- Python/opcode_metadata.h | 2 +- 8 files changed, 272 insertions(+), 269 deletions(-) diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index 9e9b523e7c2222..46bc18cff86d5a 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -152,6 +152,8 @@ extern struct _PyInterpreterFrame* _PyEval_GetFrame(void); extern PyObject* _Py_MakeCoro(PyFunctionObject *func); +/* Handle signals, pending calls, GIL drop request + and asynchronous exception */ extern int _Py_HandlePending(PyThreadState *tstate); diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 8aab7ebc5cf0cb..f69ac2beef4c20 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -141,8 +141,8 @@ dummy_func( ERROR_IF(err, error); next_instr--; } - else if (_Py_atomic_load_relaxed_int32(&tstate->interp->ceval.eval_breaker) && oparg < 2) { - goto handle_eval_breaker; + else if (oparg < 2) { + CHECK_EVAL_BREAKER(); } } @@ -158,6 +158,9 @@ dummy_func( next_instr--; } else { + if (oparg < 2) { + CHECK_EVAL_BREAKER(); + } _PyFrame_SetStackPointer(frame, stack_pointer); int err = _Py_call_instrumentation( tstate, oparg > 0, frame, next_instr-1); @@ -168,9 +171,6 @@ dummy_func( next_instr = frame->prev_instr; DISPATCH(); } - if (_Py_atomic_load_relaxed_int32(&tstate->interp->ceval.eval_breaker) && oparg < 2) { - goto handle_eval_breaker; - } } } @@ -2223,6 +2223,7 @@ dummy_func( } inst(JUMP_BACKWARD, (--)) { + CHECK_EVAL_BREAKER(); _Py_CODEUNIT *here = next_instr - 1; assert(oparg <= INSTR_OFFSET()); JUMPBY(1-oparg); @@ -2240,7 +2241,6 @@ dummy_func( goto resume_frame; } #endif /* ENABLE_SPECIALIZATION */ - CHECK_EVAL_BREAKER(); } pseudo(JUMP) = { @@ -2254,8 +2254,13 @@ dummy_func( }; inst(ENTER_EXECUTOR, (--)) { + CHECK_EVAL_BREAKER(); + PyCodeObject *code = _PyFrame_GetCode(frame); _PyExecutorObject *executor = (_PyExecutorObject *)code->co_executors->executors[oparg&255]; + int original_oparg = executor->vm_data.oparg | (oparg & 0xfffff00); + JUMPBY(1-original_oparg); + frame->prev_instr = next_instr - 1; Py_INCREF(executor); frame = executor->execute(executor, frame, stack_pointer); if (frame == NULL) { @@ -3570,8 +3575,8 @@ dummy_func( } inst(INSTRUMENTED_JUMP_BACKWARD, ( -- )) { - INSTRUMENTED_JUMP(next_instr-1, next_instr+1-oparg, PY_MONITORING_EVENT_JUMP); CHECK_EVAL_BREAKER(); + INSTRUMENTED_JUMP(next_instr-1, next_instr+1-oparg, PY_MONITORING_EVENT_JUMP); } inst(INSTRUMENTED_POP_JUMP_IF_TRUE, ( -- )) { diff --git a/Python/ceval.c b/Python/ceval.c index 6ce1a6a636ed71..9bcb83f9c993cf 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -763,68 +763,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int DISPATCH(); -handle_eval_breaker: - - /* Do periodic things, like check for signals and async I/0. - * We need to do reasonably frequently, but not too frequently. - * All loops should include a check of the eval breaker. - * We also check on return from any builtin function. - * - * ## More Details ### - * - * The eval loop (this function) normally executes the instructions - * of a code object sequentially. However, the runtime supports a - * number of out-of-band execution scenarios that may pause that - * sequential execution long enough to do that out-of-band work - * in the current thread using the current PyThreadState. - * - * The scenarios include: - * - * - cyclic garbage collection - * - GIL drop requests - * - "async" exceptions - * - "pending calls" (some only in the main thread) - * - signal handling (only in the main thread) - * - * When the need for one of the above is detected, the eval loop - * pauses long enough to handle the detected case. Then, if doing - * so didn't trigger an exception, the eval loop resumes executing - * the sequential instructions. - * - * To make this work, the eval loop periodically checks if any - * of the above needs to happen. The individual checks can be - * expensive if computed each time, so a while back we switched - * to using pre-computed, per-interpreter variables for the checks, - * and later consolidated that to a single "eval breaker" variable - * (now a PyInterpreterState field). - * - * For the longest time, the eval breaker check would happen - * frequently, every 5 or so times through the loop, regardless - * of what instruction ran last or what would run next. Then, in - * early 2021 (gh-18334, commit 4958f5d), we switched to checking - * the eval breaker less frequently, by hard-coding the check to - * specific places in the eval loop (e.g. certain instructions). - * The intent then was to check after returning from calls - * and on the back edges of loops. - * - * In addition to being more efficient, that approach keeps - * the eval loop from running arbitrary code between instructions - * that don't handle that well. (See gh-74174.) - * - * Currently, the eval breaker check happens here at the - * "handle_eval_breaker" label. Some instructions come here - * explicitly (goto) and some indirectly. Notably, the check - * happens on back edges in the control flow graph, which - * pretty much applies to all loops and most calls. - * (See bytecodes.c for exact information.) - * - * One consequence of this approach is that it might not be obvious - * how to force any specific thread to pick up the eval breaker, - * or for any specific thread to not pick it up. Mostly this - * involves judicious uses of locks and careful ordering of code, - * while avoiding code that might trigger the eval breaker - * until so desired. - */ if (_Py_HandlePending(tstate) != 0) { goto error; } @@ -2796,13 +2734,7 @@ _PyUopExecute(_PyExecutorObject *executor, _PyInterpreterFrame *frame, PyObject PyThreadState *tstate = _PyThreadState_GET(); _PyUOpExecutorObject *self = (_PyUOpExecutorObject *)executor; - // Equivalent to CHECK_EVAL_BREAKER() - _Py_CHECK_EMSCRIPTEN_SIGNALS_PERIODICALLY(); - if (_Py_atomic_load_relaxed_int32(&tstate->interp->ceval.eval_breaker)) { - if (_Py_HandlePending(tstate) != 0) { - goto error; - } - } + CHECK_EVAL_BREAKER(); OBJECT_STAT_INC(optimization_traces_executed); _Py_CODEUNIT *ip_offset = (_Py_CODEUNIT *)_PyFrame_GetCode(frame)->co_code_adaptive; diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c index c6b1f9ef689ee1..7c9ad07cc7207b 100644 --- a/Python/ceval_gil.c +++ b/Python/ceval_gil.c @@ -1052,8 +1052,65 @@ _PyEval_FiniState(struct _ceval_state *ceval) } } -/* Handle signals, pending calls, GIL drop request - and asynchronous exception */ + +/* Do periodic things, like check for signals and async I/0. +* We need to do reasonably frequently, but not too frequently. +* All loops should include a check of the eval breaker. +* We also check on return from any builtin function. +* +* ## More Details ### +* +* The eval loop (this function) normally executes the instructions +* of a code object sequentially. However, the runtime supports a +* number of out-of-band execution scenarios that may pause that +* sequential execution long enough to do that out-of-band work +* in the current thread using the current PyThreadState. +* +* The scenarios include: +* +* - cyclic garbage collection +* - GIL drop requests +* - "async" exceptions +* - "pending calls" (some only in the main thread) +* - signal handling (only in the main thread) +* +* When the need for one of the above is detected, the eval loop +* pauses long enough to handle the detected case. Then, if doing +* so didn't trigger an exception, the eval loop resumes executing +* the sequential instructions. +* +* To make this work, the eval loop periodically checks if any +* of the above needs to happen. The individual checks can be +* expensive if computed each time, so a while back we switched +* to using pre-computed, per-interpreter variables for the checks, +* and later consolidated that to a single "eval breaker" variable +* (now a PyInterpreterState field). +* +* For the longest time, the eval breaker check would happen +* frequently, every 5 or so times through the loop, regardless +* of what instruction ran last or what would run next. Then, in +* early 2021 (gh-18334, commit 4958f5d), we switched to checking +* the eval breaker less frequently, by hard-coding the check to +* specific places in the eval loop (e.g. certain instructions). +* The intent then was to check after returning from calls +* and on the back edges of loops. +* +* In addition to being more efficient, that approach keeps +* the eval loop from running arbitrary code between instructions +* that don't handle that well. (See gh-74174.) +* +* Currently, the eval breaker check happens on back edges in +* the control flow graph, which pretty much applies to all loops, +* and most calls. +* (See bytecodes.c for exact information.) +* +* One consequence of this approach is that it might not be obvious +* how to force any specific thread to pick up the eval breaker, +* or for any specific thread to not pick it up. Mostly this +* involves judicious uses of locks and careful ordering of code, +* while avoiding code that might trigger the eval breaker +* until so desired. +*/ int _Py_HandlePending(PyThreadState *tstate) { diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h index 0d41ef5a14cef4..72800aaaaa2ac4 100644 --- a/Python/ceval_macros.h +++ b/Python/ceval_macros.h @@ -117,7 +117,9 @@ #define CHECK_EVAL_BREAKER() \ _Py_CHECK_EMSCRIPTEN_SIGNALS_PERIODICALLY(); \ if (_Py_atomic_load_relaxed_int32(&tstate->interp->ceval.eval_breaker)) { \ - goto handle_eval_breaker; \ + if (_Py_HandlePending(tstate) != 0) { \ + goto error; \ + } \ } diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index d1e0443db613d2..39a4490e51c24a 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1629,7 +1629,7 @@ case GET_LEN: { PyObject *obj = stack_pointer[-1]; PyObject *len_o; - #line 2304 "Python/bytecodes.c" + #line 2309 "Python/bytecodes.c" // PUSH(len(TOS)) Py_ssize_t len_i = PyObject_Length(obj); if (len_i < 0) goto error; @@ -1646,7 +1646,7 @@ PyObject *type = stack_pointer[-2]; PyObject *subject = stack_pointer[-3]; PyObject *attrs; - #line 2312 "Python/bytecodes.c" + #line 2317 "Python/bytecodes.c" // Pop TOS and TOS1. Set TOS to a tuple of attributes on success, or // None on failure. assert(PyTuple_CheckExact(names)); @@ -1655,7 +1655,7 @@ Py_DECREF(subject); Py_DECREF(type); Py_DECREF(names); - #line 2317 "Python/bytecodes.c" + #line 2322 "Python/bytecodes.c" if (attrs) { assert(PyTuple_CheckExact(attrs)); // Success! } @@ -1672,7 +1672,7 @@ case MATCH_MAPPING: { PyObject *subject = stack_pointer[-1]; PyObject *res; - #line 2327 "Python/bytecodes.c" + #line 2332 "Python/bytecodes.c" int match = Py_TYPE(subject)->tp_flags & Py_TPFLAGS_MAPPING; res = match ? Py_True : Py_False; #line 1679 "Python/executor_cases.c.h" @@ -1684,7 +1684,7 @@ case MATCH_SEQUENCE: { PyObject *subject = stack_pointer[-1]; PyObject *res; - #line 2332 "Python/bytecodes.c" + #line 2337 "Python/bytecodes.c" int match = Py_TYPE(subject)->tp_flags & Py_TPFLAGS_SEQUENCE; res = match ? Py_True : Py_False; #line 1691 "Python/executor_cases.c.h" @@ -1697,7 +1697,7 @@ PyObject *keys = stack_pointer[-1]; PyObject *subject = stack_pointer[-2]; PyObject *values_or_none; - #line 2337 "Python/bytecodes.c" + #line 2342 "Python/bytecodes.c" // On successful match, PUSH(values). Otherwise, PUSH(None). values_or_none = match_keys(tstate, subject, keys); if (values_or_none == NULL) goto error; @@ -1710,12 +1710,12 @@ case GET_ITER: { PyObject *iterable = stack_pointer[-1]; PyObject *iter; - #line 2343 "Python/bytecodes.c" + #line 2348 "Python/bytecodes.c" /* before: [obj]; after [getiter(obj)] */ iter = PyObject_GetIter(iterable); #line 1717 "Python/executor_cases.c.h" Py_DECREF(iterable); - #line 2346 "Python/bytecodes.c" + #line 2351 "Python/bytecodes.c" if (iter == NULL) goto pop_1_error; #line 1721 "Python/executor_cases.c.h" stack_pointer[-1] = iter; @@ -1725,7 +1725,7 @@ case GET_YIELD_FROM_ITER: { PyObject *iterable = stack_pointer[-1]; PyObject *iter; - #line 2350 "Python/bytecodes.c" + #line 2355 "Python/bytecodes.c" /* before: [obj]; after [getiter(obj)] */ if (PyCoro_CheckExact(iterable)) { /* `iterable` is a coroutine */ @@ -1750,7 +1750,7 @@ } #line 1752 "Python/executor_cases.c.h" Py_DECREF(iterable); - #line 2373 "Python/bytecodes.c" + #line 2378 "Python/bytecodes.c" } #line 1756 "Python/executor_cases.c.h" stack_pointer[-1] = iter; @@ -1762,7 +1762,7 @@ PyObject *lasti = stack_pointer[-3]; PyObject *exit_func = stack_pointer[-4]; PyObject *res; - #line 2605 "Python/bytecodes.c" + #line 2610 "Python/bytecodes.c" /* At the top of the stack are 4 values: - val: TOP = exc_info() - unused: SECOND = previous exception @@ -1792,7 +1792,7 @@ case PUSH_EXC_INFO: { PyObject *new_exc = stack_pointer[-1]; PyObject *prev_exc; - #line 2644 "Python/bytecodes.c" + #line 2649 "Python/bytecodes.c" _PyErr_StackItem *exc_info = tstate->exc_info; if (exc_info->exc_value != NULL) { prev_exc = exc_info->exc_value; @@ -1811,7 +1811,7 @@ case EXIT_INIT_CHECK: { PyObject *should_be_none = stack_pointer[-1]; - #line 3013 "Python/bytecodes.c" + #line 3018 "Python/bytecodes.c" assert(STACK_LEVEL() == 2); if (should_be_none != Py_None) { PyErr_Format(PyExc_TypeError, @@ -1827,7 +1827,7 @@ case MAKE_FUNCTION: { PyObject *codeobj = stack_pointer[-1]; PyObject *func; - #line 3427 "Python/bytecodes.c" + #line 3432 "Python/bytecodes.c" PyFunctionObject *func_obj = (PyFunctionObject *) PyFunction_New(codeobj, GLOBALS()); @@ -1847,7 +1847,7 @@ case SET_FUNCTION_ATTRIBUTE: { PyObject *func = stack_pointer[-1]; PyObject *attr = stack_pointer[-2]; - #line 3441 "Python/bytecodes.c" + #line 3446 "Python/bytecodes.c" assert(PyFunction_Check(func)); PyFunctionObject *func_obj = (PyFunctionObject *)func; switch(oparg) { @@ -1883,13 +1883,13 @@ PyObject *stop = stack_pointer[-(1 + ((oparg == 3) ? 1 : 0))]; PyObject *start = stack_pointer[-(2 + ((oparg == 3) ? 1 : 0))]; PyObject *slice; - #line 3491 "Python/bytecodes.c" + #line 3496 "Python/bytecodes.c" slice = PySlice_New(start, stop, step); #line 1889 "Python/executor_cases.c.h" Py_DECREF(start); Py_DECREF(stop); Py_XDECREF(step); - #line 3493 "Python/bytecodes.c" + #line 3498 "Python/bytecodes.c" if (slice == NULL) { STACK_SHRINK(((oparg == 3) ? 1 : 0)); goto pop_2_error; } #line 1895 "Python/executor_cases.c.h" STACK_SHRINK(((oparg == 3) ? 1 : 0)); @@ -1901,7 +1901,7 @@ case CONVERT_VALUE: { PyObject *value = stack_pointer[-1]; PyObject *result; - #line 3497 "Python/bytecodes.c" + #line 3502 "Python/bytecodes.c" convertion_func_ptr conv_fn; assert(oparg >= FVC_STR && oparg <= FVC_ASCII); conv_fn = CONVERSION_FUNCTIONS[oparg]; @@ -1916,7 +1916,7 @@ case FORMAT_SIMPLE: { PyObject *value = stack_pointer[-1]; PyObject *res; - #line 3506 "Python/bytecodes.c" + #line 3511 "Python/bytecodes.c" /* If value is a unicode object, then we know the result * of format(value) is value itself. */ if (!PyUnicode_CheckExact(value)) { @@ -1936,7 +1936,7 @@ PyObject *fmt_spec = stack_pointer[-1]; PyObject *value = stack_pointer[-2]; PyObject *res; - #line 3519 "Python/bytecodes.c" + #line 3524 "Python/bytecodes.c" res = PyObject_Format(value, fmt_spec); Py_DECREF(value); Py_DECREF(fmt_spec); @@ -1950,7 +1950,7 @@ case COPY: { PyObject *bottom = stack_pointer[-(1 + (oparg-1))]; PyObject *top; - #line 3526 "Python/bytecodes.c" + #line 3531 "Python/bytecodes.c" assert(oparg > 0); top = Py_NewRef(bottom); #line 1957 "Python/executor_cases.c.h" @@ -1962,7 +1962,7 @@ case SWAP: { PyObject *top = stack_pointer[-1]; PyObject *bottom = stack_pointer[-(2 + (oparg-2))]; - #line 3551 "Python/bytecodes.c" + #line 3556 "Python/bytecodes.c" assert(oparg >= 2); #line 1968 "Python/executor_cases.c.h" stack_pointer[-1] = bottom; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 21cd2d0126ab30..eb2422943984b1 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -17,8 +17,8 @@ if (err) goto error; next_instr--; } - else if (_Py_atomic_load_relaxed_int32(&tstate->interp->ceval.eval_breaker) && oparg < 2) { - goto handle_eval_breaker; + else if (oparg < 2) { + CHECK_EVAL_BREAKER(); } #line 24 "Python/generated_cases.c.h" DISPATCH(); @@ -37,6 +37,9 @@ next_instr--; } else { + if (oparg < 2) { + CHECK_EVAL_BREAKER(); + } _PyFrame_SetStackPointer(frame, stack_pointer); int err = _Py_call_instrumentation( tstate, oparg > 0, frame, next_instr-1); @@ -47,9 +50,6 @@ next_instr = frame->prev_instr; DISPATCH(); } - if (_Py_atomic_load_relaxed_int32(&tstate->interp->ceval.eval_breaker) && oparg < 2) { - goto handle_eval_breaker; - } } #line 55 "Python/generated_cases.c.h" DISPATCH(); @@ -3182,6 +3182,7 @@ TARGET(JUMP_BACKWARD) { #line 2226 "Python/bytecodes.c" + CHECK_EVAL_BREAKER(); _Py_CODEUNIT *here = next_instr - 1; assert(oparg <= INSTR_OFFSET()); JUMPBY(1-oparg); @@ -3199,15 +3200,19 @@ goto resume_frame; } #endif /* ENABLE_SPECIALIZATION */ - #line 3203 "Python/generated_cases.c.h" - CHECK_EVAL_BREAKER(); + #line 3204 "Python/generated_cases.c.h" DISPATCH(); } TARGET(ENTER_EXECUTOR) { #line 2257 "Python/bytecodes.c" + CHECK_EVAL_BREAKER(); + PyCodeObject *code = _PyFrame_GetCode(frame); _PyExecutorObject *executor = (_PyExecutorObject *)code->co_executors->executors[oparg&255]; + int original_oparg = executor->vm_data.oparg | (oparg & 0xfffff00); + JUMPBY(1-original_oparg); + frame->prev_instr = next_instr - 1; Py_INCREF(executor); frame = executor->execute(executor, frame, stack_pointer); if (frame == NULL) { @@ -3215,81 +3220,81 @@ goto resume_with_error; } goto resume_frame; - #line 3219 "Python/generated_cases.c.h" + #line 3224 "Python/generated_cases.c.h" } TARGET(POP_JUMP_IF_FALSE) { PyObject *cond = stack_pointer[-1]; - #line 2269 "Python/bytecodes.c" + #line 2274 "Python/bytecodes.c" assert(PyBool_Check(cond)); JUMPBY(oparg * Py_IsFalse(cond)); - #line 3227 "Python/generated_cases.c.h" + #line 3232 "Python/generated_cases.c.h" STACK_SHRINK(1); DISPATCH(); } TARGET(POP_JUMP_IF_TRUE) { PyObject *cond = stack_pointer[-1]; - #line 2274 "Python/bytecodes.c" + #line 2279 "Python/bytecodes.c" assert(PyBool_Check(cond)); JUMPBY(oparg * Py_IsTrue(cond)); - #line 3237 "Python/generated_cases.c.h" + #line 3242 "Python/generated_cases.c.h" STACK_SHRINK(1); DISPATCH(); } TARGET(POP_JUMP_IF_NOT_NONE) { PyObject *value = stack_pointer[-1]; - #line 2279 "Python/bytecodes.c" + #line 2284 "Python/bytecodes.c" if (!Py_IsNone(value)) { - #line 3246 "Python/generated_cases.c.h" + #line 3251 "Python/generated_cases.c.h" Py_DECREF(value); - #line 2281 "Python/bytecodes.c" + #line 2286 "Python/bytecodes.c" JUMPBY(oparg); } - #line 3251 "Python/generated_cases.c.h" + #line 3256 "Python/generated_cases.c.h" STACK_SHRINK(1); DISPATCH(); } TARGET(POP_JUMP_IF_NONE) { PyObject *value = stack_pointer[-1]; - #line 2286 "Python/bytecodes.c" + #line 2291 "Python/bytecodes.c" if (Py_IsNone(value)) { JUMPBY(oparg); } else { - #line 3263 "Python/generated_cases.c.h" + #line 3268 "Python/generated_cases.c.h" Py_DECREF(value); - #line 2291 "Python/bytecodes.c" + #line 2296 "Python/bytecodes.c" } - #line 3267 "Python/generated_cases.c.h" + #line 3272 "Python/generated_cases.c.h" STACK_SHRINK(1); DISPATCH(); } TARGET(JUMP_BACKWARD_NO_INTERRUPT) { - #line 2295 "Python/bytecodes.c" + #line 2300 "Python/bytecodes.c" /* This bytecode is used in the `yield from` or `await` loop. * If there is an interrupt, we want it handled in the innermost * generator or coroutine, so we deliberately do not check it here. * (see bpo-30039). */ JUMPBY(-oparg); - #line 3280 "Python/generated_cases.c.h" + #line 3285 "Python/generated_cases.c.h" DISPATCH(); } TARGET(GET_LEN) { PyObject *obj = stack_pointer[-1]; PyObject *len_o; - #line 2304 "Python/bytecodes.c" + #line 2309 "Python/bytecodes.c" // PUSH(len(TOS)) Py_ssize_t len_i = PyObject_Length(obj); if (len_i < 0) goto error; len_o = PyLong_FromSsize_t(len_i); if (len_o == NULL) goto error; - #line 3293 "Python/generated_cases.c.h" + #line 3298 "Python/generated_cases.c.h" STACK_GROW(1); stack_pointer[-1] = len_o; DISPATCH(); @@ -3300,16 +3305,16 @@ PyObject *type = stack_pointer[-2]; PyObject *subject = stack_pointer[-3]; PyObject *attrs; - #line 2312 "Python/bytecodes.c" + #line 2317 "Python/bytecodes.c" // Pop TOS and TOS1. Set TOS to a tuple of attributes on success, or // None on failure. assert(PyTuple_CheckExact(names)); attrs = match_class(tstate, subject, type, oparg, names); - #line 3309 "Python/generated_cases.c.h" + #line 3314 "Python/generated_cases.c.h" Py_DECREF(subject); Py_DECREF(type); Py_DECREF(names); - #line 2317 "Python/bytecodes.c" + #line 2322 "Python/bytecodes.c" if (attrs) { assert(PyTuple_CheckExact(attrs)); // Success! } @@ -3317,7 +3322,7 @@ if (_PyErr_Occurred(tstate)) goto pop_3_error; attrs = Py_None; // Failure! } - #line 3321 "Python/generated_cases.c.h" + #line 3326 "Python/generated_cases.c.h" STACK_SHRINK(2); stack_pointer[-1] = attrs; DISPATCH(); @@ -3326,10 +3331,10 @@ TARGET(MATCH_MAPPING) { PyObject *subject = stack_pointer[-1]; PyObject *res; - #line 2327 "Python/bytecodes.c" + #line 2332 "Python/bytecodes.c" int match = Py_TYPE(subject)->tp_flags & Py_TPFLAGS_MAPPING; res = match ? Py_True : Py_False; - #line 3333 "Python/generated_cases.c.h" + #line 3338 "Python/generated_cases.c.h" STACK_GROW(1); stack_pointer[-1] = res; DISPATCH(); @@ -3338,10 +3343,10 @@ TARGET(MATCH_SEQUENCE) { PyObject *subject = stack_pointer[-1]; PyObject *res; - #line 2332 "Python/bytecodes.c" + #line 2337 "Python/bytecodes.c" int match = Py_TYPE(subject)->tp_flags & Py_TPFLAGS_SEQUENCE; res = match ? Py_True : Py_False; - #line 3345 "Python/generated_cases.c.h" + #line 3350 "Python/generated_cases.c.h" STACK_GROW(1); stack_pointer[-1] = res; DISPATCH(); @@ -3351,11 +3356,11 @@ PyObject *keys = stack_pointer[-1]; PyObject *subject = stack_pointer[-2]; PyObject *values_or_none; - #line 2337 "Python/bytecodes.c" + #line 2342 "Python/bytecodes.c" // On successful match, PUSH(values). Otherwise, PUSH(None). values_or_none = match_keys(tstate, subject, keys); if (values_or_none == NULL) goto error; - #line 3359 "Python/generated_cases.c.h" + #line 3364 "Python/generated_cases.c.h" STACK_GROW(1); stack_pointer[-1] = values_or_none; DISPATCH(); @@ -3364,14 +3369,14 @@ TARGET(GET_ITER) { PyObject *iterable = stack_pointer[-1]; PyObject *iter; - #line 2343 "Python/bytecodes.c" + #line 2348 "Python/bytecodes.c" /* before: [obj]; after [getiter(obj)] */ iter = PyObject_GetIter(iterable); - #line 3371 "Python/generated_cases.c.h" + #line 3376 "Python/generated_cases.c.h" Py_DECREF(iterable); - #line 2346 "Python/bytecodes.c" + #line 2351 "Python/bytecodes.c" if (iter == NULL) goto pop_1_error; - #line 3375 "Python/generated_cases.c.h" + #line 3380 "Python/generated_cases.c.h" stack_pointer[-1] = iter; DISPATCH(); } @@ -3379,7 +3384,7 @@ TARGET(GET_YIELD_FROM_ITER) { PyObject *iterable = stack_pointer[-1]; PyObject *iter; - #line 2350 "Python/bytecodes.c" + #line 2355 "Python/bytecodes.c" /* before: [obj]; after [getiter(obj)] */ if (PyCoro_CheckExact(iterable)) { /* `iterable` is a coroutine */ @@ -3402,11 +3407,11 @@ if (iter == NULL) { goto error; } - #line 3406 "Python/generated_cases.c.h" + #line 3411 "Python/generated_cases.c.h" Py_DECREF(iterable); - #line 2373 "Python/bytecodes.c" + #line 2378 "Python/bytecodes.c" } - #line 3410 "Python/generated_cases.c.h" + #line 3415 "Python/generated_cases.c.h" stack_pointer[-1] = iter; DISPATCH(); } @@ -3416,7 +3421,7 @@ static_assert(INLINE_CACHE_ENTRIES_FOR_ITER == 1, "incorrect cache size"); PyObject *iter = stack_pointer[-1]; PyObject *next; - #line 2391 "Python/bytecodes.c" + #line 2396 "Python/bytecodes.c" #if ENABLE_SPECIALIZATION _PyForIterCache *cache = (_PyForIterCache *)next_instr; if (ADAPTIVE_COUNTER_IS_ZERO(cache->counter)) { @@ -3448,7 +3453,7 @@ DISPATCH(); } // Common case: no jump, leave it to the code generator - #line 3452 "Python/generated_cases.c.h" + #line 3457 "Python/generated_cases.c.h" STACK_GROW(1); stack_pointer[-1] = next; next_instr += 1; @@ -3456,7 +3461,7 @@ } TARGET(INSTRUMENTED_FOR_ITER) { - #line 2425 "Python/bytecodes.c" + #line 2430 "Python/bytecodes.c" _Py_CODEUNIT *here = next_instr-1; _Py_CODEUNIT *target; PyObject *iter = TOP(); @@ -3482,14 +3487,14 @@ target = next_instr + INLINE_CACHE_ENTRIES_FOR_ITER + oparg + 1; } INSTRUMENTED_JUMP(here, target, PY_MONITORING_EVENT_BRANCH); - #line 3486 "Python/generated_cases.c.h" + #line 3491 "Python/generated_cases.c.h" DISPATCH(); } TARGET(FOR_ITER_LIST) { PyObject *iter = stack_pointer[-1]; PyObject *next; - #line 2453 "Python/bytecodes.c" + #line 2458 "Python/bytecodes.c" DEOPT_IF(Py_TYPE(iter) != &PyListIter_Type, FOR_ITER); _PyListIterObject *it = (_PyListIterObject *)iter; STAT_INC(FOR_ITER, hit); @@ -3510,7 +3515,7 @@ DISPATCH(); end_for_iter_list: // Common case: no jump, leave it to the code generator - #line 3514 "Python/generated_cases.c.h" + #line 3519 "Python/generated_cases.c.h" STACK_GROW(1); stack_pointer[-1] = next; next_instr += 1; @@ -3520,7 +3525,7 @@ TARGET(FOR_ITER_TUPLE) { PyObject *iter = stack_pointer[-1]; PyObject *next; - #line 2476 "Python/bytecodes.c" + #line 2481 "Python/bytecodes.c" _PyTupleIterObject *it = (_PyTupleIterObject *)iter; DEOPT_IF(Py_TYPE(it) != &PyTupleIter_Type, FOR_ITER); STAT_INC(FOR_ITER, hit); @@ -3541,7 +3546,7 @@ DISPATCH(); end_for_iter_tuple: // Common case: no jump, leave it to the code generator - #line 3545 "Python/generated_cases.c.h" + #line 3550 "Python/generated_cases.c.h" STACK_GROW(1); stack_pointer[-1] = next; next_instr += 1; @@ -3551,7 +3556,7 @@ TARGET(FOR_ITER_RANGE) { PyObject *iter = stack_pointer[-1]; PyObject *next; - #line 2499 "Python/bytecodes.c" + #line 2504 "Python/bytecodes.c" _PyRangeIterObject *r = (_PyRangeIterObject *)iter; DEOPT_IF(Py_TYPE(r) != &PyRangeIter_Type, FOR_ITER); STAT_INC(FOR_ITER, hit); @@ -3570,7 +3575,7 @@ if (next == NULL) { goto error; } - #line 3574 "Python/generated_cases.c.h" + #line 3579 "Python/generated_cases.c.h" STACK_GROW(1); stack_pointer[-1] = next; next_instr += 1; @@ -3579,7 +3584,7 @@ TARGET(FOR_ITER_GEN) { PyObject *iter = stack_pointer[-1]; - #line 2520 "Python/bytecodes.c" + #line 2525 "Python/bytecodes.c" DEOPT_IF(tstate->interp->eval_frame, FOR_ITER); PyGenObject *gen = (PyGenObject *)iter; DEOPT_IF(Py_TYPE(gen) != &PyGen_Type, FOR_ITER); @@ -3595,14 +3600,14 @@ assert(next_instr[oparg].op.code == END_FOR || next_instr[oparg].op.code == INSTRUMENTED_END_FOR); DISPATCH_INLINED(gen_frame); - #line 3599 "Python/generated_cases.c.h" + #line 3604 "Python/generated_cases.c.h" } TARGET(BEFORE_ASYNC_WITH) { PyObject *mgr = stack_pointer[-1]; PyObject *exit; PyObject *res; - #line 2538 "Python/bytecodes.c" + #line 2543 "Python/bytecodes.c" PyObject *enter = _PyObject_LookupSpecial(mgr, &_Py_ID(__aenter__)); if (enter == NULL) { if (!_PyErr_Occurred(tstate)) { @@ -3625,16 +3630,16 @@ Py_DECREF(enter); goto error; } - #line 3629 "Python/generated_cases.c.h" + #line 3634 "Python/generated_cases.c.h" Py_DECREF(mgr); - #line 2561 "Python/bytecodes.c" + #line 2566 "Python/bytecodes.c" res = _PyObject_CallNoArgs(enter); Py_DECREF(enter); if (res == NULL) { Py_DECREF(exit); if (true) goto pop_1_error; } - #line 3638 "Python/generated_cases.c.h" + #line 3643 "Python/generated_cases.c.h" STACK_GROW(1); stack_pointer[-1] = res; stack_pointer[-2] = exit; @@ -3645,7 +3650,7 @@ PyObject *mgr = stack_pointer[-1]; PyObject *exit; PyObject *res; - #line 2570 "Python/bytecodes.c" + #line 2575 "Python/bytecodes.c" /* pop the context manager, push its __exit__ and the * value returned from calling its __enter__ */ @@ -3671,16 +3676,16 @@ Py_DECREF(enter); goto error; } - #line 3675 "Python/generated_cases.c.h" + #line 3680 "Python/generated_cases.c.h" Py_DECREF(mgr); - #line 2596 "Python/bytecodes.c" + #line 2601 "Python/bytecodes.c" res = _PyObject_CallNoArgs(enter); Py_DECREF(enter); if (res == NULL) { Py_DECREF(exit); if (true) goto pop_1_error; } - #line 3684 "Python/generated_cases.c.h" + #line 3689 "Python/generated_cases.c.h" STACK_GROW(1); stack_pointer[-1] = res; stack_pointer[-2] = exit; @@ -3692,7 +3697,7 @@ PyObject *lasti = stack_pointer[-3]; PyObject *exit_func = stack_pointer[-4]; PyObject *res; - #line 2605 "Python/bytecodes.c" + #line 2610 "Python/bytecodes.c" /* At the top of the stack are 4 values: - val: TOP = exc_info() - unused: SECOND = previous exception @@ -3713,7 +3718,7 @@ res = PyObject_Vectorcall(exit_func, stack + 1, 3 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL); if (res == NULL) goto error; - #line 3717 "Python/generated_cases.c.h" + #line 3722 "Python/generated_cases.c.h" STACK_GROW(1); stack_pointer[-1] = res; DISPATCH(); @@ -3722,7 +3727,7 @@ TARGET(PUSH_EXC_INFO) { PyObject *new_exc = stack_pointer[-1]; PyObject *prev_exc; - #line 2644 "Python/bytecodes.c" + #line 2649 "Python/bytecodes.c" _PyErr_StackItem *exc_info = tstate->exc_info; if (exc_info->exc_value != NULL) { prev_exc = exc_info->exc_value; @@ -3732,7 +3737,7 @@ } assert(PyExceptionInstance_Check(new_exc)); exc_info->exc_value = Py_NewRef(new_exc); - #line 3736 "Python/generated_cases.c.h" + #line 3741 "Python/generated_cases.c.h" STACK_GROW(1); stack_pointer[-1] = new_exc; stack_pointer[-2] = prev_exc; @@ -3746,7 +3751,7 @@ uint32_t type_version = read_u32(&next_instr[1].cache); uint32_t keys_version = read_u32(&next_instr[3].cache); PyObject *descr = read_obj(&next_instr[5].cache); - #line 2656 "Python/bytecodes.c" + #line 2661 "Python/bytecodes.c" /* Cached method object */ PyTypeObject *self_cls = Py_TYPE(self); assert(type_version != 0); @@ -3763,7 +3768,7 @@ assert(_PyType_HasFeature(Py_TYPE(res2), Py_TPFLAGS_METHOD_DESCRIPTOR)); res = self; assert(oparg & 1); - #line 3767 "Python/generated_cases.c.h" + #line 3772 "Python/generated_cases.c.h" STACK_GROW(((oparg & 1) ? 1 : 0)); stack_pointer[-1] = res; if (oparg & 1) { stack_pointer[-(1 + ((oparg & 1) ? 1 : 0))] = res2; } @@ -3777,7 +3782,7 @@ PyObject *res; uint32_t type_version = read_u32(&next_instr[1].cache); PyObject *descr = read_obj(&next_instr[5].cache); - #line 2675 "Python/bytecodes.c" + #line 2680 "Python/bytecodes.c" PyTypeObject *self_cls = Py_TYPE(self); DEOPT_IF(self_cls->tp_version_tag != type_version, LOAD_ATTR); assert(self_cls->tp_dictoffset == 0); @@ -3787,7 +3792,7 @@ res2 = Py_NewRef(descr); res = self; assert(oparg & 1); - #line 3791 "Python/generated_cases.c.h" + #line 3796 "Python/generated_cases.c.h" STACK_GROW(((oparg & 1) ? 1 : 0)); stack_pointer[-1] = res; if (oparg & 1) { stack_pointer[-(1 + ((oparg & 1) ? 1 : 0))] = res2; } @@ -3801,7 +3806,7 @@ PyObject *res; uint32_t type_version = read_u32(&next_instr[1].cache); PyObject *descr = read_obj(&next_instr[5].cache); - #line 2687 "Python/bytecodes.c" + #line 2692 "Python/bytecodes.c" PyTypeObject *self_cls = Py_TYPE(self); DEOPT_IF(self_cls->tp_version_tag != type_version, LOAD_ATTR); Py_ssize_t dictoffset = self_cls->tp_dictoffset; @@ -3815,7 +3820,7 @@ res2 = Py_NewRef(descr); res = self; assert(oparg & 1); - #line 3819 "Python/generated_cases.c.h" + #line 3824 "Python/generated_cases.c.h" STACK_GROW(((oparg & 1) ? 1 : 0)); stack_pointer[-1] = res; if (oparg & 1) { stack_pointer[-(1 + ((oparg & 1) ? 1 : 0))] = res2; } @@ -3824,16 +3829,16 @@ } TARGET(KW_NAMES) { - #line 2703 "Python/bytecodes.c" + #line 2708 "Python/bytecodes.c" assert(kwnames == NULL); assert(oparg < PyTuple_GET_SIZE(FRAME_CO_CONSTS)); kwnames = GETITEM(FRAME_CO_CONSTS, oparg); - #line 3832 "Python/generated_cases.c.h" + #line 3837 "Python/generated_cases.c.h" DISPATCH(); } TARGET(INSTRUMENTED_CALL) { - #line 2709 "Python/bytecodes.c" + #line 2714 "Python/bytecodes.c" int is_meth = PEEK(oparg+2) != NULL; int total_args = oparg + is_meth; PyObject *function = PEEK(total_args + 1); @@ -3846,7 +3851,7 @@ _PyCallCache *cache = (_PyCallCache *)next_instr; INCREMENT_ADAPTIVE_COUNTER(cache->counter); GO_TO_INSTRUCTION(CALL); - #line 3850 "Python/generated_cases.c.h" + #line 3855 "Python/generated_cases.c.h" } TARGET(CALL) { @@ -3856,7 +3861,7 @@ PyObject *callable = stack_pointer[-(1 + oparg)]; PyObject *method = stack_pointer[-(2 + oparg)]; PyObject *res; - #line 2755 "Python/bytecodes.c" + #line 2760 "Python/bytecodes.c" int is_meth = method != NULL; int total_args = oparg; if (is_meth) { @@ -3938,7 +3943,7 @@ Py_DECREF(args[i]); } if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - #line 3942 "Python/generated_cases.c.h" + #line 3947 "Python/generated_cases.c.h" STACK_SHRINK(oparg); STACK_SHRINK(1); stack_pointer[-1] = res; @@ -3950,7 +3955,7 @@ TARGET(CALL_BOUND_METHOD_EXACT_ARGS) { PyObject *callable = stack_pointer[-(1 + oparg)]; PyObject *method = stack_pointer[-(2 + oparg)]; - #line 2843 "Python/bytecodes.c" + #line 2848 "Python/bytecodes.c" DEOPT_IF(method != NULL, CALL); DEOPT_IF(Py_TYPE(callable) != &PyMethod_Type, CALL); STAT_INC(CALL, hit); @@ -3960,7 +3965,7 @@ PEEK(oparg + 2) = Py_NewRef(meth); // method Py_DECREF(callable); GO_TO_INSTRUCTION(CALL_PY_EXACT_ARGS); - #line 3964 "Python/generated_cases.c.h" + #line 3969 "Python/generated_cases.c.h" } TARGET(CALL_PY_EXACT_ARGS) { @@ -3969,7 +3974,7 @@ PyObject *callable = stack_pointer[-(1 + oparg)]; PyObject *method = stack_pointer[-(2 + oparg)]; uint32_t func_version = read_u32(&next_instr[1].cache); - #line 2855 "Python/bytecodes.c" + #line 2860 "Python/bytecodes.c" assert(kwnames == NULL); DEOPT_IF(tstate->interp->eval_frame, CALL); int is_meth = method != NULL; @@ -3995,7 +4000,7 @@ SKIP_OVER(INLINE_CACHE_ENTRIES_CALL); frame->return_offset = 0; DISPATCH_INLINED(new_frame); - #line 3999 "Python/generated_cases.c.h" + #line 4004 "Python/generated_cases.c.h" } TARGET(CALL_PY_WITH_DEFAULTS) { @@ -4003,7 +4008,7 @@ PyObject *callable = stack_pointer[-(1 + oparg)]; PyObject *method = stack_pointer[-(2 + oparg)]; uint32_t func_version = read_u32(&next_instr[1].cache); - #line 2883 "Python/bytecodes.c" + #line 2888 "Python/bytecodes.c" assert(kwnames == NULL); DEOPT_IF(tstate->interp->eval_frame, CALL); int is_meth = method != NULL; @@ -4039,7 +4044,7 @@ SKIP_OVER(INLINE_CACHE_ENTRIES_CALL); frame->return_offset = 0; DISPATCH_INLINED(new_frame); - #line 4043 "Python/generated_cases.c.h" + #line 4048 "Python/generated_cases.c.h" } TARGET(CALL_NO_KW_TYPE_1) { @@ -4047,7 +4052,7 @@ PyObject *callable = stack_pointer[-(1 + oparg)]; PyObject *null = stack_pointer[-(2 + oparg)]; PyObject *res; - #line 2921 "Python/bytecodes.c" + #line 2926 "Python/bytecodes.c" assert(kwnames == NULL); assert(oparg == 1); DEOPT_IF(null != NULL, CALL); @@ -4057,7 +4062,7 @@ res = Py_NewRef(Py_TYPE(obj)); Py_DECREF(obj); Py_DECREF(&PyType_Type); // I.e., callable - #line 4061 "Python/generated_cases.c.h" + #line 4066 "Python/generated_cases.c.h" STACK_SHRINK(oparg); STACK_SHRINK(1); stack_pointer[-1] = res; @@ -4070,7 +4075,7 @@ PyObject *callable = stack_pointer[-(1 + oparg)]; PyObject *null = stack_pointer[-(2 + oparg)]; PyObject *res; - #line 2933 "Python/bytecodes.c" + #line 2938 "Python/bytecodes.c" assert(kwnames == NULL); assert(oparg == 1); DEOPT_IF(null != NULL, CALL); @@ -4081,7 +4086,7 @@ Py_DECREF(arg); Py_DECREF(&PyUnicode_Type); // I.e., callable if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - #line 4085 "Python/generated_cases.c.h" + #line 4090 "Python/generated_cases.c.h" STACK_SHRINK(oparg); STACK_SHRINK(1); stack_pointer[-1] = res; @@ -4095,7 +4100,7 @@ PyObject *callable = stack_pointer[-(1 + oparg)]; PyObject *null = stack_pointer[-(2 + oparg)]; PyObject *res; - #line 2947 "Python/bytecodes.c" + #line 2952 "Python/bytecodes.c" assert(kwnames == NULL); assert(oparg == 1); DEOPT_IF(null != NULL, CALL); @@ -4106,7 +4111,7 @@ Py_DECREF(arg); Py_DECREF(&PyTuple_Type); // I.e., tuple if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - #line 4110 "Python/generated_cases.c.h" + #line 4115 "Python/generated_cases.c.h" STACK_SHRINK(oparg); STACK_SHRINK(1); stack_pointer[-1] = res; @@ -4119,7 +4124,7 @@ PyObject **args = (stack_pointer - oparg); PyObject *callable = stack_pointer[-(1 + oparg)]; PyObject *null = stack_pointer[-(2 + oparg)]; - #line 2961 "Python/bytecodes.c" + #line 2966 "Python/bytecodes.c" /* This instruction does the following: * 1. Creates the object (by calling ``object.__new__``) * 2. Pushes a shim frame to the frame stack (to cleanup after ``__init__``) @@ -4169,12 +4174,12 @@ frame = cframe.current_frame = init_frame; CALL_STAT_INC(inlined_py_calls); goto start_frame; - #line 4173 "Python/generated_cases.c.h" + #line 4178 "Python/generated_cases.c.h" } TARGET(EXIT_INIT_CHECK) { PyObject *should_be_none = stack_pointer[-1]; - #line 3013 "Python/bytecodes.c" + #line 3018 "Python/bytecodes.c" assert(STACK_LEVEL() == 2); if (should_be_none != Py_None) { PyErr_Format(PyExc_TypeError, @@ -4182,7 +4187,7 @@ Py_TYPE(should_be_none)->tp_name); goto error; } - #line 4186 "Python/generated_cases.c.h" + #line 4191 "Python/generated_cases.c.h" STACK_SHRINK(1); DISPATCH(); } @@ -4192,7 +4197,7 @@ PyObject *callable = stack_pointer[-(1 + oparg)]; PyObject *method = stack_pointer[-(2 + oparg)]; PyObject *res; - #line 3023 "Python/bytecodes.c" + #line 3028 "Python/bytecodes.c" int is_meth = method != NULL; int total_args = oparg; if (is_meth) { @@ -4214,7 +4219,7 @@ } Py_DECREF(tp); if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - #line 4218 "Python/generated_cases.c.h" + #line 4223 "Python/generated_cases.c.h" STACK_SHRINK(oparg); STACK_SHRINK(1); stack_pointer[-1] = res; @@ -4228,7 +4233,7 @@ PyObject *callable = stack_pointer[-(1 + oparg)]; PyObject *method = stack_pointer[-(2 + oparg)]; PyObject *res; - #line 3048 "Python/bytecodes.c" + #line 3053 "Python/bytecodes.c" /* Builtin METH_O functions */ assert(kwnames == NULL); int is_meth = method != NULL; @@ -4256,7 +4261,7 @@ Py_DECREF(arg); Py_DECREF(callable); if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - #line 4260 "Python/generated_cases.c.h" + #line 4265 "Python/generated_cases.c.h" STACK_SHRINK(oparg); STACK_SHRINK(1); stack_pointer[-1] = res; @@ -4270,7 +4275,7 @@ PyObject *callable = stack_pointer[-(1 + oparg)]; PyObject *method = stack_pointer[-(2 + oparg)]; PyObject *res; - #line 3079 "Python/bytecodes.c" + #line 3084 "Python/bytecodes.c" /* Builtin METH_FASTCALL functions, without keywords */ assert(kwnames == NULL); int is_meth = method != NULL; @@ -4302,7 +4307,7 @@ 'invalid'). In those cases an exception is set, so we must handle it. */ - #line 4306 "Python/generated_cases.c.h" + #line 4311 "Python/generated_cases.c.h" STACK_SHRINK(oparg); STACK_SHRINK(1); stack_pointer[-1] = res; @@ -4316,7 +4321,7 @@ PyObject *callable = stack_pointer[-(1 + oparg)]; PyObject *method = stack_pointer[-(2 + oparg)]; PyObject *res; - #line 3114 "Python/bytecodes.c" + #line 3119 "Python/bytecodes.c" /* Builtin METH_FASTCALL | METH_KEYWORDS functions */ int is_meth = method != NULL; int total_args = oparg; @@ -4348,7 +4353,7 @@ } Py_DECREF(callable); if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - #line 4352 "Python/generated_cases.c.h" + #line 4357 "Python/generated_cases.c.h" STACK_SHRINK(oparg); STACK_SHRINK(1); stack_pointer[-1] = res; @@ -4362,7 +4367,7 @@ PyObject *callable = stack_pointer[-(1 + oparg)]; PyObject *method = stack_pointer[-(2 + oparg)]; PyObject *res; - #line 3149 "Python/bytecodes.c" + #line 3154 "Python/bytecodes.c" assert(kwnames == NULL); /* len(o) */ int is_meth = method != NULL; @@ -4387,7 +4392,7 @@ Py_DECREF(callable); Py_DECREF(arg); if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - #line 4391 "Python/generated_cases.c.h" + #line 4396 "Python/generated_cases.c.h" STACK_SHRINK(oparg); STACK_SHRINK(1); stack_pointer[-1] = res; @@ -4400,7 +4405,7 @@ PyObject *callable = stack_pointer[-(1 + oparg)]; PyObject *method = stack_pointer[-(2 + oparg)]; PyObject *res; - #line 3176 "Python/bytecodes.c" + #line 3181 "Python/bytecodes.c" assert(kwnames == NULL); /* isinstance(o, o2) */ int is_meth = method != NULL; @@ -4427,7 +4432,7 @@ Py_DECREF(cls); Py_DECREF(callable); if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - #line 4431 "Python/generated_cases.c.h" + #line 4436 "Python/generated_cases.c.h" STACK_SHRINK(oparg); STACK_SHRINK(1); stack_pointer[-1] = res; @@ -4439,7 +4444,7 @@ PyObject **args = (stack_pointer - oparg); PyObject *self = stack_pointer[-(1 + oparg)]; PyObject *method = stack_pointer[-(2 + oparg)]; - #line 3206 "Python/bytecodes.c" + #line 3211 "Python/bytecodes.c" assert(kwnames == NULL); assert(oparg == 1); assert(method != NULL); @@ -4457,14 +4462,14 @@ SKIP_OVER(INLINE_CACHE_ENTRIES_CALL + 1); assert(next_instr[-1].op.code == POP_TOP); DISPATCH(); - #line 4461 "Python/generated_cases.c.h" + #line 4466 "Python/generated_cases.c.h" } TARGET(CALL_NO_KW_METHOD_DESCRIPTOR_O) { PyObject **args = (stack_pointer - oparg); PyObject *method = stack_pointer[-(2 + oparg)]; PyObject *res; - #line 3226 "Python/bytecodes.c" + #line 3231 "Python/bytecodes.c" assert(kwnames == NULL); int is_meth = method != NULL; int total_args = oparg; @@ -4495,7 +4500,7 @@ Py_DECREF(arg); Py_DECREF(callable); if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - #line 4499 "Python/generated_cases.c.h" + #line 4504 "Python/generated_cases.c.h" STACK_SHRINK(oparg); STACK_SHRINK(1); stack_pointer[-1] = res; @@ -4508,7 +4513,7 @@ PyObject **args = (stack_pointer - oparg); PyObject *method = stack_pointer[-(2 + oparg)]; PyObject *res; - #line 3260 "Python/bytecodes.c" + #line 3265 "Python/bytecodes.c" int is_meth = method != NULL; int total_args = oparg; if (is_meth) { @@ -4537,7 +4542,7 @@ } Py_DECREF(callable); if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - #line 4541 "Python/generated_cases.c.h" + #line 4546 "Python/generated_cases.c.h" STACK_SHRINK(oparg); STACK_SHRINK(1); stack_pointer[-1] = res; @@ -4550,7 +4555,7 @@ PyObject **args = (stack_pointer - oparg); PyObject *method = stack_pointer[-(2 + oparg)]; PyObject *res; - #line 3292 "Python/bytecodes.c" + #line 3297 "Python/bytecodes.c" assert(kwnames == NULL); assert(oparg == 0 || oparg == 1); int is_meth = method != NULL; @@ -4579,7 +4584,7 @@ Py_DECREF(self); Py_DECREF(callable); if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - #line 4583 "Python/generated_cases.c.h" + #line 4588 "Python/generated_cases.c.h" STACK_SHRINK(oparg); STACK_SHRINK(1); stack_pointer[-1] = res; @@ -4592,7 +4597,7 @@ PyObject **args = (stack_pointer - oparg); PyObject *method = stack_pointer[-(2 + oparg)]; PyObject *res; - #line 3324 "Python/bytecodes.c" + #line 3329 "Python/bytecodes.c" assert(kwnames == NULL); int is_meth = method != NULL; int total_args = oparg; @@ -4620,7 +4625,7 @@ } Py_DECREF(callable); if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - #line 4624 "Python/generated_cases.c.h" + #line 4629 "Python/generated_cases.c.h" STACK_SHRINK(oparg); STACK_SHRINK(1); stack_pointer[-1] = res; @@ -4630,9 +4635,9 @@ } TARGET(INSTRUMENTED_CALL_FUNCTION_EX) { - #line 3355 "Python/bytecodes.c" + #line 3360 "Python/bytecodes.c" GO_TO_INSTRUCTION(CALL_FUNCTION_EX); - #line 4636 "Python/generated_cases.c.h" + #line 4641 "Python/generated_cases.c.h" } TARGET(CALL_FUNCTION_EX) { @@ -4641,7 +4646,7 @@ PyObject *callargs = stack_pointer[-(1 + ((oparg & 1) ? 1 : 0))]; PyObject *func = stack_pointer[-(2 + ((oparg & 1) ? 1 : 0))]; PyObject *result; - #line 3359 "Python/bytecodes.c" + #line 3364 "Python/bytecodes.c" // DICT_MERGE is called before this opcode if there are kwargs. // It converts all dict subtypes in kwargs into regular dicts. assert(kwargs == NULL || PyDict_CheckExact(kwargs)); @@ -4703,14 +4708,14 @@ } result = PyObject_Call(func, callargs, kwargs); } - #line 4707 "Python/generated_cases.c.h" + #line 4712 "Python/generated_cases.c.h" Py_DECREF(func); Py_DECREF(callargs); Py_XDECREF(kwargs); - #line 3421 "Python/bytecodes.c" + #line 3426 "Python/bytecodes.c" assert(PEEK(3 + (oparg & 1)) == NULL); if (result == NULL) { STACK_SHRINK(((oparg & 1) ? 1 : 0)); goto pop_3_error; } - #line 4714 "Python/generated_cases.c.h" + #line 4719 "Python/generated_cases.c.h" STACK_SHRINK(((oparg & 1) ? 1 : 0)); STACK_SHRINK(2); stack_pointer[-1] = result; @@ -4721,7 +4726,7 @@ TARGET(MAKE_FUNCTION) { PyObject *codeobj = stack_pointer[-1]; PyObject *func; - #line 3427 "Python/bytecodes.c" + #line 3432 "Python/bytecodes.c" PyFunctionObject *func_obj = (PyFunctionObject *) PyFunction_New(codeobj, GLOBALS()); @@ -4733,7 +4738,7 @@ func_obj->func_version = ((PyCodeObject *)codeobj)->co_version; func = (PyObject *)func_obj; - #line 4737 "Python/generated_cases.c.h" + #line 4742 "Python/generated_cases.c.h" stack_pointer[-1] = func; DISPATCH(); } @@ -4741,7 +4746,7 @@ TARGET(SET_FUNCTION_ATTRIBUTE) { PyObject *func = stack_pointer[-1]; PyObject *attr = stack_pointer[-2]; - #line 3441 "Python/bytecodes.c" + #line 3446 "Python/bytecodes.c" assert(PyFunction_Check(func)); PyFunctionObject *func_obj = (PyFunctionObject *)func; switch(oparg) { @@ -4766,14 +4771,14 @@ default: Py_UNREACHABLE(); } - #line 4770 "Python/generated_cases.c.h" + #line 4775 "Python/generated_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = func; DISPATCH(); } TARGET(RETURN_GENERATOR) { - #line 3468 "Python/bytecodes.c" + #line 3473 "Python/bytecodes.c" assert(PyFunction_Check(frame->f_funcobj)); PyFunctionObject *func = (PyFunctionObject *)frame->f_funcobj; PyGenObject *gen = (PyGenObject *)_Py_MakeCoro(func); @@ -4794,7 +4799,7 @@ frame = cframe.current_frame = prev; _PyFrame_StackPush(frame, (PyObject *)gen); goto resume_frame; - #line 4798 "Python/generated_cases.c.h" + #line 4803 "Python/generated_cases.c.h" } TARGET(BUILD_SLICE) { @@ -4802,15 +4807,15 @@ PyObject *stop = stack_pointer[-(1 + ((oparg == 3) ? 1 : 0))]; PyObject *start = stack_pointer[-(2 + ((oparg == 3) ? 1 : 0))]; PyObject *slice; - #line 3491 "Python/bytecodes.c" + #line 3496 "Python/bytecodes.c" slice = PySlice_New(start, stop, step); - #line 4808 "Python/generated_cases.c.h" + #line 4813 "Python/generated_cases.c.h" Py_DECREF(start); Py_DECREF(stop); Py_XDECREF(step); - #line 3493 "Python/bytecodes.c" + #line 3498 "Python/bytecodes.c" if (slice == NULL) { STACK_SHRINK(((oparg == 3) ? 1 : 0)); goto pop_2_error; } - #line 4814 "Python/generated_cases.c.h" + #line 4819 "Python/generated_cases.c.h" STACK_SHRINK(((oparg == 3) ? 1 : 0)); STACK_SHRINK(1); stack_pointer[-1] = slice; @@ -4820,14 +4825,14 @@ TARGET(CONVERT_VALUE) { PyObject *value = stack_pointer[-1]; PyObject *result; - #line 3497 "Python/bytecodes.c" + #line 3502 "Python/bytecodes.c" convertion_func_ptr conv_fn; assert(oparg >= FVC_STR && oparg <= FVC_ASCII); conv_fn = CONVERSION_FUNCTIONS[oparg]; result = conv_fn(value); Py_DECREF(value); if (result == NULL) goto pop_1_error; - #line 4831 "Python/generated_cases.c.h" + #line 4836 "Python/generated_cases.c.h" stack_pointer[-1] = result; DISPATCH(); } @@ -4835,7 +4840,7 @@ TARGET(FORMAT_SIMPLE) { PyObject *value = stack_pointer[-1]; PyObject *res; - #line 3506 "Python/bytecodes.c" + #line 3511 "Python/bytecodes.c" /* If value is a unicode object, then we know the result * of format(value) is value itself. */ if (!PyUnicode_CheckExact(value)) { @@ -4846,7 +4851,7 @@ else { res = value; } - #line 4850 "Python/generated_cases.c.h" + #line 4855 "Python/generated_cases.c.h" stack_pointer[-1] = res; DISPATCH(); } @@ -4855,12 +4860,12 @@ PyObject *fmt_spec = stack_pointer[-1]; PyObject *value = stack_pointer[-2]; PyObject *res; - #line 3519 "Python/bytecodes.c" + #line 3524 "Python/bytecodes.c" res = PyObject_Format(value, fmt_spec); Py_DECREF(value); Py_DECREF(fmt_spec); if (res == NULL) goto pop_2_error; - #line 4864 "Python/generated_cases.c.h" + #line 4869 "Python/generated_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = res; DISPATCH(); @@ -4869,10 +4874,10 @@ TARGET(COPY) { PyObject *bottom = stack_pointer[-(1 + (oparg-1))]; PyObject *top; - #line 3526 "Python/bytecodes.c" + #line 3531 "Python/bytecodes.c" assert(oparg > 0); top = Py_NewRef(bottom); - #line 4876 "Python/generated_cases.c.h" + #line 4881 "Python/generated_cases.c.h" STACK_GROW(1); stack_pointer[-1] = top; DISPATCH(); @@ -4884,7 +4889,7 @@ PyObject *rhs = stack_pointer[-1]; PyObject *lhs = stack_pointer[-2]; PyObject *res; - #line 3531 "Python/bytecodes.c" + #line 3536 "Python/bytecodes.c" #if ENABLE_SPECIALIZATION _PyBinaryOpCache *cache = (_PyBinaryOpCache *)next_instr; if (ADAPTIVE_COUNTER_IS_ZERO(cache->counter)) { @@ -4899,12 +4904,12 @@ assert((unsigned)oparg < Py_ARRAY_LENGTH(binary_ops)); assert(binary_ops[oparg]); res = binary_ops[oparg](lhs, rhs); - #line 4903 "Python/generated_cases.c.h" + #line 4908 "Python/generated_cases.c.h" Py_DECREF(lhs); Py_DECREF(rhs); - #line 3546 "Python/bytecodes.c" + #line 3551 "Python/bytecodes.c" if (res == NULL) goto pop_2_error; - #line 4908 "Python/generated_cases.c.h" + #line 4913 "Python/generated_cases.c.h" STACK_SHRINK(1); stack_pointer[-1] = res; next_instr += 1; @@ -4914,16 +4919,16 @@ TARGET(SWAP) { PyObject *top = stack_pointer[-1]; PyObject *bottom = stack_pointer[-(2 + (oparg-2))]; - #line 3551 "Python/bytecodes.c" + #line 3556 "Python/bytecodes.c" assert(oparg >= 2); - #line 4920 "Python/generated_cases.c.h" + #line 4925 "Python/generated_cases.c.h" stack_pointer[-1] = bottom; stack_pointer[-(2 + (oparg-2))] = top; DISPATCH(); } TARGET(INSTRUMENTED_INSTRUCTION) { - #line 3555 "Python/bytecodes.c" + #line 3560 "Python/bytecodes.c" int next_opcode = _Py_call_instrumentation_instruction( tstate, frame, next_instr-1); if (next_opcode < 0) goto error; @@ -4935,48 +4940,48 @@ assert(next_opcode > 0 && next_opcode < 256); opcode = next_opcode; DISPATCH_GOTO(); - #line 4939 "Python/generated_cases.c.h" + #line 4944 "Python/generated_cases.c.h" } TARGET(INSTRUMENTED_JUMP_FORWARD) { - #line 3569 "Python/bytecodes.c" + #line 3574 "Python/bytecodes.c" INSTRUMENTED_JUMP(next_instr-1, next_instr+oparg, PY_MONITORING_EVENT_JUMP); - #line 4945 "Python/generated_cases.c.h" + #line 4950 "Python/generated_cases.c.h" DISPATCH(); } TARGET(INSTRUMENTED_JUMP_BACKWARD) { - #line 3573 "Python/bytecodes.c" - INSTRUMENTED_JUMP(next_instr-1, next_instr+1-oparg, PY_MONITORING_EVENT_JUMP); - #line 4952 "Python/generated_cases.c.h" + #line 3578 "Python/bytecodes.c" CHECK_EVAL_BREAKER(); + INSTRUMENTED_JUMP(next_instr-1, next_instr+1-oparg, PY_MONITORING_EVENT_JUMP); + #line 4958 "Python/generated_cases.c.h" DISPATCH(); } TARGET(INSTRUMENTED_POP_JUMP_IF_TRUE) { - #line 3578 "Python/bytecodes.c" + #line 3583 "Python/bytecodes.c" PyObject *cond = POP(); assert(PyBool_Check(cond)); _Py_CODEUNIT *here = next_instr - 1; int offset = Py_IsTrue(cond) * oparg; INSTRUMENTED_JUMP(here, next_instr + offset, PY_MONITORING_EVENT_BRANCH); - #line 4964 "Python/generated_cases.c.h" + #line 4969 "Python/generated_cases.c.h" DISPATCH(); } TARGET(INSTRUMENTED_POP_JUMP_IF_FALSE) { - #line 3586 "Python/bytecodes.c" + #line 3591 "Python/bytecodes.c" PyObject *cond = POP(); assert(PyBool_Check(cond)); _Py_CODEUNIT *here = next_instr - 1; int offset = Py_IsFalse(cond) * oparg; INSTRUMENTED_JUMP(here, next_instr + offset, PY_MONITORING_EVENT_BRANCH); - #line 4975 "Python/generated_cases.c.h" + #line 4980 "Python/generated_cases.c.h" DISPATCH(); } TARGET(INSTRUMENTED_POP_JUMP_IF_NONE) { - #line 3594 "Python/bytecodes.c" + #line 3599 "Python/bytecodes.c" PyObject *value = POP(); _Py_CODEUNIT *here = next_instr-1; int offset; @@ -4988,12 +4993,12 @@ offset = 0; } INSTRUMENTED_JUMP(here, next_instr + offset, PY_MONITORING_EVENT_BRANCH); - #line 4992 "Python/generated_cases.c.h" + #line 4997 "Python/generated_cases.c.h" DISPATCH(); } TARGET(INSTRUMENTED_POP_JUMP_IF_NOT_NONE) { - #line 3608 "Python/bytecodes.c" + #line 3613 "Python/bytecodes.c" PyObject *value = POP(); _Py_CODEUNIT *here = next_instr-1; int offset; @@ -5005,30 +5010,30 @@ offset = oparg; } INSTRUMENTED_JUMP(here, next_instr + offset, PY_MONITORING_EVENT_BRANCH); - #line 5009 "Python/generated_cases.c.h" + #line 5014 "Python/generated_cases.c.h" DISPATCH(); } TARGET(EXTENDED_ARG) { - #line 3622 "Python/bytecodes.c" + #line 3627 "Python/bytecodes.c" assert(oparg); opcode = next_instr->op.code; oparg = oparg << 8 | next_instr->op.arg; PRE_DISPATCH_GOTO(); DISPATCH_GOTO(); - #line 5020 "Python/generated_cases.c.h" + #line 5025 "Python/generated_cases.c.h" } TARGET(CACHE) { - #line 3630 "Python/bytecodes.c" + #line 3635 "Python/bytecodes.c" assert(0 && "Executing a cache."); Py_UNREACHABLE(); - #line 5027 "Python/generated_cases.c.h" + #line 5032 "Python/generated_cases.c.h" } TARGET(RESERVED) { - #line 3635 "Python/bytecodes.c" + #line 3640 "Python/bytecodes.c" assert(0 && "Executing RESERVED instruction."); Py_UNREACHABLE(); - #line 5034 "Python/generated_cases.c.h" + #line 5039 "Python/generated_cases.c.h" } diff --git a/Python/opcode_metadata.h b/Python/opcode_metadata.h index ac3d800d8fe0fe..82c98235892287 100644 --- a/Python/opcode_metadata.h +++ b/Python/opcode_metadata.h @@ -1087,7 +1087,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[512] = { [JUMP_BACKWARD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [JUMP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [JUMP_NO_INTERRUPT] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [ENTER_EXECUTOR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [ENTER_EXECUTOR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [POP_JUMP_IF_TRUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [POP_JUMP_IF_NOT_NONE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, From b4efdf8cda8fbbd0ca53b457d5f6e46a59348caf Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Mon, 3 Jul 2023 21:29:44 +0100 Subject: [PATCH 05/39] GH-106330: Fix matching of empty path in `pathlib.PurePath.match()` (GH-106331) We match paths using the `_lines` attribute, which is derived from the path's string representation. The bug arises because an empty path's string representation is `'.'` (not `''`), which is matched by the `'*'` wildcard. --- Lib/pathlib.py | 8 ++++++-- Lib/test/test_pathlib.py | 4 ++++ .../2023-07-02-10-56-41.gh-issue-106330.QSkIUH.rst | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-07-02-10-56-41.gh-issue-106330.QSkIUH.rst diff --git a/Lib/pathlib.py b/Lib/pathlib.py index e15718dc98d677..f3813e04109904 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -463,8 +463,12 @@ def _lines(self): try: return self._lines_cached except AttributeError: - trans = _SWAP_SEP_AND_NEWLINE[self._flavour.sep] - self._lines_cached = str(self).translate(trans) + path_str = str(self) + if path_str == '.': + self._lines_cached = '' + else: + trans = _SWAP_SEP_AND_NEWLINE[self._flavour.sep] + self._lines_cached = path_str.translate(trans) return self._lines_cached def __eq__(self, other): diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 464a835212d472..eb2b0cfb26e85f 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -384,6 +384,10 @@ def test_match_common(self): self.assertTrue(P('A.py').match('a.PY', case_sensitive=False)) self.assertFalse(P('c:/a/B.Py').match('C:/A/*.pY', case_sensitive=True)) self.assertTrue(P('/a/b/c.py').match('/A/*/*.Py', case_sensitive=False)) + # Matching against empty path + self.assertFalse(P().match('*')) + self.assertTrue(P().match('**')) + self.assertFalse(P().match('**/*')) def test_ordering_common(self): # Ordering is tuple-alike. diff --git a/Misc/NEWS.d/next/Library/2023-07-02-10-56-41.gh-issue-106330.QSkIUH.rst b/Misc/NEWS.d/next/Library/2023-07-02-10-56-41.gh-issue-106330.QSkIUH.rst new file mode 100644 index 00000000000000..c1f55ab658b517 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-07-02-10-56-41.gh-issue-106330.QSkIUH.rst @@ -0,0 +1,2 @@ +Fix incorrect matching of empty paths in :meth:`pathlib.PurePath.match`. +This bug was introduced in Python 3.12.0 beta 1. From 77090370952307730ea71d68b848cce0dc8cbd83 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Mon, 3 Jul 2023 15:38:38 -0500 Subject: [PATCH 06/39] Small speed-up for the convolve() recipe. (GH-106371) --- Doc/library/itertools.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 56d6599798af20..a2d1798a2c6da1 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -1085,8 +1085,8 @@ The following recipes have a more mathematical flavor: kernel = tuple(kernel)[::-1] n = len(kernel) padded_signal = chain(repeat(0, n-1), signal, repeat(0, n-1)) - for window in sliding_window(padded_signal, n): - yield math.sumprod(kernel, window) + windowed_signal = sliding_window(padded_signal, n) + return map(math.sumprod, repeat(kernel), windowed_signal) def polynomial_from_roots(roots): """Compute a polynomial's coefficients from its roots. From 71b40443fed6acb58330ee262f8d674b394f41d3 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 3 Jul 2023 23:16:21 +0200 Subject: [PATCH 07/39] gh-104683: Modernise Argument Clinic parameter state machine (#106362) Use enums and pattern matching to make the code more readable. Co-authored-by: Alex Waygood --- Tools/clinic/clinic.py | 129 +++++++++++++++++++++++++---------------- 1 file changed, 78 insertions(+), 51 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 41d4274fc744e0..6380b9ce38f5f6 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -4294,6 +4294,37 @@ def dedent(self, line): StateKeeper = Callable[[str | None], None] ConverterArgs = dict[str, Any] +class ParamState(enum.IntEnum): + """Parameter parsing state. + + [ [ a, b, ] c, ] d, e, f=3, [ g, h, [ i ] ] <- line + 01 2 3 4 5 6 <- state transitions + """ + # Before we've seen anything. + # Legal transitions: to LEFT_SQUARE_BEFORE or REQUIRED + START = 0 + + # Left square backets before required params. + LEFT_SQUARE_BEFORE = 1 + + # In a group, before required params. + GROUP_BEFORE = 2 + + # Required params, positional-or-keyword or positional-only (we + # don't know yet). Renumber left groups! + REQUIRED = 3 + + # Positional-or-keyword or positional-only params that now must have + # default values. + OPTIONAL = 4 + + # In a group, after required params. + GROUP_AFTER = 5 + + # Right square brackets after required params. + RIGHT_SQUARE_AFTER = 6 + + class DSLParser: function: Function | None state: StateKeeper @@ -4331,7 +4362,7 @@ def reset(self) -> None: self.keyword_only = False self.positional_only = False self.group = 0 - self.parameter_state = self.ps_start + self.parameter_state: ParamState = ParamState.START self.seen_positional_with_default = False self.indent = IndentStack() self.kind = CALLABLE @@ -4726,22 +4757,8 @@ def state_modulename_name(self, line: str | None) -> None: # # These rules are enforced with a single state variable: # "parameter_state". (Previously the code was a miasma of ifs and - # separate boolean state variables.) The states are: - # - # [ [ a, b, ] c, ] d, e, f=3, [ g, h, [ i ] ] <- line - # 01 2 3 4 5 6 <- state transitions - # - # 0: ps_start. before we've seen anything. legal transitions are to 1 or 3. - # 1: ps_left_square_before. left square brackets before required parameters. - # 2: ps_group_before. in a group, before required parameters. - # 3: ps_required. required parameters, positional-or-keyword or positional-only - # (we don't know yet). (renumber left groups!) - # 4: ps_optional. positional-or-keyword or positional-only parameters that - # now must have default values. - # 5: ps_group_after. in a group, after required parameters. - # 6: ps_right_square_after. right square brackets after required parameters. - ps_start, ps_left_square_before, ps_group_before, ps_required, \ - ps_optional, ps_group_after, ps_right_square_after = range(7) + # separate boolean state variables.) The states are defined in the + # ParamState class. def state_parameters_start(self, line: str | None) -> None: if not self.valid_line(line): @@ -4759,8 +4776,8 @@ def to_required(self): """ Transition to the "required" parameter state. """ - if self.parameter_state != self.ps_required: - self.parameter_state = self.ps_required + if self.parameter_state is not ParamState.REQUIRED: + self.parameter_state = ParamState.REQUIRED for p in self.function.parameters.values(): p.group = -p.group @@ -4793,17 +4810,18 @@ def state_parameter(self, line): self.parse_special_symbol(line) return - if self.parameter_state in (self.ps_start, self.ps_required): - self.to_required() - elif self.parameter_state == self.ps_left_square_before: - self.parameter_state = self.ps_group_before - elif self.parameter_state == self.ps_group_before: - if not self.group: + match self.parameter_state: + case ParamState.START | ParamState.REQUIRED: self.to_required() - elif self.parameter_state in (self.ps_group_after, self.ps_optional): - pass - else: - fail("Function " + self.function.name + " has an unsupported group configuration. (Unexpected state " + str(self.parameter_state) + ".a)") + case ParamState.LEFT_SQUARE_BEFORE: + self.parameter_state = ParamState.GROUP_BEFORE + case ParamState.GROUP_BEFORE: + if not self.group: + self.to_required() + case ParamState.GROUP_AFTER | ParamState.OPTIONAL: + pass + case st: + fail(f"Function {self.function.name} has an unsupported group configuration. (Unexpected state {st}.a)") # handle "as" for parameters too c_name = None @@ -4863,8 +4881,9 @@ def state_parameter(self, line): name, legacy, kwargs = self.parse_converter(parameter.annotation) if not default: - if self.parameter_state == self.ps_optional: - fail("Can't have a parameter without a default (" + repr(parameter_name) + ")\nafter a parameter with a default!") + if self.parameter_state is ParamState.OPTIONAL: + fail(f"Can't have a parameter without a default ({parameter_name!r})\n" + "after a parameter with a default!") if is_vararg: value = NULL kwargs.setdefault('c_default', "NULL") @@ -4876,8 +4895,8 @@ def state_parameter(self, line): if is_vararg: fail("Vararg can't take a default value!") - if self.parameter_state == self.ps_required: - self.parameter_state = self.ps_optional + if self.parameter_state is ParamState.REQUIRED: + self.parameter_state = ParamState.OPTIONAL default = default.strip() bad = False ast_input = f"x = {default}" @@ -5001,14 +5020,14 @@ def bad_node(self, node): if isinstance(converter, self_converter): if len(self.function.parameters) == 1: - if (self.parameter_state != self.ps_required): + if self.parameter_state is not ParamState.REQUIRED: fail("A 'self' parameter cannot be marked optional.") if value is not unspecified: fail("A 'self' parameter cannot have a default value.") if self.group: fail("A 'self' parameter cannot be in an optional group.") kind = inspect.Parameter.POSITIONAL_ONLY - self.parameter_state = self.ps_start + self.parameter_state = ParamState.START self.function.parameters.clear() else: fail("A 'self' parameter, if specified, must be the very first thing in the parameter block.") @@ -5016,7 +5035,7 @@ def bad_node(self, node): if isinstance(converter, defining_class_converter): _lp = len(self.function.parameters) if _lp == 1: - if (self.parameter_state != self.ps_required): + if self.parameter_state is not ParamState.REQUIRED: fail("A 'defining_class' parameter cannot be marked optional.") if value is not unspecified: fail("A 'defining_class' parameter cannot have a default value.") @@ -5065,12 +5084,13 @@ def parse_special_symbol(self, symbol): fail("Function " + self.function.name + " uses '*' more than once.") self.keyword_only = True elif symbol == '[': - if self.parameter_state in (self.ps_start, self.ps_left_square_before): - self.parameter_state = self.ps_left_square_before - elif self.parameter_state in (self.ps_required, self.ps_group_after): - self.parameter_state = self.ps_group_after - else: - fail("Function " + self.function.name + " has an unsupported group configuration. (Unexpected state " + str(self.parameter_state) + ".b)") + match self.parameter_state: + case ParamState.START | ParamState.LEFT_SQUARE_BEFORE: + self.parameter_state = ParamState.LEFT_SQUARE_BEFORE + case ParamState.REQUIRED | ParamState.GROUP_AFTER: + self.parameter_state = ParamState.GROUP_AFTER + case st: + fail(f"Function {self.function.name} has an unsupported group configuration. (Unexpected state {st}.b)") self.group += 1 self.function.docstring_only = True elif symbol == ']': @@ -5079,20 +5099,27 @@ def parse_special_symbol(self, symbol): if not any(p.group == self.group for p in self.function.parameters.values()): fail("Function " + self.function.name + " has an empty group.\nAll groups must contain at least one parameter.") self.group -= 1 - if self.parameter_state in (self.ps_left_square_before, self.ps_group_before): - self.parameter_state = self.ps_group_before - elif self.parameter_state in (self.ps_group_after, self.ps_right_square_after): - self.parameter_state = self.ps_right_square_after - else: - fail("Function " + self.function.name + " has an unsupported group configuration. (Unexpected state " + str(self.parameter_state) + ".c)") + match self.parameter_state: + case ParamState.LEFT_SQUARE_BEFORE | ParamState.GROUP_BEFORE: + self.parameter_state = ParamState.GROUP_BEFORE + case ParamState.GROUP_AFTER | ParamState.RIGHT_SQUARE_AFTER: + self.parameter_state = ParamState.RIGHT_SQUARE_AFTER + case st: + fail(f"Function {self.function.name} has an unsupported group configuration. (Unexpected state {st}.c)") elif symbol == '/': if self.positional_only: fail("Function " + self.function.name + " uses '/' more than once.") self.positional_only = True - # ps_required and ps_optional are allowed here, that allows positional-only without option groups + # REQUIRED and OPTIONAL are allowed here, that allows positional-only without option groups # to work (and have default values!) - if (self.parameter_state not in (self.ps_required, self.ps_optional, self.ps_right_square_after, self.ps_group_before)) or self.group: - fail("Function " + self.function.name + " has an unsupported group configuration. (Unexpected state " + str(self.parameter_state) + ".d)") + allowed = { + ParamState.REQUIRED, + ParamState.OPTIONAL, + ParamState.RIGHT_SQUARE_AFTER, + ParamState.GROUP_BEFORE, + } + if (self.parameter_state not in allowed) or self.group: + fail(f"Function {self.function.name} has an unsupported group configuration. (Unexpected state {self.parameter_state}.d)") if self.keyword_only: fail("Function " + self.function.name + " mixes keyword-only and positional-only parameters, which is unsupported.") # fixup preceding parameters From 3ee8dac7a1b3882aa3aac7703bdae2de7b6402ad Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 3 Jul 2023 23:57:20 +0200 Subject: [PATCH 08/39] gh-106368: Clean up Argument Clinic tests (#106373) --- Lib/test/test_clinic.py | 796 ++++++++++++++++++++++------------------ 1 file changed, 440 insertions(+), 356 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 51d2ac972752fd..b3602887ab6352 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -4,6 +4,7 @@ from test import support, test_tools from test.support import os_helper +from textwrap import dedent from unittest import TestCase import collections import inspect @@ -171,43 +172,43 @@ def test_solo_newline(self): def test_no_substitution(self): self._test(""" abc - """, """ + """, """ abc - """) + """) def test_empty_substitution(self): self._test(""" abc {name} def - """, """ + """, """ abc def - """, name='') + """, name='') def test_single_line_substitution(self): self._test(""" abc {name} def - """, """ + """, """ abc GARGLE def - """, name='GARGLE') + """, name='GARGLE') def test_multiline_substitution(self): self._test(""" abc {name} def - """, """ + """, """ abc bingle bungle def - """, name='bingle\nbungle\n') + """, name='bingle\nbungle\n') class InertParser: def __init__(self, clinic): @@ -240,9 +241,9 @@ def round_trip(self, input): def test_round_trip_1(self): self.round_trip(""" - verbatim text here - lah dee dah -""") + verbatim text here + lah dee dah + """) def test_round_trip_2(self): self.round_trip(""" verbatim text here @@ -286,22 +287,38 @@ def test_clinic_1(self): class ClinicParserTest(TestCase): + def checkDocstring(self, fn, expected): + self.assertTrue(hasattr(fn, "docstring")) + self.assertEqual(fn.docstring.strip(), + dedent(expected).strip()) + def test_trivial(self): parser = DSLParser(FakeClinic()) - block = clinic.Block("module os\nos.access") + block = clinic.Block(""" + module os + os.access + """) parser.parse(block) module, function = block.signatures self.assertEqual("access", function.name) self.assertEqual("os", module.name) def test_ignore_line(self): - block = self.parse("#\nmodule os\nos.access") + block = self.parse(dedent(""" + # + module os + os.access + """)) module, function = block.signatures self.assertEqual("access", function.name) self.assertEqual("os", module.name) def test_param(self): - function = self.parse_function("module os\nos.access\n path: int") + function = self.parse_function(""" + module os + os.access + path: int + """) self.assertEqual("access", function.name) self.assertEqual(2, len(function.parameters)) p = function.parameters['path'] @@ -309,236 +326,296 @@ def test_param(self): self.assertIsInstance(p.converter, clinic.int_converter) def test_param_default(self): - function = self.parse_function("module os\nos.access\n follow_symlinks: bool = True") + function = self.parse_function(""" + module os + os.access + follow_symlinks: bool = True + """) p = function.parameters['follow_symlinks'] self.assertEqual(True, p.default) def test_param_with_continuations(self): - function = self.parse_function("module os\nos.access\n follow_symlinks: \\\n bool \\\n =\\\n True") + function = self.parse_function(r""" + module os + os.access + follow_symlinks: \ + bool \ + = \ + True + """) p = function.parameters['follow_symlinks'] self.assertEqual(True, p.default) def test_param_default_expression(self): - function = self.parse_function("module os\nos.access\n follow_symlinks: int(c_default='MAXSIZE') = sys.maxsize") + function = self.parse_function(""" + module os + os.access + follow_symlinks: int(c_default='MAXSIZE') = sys.maxsize + """) p = function.parameters['follow_symlinks'] self.assertEqual(sys.maxsize, p.default) self.assertEqual("MAXSIZE", p.converter.c_default) - s = self.parse_function_should_fail("module os\nos.access\n follow_symlinks: int = sys.maxsize") - self.assertEqual(s, "Error on line 0:\nWhen you specify a named constant ('sys.maxsize') as your default value,\nyou MUST specify a valid c_default.\n") + expected_msg = ( + "Error on line 0:\n" + "When you specify a named constant ('sys.maxsize') as your default value,\n" + "you MUST specify a valid c_default.\n" + ) + out = self.parse_function_should_fail(""" + module os + os.access + follow_symlinks: int = sys.maxsize + """) + self.assertEqual(out, expected_msg) def test_param_no_docstring(self): function = self.parse_function(""" -module os -os.access - follow_symlinks: bool = True - something_else: str = ''""") + module os + os.access + follow_symlinks: bool = True + something_else: str = '' + """) p = function.parameters['follow_symlinks'] self.assertEqual(3, len(function.parameters)) - self.assertIsInstance(function.parameters['something_else'].converter, clinic.str_converter) + conv = function.parameters['something_else'].converter + self.assertIsInstance(conv, clinic.str_converter) def test_param_default_parameters_out_of_order(self): - s = self.parse_function_should_fail(""" -module os -os.access - follow_symlinks: bool = True - something_else: str""") - self.assertEqual(s, """Error on line 0: -Can't have a parameter without a default ('something_else') -after a parameter with a default! -""") + expected_msg = ( + "Error on line 0:\n" + "Can't have a parameter without a default ('something_else')\n" + "after a parameter with a default!\n" + ) + out = self.parse_function_should_fail(""" + module os + os.access + follow_symlinks: bool = True + something_else: str""") + self.assertEqual(out, expected_msg) def disabled_test_converter_arguments(self): - function = self.parse_function("module os\nos.access\n path: path_t(allow_fd=1)") + function = self.parse_function(""" + module os + os.access + path: path_t(allow_fd=1) + """) p = function.parameters['path'] self.assertEqual(1, p.converter.args['allow_fd']) def test_function_docstring(self): function = self.parse_function(""" -module os -os.stat as os_stat_fn + module os + os.stat as os_stat_fn - path: str - Path to be examined + path: str + Path to be examined -Perform a stat system call on the given path.""") - self.assertEqual(""" -stat($module, /, path) --- + Perform a stat system call on the given path. + """) + self.checkDocstring(function, """ + stat($module, /, path) + -- -Perform a stat system call on the given path. + Perform a stat system call on the given path. - path - Path to be examined -""".strip(), function.docstring) + path + Path to be examined + """) def test_explicit_parameters_in_docstring(self): - function = self.parse_function(""" -module foo -foo.bar - x: int - Documentation for x. - y: int + function = self.parse_function(dedent(""" + module foo + foo.bar + x: int + Documentation for x. + y: int -This is the documentation for foo. + This is the documentation for foo. -Okay, we're done here. -""") - self.assertEqual(""" -bar($module, /, x, y) --- + Okay, we're done here. + """)) + self.checkDocstring(function, """ + bar($module, /, x, y) + -- -This is the documentation for foo. + This is the documentation for foo. - x - Documentation for x. + x + Documentation for x. -Okay, we're done here. -""".strip(), function.docstring) + Okay, we're done here. + """) def test_parser_regression_special_character_in_parameter_column_of_docstring_first_line(self): - function = self.parse_function(""" -module os -os.stat - path: str -This/used to break Clinic! -""") - self.assertEqual("stat($module, /, path)\n--\n\nThis/used to break Clinic!", function.docstring) + function = self.parse_function(dedent(""" + module os + os.stat + path: str + This/used to break Clinic! + """)) + self.checkDocstring(function, """ + stat($module, /, path) + -- + + This/used to break Clinic! + """) def test_c_name(self): - function = self.parse_function("module os\nos.stat as os_stat_fn") + function = self.parse_function(""" + module os + os.stat as os_stat_fn + """) self.assertEqual("os_stat_fn", function.c_basename) def test_return_converter(self): - function = self.parse_function("module os\nos.stat -> int") + function = self.parse_function(""" + module os + os.stat -> int + """) self.assertIsInstance(function.return_converter, clinic.int_return_converter) def test_star(self): - function = self.parse_function("module os\nos.access\n *\n follow_symlinks: bool = True") + function = self.parse_function(""" + module os + os.access + * + follow_symlinks: bool = True + """) p = function.parameters['follow_symlinks'] self.assertEqual(inspect.Parameter.KEYWORD_ONLY, p.kind) self.assertEqual(0, p.group) def test_group(self): - function = self.parse_function("module window\nwindow.border\n [\n ls : int\n ]\n /\n") + function = self.parse_function(""" + module window + window.border + [ + ls: int + ] + / + """) p = function.parameters['ls'] self.assertEqual(1, p.group) def test_left_group(self): function = self.parse_function(""" -module curses -curses.addch - [ - y: int - Y-coordinate. - x: int - X-coordinate. - ] - ch: char - Character to add. - [ - attr: long - Attributes for the character. - ] - / -""") - for name, group in ( + module curses + curses.addch + [ + y: int + Y-coordinate. + x: int + X-coordinate. + ] + ch: char + Character to add. + [ + attr: long + Attributes for the character. + ] + / + """) + dataset = ( ('y', -1), ('x', -1), ('ch', 0), ('attr', 1), - ): - p = function.parameters[name] - self.assertEqual(p.group, group) - self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY) - self.assertEqual(function.docstring.strip(), """ -addch([y, x,] ch, [attr]) - - - y - Y-coordinate. - x - X-coordinate. - ch - Character to add. - attr - Attributes for the character. - """.strip()) + ) + for name, group in dataset: + with self.subTest(name=name, group=group): + p = function.parameters[name] + self.assertEqual(p.group, group) + self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY) + self.checkDocstring(function, """ + addch([y, x,] ch, [attr]) + + + y + Y-coordinate. + x + X-coordinate. + ch + Character to add. + attr + Attributes for the character. + """) def test_nested_groups(self): function = self.parse_function(""" -module curses -curses.imaginary - [ - [ - y1: int - Y-coordinate. - y2: int - Y-coordinate. - ] - x1: int - X-coordinate. - x2: int - X-coordinate. - ] - ch: char - Character to add. - [ - attr1: long - Attributes for the character. - attr2: long - Attributes for the character. - attr3: long - Attributes for the character. - [ - attr4: long - Attributes for the character. - attr5: long - Attributes for the character. - attr6: long - Attributes for the character. - ] - ] - / -""") - for name, group in ( + module curses + curses.imaginary + [ + [ + y1: int + Y-coordinate. + y2: int + Y-coordinate. + ] + x1: int + X-coordinate. + x2: int + X-coordinate. + ] + ch: char + Character to add. + [ + attr1: long + Attributes for the character. + attr2: long + Attributes for the character. + attr3: long + Attributes for the character. + [ + attr4: long + Attributes for the character. + attr5: long + Attributes for the character. + attr6: long + Attributes for the character. + ] + ] + / + """) + dataset = ( ('y1', -2), ('y2', -2), ('x1', -1), ('x2', -1), ('ch', 0), ('attr1', 1), ('attr2', 1), ('attr3', 1), ('attr4', 2), ('attr5', 2), ('attr6', 2), - ): - p = function.parameters[name] - self.assertEqual(p.group, group) - self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY) - - self.assertEqual(function.docstring.strip(), """ -imaginary([[y1, y2,] x1, x2,] ch, [attr1, attr2, attr3, [attr4, attr5, - attr6]]) - - - y1 - Y-coordinate. - y2 - Y-coordinate. - x1 - X-coordinate. - x2 - X-coordinate. - ch - Character to add. - attr1 - Attributes for the character. - attr2 - Attributes for the character. - attr3 - Attributes for the character. - attr4 - Attributes for the character. - attr5 - Attributes for the character. - attr6 - Attributes for the character. - """.strip()) + ) + for name, group in dataset: + with self.subTest(name=name, group=group): + p = function.parameters[name] + self.assertEqual(p.group, group) + self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY) + + self.checkDocstring(function, """ + imaginary([[y1, y2,] x1, x2,] ch, [attr1, attr2, attr3, [attr4, attr5, + attr6]]) + + + y1 + Y-coordinate. + y2 + Y-coordinate. + x1 + X-coordinate. + x2 + X-coordinate. + ch + Character to add. + attr1 + Attributes for the character. + attr2 + Attributes for the character. + attr3 + Attributes for the character. + attr4 + Attributes for the character. + attr5 + Attributes for the character. + attr6 + Attributes for the character. + """) def parse_function_should_fail(self, s): with support.captured_stdout() as stdout: @@ -547,104 +624,108 @@ def parse_function_should_fail(self, s): return stdout.getvalue() def test_disallowed_grouping__two_top_groups_on_left(self): - s = self.parse_function_should_fail(""" -module foo -foo.two_top_groups_on_left - [ - group1 : int - ] - [ - group2 : int - ] - param: int - """) - self.assertEqual(s, - ('Error on line 0:\n' - 'Function two_top_groups_on_left has an unsupported group configuration. (Unexpected state 2.b)\n')) + expected_msg = ( + 'Error on line 0:\n' + 'Function two_top_groups_on_left has an unsupported group ' + 'configuration. (Unexpected state 2.b)\n' + ) + out = self.parse_function_should_fail(""" + module foo + foo.two_top_groups_on_left + [ + group1 : int + ] + [ + group2 : int + ] + param: int + """) + self.assertEqual(out, expected_msg) def test_disallowed_grouping__two_top_groups_on_right(self): self.parse_function_should_fail(""" -module foo -foo.two_top_groups_on_right - param: int - [ - group1 : int - ] - [ - group2 : int - ] - """) + module foo + foo.two_top_groups_on_right + param: int + [ + group1 : int + ] + [ + group2 : int + ] + """) def test_disallowed_grouping__parameter_after_group_on_right(self): self.parse_function_should_fail(""" -module foo -foo.parameter_after_group_on_right - param: int - [ - [ - group1 : int - ] - group2 : int - ] - """) + module foo + foo.parameter_after_group_on_right + param: int + [ + [ + group1 : int + ] + group2 : int + ] + """) def test_disallowed_grouping__group_after_parameter_on_left(self): self.parse_function_should_fail(""" -module foo -foo.group_after_parameter_on_left - [ - group2 : int - [ - group1 : int - ] - ] - param: int - """) + module foo + foo.group_after_parameter_on_left + [ + group2 : int + [ + group1 : int + ] + ] + param: int + """) def test_disallowed_grouping__empty_group_on_left(self): self.parse_function_should_fail(""" -module foo -foo.empty_group - [ - [ - ] - group2 : int - ] - param: int - """) + module foo + foo.empty_group + [ + [ + ] + group2 : int + ] + param: int + """) def test_disallowed_grouping__empty_group_on_right(self): self.parse_function_should_fail(""" -module foo -foo.empty_group - param: int - [ - [ - ] - group2 : int - ] - """) + module foo + foo.empty_group + param: int + [ + [ + ] + group2 : int + ] + """) def test_no_parameters(self): function = self.parse_function(""" -module foo -foo.bar + module foo + foo.bar -Docstring + Docstring -""") + """) self.assertEqual("bar($module, /)\n--\n\nDocstring", function.docstring) self.assertEqual(1, len(function.parameters)) # self! def test_init_with_no_parameters(self): function = self.parse_function(""" -module foo -class foo.Bar "unused" "notneeded" -foo.Bar.__init__ + module foo + class foo.Bar "unused" "notneeded" + foo.Bar.__init__ -Docstring + Docstring + + """, signatures_in_block=3, function_index=2) -""", signatures_in_block=3, function_index=2) # self is not in the signature self.assertEqual("Bar()\n--\n\nDocstring", function.docstring) # but it *is* a parameter @@ -652,113 +733,117 @@ class foo.Bar "unused" "notneeded" def test_illegal_module_line(self): self.parse_function_should_fail(""" -module foo -foo.bar => int - / -""") + module foo + foo.bar => int + / + """) def test_illegal_c_basename(self): self.parse_function_should_fail(""" -module foo -foo.bar as 935 - / -""") + module foo + foo.bar as 935 + / + """) def test_single_star(self): self.parse_function_should_fail(""" -module foo -foo.bar - * - * -""") + module foo + foo.bar + * + * + """) def test_parameters_required_after_star_without_initial_parameters_or_docstring(self): self.parse_function_should_fail(""" -module foo -foo.bar - * -""") + module foo + foo.bar + * + """) def test_parameters_required_after_star_without_initial_parameters_with_docstring(self): self.parse_function_should_fail(""" -module foo -foo.bar - * -Docstring here. -""") + module foo + foo.bar + * + Docstring here. + """) def test_parameters_required_after_star_with_initial_parameters_without_docstring(self): self.parse_function_should_fail(""" -module foo -foo.bar - this: int - * -""") + module foo + foo.bar + this: int + * + """) def test_parameters_required_after_star_with_initial_parameters_and_docstring(self): self.parse_function_should_fail(""" -module foo -foo.bar - this: int - * -Docstring. -""") + module foo + foo.bar + this: int + * + Docstring. + """) def test_single_slash(self): self.parse_function_should_fail(""" -module foo -foo.bar - / - / -""") + module foo + foo.bar + / + / + """) def test_mix_star_and_slash(self): self.parse_function_should_fail(""" -module foo -foo.bar - x: int - y: int - * - z: int - / -""") + module foo + foo.bar + x: int + y: int + * + z: int + / + """) def test_parameters_not_permitted_after_slash_for_now(self): self.parse_function_should_fail(""" -module foo -foo.bar - / - x: int -""") + module foo + foo.bar + / + x: int + """) def test_parameters_no_more_than_one_vararg(self): - s = self.parse_function_should_fail(""" -module foo -foo.bar - *vararg1: object - *vararg2: object -""") - self.assertEqual(s, "Error on line 0:\nToo many var args\n") + expected_msg = ( + "Error on line 0:\n" + "Too many var args\n" + ) + out = self.parse_function_should_fail(""" + module foo + foo.bar + *vararg1: object + *vararg2: object + """) + self.assertEqual(out, expected_msg) def test_function_not_at_column_0(self): function = self.parse_function(""" - module foo - foo.bar - x: int - Nested docstring here, goeth. - * - y: str - Not at column 0! -""") - self.assertEqual(""" -bar($module, /, x, *, y) --- + module foo + foo.bar + x: int + Nested docstring here, goeth. + * + y: str + Not at column 0! + """) + self.checkDocstring(function, """ + bar($module, /, x, *, y) + -- -Not at column 0! + Not at column 0! - x - Nested docstring here, goeth. -""".strip(), function.docstring) + x + Nested docstring here, goeth. + """) def test_directive(self): c = FakeClinic() @@ -772,46 +857,39 @@ def test_directive(self): def test_legacy_converters(self): block = self.parse('module os\nos.access\n path: "s"') module, function = block.signatures - self.assertIsInstance((function.parameters['path']).converter, clinic.str_converter) + conv = (function.parameters['path']).converter + self.assertIsInstance(conv, clinic.str_converter) def test_legacy_converters_non_string_constant_annotation(self): - expected_failure_message = """\ -Error on line 0: -Annotations must be either a name, a function call, or a string. -""" - - s = self.parse_function_should_fail('module os\nos.access\n path: 42') - self.assertEqual(s, expected_failure_message) - - s = self.parse_function_should_fail('module os\nos.access\n path: 42.42') - self.assertEqual(s, expected_failure_message) - - s = self.parse_function_should_fail('module os\nos.access\n path: 42j') - self.assertEqual(s, expected_failure_message) - - s = self.parse_function_should_fail('module os\nos.access\n path: b"42"') - self.assertEqual(s, expected_failure_message) - - def test_other_bizarre_things_in_annotations_fail(self): - expected_failure_message = """\ -Error on line 0: -Annotations must be either a name, a function call, or a string. -""" - - s = self.parse_function_should_fail( - 'module os\nos.access\n path: {"some": "dictionary"}' + expected_failure_message = ( + "Error on line 0:\n" + "Annotations must be either a name, a function call, or a string.\n" ) - self.assertEqual(s, expected_failure_message) - - s = self.parse_function_should_fail( - 'module os\nos.access\n path: ["list", "of", "strings"]' + dataset = ( + 'module os\nos.access\n path: 42', + 'module os\nos.access\n path: 42.42', + 'module os\nos.access\n path: 42j', + 'module os\nos.access\n path: b"42"', ) - self.assertEqual(s, expected_failure_message) + for block in dataset: + with self.subTest(block=block): + out = self.parse_function_should_fail(block) + self.assertEqual(out, expected_failure_message) - s = self.parse_function_should_fail( - 'module os\nos.access\n path: (x for x in range(42))' + def test_other_bizarre_things_in_annotations_fail(self): + expected_failure_message = ( + "Error on line 0:\n" + "Annotations must be either a name, a function call, or a string.\n" + ) + dataset = ( + 'module os\nos.access\n path: {"some": "dictionary"}', + 'module os\nos.access\n path: ["list", "of", "strings"]', + 'module os\nos.access\n path: (x for x in range(42))', ) - self.assertEqual(s, expected_failure_message) + for block in dataset: + with self.subTest(block=block): + out = self.parse_function_should_fail(block) + self.assertEqual(out, expected_failure_message) def test_kwarg_splats_disallowed_in_function_call_annotations(self): expected_error_msg = ( @@ -945,10 +1023,16 @@ def test_scaffolding(self): self.assertEqual(repr(clinic.NULL), '') # test that fail fails + expected = ( + 'Error in file "clown.txt" on line 69:\n' + 'The igloos are melting!\n' + ) with support.captured_stdout() as stdout: with self.assertRaises(SystemExit): - clinic.fail('The igloos are melting!', filename='clown.txt', line_number=69) - self.assertEqual(stdout.getvalue(), 'Error in file "clown.txt" on line 69:\nThe igloos are melting!\n') + clinic.fail('The igloos are melting!', + filename='clown.txt', line_number=69) + actual = stdout.getvalue() + self.assertEqual(actual, expected) class ClinicExternalTest(TestCase): From b24479dcba6e8952039066564d448d5ac4b37bef Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 4 Jul 2023 00:10:46 +0200 Subject: [PATCH 09/39] gh-104050: Annotate more Argument Clinic DSLParser state methods (#106376) Annotate the following methods: - state_parameter() - state_parameter_docstring_start() Co-authored-by: Alex Waygood --- Tools/clinic/clinic.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 6380b9ce38f5f6..a07fcbd8cabf76 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -4781,14 +4781,16 @@ def to_required(self): for p in self.function.parameters.values(): p.group = -p.group - def state_parameter(self, line): - if self.parameter_continuation: - line = self.parameter_continuation + ' ' + line.lstrip() - self.parameter_continuation = '' + def state_parameter(self, line: str | None) -> None: + assert isinstance(self.function, Function) if not self.valid_line(line): return + if self.parameter_continuation: + line = self.parameter_continuation + ' ' + line.lstrip() + self.parameter_continuation = '' + assert self.indent.depth == 2 indent = self.indent.infer(line) if indent == -1: @@ -4839,6 +4841,7 @@ def state_parameter(self, line): fields[0] = name line = ' '.join(fields) + default: str | None base, equals, default = line.rpartition('=') if not equals: base = default @@ -4861,7 +4864,9 @@ def state_parameter(self, line): if not module: fail("Function " + self.function.name + " has an invalid parameter declaration:\n\t" + line) - function_args = module.body[0].args + function = module.body[0] + assert isinstance(function, ast.FunctionDef) + function_args = function.args if len(function_args.args) > 1: fail("Function " + self.function.name + " has an invalid parameter declaration (comma?):\n\t" + line) @@ -4884,6 +4889,7 @@ def state_parameter(self, line): if self.parameter_state is ParamState.OPTIONAL: fail(f"Can't have a parameter without a default ({parameter_name!r})\n" "after a parameter with a default!") + value: Sentinels | Null if is_vararg: value = NULL kwargs.setdefault('c_default', "NULL") @@ -4946,8 +4952,11 @@ def bad_node(self, node): if bad: fail("Unsupported expression as default value: " + repr(default)) - expr = module.body[0].value + assignment = module.body[0] + assert isinstance(assignment, ast.Assign) + expr = assignment.value # mild hack: explicitly support NULL as a default value + c_default: str | None if isinstance(expr, ast.Name) and expr.id == 'NULL': value = NULL py_default = '' @@ -4964,7 +4973,7 @@ def bad_node(self, node): value = unknown elif isinstance(expr, ast.Attribute): a = [] - n = expr + n: ast.expr | ast.Attribute = expr while isinstance(n, ast.Attribute): a.append(n.attr) n = n.value @@ -4984,7 +4993,7 @@ def bad_node(self, node): else: value = ast.literal_eval(expr) py_default = repr(value) - if isinstance(value, (bool, None.__class__)): + if isinstance(value, (bool, NoneType)): c_default = "Py_" + py_default elif isinstance(value, str): c_default = c_repr(value) @@ -5011,6 +5020,7 @@ def bad_node(self, node): # but the parameter object gets the python name converter = dict[name](c_name or parameter_name, parameter_name, self.function, value, **kwargs) + kind: inspect._ParameterKind if is_vararg: kind = inspect.Parameter.VAR_POSITIONAL elif self.keyword_only: @@ -5130,7 +5140,7 @@ def parse_special_symbol(self, symbol): fail("Function " + self.function.name + " mixes keyword-only and positional-only parameters, which is unsupported.") p.kind = inspect.Parameter.POSITIONAL_ONLY - def state_parameter_docstring_start(self, line: str) -> None: + def state_parameter_docstring_start(self, line: str | None) -> None: self.parameter_docstring_indent = len(self.indent.margin) assert self.indent.depth == 3 return self.next(self.state_parameter_docstring, line) From 506cfdf141f03186d5cdf9bb31caa40294eba4e5 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 4 Jul 2023 00:35:46 +0200 Subject: [PATCH 10/39] gh-106320: Remove more private _PyUnicode C API functions (#106382) Remove more private _PyUnicode C API functions: move them to the internal C API (pycore_unicodeobject.h). No longer export most pycore_unicodeobject.h functions. --- Include/cpython/unicodeobject.h | 69 ------------------------ Include/internal/pycore_unicodeobject.h | 70 ++++++++++++++++++++++++- Python/future.c | 1 + 3 files changed, 70 insertions(+), 70 deletions(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index c5892a80d2c54d..fcd9c28e030c1f 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -394,11 +394,6 @@ static inline int PyUnicode_READY(PyObject* Py_UNUSED(op)) } #define PyUnicode_READY(op) PyUnicode_READY(_PyObject_CAST(op)) -/* Get a copy of a Unicode string. */ -PyAPI_FUNC(PyObject*) _PyUnicode_Copy( - PyObject *unicode - ); - /* Copy character from one unicode object into another, this function performs character conversion when necessary and falls back to memcpy() if possible. @@ -425,17 +420,6 @@ PyAPI_FUNC(Py_ssize_t) PyUnicode_CopyCharacters( Py_ssize_t how_many ); -/* Unsafe version of PyUnicode_CopyCharacters(): don't check arguments and so - may crash if parameters are invalid (e.g. if the output string - is too short). */ -PyAPI_FUNC(void) _PyUnicode_FastCopyCharacters( - PyObject *to, - Py_ssize_t to_start, - PyObject *from, - Py_ssize_t from_start, - Py_ssize_t how_many - ); - /* Fill a string with a character: write fill_char into unicode[start:start+length]. @@ -451,15 +435,6 @@ PyAPI_FUNC(Py_ssize_t) PyUnicode_Fill( Py_UCS4 fill_char ); -/* Unsafe version of PyUnicode_Fill(): don't check arguments and so may crash - if parameters are invalid (e.g. if length is longer than the string). */ -PyAPI_FUNC(void) _PyUnicode_FastFill( - PyObject *unicode, - Py_ssize_t start, - Py_ssize_t length, - Py_UCS4 fill_char - ); - /* Create a new string from a buffer of Py_UCS1, Py_UCS2 or Py_UCS4 characters. Scan the string to find the maximum character. */ PyAPI_FUNC(PyObject*) PyUnicode_FromKindAndData( @@ -467,19 +442,6 @@ PyAPI_FUNC(PyObject*) PyUnicode_FromKindAndData( const void *buffer, Py_ssize_t size); -/* Create a new string from a buffer of ASCII characters. - WARNING: Don't check if the string contains any non-ASCII character. */ -PyAPI_FUNC(PyObject*) _PyUnicode_FromASCII( - const char *buffer, - Py_ssize_t size); - -/* Compute the maximum character of the substring unicode[start:end]. - Return 127 for an empty string. */ -PyAPI_FUNC(Py_UCS4) _PyUnicode_FindMaxChar ( - PyObject *unicode, - Py_ssize_t start, - Py_ssize_t end); - /* --- Manage the default encoding ---------------------------------------- */ /* Returns a pointer to the default encoding (UTF-8) of the @@ -618,37 +580,6 @@ PyAPI_FUNC(PyObject*) _PyUnicode_TransformDecimalAndSpaceToASCII( PyObject *unicode /* Unicode object */ ); -/* --- Methods & Slots ---------------------------------------------------- */ - -PyAPI_FUNC(PyObject *) _PyUnicode_JoinArray( - PyObject *separator, - PyObject *const *items, - Py_ssize_t seqlen - ); - -/* Test whether a unicode is equal to ASCII identifier. Return 1 if true, - 0 otherwise. The right argument must be ASCII identifier. - Any error occurs inside will be cleared before return. */ -PyAPI_FUNC(int) _PyUnicode_EqualToASCIIId( - PyObject *left, /* Left string */ - _Py_Identifier *right /* Right identifier */ - ); - -/* Test whether a unicode is equal to ASCII string. Return 1 if true, - 0 otherwise. The right argument must be ASCII-encoded string. - Any error occurs inside will be cleared before return. */ -PyAPI_FUNC(int) _PyUnicode_EqualToASCIIString( - PyObject *left, - const char *right /* ASCII-encoded string */ - ); - -/* Externally visible for str.strip(unicode) */ -PyAPI_FUNC(PyObject *) _PyUnicode_XStrip( - PyObject *self, - int striptype, - PyObject *sepobj - ); - /* === Characters Type APIs =============================================== */ /* These should not be used directly. Use the Py_UNICODE_IS* and diff --git a/Include/internal/pycore_unicodeobject.h b/Include/internal/pycore_unicodeobject.h index a8c7f1957f3600..da01f57f962793 100644 --- a/Include/internal/pycore_unicodeobject.h +++ b/Include/internal/pycore_unicodeobject.h @@ -14,6 +14,44 @@ extern "C" { void _PyUnicode_ExactDealloc(PyObject *op); Py_ssize_t _PyUnicode_InternedSize(void); +/* Get a copy of a Unicode string. */ +PyAPI_FUNC(PyObject*) _PyUnicode_Copy( + PyObject *unicode + ); + +/* Unsafe version of PyUnicode_Fill(): don't check arguments and so may crash + if parameters are invalid (e.g. if length is longer than the string). */ +extern void _PyUnicode_FastFill( + PyObject *unicode, + Py_ssize_t start, + Py_ssize_t length, + Py_UCS4 fill_char + ); + +/* Unsafe version of PyUnicode_CopyCharacters(): don't check arguments and so + may crash if parameters are invalid (e.g. if the output string + is too short). */ +extern void _PyUnicode_FastCopyCharacters( + PyObject *to, + Py_ssize_t to_start, + PyObject *from, + Py_ssize_t from_start, + Py_ssize_t how_many + ); + +/* Create a new string from a buffer of ASCII characters. + WARNING: Don't check if the string contains any non-ASCII character. */ +extern PyObject* _PyUnicode_FromASCII( + const char *buffer, + Py_ssize_t size); + +/* Compute the maximum character of the substring unicode[start:end]. + Return 127 for an empty string. */ +extern Py_UCS4 _PyUnicode_FindMaxChar ( + PyObject *unicode, + Py_ssize_t start, + Py_ssize_t end); + /* --- _PyUnicodeWriter API ----------------------------------------------- */ typedef struct { @@ -141,10 +179,40 @@ PyAPI_FUNC(int) _PyUnicode_FormatAdvancedWriter( /* --- Methods & Slots ---------------------------------------------------- */ +extern PyObject* _PyUnicode_JoinArray( + PyObject *separator, + PyObject *const *items, + Py_ssize_t seqlen + ); + +/* Test whether a unicode is equal to ASCII identifier. Return 1 if true, + 0 otherwise. The right argument must be ASCII identifier. + Any error occurs inside will be cleared before return. */ +extern int _PyUnicode_EqualToASCIIId( + PyObject *left, /* Left string */ + _Py_Identifier *right /* Right identifier */ + ); + +/* Test whether a unicode is equal to ASCII string. Return 1 if true, + 0 otherwise. The right argument must be ASCII-encoded string. + Any error occurs inside will be cleared before return. */ +PyAPI_FUNC(int) _PyUnicode_EqualToASCIIString( + PyObject *left, + const char *right /* ASCII-encoded string */ + ); + +/* Externally visible for str.strip(unicode) */ +extern PyObject* _PyUnicode_XStrip( + PyObject *self, + int striptype, + PyObject *sepobj + ); + + /* Using explicit passed-in values, insert the thousands grouping into the string pointed to by buffer. For the argument descriptions, see Objects/stringlib/localeutil.h */ -PyAPI_FUNC(Py_ssize_t) _PyUnicode_InsertThousandsGrouping( +extern Py_ssize_t _PyUnicode_InsertThousandsGrouping( _PyUnicodeWriter *writer, Py_ssize_t n_buffer, PyObject *digits, diff --git a/Python/future.c b/Python/future.c index d56f7330964684..0dbc7ede20f324 100644 --- a/Python/future.c +++ b/Python/future.c @@ -1,5 +1,6 @@ #include "Python.h" #include "pycore_ast.h" // _PyAST_GetDocString() +#include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString() #define UNDEFINED_FUTURE_FEATURE "future feature %.100s is not defined" From f6d2bb18aba844f6bb5836797c72eb791b7f3644 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 4 Jul 2023 00:52:27 +0200 Subject: [PATCH 11/39] gh-86085: Remove _PyCodec_Forget() declaration (#106377) The code was already removed by: commit c9f696cb96d1c362d5cad871f61da520572d9b08. --- Include/internal/pycore_codecs.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/Include/internal/pycore_codecs.h b/Include/internal/pycore_codecs.h index 2f8d9d510019ba..a2465192eacd5e 100644 --- a/Include/internal/pycore_codecs.h +++ b/Include/internal/pycore_codecs.h @@ -6,8 +6,6 @@ extern "C" { extern PyObject* _PyCodec_Lookup(const char *encoding); -extern int _PyCodec_Forget(const char *encoding); - /* Text codec specific encoding and decoding API. Checks the encoding against a list of codecs which do not From 2e92edbf6de9578b30cca8e48c4bfb2ba71ae97a Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 4 Jul 2023 01:02:07 +0200 Subject: [PATCH 12/39] gh-106320: Remove private _PyImport C API functions (#106383) * Remove private _PyImport C API functions: move them to the internal C API (pycore_import.h). * No longer export most of these private functions. * _testcapi avoids private _PyImport_GetModuleAttrString(). --- Include/cpython/import.h | 20 -------------------- Include/internal/pycore_import.h | 20 ++++++++++++++++++++ Modules/_elementtree.c | 5 +++++ Modules/_sqlite/connection.c | 1 + Modules/_sqlite/module.c | 6 ++++++ Modules/_testcapimodule.c | 10 ++++++++-- Modules/cjkcodecs/cjkcodecs.h | 1 + Modules/pyexpat.c | 5 +++++ 8 files changed, 46 insertions(+), 22 deletions(-) diff --git a/Include/cpython/import.h b/Include/cpython/import.h index 2bca4ade4c4f2c..cdfdd15bfa48d2 100644 --- a/Include/cpython/import.h +++ b/Include/cpython/import.h @@ -4,23 +4,6 @@ PyMODINIT_FUNC PyInit__imp(void); -PyAPI_FUNC(int) _PyImport_IsInitialized(PyInterpreterState *); - -PyAPI_FUNC(PyObject *) _PyImport_GetModuleId(_Py_Identifier *name); -PyAPI_FUNC(int) _PyImport_SetModule(PyObject *name, PyObject *module); -PyAPI_FUNC(int) _PyImport_SetModuleString(const char *name, PyObject* module); - -PyAPI_FUNC(void) _PyImport_AcquireLock(PyInterpreterState *interp); -PyAPI_FUNC(int) _PyImport_ReleaseLock(PyInterpreterState *interp); - -PyAPI_FUNC(int) _PyImport_FixupBuiltin( - PyObject *mod, - const char *name, /* UTF-8 encoded string */ - PyObject *modules - ); -PyAPI_FUNC(int) _PyImport_FixupExtensionObject(PyObject*, PyObject *, - PyObject *, PyObject *); - struct _inittab { const char *name; /* ASCII encoded string */ PyObject* (*initfunc)(void); @@ -41,6 +24,3 @@ struct _frozen { collection of frozen modules: */ PyAPI_DATA(const struct _frozen *) PyImport_FrozenModules; - -PyAPI_DATA(PyObject *) _PyImport_GetModuleAttr(PyObject *, PyObject *); -PyAPI_DATA(PyObject *) _PyImport_GetModuleAttrString(const char *, const char *); diff --git a/Include/internal/pycore_import.h b/Include/internal/pycore_import.h index ee93f7d99d9155..457a654aff4644 100644 --- a/Include/internal/pycore_import.h +++ b/Include/internal/pycore_import.h @@ -7,6 +7,26 @@ extern "C" { #include "pycore_time.h" // _PyTime_t +extern int _PyImport_IsInitialized(PyInterpreterState *); + +PyAPI_FUNC(PyObject *) _PyImport_GetModuleId(_Py_Identifier *name); +PyAPI_FUNC(int) _PyImport_SetModule(PyObject *name, PyObject *module); +PyAPI_FUNC(int) _PyImport_SetModuleString(const char *name, PyObject* module); + +extern void _PyImport_AcquireLock(PyInterpreterState *interp); +extern int _PyImport_ReleaseLock(PyInterpreterState *interp); + +extern int _PyImport_FixupBuiltin( + PyObject *mod, + const char *name, /* UTF-8 encoded string */ + PyObject *modules + ); +extern int _PyImport_FixupExtensionObject(PyObject*, PyObject *, + PyObject *, PyObject *); + +PyAPI_DATA(PyObject *) _PyImport_GetModuleAttr(PyObject *, PyObject *); +PyAPI_DATA(PyObject *) _PyImport_GetModuleAttrString(const char *, const char *); + struct _import_runtime_state { /* The builtin modules (defined in config.c). */ diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index 48280690a707a4..3e742e067e7db1 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -11,7 +11,12 @@ *-------------------------------------------------------------------- */ +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif + #include "Python.h" +#include "pycore_import.h" // _PyImport_GetModuleAttrString() #include "structmember.h" // PyMemberDef #include "expat.h" #include "pyexpat.h" diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 967ba2812080e5..d71cef14779e51 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -33,6 +33,7 @@ #include "blob.h" #include "prepare_protocol.h" #include "util.h" +#include "pycore_import.h" // _PyImport_GetModuleAttrString() #include "pycore_weakref.h" // _PyWeakref_IS_DEAD() #include diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c index ea4d8c58b7ee0e..368e581b4f3355 100644 --- a/Modules/_sqlite/module.c +++ b/Modules/_sqlite/module.c @@ -21,6 +21,10 @@ * 3. This notice may not be removed or altered from any source distribution. */ +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif + #include "connection.h" #include "statement.h" #include "cursor.h" @@ -29,6 +33,8 @@ #include "row.h" #include "blob.h" +#include "pycore_import.h" // _PyImport_GetModuleAttrString() + #if SQLITE_VERSION_NUMBER < 3015002 #error "SQLite 3.15.2 or higher required" #endif diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index ce1131743eb2a4..d1044b5445202d 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -1267,9 +1267,15 @@ test_pep3118_obsolete_write_locks(PyObject* self, PyObject *Py_UNUSED(ignored)) if (ret != -1 || match == 0) goto error; + PyObject *mod_io = PyImport_ImportModule("_io"); + if (mod_io == NULL) { + return NULL; + } + /* bytesiobuf_getbuffer() */ - PyTypeObject *type = (PyTypeObject *)_PyImport_GetModuleAttrString( - "_io", "_BytesIOBuffer"); + PyTypeObject *type = (PyTypeObject *)PyObject_GetAttrString( + mod_io, "_BytesIOBuffer"); + Py_DECREF(mod_io); if (type == NULL) { return NULL; } diff --git a/Modules/cjkcodecs/cjkcodecs.h b/Modules/cjkcodecs/cjkcodecs.h index 97290aac3ba439..ee588785e7403f 100644 --- a/Modules/cjkcodecs/cjkcodecs.h +++ b/Modules/cjkcodecs/cjkcodecs.h @@ -13,6 +13,7 @@ #include "Python.h" #include "multibytecodec.h" +#include "pycore_import.h" // _PyImport_GetModuleAttrString() /* a unicode "undefined" code point */ diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c index e3333fff00b2b2..28915359fb49e2 100644 --- a/Modules/pyexpat.c +++ b/Modules/pyexpat.c @@ -1,4 +1,9 @@ +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif + #include "Python.h" +#include "pycore_import.h" // _PyImport_SetModule() #include #include "structmember.h" // PyMemberDef From 648688c137744a623a71dc2413d2879b80c99eae Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 4 Jul 2023 01:10:47 +0200 Subject: [PATCH 13/39] gh-106368: Harden Argument Clinic parser tests (#106384) --- Lib/test/test_clinic.py | 110 +++++++++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 42 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index b3602887ab6352..c5cfe53e0df99b 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -643,7 +643,7 @@ def test_disallowed_grouping__two_top_groups_on_left(self): self.assertEqual(out, expected_msg) def test_disallowed_grouping__two_top_groups_on_right(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.two_top_groups_on_right param: int @@ -654,9 +654,14 @@ def test_disallowed_grouping__two_top_groups_on_right(self): group2 : int ] """) + msg = ( + "Function two_top_groups_on_right has an unsupported group " + "configuration. (Unexpected state 6.b)" + ) + self.assertIn(msg, out) def test_disallowed_grouping__parameter_after_group_on_right(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.parameter_after_group_on_right param: int @@ -667,9 +672,14 @@ def test_disallowed_grouping__parameter_after_group_on_right(self): group2 : int ] """) + msg = ( + "Function parameter_after_group_on_right has an unsupported group " + "configuration. (Unexpected state 6.a)" + ) + self.assertIn(msg, out) def test_disallowed_grouping__group_after_parameter_on_left(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.group_after_parameter_on_left [ @@ -680,9 +690,14 @@ def test_disallowed_grouping__group_after_parameter_on_left(self): ] param: int """) + msg = ( + "Function group_after_parameter_on_left has an unsupported group " + "configuration. (Unexpected state 2.b)" + ) + self.assertIn(msg, out) def test_disallowed_grouping__empty_group_on_left(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.empty_group [ @@ -692,9 +707,14 @@ def test_disallowed_grouping__empty_group_on_left(self): ] param: int """) + msg = ( + "Function empty_group has an empty group.\n" + "All groups must contain at least one parameter." + ) + self.assertIn(msg, out) def test_disallowed_grouping__empty_group_on_right(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.empty_group param: int @@ -704,6 +724,11 @@ def test_disallowed_grouping__empty_group_on_right(self): group2 : int ] """) + msg = ( + "Function empty_group has an empty group.\n" + "All groups must contain at least one parameter." + ) + self.assertIn(msg, out) def test_no_parameters(self): function = self.parse_function(""" @@ -732,69 +757,60 @@ class foo.Bar "unused" "notneeded" self.assertEqual(1, len(function.parameters)) def test_illegal_module_line(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.bar => int / """) + msg = "Illegal function name: foo.bar => int" + self.assertIn(msg, out) def test_illegal_c_basename(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.bar as 935 / """) + msg = "Illegal C basename: 935" + self.assertIn(msg, out) def test_single_star(self): - self.parse_function_should_fail(""" - module foo - foo.bar - * - * - """) - - def test_parameters_required_after_star_without_initial_parameters_or_docstring(self): - self.parse_function_should_fail(""" - module foo - foo.bar - * - """) - - def test_parameters_required_after_star_without_initial_parameters_with_docstring(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.bar * - Docstring here. - """) - - def test_parameters_required_after_star_with_initial_parameters_without_docstring(self): - self.parse_function_should_fail(""" - module foo - foo.bar - this: int * """) + self.assertIn("Function bar uses '*' more than once.", out) - def test_parameters_required_after_star_with_initial_parameters_and_docstring(self): - self.parse_function_should_fail(""" - module foo - foo.bar - this: int - * - Docstring. - """) + def test_parameters_required_after_star(self): + dataset = ( + "module foo\nfoo.bar\n *", + "module foo\nfoo.bar\n *\nDocstring here.", + "module foo\nfoo.bar\n this: int\n *", + "module foo\nfoo.bar\n this: int\n *\nDocstring.", + ) + msg = "Function bar specifies '*' without any parameters afterwards." + for block in dataset: + with self.subTest(block=block): + out = self.parse_function_should_fail(block) + self.assertIn(msg, out) def test_single_slash(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.bar / / """) + msg = ( + "Function bar has an unsupported group configuration. " + "(Unexpected state 0.d)" + ) + self.assertIn(msg, out) def test_mix_star_and_slash(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.bar x: int @@ -803,14 +819,24 @@ def test_mix_star_and_slash(self): z: int / """) + msg = ( + "Function bar mixes keyword-only and positional-only parameters, " + "which is unsupported." + ) + self.assertIn(msg, out) def test_parameters_not_permitted_after_slash_for_now(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.bar / x: int """) + msg = ( + "Function bar has an unsupported group configuration. " + "(Unexpected state 0.d)" + ) + self.assertIn(msg, out) def test_parameters_no_more_than_one_vararg(self): expected_msg = ( From b4256135809d78d342e9d92e8bc3f527d3d3057f Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 4 Jul 2023 01:37:48 +0200 Subject: [PATCH 14/39] gh-106320: Fix _PyImport_GetModuleAttr() declaration (#106386) Replace PyAPI_DATA() with PyAPI_FUNC(). --- Include/internal/pycore_import.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Include/internal/pycore_import.h b/Include/internal/pycore_import.h index 457a654aff4644..c048ae88d9000c 100644 --- a/Include/internal/pycore_import.h +++ b/Include/internal/pycore_import.h @@ -24,8 +24,8 @@ extern int _PyImport_FixupBuiltin( extern int _PyImport_FixupExtensionObject(PyObject*, PyObject *, PyObject *, PyObject *); -PyAPI_DATA(PyObject *) _PyImport_GetModuleAttr(PyObject *, PyObject *); -PyAPI_DATA(PyObject *) _PyImport_GetModuleAttrString(const char *, const char *); +PyAPI_FUNC(PyObject *) _PyImport_GetModuleAttr(PyObject *, PyObject *); +PyAPI_FUNC(PyObject *) _PyImport_GetModuleAttrString(const char *, const char *); struct _import_runtime_state { From 3406f8cce542ea4edf4153c0fac5216df283a9b1 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 4 Jul 2023 01:58:27 +0200 Subject: [PATCH 15/39] gh-106368: Increase Argument Clinic test coverage (#106389) Add: - test_disallowed_gropuing__no_matching_bracket - test_double_slash --- Lib/test/test_clinic.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index c5cfe53e0df99b..03754d0bf123be 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -730,6 +730,18 @@ def test_disallowed_grouping__empty_group_on_right(self): ) self.assertIn(msg, out) + def test_disallowed_grouping__no_matching_bracket(self): + out = self.parse_function_should_fail(""" + module foo + foo.empty_group + param: int + ] + group2: int + ] + """) + msg = "Function empty_group has a ] without a matching [." + self.assertIn(msg, out) + def test_no_parameters(self): function = self.parse_function(""" module foo @@ -809,6 +821,18 @@ def test_single_slash(self): ) self.assertIn(msg, out) + def test_double_slash(self): + out = self.parse_function_should_fail(""" + module foo + foo.bar + a: int + / + b: int + / + """) + msg = "Function bar uses '/' more than once." + self.assertIn(msg, out) + def test_mix_star_and_slash(self): out = self.parse_function_should_fail(""" module foo From d8c5d76da2d5c3e8f9c05fcfc59dc1aaaa1fe6e1 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 4 Jul 2023 09:29:52 +0200 Subject: [PATCH 16/39] gh-106320: Remove private _PyUnicode codecs C API functions (#106385) Remove private _PyUnicode codecs C API functions: move them to the internal C API (pycore_unicodeobject.h). No longer export most of these functions. --- Include/cpython/unicodeobject.h | 106 ------------------------ Include/internal/pycore_unicodeobject.h | 100 ++++++++++++++++++++++ Parser/string_parser.c | 1 + 3 files changed, 101 insertions(+), 106 deletions(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index fcd9c28e030c1f..dc8f6437c0e2c4 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -461,112 +461,6 @@ PyAPI_FUNC(const char *) PyUnicode_AsUTF8(PyObject *unicode); #define _PyUnicode_AsString PyUnicode_AsUTF8 -/* --- UTF-7 Codecs ------------------------------------------------------- */ - -PyAPI_FUNC(PyObject*) _PyUnicode_EncodeUTF7( - PyObject *unicode, /* Unicode object */ - int base64SetO, /* Encode RFC2152 Set O characters in base64 */ - int base64WhiteSpace, /* Encode whitespace (sp, ht, nl, cr) in base64 */ - const char *errors /* error handling */ - ); - -/* --- UTF-8 Codecs ------------------------------------------------------- */ - -PyAPI_FUNC(PyObject*) _PyUnicode_AsUTF8String( - PyObject *unicode, - const char *errors); - -/* --- UTF-32 Codecs ------------------------------------------------------ */ - -PyAPI_FUNC(PyObject*) _PyUnicode_EncodeUTF32( - PyObject *object, /* Unicode object */ - const char *errors, /* error handling */ - int byteorder /* byteorder to use 0=BOM+native;-1=LE,1=BE */ - ); - -/* --- UTF-16 Codecs ------------------------------------------------------ */ - -/* Returns a Python string object holding the UTF-16 encoded value of - the Unicode data. - - If byteorder is not 0, output is written according to the following - byte order: - - byteorder == -1: little endian - byteorder == 0: native byte order (writes a BOM mark) - byteorder == 1: big endian - - If byteorder is 0, the output string will always start with the - Unicode BOM mark (U+FEFF). In the other two modes, no BOM mark is - prepended. -*/ -PyAPI_FUNC(PyObject*) _PyUnicode_EncodeUTF16( - PyObject* unicode, /* Unicode object */ - const char *errors, /* error handling */ - int byteorder /* byteorder to use 0=BOM+native;-1=LE,1=BE */ - ); - -/* --- Unicode-Escape Codecs ---------------------------------------------- */ - -/* Variant of PyUnicode_DecodeUnicodeEscape that supports partial decoding. */ -PyAPI_FUNC(PyObject*) _PyUnicode_DecodeUnicodeEscapeStateful( - const char *string, /* Unicode-Escape encoded string */ - Py_ssize_t length, /* size of string */ - const char *errors, /* error handling */ - Py_ssize_t *consumed /* bytes consumed */ -); -/* Helper for PyUnicode_DecodeUnicodeEscape that detects invalid escape - chars. */ -PyAPI_FUNC(PyObject*) _PyUnicode_DecodeUnicodeEscapeInternal( - const char *string, /* Unicode-Escape encoded string */ - Py_ssize_t length, /* size of string */ - const char *errors, /* error handling */ - Py_ssize_t *consumed, /* bytes consumed */ - const char **first_invalid_escape /* on return, points to first - invalid escaped char in - string. */ -); - -/* --- Raw-Unicode-Escape Codecs ---------------------------------------------- */ - -/* Variant of PyUnicode_DecodeRawUnicodeEscape that supports partial decoding. */ -PyAPI_FUNC(PyObject*) _PyUnicode_DecodeRawUnicodeEscapeStateful( - const char *string, /* Unicode-Escape encoded string */ - Py_ssize_t length, /* size of string */ - const char *errors, /* error handling */ - Py_ssize_t *consumed /* bytes consumed */ -); - -/* --- Latin-1 Codecs ----------------------------------------------------- */ - -PyAPI_FUNC(PyObject*) _PyUnicode_AsLatin1String( - PyObject* unicode, - const char* errors); - -/* --- ASCII Codecs ------------------------------------------------------- */ - -PyAPI_FUNC(PyObject*) _PyUnicode_AsASCIIString( - PyObject* unicode, - const char* errors); - -/* --- Character Map Codecs ----------------------------------------------- */ - -/* Translate an Unicode object by applying a character mapping table to - it and return the resulting Unicode object. - - The mapping table must map Unicode ordinal integers to Unicode strings, - Unicode ordinal integers or None (causing deletion of the character). - - Mapping tables may be dictionaries or sequences. Unmapped character - ordinals (ones which cause a LookupError) are left untouched and - are copied as-is. -*/ -PyAPI_FUNC(PyObject*) _PyUnicode_EncodeCharmap( - PyObject *unicode, /* Unicode object */ - PyObject *mapping, /* encoding mapping */ - const char *errors /* error handling */ - ); - /* --- Decimal Encoder ---------------------------------------------------- */ /* Coverts a Unicode object holding a decimal value to an ASCII string diff --git a/Include/internal/pycore_unicodeobject.h b/Include/internal/pycore_unicodeobject.h index da01f57f962793..dd20ac19d413b8 100644 --- a/Include/internal/pycore_unicodeobject.h +++ b/Include/internal/pycore_unicodeobject.h @@ -177,6 +177,106 @@ PyAPI_FUNC(int) _PyUnicode_FormatAdvancedWriter( Py_ssize_t start, Py_ssize_t end); +/* --- UTF-7 Codecs ------------------------------------------------------- */ + +extern PyObject* _PyUnicode_EncodeUTF7( + PyObject *unicode, /* Unicode object */ + int base64SetO, /* Encode RFC2152 Set O characters in base64 */ + int base64WhiteSpace, /* Encode whitespace (sp, ht, nl, cr) in base64 */ + const char *errors); /* error handling */ + +/* --- UTF-8 Codecs ------------------------------------------------------- */ + +PyAPI_FUNC(PyObject*) _PyUnicode_AsUTF8String( + PyObject *unicode, + const char *errors); + +/* --- UTF-32 Codecs ------------------------------------------------------ */ + +PyAPI_FUNC(PyObject*) _PyUnicode_EncodeUTF32( + PyObject *object, /* Unicode object */ + const char *errors, /* error handling */ + int byteorder); /* byteorder to use 0=BOM+native;-1=LE,1=BE */ + +/* --- UTF-16 Codecs ------------------------------------------------------ */ + +/* Returns a Python string object holding the UTF-16 encoded value of + the Unicode data. + + If byteorder is not 0, output is written according to the following + byte order: + + byteorder == -1: little endian + byteorder == 0: native byte order (writes a BOM mark) + byteorder == 1: big endian + + If byteorder is 0, the output string will always start with the + Unicode BOM mark (U+FEFF). In the other two modes, no BOM mark is + prepended. +*/ +PyAPI_FUNC(PyObject*) _PyUnicode_EncodeUTF16( + PyObject* unicode, /* Unicode object */ + const char *errors, /* error handling */ + int byteorder); /* byteorder to use 0=BOM+native;-1=LE,1=BE */ + +/* --- Unicode-Escape Codecs ---------------------------------------------- */ + +/* Variant of PyUnicode_DecodeUnicodeEscape that supports partial decoding. */ +extern PyObject* _PyUnicode_DecodeUnicodeEscapeStateful( + const char *string, /* Unicode-Escape encoded string */ + Py_ssize_t length, /* size of string */ + const char *errors, /* error handling */ + Py_ssize_t *consumed); /* bytes consumed */ + +/* Helper for PyUnicode_DecodeUnicodeEscape that detects invalid escape + chars. */ +PyAPI_FUNC(PyObject*) _PyUnicode_DecodeUnicodeEscapeInternal( + const char *string, /* Unicode-Escape encoded string */ + Py_ssize_t length, /* size of string */ + const char *errors, /* error handling */ + Py_ssize_t *consumed, /* bytes consumed */ + const char **first_invalid_escape); /* on return, points to first + invalid escaped char in + string. */ + +/* --- Raw-Unicode-Escape Codecs ---------------------------------------------- */ + +/* Variant of PyUnicode_DecodeRawUnicodeEscape that supports partial decoding. */ +extern PyObject* _PyUnicode_DecodeRawUnicodeEscapeStateful( + const char *string, /* Unicode-Escape encoded string */ + Py_ssize_t length, /* size of string */ + const char *errors, /* error handling */ + Py_ssize_t *consumed); /* bytes consumed */ + +/* --- Latin-1 Codecs ----------------------------------------------------- */ + +extern PyObject* _PyUnicode_AsLatin1String( + PyObject* unicode, + const char* errors); + +/* --- ASCII Codecs ------------------------------------------------------- */ + +extern PyObject* _PyUnicode_AsASCIIString( + PyObject* unicode, + const char* errors); + +/* --- Character Map Codecs ----------------------------------------------- */ + +/* Translate an Unicode object by applying a character mapping table to + it and return the resulting Unicode object. + + The mapping table must map Unicode ordinal integers to Unicode strings, + Unicode ordinal integers or None (causing deletion of the character). + + Mapping tables may be dictionaries or sequences. Unmapped character + ordinals (ones which cause a LookupError) are left untouched and + are copied as-is. +*/ +extern PyObject* _PyUnicode_EncodeCharmap( + PyObject *unicode, /* Unicode object */ + PyObject *mapping, /* encoding mapping */ + const char *errors); /* error handling */ + /* --- Methods & Slots ---------------------------------------------------- */ extern PyObject* _PyUnicode_JoinArray( diff --git a/Parser/string_parser.c b/Parser/string_parser.c index 20459e89463494..bc1f99d607ae4d 100644 --- a/Parser/string_parser.c +++ b/Parser/string_parser.c @@ -1,6 +1,7 @@ #include #include +#include "pycore_unicodeobject.h" // _PyUnicode_DecodeUnicodeEscapeInternal() #include "tokenizer.h" #include "pegen.h" From ec931fc3943df0b94f2e250d7723892f2b3414bd Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 4 Jul 2023 10:27:23 +0200 Subject: [PATCH 17/39] gh-106320: Remove _PyBytesWriter C API (#106399) Remove the _PyBytesWriter C API: move it to the internal C API (pycore_bytesobject.h). --- Include/cpython/bytesobject.h | 80 -------------------------- Include/internal/pycore_bytesobject.h | 81 +++++++++++++++++++++++++++ Include/internal/pycore_long.h | 3 +- Modules/_pickle.c | 3 +- Modules/_struct.c | 1 + 5 files changed, 86 insertions(+), 82 deletions(-) diff --git a/Include/cpython/bytesobject.h b/Include/cpython/bytesobject.h index e982031c107de2..0af4c83b1e5bc7 100644 --- a/Include/cpython/bytesobject.h +++ b/Include/cpython/bytesobject.h @@ -47,83 +47,3 @@ static inline Py_ssize_t PyBytes_GET_SIZE(PyObject *op) { /* _PyBytes_Join(sep, x) is like sep.join(x). sep must be PyBytesObject*, x must be an iterable object. */ PyAPI_FUNC(PyObject *) _PyBytes_Join(PyObject *sep, PyObject *x); - - -/* The _PyBytesWriter structure is big: it contains an embedded "stack buffer". - A _PyBytesWriter variable must be declared at the end of variables in a - function to optimize the memory allocation on the stack. */ -typedef struct { - /* bytes, bytearray or NULL (when the small buffer is used) */ - PyObject *buffer; - - /* Number of allocated size. */ - Py_ssize_t allocated; - - /* Minimum number of allocated bytes, - incremented by _PyBytesWriter_Prepare() */ - Py_ssize_t min_size; - - /* If non-zero, use a bytearray instead of a bytes object for buffer. */ - int use_bytearray; - - /* If non-zero, overallocate the buffer (default: 0). - This flag must be zero if use_bytearray is non-zero. */ - int overallocate; - - /* Stack buffer */ - int use_small_buffer; - char small_buffer[512]; -} _PyBytesWriter; - -/* Initialize a bytes writer - - By default, the overallocation is disabled. Set the overallocate attribute - to control the allocation of the buffer. */ -PyAPI_FUNC(void) _PyBytesWriter_Init(_PyBytesWriter *writer); - -/* Get the buffer content and reset the writer. - Return a bytes object, or a bytearray object if use_bytearray is non-zero. - Raise an exception and return NULL on error. */ -PyAPI_FUNC(PyObject *) _PyBytesWriter_Finish(_PyBytesWriter *writer, - void *str); - -/* Deallocate memory of a writer (clear its internal buffer). */ -PyAPI_FUNC(void) _PyBytesWriter_Dealloc(_PyBytesWriter *writer); - -/* Allocate the buffer to write size bytes. - Return the pointer to the beginning of buffer data. - Raise an exception and return NULL on error. */ -PyAPI_FUNC(void*) _PyBytesWriter_Alloc(_PyBytesWriter *writer, - Py_ssize_t size); - -/* Ensure that the buffer is large enough to write *size* bytes. - Add size to the writer minimum size (min_size attribute). - - str is the current pointer inside the buffer. - Return the updated current pointer inside the buffer. - Raise an exception and return NULL on error. */ -PyAPI_FUNC(void*) _PyBytesWriter_Prepare(_PyBytesWriter *writer, - void *str, - Py_ssize_t size); - -/* Resize the buffer to make it larger. - The new buffer may be larger than size bytes because of overallocation. - Return the updated current pointer inside the buffer. - Raise an exception and return NULL on error. - - Note: size must be greater than the number of allocated bytes in the writer. - - This function doesn't use the writer minimum size (min_size attribute). - - See also _PyBytesWriter_Prepare(). - */ -PyAPI_FUNC(void*) _PyBytesWriter_Resize(_PyBytesWriter *writer, - void *str, - Py_ssize_t size); - -/* Write bytes. - Raise an exception and return NULL on error. */ -PyAPI_FUNC(void*) _PyBytesWriter_WriteBytes(_PyBytesWriter *writer, - void *str, - const void *bytes, - Py_ssize_t size); diff --git a/Include/internal/pycore_bytesobject.h b/Include/internal/pycore_bytesobject.h index d36fa9569d64a5..115c0c52c8f9a9 100644 --- a/Include/internal/pycore_bytesobject.h +++ b/Include/internal/pycore_bytesobject.h @@ -41,6 +41,87 @@ PyAPI_FUNC(void) _PyBytes_Repeat(char* dest, Py_ssize_t len_dest, const char* src, Py_ssize_t len_src); +/* --- _PyBytesWriter ----------------------------------------------------- */ + +/* The _PyBytesWriter structure is big: it contains an embedded "stack buffer". + A _PyBytesWriter variable must be declared at the end of variables in a + function to optimize the memory allocation on the stack. */ +typedef struct { + /* bytes, bytearray or NULL (when the small buffer is used) */ + PyObject *buffer; + + /* Number of allocated size. */ + Py_ssize_t allocated; + + /* Minimum number of allocated bytes, + incremented by _PyBytesWriter_Prepare() */ + Py_ssize_t min_size; + + /* If non-zero, use a bytearray instead of a bytes object for buffer. */ + int use_bytearray; + + /* If non-zero, overallocate the buffer (default: 0). + This flag must be zero if use_bytearray is non-zero. */ + int overallocate; + + /* Stack buffer */ + int use_small_buffer; + char small_buffer[512]; +} _PyBytesWriter; + +/* Initialize a bytes writer + + By default, the overallocation is disabled. Set the overallocate attribute + to control the allocation of the buffer. */ +PyAPI_FUNC(void) _PyBytesWriter_Init(_PyBytesWriter *writer); + +/* Get the buffer content and reset the writer. + Return a bytes object, or a bytearray object if use_bytearray is non-zero. + Raise an exception and return NULL on error. */ +PyAPI_FUNC(PyObject *) _PyBytesWriter_Finish(_PyBytesWriter *writer, + void *str); + +/* Deallocate memory of a writer (clear its internal buffer). */ +PyAPI_FUNC(void) _PyBytesWriter_Dealloc(_PyBytesWriter *writer); + +/* Allocate the buffer to write size bytes. + Return the pointer to the beginning of buffer data. + Raise an exception and return NULL on error. */ +PyAPI_FUNC(void*) _PyBytesWriter_Alloc(_PyBytesWriter *writer, + Py_ssize_t size); + +/* Ensure that the buffer is large enough to write *size* bytes. + Add size to the writer minimum size (min_size attribute). + + str is the current pointer inside the buffer. + Return the updated current pointer inside the buffer. + Raise an exception and return NULL on error. */ +PyAPI_FUNC(void*) _PyBytesWriter_Prepare(_PyBytesWriter *writer, + void *str, + Py_ssize_t size); + +/* Resize the buffer to make it larger. + The new buffer may be larger than size bytes because of overallocation. + Return the updated current pointer inside the buffer. + Raise an exception and return NULL on error. + + Note: size must be greater than the number of allocated bytes in the writer. + + This function doesn't use the writer minimum size (min_size attribute). + + See also _PyBytesWriter_Prepare(). + */ +PyAPI_FUNC(void*) _PyBytesWriter_Resize(_PyBytesWriter *writer, + void *str, + Py_ssize_t size); + +/* Write bytes. + Raise an exception and return NULL on error. */ +PyAPI_FUNC(void*) _PyBytesWriter_WriteBytes(_PyBytesWriter *writer, + void *str, + const void *bytes, + Py_ssize_t size); + #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_long.h b/Include/internal/pycore_long.h index 64c00cb1475480..3f01694e5f5ac4 100644 --- a/Include/internal/pycore_long.h +++ b/Include/internal/pycore_long.h @@ -8,7 +8,8 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif -#include "pycore_global_objects.h" // _PY_NSMALLNEGINTS +#include "pycore_bytesobject.h" // _PyBytesWriter +#include "pycore_global_objects.h"// _PY_NSMALLNEGINTS #include "pycore_runtime.h" // _PyRuntime /* diff --git a/Modules/_pickle.c b/Modules/_pickle.c index 4913a8dfee589e..a68a0aaa64c2b5 100644 --- a/Modules/_pickle.c +++ b/Modules/_pickle.c @@ -9,10 +9,11 @@ #endif #include "Python.h" +#include "pycore_bytesobject.h" // _PyBytesWriter #include "pycore_ceval.h" // _Py_EnterRecursiveCall() #include "pycore_moduleobject.h" // _PyModule_GetState() -#include "pycore_runtime.h" // _Py_ID() #include "pycore_pystate.h" // _PyThreadState_GET() +#include "pycore_runtime.h" // _Py_ID() #include "structmember.h" // PyMemberDef #include // strtol() diff --git a/Modules/_struct.c b/Modules/_struct.c index 0a6f076aac0c53..31c94927e91d68 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -8,6 +8,7 @@ #endif #include "Python.h" +#include "pycore_bytesobject.h" // _PyBytesWriter #include "pycore_moduleobject.h" // _PyModule_GetState() #include "structmember.h" // PyMemberDef #include From 8a73b57b9b5f6e36dd5a4c279f4d606d9e71a31f Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 4 Jul 2023 10:59:09 +0200 Subject: [PATCH 18/39] gh-106320: Remove _PyUnicode_TransformDecimalAndSpaceToASCII() (#106398) Remove private _PyUnicode_TransformDecimalAndSpaceToASCII() and other private _PyUnicode C API functions: move them to the internal C API (pycore_unicodeobject.h). No longer most of these functions. Replace _testcapi.unicode_transformdecimalandspacetoascii() with _testinternal._PyUnicode_TransformDecimalAndSpaceToASCII(). --- Include/cpython/unicodeobject.h | 37 ----------------------- Include/internal/pycore_unicodeobject.h | 39 +++++++++++++++++++++++-- Lib/test/test_capi/test_unicode.py | 8 +++-- Modules/_testcapi/unicode.c | 9 ------ Modules/_testinternalcapi.c | 12 ++++++++ Python/pystrhex.c | 1 + 6 files changed, 56 insertions(+), 50 deletions(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index dc8f6437c0e2c4..e75b5e154943dc 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -167,10 +167,6 @@ typedef struct { } data; /* Canonical, smallest-form Unicode buffer */ } PyUnicodeObject; -PyAPI_FUNC(int) _PyUnicode_CheckConsistency( - PyObject *op, - int check_content); - #define _PyASCIIObject_CAST(op) \ (assert(PyUnicode_Check(op)), \ @@ -461,19 +457,6 @@ PyAPI_FUNC(const char *) PyUnicode_AsUTF8(PyObject *unicode); #define _PyUnicode_AsString PyUnicode_AsUTF8 -/* --- Decimal Encoder ---------------------------------------------------- */ - -/* Coverts a Unicode object holding a decimal value to an ASCII string - for using in int, float and complex parsers. - Transforms code points that have decimal digit property to the - corresponding ASCII digit code points. Transforms spaces to ASCII. - Transforms code points starting from the first non-ASCII code point that - is neither a decimal digit nor a space to the end into '?'. */ - -PyAPI_FUNC(PyObject*) _PyUnicode_TransformDecimalAndSpaceToASCII( - PyObject *unicode /* Unicode object */ - ); - /* === Characters Type APIs =============================================== */ /* These should not be used directly. Use the Py_UNICODE_IS* and @@ -623,23 +606,3 @@ static inline int Py_UNICODE_ISALNUM(Py_UCS4 ch) { || Py_UNICODE_ISDIGIT(ch) || Py_UNICODE_ISNUMERIC(ch)); } - - -/* === Misc functions ===================================================== */ - -PyAPI_FUNC(PyObject*) _PyUnicode_FormatLong(PyObject *, int, int, int); - -/* Return an interned Unicode object for an Identifier; may fail if there is no memory.*/ -PyAPI_FUNC(PyObject*) _PyUnicode_FromId(_Py_Identifier*); - -/* Fast equality check when the inputs are known to be exact unicode types - and where the hash values are equal (i.e. a very probable match) */ -PyAPI_FUNC(int) _PyUnicode_EQ(PyObject *, PyObject *); - -/* Equality check. */ -PyAPI_FUNC(int) _PyUnicode_Equal(PyObject *, PyObject *); - -PyAPI_FUNC(int) _PyUnicode_WideCharString_Converter(PyObject *, void *); -PyAPI_FUNC(int) _PyUnicode_WideCharString_Opt_Converter(PyObject *, void *); - -PyAPI_FUNC(Py_ssize_t) _PyUnicode_ScanIdentifier(PyObject *); diff --git a/Include/internal/pycore_unicodeobject.h b/Include/internal/pycore_unicodeobject.h index dd20ac19d413b8..ad59c3e385f2d3 100644 --- a/Include/internal/pycore_unicodeobject.h +++ b/Include/internal/pycore_unicodeobject.h @@ -11,8 +11,12 @@ extern "C" { #include "pycore_fileutils.h" // _Py_error_handler #include "pycore_ucnhash.h" // _PyUnicode_Name_CAPI -void _PyUnicode_ExactDealloc(PyObject *op); -Py_ssize_t _PyUnicode_InternedSize(void); +PyAPI_FUNC(int) _PyUnicode_CheckConsistency( + PyObject *op, + int check_content); + +extern void _PyUnicode_ExactDealloc(PyObject *op); +extern Py_ssize_t _PyUnicode_InternedSize(void); /* Get a copy of a Unicode string. */ PyAPI_FUNC(PyObject*) _PyUnicode_Copy( @@ -277,6 +281,18 @@ extern PyObject* _PyUnicode_EncodeCharmap( PyObject *mapping, /* encoding mapping */ const char *errors); /* error handling */ +/* --- Decimal Encoder ---------------------------------------------------- */ + +/* Coverts a Unicode object holding a decimal value to an ASCII string + for using in int, float and complex parsers. + Transforms code points that have decimal digit property to the + corresponding ASCII digit code points. Transforms spaces to ASCII. + Transforms code points starting from the first non-ASCII code point that + is neither a decimal digit nor a space to the end into '?'. */ + +PyAPI_FUNC(PyObject*) _PyUnicode_TransformDecimalAndSpaceToASCII( + PyObject *unicode); /* Unicode object */ + /* --- Methods & Slots ---------------------------------------------------- */ extern PyObject* _PyUnicode_JoinArray( @@ -323,6 +339,25 @@ extern Py_ssize_t _PyUnicode_InsertThousandsGrouping( PyObject *thousands_sep, Py_UCS4 *maxchar); +/* --- Misc functions ----------------------------------------------------- */ + +extern PyObject* _PyUnicode_FormatLong(PyObject *, int, int, int); + +/* Return an interned Unicode object for an Identifier; may fail if there is no memory.*/ +PyAPI_FUNC(PyObject*) _PyUnicode_FromId(_Py_Identifier*); + +/* Fast equality check when the inputs are known to be exact unicode types + and where the hash values are equal (i.e. a very probable match) */ +extern int _PyUnicode_EQ(PyObject *, PyObject *); + +/* Equality check. */ +PyAPI_FUNC(int) _PyUnicode_Equal(PyObject *, PyObject *); + +extern int _PyUnicode_WideCharString_Converter(PyObject *, void *); +extern int _PyUnicode_WideCharString_Opt_Converter(PyObject *, void *); + +PyAPI_FUNC(Py_ssize_t) _PyUnicode_ScanIdentifier(PyObject *); + /* --- Runtime lifecycle -------------------------------------------------- */ extern void _PyUnicode_InitState(PyInterpreterState *); diff --git a/Lib/test/test_capi/test_unicode.py b/Lib/test/test_capi/test_unicode.py index ca914459a62bec..622ee8993907fa 100644 --- a/Lib/test/test_capi/test_unicode.py +++ b/Lib/test/test_capi/test_unicode.py @@ -7,6 +7,10 @@ import _testcapi except ImportError: _testcapi = None +try: + import _testinternalcapi +except ImportError: + _testinternalcapi = None NULL = None @@ -913,10 +917,10 @@ def test_getdefaultencoding(self): self.assertEqual(getdefaultencoding(), b'utf-8') @support.cpython_only - @unittest.skipIf(_testcapi is None, 'need _testcapi module') + @unittest.skipIf(_testinternalcapi is None, 'need _testinternalcapi module') def test_transform_decimal_and_space(self): """Test _PyUnicode_TransformDecimalAndSpaceToASCII()""" - from _testcapi import unicode_transformdecimalandspacetoascii as transform_decimal + from _testinternalcapi import _PyUnicode_TransformDecimalAndSpaceToASCII as transform_decimal self.assertEqual(transform_decimal('123'), '123') diff --git a/Modules/_testcapi/unicode.c b/Modules/_testcapi/unicode.c index 9c2760c3f763a6..51d741a6b5ff1c 100644 --- a/Modules/_testcapi/unicode.c +++ b/Modules/_testcapi/unicode.c @@ -660,14 +660,6 @@ unicode_getdefaultencoding(PyObject *self, PyObject *Py_UNUSED(ignored)) return PyBytes_FromString(s); } -/* Test _PyUnicode_TransformDecimalAndSpaceToASCII() */ -static PyObject * -unicode_transformdecimalandspacetoascii(PyObject *self, PyObject *arg) -{ - NULLABLE(arg); - return _PyUnicode_TransformDecimalAndSpaceToASCII(arg); -} - /* Test PyUnicode_DecodeUTF8() */ static PyObject * unicode_decodeutf8(PyObject *self, PyObject *args) @@ -1544,7 +1536,6 @@ static PyMethodDef TestMethods[] = { {"unicode_decodeutf8", unicode_decodeutf8, METH_VARARGS}, {"unicode_decodeutf8stateful",unicode_decodeutf8stateful, METH_VARARGS}, {"unicode_getdefaultencoding",unicode_getdefaultencoding, METH_NOARGS}, - {"unicode_transformdecimalandspacetoascii", unicode_transformdecimalandspacetoascii, METH_O}, {"unicode_concat", unicode_concat, METH_VARARGS}, {"unicode_splitlines", unicode_splitlines, METH_VARARGS}, {"unicode_split", unicode_split, METH_VARARGS}, diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 4875ee7bed1683..14f91e8da1716a 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -1253,6 +1253,17 @@ test_tstate_capi(PyObject *self, PyObject *Py_UNUSED(args)) } +/* Test _PyUnicode_TransformDecimalAndSpaceToASCII() */ +static PyObject * +unicode_transformdecimalandspacetoascii(PyObject *self, PyObject *arg) +{ + if (arg == Py_None) { + arg = NULL; + } + return _PyUnicode_TransformDecimalAndSpaceToASCII(arg); +} + + static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, @@ -1304,6 +1315,7 @@ static PyMethodDef module_functions[] = { {"_PyTime_ObjectToTimeval", test_pytime_object_to_timeval, METH_VARARGS}, {"_PyTraceMalloc_GetTraceback", tracemalloc_get_traceback, METH_VARARGS}, {"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL}, + {"_PyUnicode_TransformDecimalAndSpaceToASCII", unicode_transformdecimalandspacetoascii, METH_O}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/pystrhex.c b/Python/pystrhex.c index f798256e18ebdb..ce456b79f1655f 100644 --- a/Python/pystrhex.c +++ b/Python/pystrhex.c @@ -2,6 +2,7 @@ #include "Python.h" #include "pycore_strhex.h" // _Py_strhex_with_sep() +#include "pycore_unicodeobject.h" // _PyUnicode_CheckConsistency() #include // abs() static PyObject *_Py_strhex_impl(const char* argbuf, const Py_ssize_t arglen, From c9ce983ae1a361f431a0303aeb6f4b8e1d674275 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 4 Jul 2023 11:41:43 +0200 Subject: [PATCH 19/39] gh-106320: Remove private pylifecycle.h functions (#106400) Remove private pylifecycle.h functions: move them to the internal C API ( pycore_atexit.h, pycore_pylifecycle.h and pycore_signal.h). No longer export most of these functions. Move _testcapi.test_atexit() to _testinternalcapi. --- Include/cpython/pylifecycle.h | 27 ++---------------- Include/internal/pycore_atexit.h | 7 ++++- Include/internal/pycore_pylifecycle.h | 41 ++++++++++++++++++++------- Include/internal/pycore_signal.h | 4 ++- Modules/_asynciomodule.c | 3 +- Modules/_io/bufferedio.c | 1 + Modules/_posixsubprocess.c | 4 ++- Modules/_randommodule.c | 1 + Modules/_sqlite/connection.c | 1 + Modules/_testcapimodule.c | 32 --------------------- Modules/_testinternalcapi.c | 33 +++++++++++++++++++++ Modules/_xxinterpchannelsmodule.c | 6 +++- Modules/getbuildinfo.c | 5 ++++ Modules/posixmodule.c | 1 + Modules/readline.c | 5 ++++ Modules/signalmodule.c | 2 +- Python/_warnings.c | 3 +- Python/bootstrap_hash.c | 3 +- Python/preconfig.c | 1 + 19 files changed, 104 insertions(+), 76 deletions(-) diff --git a/Include/cpython/pylifecycle.h b/Include/cpython/pylifecycle.h index 1ca9ee91a72b15..8af34b05642512 100644 --- a/Include/cpython/pylifecycle.h +++ b/Include/cpython/pylifecycle.h @@ -19,13 +19,13 @@ PyAPI_FUNC(PyStatus) Py_PreInitializeFromArgs( Py_ssize_t argc, wchar_t **argv); -PyAPI_FUNC(int) _Py_IsCoreInitialized(void); - /* Initialization and finalization */ PyAPI_FUNC(PyStatus) Py_InitializeFromConfig( const PyConfig *config); + +// Python 3.8 provisional API (PEP 587) PyAPI_FUNC(PyStatus) _Py_InitializeMain(void); PyAPI_FUNC(int) Py_RunMain(void); @@ -33,31 +33,8 @@ PyAPI_FUNC(int) Py_RunMain(void); PyAPI_FUNC(void) _Py_NO_RETURN Py_ExitStatusException(PyStatus err); -/* Restore signals that the interpreter has called SIG_IGN on to SIG_DFL. */ -PyAPI_FUNC(void) _Py_RestoreSignals(void); - PyAPI_FUNC(int) Py_FdIsInteractive(FILE *, const char *); -PyAPI_FUNC(int) _Py_FdIsInteractive(FILE *fp, PyObject *filename); - -PyAPI_FUNC(const char *) _Py_gitidentifier(void); -PyAPI_FUNC(const char *) _Py_gitversion(void); - -PyAPI_FUNC(int) _Py_IsFinalizing(void); -PyAPI_FUNC(int) _Py_IsInterpreterFinalizing(PyInterpreterState *interp); - -/* Random */ -PyAPI_FUNC(int) _PyOS_URandom(void *buffer, Py_ssize_t size); -PyAPI_FUNC(int) _PyOS_URandomNonblock(void *buffer, Py_ssize_t size); - -/* Legacy locale support */ -PyAPI_FUNC(int) _Py_CoerceLegacyLocale(int warn); -PyAPI_FUNC(int) _Py_LegacyLocaleDetected(int warn); -PyAPI_FUNC(char *) _Py_SetLocaleFromEnv(int category); PyAPI_FUNC(PyStatus) Py_NewInterpreterFromConfig( PyThreadState **tstate_p, const PyInterpreterConfig *config); - -typedef void (*atexit_datacallbackfunc)(void *); -PyAPI_FUNC(int) _Py_AtExit( - PyInterpreterState *, atexit_datacallbackfunc, void *); diff --git a/Include/internal/pycore_atexit.h b/Include/internal/pycore_atexit.h index 63a2cd5d507d2c..fc5cb6d8826435 100644 --- a/Include/internal/pycore_atexit.h +++ b/Include/internal/pycore_atexit.h @@ -25,7 +25,8 @@ struct _atexit_runtime_state { //################### // interpreter atexit -struct atexit_callback; +typedef void (*atexit_datacallbackfunc)(void *); + typedef struct atexit_callback { atexit_datacallbackfunc func; void *data; @@ -50,6 +51,10 @@ struct atexit_state { int callback_len; }; +PyAPI_FUNC(int) _Py_AtExit( + PyInterpreterState *interp, + atexit_datacallbackfunc func, + void *data); #ifdef __cplusplus } diff --git a/Include/internal/pycore_pylifecycle.h b/Include/internal/pycore_pylifecycle.h index b07c2dba8de847..fb28652515909d 100644 --- a/Include/internal/pycore_pylifecycle.h +++ b/Include/internal/pycore_pylifecycle.h @@ -23,7 +23,7 @@ extern PyStatus _PyUnicode_InitEncodings(PyThreadState *tstate); extern int _PyUnicode_EnableLegacyWindowsFSEncoding(void); #endif -PyAPI_FUNC(int) _Py_IsLocaleCoercionTarget(const char *ctype_loc); +extern int _Py_IsLocaleCoercionTarget(const char *ctype_loc); /* Various one-time initializers */ @@ -67,30 +67,49 @@ extern PyStatus _PyGILState_Init(PyInterpreterState *interp); extern PyStatus _PyGILState_SetTstate(PyThreadState *tstate); extern void _PyGILState_Fini(PyInterpreterState *interp); -PyAPI_FUNC(void) _PyGC_DumpShutdownStats(PyInterpreterState *interp); +extern void _PyGC_DumpShutdownStats(PyInterpreterState *interp); -PyAPI_FUNC(PyStatus) _Py_PreInitializeFromPyArgv( +extern PyStatus _Py_PreInitializeFromPyArgv( const PyPreConfig *src_config, const struct _PyArgv *args); -PyAPI_FUNC(PyStatus) _Py_PreInitializeFromConfig( +extern PyStatus _Py_PreInitializeFromConfig( const PyConfig *config, const struct _PyArgv *args); -PyAPI_FUNC(wchar_t *) _Py_GetStdlibDir(void); +extern wchar_t * _Py_GetStdlibDir(void); -PyAPI_FUNC(int) _Py_HandleSystemExit(int *exitcode_p); +extern int _Py_HandleSystemExit(int *exitcode_p); -PyAPI_FUNC(PyObject*) _PyErr_WriteUnraisableDefaultHook(PyObject *unraisable); +extern PyObject* _PyErr_WriteUnraisableDefaultHook(PyObject *unraisable); -PyAPI_FUNC(void) _PyErr_Print(PyThreadState *tstate); -PyAPI_FUNC(void) _PyErr_Display(PyObject *file, PyObject *exception, +extern void _PyErr_Print(PyThreadState *tstate); +extern void _PyErr_Display(PyObject *file, PyObject *exception, PyObject *value, PyObject *tb); -PyAPI_FUNC(void) _PyErr_DisplayException(PyObject *file, PyObject *exc); +extern void _PyErr_DisplayException(PyObject *file, PyObject *exc); -PyAPI_FUNC(void) _PyThreadState_DeleteCurrent(PyThreadState *tstate); +extern void _PyThreadState_DeleteCurrent(PyThreadState *tstate); extern void _PyAtExit_Call(PyInterpreterState *interp); +extern int _Py_IsCoreInitialized(void); + +extern int _Py_FdIsInteractive(FILE *fp, PyObject *filename); + +extern const char* _Py_gitidentifier(void); +extern const char* _Py_gitversion(void); + +extern int _Py_IsFinalizing(void); +PyAPI_FUNC(int) _Py_IsInterpreterFinalizing(PyInterpreterState *interp); + +/* Random */ +extern int _PyOS_URandom(void *buffer, Py_ssize_t size); +PyAPI_FUNC(int) _PyOS_URandomNonblock(void *buffer, Py_ssize_t size); + +/* Legacy locale support */ +extern int _Py_CoerceLegacyLocale(int warn); +extern int _Py_LegacyLocaleDetected(int warn); +PyAPI_FUNC(char*) _Py_SetLocaleFromEnv(int category); + #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_signal.h b/Include/internal/pycore_signal.h index ca3f69d09fc0c1..1a454ba6f4e8fb 100644 --- a/Include/internal/pycore_signal.h +++ b/Include/internal/pycore_signal.h @@ -11,10 +11,12 @@ extern "C" { #endif #include "pycore_atomic.h" // _Py_atomic_address - #include // NSIG +/* Restore signals that the interpreter has called SIG_IGN on to SIG_DFL. */ +PyAPI_FUNC(void) _Py_RestoreSignals(void); + #ifdef _SIG_MAXSIG // gh-91145: On FreeBSD, defines NSIG as 32: it doesn't include // realtime signals: [SIGRTMIN,SIGRTMAX]. Use _SIG_MAXSIG instead. For diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 05f94ef9ed2816..3843f9c45d7236 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -3,10 +3,11 @@ #endif #include "Python.h" +#include "pycore_moduleobject.h" // _PyModule_GetState() #include "pycore_pyerrors.h" // _PyErr_ClearExcState() +#include "pycore_pylifecycle.h" // _Py_IsInterpreterFinalizing() #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_runtime_init.h" // _Py_ID() -#include "pycore_moduleobject.h" // _PyModule_GetState() #include "structmember.h" // PyMemberDef #include // offsetof() diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index 4d120d4e8af3a7..e58e87926f6731 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -11,6 +11,7 @@ #include "pycore_call.h" // _PyObject_CallNoArgs() #include "pycore_object.h" #include "pycore_pyerrors.h" // _Py_FatalErrorFormat() +#include "pycore_pylifecycle.h" // _Py_IsInterpreterFinalizing() #include "structmember.h" // PyMemberDef #include "_iomodule.h" diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c index 6caa4b8852911e..ac2b0d4f55468c 100644 --- a/Modules/_posixsubprocess.c +++ b/Modules/_posixsubprocess.c @@ -6,6 +6,7 @@ #include "Python.h" #include "pycore_fileutils.h" #include "pycore_pystate.h" +#include "pycore_signal.h" // _Py_RestoreSignals() #if defined(HAVE_PIPE2) && !defined(_GNU_SOURCE) # define _GNU_SOURCE #endif @@ -739,8 +740,9 @@ child_exec(char *const exec_array[], if (child_umask >= 0) umask(child_umask); /* umask() always succeeds. */ - if (restore_signals) + if (restore_signals) { _Py_RestoreSignals(); + } #ifdef VFORK_USABLE if (child_sigmask) { diff --git a/Modules/_randommodule.c b/Modules/_randommodule.c index fda5ef267fb470..7daa1f9327966f 100644 --- a/Modules/_randommodule.c +++ b/Modules/_randommodule.c @@ -72,6 +72,7 @@ #include "Python.h" #include "pycore_moduleobject.h" // _PyModule_GetState() +#include "pycore_pylifecycle.h" // _PyOS_URandomNonblock() #include "pycore_runtime.h" #ifdef HAVE_PROCESS_H # include // getpid() diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index d71cef14779e51..bab743674b666d 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -34,6 +34,7 @@ #include "prepare_protocol.h" #include "util.h" #include "pycore_import.h" // _PyImport_GetModuleAttrString() +#include "pycore_pylifecycle.h" // _Py_IsInterpreterFinalizing() #include "pycore_weakref.h" // _PyWeakref_IS_DEAD() #include diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index d1044b5445202d..2baf453f710267 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3293,37 +3293,6 @@ function_set_kw_defaults(PyObject *self, PyObject *args) Py_RETURN_NONE; } -struct atexit_data { - int called; -}; - -static void -callback(void *data) -{ - ((struct atexit_data *)data)->called += 1; -} - -static PyObject * -test_atexit(PyObject *self, PyObject *Py_UNUSED(args)) -{ - PyThreadState *oldts = PyThreadState_Swap(NULL); - PyThreadState *tstate = Py_NewInterpreter(); - - struct atexit_data data = {0}; - int res = _Py_AtExit(tstate->interp, callback, (void *)&data); - Py_EndInterpreter(tstate); - PyThreadState_Swap(oldts); - if (res < 0) { - return NULL; - } - if (data.called == 0) { - PyErr_SetString(PyExc_RuntimeError, "atexit callback not called"); - return NULL; - } - Py_RETURN_NONE; -} - - static PyObject * check_pyimport_addmodule(PyObject *self, PyObject *args) { @@ -3613,7 +3582,6 @@ static PyMethodDef TestMethods[] = { {"function_set_defaults", function_set_defaults, METH_VARARGS, NULL}, {"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL}, {"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL}, - {"test_atexit", test_atexit, METH_NOARGS}, {"check_pyimport_addmodule", check_pyimport_addmodule, METH_VARARGS}, {"test_weakref_capi", test_weakref_capi, METH_NOARGS}, {NULL, NULL} /* sentinel */ diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 14f91e8da1716a..84511d27e37f45 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -1264,6 +1264,38 @@ unicode_transformdecimalandspacetoascii(PyObject *self, PyObject *arg) } +struct atexit_data { + int called; +}; + +static void +callback(void *data) +{ + ((struct atexit_data *)data)->called += 1; +} + +static PyObject * +test_atexit(PyObject *self, PyObject *Py_UNUSED(args)) +{ + PyThreadState *oldts = PyThreadState_Swap(NULL); + PyThreadState *tstate = Py_NewInterpreter(); + + struct atexit_data data = {0}; + int res = _Py_AtExit(tstate->interp, callback, (void *)&data); + Py_EndInterpreter(tstate); + PyThreadState_Swap(oldts); + if (res < 0) { + return NULL; + } + + if (data.called == 0) { + PyErr_SetString(PyExc_RuntimeError, "atexit callback not called"); + return NULL; + } + Py_RETURN_NONE; +} + + static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, @@ -1316,6 +1348,7 @@ static PyMethodDef module_functions[] = { {"_PyTraceMalloc_GetTraceback", tracemalloc_get_traceback, METH_VARARGS}, {"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL}, {"_PyUnicode_TransformDecimalAndSpaceToASCII", unicode_transformdecimalandspacetoascii, METH_O}, + {"test_atexit", test_atexit, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Modules/_xxinterpchannelsmodule.c b/Modules/_xxinterpchannelsmodule.c index 1d7e7f1d71af3e..82472555ec7d62 100644 --- a/Modules/_xxinterpchannelsmodule.c +++ b/Modules/_xxinterpchannelsmodule.c @@ -1,9 +1,13 @@ - /* interpreters module */ /* low-level access to interpreter primitives */ +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif + #include "Python.h" #include "interpreteridobject.h" +#include "pycore_atexit.h" // _Py_AtExit() /* diff --git a/Modules/getbuildinfo.c b/Modules/getbuildinfo.c index a24750b76c09bc..8d553d106c6ab5 100644 --- a/Modules/getbuildinfo.c +++ b/Modules/getbuildinfo.c @@ -1,4 +1,9 @@ +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif + #include "Python.h" +#include "pycore_pylifecycle.h" // _Py_gitidentifier() #ifndef DONT_HAVE_STDIO_H #include diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index b9f42476e6b82c..aef802c232c6ce 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -19,6 +19,7 @@ #include "pycore_initconfig.h" // _PyStatus_EXCEPTION() #include "pycore_moduleobject.h" // _PyModule_GetState() #include "pycore_object.h" // _PyObject_LookupSpecial() +#include "pycore_pylifecycle.h" // _PyOS_URandom() #include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_signal.h" // Py_NSIG diff --git a/Modules/readline.c b/Modules/readline.c index ff7075c6822e27..a592919692cb83 100644 --- a/Modules/readline.c +++ b/Modules/readline.c @@ -4,8 +4,13 @@ * recently, it was largely rewritten by Guido van Rossum. */ +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif + /* Standard definitions */ #include "Python.h" +#include "pycore_pylifecycle.h" // _Py_SetLocaleFromEnv() #include #include diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c index 00ea4343735dab..3adb2e8dfe58d8 100644 --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -13,7 +13,7 @@ #include "pycore_moduleobject.h" // _PyModule_GetState() #include "pycore_pyerrors.h" // _PyErr_SetString() #include "pycore_pystate.h" // _PyThreadState_GET() -#include "pycore_signal.h" // Py_NSIG +#include "pycore_signal.h" // _Py_RestoreSignals() #ifndef MS_WINDOWS # include "posixmodule.h" diff --git a/Python/_warnings.c b/Python/_warnings.c index e4941f7b068d3f..e0580f01d9361d 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -1,10 +1,11 @@ #include "Python.h" +#include "pycore_frame.h" #include "pycore_initconfig.h" #include "pycore_interp.h" // PyInterpreterState.warnings #include "pycore_long.h" // _PyLong_GetZero() #include "pycore_pyerrors.h" +#include "pycore_pylifecycle.h" // _Py_IsInterpreterFinalizing() #include "pycore_pystate.h" // _PyThreadState_GET() -#include "pycore_frame.h" #include "clinic/_warnings.c.h" #define MODULE_NAME "_warnings" diff --git a/Python/bootstrap_hash.c b/Python/bootstrap_hash.c index 587063ef1ab29a..ef693e5df1fcc4 100644 --- a/Python/bootstrap_hash.c +++ b/Python/bootstrap_hash.c @@ -1,6 +1,7 @@ #include "Python.h" -#include "pycore_initconfig.h" #include "pycore_fileutils.h" // _Py_fstat_noraise() +#include "pycore_initconfig.h" +#include "pycore_pylifecycle.h" // _PyOS_URandomNonblock() #include "pycore_runtime.h" // _PyRuntime #ifdef MS_WINDOWS diff --git a/Python/preconfig.c b/Python/preconfig.c index 77a86d651eb0f4..5b26c75de8b3a0 100644 --- a/Python/preconfig.c +++ b/Python/preconfig.c @@ -2,6 +2,7 @@ #include "pycore_fileutils.h" // DECODE_LOCALE_ERR #include "pycore_getopt.h" // _PyOS_GetOpt() #include "pycore_initconfig.h" // _PyArgv +#include "pycore_pylifecycle.h" // _Py_LegacyLocaleDetected() #include "pycore_pymem.h" // _PyMem_GetAllocatorName() #include "pycore_runtime.h" // _PyRuntime_Initialize() From e4ba71fe4b32ae0d7fb3319d697616470fba1e58 Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Tue, 4 Jul 2023 03:03:57 -0700 Subject: [PATCH 20/39] GH-106008: Fix refleak when peepholing `None` comparisons (#106367) --- .../2023-07-03-11-38-43.gh-issue-106008.HDf1zd.rst | 2 ++ Python/flowgraph.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-07-03-11-38-43.gh-issue-106008.HDf1zd.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-07-03-11-38-43.gh-issue-106008.HDf1zd.rst b/Misc/NEWS.d/next/Core and Builtins/2023-07-03-11-38-43.gh-issue-106008.HDf1zd.rst new file mode 100644 index 00000000000000..a57b892fd53242 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-07-03-11-38-43.gh-issue-106008.HDf1zd.rst @@ -0,0 +1,2 @@ +Fix possible reference leaks when failing to optimize comparisons with +:const:`None` in the bytecode compiler. diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 213c993bb863a3..e159a4356dfe46 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -1377,9 +1377,9 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts) goto error; } if (!Py_IsNone(cnt)) { + Py_DECREF(cnt); break; } - Py_DECREF(cnt); if (bb->b_iused <= i + 2) { break; } From 8f6df5e9cbc3a1689601714192aa6ecbb23e1927 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 4 Jul 2023 13:36:40 +0200 Subject: [PATCH 21/39] gh-106368: Add tests for permutation helpers in Argument Clinic (#106407) Added new test class PermutationTests() --- Lib/test/test_clinic.py | 106 ++++++++++++++++++++++++++++++++++++++++ Tools/clinic/clinic.py | 4 +- 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 03754d0bf123be..544e7323e4f606 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -1586,5 +1586,111 @@ def test_cloned_func_with_converter_exception_message(self): self.assertEqual(func(), name) +class PermutationTests(unittest.TestCase): + """Test permutation support functions.""" + + def test_permute_left_option_groups(self): + expected = ( + (), + (3,), + (2, 3), + (1, 2, 3), + ) + data = list(zip([1, 2, 3])) # Generate a list of 1-tuples. + actual = tuple(clinic.permute_left_option_groups(data)) + self.assertEqual(actual, expected) + + def test_permute_right_option_groups(self): + expected = ( + (), + (1,), + (1, 2), + (1, 2, 3), + ) + data = list(zip([1, 2, 3])) # Generate a list of 1-tuples. + actual = tuple(clinic.permute_right_option_groups(data)) + self.assertEqual(actual, expected) + + def test_permute_optional_groups(self): + empty = { + "left": (), "required": (), "right": (), + "expected": ((),), + } + noleft1 = { + "left": (), "required": ("b",), "right": ("c",), + "expected": ( + ("b",), + ("b", "c"), + ), + } + noleft2 = { + "left": (), "required": ("b", "c",), "right": ("d",), + "expected": ( + ("b", "c"), + ("b", "c", "d"), + ), + } + noleft3 = { + "left": (), "required": ("b", "c",), "right": ("d", "e"), + "expected": ( + ("b", "c"), + ("b", "c", "d"), + ("b", "c", "d", "e"), + ), + } + noright1 = { + "left": ("a",), "required": ("b",), "right": (), + "expected": ( + ("b",), + ("a", "b"), + ), + } + noright2 = { + "left": ("a",), "required": ("b", "c"), "right": (), + "expected": ( + ("b", "c"), + ("a", "b", "c"), + ), + } + noright3 = { + "left": ("a", "b"), "required": ("c",), "right": (), + "expected": ( + ("c",), + ("b", "c"), + ("a", "b", "c"), + ), + } + leftandright1 = { + "left": ("a",), "required": ("b",), "right": ("c",), + "expected": ( + ("b",), + ("a", "b"), # Prefer left. + ("a", "b", "c"), + ), + } + leftandright2 = { + "left": ("a", "b"), "required": ("c", "d"), "right": ("e", "f"), + "expected": ( + ("c", "d"), + ("b", "c", "d"), # Prefer left. + ("a", "b", "c", "d"), # Prefer left. + ("a", "b", "c", "d", "e"), + ("a", "b", "c", "d", "e", "f"), + ), + } + dataset = ( + empty, + noleft1, noleft2, noleft3, + noright1, noright2, noright3, + leftandright1, leftandright2, + ) + for params in dataset: + with self.subTest(**params): + left, required, right, expected = params.values() + permutations = clinic.permute_optional_groups(left, required, right) + actual = tuple(permutations) + self.assertEqual(actual, expected) + + if __name__ == "__main__": unittest.main() diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index a07fcbd8cabf76..c02c82876591f8 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -518,7 +518,7 @@ class PythonLanguage(Language): def permute_left_option_groups(l): """ - Given [1, 2, 3], should yield: + Given [(1,), (2,), (3,)], should yield: () (3,) (2, 3) @@ -533,7 +533,7 @@ def permute_left_option_groups(l): def permute_right_option_groups(l): """ - Given [1, 2, 3], should yield: + Given [(1,), (2,), (3,)], should yield: () (1,) (1, 2) From dfe4de203881e8d068e6fc5b8e31075841a86d25 Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Tue, 4 Jul 2023 14:19:08 +0200 Subject: [PATCH 22/39] gh-106396: Special-case empty format spec to gen empty JoinedStr node (#106401) --- Lib/test/test_fstring.py | 18 ++++++++++++++++++ ...3-07-04-09-51-45.gh-issue-106396.DmYp7x.rst | 3 +++ Parser/action_helpers.c | 12 ++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-07-04-09-51-45.gh-issue-106396.DmYp7x.rst diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py index ba223ae124a8fb..cb14bba2602def 100644 --- a/Lib/test/test_fstring.py +++ b/Lib/test/test_fstring.py @@ -496,6 +496,24 @@ def test_ast_line_numbers_with_parentheses(self): self.assertEqual(wat2.end_col_offset, 17) self.assertEqual(fstring.end_col_offset, 18) + def test_ast_fstring_empty_format_spec(self): + expr = "f'{expr:}'" + + mod = ast.parse(expr) + self.assertEqual(type(mod), ast.Module) + self.assertEqual(len(mod.body), 1) + + fstring = mod.body[0].value + self.assertEqual(type(fstring), ast.JoinedStr) + self.assertEqual(len(fstring.values), 1) + + fv = fstring.values[0] + self.assertEqual(type(fv), ast.FormattedValue) + + format_spec = fv.format_spec + self.assertEqual(type(format_spec), ast.JoinedStr) + self.assertEqual(len(format_spec.values), 0) + def test_docstring(self): def f(): f'''Not a docstring''' diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-07-04-09-51-45.gh-issue-106396.DmYp7x.rst b/Misc/NEWS.d/next/Core and Builtins/2023-07-04-09-51-45.gh-issue-106396.DmYp7x.rst new file mode 100644 index 00000000000000..c5767e97271d9d --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-07-04-09-51-45.gh-issue-106396.DmYp7x.rst @@ -0,0 +1,3 @@ +When the format specification of an f-string expression is empty, the parser now +generates an empty :class:`ast.JoinedStr` node for it instead of an one-element +:class:`ast.JoinedStr` with an empty string :class:`ast.Constant`. diff --git a/Parser/action_helpers.c b/Parser/action_helpers.c index 70c267bb212fcb..36e0750220a30d 100644 --- a/Parser/action_helpers.c +++ b/Parser/action_helpers.c @@ -997,6 +997,18 @@ _PyPegen_setup_full_format_spec(Parser *p, Token *colon, asdl_expr_seq *spec, in if (!spec) { return NULL; } + + // This is needed to keep compatibility with 3.11, where an empty format spec is parsed + // as an *empty* JoinedStr node, instead of having an empty constant in it. + if (asdl_seq_LEN(spec) == 1) { + expr_ty e = asdl_seq_GET(spec, 0); + if (e->kind == Constant_kind + && PyUnicode_Check(e->v.Constant.value) + && PyUnicode_GetLength(e->v.Constant.value) == 0) { + spec = _Py_asdl_expr_seq_new(0, arena); + } + } + expr_ty res = _PyAST_JoinedStr(spec, lineno, col_offset, end_lineno, end_col_offset, p->arena); if (!res) { return NULL; From 80f1c6c49b4cd2bf698eb2bc3d2f3da904880dd2 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Tue, 4 Jul 2023 17:19:20 +0300 Subject: [PATCH 23/39] gh-106406: Fix _Py_IsInterpreterFinalizing() in _winapi.c (#106408) --- Modules/_winapi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/_winapi.c b/Modules/_winapi.c index d4291f557b6a66..313c12a34c6725 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -36,6 +36,7 @@ #include "Python.h" #include "pycore_moduleobject.h" // _PyModule_GetState() +#include "pycore_pylifecycle.h" // _Py_IsInterpreterFinalizing() #include "pycore_pystate.h" // _PyInterpreterState_GET #include "structmember.h" // PyMemberDef From 318ea2c72e9aed7ac92457c28747eda9424c8327 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 4 Jul 2023 17:23:00 +0100 Subject: [PATCH 24/39] GH-106360: Support very basic superblock introspection (#106422) * Add len() and indexing support to uop superblocks. --- Include/cpython/optimizer.h | 2 + Include/internal/pycore_opcode.h | 9 ++-- Lib/test/test_capi/test_misc.py | 23 ++++++++ Modules/_testinternalcapi.c | 21 ++++++++ Python/opcode_metadata.h | 6 +-- Python/optimizer.c | 70 +++++++++++++++++++++++++ Tools/build/generate_opcode_h.py | 7 +-- Tools/cases_generator/generate_cases.py | 6 +-- 8 files changed, 130 insertions(+), 14 deletions(-) diff --git a/Include/cpython/optimizer.h b/Include/cpython/optimizer.h index 2664f5bc4b1742..2260501bfd608e 100644 --- a/Include/cpython/optimizer.h +++ b/Include/cpython/optimizer.h @@ -38,6 +38,8 @@ PyAPI_FUNC(void) PyUnstable_SetOptimizer(_PyOptimizerObject* optimizer); PyAPI_FUNC(_PyOptimizerObject *) PyUnstable_GetOptimizer(void); +PyAPI_FUNC(_PyExecutorObject *)PyUnstable_GetExecutor(PyCodeObject *code, int offset); + struct _PyInterpreterFrame * _PyOptimizer_BackEdge(struct _PyInterpreterFrame *frame, _Py_CODEUNIT *src, _Py_CODEUNIT *dest, PyObject **stack_pointer); diff --git a/Include/internal/pycore_opcode.h b/Include/internal/pycore_opcode.h index 428df4ccadbc19..c7c2fdcc327884 100644 --- a/Include/internal/pycore_opcode.h +++ b/Include/internal/pycore_opcode.h @@ -242,8 +242,11 @@ const uint8_t _PyOpcode_Deopt[256] = { }; #endif // NEED_OPCODE_TABLES -#ifdef Py_DEBUG -static const char *const _PyOpcode_OpName[268] = { + +extern const char *const _PyOpcode_OpName[268]; + +#ifdef NEED_OPCODE_TABLES +const char *const _PyOpcode_OpName[268] = { [CACHE] = "CACHE", [POP_TOP] = "POP_TOP", [PUSH_NULL] = "PUSH_NULL", @@ -513,7 +516,7 @@ static const char *const _PyOpcode_OpName[268] = { [STORE_FAST_MAYBE_NULL] = "STORE_FAST_MAYBE_NULL", [LOAD_CLOSURE] = "LOAD_CLOSURE", }; -#endif +#endif // NEED_OPCODE_TABLES #define EXTRA_CASES \ case 184: \ diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 9e825a343eb318..de9f00a9e5fb48 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -2415,5 +2415,28 @@ def long_loop(): self.assertEqual(opt.get_count(), 10) +class TestUops(unittest.TestCase): + + def test_basic_loop(self): + + def testfunc(x): + i = 0 + while i < x: + i += 1 + + testfunc(1000) + + ex = None + for offset in range(0, 100, 2): + try: + ex = _testinternalcapi.get_executor(testfunc.__code__, offset) + break + except ValueError: + pass + if ex is None: + return + self.assertIn("SAVE_IP", str(ex)) + + if __name__ == "__main__": unittest.main() diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 84511d27e37f45..52e524a40672ed 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -858,6 +858,26 @@ get_optimizer(PyObject *self, PyObject *Py_UNUSED(ignored)) return opt; } +static PyObject * +get_executor(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + + if (!_PyArg_CheckPositional("get_executor", nargs, 2, 2)) { + return NULL; + } + PyObject *code = args[0]; + PyObject *offset = args[1]; + long ioffset = PyLong_AsLong(offset); + if (ioffset == -1 && PyErr_Occurred()) { + return NULL; + } + if (!PyCode_Check(code)) { + PyErr_SetString(PyExc_TypeError, "first argument must be a code object"); + return NULL; + } + return (PyObject *)PyUnstable_GetExecutor((PyCodeObject *)code, ioffset); +} + static int _pending_callback(void *arg) { /* we assume the argument is callable object to which we own a reference */ @@ -1326,6 +1346,7 @@ static PyMethodDef module_functions[] = { {"iframe_getlasti", iframe_getlasti, METH_O, NULL}, {"get_optimizer", get_optimizer, METH_NOARGS, NULL}, {"set_optimizer", set_optimizer, METH_O, NULL}, + {"get_executor", _PyCFunction_CAST(get_executor), METH_FASTCALL, NULL}, {"get_counter_optimizer", get_counter_optimizer, METH_NOARGS, NULL}, {"get_uop_optimizer", get_uop_optimizer, METH_NOARGS, NULL}, {"pending_threadfunc", _PyCFunction_CAST(pending_threadfunc), diff --git a/Python/opcode_metadata.h b/Python/opcode_metadata.h index 82c98235892287..ac86a4abd9c1b3 100644 --- a/Python/opcode_metadata.h +++ b/Python/opcode_metadata.h @@ -942,9 +942,7 @@ struct opcode_macro_expansion { #ifndef NEED_OPCODE_METADATA extern const struct opcode_metadata _PyOpcode_opcode_metadata[512]; extern const struct opcode_macro_expansion _PyOpcode_macro_expansion[256]; -#ifdef Py_DEBUG extern const char * const _PyOpcode_uop_name[512]; -#endif #else const struct opcode_metadata _PyOpcode_opcode_metadata[512] = { [NOP] = { true, INSTR_FMT_IX, 0 }, @@ -1265,7 +1263,7 @@ const struct opcode_macro_expansion _PyOpcode_macro_expansion[256] = { [COPY] = { .nuops = 1, .uops = { { COPY, 0, 0 } } }, [SWAP] = { .nuops = 1, .uops = { { SWAP, 0, 0 } } }, }; -#ifdef Py_DEBUG +#ifdef NEED_OPCODE_METADATA const char * const _PyOpcode_uop_name[512] = { [300] = "EXIT_TRACE", [301] = "SAVE_IP", @@ -1282,5 +1280,5 @@ const char * const _PyOpcode_uop_name[512] = { [312] = "_LOAD_LOCALS", [313] = "_LOAD_FROM_DICT_OR_GLOBALS", }; -#endif +#endif // NEED_OPCODE_METADATA #endif diff --git a/Python/optimizer.c b/Python/optimizer.c index 32f0b1477d203c..c3ab649b51b0eb 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -188,6 +188,23 @@ _PyOptimizer_BackEdge(_PyInterpreterFrame *frame, _Py_CODEUNIT *src, _Py_CODEUNI return frame; } +_PyExecutorObject * +PyUnstable_GetExecutor(PyCodeObject *code, int offset) +{ + int code_len = (int)Py_SIZE(code); + for (int i = 0 ; i < code_len;) { + if (_PyCode_CODE(code)[i].op.code == ENTER_EXECUTOR && i*2 == offset) { + int oparg = _PyCode_CODE(code)[i].op.arg; + _PyExecutorObject *res = code->co_executors->executors[oparg]; + Py_INCREF(res); + return res; + } + i += _PyInstruction_GetLength(code, i); + } + PyErr_SetString(PyExc_ValueError, "no executor at given offset"); + return NULL; +} + /** Test support **/ @@ -287,6 +304,58 @@ uop_dealloc(_PyUOpExecutorObject *self) { PyObject_Free(self); } +static const char * +uop_name(int index) { + if (index < EXIT_TRACE) { + return _PyOpcode_OpName[index]; + } + return _PyOpcode_uop_name[index]; +} + +static Py_ssize_t +uop_len(_PyUOpExecutorObject *self) +{ + int count = 1; + for (; count < _Py_UOP_MAX_TRACE_LENGTH; count++) { + if (self->trace[count-1].opcode == EXIT_TRACE) { + break; + } + } + return count; +} + +static PyObject * +uop_item(_PyUOpExecutorObject *self, Py_ssize_t index) +{ + for (int i = 0; i < _Py_UOP_MAX_TRACE_LENGTH; i++) { + if (self->trace[i].opcode == EXIT_TRACE) { + break; + } + if (i != index) { + continue; + } + const char *name = uop_name(self->trace[i].opcode); + PyObject *oname = _PyUnicode_FromASCII(name, strlen(name)); + if (oname == NULL) { + return NULL; + } + PyObject *operand = PyLong_FromUnsignedLongLong(self->trace[i].operand); + if (operand == NULL) { + Py_DECREF(oname); + return NULL; + } + PyObject *args[2] = { oname, operand }; + return _PyTuple_FromArraySteal(args, 2); + } + PyErr_SetNone(PyExc_IndexError); + return NULL; +} + +PySequenceMethods uop_as_sequence = { + .sq_length = (lenfunc)uop_len, + .sq_item = (ssizeargfunc)uop_item, +}; + static PyTypeObject UOpExecutor_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) .tp_name = "uop_executor", @@ -294,6 +363,7 @@ static PyTypeObject UOpExecutor_Type = { .tp_itemsize = 0, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, .tp_dealloc = (destructor)uop_dealloc, + .tp_as_sequence = &uop_as_sequence, }; static int diff --git a/Tools/build/generate_opcode_h.py b/Tools/build/generate_opcode_h.py index 4711fbbd1eb8c3..2e841e6097aa25 100644 --- a/Tools/build/generate_opcode_h.py +++ b/Tools/build/generate_opcode_h.py @@ -184,14 +184,15 @@ def main(opcode_py, fobj.write(f"#define ENABLE_SPECIALIZATION {int(ENABLE_SPECIALIZATION)}") iobj.write("\n") - iobj.write("#ifdef Py_DEBUG\n") - iobj.write(f"static const char *const _PyOpcode_OpName[{NUM_OPCODES}] = {{\n") + iobj.write(f"\nextern const char *const _PyOpcode_OpName[{NUM_OPCODES}];\n") + iobj.write("\n#ifdef NEED_OPCODE_TABLES\n") + iobj.write(f"const char *const _PyOpcode_OpName[{NUM_OPCODES}] = {{\n") for op, name in enumerate(opname_including_specialized): if name[0] != "<": op = name iobj.write(f''' [{op}] = "{name}",\n''') iobj.write("};\n") - iobj.write("#endif\n") + iobj.write("#endif // NEED_OPCODE_TABLES\n") iobj.write("\n") iobj.write("#define EXTRA_CASES \\\n") diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index 657dfa93fd537d..a90abfe20c1739 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -1224,9 +1224,7 @@ def write_metadata(self) -> None: self.out.emit("#ifndef NEED_OPCODE_METADATA") self.out.emit("extern const struct opcode_metadata _PyOpcode_opcode_metadata[512];") self.out.emit("extern const struct opcode_macro_expansion _PyOpcode_macro_expansion[256];") - self.out.emit("#ifdef Py_DEBUG") self.out.emit("extern const char * const _PyOpcode_uop_name[512];") - self.out.emit("#endif") self.out.emit("#else") self.out.emit("const struct opcode_metadata _PyOpcode_opcode_metadata[512] = {") @@ -1273,10 +1271,10 @@ def write_metadata(self) -> None: case _: typing.assert_never(thing) - self.out.emit("#ifdef Py_DEBUG") + self.out.emit("#ifdef NEED_OPCODE_METADATA") with self.out.block("const char * const _PyOpcode_uop_name[512] =", ";"): self.write_uop_items(lambda name, counter: f"[{counter}] = \"{name}\",") - self.out.emit("#endif") + self.out.emit("#endif // NEED_OPCODE_METADATA") self.out.emit("#endif") From aa85c93792876a602fc9ce2083b64958507d29bf Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 4 Jul 2023 18:55:45 +0200 Subject: [PATCH 25/39] gh-106320: Remove _PyInterpreterState_HasFeature() (#106425) Remove the _PyInterpreterState_HasFeature() function from the C API: move it to the internal C API (pycore_interp.h). No longer export the function. --- Include/cpython/pystate.h | 32 -------------------------------- Include/internal/pycore_interp.h | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index e08bcdaf197628..4254110889fc6c 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -3,38 +3,6 @@ #endif -/* -Runtime Feature Flags - -Each flag indicate whether or not a specific runtime feature -is available in a given context. For example, forking the process -might not be allowed in the current interpreter (i.e. os.fork() would fail). -*/ - -/* Set if the interpreter share obmalloc runtime state - with the main interpreter. */ -#define Py_RTFLAGS_USE_MAIN_OBMALLOC (1UL << 5) - -/* Set if import should check a module for subinterpreter support. */ -#define Py_RTFLAGS_MULTI_INTERP_EXTENSIONS (1UL << 8) - -/* Set if threads are allowed. */ -#define Py_RTFLAGS_THREADS (1UL << 10) - -/* Set if daemon threads are allowed. */ -#define Py_RTFLAGS_DAEMON_THREADS (1UL << 11) - -/* Set if os.fork() is allowed. */ -#define Py_RTFLAGS_FORK (1UL << 15) - -/* Set if os.exec*() is allowed. */ -#define Py_RTFLAGS_EXEC (1UL << 16) - - -PyAPI_FUNC(int) _PyInterpreterState_HasFeature(PyInterpreterState *interp, - unsigned long feature); - - /* private interpreter helpers */ PyAPI_FUNC(int) _PyInterpreterState_RequiresIDRef(PyInterpreterState *); diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index a1f00df12d6ac9..bb37cafe6286a9 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -275,6 +275,38 @@ PyAPI_FUNC(int) _PyInterpreterState_GetConfigCopy( PyAPI_FUNC(int) _PyInterpreterState_SetConfig( const struct PyConfig *config); + +/* +Runtime Feature Flags + +Each flag indicate whether or not a specific runtime feature +is available in a given context. For example, forking the process +might not be allowed in the current interpreter (i.e. os.fork() would fail). +*/ + +/* Set if the interpreter share obmalloc runtime state + with the main interpreter. */ +#define Py_RTFLAGS_USE_MAIN_OBMALLOC (1UL << 5) + +/* Set if import should check a module for subinterpreter support. */ +#define Py_RTFLAGS_MULTI_INTERP_EXTENSIONS (1UL << 8) + +/* Set if threads are allowed. */ +#define Py_RTFLAGS_THREADS (1UL << 10) + +/* Set if daemon threads are allowed. */ +#define Py_RTFLAGS_DAEMON_THREADS (1UL << 11) + +/* Set if os.fork() is allowed. */ +#define Py_RTFLAGS_FORK (1UL << 15) + +/* Set if os.exec*() is allowed. */ +#define Py_RTFLAGS_EXEC (1UL << 16) + +extern int _PyInterpreterState_HasFeature(PyInterpreterState *interp, + unsigned long feature); + + #ifdef __cplusplus } #endif From 8a4bba8b9764ba28667b137fe62c11aea672f500 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 5 Jul 2023 02:02:49 +0900 Subject: [PATCH 26/39] gh-106162: array: suppress warning in test_array (#106404) array: suppress warning in test_array --- Lib/test/test_array.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py index bec3766b87b202..a219fa365e7f20 100755 --- a/Lib/test/test_array.py +++ b/Lib/test/test_array.py @@ -215,6 +215,14 @@ class BaseTest: # outside: An entry that is not in example # minitemsize: the minimum guaranteed itemsize + def setUp(self): + self.enterContext(warnings.catch_warnings()) + warnings.filterwarnings( + "ignore", + message="The 'u' type code is deprecated and " + "will be removed in Python 3.16", + category=DeprecationWarning) + def assertEntryEqual(self, entry1, entry2): self.assertEqual(entry1, entry2) From 2dfc7fae787e65726f24bfe9efe05418b05ee8e2 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Tue, 4 Jul 2023 19:34:43 +0200 Subject: [PATCH 27/39] gh-61215: Rename `wait_until_any_call` to `wait_until_any_call_with` (#106414) mock: Rename `wait_until_any_call` to `wait_until_any_call_with` Rename the method to be more explicit that it expects the args and kwargs to wait for. --- Doc/library/unittest.mock.rst | 4 +- .../testmock/testthreadingmock.py | 50 +++++++++---------- Lib/unittest/mock.py | 2 +- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index 756422bd6eab9e..6d5f17d1c2c5cd 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -1126,7 +1126,7 @@ object:: >>> mock.wait_until_called(timeout=1) >>> thread.join() - .. method:: wait_until_any_call(*args, **kwargs) + .. method:: wait_until_any_call_with(*args, **kwargs) Waits until the the mock is called with the specified arguments. @@ -1136,7 +1136,7 @@ object:: >>> mock = ThreadingMock() >>> thread = threading.Thread(target=mock, args=("arg1", "arg2",), kwargs={"arg": "thing"}) >>> thread.start() - >>> mock.wait_until_any_call("arg1", "arg2", arg="thing") + >>> mock.wait_until_any_call_with("arg1", "arg2", arg="thing") >>> thread.join() .. attribute:: DEFAULT_TIMEOUT diff --git a/Lib/test/test_unittest/testmock/testthreadingmock.py b/Lib/test/test_unittest/testmock/testthreadingmock.py index 6219cc58abdc44..2b4dca3a4be179 100644 --- a/Lib/test/test_unittest/testmock/testthreadingmock.py +++ b/Lib/test/test_unittest/testmock/testthreadingmock.py @@ -87,7 +87,7 @@ def test_no_name_clash(self): waitable_mock.timeout = "mytimeout" waitable_mock("works") waitable_mock.wait_until_called() - waitable_mock.wait_until_any_call("works") + waitable_mock.wait_until_any_call_with("works") def test_wait_success(self): waitable_mock = self._make_mock(spec=Something) @@ -96,7 +96,7 @@ def test_wait_success(self): something = Something() self.run_async(something.method_1, delay=0.01) something.method_1.wait_until_called() - something.method_1.wait_until_any_call() + something.method_1.wait_until_any_call_with() something.method_1.assert_called() def test_wait_success_with_instance_timeout(self): @@ -106,7 +106,7 @@ def test_wait_success_with_instance_timeout(self): something = Something() self.run_async(something.method_1, delay=0.01) something.method_1.wait_until_called() - something.method_1.wait_until_any_call() + something.method_1.wait_until_any_call_with() something.method_1.assert_called() def test_wait_failed_with_instance_timeout(self): @@ -117,7 +117,7 @@ def test_wait_failed_with_instance_timeout(self): self.run_async(something.method_1, delay=0.5) self.assertRaises(AssertionError, waitable_mock.method_1.wait_until_called) self.assertRaises( - AssertionError, waitable_mock.method_1.wait_until_any_call + AssertionError, waitable_mock.method_1.wait_until_any_call_with ) def test_wait_success_with_timeout_override(self): @@ -137,7 +137,7 @@ def test_wait_failed_with_timeout_override(self): with self.assertRaises(AssertionError): something.method_1.wait_until_called(timeout=0.05) with self.assertRaises(AssertionError): - something.method_1.wait_until_any_call(timeout=0.05) + something.method_1.wait_until_any_call_with(timeout=0.05) def test_wait_success_called_before(self): waitable_mock = self._make_mock() @@ -146,7 +146,7 @@ def test_wait_success_called_before(self): something = Something() something.method_1() something.method_1.wait_until_called() - something.method_1.wait_until_any_call() + something.method_1.wait_until_any_call_with() something.method_1.assert_called() def test_wait_magic_method(self): @@ -158,7 +158,7 @@ def test_wait_magic_method(self): something.method_1.__str__.wait_until_called() something.method_1.__str__.assert_called() - def test_wait_until_any_call_positional(self): + def test_wait_until_any_call_with_positional(self): waitable_mock = self._make_mock(spec=Something) with patch(f"{__name__}.Something", waitable_mock): @@ -168,16 +168,16 @@ def test_wait_until_any_call_positional(self): self.run_async(something.method_1, 3, delay=0.3) self.assertNotIn(call(1), something.method_1.mock_calls) - something.method_1.wait_until_any_call(1) + something.method_1.wait_until_any_call_with(1) something.method_1.assert_called_with(1) self.assertNotIn(call(2), something.method_1.mock_calls) self.assertNotIn(call(3), something.method_1.mock_calls) - something.method_1.wait_until_any_call(3) + something.method_1.wait_until_any_call_with(3) self.assertIn(call(2), something.method_1.mock_calls) - something.method_1.wait_until_any_call(2) + something.method_1.wait_until_any_call_with(2) - def test_wait_until_any_call_keywords(self): + def test_wait_until_any_call_with_keywords(self): waitable_mock = self._make_mock(spec=Something) with patch(f"{__name__}.Something", waitable_mock): @@ -187,16 +187,16 @@ def test_wait_until_any_call_keywords(self): self.run_async(something.method_1, c=3, delay=0.3) self.assertNotIn(call(a=1), something.method_1.mock_calls) - something.method_1.wait_until_any_call(a=1) + something.method_1.wait_until_any_call_with(a=1) something.method_1.assert_called_with(a=1) self.assertNotIn(call(b=2), something.method_1.mock_calls) self.assertNotIn(call(c=3), something.method_1.mock_calls) - something.method_1.wait_until_any_call(c=3) + something.method_1.wait_until_any_call_with(c=3) self.assertIn(call(b=2), something.method_1.mock_calls) - something.method_1.wait_until_any_call(b=2) + something.method_1.wait_until_any_call_with(b=2) - def test_wait_until_any_call_no_argument_fails_when_called_with_arg(self): + def test_wait_until_any_call_with_no_argument_fails_when_called_with_arg(self): waitable_mock = self._make_mock(timeout=0.01) with patch(f"{__name__}.Something", waitable_mock): @@ -205,25 +205,25 @@ def test_wait_until_any_call_no_argument_fails_when_called_with_arg(self): something.method_1.assert_called_with(1) with self.assertRaises(AssertionError): - something.method_1.wait_until_any_call() + something.method_1.wait_until_any_call_with() something.method_1() - something.method_1.wait_until_any_call() + something.method_1.wait_until_any_call_with() - def test_wait_until_any_call_global_default(self): + def test_wait_until_any_call_with_global_default(self): with patch.object(ThreadingMock, "DEFAULT_TIMEOUT"): ThreadingMock.DEFAULT_TIMEOUT = 0.01 m = self._make_mock() with self.assertRaises(AssertionError): - m.wait_until_any_call() + m.wait_until_any_call_with() with self.assertRaises(AssertionError): m.wait_until_called() m() - m.wait_until_any_call() + m.wait_until_any_call_with() assert ThreadingMock.DEFAULT_TIMEOUT != 0.01 - def test_wait_until_any_call_change_global_and_override(self): + def test_wait_until_any_call_with_change_global_and_override(self): with patch.object(ThreadingMock, "DEFAULT_TIMEOUT"): ThreadingMock.DEFAULT_TIMEOUT = 0.01 @@ -256,10 +256,10 @@ def test_reset_mock_resets_wait(self): with self.assertRaises(AssertionError): m.wait_until_called() with self.assertRaises(AssertionError): - m.wait_until_any_call() + m.wait_until_any_call_with() m() m.wait_until_called() - m.wait_until_any_call() + m.wait_until_any_call_with() m.assert_called_once() m.reset_mock() @@ -267,10 +267,10 @@ def test_reset_mock_resets_wait(self): with self.assertRaises(AssertionError): m.wait_until_called() with self.assertRaises(AssertionError): - m.wait_until_any_call() + m.wait_until_any_call_with() m() m.wait_until_called() - m.wait_until_any_call() + m.wait_until_any_call_with() m.assert_called_once() diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index b542a9a2c51a9f..7ef7e7180b31c2 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -3070,7 +3070,7 @@ def wait_until_called(self, *, timeout=_timeout_unset): f" timeout({timeout}).") raise AssertionError(msg) - def wait_until_any_call(self, *args, **kwargs): + def wait_until_any_call_with(self, *args, **kwargs): """Wait until the mock object is called with given args. Waits for the timeout in seconds provided in the constructor. From c5dacc8fa0c3013be8b457afac996bdae1dc12d2 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Tue, 4 Jul 2023 21:20:00 +0300 Subject: [PATCH 28/39] gh-106217: Truncate the issue body size of `new-bugs-announce-notifier` (#106423) --- .github/workflows/new-bugs-announce-notifier.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/new-bugs-announce-notifier.yml b/.github/workflows/new-bugs-announce-notifier.yml index e3572db670693e..80514b4d2ca572 100644 --- a/.github/workflows/new-bugs-announce-notifier.yml +++ b/.github/workflows/new-bugs-announce-notifier.yml @@ -44,7 +44,7 @@ jobs: // We need to truncate the body size, because the max size for // the whole payload is 16kb. We want to be safe and assume that // body can take up to ~8kb of space. - body : issue.data.body.substring(8000) + body : issue.data.body.substring(0, 8000) }; const data = { From eeb5c63179eba0390c8937d0803a41092225be7e Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 4 Jul 2023 20:45:32 +0100 Subject: [PATCH 29/39] Add some codeowners for `Tools/clinic/` (#106430) Co-authored-by: Erlend E. Aasland --- .github/CODEOWNERS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c044e9b8f88c58..5b471c79f75eea 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -173,3 +173,7 @@ Doc/c-api/stable.rst @encukou # zipfile.Path **/*zipfile/*_path.py @jaraco + +# Argument Clinic +/Tools/clinic/** @erlend-aasland @AlexWaygood +/Lib/test/test_clinic.py @erlend-aasland @AlexWaygood From 17af98227f883a93bfbd866b4bcc719ba9d682a1 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 4 Jul 2023 14:42:12 -0700 Subject: [PATCH 30/39] gh-106320: Fix specialize.c compilation by including pycore_pylifecycle.h (#106434) Compilation of Python/specialize.c was broken on macOS for me by gh-106400. --- Python/specialize.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Python/specialize.c b/Python/specialize.c index 22c58e2c46fc36..a3fce2e6775912 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -9,6 +9,7 @@ #include "pycore_opcode.h" // _PyOpcode_Caches #include "structmember.h" // struct PyMemberDef, T_OFFSET_EX #include "pycore_descrobject.h" +#include "pycore_pylifecycle.h" // _PyOS_URandomNonblock() #include // rand() From 110b97c94ce2544dc731298e35dfb003d58af626 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 5 Jul 2023 00:13:30 +0200 Subject: [PATCH 31/39] gh-104050: Annotate toplevel functions in clinic.py (#106435) Co-authored-by: Alex Waygood --- Tools/clinic/clinic.py | 63 +++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index c02c82876591f8..3d18d9560bc28b 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -575,7 +575,7 @@ def permute_optional_groups(left, required, right): return tuple(accumulator) -def strip_leading_and_trailing_blank_lines(s): +def strip_leading_and_trailing_blank_lines(s: str) -> str: lines = s.rstrip().split('\n') while lines: line = lines[0] @@ -585,7 +585,11 @@ def strip_leading_and_trailing_blank_lines(s): return '\n'.join(lines) @functools.lru_cache() -def normalize_snippet(s, *, indent=0): +def normalize_snippet( + s: str, + *, + indent: int = 0 +) -> str: """ Reformats s: * removes leading and trailing blank lines @@ -599,7 +603,11 @@ def normalize_snippet(s, *, indent=0): return s -def declare_parser(f, *, hasformat=False): +def declare_parser( + f: Function, + *, + hasformat: bool = False +) -> str: """ Generates the code template for a static local PyArg_Parser variable, with an initializer. For core code (incl. builtin modules) the @@ -658,7 +666,10 @@ def declare_parser(f, *, hasformat=False): return normalize_snippet(declarations) -def wrap_declarations(text, length=78): +def wrap_declarations( + text: str, + length: int = 78 +) -> str: """ A simple-minded text wrapper for C function declarations. @@ -680,14 +691,14 @@ def wrap_declarations(text, length=78): if not after_l_paren: lines.append(line) continue - parameters, _, after_r_paren = after_l_paren.partition(')') + in_paren, _, after_r_paren = after_l_paren.partition(')') if not _: lines.append(line) continue - if ',' not in parameters: + if ',' not in in_paren: lines.append(line) continue - parameters = [x.strip() + ", " for x in parameters.split(',')] + parameters = [x.strip() + ", " for x in in_paren.split(',')] prefix += "(" if len(prefix) < length: spaces = " " * len(prefix) @@ -1589,7 +1600,12 @@ def OverrideStdioWith(stdout): sys.stdout = saved_stdout -def create_regex(before, after, word=True, whole_line=True): +def create_regex( + before: str, + after: str, + word: bool = True, + whole_line: bool = True +) -> re.Pattern[str]: """Create an re object for matching marker lines.""" group_re = r"\w+" if word else ".+" pattern = r'{}({}){}' @@ -1985,7 +2001,7 @@ def file_changed(filename: str, new_contents: str) -> bool: return True -def write_file(filename: str, new_contents: str): +def write_file(filename: str, new_contents: str) -> None: # Atomic write using a temporary file and os.replace() filename_new = f"{filename}.new" with open(filename_new, "w", encoding="utf-8") as fp: @@ -2602,7 +2618,10 @@ def __getattribute__(self, name: str): fail("Stepped on a land mine, trying to access attribute " + repr(name) + ":\n" + self.__message__) -def add_c_converter(f, name=None): +def add_c_converter( + f: type[CConverter], + name: str | None = None +) -> type[CConverter]: if not name: name = f.__name__ if not name.endswith('_converter'): @@ -2620,7 +2639,10 @@ def add_default_legacy_c_converter(cls): legacy_converters[cls.format_unit] = cls return cls -def add_legacy_c_converter(format_unit, **kwargs): +def add_legacy_c_converter( + format_unit: str, + **kwargs +) -> Callable[[ConverterType], ConverterType]: """ Adds a legacy converter. """ @@ -3887,7 +3909,9 @@ def parse_arg(self, argname: str, displayname: str) -> str: return super().parse_arg(argname, displayname) -def correct_name_for_self(f) -> tuple[str, str]: +def correct_name_for_self( + f: Function +) -> tuple[str, str]: if f.kind in (CALLABLE, METHOD_INIT): if f.cls: return "PyObject *", "self" @@ -3898,7 +3922,9 @@ def correct_name_for_self(f) -> tuple[str, str]: return "PyTypeObject *", "type" raise RuntimeError("Unhandled type of function f: " + repr(f.kind)) -def required_type_for_self_for_parser(f): +def required_type_for_self_for_parser( + f: Function +) -> str | None: type, _ = correct_name_for_self(f) if f.kind in (METHOD_INIT, METHOD_NEW, STATIC_METHOD, CLASS_METHOD): return type @@ -4193,7 +4219,12 @@ class float_return_converter(double_return_converter): cast = '(double)' -def eval_ast_expr(node, globals, *, filename='-'): +def eval_ast_expr( + node: ast.expr, + globals: dict[str, Any], + *, + filename: str = '-' +) -> FunctionType: """ Takes an ast.Expr node. Compiles and evaluates it. Returns the result of the expression. @@ -4205,8 +4236,8 @@ def eval_ast_expr(node, globals, *, filename='-'): if isinstance(node, ast.Expr): node = node.value - node = ast.Expression(node) - co = compile(node, filename, 'eval') + expr = ast.Expression(node) + co = compile(expr, filename, 'eval') fn = FunctionType(co, globals) return fn() From 22087516bca0b339a04742765febc9c20a9d6b21 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 5 Jul 2023 00:15:10 +0200 Subject: [PATCH 32/39] gh-104050: Annotate Argument Clinic parameter permutation helpers (#106431) Co-authored-by: Alex Waygood --- Tools/clinic/clinic.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 3d18d9560bc28b..898361474f7214 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -28,7 +28,12 @@ import textwrap import traceback -from collections.abc import Callable +from collections.abc import ( + Callable, + Iterable, + Iterator, + Sequence, +) from types import FunctionType, NoneType from typing import ( Any, @@ -516,7 +521,13 @@ class PythonLanguage(Language): checksum_line = "#/*[{dsl_name} end generated code: {arguments}]*/" -def permute_left_option_groups(l): +ParamGroup = Iterable["Parameter"] +ParamTuple = tuple["Parameter", ...] + + +def permute_left_option_groups( + l: Sequence[ParamGroup] +) -> Iterator[ParamTuple]: """ Given [(1,), (2,), (3,)], should yield: () @@ -525,13 +536,15 @@ def permute_left_option_groups(l): (1, 2, 3) """ yield tuple() - accumulator = [] + accumulator: list[Parameter] = [] for group in reversed(l): accumulator = list(group) + accumulator yield tuple(accumulator) -def permute_right_option_groups(l): +def permute_right_option_groups( + l: Sequence[ParamGroup] +) -> Iterator[ParamTuple]: """ Given [(1,), (2,), (3,)], should yield: () @@ -540,13 +553,17 @@ def permute_right_option_groups(l): (1, 2, 3) """ yield tuple() - accumulator = [] + accumulator: list[Parameter] = [] for group in l: accumulator.extend(group) yield tuple(accumulator) -def permute_optional_groups(left, required, right): +def permute_optional_groups( + left: Sequence[ParamGroup], + required: ParamGroup, + right: Sequence[ParamGroup] +) -> tuple[ParamTuple, ...]: """ Generator function that computes the set of acceptable argument lists for the provided iterables of @@ -561,7 +578,7 @@ def permute_optional_groups(left, required, right): if left: raise ValueError("required is empty but left is not") - accumulator = [] + accumulator: list[ParamTuple] = [] counts = set() for r in permute_right_option_groups(right): for l in permute_left_option_groups(left): From 2fb9480c8313ab524d333b18e4af09f05f5b8afa Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 5 Jul 2023 00:35:57 +0200 Subject: [PATCH 33/39] gh-106368: Add tests for formatting helpers in Argument Clinic (#106415) Co-authored-by: Alex Waygood --- Lib/test/test_clinic.py | 164 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 544e7323e4f606..c095d14234375f 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -1692,5 +1692,169 @@ def test_permute_optional_groups(self): self.assertEqual(actual, expected) +class FormatHelperTests(unittest.TestCase): + + def test_strip_leading_and_trailing_blank_lines(self): + dataset = ( + # Input lines, expected output. + ("a\nb", "a\nb"), + ("a\nb\n", "a\nb"), + ("a\nb ", "a\nb"), + ("\na\nb\n\n", "a\nb"), + ("\n\na\nb\n\n", "a\nb"), + ("\n\na\n\nb\n\n", "a\n\nb"), + # Note, leading whitespace is preserved: + (" a\nb", " a\nb"), + (" a\nb ", " a\nb"), + (" \n \n a\nb \n \n ", " a\nb"), + ) + for lines, expected in dataset: + with self.subTest(lines=lines, expected=expected): + out = clinic.strip_leading_and_trailing_blank_lines(lines) + self.assertEqual(out, expected) + + def test_normalize_snippet(self): + snippet = """ + one + two + three + """ + + # Expected outputs: + zero_indent = ( + "one\n" + "two\n" + "three" + ) + four_indent = ( + " one\n" + " two\n" + " three" + ) + eight_indent = ( + " one\n" + " two\n" + " three" + ) + expected_outputs = {0: zero_indent, 4: four_indent, 8: eight_indent} + for indent, expected in expected_outputs.items(): + with self.subTest(indent=indent): + actual = clinic.normalize_snippet(snippet, indent=indent) + self.assertEqual(actual, expected) + + def test_accumulator(self): + acc = clinic.text_accumulator() + self.assertEqual(acc.output(), "") + acc.append("a") + self.assertEqual(acc.output(), "a") + self.assertEqual(acc.output(), "") + acc.append("b") + self.assertEqual(acc.output(), "b") + self.assertEqual(acc.output(), "") + acc.append("c") + acc.append("d") + self.assertEqual(acc.output(), "cd") + self.assertEqual(acc.output(), "") + + def test_quoted_for_c_string(self): + dataset = ( + # input, expected + (r"abc", r"abc"), + (r"\abc", r"\\abc"), + (r"\a\bc", r"\\a\\bc"), + (r"\a\\bc", r"\\a\\\\bc"), + (r'"abc"', r'\"abc\"'), + (r"'a'", r"\'a\'"), + ) + for line, expected in dataset: + with self.subTest(line=line, expected=expected): + out = clinic.quoted_for_c_string(line) + self.assertEqual(out, expected) + + def test_rstrip_lines(self): + lines = ( + "a \n" + "b\n" + " c\n" + " d \n" + ) + expected = ( + "a\n" + "b\n" + " c\n" + " d\n" + ) + out = clinic.rstrip_lines(lines) + self.assertEqual(out, expected) + + def test_format_escape(self): + line = "{}, {a}" + expected = "{{}}, {{a}}" + out = clinic.format_escape(line) + self.assertEqual(out, expected) + + def test_indent_all_lines(self): + # Blank lines are expected to be unchanged. + self.assertEqual(clinic.indent_all_lines("", prefix="bar"), "") + + lines = ( + "one\n" + "two" # The missing newline is deliberate. + ) + expected = ( + "barone\n" + "bartwo" + ) + out = clinic.indent_all_lines(lines, prefix="bar") + self.assertEqual(out, expected) + + # If last line is empty, expect it to be unchanged. + lines = ( + "\n" + "one\n" + "two\n" + "" + ) + expected = ( + "bar\n" + "barone\n" + "bartwo\n" + "" + ) + out = clinic.indent_all_lines(lines, prefix="bar") + self.assertEqual(out, expected) + + def test_suffix_all_lines(self): + # Blank lines are expected to be unchanged. + self.assertEqual(clinic.suffix_all_lines("", suffix="foo"), "") + + lines = ( + "one\n" + "two" # The missing newline is deliberate. + ) + expected = ( + "onefoo\n" + "twofoo" + ) + out = clinic.suffix_all_lines(lines, suffix="foo") + self.assertEqual(out, expected) + + # If last line is empty, expect it to be unchanged. + lines = ( + "\n" + "one\n" + "two\n" + "" + ) + expected = ( + "foo\n" + "onefoo\n" + "twofoo\n" + "" + ) + out = clinic.suffix_all_lines(lines, suffix="foo") + self.assertEqual(out, expected) + + if __name__ == "__main__": unittest.main() From 7bb9fa5ae486912d5d0a9372f213ba6a72c4cde1 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 5 Jul 2023 01:07:57 +0200 Subject: [PATCH 34/39] gh-104050: Partially annotate Argument Clinic CLanguage class (#106437) Co-authored-by: Alex Waygood --- Tools/clinic/clinic.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 898361474f7214..5f5d024b5aa6f8 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -752,10 +752,14 @@ def __init__(self, filename): self.cpp = cpp.Monitor(filename) self.cpp.fail = fail - def parse_line(self, line): + def parse_line(self, line: str) -> None: self.cpp.writeline(line) - def render(self, clinic, signatures): + def render( + self, + clinic: Clinic | None, + signatures: Iterable[Function] + ) -> str: function = None for o in signatures: if isinstance(o, Function): @@ -764,7 +768,10 @@ def render(self, clinic, signatures): function = o return self.render_function(clinic, function) - def docstring_for_c_string(self, f): + def docstring_for_c_string( + self, + f: Function + ) -> str: if re.search(r'[^\x00-\x7F]', f.docstring): warn("Non-ascii character appear in docstring.") @@ -1345,7 +1352,7 @@ def parser_body(prototype, *fields, declarations=''): return d2 @staticmethod - def group_to_variable_name(group): + def group_to_variable_name(group: int) -> str: adjective = "left_" if group < 0 else "right_" return "group_" + adjective + str(abs(group)) @@ -1441,8 +1448,12 @@ def render_option_group_parsing(self, f, template_dict): add("}") template_dict['option_group_parsing'] = format_escape(output()) - def render_function(self, clinic, f): - if not f: + def render_function( + self, + clinic: Clinic | None, + f: Function | None + ) -> str: + if f is None or clinic is None: return "" add, output = text_accumulator() @@ -1504,10 +1515,12 @@ def render_function(self, clinic, f): template_dict = {} + assert isinstance(f.full_name, str) full_name = f.full_name template_dict['full_name'] = full_name if new_or_init: + assert isinstance(f.cls, Class) name = f.cls.name else: name = f.name From ad075682ba49c3d90cb9b09341f8bf2ea56761d8 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 4 Jul 2023 22:08:25 -0700 Subject: [PATCH 35/39] tp_flags docs: fix indentation (#106420) --- Doc/c-api/typeobj.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst index c6e783acdf0654..239c191457f516 100644 --- a/Doc/c-api/typeobj.rst +++ b/Doc/c-api/typeobj.rst @@ -1143,14 +1143,14 @@ and :c:type:`PyType_Type` effectively act as defaults.) :const:`Py_TPFLAGS_IMMUTABLETYPE` flag set. For extension types, it is inherited whenever :c:member:`~PyTypeObject.tp_descr_get` is inherited. - .. data:: Py_TPFLAGS_MANAGED_DICT + .. data:: Py_TPFLAGS_MANAGED_DICT - This bit indicates that instances of the class have a ``__dict__`` - attribute, and that the space for the dictionary is managed by the VM. + This bit indicates that instances of the class have a ``__dict__`` + attribute, and that the space for the dictionary is managed by the VM. - If this flag is set, :const:`Py_TPFLAGS_HAVE_GC` should also be set. + If this flag is set, :const:`Py_TPFLAGS_HAVE_GC` should also be set. - .. versionadded:: 3.12 + .. versionadded:: 3.12 **Inheritance:** @@ -1158,12 +1158,12 @@ and :c:type:`PyType_Type` effectively act as defaults.) :c:member:`~PyTypeObject.tp_dictoffset` field is set in a superclass. - .. data:: Py_TPFLAGS_MANAGED_WEAKREF + .. data:: Py_TPFLAGS_MANAGED_WEAKREF - This bit indicates that instances of the class should be weakly - referenceable. + This bit indicates that instances of the class should be weakly + referenceable. - .. versionadded:: 3.12 + .. versionadded:: 3.12 **Inheritance:** From a941bd6c53ac4646926292557a7bb2a86f8025c3 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 5 Jul 2023 10:33:05 +0200 Subject: [PATCH 36/39] gh-104683: Rename Lib/test/clinic.test as Lib/test/clinic.test.c (#106443) --- Lib/test/{clinic.test => clinic.test.c} | 0 Lib/test/test_clinic.py | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) rename Lib/test/{clinic.test => clinic.test.c} (100%) diff --git a/Lib/test/clinic.test b/Lib/test/clinic.test.c similarity index 100% rename from Lib/test/clinic.test rename to Lib/test/clinic.test.c diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index c095d14234375f..7c46e8a81803a2 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -1089,15 +1089,16 @@ class ClinicExternalTest(TestCase): maxDiff = None def test_external(self): + CLINIC_TEST = 'clinic.test.c' # bpo-42398: Test that the destination file is left unchanged if the # content does not change. Moreover, check also that the file # modification time does not change in this case. - source = support.findfile('clinic.test') + source = support.findfile(CLINIC_TEST) with open(source, 'r', encoding='utf-8') as f: orig_contents = f.read() with os_helper.temp_dir() as tmp_dir: - testfile = os.path.join(tmp_dir, 'clinic.test.c') + testfile = os.path.join(tmp_dir, CLINIC_TEST) with open(testfile, 'w', encoding='utf-8') as f: f.write(orig_contents) old_mtime_ns = os.stat(testfile).st_mtime_ns From 9d1d4f9c73a71192b22ab52a2eb9278737f98ddb Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 5 Jul 2023 13:23:22 +0200 Subject: [PATCH 37/39] gh-64595: Fix regression in file write logic in Argument Clinic (#106449) Revert the two commits that introduced the regressions: - gh-104152 - gh-104507 --- Lib/test/test_clinic.py | 7 +++---- Tools/clinic/clinic.py | 35 ++++++++++++----------------------- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 7c46e8a81803a2..685ba58642a5ae 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -100,9 +100,8 @@ def test_eol(self): # the last line of the block got corrupted. c = clinic.Clinic(clinic.CLanguage(None), filename="file") raw = "/*[clinic]\nfoo\n[clinic]*/" - cooked, _ = c.parse(raw) - lines = cooked.splitlines() - end_line = lines[2].rstrip() + cooked = c.parse(raw).splitlines() + end_line = cooked[2].rstrip() # this test is redundant, it's just here explicitly to catch # the regression test so we don't forget what it looked like self.assertNotEqual(end_line, "[clinic]*/[clinic]*/") @@ -261,7 +260,7 @@ def _test_clinic(self, input, output): c = clinic.Clinic(language, filename="file") c.parsers['inert'] = InertParser(c) c.parsers['copy'] = CopyParser(c) - computed, _ = c.parse(input) + computed = c.parse(input) self.assertEqual(output, computed) def test_clinic_1(self): diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 5f5d024b5aa6f8..7ada7e9d917b38 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -2021,22 +2021,20 @@ def dump(self): extensions['py'] = PythonLanguage -def file_changed(filename: str, new_contents: str) -> bool: - """Return true if file contents changed (meaning we must update it)""" +def write_file(filename: str, new_contents: str) -> None: try: - with open(filename, encoding="utf-8") as fp: + with open(filename, 'r', encoding="utf-8") as fp: old_contents = fp.read() - return old_contents != new_contents - except FileNotFoundError: - return True - -def write_file(filename: str, new_contents: str) -> None: + if old_contents == new_contents: + # no change: avoid modifying the file modification time + return + except FileNotFoundError: + pass # Atomic write using a temporary file and os.replace() filename_new = f"{filename}.new" with open(filename_new, "w", encoding="utf-8") as fp: fp.write(new_contents) - try: os.replace(filename_new, filename) except: @@ -2214,8 +2212,6 @@ def parse(self, input): traceback.format_exc().rstrip()) printer.print_block(block) - clinic_out = [] - # these are destinations not buffers for name, destination in self.destinations.items(): if destination.type == 'suppress': @@ -2223,7 +2219,6 @@ def parse(self, input): output = destination.dump() if output: - block = Block("", dsl_name="clinic", output=output) if destination.type == 'buffer': @@ -2255,11 +2250,10 @@ def parse(self, input): block.input = 'preserve\n' printer_2 = BlockPrinter(self.language) printer_2.print_block(block, core_includes=True) - pair = destination.filename, printer_2.f.getvalue() - clinic_out.append(pair) + write_file(destination.filename, printer_2.f.getvalue()) continue - return printer.f.getvalue(), clinic_out + return printer.f.getvalue() def _module_and_class(self, fields): @@ -2321,14 +2315,9 @@ def parse_file( assert isinstance(language, CLanguage) clinic = Clinic(language, verify=verify, filename=filename) - src_out, clinic_out = clinic.parse(raw) - - changes = [(fn, data) for fn, data in clinic_out if file_changed(fn, data)] - if changes: - # Always (re)write the source file. - write_file(output, src_out) - for fn, data in clinic_out: - write_file(fn, data) + cooked = clinic.parse(raw) + + write_file(output, cooked) def compute_checksum( From 12a98138083589314d3da14bc97f2d8517947437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= Date: Wed, 5 Jul 2023 15:07:02 +0000 Subject: [PATCH 38/39] Clarify state of CancelledError in doc (#106453) This change makes it explicit that asyncio.CancelledError is not a subclass of Exception. --- Doc/library/asyncio-exceptions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/asyncio-exceptions.rst b/Doc/library/asyncio-exceptions.rst index 9250f01b8a0895..7ad9103ca3fdfc 100644 --- a/Doc/library/asyncio-exceptions.rst +++ b/Doc/library/asyncio-exceptions.rst @@ -31,7 +31,7 @@ Exceptions .. versionchanged:: 3.8 - :exc:`CancelledError` is now a subclass of :class:`BaseException`. + :exc:`CancelledError` is now a subclass of :class:`BaseException` rather than :class:`Exception`. .. exception:: InvalidStateError From 70e2a42647f2f4b53d0f07c0c7db48ea27e066fa Mon Sep 17 00:00:00 2001 From: JosephSBoyle <48555120+JosephSBoyle@users.noreply.github.com> Date: Wed, 5 Jul 2023 17:17:37 +0100 Subject: [PATCH 39/39] gh-102542 Remove unused bytes object and bytes slicing (#106433) Remove unused bytes object and bytes slicing Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com> --- Lib/email/mime/audio.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Lib/email/mime/audio.py b/Lib/email/mime/audio.py index 065819b2a2101d..aa0c4905cbb2b4 100644 --- a/Lib/email/mime/audio.py +++ b/Lib/email/mime/audio.py @@ -6,7 +6,6 @@ __all__ = ['MIMEAudio'] -from io import BytesIO from email import encoders from email.mime.nonmultipart import MIMENonMultipart @@ -59,10 +58,8 @@ def _what(data): # sndhdr.what() had a pretty cruddy interface, unfortunately. This is why # we re-do it here. It would be easier to reverse engineer the Unix 'file' # command and use the standard 'magic' file, as shipped with a modern Unix. - hdr = data[:512] - fakefile = BytesIO(hdr) for testfn in _rules: - if res := testfn(hdr, fakefile): + if res := testfn(data): return res else: return None @@ -74,7 +71,7 @@ def rule(rulefunc): @rule -def _aiff(h, f): +def _aiff(h): if not h.startswith(b'FORM'): return None if h[8:12] in {b'AIFC', b'AIFF'}: @@ -84,7 +81,7 @@ def _aiff(h, f): @rule -def _au(h, f): +def _au(h): if h.startswith(b'.snd'): return 'basic' else: @@ -92,7 +89,7 @@ def _au(h, f): @rule -def _wav(h, f): +def _wav(h): # 'RIFF' 'WAVE' 'fmt ' if not h.startswith(b'RIFF') or h[8:12] != b'WAVE' or h[12:16] != b'fmt ': return None