Skip to content

Commit af94c02

Browse files
alexmalyshevmeta-codesync[bot]
authored andcommitted
Fix reference count errors with PyCode_* functions
Summary: These functions return strong references on Python 3.11 and later. The compatibility shims we had in cinderx/Common/code.h were wrong, they returned borrowed references, and we wrote code expecting their results to be borrowed. Switch the compatibility shims to use strong references and fix callsites to properly decref them as needed. Callsites in shadowcode and the 3.10 interpreter can just be inlined and use borrowed references. That code plus the shims will go away once 3.10 support goes away (hopefully soon). The issue was pointed out to me by Claude. The solution it generated was a mess, so I handwrote this instead. Reviewed By: kddnewton, jbower-fb Differential Revision: D94730099 fbshipit-source-id: 79e6ec81f63314500cdd8bded6792fc95e760250
1 parent cd41e42 commit af94c02

9 files changed

Lines changed: 48 additions & 45 deletions

File tree

cinderx/Common/code.cpp

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,10 @@ PyObject* getVarnameTuple(BorrowedRef<PyCodeObject> code, int* idx) {
5252
}
5353

5454
*idx -= code->co_nlocals;
55-
auto ncellvars = PyTuple_GET_SIZE(PyCode_GetCellvars(code));
55+
auto cellvars = Ref<>::steal(PyCode_GetCellvars(code));
56+
auto ncellvars = PyTuple_GET_SIZE(cellvars.get());
5657
if (*idx < ncellvars) {
57-
return PyCode_GetCellvars(code);
58+
return cellvars.release();
5859
}
5960

6061
*idx -= ncellvars;
@@ -65,14 +66,14 @@ PyObject* getVarname(BorrowedRef<PyCodeObject> code, int idx) {
6566
#if PY_VERSION_HEX >= 0x030C0000
6667
return PyTuple_GET_ITEM(code->co_localsplusnames, idx);
6768
#else
68-
PyObject* tuple = getVarnameTuple(code, &idx);
69-
return PyTuple_GET_ITEM(tuple, idx);
69+
auto tuple = Ref<>::steal(getVarnameTuple(code, &idx));
70+
return PyTuple_GET_ITEM(tuple.get(), idx);
7071
#endif
7172
}
7273

