|
1 | 1 | """Test the pypdf.generic module.""" |
2 | 2 |
|
3 | 3 | import codecs |
| 4 | +import gc |
| 5 | +import weakref |
4 | 6 | from base64 import a85encode |
5 | 7 | from copy import deepcopy |
6 | 8 | from io import BytesIO |
@@ -916,6 +918,63 @@ def test_cloning(caplog): |
916 | 918 | assert isinstance(obj21.get("/Test2"), IndirectObject) |
917 | 919 |
|
918 | 920 |
|
| 921 | +def test_cloning_indirect_obj_keeps_hard_reference(): |
| 922 | + """ |
| 923 | + Reported in #3450 |
| 924 | +
|
| 925 | + Ensure that cloning an IndirectObject keeps a hard reference to |
| 926 | + the underlying object, preventing its deallocation, which could allow |
| 927 | + `id(obj)` to return the same value for different objects. |
| 928 | + """ |
| 929 | + writer1 = PdfWriter() |
| 930 | + indirect_object = IndirectObject(1, 0, writer1) |
| 931 | + |
| 932 | + # Create a weak reference to the underlying object to test later |
| 933 | + # if it is still alive in memory or not |
| 934 | + obj_weakref = weakref.ref(indirect_object.pdf) |
| 935 | + assert obj_weakref() is not None |
| 936 | + |
| 937 | + writer2 = PdfWriter() |
| 938 | + indirect_object.clone(writer2) |
| 939 | + |
| 940 | + # Mimic indirect_object/writer1 going out of scope and being |
| 941 | + # garbage collected. Clone should have kept a hard reference to |
| 942 | + # it, preventing its deallocation. |
| 943 | + del indirect_object |
| 944 | + del writer1 |
| 945 | + gc.collect() |
| 946 | + assert obj_weakref() is not None |
| 947 | + |
| 948 | + |
| 949 | +def test_cloning_null_obj_keeps_hard_reference(): |
| 950 | + """ |
| 951 | + Ensure that cloning a NullObject keeps a hard reference to |
| 952 | + the underlying object, preventing its deallocation, which could allow |
| 953 | + `id(obj)` to return the same value for different objects. |
| 954 | + """ |
| 955 | + writer1 = PdfWriter() |
| 956 | + indirect_object = IndirectObject(1, 0, writer1) |
| 957 | + null_obj = NullObject() |
| 958 | + null_obj.indirect_reference = indirect_object |
| 959 | + |
| 960 | + # Create a weak reference to the underlying object to test later |
| 961 | + # if it is still alive in memory or not |
| 962 | + obj_weakref = weakref.ref(indirect_object.pdf) |
| 963 | + assert obj_weakref() is not None |
| 964 | + |
| 965 | + writer2 = PdfWriter() |
| 966 | + null_obj.clone(writer2) |
| 967 | + |
| 968 | + # Mimic indirect_object/writer1 going out of scope and being |
| 969 | + # garbage collected. Clone should have kept a hard reference to |
| 970 | + # it, preventing its deallocation. |
| 971 | + del indirect_object |
| 972 | + del writer1 |
| 973 | + del null_obj |
| 974 | + gc.collect() |
| 975 | + assert obj_weakref() is not None |
| 976 | + |
| 977 | + |
919 | 978 | @pytest.mark.enable_socket |
920 | 979 | def test_append_with_indirectobject_not_pointing(caplog): |
921 | 980 | """ |
|
0 commit comments