diff --git a/src/silo/silo_f.c b/src/silo/silo_f.c index 81d2e2b7..ebffa266 100644 --- a/src/silo/silo_f.c +++ b/src/silo/silo_f.c @@ -854,7 +854,7 @@ DBPUTMSP_FC (int *dbid, FCD_DB name, int *lname, FCD_DB matname, int *lmatname, *status = DBPutMatspecies(dbfile, nm, mnm, *nmat, nmatspec, speclist, dims, *ndims, *nspecies_mf, - species_mf, mix_speclist, *mixlen, + FPTR(species_mf), FPTR(mix_speclist), *mixlen, *datatype, optlist); FREE(nm); @@ -1412,7 +1412,7 @@ DBPUTFL_FC (int *dbid, FCD_DB name, int *lname, int *nfaces, int *ndims, nodelist, *lnodelist, *origin, FPTR(zoneno), shapesize, shapecnt, *nshapes, - types, typelist, *ntypes); + FPTR(types), FPTR(typelist), *ntypes); FREE(nm); @@ -2485,7 +2485,8 @@ DBPUTQV1_FC (int *dbid, FCD_DB name, dbfile = (DBfile *) DBFortranAccessPointer(*dbid); - *status = DBPutQuadvar1(dbfile, nm, mnm, var, dims, *ndims, mixvar, + *status = DBPutQuadvar1(dbfile, nm, mnm, var, dims, *ndims, + *mixlen==0?NULL:mixvar, *mixlen, *datatype, *centering, optlist); FREE(nm); @@ -2612,7 +2613,7 @@ DBPUTQV_FC (int *dbid, FCD_DB vname, int *lvname, FCD_DB mname, int *lmname, } else { API_ERROR("vars", E_BADARGS); } - if ((*(int *)mixvar) != DB_F77NULL) { + if (mixlen > 0) { /* Now convert the Fortran data array into a C array of data */ cmixvar = (void **)malloc(sizeof(void*) * (*nvars)); /* Make pointers to Fortran address in vars array */ @@ -2895,8 +2896,8 @@ DBPUTUV1_FC (int *dbid, FCD_DB name, dbfile = (DBfile *) DBFortranAccessPointer(*dbid); - *status = DBPutUcdvar1(dbfile, nm, mnm, var, *nels, mixvar, *mixlen, - *datatype, *centering, optlist); + *status = DBPutUcdvar1(dbfile, nm, mnm, var, *nels, mixlen==0?NULL:mixvar, + *mixlen, *datatype, *centering, optlist); FREE(nm); FREE(mnm); @@ -4249,22 +4250,17 @@ DBGETQV1_FC (int *dbid, FCD_DB varname, int *lvarname, void *var, int *dims, *ndims = qv->ndims; *centering = (qv->align[0] == 0.) ? DB_NODECENT : DB_ZONECENT; *datatype = qv->datatype; + *mixlen = qv->mixlen; for (i = 0; i < qv->ndims; i++) dims[i] = qv->dims[i]; - /*------------------------------------------------------------ - * If there was mixed data, copy that too. - * - * Kludge city!!! I'm assuming name of mixed component is - * 'varname_mix'. This info should be kept in DBquadvar - * somewhere. - *-----------------------------------------------------------*/ - if ((*(int *)mixvar) != DB_F77NULL) { - strcpy(tmpstr, varnm); - strcat(tmpstr, "_mix"); - - *mixlen = DBGetVarLength(dbfile, tmpstr); - DBReadVar(dbfile, tmpstr, mixvar); + /*------------------------------ + * Copy the mixed vals of quad + * var into the supplied space. + *-----------------------------*/ + if (qv->mixlen > 0 && (FPTR(mixvar))) { + nbytes = qv->mixlen * db_GetMachDataSize(qv->datatype); + memcpy(mixvar, qv->mixvals[0], nbytes); } DBFreeQuadvar(qv); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 795bd698..3e39138f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -738,14 +738,10 @@ if(SILO_ENABLE_SILOCK AND NOT WIN32) COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/testsilock ${silo_test_output_dir}) - add_test(NAME testsilock - COMMAND ${CMAKE_COMMAND} -E env - "${silo_test_output_dir}/testsilock") + add_test(NAME testsilock COMMAND "${silo_test_output_dir}/testsilock") set_tests_properties(onehex PROPERTIES DEPENDS testsilock RESOURCE_LOCK onehex.silo) if(SILO_ENABLE_HDF5 AND HDF5_FOUND) - add_test(NAME testsilock-hdf5 - COMMAND ${CMAKE_COMMAND} -E env - "${silo_test_output_dir}/testsilock" DB_HDF5) + add_test(NAME testsilock-hdf5 COMMAND "${silo_test_output_dir}/testsilock" DB_HDF5) set_tests_properties(onehex-hdf5 PROPERTIES DEPENDS testsilock-hdf5) set_tests_properties(testsilock-hdf5 PROPERTIES DEPENDS testsilock RESOURCE_LOCK onehex.silo) endif() @@ -786,7 +782,7 @@ if(SILO_ENABLE_PYTHON_MODULE AND Python_EXECUTABLE) set_tests_properties(test-write-py-hdf5 PROPERTIES DEPENDS sami-hdf5 REQUIRED_FILES sami.silo) set_tests_properties(test-write-py-hdf5 PROPERTIES RESOURCE_LOCK "sami.silo") set_tests_properties(test-write-py-hdf5 PROPERTIES LABELS "python;hdf5") - add_test(NAME test-scale-quadvar-hdf5 COMMAND ${CMAKE_COMMAND} -E env "${silo_test_output_dir}/test_scale_quadvar") + add_test(NAME test-scale-quadvar-hdf5 COMMAND "${silo_test_output_dir}/test_scale_quadvar") set_tests_properties(test-scale-quadvar-hdf5 PROPERTIES SKIP_RETURN_CODE ${_silo_test_skip_retval} ENVIRONMENT "PYTHONPATH=$${_envvar_path_sep}$ENV{PYTHONPATH};LD_PRELOAD=${SILO_LIBASAN_PATH}:${SILO_LIBUBSAN_PATH};ASAN_OPTIONS=detect_leaks=0;SILO_TEST_SKIP_RETURN_CODE=${_silo_test_skip_retval}") set_tests_properties(test-scale-quadvar-hdf5 PROPERTIES LABELS "python;hdf5") @@ -816,14 +812,14 @@ if(SILO_ENABLE_PYTHON_MODULE AND Python_EXECUTABLE) set_tests_properties(multi_test-reset-hdf5 test-read-py-hdf5 PROPERTIES REQUIRED_FILES "multi_ucd3d.h5;multi_rect3d.h5") endif() - add_test(NAME testonehex COMMAND ${CMAKE_COMMAND} -E env "${silo_test_output_dir}/testonehex" DB_PDB) + add_test(NAME testonehex COMMAND "${silo_test_output_dir}/testonehex" DB_PDB) set_tests_properties(testonehex PROPERTIES DEPENDS onehex) set_tests_properties(testonehex PROPERTIES ENVIRONMENT "PYTHONPATH=$${_envvar_path_sep}$ENV{PYTHONPATH};LD_PRELOAD=${SILO_LIBASAN_PATH}:${SILO_LIBUBSAN_PATH};ASAN_OPTIONS=detect_leaks=0") set_tests_properties(testonehex PROPERTIES LABELS "python") set_tests_properties(testonehex onehex PROPERTIES RESOURCE_LOCK "onehex.silo") if(SILO_ENABLE_HDF5 AND HDF5_FOUND) - add_test(NAME testonehex-hdf5 COMMAND ${CMAKE_COMMAND} -E env "${silo_test_output_dir}/testonehex" DB_HDF5) + add_test(NAME testonehex-hdf5 COMMAND "${silo_test_output_dir}/testonehex" DB_HDF5) set_tests_properties(testonehex-hdf5 PROPERTIES DEPENDS "onehex-hdf5;testonehex") set_tests_properties(testonehex-hdf5 PROPERTIES ENVIRONMENT "PYTHONPATH=$${_envvar_path_sep}$ENV{PYTHONPATH};LD_PRELOAD=${SILO_LIBASAN_PATH}:${SILO_LIBUBSAN_PATH};ASAN_OPTIONS=detect_leaks=0") set_tests_properties(testonehex-hdf5 PROPERTIES LABELS "python;hdf5") diff --git a/tests/dbf77null.f b/tests/dbf77null.f new file mode 100644 index 00000000..da03e973 --- /dev/null +++ b/tests/dbf77null.f @@ -0,0 +1,202 @@ +C*********************************************************************** +C Copyright (C) 1994-2026 Lawrence Livermore National Security, LLC. +C LLNL-CODE-425250. +C All rights reserved. +C +C This file is part of Silo. For details, see silo.llnl.gov. +C*********************************************************************** +C +C Purpose +C +C Exercise the DB_F77NULL convention in Silo's Fortran interface. +C +C In particular, verify +C +C 1. DB_F77NULL may be supplied for an absent mixed-data array. +C 2. DB_F77NULL may be supplied for optional facelist arrays. +C 3. A legitimate INTEGER mixed-data array whose first value is +C -99 is not mistaken for DB_F77NULL. +C +C Case 3 intentionally demonstrates why a wrapper must not blindly +C apply FPTR() to a nullable data array when another argument (such +C as mixlen) can unambiguously say whether the array is present. +C +C ChatGPT via Mark C. Miller, Thu Aug 27 20:24:53 PDT 2026 +C*********************************************************************** + + program f77null + + implicit none + include "silo.inc" + + integer dbid, err, driver, nargs, errmode + integer status, nerrors + integer dims(1), vdims(1) + integer ivar(1), mixvar(1) + integer rvar(1), rmixvar(1), rdims(1) + integer rndims, rmixlen, rdatatype, rcentering + integer nodelist(2), shapesize(1), shapecnt(1) + character*256 cloption + real x(2) + + errmode = DB_NONE + driver = DB_PDB + nargs = iargc() + if (nargs .gt. 0) then + call getarg(1, cloption) + if (cloption .eq. "DB_HDF5") driver = DB_HDF5 + if (cloption .eq. "show-all-errors") then + errmode = DB_ALL_AND_DRVR + endif + endif + + status = dbshowerrors(errmode) + + nerrors = 0 + +C...Create a tiny 1-D quad mesh. + + x(1) = 0.0 + x(2) = 1.0 + dims(1) = 2 + + err = dbcreate("dbf77null.silo", 12, DB_CLOBBER, DB_LOCAL, + . "DB_F77NULL regression test", 26, + . driver, dbid) + if (err .ne. 0) then + print *, "dbcreate failed" + stop 1 + endif + + status = 0 + err = dbputqm(dbid, "mesh", 4, + . "X", 1, DB_F77NULLSTRING, 0, + . DB_F77NULLSTRING, 0, + . x, DB_F77NULL, DB_F77NULL, + . dims, 1, DB_FLOAT, DB_COLLINEAR, + . DB_F77NULL, status) + if (err .ne. 0 .or. status .lt. 0) then + print *, "dbputqm failed" + nerrors = nerrors + 1 + endif + +C...A scalar zone-centered variable with no mixed data. + + vdims(1) = 1 + ivar(1) = 17 + + status = 0 + err = dbputqv1(dbid, "nomix", 5, "mesh", 4, + . ivar, vdims, 1, DB_F77NULL, 0, + . DB_INT, DB_ZONECENT, DB_F77NULL, status) + if (err .ne. 0 .or. status .lt. 0) then + print *, "dbputqv1 with DB_F77NULL mixvar failed" + nerrors = nerrors + 1 + endif + +C...Now provide real mixed data whose first value happens to equal the +C...DB_F77NULL magic integer value. This is legitimate data and must +C...NOT be converted to a C NULL pointer. +C... +C...A wrapper of the form FPTR(mixvar) gets this wrong because FPTR +C...examines the first INTEGER value. For DBPutQuadvar1, mixlen already +C...tells the wrapper whether mixvar is present, so the safer conversion +C...is conceptually: *mixlen == 0 ? NULL : mixvar + + mixvar(1) = -99 + + status = 0 + err = dbputqv1(dbid, "minus99", 7, "mesh", 4, + . ivar, vdims, 1, mixvar, 1, + . DB_INT, DB_ZONECENT, DB_F77NULL, status) + if (err .ne. 0 .or. status .lt. 0) then + print *, "dbputqv1 rejected legitimate mixvar(1)=-99" + nerrors = nerrors + 1 + endif + +C...Exercise DB_F77NULL for optional facelist arrays. + + nodelist(1) = 0 + nodelist(2) = 1 + shapesize(1) = 2 + shapecnt(1) = 1 + + status = 0 + err = dbputfl(dbid, "fl", 2, 1, 2, + . nodelist, 2, 0, DB_F77NULL, + . shapesize, shapecnt, 1, + . DB_F77NULL, DB_F77NULL, 0, status) + if (err .ne. 0 .or. status .lt. 0) then + print *, "dbputfl with DB_F77NULL optional arrays failed" + nerrors = nerrors + 1 + endif + + err = dbclose(dbid) + if (err .ne. 0) then + print *, "dbclose failed" + nerrors = nerrors + 1 + endif + +C...Read the -99 mixed data back. + + err = dbopen("dbf77null.silo", 12, driver, DB_READ, dbid) + if (err .ne. 0) then + print *, "dbopen failed" + stop 1 + endif + + rvar(1) = 0 + rmixvar(1) = 0 + rdims(1) = 0 + rndims = 0 + rmixlen = 0 + rdatatype = 0 + rcentering = 0 + + err = dbgetqv1(dbid, "minus99", 7, rvar, rdims, rndims, + . rmixvar, rmixlen, rdatatype, rcentering) + if (err .ne. 0) then + print *, "dbgetqv1(minus99) failed" + nerrors = nerrors + 1 + else + if (rmixlen .ne. 1) then + print *, "wrong mixlen; expected 1, got ", rmixlen + nerrors = nerrors + 1 + endif + if (rmixvar(1) .ne. -99) then + print *, "wrong mixed value; expected -99, got ", + . rmixvar(1) + nerrors = nerrors + 1 + endif + endif + +C...Also exercise DB_F77NULL on the output side of dbgetqv1. + + rvar(1) = 0 + rdims(1) = 0 + rndims = 0 + rmixlen = 0 + rdatatype = 0 + rcentering = 0 + + err = dbgetqv1(dbid, "nomix", 5, rvar, rdims, rndims, + . DB_F77NULL, rmixlen, rdatatype, rcentering) + if (err .ne. 0) then + print *, "dbgetqv1 with DB_F77NULL mixvar failed" + nerrors = nerrors + 1 + endif + + err = dbclose(dbid) + if (err .ne. 0) then + print *, "final dbclose failed" + nerrors = nerrors + 1 + endif + + if (nerrors .ne. 0) then + print *, "DB_F77NULL regression test failed:", nerrors + stop 1 + endif + + print *, "DB_F77NULL regression test passed" + stop + end diff --git a/tests/test_scale_quadvar b/tests/test_scale_quadvar index dd3186ee..1e2783e3 100755 --- a/tests/test_scale_quadvar +++ b/tests/test_scale_quadvar @@ -121,6 +121,13 @@ fi # Use python module to create data with no special features # env PYTHONPATH=$(dirname $silo_pyso) $pycmd $scale_quadvar_py 1>/dev/null 2>&1 +py_status=$? +if [ -n "$SILO_TEST_SKIP_RETURN_CODE" ] && [ "$py_status" -eq "$SILO_TEST_SKIP_RETURN_CODE" ]; then + exit $SILO_TEST_SKIP_RETURN_CODE +fi +if [ "$py_status" -ne 0 ]; then + exit "$py_status" +fi # # Now, run browser and make sure we don't get any errors in the diff