7374
uint32_t hashBytecode(BorrowedRef<PyCodeObject> code) {
7475
uint32_t crc = crc32(0, nullptr, 0);
75-
PyObject* bc = PyCode_GetCode(code);
76+
auto bc = Ref<>::steal(PyCode_GetCode(code));
7677
if (!PyBytes_Check(bc)) {
7778
return crc;
7879
}
@@ -111,11 +112,11 @@ _Py_CODEUNIT* codeUnit(PyCodeObject* code) {
111112
#if PY_VERSION_HEX >= 0x030C0000
112113
return _PyCode_CODE(code);
113114
#else
114-
PyObject* bytes_obj = PyCode_GetCode(code);
115+
auto bytes_obj = Ref<>::steal(PyCode_GetCode(code));
115116
JIT_DCHECK(
116117
PyBytes_CheckExact(bytes_obj),
117118
"Code object must have its instructions stored as a byte string");
118-
return (_Py_CODEUNIT*)PyBytes_AS_STRING(PyCode_GetCode(code));
119+
return (_Py_CODEUNIT*)PyBytes_AS_STRING(bytes_obj.get());
119120
#endif
120121
}
121122

@@ -125,7 +126,8 @@ size_t countIndices(PyCodeObject* code) {
125126
// which we don't need just to determine the number of indices.
126127
return _PyCode_NBYTES(code) / sizeof(_Py_CODEUNIT);
127128
#else
128-
return PyBytes_GET_SIZE(PyCode_GetCode(code)) / sizeof(_Py_CODEUNIT);
129+
auto bytes_obj = Ref<>::steal(PyCode_GetCode(code));
130+
return PyBytes_GET_SIZE(bytes_obj.get()) / sizeof(_Py_CODEUNIT);
129131
#endif
130132
}
131133

@@ -268,15 +270,17 @@ int numCellvars(PyCodeObject* code) {
268270
#if PY_VERSION_HEX >= 0x030B0000
269271
return code->co_ncellvars;
270272
#else
271-
return PyTuple_GET_SIZE(PyCode_GetCellvars(code));
273+
auto cellvars = Ref<>::steal(PyCode_GetCellvars(code));
274+
return PyTuple_GET_SIZE(cellvars.get());
272275
#endif
273276
}
274277

275278
int numFreevars(PyCodeObject* code) {
276279
#if PY_VERSION_HEX >= 0x030B0000
277280
return code->co_nfreevars;
278281
#else
279-
return PyTuple_GET_SIZE(PyCode_GetFreevars(code));
282+
auto freevars = Ref<>::steal(PyCode_GetFreevars(code));
283+
return PyTuple_GET_SIZE(freevars.get());
280284
#endif
281285
}
282286

cinderx/Common/code.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,19 @@ extern "C" {
2121
#if PY_VERSION_HEX < 0x030B0000
2222

2323
static inline PyObject* PyCode_GetCode(PyCodeObject* code) {
24-
return code->co_code;
24+
return Py_NewRef(code->co_code);
2525
}
2626

2727
static inline PyObject* PyCode_GetVarnames(PyCodeObject* code) {
28-
return code->co_varnames;
28+
return Py_NewRef(code->co_varnames);
2929
}
3030

3131
static inline PyObject* PyCode_GetCellvars(PyCodeObject* code) {
32-
return code->co_cellvars;
32+
return Py_NewRef(code->co_cellvars);
3333
}
3434

3535
static inline PyObject* PyCode_GetFreevars(PyCodeObject* code) {
36-
return code->co_freevars;
36+
return Py_NewRef(code->co_freevars);
3737
}
3838

3939
static inline PyCodeObject* PyUnstable_Code_New(
@@ -163,8 +163,8 @@ std::string codeFullname(
163163
std::string funcFullname(BorrowedRef<PyFunctionObject> func);
164164

165165
// Given a code object and an index into f_localsplus, compute which of
166-
// code->co_varnames, code->cellvars, or code->freevars contains the name of
167-
// the variable. Return that tuple and adjust idx as needed.
166+
// code->co_varnames, code->cellvars, or code->freevars contains the name of the
167+
// variable. Return a new reference to that tuple and adjust idx as needed.
168168
PyObject* getVarnameTuple(BorrowedRef<PyCodeObject> code, int* idx);
169169

170170
// Similar to getVarnameTuple, but return the name itself rather than the

cinderx/Immortalize/immortalize.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ bool immortalize(PyObject* obj) {
4444
#if PY_VERSION_HEX < 0x030B0000
4545
// In 3.11 these changed to have the bytes embedded in the code object and
4646
// the names in a unified tuple
47-
IMMORTALIZE(PyCode_GetCode(code));
48-
IMMORTALIZE(PyCode_GetVarnames(code));
49-
IMMORTALIZE(PyCode_GetFreevars(code));
50-
IMMORTALIZE(PyCode_GetCellvars(code));
47+
IMMORTALIZE(code->co_code);
48+
IMMORTALIZE(code->co_varnames);
49+
IMMORTALIZE(code->co_freevars);
50+
IMMORTALIZE(code->co_cellvars);
5151
#else
5252
IMMORTALIZE(code->co_localspluskinds);
5353
IMMORTALIZE(code->co_localsplusnames);

cinderx/Interpreter/3.10/interpreter.c

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -310,11 +310,11 @@ Ci_EvalFrame(PyThreadState* tstate, PyFrameObject* f, int throwflag) {
310310
consts = co->co_consts;
311311
fastlocals = f->f_localsplus;
312312
freevars = f->f_localsplus + co->co_nlocals;
313-
assert(PyBytes_Check(PyCode_GetCode(co)));
314-
assert(PyBytes_GET_SIZE(PyCode_GetCode(co)) <= INT_MAX);
315-
assert(PyBytes_GET_SIZE(PyCode_GetCode(co)) % sizeof(_Py_CODEUNIT) == 0);
313+
assert(PyBytes_Check(co->co_code));
314+
assert(PyBytes_GET_SIZE(co->co_code) <= INT_MAX);
315+
assert(PyBytes_GET_SIZE(co->co_code) % sizeof(_Py_CODEUNIT) == 0);
316316
assert(_Py_IS_ALIGNED(
317-
PyBytes_AS_STRING(PyCode_GetCode(co)), sizeof(_Py_CODEUNIT)));
317+
PyBytes_AS_STRING(co->co_code), sizeof(_Py_CODEUNIT)));
318318

319319
/* facebook begin t39538061 */
320320
shadow.code = co;
@@ -504,7 +504,7 @@ Ci_EvalFrame(PyThreadState* tstate, PyFrameObject* f, int throwflag) {
504504
tstate,
505505
PyExc_UnboundLocalError,
506506
UNBOUNDLOCAL_ERROR_MSG,
507-
PyTuple_GetItem(PyCode_GetVarnames(co), oparg));
507+
PyTuple_GetItem(co->co_varnames, oparg));
508508
goto error;
509509
}
510510
Py_INCREF(value);
@@ -1636,10 +1636,10 @@ Ci_EvalFrame(PyThreadState* tstate, PyFrameObject* f, int throwflag) {
16361636
PyObject *name, *value, *locals = f->f_locals;
16371637
Py_ssize_t idx;
16381638
assert(locals);
1639-
assert(oparg >= PyTuple_GET_SIZE(PyCode_GetCellvars(co)));
1640-
idx = oparg - PyTuple_GET_SIZE(PyCode_GetCellvars(co));
1641-
assert(idx >= 0 && idx < PyTuple_GET_SIZE(PyCode_GetFreevars(co)));
1642-
name = PyTuple_GET_ITEM(PyCode_GetFreevars(co), idx);
1639+
assert(oparg >= PyTuple_GET_SIZE(co->co_cellvars));
1640+
idx = oparg - PyTuple_GET_SIZE(co->co_cellvars);
1641+
assert(idx >= 0 && idx < PyTuple_GET_SIZE(co->co_freevars));
1642+
name = PyTuple_GET_ITEM(co->co_freevars, idx);
16431643
if (PyDict_CheckExact(locals)) {
16441644
value = PyDict_GetItemWithError(locals, name);
16451645
if (value != NULL) {
@@ -5020,8 +5020,8 @@ _Ci_CheckArgs(PyThreadState* tstate, PyFrameObject* f, PyCodeObject* co) {
50205020
"%U expected '%s' for argument %U, got '%s'",
50215021
co->co_name,
50225022
type->tp_name,
5023-
idx < 0 ? PyTuple_GetItem(PyCode_GetCellvars(co), -(idx + 1))
5024-
: PyTuple_GetItem(PyCode_GetVarnames(co), idx),
5023+
idx < 0 ? PyTuple_GetItem(co->co_cellvars, -(idx + 1))
5024+
: PyTuple_GetItem(co->co_varnames, idx),
50255025
Py_TYPE(val)->tp_name);
50265026
Py_DECREF(type);
50275027
return -1;
@@ -5070,8 +5070,8 @@ _Ci_CheckArgs(PyThreadState* tstate, PyFrameObject* f, PyCodeObject* co) {
50705070
"%U expected '%s' for argument %U, got '%s'",
50715071
co->co_name,
50725072
check->tai_type->tp_name,
5073-
idx < 0 ? PyTuple_GetItem(PyCode_GetCellvars(co), -(idx + 1))
5074-
: PyTuple_GetItem(PyCode_GetVarnames(co), idx),
5073+
idx < 0 ? PyTuple_GetItem(co->co_cellvars, -(idx + 1))
5074+
: PyTuple_GetItem(co->co_varnames, idx),
50755075
Py_TYPE(val)->tp_name);
50765076
return -1;
50775077
}

cinderx/Jit/hir/hir.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1281,7 +1281,7 @@ const Environment::ReferenceSet& Environment::references() const {
12811281

12821282
bool usesRuntimeFunc([[maybe_unused]] BorrowedRef<PyCodeObject> code) {
12831283
#if PY_VERSION_HEX < 0x030C0000
1284-
return PyTuple_GET_SIZE(PyCode_GetFreevars(code)) > 0;
1284+
return numFreevars(code) > 0;
12851285
#else
12861286
// In 3.12+ we always need the runtime function because we use it to
12871287
// initialize the _PyInterpreterFrame object.

cinderx/Jit/hir/inliner.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,10 @@ bool canInline(Function& caller, AbstractCall* call_instr) {
154154
}
155155
}
156156
#else
157-
Py_ssize_t ncellvars = PyTuple_GET_SIZE(PyCode_GetCellvars(code));
158-
if (ncellvars > 0) {
157+
if (numCellvars(code) > 0) {
159158
return fail(InlineFailureType::kHasCellvars);
160159
}
161-
Py_ssize_t nfreevars = PyTuple_GET_SIZE(PyCode_GetFreevars(code));
162-
if (nfreevars > 0) {
160+
if (numFreevars(code) > 0) {
163161
return fail(InlineFailureType::kHasFreevars);
164162
}
165163
#endif

cinderx/Jit/hir/printer.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ format_varname(const Function* func, const Instr& instr, int idx) {
231231
return fmt::format("{}", idx);
232232
}
233233

234-
auto names = getVarnameTuple(code, &idx);
234+
auto names = Ref<>::steal(getVarnameTuple(code, &idx));
235235
return format_name_impl(idx, names);
236236
}
237237

cinderx/RuntimeTests/deopt_test.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,8 +347,9 @@ def test(x, y):
347347
const int jump_index = 42;
348348
const int pop_instr_offset = 2;
349349
#endif
350+
auto code_bytes = Ref<>::steal(PyCode_GetCode(code));
350351
ASSERT_EQ(
351-
PyBytes_AS_STRING(PyCode_GetCode(code))[jump_index + pop_instr_offset],
352+
PyBytes_AS_STRING(code_bytes.get())[jump_index + pop_instr_offset],
352353
(char)POP_JUMP_IF_ZERO);
353354

354355
DeoptMetadata dm;

cinderx/Shadowcode/shadowcode.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ int _PyShadow_GetOriginalOparg(
619619
_PyShadow_EvalState* state,
620620
const _Py_CODEUNIT* next_instr) {
621621
_Py_CODEUNIT* rawcode =
622-
(_Py_CODEUNIT*)PyBytes_AS_STRING(PyCode_GetCode(state->code));
622+
(_Py_CODEUNIT*)PyBytes_AS_STRING(state->code->co_code);
623623
_Py_CODEUNIT* instr = &rawcode[next_instr - *state->first_instr];
624624
instr--; /* we point to the next instruction, we want the current one */
625625
const int existing_size = opsize(instr, rawcode);
@@ -2329,7 +2329,7 @@ PyObject* _PyShadow_GetInlineCacheStats(PyObject* self) {
23292329
#endif
23302330

23312331
int _PyShadow_InitCache(PyCodeObject* co) {
2332-
char* buffer = PyBytes_AS_STRING(PyCode_GetCode(co));
2332+
char* buffer = PyBytes_AS_STRING(co->co_code);
23332333

23342334
/* names is a bitmask of seen names, if we have a limited number */
23352335
size_t names = 0, funcs = 0;
@@ -2356,7 +2356,7 @@ int _PyShadow_InitCache(PyCodeObject* co) {
23562356
/* Scan the byte code for all LOAD_GLOBALs and pre-allocate enough space
23572357
* for all of them */
23582358
_Py_CODEUNIT* instr = (_Py_CODEUNIT*)buffer;
2359-
_Py_CODEUNIT* end = (_Py_CODEUNIT*)(buffer + Py_SIZE(PyCode_GetCode(co)));
2359+
_Py_CODEUNIT* end = (_Py_CODEUNIT*)(buffer + Py_SIZE(co->co_code));
23602360
while (instr < end) {
23612361
unsigned char opcode = _Py_OPCODE(*instr);
23622362
int oparg = _Py_OPARG(*instr);
@@ -2407,13 +2407,13 @@ int _PyShadow_InitCache(PyCodeObject* co) {
24072407
}
24082408

24092409
_PyShadowCode* shadow;
2410-
shadow = PyMem_Malloc(sizeof(_PyShadowCode) + Py_SIZE(PyCode_GetCode(co)));
2410+
shadow = PyMem_Malloc(sizeof(_PyShadowCode) + Py_SIZE(co->co_code));
24112411
if (shadow == NULL) {
24122412
return -1;
24132413
}
24142414

24152415
shadow->update_count = 0;
2416-
shadow->len = Py_SIZE(PyCode_GetCode(co));
2416+
shadow->len = Py_SIZE(co->co_code);
24172417
memcpy(shadow->code, buffer, shadow->len);
24182418

24192419
if (glob_count) {

0 commit comments

Comments
 (0)