From 59de879c057b520b8b5b0ba414055bff9e6416af Mon Sep 17 00:00:00 2001 From: Vincent Gao Date: Wed, 24 Jun 2026 23:27:46 +0200 Subject: [PATCH] Fix get_matrix() aliasing border rows when border > 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `[[False] * width] * self.border` creates *border* references to the same inner list object. When border > 1 this means all top (or all bottom) border rows in the returned matrix share a single backing list, so mutating any one of them silently corrupts the rest. Replace the list-multiplication with list comprehensions to give every border row its own independent list. Reproducer (raises AssertionError before this fix): qr = QRCode(border=4) qr.add_data("1") m = qr.get_matrix() assert m[0] is not m[1] # was the *same* list – False without fix Adds a regression test that verifies row identity and all-False content for each border row when border=4. --- qrcode/main.py | 4 ++-- qrcode/tests/test_qrcode.py | 29 +++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/qrcode/main.py b/qrcode/main.py index 593c581b..929ef120 100644 --- a/qrcode/main.py +++ b/qrcode/main.py @@ -523,11 +523,11 @@ def get_matrix(self): return self.modules width = len(self.modules) + self.border * 2 - code = [[False] * width] * self.border + code = [[False] * width for _ in range(self.border)] x_border = [False] * self.border for module in self.modules: code.append(x_border + cast("list[bool]", module) + x_border) - code += [[False] * width] * self.border + code += [[False] * width for _ in range(self.border)] return code diff --git a/qrcode/tests/test_qrcode.py b/qrcode/tests/test_qrcode.py index a98f8724..285ee21f 100644 --- a/qrcode/tests/test_qrcode.py +++ b/qrcode/tests/test_qrcode.py @@ -43,8 +43,7 @@ def test_glog_zero_binary_data_at_capacity(): # Version 5 + Q = 60 bytes capacity, data padded with trailing null bytes data = ( b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x16" - b"Hello from kakaworld!!" - + b"\x00" * 26 + b"Hello from kakaworld!!" + b"\x00" * 26 ) assert len(data) == 60 qr = qrcode.QRCode(version=5, error_correction=qrcode.constants.ERROR_CORRECT_Q) @@ -303,6 +302,32 @@ def test_get_matrix_border(): assert matrix == qr.modules +def test_get_matrix_border_rows_not_aliased(): + # With border > 1, each border row must be a distinct list object so that + # mutating one row does not corrupt the others. + qr = qrcode.QRCode(border=4) + qr.add_data("1") + matrix = qr.get_matrix() + border = qr.border + top_rows = matrix[:border] + bottom_rows = matrix[-border:] + # Every pair of top border rows must be distinct objects. + for i in range(border): + for j in range(i + 1, border): + assert top_rows[i] is not top_rows[j], ( + f"Top border rows {i} and {j} are the same object (aliased)" + ) + # Every pair of bottom border rows must be distinct objects. + for i in range(border): + for j in range(i + 1, border): + assert bottom_rows[i] is not bottom_rows[j], ( + f"Bottom border rows {i} and {j} are the same object (aliased)" + ) + # All border rows must contain only False values. + for row in top_rows + bottom_rows: + assert row == [False] * len(row) + + def test_negative_size_at_construction(): with pytest.raises(ValueError): qrcode.QRCode(box_size=-1)