From 1e5962b7860e2e5fc91e89721a176e4f6e61d135 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 8 Oct 2025 02:24:18 +0100 Subject: [PATCH] Fix infinite loop on empty Buffer --- mypyc/build.py | 1 - mypyc/lib-rt/librt_internal.c | 8 +++++--- mypyc/test-data/run-classes.test | 9 +++++++++ mypyc/test/test_run.py | 6 ++++++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/mypyc/build.py b/mypyc/build.py index 40638b31d000..13648911c0b5 100644 --- a/mypyc/build.py +++ b/mypyc/build.py @@ -664,7 +664,6 @@ def mypycify( ) if install_librt: - os.makedirs("librt", exist_ok=True) for name in RUNTIME_C_FILES: rt_file = os.path.join(build_dir, name) with open(os.path.join(include_dir(), name), encoding="utf-8") as f: diff --git a/mypyc/lib-rt/librt_internal.c b/mypyc/lib-rt/librt_internal.c index cb9aa1025821..b97d6665b515 100644 --- a/mypyc/lib-rt/librt_internal.c +++ b/mypyc/lib-rt/librt_internal.c @@ -70,12 +70,14 @@ Buffer_init_internal(BufferObject *self, PyObject *source) { PyErr_SetString(PyExc_TypeError, "source must be a bytes object"); return -1; } - self->size = PyBytes_GET_SIZE(source); - self->end = self->size; + self->end = PyBytes_GET_SIZE(source); + // Allocate at least one byte to simplify resizing logic. + // The original bytes buffer has last null byte, so this is safe. + self->size = self->end + 1; // This returns a pointer to internal bytes data, so make our own copy. char *buf = PyBytes_AsString(source); self->buf = PyMem_Malloc(self->size); - memcpy(self->buf, buf, self->size); + memcpy(self->buf, buf, self->end); } else { self->buf = PyMem_Malloc(START_SIZE); self->size = START_SIZE; diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index 8755169fdb0b..84704ce66c81 100644 --- a/mypyc/test-data/run-classes.test +++ b/mypyc/test-data/run-classes.test @@ -2866,6 +2866,15 @@ test_buffer_roundtrip_interpreted() test_buffer_int_size_interpreted() test_buffer_str_size_interpreted() +[case testBufferEmpty_librt_internal] +from librt.internal import Buffer, write_int, read_int + +def test_empty() -> None: + b = Buffer(b"") + write_int(b, 42) + b1 = Buffer(b.getvalue()) + assert read_int(b1) == 42 + [case testEnumMethodCalls] from enum import Enum from typing import overload, Optional, Union diff --git a/mypyc/test/test_run.py b/mypyc/test/test_run.py index 22ab18e97293..953f61329395 100644 --- a/mypyc/test/test_run.py +++ b/mypyc/test/test_run.py @@ -285,6 +285,12 @@ def run_case_step(self, testcase: DataDrivenTestCase, incremental_step: int) -> ) ) + if librt: + # This hack forces Python to prefer the local "installation". + os.makedirs("librt", exist_ok=True) + with open(os.path.join("librt", "__init__.py"), "a"): + pass + if not run_setup(setup_file, ["build_ext", "--inplace"]): if testcase.config.getoption("--mypyc-showc"): show_c(cfiles)