Skip to content

Commit 59e9e7d

Browse files
authored
Fix infinite loop on empty Buffer (#20024)
Currently `Buffer(b"")` (as opposite to `Buffer()`) can go into an infinite loop when resizing. Fix this by allocating one more byte, that is conveniently guaranteed by `bytes` ABI. I also move the `librt/__init__.py` hack to tests, since this is the only place where it is needed. I am still not sure why `*.so` from `site-packages` is preferred over a "local" namespace package with the same `*.so`.
1 parent 4ff1830 commit 59e9e7d

File tree

4 files changed

+20
-4
lines changed

4 files changed

+20
-4
lines changed

mypyc/build.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,6 @@ def mypycify(
664664
)
665665

666666
if install_librt:
667-
os.makedirs("librt", exist_ok=True)
668667
for name in RUNTIME_C_FILES:
669668
rt_file = os.path.join(build_dir, name)
670669
with open(os.path.join(include_dir(), name), encoding="utf-8") as f:

mypyc/lib-rt/librt_internal.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,14 @@ Buffer_init_internal(BufferObject *self, PyObject *source) {
7070
PyErr_SetString(PyExc_TypeError, "source must be a bytes object");
7171
return -1;
7272
}
73-
self->size = PyBytes_GET_SIZE(source);
74-
self->end = self->size;
73+
self->end = PyBytes_GET_SIZE(source);
74+
// Allocate at least one byte to simplify resizing logic.
75+
// The original bytes buffer has last null byte, so this is safe.
76+
self->size = self->end + 1;
7577
// This returns a pointer to internal bytes data, so make our own copy.
7678
char *buf = PyBytes_AsString(source);
7779
self->buf = PyMem_Malloc(self->size);
78-
memcpy(self->buf, buf, self->size);
80+
memcpy(self->buf, buf, self->end);
7981
} else {
8082
self->buf = PyMem_Malloc(START_SIZE);
8183
self->size = START_SIZE;

mypyc/test-data/run-classes.test

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2866,6 +2866,15 @@ test_buffer_roundtrip_interpreted()
28662866
test_buffer_int_size_interpreted()
28672867
test_buffer_str_size_interpreted()
28682868

2869+
[case testBufferEmpty_librt_internal]
2870+
from librt.internal import Buffer, write_int, read_int
2871+
2872+
def test_empty() -> None:
2873+
b = Buffer(b"")
2874+
write_int(b, 42)
2875+
b1 = Buffer(b.getvalue())
2876+
assert read_int(b1) == 42
2877+
28692878
[case testEnumMethodCalls]
28702879
from enum import Enum
28712880
from typing import overload, Optional, Union

mypyc/test/test_run.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,12 @@ def run_case_step(self, testcase: DataDrivenTestCase, incremental_step: int) ->
285285
)
286286
)
287287

288+
if librt:
289+
# This hack forces Python to prefer the local "installation".
290+
os.makedirs("librt", exist_ok=True)
291+
with open(os.path.join("librt", "__init__.py"), "a"):
292+
pass
293+
288294
if not run_setup(setup_file, ["build_ext", "--inplace"]):
289295
if testcase.config.getoption("--mypyc-showc"):
290296
show_c(cfiles)

0 commit comments

Comments
 (0)