diff --git a/autotest/gcore/basic_test.py b/autotest/gcore/basic_test.py index 115027a4618e..c996a46d5ed9 100755 --- a/autotest/gcore/basic_test.py +++ b/autotest/gcore/basic_test.py @@ -1029,6 +1029,60 @@ def test_ovr_band_use_after_dataset_close(): ovr.Checksum() +def test_sample_ovr_band_use_after_dataset_close(): + with gdal.Open("data/byte_with_ovr.tif") as ds: + ovr = ds.GetRasterBand(1).GetSampleOverview(0) + + # Make sure ds.__exit__() invalidation has propagated to sample overviews + + with pytest.raises( + Exception, + match=r"in method 'Band_Checksum', argument 1 of type 'GDALRasterBandShadow \*'", + ): + ovr.Checksum() + + +@pytest.mark.require_driver("HFA") +def test_rat_use_after_dataset_close(): + with gdal.Open("data/rat.img") as ds: + rat = ds.GetRasterBand(1).GetDefaultRAT() + + # Make sure ds.__exit__() invalidation has propagated to the RAT + + with pytest.raises( + Exception, + match=r"in method 'RasterAttributeTable_GetRowCount', argument 1 of type 'GDALRasterAttributeTableShadow \*'", + ): + rat.GetRowCount() + + +@pytest.mark.require_driver("BMP") +def test_color_table_use_after_dataset_close(): + with gdal.Open("data/8bit_pal.bmp") as ds: + ct = ds.GetRasterBand(1).GetRasterColorTable() + + # Make sure ds.__exit__() invalidation has propagated to the color table + + with pytest.raises( + Exception, + match=r"in method 'ColorTable_GetCount', argument 1 of type 'GDALColorTableShadow \*'", + ): + ct.GetCount() + + +def test_band_dataset_use_after_dataset_close(): + with gdal.Open("data/byte.tif") as ds: + band_ds = ds.GetRasterBand(1).GetDataset() + + # Make sure ds.__exit__() invalidation has propagated to Band.GetDataset() + + with pytest.raises( + Exception, + match=r"in method 'Dataset_RasterXSize_get', argument 1 of type 'GDALDatasetShadow \*'", + ): + assert band_ds.RasterXSize + + @pytest.mark.slow() def test_checksum_more_than_2billion_pixels(): diff --git a/autotest/ogr/ogr_basic_test.py b/autotest/ogr/ogr_basic_test.py index 329de5a0e9f7..5b8cfc23c599 100755 --- a/autotest/ogr/ogr_basic_test.py +++ b/autotest/ogr/ogr_basic_test.py @@ -1384,3 +1384,73 @@ def test_ogr_setpoint_grows_geometry(): assert g.GetPointCount() == 16 g.SetPointZM(20, 1, 1, 1, 1) + + +############################################################################### +# Test that objects borrowed from a Dataset are invalidated when it closes + + +def test_field_domain_use_after_dataset_close(): + with gdal.GetDriverByName("MEM").CreateVector("") as ds: + ds.AddFieldDomain( + ogr.CreateRangeFieldDomain( + "dom", "", ogr.OFTInteger, ogr.OFSTNone, 1, True, 2, True + ) + ) + dom = ds.GetFieldDomain("dom") + + # Make sure ds.__exit__() invalidation has propagated to the field domain + + with pytest.raises( + Exception, + match=r"in method 'FieldDomain_GetName', argument 1 of type 'OGRFieldDomainShadow \*'", + ): + dom.GetName() + + +def test_style_table_use_after_dataset_close(): + with gdal.GetDriverByName("MEM").CreateVector("") as ds: + st = ogr.StyleTable() + st.AddStyle("style", "PEN(c:#FF0000)") + ds.SetStyleTable(st) + style_table = ds.GetStyleTable() + + # Make sure ds.__exit__() invalidation has propagated to the style table + + with pytest.raises( + Exception, + match=r"in method 'StyleTable_Find', argument 1 of type 'OGRStyleTableShadow \*'", + ): + style_table.Find("style") + + +def test_layer_style_table_use_after_dataset_close(): + with gdal.GetDriverByName("MEM").CreateVector("") as ds: + lyr = ds.CreateLayer("lyr") + st = ogr.StyleTable() + st.AddStyle("style", "PEN(c:#FF0000)") + lyr.SetStyleTable(st) + style_table = lyr.GetStyleTable() + + # Make sure ds.__exit__() invalidation has propagated to the layer style table + + with pytest.raises( + Exception, + match=r"in method 'StyleTable_Find', argument 1 of type 'OGRStyleTableShadow \*'", + ): + style_table.Find("style") + + +def test_spatial_filter_use_after_dataset_close(): + with gdal.OpenEx("data/poly.shp") as ds: + lyr = ds.GetLayer(0) + lyr.SetSpatialFilterRect(0, 0, 1, 1) + filter_geom = lyr.GetSpatialFilter() + + # Make sure ds.__exit__() invalidation has propagated to the spatial filter + + with pytest.raises( + Exception, + match=r"in method 'Geometry_ExportToWkt', argument 1 of type 'OGRGeometryShadow \*'", + ): + filter_geom.ExportToWkt() diff --git a/swig/include/python/gdal_python.i b/swig/include/python/gdal_python.i index 9544ef7013ef..13d13f31d01d 100644 --- a/swig/include/python/gdal_python.i +++ b/swig/include/python/gdal_python.i @@ -1346,6 +1346,31 @@ void wrapper_VSIGetMemFileBuffer(const char *utf8_string, GByte **out, vsi_l_off self._parent_ds()._add_child_ref(val) %} +%feature("pythonappend") GetSampleOverview %{ + if hasattr(self, '_parent_ds') and self._parent_ds(): + self._parent_ds()._add_child_ref(val) +%} + +%feature("pythonappend") GetDefaultRAT %{ + if hasattr(self, '_parent_ds') and self._parent_ds(): + self._parent_ds()._add_child_ref(val) +%} + +%feature("pythonappend") GetRasterColorTable %{ + if hasattr(self, '_parent_ds') and self._parent_ds(): + self._parent_ds()._add_child_ref(val) +%} + +%feature("pythonappend") GetColorTable %{ + if hasattr(self, '_parent_ds') and self._parent_ds(): + self._parent_ds()._add_child_ref(val) +%} + +%feature("pythonappend") GetDataset %{ + if hasattr(self, '_parent_ds') and self._parent_ds(): + self._parent_ds()._add_child_ref(val) +%} + %feature("shadow") ComputeStatistics %{ def ComputeStatistics(self, *args, **kwargs): """ComputeStatistics(Band self, bool approx_ok, callback=None, callback_data=None) @@ -2604,6 +2629,14 @@ def ComputeInterBandCovarianceMatrix(self, self._add_child_ref(val) %} +%feature("pythonappend") GetFieldDomain %{ + self._add_child_ref(val) +%} + +%feature("pythonappend") GetStyleTable %{ + self._add_child_ref(val) +%} + } %extend GDALMajorObjectShadow { diff --git a/swig/include/python/ogr_python.i b/swig/include/python/ogr_python.i index 338abaa123b5..15b1debb9b51 100644 --- a/swig/include/python/ogr_python.i +++ b/swig/include/python/ogr_python.i @@ -496,6 +496,16 @@ def _WarnIfUserHasNotSpecifiedIfUsingExceptions(): %} +%feature("pythonappend") GetSpatialFilter %{ + if hasattr(self, '_parent_ds') and self._parent_ds(): + self._parent_ds()._add_child_ref(val) +%} + +%feature("pythonappend") GetStyleTable %{ + if hasattr(self, '_parent_ds') and self._parent_ds(): + self._parent_ds()._add_child_ref(val) +%} + } %extend OGRFeatureShadow {