Skip to content

Instantly share code, notes, and snippets.

@ebrahimebrahim
Last active March 11, 2026 21:43
Show Gist options
  • Select an option

  • Save ebrahimebrahim/29abc95780e51edd7ec31dd61e37195b to your computer and use it in GitHub Desktop.

Select an option

Save ebrahimebrahim/29abc95780e51edd7ec31dd61e37195b to your computer and use it in GitHub Desktop.
Analysis of vtkMRMLMarkupsStorageNodeTest2 failure with VTK 9.6 (rapidjson ODR violation)

(Generated by Claude Code)

vtkMRMLMarkupsStorageNodeTest2 failure analysis

Symptom

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'

Root cause: ODR violation with rapidjson weak symbols

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.cxx
  • Modules/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.

Why it manifests with VTK 9.6

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.

Suggested fixes

Option A: Explicit template parameter in vtkMRMLJsonElement.cxx (minimal, targeted)

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.

Option B: Add defines to all rapidjson users (comprehensive)

Add the following before rapidjson includes in:

  • Modules/Loadable/Volumes/Logic/vtkSlicerVolumesLogic.cxx
  • Modules/Loadable/Terminologies/Logic/vtkSlicerTerminologiesModuleLogic.cxx
#define RAPIDJSON_WRITE_DEFAULT_FLAGS 2
#define RAPIDJSON_PARSE_DEFAULT_FLAGS 256

This eliminates the ODR violation entirely so all libraries agree on the default flags.

Option C: Both A and B

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.

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