@@ -54,6 +54,8 @@ function _cdata_release_array(ptr::Ptr{Arrow.ArrowArray})
5454end
5555
5656using Dates
57+ using Libdl
58+ using Random
5759
5860const _CDATA_RELEASE_SCHEMA =
5961 @cfunction (_cdata_release_schema, Cvoid, (Ptr{Arrow. ArrowSchema},))
@@ -242,6 +244,10 @@ function _set_schema!(
242244 return f
243245end
244246
247+ function _replace_schema! (f:: CDataFixture ; kwargs... )
248+ return _set_schema! (f; kwargs... )
249+ end
250+
245251function _set_array! (
246252 f:: CDataFixture ;
247253 length= f. array[]. length,
@@ -270,6 +276,10 @@ function _set_array!(
270276 return f
271277end
272278
279+ function _replace_array! (f:: CDataFixture ; len= f. array[]. length, kwargs... )
280+ return _set_array! (f; length= len, kwargs... )
281+ end
282+
273283@testset " Arrow C Data Interface import" begin
274284 @testset " ABI layout" begin
275285 ptr = sizeof (Ptr{Cvoid})
@@ -1148,6 +1158,214 @@ end
11481158 end
11491159 bad (child)
11501160 end
1161+
1162+ @testset " deterministic malformed import fuzz" begin
1163+ iters = something (tryparse (Int, get (ENV , " ARROW_CDATA_FUZZ_ITERS" , " 64" )), 64 )
1164+ iters = clamp (iters, 0 , 10_000 )
1165+ rng = Random. MersenneTwister (0x0cda7a )
1166+ for _ = 1 : iters
1167+ f = _primitive_fixture (" i" , Int32[10 , 20 , 30 ])
1168+ case = rand (rng, 1 : 11 )
1169+ if case == 1
1170+ _replace_array! (f; len= Int64 (- 1 ))
1171+ elseif case == 2
1172+ _replace_array! (f; offset= Int64 (- 1 ))
1173+ elseif case == 3
1174+ _replace_array! (f; null_count= Int64 (4 ))
1175+ elseif case == 4
1176+ _replace_array! (f; n_buffers= Int64 (1 ))
1177+ elseif case == 5
1178+ _replace_array! (f; buffers= Ptr {Ptr{Cvoid}} (C_NULL ))
1179+ elseif case == 6
1180+ _replace_schema! (f; format= Cstring (C_NULL ))
1181+ elseif case == 7
1182+ _replace_schema! (f; release= Ptr {Cvoid} (C_NULL ))
1183+ elseif case == 8
1184+ _replace_array! (f; release= Ptr {Cvoid} (C_NULL ))
1185+ elseif case == 9
1186+ fmt = Vector {UInt8} (" +l\0 " )
1187+ push! (f. roots, fmt)
1188+ _replace_schema! (f; format= Cstring (pointer (fmt)))
1189+ elseif case == 10
1190+ offsets = Int32[0 , 2 , 1 , 3 ]
1191+ bytes = UInt8[0x01 , 0x02 , 0x03 ]
1192+ buffers = Ptr{Cvoid}[
1193+ C_NULL ,
1194+ Ptr {Cvoid} (pointer (offsets)),
1195+ Ptr {Cvoid} (pointer (bytes)),
1196+ ]
1197+ push! (f. roots, offsets)
1198+ push! (f. roots, bytes)
1199+ push! (f. roots, buffers)
1200+ fmt = Vector {UInt8} (" z\0 " )
1201+ push! (f. roots, fmt)
1202+ _replace_schema! (f; format= Cstring (pointer (fmt)))
1203+ _replace_array! (
1204+ f;
1205+ buffers= Ptr {Ptr{Cvoid}} (pointer (buffers)),
1206+ n_buffers= Int64 (3 ),
1207+ )
1208+ else
1209+ fmt = Vector {UInt8} (" w:$(Arrow. _CDATA_MAX_FIXED_SIZE + rand (rng, 1 : 8 )) \0 " )
1210+ push! (f. roots, fmt)
1211+ _replace_schema! (f; format= Cstring (pointer (fmt)))
1212+ end
1213+ @test_throws ArgumentError Arrow. from_c_data (_schema_ptr (f), _array_ptr (f))
1214+ end
1215+ end
1216+
1217+ @testset " malformed nested inputs" begin
1218+ child = _primitive_fixture (" i" , Int32[1 , 2 , 3 ])
1219+ _replace_schema! (child; release= Ptr {Cvoid} (C_NULL ))
1220+ offsets = Int32[0 , 1 ]
1221+ f = _cdata_fixture (
1222+ " +l" ,
1223+ 1 ,
1224+ Ptr{Cvoid}[C_NULL , Ptr {Cvoid} (pointer (offsets))];
1225+ children= [child],
1226+ )
1227+ push! (f. roots, offsets)
1228+ @test_throws ArgumentError Arrow. from_c_data (_schema_ptr (f), _array_ptr (f))
1229+
1230+ child = _primitive_fixture (" i" , Int32[1 ])
1231+ root = _cdata_fixture (" +s" , 2 , Ptr{Cvoid}[C_NULL ]; children= [child])
1232+ @test_throws ArgumentError Arrow. from_c_data (_schema_ptr (root), _array_ptr (root))
1233+
1234+ child = _primitive_fixture (" i" , Int32[])
1235+ f = _cdata_fixture (
1236+ " +w:2" ,
1237+ 1 ,
1238+ Ptr{Cvoid}[C_NULL ];
1239+ offset= Int64 (typemax (Int)),
1240+ children= [child],
1241+ )
1242+ @test_throws ArgumentError Arrow. from_c_data (_schema_ptr (f), _array_ptr (f))
1243+ end
1244+
1245+ @testset " C producer smoke" begin
1246+ cc = Sys. which (" cc" )
1247+ if cc === nothing || Sys. iswindows ()
1248+ @test_skip " C producer smoke requires a C compiler"
1249+ else
1250+ c_src = """
1251+ #include <stdint.h>
1252+ struct ArrowSchema {
1253+ const char* format;
1254+ const char* name;
1255+ const char* metadata;
1256+ int64_t flags;
1257+ int64_t n_children;
1258+ struct ArrowSchema** children;
1259+ struct ArrowSchema* dictionary;
1260+ void (*release)(struct ArrowSchema*);
1261+ void* private_data;
1262+ };
1263+ struct ArrowArray {
1264+ int64_t length;
1265+ int64_t null_count;
1266+ int64_t offset;
1267+ int64_t n_buffers;
1268+ int64_t n_children;
1269+ const void** buffers;
1270+ struct ArrowArray** children;
1271+ struct ArrowArray* dictionary;
1272+ void (*release)(struct ArrowArray*);
1273+ void* private_data;
1274+ };
1275+ static const char format[] = "i";
1276+ static const uint8_t validity[] = {0x05};
1277+ static const int32_t data[] = {10, 20, 30};
1278+ static const void* buffers[] = {validity, data};
1279+ static void release_schema(struct ArrowSchema* schema) {
1280+ schema->release = 0;
1281+ }
1282+ static void release_array(struct ArrowArray* array) {
1283+ array->release = 0;
1284+ }
1285+ int make_nullable_int32(struct ArrowSchema* schema, struct ArrowArray* array) {
1286+ if (!schema || !array) return -1;
1287+ schema->format = format;
1288+ schema->name = "";
1289+ schema->metadata = 0;
1290+ schema->flags = 2;
1291+ schema->n_children = 0;
1292+ schema->children = 0;
1293+ schema->dictionary = 0;
1294+ schema->release = release_schema;
1295+ schema->private_data = 0;
1296+ array->length = 3;
1297+ array->null_count = 1;
1298+ array->offset = 0;
1299+ array->n_buffers = 2;
1300+ array->n_children = 0;
1301+ array->buffers = buffers;
1302+ array->children = 0;
1303+ array->dictionary = 0;
1304+ array->release = release_array;
1305+ array->private_data = 0;
1306+ return 0;
1307+ }
1308+ """
1309+ mktempdir () do dir
1310+ src = joinpath (dir, " producer.c" )
1311+ lib = joinpath (dir, " producer.$(Libdl. dlext) " )
1312+ write (src, c_src)
1313+ if Sys. isapple ()
1314+ run (` $cc -dynamiclib -o $lib $src ` )
1315+ else
1316+ run (` $cc -shared -fPIC -o $lib $src ` )
1317+ end
1318+ handle = Libdl. dlopen (lib)
1319+ try
1320+ make = Libdl. dlsym (handle, :make_nullable_int32 )
1321+ schema = Ref (
1322+ Arrow. ArrowSchema (
1323+ Cstring (C_NULL ),
1324+ Cstring (C_NULL ),
1325+ Cstring (C_NULL ),
1326+ 0 ,
1327+ 0 ,
1328+ Ptr {Ptr{Arrow.ArrowSchema}} (C_NULL ),
1329+ Ptr {Arrow.ArrowSchema} (C_NULL ),
1330+ Ptr {Cvoid} (C_NULL ),
1331+ Ptr {Cvoid} (C_NULL ),
1332+ ),
1333+ )
1334+ array = Ref (
1335+ Arrow. ArrowArray (
1336+ 0 ,
1337+ 0 ,
1338+ 0 ,
1339+ 0 ,
1340+ 0 ,
1341+ Ptr {Ptr{Cvoid}} (C_NULL ),
1342+ Ptr {Ptr{Arrow.ArrowArray}} (C_NULL ),
1343+ Ptr {Arrow.ArrowArray} (C_NULL ),
1344+ Ptr {Cvoid} (C_NULL ),
1345+ Ptr {Cvoid} (C_NULL ),
1346+ ),
1347+ )
1348+ @test ccall (
1349+ make,
1350+ Cint,
1351+ (Ptr{Arrow. ArrowSchema}, Ptr{Arrow. ArrowArray}),
1352+ Base. unsafe_convert (Ptr{Arrow. ArrowSchema}, schema),
1353+ Base. unsafe_convert (Ptr{Arrow. ArrowArray}, array),
1354+ ) == 0
1355+ x = Arrow. from_c_data (
1356+ Base. unsafe_convert (Ptr{Arrow. ArrowSchema}, schema),
1357+ Base. unsafe_convert (Ptr{Arrow. ArrowArray}, array),
1358+ )
1359+ @test isequal (collect (x), Union{Int32,Missing}[10 , missing , 30 ])
1360+ Arrow. release_c_data (x)
1361+ @test schema[]. release == C_NULL
1362+ @test array[]. release == C_NULL
1363+ finally
1364+ Libdl. dlclose (handle)
1365+ end
1366+ end
1367+ end
1368+ end
11511369end
11521370
11531371_schema_ref_ptr (ref:: Ref{Arrow.ArrowSchema} ) =
@@ -1591,4 +1809,95 @@ end
15911809 @test length (Arrow. _CDATA_EXPORT_SCHEMA_OWNERS) == schema_count
15921810 @test length (Arrow. _CDATA_EXPORT_ARRAY_OWNERS) == array_count
15931811 end
1812+
1813+ @testset " malformed nested export layouts" begin
1814+ schema_count = length (Arrow. _CDATA_EXPORT_SCHEMA_OWNERS)
1815+ array_count = length (Arrow. _CDATA_EXPORT_ARRAY_OWNERS)
1816+
1817+ child = Arrow. toarrowvector (Int32[1 ])
1818+ bad_struct =
1819+ Arrow. Struct {NamedTuple{(:a,),Tuple{Int32}},Tuple{typeof(child)},(:a,)} (
1820+ Arrow. ValidityBitmap (UInt8[], 1 , 0 , 0 ),
1821+ (child,),
1822+ 2 ,
1823+ nothing ,
1824+ )
1825+ @test_throws ArgumentError Arrow. to_c_data (bad_struct)
1826+
1827+ fixed_child = Arrow. toarrowvector (Int32[1 , 2 , 3 , 4 , 5 ])
1828+ bad_fixed = Arrow. FixedSizeList {NTuple{3,Int32},typeof(fixed_child)} (
1829+ UInt8[],
1830+ Arrow. ValidityBitmap (UInt8[], 1 , 0 , 0 ),
1831+ fixed_child,
1832+ 2 ,
1833+ nothing ,
1834+ )
1835+ @test_throws ArgumentError Arrow. to_c_data (bad_fixed)
1836+
1837+ list_child = Arrow. toarrowvector (Int32[1 ])
1838+ bad_list = Arrow. List {Vector{Int32},Int32,typeof(list_child)} (
1839+ UInt8[],
1840+ Arrow. ValidityBitmap (UInt8[], 1 , 0 , 0 ),
1841+ Arrow. Offsets (UInt8[], Int32[0 , 2 ]),
1842+ list_child,
1843+ 1 ,
1844+ nothing ,
1845+ )
1846+ @test_throws ArgumentError Arrow. to_c_data (bad_list)
1847+
1848+ @test length (Arrow. _CDATA_EXPORT_SCHEMA_OWNERS) == schema_count
1849+ @test length (Arrow. _CDATA_EXPORT_ARRAY_OWNERS) == array_count
1850+ end
1851+
1852+ @testset " repeated GC stress" begin
1853+ for i = 1 : 24
1854+ tbl = (
1855+ id= Int32[i, i + 1 , i + 2 ],
1856+ label= [" a$(i) " , " b$(i) " , " c$(i) " ],
1857+ flags= Union{Bool,Missing}[true , missing , isodd (i)],
1858+ )
1859+ schema, array = Arrow. to_c_data (tbl)
1860+ imported = _import_exported (schema, array)
1861+ GC. gc (true )
1862+ GC. gc (true )
1863+ @test collect (imported. id) == tbl. id
1864+ @test collect (imported. label) == tbl. label
1865+ @test isequal (collect (imported. flags), tbl. flags)
1866+ Arrow. release_c_data (imported)
1867+ @test schema[]. release == C_NULL
1868+ @test array[]. release == C_NULL
1869+ end
1870+ end
1871+
1872+ @testset " optional PyArrow C Data smoke" begin
1873+ python = Sys. which (" python3" )
1874+ if python === nothing
1875+ @test_skip " python3 not available"
1876+ else
1877+ script = """
1878+ try:
1879+ import pyarrow as pa
1880+ except Exception:
1881+ print("skip: pyarrow unavailable")
1882+ raise SystemExit(0)
1883+ arr = pa.array([1, None, 3], type=pa.int32())
1884+ if not hasattr(arr, "__arrow_c_array__"):
1885+ print("skip: pyarrow C array export unavailable")
1886+ raise SystemExit(0)
1887+ if not hasattr(pa.Array, "_import_from_c_capsule"):
1888+ print("skip: pyarrow C capsule import unavailable")
1889+ raise SystemExit(0)
1890+ capsules = arr.__arrow_c_array__()
1891+ out = pa.Array._import_from_c_capsule(*capsules)
1892+ assert out.to_pylist() == [1, None, 3]
1893+ print("ok")
1894+ """
1895+ out = readchomp (` $python -c $script ` )
1896+ if startswith (out, " skip:" )
1897+ @test_skip out
1898+ else
1899+ @test out == " ok"
1900+ end
1901+ end
1902+ end
15941903end
0 commit comments