Skip to content

COMP: Declare the exported functions that no header declares - #37

Merged
hjmjohnson merged 4 commits into
InsightSoftwareConsortium:masterfrom
gdevenyi:pr/fix-missing-prototypes
Sep 21, 2026
Merged

hjmjohnson merged 4 commits into
InsightSoftwareConsortium:masterfrom
gdevenyi:pr/fix-missing-prototypes

Conversation

@gdevenyi

@gdevenyi gdevenyi commented Aug 15, 2026 •

Copy link
Copy Markdown

Fourteen functions are compiled into libniftiio, libnifti2, libfslio and
libcifti with external linkage, and not one is declared in any header --
so they are exported, linkable, and invisible.

nifti_fileexists nifti1_io.c and nifti2_io.c
axml_recur_find_xml afni_xml.c
FslIsValidFileType, FslGetFileType2, fslio.c
FslFileType, FslGetReadFileType,
FslGetHdrImgNames, FslInit4Write,
fsl_fileexists, check_for_multiple_filenames,
FslSetVoxUnits, FslGetVoxUnits,
FslSetIntensityScaling

Each gets a prototype in its own translation unit, which is the only fix
that leaves the interface alone. static is what clang-tidy's
misc-use-internal-linkage wants, and it would delete all fourteen symbols
from the shared libraries; moving them to a public header would enlarge
the API with functions nobody has reviewed as public.


Interface impact: none. On the union of all these changes, configured with USE_FSL_CODE=ON and USE_CIFTI_CODE=ON: all 448 exported symbols across libniftiio, libnifti2, libznz, libfslio, libnifticdf and libcifti are identical to master under nm -D --defined-only, and all ten installed headers are identical under gcc -E -P. Under gcc -dM -E one macro definition differs, intentionally and only in text: #61 makes FSL_RADIOLOGICAL read (-1) so it is safe inside an expression. Its value is still -1, checked by compiling against each installed fslio.h and printing it.

