Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 169 additions & 0 deletions bin/make_vers
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,19 @@ sub parse_line ($) {
$curr_sym_number++;
}

# For API versions earlier than the function's introduction,
# default to the earliest version (version 1) for maximum compatibility
my $intro_vers = $vers_list[0];
foreach my $hash_vers (@versions) {
if ($hash_vers < $intro_vers) {
if ($line_type == 1) {
$api_vers_to_function_vers{$hash_vers}{$name} = 1;
} else {
$api_vers_to_type_vers{$hash_vers}{$name} = 1;
}
}
}

# Store the number of symbol versions in a hash table, indexed by the name
if ($line_type == 1) {
$functions{$name} = $#vers_list + 1;
Expand Down Expand Up @@ -519,6 +532,160 @@ sub create_doxygen_cmake ($) {
close CMAKE;
}

##############################################################################
# Create the generated test file for verifying API version defaulting
#
sub create_test ($) {
my $prefix = shift; # Get the prefix for the source file (e.g., "src/")
my $file = "tapi_version_default.c";

# Output to test directory (relative to src/)
my $test_dir = $prefix;
$test_dir =~ s/src\/?$/test\//;

# Validate the substitution succeeded
if ($test_dir eq $prefix && $prefix ne "") {
die "create_test: prefix '$prefix' does not end in 'src/' -- cannot derive test directory";
}

# Create test directory if it doesn't exist
if ($test_dir ne "" && ! -d $test_dir) {
mkdir $test_dir or die "unable to create directory: $test_dir";
}

# Open new test file
open TEST, ">${test_dir}${file}" or die "unable to create test file: ${test_dir}${file}";

# Print copyright and warning
print_copyright(*TEST);
print_warning(*TEST);

# Print test description
print TEST "/*\n";
print TEST " * Purpose: Tests that versioned API function macros default to the earliest\n";
print TEST " * version for functions introduced after the configured global API\n";
print TEST " * version. For example, with H5_USE_16_API, H5Sencode (introduced\n";
print TEST " * in v1.8) should map to H5Sencode1, not H5Sencode2.\n";
print TEST " *\n";
print TEST " * This test is compiled multiple times with different\n";
print TEST " * -DH5_USE_*_API and -DTEST_API_VERSION=* flags to verify each\n";
print TEST " * API version level.\n";
print TEST " */\n";

# Emit preamble that clears all global API version defaults.
# When the project is configured with a default API version (e.g.,
# HDF5_DEFAULT_API_VERSION=v16), H5pubconf.h defines H5_USE_16_API_DEFAULT,
# which causes H5version.h to activate H5_USE_16_API. If the test also
# defines -DH5_USE_200_API, both blocks would be active and the earliest
# one wins (setting everything to version 1). To isolate each test to
# exactly one API version, we include H5pubconf.h first (setting its
# include guard), then #undef all API version macros, and re-establish
# only the version under test before including the rest of the headers.
print TEST "/* Include config header first to set its include guard */\n";
print TEST "#include \"H5pubconf.h\"\n\n";
print TEST "/* Clear all API version macros that may have been set by the\n";
print TEST " * global default configuration, so only the version under test\n";
print TEST " * is active when H5version.h is processed.\n";
print TEST " */\n";
foreach my $api_vers (@versions) {
print TEST "#undef H5_USE_${api_vers}_API_DEFAULT\n";
print TEST "#undef H5_USE_${api_vers}_API\n";
}
print TEST "\n";
print TEST "/* Re-establish only the API version under test */\n";
my $first_preamble = 1;
foreach my $api_vers (@versions) {
if ($first_preamble) {
print TEST "#if TEST_API_VERSION == $api_vers\n";
$first_preamble = 0;
}
else {
print TEST "#elif TEST_API_VERSION == $api_vers\n";
}
print TEST " #define H5_USE_${api_vers}_API 1\n";
}
print TEST "#else\n";
print TEST " #error \"TEST_API_VERSION not set to a valid value\"\n";
print TEST "#endif\n\n";

print TEST "#include \"h5test.h\"\n\n";

# Print CHECK_VERS macro for functions
print TEST "/*\n";
print TEST " * Helper macro: check that a _vers macro equals an expected value.\n";
print TEST " */\n";
print TEST "#define CHECK_VERS(func_name, expected) \\\n";
print TEST " do { \\\n";
print TEST " if (func_name##_vers != (expected)) { \\\n";
print TEST " fprintf(stderr, \"FAIL: %s_vers = %d, expected %d\\n\", \\\n";
print TEST " #func_name, func_name##_vers, (expected)); \\\n";
print TEST " nerrors++; \\\n";
print TEST " } \\\n";
print TEST " } while (0)\n\n";

# Print CHECK_VERS_T macro for typedefs
print TEST "#define CHECK_VERS_T(type_name, expected) \\\n";
print TEST " do { \\\n";
print TEST " if (type_name##_t_vers != (expected)) { \\\n";
print TEST " fprintf(stderr, \"FAIL: %s_t_vers = %d, expected %d\\n\", \\\n";
print TEST " #type_name, type_name##_t_vers, (expected)); \\\n";
print TEST " nerrors++; \\\n";
print TEST " } \\\n";
print TEST " } while (0)\n\n";

# Print main function
print TEST "int\n";
print TEST "main(void)\n";
print TEST "{\n";
print TEST " int nerrors = 0;\n\n";
print TEST " TESTING(\"API version defaulting for versioned functions\");\n\n";

# Generate test sections for each API version
my $first = 1;
foreach my $api_vers (@versions) {
if ($first) {
print TEST "#if TEST_API_VERSION == $api_vers\n";
$first = 0;
}
else {
print TEST "#elif TEST_API_VERSION == $api_vers\n";
}
print TEST " printf(\"Configured with H5_USE_${api_vers}_API\\n\");\n\n";

# Print checks for all functions at this API version
for my $name (sort keys %{$api_vers_to_function_vers{$api_vers}}) {
my $expected = $api_vers_to_function_vers{$api_vers}{$name};
print TEST " CHECK_VERS($name, $expected);\n";
}
print TEST "\n";

# Print checks for all typedefs at this API version
for my $name (sort keys %{$api_vers_to_type_vers{$api_vers}}) {
my $expected = $api_vers_to_type_vers{$api_vers}{$name};
print TEST " CHECK_VERS_T($name, $expected);\n";
}
print TEST "\n";
}

# Close the #if/#elif chain
print TEST "#else\n";
print TEST "#error \"TEST_API_VERSION not set to a valid value\"\n";
print TEST "#endif\n\n";

# Print result checking and return
print TEST " if (nerrors) {\n";
print TEST " H5_FAILED();\n";
print TEST " fprintf(stderr, \" %d version check%s failed\\n\", nerrors, nerrors > 1 ? \"s\" : \"\");\n";
print TEST " return 1;\n";
print TEST " }\n\n";
print TEST " PASSED();\n";
print TEST " return 0;\n";
print TEST "}\n";

# Close test file
close TEST;
}

##############################################################################
# Read symbol version file (given as command-line argument) in and process it
# into internal data structures, then create header files.
Expand Down Expand Up @@ -551,5 +718,7 @@ for $file (@ARGV) {
create_public($prefix);
print "Generating 'doxygen/H5version_doxygen.cmake'\n";
create_doxygen_cmake($prefix);
print "Generating 'test/tapi_version_default.c'\n";
create_test($prefix);
}

4 changes: 4 additions & 0 deletions release_docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ We would like to thank the many HDF5 community members who contributed to this r

## Library

### Versioned API functions now default to earliest version for older API settings

When a global API compatibility version is set (e.g., `H5_USE_16_API`), functions introduced after that version previously defaulted to their latest version, which could break applications. For example, an application using `H5_USE_16_API` that called `H5Sencode()` (introduced in 1.8, versioned in 1.12) would get `H5Sencode2()` instead of `H5Sencode1()`, potentially causing compilation or runtime failures. Versioned functions now default to their earliest (version 1) variant when the configured API level predates the function's introduction, providing maximum compatibility. See issue [#6278](https://github.com/HDFGroup/hdf5/issues/6278).

## Parallel Library

## Fortran Library
Expand Down
84 changes: 84 additions & 0 deletions src/H5version.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@
#define H5Dopen_vers 1
#endif /* !defined(H5Dopen_vers) */

#if !defined(H5Dread_chunk_vers)
#define H5Dread_chunk_vers 1
#endif /* !defined(H5Dread_chunk_vers) */

#if !defined(H5Eclear_vers)
#define H5Eclear_vers 1
#endif /* !defined(H5Eclear_vers) */
Expand All @@ -108,6 +112,10 @@
#define H5Ewalk_vers 1
#endif /* !defined(H5Ewalk_vers) */

#if !defined(H5Fget_info_vers)
#define H5Fget_info_vers 1
#endif /* !defined(H5Fget_info_vers) */

#if !defined(H5Gcreate_vers)
#define H5Gcreate_vers 1
#endif /* !defined(H5Gcreate_vers) */
Expand All @@ -116,6 +124,58 @@
#define H5Gopen_vers 1
#endif /* !defined(H5Gopen_vers) */

#if !defined(H5Iregister_type_vers)
#define H5Iregister_type_vers 1
#endif /* !defined(H5Iregister_type_vers) */

#if !defined(H5Lget_info_vers)
#define H5Lget_info_vers 1
#endif /* !defined(H5Lget_info_vers) */

#if !defined(H5Lget_info_by_idx_vers)
#define H5Lget_info_by_idx_vers 1
#endif /* !defined(H5Lget_info_by_idx_vers) */

#if !defined(H5Literate_vers)
#define H5Literate_vers 1
#endif /* !defined(H5Literate_vers) */

#if !defined(H5Literate_by_name_vers)
#define H5Literate_by_name_vers 1
#endif /* !defined(H5Literate_by_name_vers) */

#if !defined(H5Lvisit_vers)
#define H5Lvisit_vers 1
#endif /* !defined(H5Lvisit_vers) */

#if !defined(H5Lvisit_by_name_vers)
#define H5Lvisit_by_name_vers 1
#endif /* !defined(H5Lvisit_by_name_vers) */

#if !defined(H5Oget_info_vers)
#define H5Oget_info_vers 1
#endif /* !defined(H5Oget_info_vers) */

#if !defined(H5Oget_info_by_idx_vers)
#define H5Oget_info_by_idx_vers 1
#endif /* !defined(H5Oget_info_by_idx_vers) */

#if !defined(H5Oget_info_by_name_vers)
#define H5Oget_info_by_name_vers 1
#endif /* !defined(H5Oget_info_by_name_vers) */

#if !defined(H5Ovisit_vers)
#define H5Ovisit_vers 1
#endif /* !defined(H5Ovisit_vers) */

#if !defined(H5Ovisit_by_name_vers)
#define H5Ovisit_by_name_vers 1
#endif /* !defined(H5Ovisit_by_name_vers) */

#if !defined(H5Pencode_vers)
#define H5Pencode_vers 1
#endif /* !defined(H5Pencode_vers) */

#if !defined(H5Pget_filter_vers)
#define H5Pget_filter_vers 1
#endif /* !defined(H5Pget_filter_vers) */
Expand All @@ -140,6 +200,10 @@
#define H5Rget_obj_type_vers 1
#endif /* !defined(H5Rget_obj_type_vers) */

#if !defined(H5Sencode_vers)
#define H5Sencode_vers 1
#endif /* !defined(H5Sencode_vers) */

#if !defined(H5Tarray_create_vers)
#define H5Tarray_create_vers 1
#endif /* !defined(H5Tarray_create_vers) */
Expand All @@ -148,6 +212,10 @@
#define H5Tcommit_vers 1
#endif /* !defined(H5Tcommit_vers) */

#if !defined(H5Tdecode_vers)
#define H5Tdecode_vers 1
#endif /* !defined(H5Tdecode_vers) */

#if !defined(H5Tget_array_dims_vers)
#define H5Tget_array_dims_vers 1
#endif /* !defined(H5Tget_array_dims_vers) */
Expand All @@ -164,6 +232,14 @@
#define H5E_auto_t_vers 1
#endif /* !defined(H5E_auto_t_vers) */

#if !defined(H5O_info_t_vers)
#define H5O_info_t_vers 1
#endif /* !defined(H5O_info_t_vers) */

#if !defined(H5O_iterate_t_vers)
#define H5O_iterate_t_vers 1
#endif /* !defined(H5O_iterate_t_vers) */

#if !defined(H5Z_class_t_vers)
#define H5Z_class_t_vers 1
#endif /* !defined(H5Z_class_t_vers) */
Expand Down Expand Up @@ -192,6 +268,10 @@
#define H5Dopen_vers 2
#endif /* !defined(H5Dopen_vers) */

#if !defined(H5Dread_chunk_vers)
#define H5Dread_chunk_vers 1
#endif /* !defined(H5Dread_chunk_vers) */

#if !defined(H5Eclear_vers)
#define H5Eclear_vers 2
#endif /* !defined(H5Eclear_vers) */
Expand Down Expand Up @@ -276,6 +356,10 @@
#define H5Ovisit_by_name_vers 1
#endif /* !defined(H5Ovisit_by_name_vers) */

#if !defined(H5Pencode_vers)
#define H5Pencode_vers 1
#endif /* !defined(H5Pencode_vers) */

#if !defined(H5Pget_filter_vers)
#define H5Pget_filter_vers 2
#endif /* !defined(H5Pget_filter_vers) */
Expand Down
36 changes: 36 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,42 @@ foreach (h5_test ${H5_CHECK_TESTS})
ADD_H5_EXE(${h5_test})
endforeach ()

#-- Adding test executables for API version defaulting
# Each is compiled with a different -DH5_USE_*_API and -DTEST_API_VERSION=*
# to verify that functions introduced after the configured API version
# default to their earliest version.
# The test source is auto-generated by bin/make_vers from H5vers.txt.
# Older API versions (v16-v114) require deprecated symbols to be enabled.
# NOTE: Cannot use ADD_H5_EXE here because multiple targets share one source
# file (tapi_version_default.c) with different compile definitions.
if (HDF5_ENABLE_DEPRECATED_SYMBOLS)
set (API_VERSION_TEST_NUMBERS 16 18 110 112 114 200)
else ()
set (API_VERSION_TEST_NUMBERS 200)
endif ()
foreach (api_num IN LISTS API_VERSION_TEST_NUMBERS)
set (test_name tapi_version_default_v${api_num})
add_executable (${test_name} ${HDF5_TEST_SOURCE_DIR}/tapi_version_default.c)
target_include_directories (${test_name} PRIVATE "${HDF5_SRC_INCLUDE_DIRS};${HDF5_SRC_BINARY_DIR};${HDF5_TEST_BINARY_DIR};$<$<BOOL:${HDF5_ENABLE_PARALLEL}>:${MPI_C_INCLUDE_DIRS}>")
target_compile_options(${test_name} PRIVATE ${HDF5_CMAKE_C_FLAGS})
target_compile_definitions(${test_name} PRIVATE
${HDF5_TEST_COMPILE_DEFS_PRIVATE}
H5_USE_${api_num}_API
TEST_API_VERSION=${api_num}
)
if (NOT BUILD_SHARED_LIBS)
TARGET_C_PROPERTIES (${test_name} STATIC)
target_link_libraries (${test_name} PRIVATE ${HDF5_TEST_LIB_TARGET})
else ()
TARGET_C_PROPERTIES (${test_name} SHARED)
target_link_libraries (${test_name} PRIVATE ${HDF5_TEST_LIBSH_TARGET})
endif ()
set_target_properties (${test_name} PROPERTIES FOLDER test)
if (HDF5_ENABLE_FORMATTERS)
clang_format (HDF5_TEST_${test_name}_FORMAT ${test_name})
endif ()
endforeach ()

#-- Adding test for libinfo
set (GREP_RUNNER ${PROJECT_BINARY_DIR}/GrepRunner.cmake)
file (WRITE ${GREP_RUNNER} "file (STRINGS \${TEST_PROGRAM} TEST_RESULT REGEX \"SUMMARY OF THE HDF5 CONFIGURATION\")
Expand Down
Loading
Loading