GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 86.7% 104 / 0 / 120
Functions: 100.0% 2 / 0 / 2
Branches: 48.8% 122 / 0 / 250

src/G4OCCTMaterialMapReader.cc
Line Branch Exec Source
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 // Copyright (C) 2026 G4OCCT Contributors
3
4 /// @file G4OCCTMaterialMapReader.cc
5 /// @brief Implementation of G4OCCTMaterialMapReader.
6
7 #include "G4OCCT/G4OCCTMaterialMapReader.hh"
8
9 // Xerces — provided transitively by Geant4::G4gdml.
10 // G4GDMLRead.hh already includes the Xerces DOM headers; we repeat them here
11 // for clarity and to document the direct dependency.
12 #include <xercesc/dom/DOM.hpp>
13 #include <xercesc/parsers/XercesDOMParser.hpp>
14 #include <xercesc/util/PlatformUtils.hpp>
15
16 // Geant4
17 #include <G4Exception.hh>
18 #include <G4Material.hh>
19 #include <G4NistManager.hh>
20
21 15 G4OCCTMaterialMap G4OCCTMaterialMapReader::ReadFile(const G4String& path) {
22 // Xerces initialization is ref-counted; safe to call multiple times.
23 15 xercesc::XMLPlatformUtils::Initialize();
24
25 15 G4OCCTMaterialMap result;
26
27 // Pre-parse fatal errors (MatReader001–005) are saved here and reported
28 // after the parser is destroyed and Terminate() has been called. Calling
29 // Terminate() inside the parser's {} scope and then returning through the
30 // scope exit would invoke XercesDOMParser::~XercesDOMParser() after
31 // Terminate() — a SEGFAULT if a non-aborting G4VExceptionHandler allows
32 // execution to continue past G4Exception().
33 15 G4String preFatalCode;
34 15 G4String preFatalMsg;
35
36 // IIFE: contains the XercesDOMParser so it is destroyed before Terminate().
37 // Early 'return' from the lambda exits the lambda scope cleanly, ensuring
38 // the parser destructor runs before Terminate() is called below.
39
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
30 [&]() {
40 15 xercesc::XercesDOMParser parser;
41
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 parser.setDoNamespaces(false);
42
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 parser.setDoSchema(false);
43
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 parser.setCreateEntityReferenceNodes(false);
44
45 try {
46
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 parser.parse(path.c_str());
47
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
15 } catch (const xercesc::XMLException& e) {
48 ✗ preFatalCode = "G4OCCT_MatReader001";
49 ✗ preFatalMsg = "XML error parsing '" + path + "': " + Transcode(e.getMessage());
50 return;
51 ✗ } catch (const xercesc::DOMException& e) {
52 ✗ preFatalCode = "G4OCCT_MatReader002";
53 ✗ preFatalMsg = "DOM error parsing '" + path + "': " + Transcode(e.getMessage());
54 return;
55 ✗ }
56
57
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 const xercesc::DOMDocument* const doc = parser.getDocument();
58
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 14 times.
15 if (!doc) {
59
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 preFatalCode = "G4OCCT_MatReader003";
60
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 preFatalMsg = "Cannot open document: " + path;
61 1 return;
62 }
63
64
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 const xercesc::DOMElement* const root = doc->getDocumentElement();
65
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 12 times.
14 if (!root) {
66
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 preFatalCode = "G4OCCT_MatReader004";
67
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 preFatalMsg = "Empty document: " + path;
68 2 return;
69 }
70
71
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 const G4String rootTag = Transcode(root->getTagName());
72
3/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 11 times.
12 if (rootTag != "materials") {
73
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 preFatalCode = "G4OCCT_MatReader005";
74
4/8
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
1 preFatalMsg = "Root element must be <materials>, got <" + rootTag + "> in '" + path + "'";
75 1 return;
76 }
77
78 // ── Pass 1: isotopes and elements ──────────────────────────────────────
79 // Must precede materials so that <fraction ref="Si"/> can resolve "Si".
80
3/4
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 59 times.
70 for (xercesc::DOMNode* node = root->getFirstChild(); node != nullptr;
81
1/2
✓ Branch 0 taken 59 times.
✗ Branch 1 not taken.
59 node = node->getNextSibling()) {
82
3/4
✓ Branch 0 taken 59 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 40 times.
✓ Branch 3 taken 19 times.
59 if (node->getNodeType() != xercesc::DOMNode::ELEMENT_NODE) {
83 40 continue;
84 }
85
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 const auto* child = dynamic_cast<const xercesc::DOMElement*>(node);
86
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 if (!child) {
87 ✗ continue;
88 }
89
2/4
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✗ Branch 3 not taken.
19 const G4String tag = Transcode(child->getTagName());
90
3/4
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 18 times.
19 if (tag == "isotope") {
91
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 IsotopeRead(child);
92
3/4
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 17 times.
19 } else if (tag == "element") {
93
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 ElementRead(child);
94 1 }
95 19 }
96
97 // ── Pass 2: materials ──────────────────────────────────────────────────
98
3/4
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 59 times.
70 for (xercesc::DOMNode* node = root->getFirstChild(); node != nullptr;
99
1/2
✓ Branch 0 taken 59 times.
✗ Branch 1 not taken.
59 node = node->getNextSibling()) {
100
3/4
✓ Branch 0 taken 59 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 40 times.
✓ Branch 3 taken 19 times.
59 if (node->getNodeType() != xercesc::DOMNode::ELEMENT_NODE) {
101 40 continue;
102 }
103
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
19 const auto* child = dynamic_cast<const xercesc::DOMElement*>(node);
104
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
19 if (!child) {
105 ✗ continue;
106 }
107
5/8
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 19 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 17 times.
19 if (Transcode(child->getTagName()) != "material") {
108 2 continue;
109 }
110
111 // Extract our custom attributes and the GDML 'name' attribute.
112 17 G4String stepName;
113 17 G4String geant4Name;
114 17 G4String gdmlName;
115
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
17 const xercesc::DOMNamedNodeMap* attrs = child->getAttributes();
116
3/4
✓ Branch 0 taken 55 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
✓ Branch 3 taken 17 times.
55 for (XMLSize_t i = 0; i < attrs->getLength(); ++i) {
117
2/4
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
✗ Branch 3 not taken.
38 const auto* attr = dynamic_cast<const xercesc::DOMAttr*>(attrs->item(i));
118
1/2
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
38 if (!attr) {
119 ✗ continue;
120 }
121
2/4
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
✗ Branch 3 not taken.
38 const G4String aName = Transcode(attr->getName());
122
2/4
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
✗ Branch 3 not taken.
38 const G4String aValue = Transcode(attr->getValue());
123
3/4
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 22 times.
38 if (aName == "stepName") {
124
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 stepName = aValue;
125
3/4
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 11 times.
38 } else if (aName == "geant4Name") {
126
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 geant4Name = aValue;
127
3/4
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 6 times.
22 } else if (aName == "name") {
128
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 gdmlName = aValue;
129 5 }
130 38 }
131
132
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 16 times.
17 if (stepName.empty()) {
133
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 G4Exception("G4OCCTMaterialMapReader::ReadFile", "G4OCCT_MatReader006", FatalException,
134
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
1 ("<material> element in '" + path +
135 "' is missing the required 'stepName' "
136 "attribute.")
137 1 .c_str());
138 1 continue;
139 }
140
141 16 G4Material* mat = nullptr;
142
143
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 6 times.
16 if (!geant4Name.empty()) {
144 // ── Type 1: NIST alias ────────────────────────────────────────────
145
2/4
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
10 mat = G4NistManager::Instance()->FindOrBuildMaterial(geant4Name);
146
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 9 times.
10 if (!mat) {
147
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 G4Exception("G4OCCTMaterialMapReader::ReadFile", "G4OCCT_MatReader007", FatalException,
148
4/8
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 time.
1 ("NIST material '" + geant4Name + "' not found (stepName='" + stepName +
149 "'). "
150 "Check the spelling against the Geant4 NIST material database.")
151 1 .c_str());
152 1 continue;
153 }
154 9 } else {
155 // ── Type 2: Inline GDML material definition ───────────────────────
156
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 5 times.
6 if (gdmlName.empty()) {
157
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 G4Exception("G4OCCTMaterialMapReader::ReadFile", "G4OCCT_MatReader008", FatalException,
158
4/8
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 time.
1 ("Inline <material stepName=\"" + stepName + "\"> in '" + path +
159 "' requires a 'name' attribute (used as the GDML material registry key).")
160 1 .c_str());
161 1 continue;
162 }
163 // Reuse the material if it was already registered in the global
164 // G4 material table (e.g. by a preceding G4GDMLParser::Read call
165 // on the reference geometry). Creating a duplicate G4Material
166 // with the same name is a fatal error in Geant4.
167 //
168 // G4GDMLParser::Read() appends "0x<ptr>" suffixes (GenerateName)
169 // and then strips them back to plain names via StripNames(). Check
170 // both the generated name (pre-strip) and the plain name (post-strip)
171 // so that inline materials already registered under either convention
172 // are reused without re-creation.
173
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
5 mat = G4Material::GetMaterial(GenerateName(gdmlName), /*warning=*/false);
174
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 time.
5 if (mat == nullptr) {
175
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 mat = G4Material::GetMaterial(gdmlName, /*warning=*/false);
176 4 }
177
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 time.
5 if (mat == nullptr) {
178 // Material not yet registered — delegate full GDML parsing to
179 // the inherited method, which creates the G4Material.
180
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 MaterialRead(child);
181
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 mat = GetMaterial(GenerateName(gdmlName));
182
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (mat == nullptr) {
183 ✗ mat = G4Material::GetMaterial(gdmlName, /*warning=*/false);
184 ✗ }
185 4 }
186
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (!mat) {
187 ✗ G4Exception("G4OCCTMaterialMapReader::ReadFile", "G4OCCT_MatReader009", FatalException,
188 ✗ ("Failed to create inline material '" + gdmlName + "' (stepName='" +
189 ✗ stepName + "') in '" + path +
190 "'. Check that density and composition are valid.")
191 ✗ .c_str());
192 ✗ continue;
193 }
194 }
195
196
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 result.Add(stepName, mat);
197 17 }
198 15 }(); // XercesDOMParser destroyed here
199
200
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 xercesc::XMLPlatformUtils::Terminate();
201
202
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 11 times.
15 if (!preFatalCode.empty()) {
203
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
8 G4Exception("G4OCCTMaterialMapReader::ReadFile", preFatalCode.c_str(), FatalException,
204 4 preFatalMsg.c_str());
205 // Normally unreachable: FatalException aborts. If a non-aborting handler
206 // is installed (e.g. G4OCCTFatalCatcher in tests), return an empty map so
207 // callers get a predictable, safe value.
208 4 return result;
209 }
210
211 11 return result;
212 15 }
213