Verification. This branch: builds with gcc 16.1.1, ctest unchanged from master (2 of 345 fail on master itself in this environment; #31 and #29 each fix one). The union of all the PRs: 0 errors under both gcc 16.1.1 and clang 22.1.8, ctest 345/345 under each, and the whole suite under valgrind memcheck with --trace-children=yes gives 484 traced processes with no invalid access, no uninitialised value and no leak in any nifti binary.

Coordination. Every line of every branch was compared, whitespace-normalised, against the diffs of the open PRs (#11, #21, #22, #23, #24). Where one of those already changes a line, the line was left alone, and the few deliberate overlaps are named in the text above. What survives is 17 compiler warnings, all of them on those lines: 9 -Wsign-conversion (5 in fslio.c for #22, 2 in nifti2_io.c and 2 in nifti_tester001.c for #24) and 8 -Wcalloc-transposed-args in nifti_findhdrname and nifti_findimgname, which #11 rewrites. No formatting changes appear anywhere, to stay clear of #10 and #12.

One of a set of independent, single-purpose PRs. Each bases on master and can be merged on its own, in any order.

The full set of PRs (35)

The union of all of them is on the fork as all-changes, if you want to build and test the lot at once.

CI and build

Configuration and documentation

Defects

Warning and check classes

This was referenced Aug 15, 2026
@hjmjohnson

Copy link
Copy Markdown
Member

Rebased onto current master and verified with GCC 13.3, which reports these same defects as -Wmissing-declarations. Added a second commit so FslGetAuxFile derives its bound from sizeof like the setter in #46.

before after
warnings 15 1 (the -Wstringop-truncation #46 fixes)
tests 345 pass 345 pass
exported symbols 448 448, identical

The symbol baseline landed in #77, so this PR's central claim — that declaring rather than static-ifying leaves the ABI alone — is now checked by CI rather than asserted.

Comments and commit message trimmed to the prose budget

The four six-line comment blocks are now one line each:

/* Exported but unpublished; static would drop symbols downstream links against. */

The ABI constraint is the load-bearing part and is kept; the rest restated the diff. The -Wmissing-prototypes reference was also dropped because the flag name is compiler-specific — GCC reports the identical defects under -Wmissing-declarations, which is what made them invisible when the warning set was measured on AppleClang alone.

The commit body was cut from 18 lines to 6, dropping the table of function names that the diff already shows, and the Co-Authored-By: / Claude-Session: trailers were removed. Authorship is unchanged.

Two symbols still worth a decision

fsl_fileexists and check_for_multiple_filenames are unprefixed global names, and this PR's own reasoning argues against declaring them. Making them static would be the opposite call and would change the exported set, which the baseline job would now catch. Left as-is here because that is an interface decision rather than part of this fix; #36 takes the static route for a different symbol, so the two PRs currently encode opposite policies.

Separately, nifti_fileexists is defined identically in both libniftiio and libnifti2. The symbol baseline shows 100 names exported by more than one library, so ELF interposition decides which one a caller linking both gets. Out of scope here, recorded for follow-up.

@hjmjohnson
hjmjohnson force-pushed the pr/fix-missing-prototypes branch from 1fcc7b4 to 373e2cb Compare September 21, 2026 19:13
@hjmjohnson

Copy link
Copy Markdown
Member

Restructured into three commits, because the fourteen functions are not one situation. fslio.h already publishes the direct counterpart of several of them, and two others carry neither the Fsl prefix nor an FSL_API declaration — declaring all fourteen locally would have frozen both mistakes in place.

Commit Symbols
Ten functions whose sibling is already published ENH: declare in fslio.h / afni_xml.h 448 → 448
Two internal helpers that leaked into the exported set COMP: static 448 → 446
nifti_fileexists, exported by two libraries COMP: declared locally, pending unchanged

Verified on Ubuntu 24.04 with GCC 13.3: warnings 15 → 1, 345/345 tests, symbol baseline updated and matching. The one remaining warning is the -Wstringop-truncation that #46 fixes.

Why these ten belong in the headers

fslio.h declares the counterpart of each with FSL_API, so their absence reads as a header that lost entries rather than a deliberate boundary:

Published Missing until now
FslGetIntensityScaling FslSetIntensityScaling
FslSetVoxDim, FslGetVoxDim, FslSetTimeUnits, FslGetTimeUnits FslSetVoxUnits, FslGetVoxUnits
FslIsSingleFileType, FslIsCompressedFileType, FslBaseFileType, FslFileTypeString FslIsValidFileType, FslGetFileType2, FslFileType, FslGetReadFileType
FslInit, FslInitHeader FslInit4Write, FslGetHdrImgNames

A public getter with a private setter is the clearest case: FslGetIntensityScaling has been callable all along while FslSetIntensityScaling was not declared anywhere.

axml_recur_find_xml is in the same position in cifti/afni_xml.h, which declares nineteen axml_* functions including its direct sibling axml_recur.

These declarations are additive. No symbol changes, and no caller has to be edited.

Why these two are static

fsl_fileexists and check_for_multiple_filenames are called only from fslio.c, carry neither the Fsl prefix nor an FSL_API declaration, and have no counterpart in any header. Nothing in the tree calls them across a translation unit.

This removes two symbols from libfslio, which is an ABI change and is recorded in cmake/exported_symbols_linux.txt in the same commit, so it appears in review rather than in a CI failure:

- libfslio.so check_for_multiple_filenames
- libfslio.so fsl_fileexists

The route back out is written at the definitions:

/* static: if a downstream project needs these, declare them in fslio.h with FSL_API. */
Why nifti_fileexists is left alone

It is defined with external linkage in both nifti1_io.c and nifti2_io.c, so libniftiio and libnifti2 export the same name and ELF link order decides which implementation a caller linking both resolves to. Publishing it blesses the duplication; making it static is an ABI break for an unknown set of consumers. Neither should be chosen while the duplication stands, so it is declared locally with the reason recorded at the declaration.

It is also not an isolated case. The symbol baseline shows 100 names exported by both libraries:

awk '{print $2}' cmake/exported_symbols_linux.txt | sort | uniq -d

nifti_fileexists surfaced first only because it was also undeclared. A policy for the whole overlap will settle it; a decision for this one symbol leaves ninety-nine behind.

Fourth commit

STYLE: Derive the aux_file bound from sizeof makes FslGetAuxFile derive its bound the same way FslSetAuxFile will after #46, so the pair cannot drift if the field width changes. No behavior change: sizeof is 24, so the copy still writes at most 23 bytes.

FslGetHdrImgNames and FslSetIntensityScaling are defined here but
declared nowhere, while FslGetIntensityScaling and FslInit are already
published.  axml_recur_find_xml sits beside axml_recur in afni_xml.h
the same way.

The upstream fslio was deleted in 2015 in favour of a C++ replacement,
so this copy is the surviving one and its header is ours to correct.

Additive; no symbol changes.
Nine functions in fslio.c have external linkage and no declaration in
any header.  Nothing in the tree calls them across a translation unit,
AFNI's vendored copy never calls them, and no public source outside a
vendored copy of this file references them, so a caller would have had
to declare them itself.

Collect their prototypes in one block so the boundary between internal
and published is visible in one place.

FslSetVoxUnits and FslGetVoxUnits have no caller at all and are left
under #if 0 rather than deleted; FslSetTimeUnits and FslGetTimeUnits
are published, so the asymmetry is worth keeping visible.

Removes nine symbols from libfslio, which the baseline records.
nifti1_io.c and nifti2_io.c each define nifti_fileexists with external
linkage, so libniftiio and libnifti2 export the same name and ELF link
order decides which one a caller linking both resolves to.

Declaring it locally silences the warning without choosing between
publishing it and making it static; that choice needs the duplication
settled first, and it covers a hundred names, not this one.
Keeps FslGetAuxFile and FslSetAuxFile deriving the same length so the
pair cannot drift if the field width changes.  sizeof is 24, so the
copy still writes at most 23 bytes.
@hjmjohnson
hjmjohnson force-pushed the pr/fix-missing-prototypes branch from 373e2cb to 15db8e0 Compare September 21, 2026 22:13
@hjmjohnson

Copy link
Copy Markdown
Member

Restructured again, on evidence rather than on the internal-consistency argument I used before. Three functions are declared; nine are given internal linkage; nifti_fileexists is left alone.

Verified on Ubuntu 24.04 with GCC 13.3: warnings 15 → 1, 345/345 tests, exported symbols 448 → 439 with the baseline updated in the same commit. The remaining warning is the -Wstringop-truncation that #46 fixes; together the two take the default configuration to zero.

Commit Change Symbols
ENH: declare 3 in fslio.h / afni_xml.h 448 → 448
COMP: 9 given internal linkage 448 → 439
COMP: nifti_fileexists declared locally, deferred unchanged
STYLE: FslGetAuxFile bound derived from sizeof unchanged
Why only three are declared

The previous revision declared ten, reasoning that each had a published counterpart. That argument does not survive checking who actually calls them.

  • AFNI vendors fsliolib and calls none of the 72 FSL_API functions from its own code. Every reference in a 184 MB tree is inside its vendored src/nifti/ copy.
  • A GitHub-wide search for each of the nine finds no use outside a vendored copy of fslio.c or fslio.h. The one apparent exception, FslFileType in FSL's newimage/generalio.cc, is a substring match on FslGetFileType and FslFileTypeString.
  • Because none of them was ever in a header, a consumer would have had to write its own declaration to call one. No public source does.

FslGetHdrImgNames and FslSetIntensityScaling are declared because they are defined in this file while their direct counterparts, FslInit and FslGetIntensityScaling, are already published here. axml_recur_find_xml sits beside axml_recur in afni_xml.h the same way.

The upstream this was copied from no longer exists

git.fmrib.ox.ac.uk/fsl/fslio deleted fslio.c and fslio.h in January 2015: "Remove fslio.cc,h for NewNew", replaced by a C++ implementation. The repository's current tree holds only imln, imrm, imtest and remove_ext.

The last change to fslio.h upstream was bfefa46, 13 November 2008, "Added declaration of FslGetHdrImgNames" — one of the two declarations this PR adds. Our copy predates it.

So there is no newer version to track and no upstream to send this to. This copy is the surviving one, which is what makes correcting its header a maintenance decision rather than a divergence.

Two functions left under #if 0

FslSetVoxUnits and FslGetVoxUnits have no caller anywhere: not in this file, not in AFNI, not in any public source, and they are declared in no header. Giving them internal linkage made that visible as -Wunused-function, which external linkage had been suppressing.

They are wrapped in #if 0 rather than deleted because FslSetTimeUnits and FslGetTimeUnits are published. The asymmetry may be an omission worth correcting later, and deleting the implementations would make it harder to notice, not easier.

What nifti_fileexists is waiting on

It is defined with external linkage in both nifti1_io.c and nifti2_io.c, so libniftiio and libnifti2 export the same name and link order decides which one a caller linking both resolves to. It is declared locally here, which silences the warning without choosing between publishing it and making it static.

That choice should not be made one symbol at a time. The baseline shows 100 names exported by both libraries, and 60 of the 100 have different implementations, because nifti2_io.c is a retyped fork in which nifti_image holds int64_t dimensions where nifti1_io.c holds int. Both export, for instance, nifti_add_extension(nifti_image *, const char *, int, int) over incompatible structs.

Still warns under FSLSTYLE=ON, and not addressed here

-DFSLSTYLE=ON compiles paths the default build does not, and two defects live there:

  • niftilib/nifti1_io.c:2830 and nifti2/nifti2_io.c:3768 free basename and then read it in the fprintf that follows — -Wuse-after-free. BUG: Fix the ambiguous-filename path in nifti_findhdrname #54 rewrites that block; whether it removes the use-after-free is worth confirming before it merges.
  • doPigz and doPigz2 are undeclared in both files, the same defect this PR fixes elsewhere, but inside #ifdef PIGZ so they were not in the original list.

Neither is in scope here. Both became visible only once #75 made CI compile those paths.

@hjmjohnson
hjmjohnson merged commit 064dc2d into InsightSoftwareConsortium:master Sep 21, 2026
17 of 21 checks passed
@hjmjohnson

Copy link
Copy Markdown
Member

The commit messages in this range were rewritten to remove trailers that do not belong in permanent history: Co-Authored-By: naming an AI tool, and Claude-Session: URLs that resolve for nobody. Only messages changed — the tree at the tip of master is byte-identical, and author, committer, and dates are preserved.

This PR's commits on the rewritten master:

  • 4673c40690 ENH: Declare the three functions that have a published counterpart
  • 8cdcf1ab1e COMP: Give the fsliolib internal functions static linkage
  • 54e74e1ccf COMP: Declare nifti_fileexists where it is defined
  • 0c0e66c953 STYLE: Derive the aux_file bound from sizeof

The SHA recorded above by GitHub is from the pre-rewrite history and no longer resolves.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants