(Generated by Claude Code)
The test writes a markups JSON file containing NaN (for an undefined control point position),
then reads it back. The read fails:
vtkMRMLJsonElement.cxx:677 ERR| vtkMRMLJsonReader: Error parsing the file '...fiducial-temp.mrk.json'
vtkMRMLJsonElement.cxx and vtkMRMLJsonElement_Private.h define
RAPIDJSON_PARSE_DEFAULT_FLAGS to 256 (kParseNanAndInfFlag) before including rapidjson headers.
This makes the default ParseStream template instantiation use flag 256, allowing NaN parsing.
However, two other source files include rapidjson without these defines:
Modules/Loadable/Volumes/Logic/vtkSlicerVolumesLogic.cxxModules/Loadable/Terminologies/Logic/vtkSlicerTerminologiesModuleLogic.cxx
Each translation unit produces its own weak symbol for
rapidjson::GenericDocument::ParseStream<FileReadStream>.
In the version from MRMLCore, this calls ParseStream<256u, UTF8, FileReadStream> (NaN-aware).
In the versions from Volumes and Terminologies, it calls ParseStream<0u, UTF8, FileReadStream>
(strict JSON, no NaN).
Confirmed via nm -gC:
| Library | ParseStream<FileReadStream> calls... |
|---|---|
libMRMLCore.so |
ParseStream<256u, ...> (NaN OK) |
libvtkSlicerVolumesModuleLogic.so |
ParseStream<0u, ...> (no NaN) |
libvtkSlicerTerminologiesModuleLogic.so |
ParseStream<0u, ...> (no NaN) |
The dynamic linker resolves all three weak ParseStream<FileReadStream> symbols to one
implementation. If it picks the Volumes or Terminologies version, all callers (including MRMLCore)
get the version without NaN support, and parsing fails.
VTK 9.6 changes library load order (likely due to new/reordered link dependencies), causing the wrong weak symbol to win. With VTK 9.5 the MRMLCore symbol happened to win. This is not guaranteed by any spec — it was always a latent bug.
The nightly CDash (Release build) passes because the compiler inlines the template differently in optimized builds, avoiding the cross-library symbol conflict.
In vtkMRMLJsonElement.cxx around line 675, change:
if (jsonElement->Internal->JsonRoot->Document->ParseStream(fs).HasParseError())to:
if (jsonElement->Internal->JsonRoot->Document
->ParseStream<rapidjson::kParseNanAndInfFlag, rapidjson::UTF8<char>>(fs)
.HasParseError())This bypasses the default-flag template and uses an explicit instantiation that cannot be hijacked by a different library's weak symbol.
Do the same for any other ParseStream or Parse calls in this file that rely on
kParseDefaultFlags.
Add the following before rapidjson includes in:
Modules/Loadable/Volumes/Logic/vtkSlicerVolumesLogic.cxxModules/Loadable/Terminologies/Logic/vtkSlicerTerminologiesModuleLogic.cxx
#define RAPIDJSON_WRITE_DEFAULT_FLAGS 2
#define RAPIDJSON_PARSE_DEFAULT_FLAGS 256This eliminates the ODR violation entirely so all libraries agree on the default flags.
Apply both for defense in depth. Option A protects against future ODR issues from any new rapidjson user. Option B ensures consistent behavior across all current users.