Skip to content

Commit 23174b9

Browse files
andyclaude
andcommitted
Fix PropTSVecTable out-of-bounds from non-uniform geometry traces (#27)
Root cause is on the parser side. gaussian-*.desc routes THREE parse types into one property: [GEOMTRACE] from "Input orientation:", another [GEOMTRACE] from "Z-Matrix orientation:", and [GEOMTRACE1] from "Standard orientation:". All three run gaussian-*.geomtrace, which hardcodes 'key: GEOMTRACE' regardless of which parse type invoked it, and the stored key comes from that line, not the parse type -- so all three land in GEOMTRACE. (There is no GEOMTRACE1 property at all.) Those blocks do not have the same atom count. Gaussian prints Z-matrix dummy centres (atomic number -1) in "Z-Matrix orientation" but omits them from "Standard orientation". Verified on a real g16 NH3 Z-matrix optimization with one dummy centre: the same job alternates 5-row and 4-row steps into one PropTSVecTable. PropTSVecTable then takes its declared shape from the FIRST step only (DavPropCache::putTSProp writes rows/columns just once, on document creation; later steps append bare <step> children), and value() checked row/col against that declared shape without ever reconciling it against the actual vector length. With p_numRows=5/p_numColumns=3 from a 5-row first step, value(1,4,2) computes index 4*3+2=14 into a 12-element vector -- reading 2 elements past the end. Reproduced as a real AddressSanitizer heap-buffer-overflow. A prior commit (f8e3be6) attributed #27 to PropTSVecVector::value()'s inverted bounds check. That was a different, real bug; the accessor named in this issue's title was not in its list, so #27 was still live. Fixed on both sides: Parser (the root cause) -- skip dummy centres (atomic number < 0) and renumber row labels sequentially, in all 8 gaussian-*.geomtrace{,-input} scripts. Ghost/Bq centres (atomic number 0) are deliberately kept, since they appear consistently in every orientation block. This also fixes an independent silent-corruption bug: a dummy can sit anywhere in the centre list, so every atom after it was given the WRONG atom's coordinates. On the NH3 fixture the old output was "1-N 2-Uns 3-H 4-H 5-H" -- a phantom atom (the "Uns" label is $atomicSymbol[-1], Perl's negative index landing on the last element of the periodic table) with the real hydrogens shifted down a row. Any Z-matrix job with dummy atoms had a silently wrong geometry trace, independent of the crash. Storage (the backstop) -- across PropTSVecTable, PropVecTable, PropTSTable and PropTSVecVector: value() now bounds-checks the computed index against the actual vector size, not just the declared shape; values(int) bounds-checks its table index (it was unchecked); setValues() drops steps whose size disagrees with rows*columns and no longer leaks the previous p_values; and appendTable() refuses malformed tables instead of warning and pushing them anyway. EE_FATAL (exit(1)) in these paths is downgraded to a warning plus refusal -- malformed parser output killed the entire GUI rather than dropping one step, and PropertyTask::updatePropTSVecTable already drops mismatched steps silently, so refusing is the consistent behaviour. Both sides were needed: hardening the accessor alone would leave the trace silently wrong (dummy coordinates on real atoms), and fixing the parser alone would leave every other code able to produce the same over-read through an unvalidated setValues(). src/tdat/test/PropTSVecTableIssue27Test.C is a standalone reproduction (not wired into CMake, matching the src/dsm/dav/test convention); build instructions are in its header. Pre-fix it trips ASan, post-fix it is clean. Verified: ninja builder and a full ninja are clean. Old vs new parser on the real fixture gives 5-row/4-row steps before and uniform 4-row steps after. NOT verified: no live GUI this session, so the end-to-end path (dummy- atom job through ECCE, geometry trace in Builder) is untested. The original reporter's G09 file is unavailable, so their specific trigger is unconfirmed -- see the follow-up issue on VIB being cast to PropTSVecTable*, which reports the same assertion location. Per this bug family's usual caveat, already-parsed jobs are not retroactively fixed; their stored steps are now dropped with a warning rather than over-read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 4cb500b commit 23174b9

13 files changed

Lines changed: 375 additions & 33 deletions

‎scripts/parsers/gaussian-03.geomtrace‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,27 @@ $| = 1;
4040
s/D/e/g; # Use "e" in exponentials
4141
($atomIndex,$atomicNumber,$atomicType,$xcoord,$ycoord,$zcoord) =
4242
split(/ +/,$_);
43+
# Skip Gaussian dummy centres (atomic number -1, i.e. an "X" in a
44+
# Z-matrix). They are printed in the "Z-Matrix orientation" block but
45+
# NOT in the "Input orientation"/"Standard orientation" blocks, and all
46+
# three blocks are routed into the single GEOMTRACE property key by the
47+
# gaussian-*.desc files (this script hardcodes 'key: GEOMTRACE'
48+
# regardless of which parse type invoked it). Including them therefore
49+
# produced a PropTSVecTable whose steps have different row counts --
50+
# GitHub issue #27 -- and, because a dummy can sit anywhere in the
51+
# centre list, shifted every following atom's coordinates onto the
52+
# wrong atom in the geometry trace. ECCE's chemical system has no
53+
# dummy atoms, so dropping them here makes each step line up 1:1 with
54+
# the fragment. Ghost/Bq centres (atomic number 0) are kept: they
55+
# appear consistently in every orientation block.
56+
next if ($atomicNumber < 0);
4357
$line = join(" ",($xcoord,$ycoord,$zcoord));
4458
# $line = join(" ",($atomicSymbol[$atomicNumber],$xcoord,$ycoord,$zcoord));
4559
push(@coordlines,$line);
46-
$line = join("-",$atomIndex,($atomicSymbol[$atomicNumber]));
60+
# Renumber sequentially: with dummy centres skipped, Gaussian's own
61+
# centre numbers no longer match the row index (or ECCE's atom
62+
# numbering), which is all this label is for.
63+
$line = join("-",++$rowIndex,($atomicSymbol[$atomicNumber]));
4764
$rowLabels .= " $line";
4865
} else {
4966
last;

‎scripts/parsers/gaussian-03.geomtrace-input‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,27 @@ $| = 1;
4040
s/D/e/g; # Use "e" in exponentials
4141
($atomIndex,$atomicNumber,$atomicType,$xcoord,$ycoord,$zcoord) =
4242
split(/ +/,$_);
43+
# Skip Gaussian dummy centres (atomic number -1, i.e. an "X" in a
44+
# Z-matrix). They are printed in the "Z-Matrix orientation" block but
45+
# NOT in the "Input orientation"/"Standard orientation" blocks, and all
46+
# three blocks are routed into the single GEOMTRACE property key by the
47+
# gaussian-*.desc files (this script hardcodes 'key: GEOMTRACE'
48+
# regardless of which parse type invoked it). Including them therefore
49+
# produced a PropTSVecTable whose steps have different row counts --
50+
# GitHub issue #27 -- and, because a dummy can sit anywhere in the
51+
# centre list, shifted every following atom's coordinates onto the
52+
# wrong atom in the geometry trace. ECCE's chemical system has no
53+
# dummy atoms, so dropping them here makes each step line up 1:1 with
54+
# the fragment. Ghost/Bq centres (atomic number 0) are kept: they
55+
# appear consistently in every orientation block.
56+
next if ($atomicNumber < 0);
4357
$line = join(" ",($xcoord,$ycoord,$zcoord));
4458
# $line = join(" ",($atomicSymbol[$atomicNumber],$xcoord,$ycoord,$zcoord));
4559
push(@coordlines,$line);
46-
$line = join("-",$atomIndex,($atomicSymbol[$atomicNumber]));
60+
# Renumber sequentially: with dummy centres skipped, Gaussian's own
61+
# centre numbers no longer match the row index (or ECCE's atom
62+
# numbering), which is all this label is for.
63+
$line = join("-",++$rowIndex,($atomicSymbol[$atomicNumber]));
4764
$rowLabels .= " $line";
4865
} else {
4966
last;

‎scripts/parsers/gaussian-09.geomtrace‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,27 @@ $| = 1;
4040
s/D/e/g; # Use "e" in exponentials
4141
($atomIndex,$atomicNumber,$atomicType,$xcoord,$ycoord,$zcoord) =
4242
split(/ +/,$_);
43+
# Skip Gaussian dummy centres (atomic number -1, i.e. an "X" in a
44+
# Z-matrix). They are printed in the "Z-Matrix orientation" block but
45+
# NOT in the "Input orientation"/"Standard orientation" blocks, and all
46+
# three blocks are routed into the single GEOMTRACE property key by the
47+
# gaussian-*.desc files (this script hardcodes 'key: GEOMTRACE'
48+
# regardless of which parse type invoked it). Including them therefore
49+
# produced a PropTSVecTable whose steps have different row counts --
50+
# GitHub issue #27 -- and, because a dummy can sit anywhere in the
51+
# centre list, shifted every following atom's coordinates onto the
52+
# wrong atom in the geometry trace. ECCE's chemical system has no
53+
# dummy atoms, so dropping them here makes each step line up 1:1 with
54+
# the fragment. Ghost/Bq centres (atomic number 0) are kept: they
55+
# appear consistently in every orientation block.
56+
next if ($atomicNumber < 0);
4357
$line = join(" ",($xcoord,$ycoord,$zcoord));
4458
# $line = join(" ",($atomicSymbol[$atomicNumber],$xcoord,$ycoord,$zcoord));
4559
push(@coordlines,$line);
46-
$line = join("-",$atomIndex,($atomicSymbol[$atomicNumber]));
60+
# Renumber sequentially: with dummy centres skipped, Gaussian's own
61+
# centre numbers no longer match the row index (or ECCE's atom
62+
# numbering), which is all this label is for.
63+
$line = join("-",++$rowIndex,($atomicSymbol[$atomicNumber]));
4764
$rowLabels .= " $line";
4865
} else {
4966
last;

‎scripts/parsers/gaussian-09.geomtrace-input‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,27 @@ $| = 1;
4040
s/D/e/g; # Use "e" in exponentials
4141
($atomIndex,$atomicNumber,$atomicType,$xcoord,$ycoord,$zcoord) =
4242
split(/ +/,$_);
43+
# Skip Gaussian dummy centres (atomic number -1, i.e. an "X" in a
44+
# Z-matrix). They are printed in the "Z-Matrix orientation" block but
45+
# NOT in the "Input orientation"/"Standard orientation" blocks, and all
46+
# three blocks are routed into the single GEOMTRACE property key by the
47+
# gaussian-*.desc files (this script hardcodes 'key: GEOMTRACE'
48+
# regardless of which parse type invoked it). Including them therefore
49+
# produced a PropTSVecTable whose steps have different row counts --
50+
# GitHub issue #27 -- and, because a dummy can sit anywhere in the
51+
# centre list, shifted every following atom's coordinates onto the
52+
# wrong atom in the geometry trace. ECCE's chemical system has no
53+
# dummy atoms, so dropping them here makes each step line up 1:1 with
54+
# the fragment. Ghost/Bq centres (atomic number 0) are kept: they
55+
# appear consistently in every orientation block.
56+
next if ($atomicNumber < 0);
4357
$line = join(" ",($xcoord,$ycoord,$zcoord));
4458
# $line = join(" ",($atomicSymbol[$atomicNumber],$xcoord,$ycoord,$zcoord));
4559
push(@coordlines,$line);
46-
$line = join("-",$atomIndex,($atomicSymbol[$atomicNumber]));
60+
# Renumber sequentially: with dummy centres skipped, Gaussian's own
61+
# centre numbers no longer match the row index (or ECCE's atom
62+
# numbering), which is all this label is for.
63+
$line = join("-",++$rowIndex,($atomicSymbol[$atomicNumber]));
4764
$rowLabels .= " $line";
4865
} else {
4966
last;

‎scripts/parsers/gaussian-16.geomtrace‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,27 @@ $| = 1;
4040
s/D/e/g; # Use "e" in exponentials
4141
($atomIndex,$atomicNumber,$atomicType,$xcoord,$ycoord,$zcoord) =
4242
split(/ +/,$_);
43+
# Skip Gaussian dummy centres (atomic number -1, i.e. an "X" in a
44+
# Z-matrix). They are printed in the "Z-Matrix orientation" block but
45+
# NOT in the "Input orientation"/"Standard orientation" blocks, and all
46+
# three blocks are routed into the single GEOMTRACE property key by the
47+
# gaussian-*.desc files (this script hardcodes 'key: GEOMTRACE'
48+
# regardless of which parse type invoked it). Including them therefore
49+
# produced a PropTSVecTable whose steps have different row counts --
50+
# GitHub issue #27 -- and, because a dummy can sit anywhere in the
51+
# centre list, shifted every following atom's coordinates onto the
52+
# wrong atom in the geometry trace. ECCE's chemical system has no
53+
# dummy atoms, so dropping them here makes each step line up 1:1 with
54+
# the fragment. Ghost/Bq centres (atomic number 0) are kept: they
55+
# appear consistently in every orientation block.
56+
next if ($atomicNumber < 0);
4357
$line = join(" ",($xcoord,$ycoord,$zcoord));
4458
# $line = join(" ",($atomicSymbol[$atomicNumber],$xcoord,$ycoord,$zcoord));
4559
push(@coordlines,$line);
46-
$line = join("-",$atomIndex,($atomicSymbol[$atomicNumber]));
60+
# Renumber sequentially: with dummy centres skipped, Gaussian's own
61+
# centre numbers no longer match the row index (or ECCE's atom
62+
# numbering), which is all this label is for.
63+
$line = join("-",++$rowIndex,($atomicSymbol[$atomicNumber]));
4764
$rowLabels .= " $line";
4865
} else {
4966
last;

‎scripts/parsers/gaussian-16.geomtrace-input‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,27 @@ $| = 1;
4040
s/D/e/g; # Use "e" in exponentials
4141
($atomIndex,$atomicNumber,$atomicType,$xcoord,$ycoord,$zcoord) =
4242
split(/ +/,$_);
43+
# Skip Gaussian dummy centres (atomic number -1, i.e. an "X" in a
44+
# Z-matrix). They are printed in the "Z-Matrix orientation" block but
45+
# NOT in the "Input orientation"/"Standard orientation" blocks, and all
46+
# three blocks are routed into the single GEOMTRACE property key by the
47+
# gaussian-*.desc files (this script hardcodes 'key: GEOMTRACE'
48+
# regardless of which parse type invoked it). Including them therefore
49+
# produced a PropTSVecTable whose steps have different row counts --
50+
# GitHub issue #27 -- and, because a dummy can sit anywhere in the
51+
# centre list, shifted every following atom's coordinates onto the
52+
# wrong atom in the geometry trace. ECCE's chemical system has no
53+
# dummy atoms, so dropping them here makes each step line up 1:1 with
54+
# the fragment. Ghost/Bq centres (atomic number 0) are kept: they
55+
# appear consistently in every orientation block.
56+
next if ($atomicNumber < 0);
4357
$line = join(" ",($xcoord,$ycoord,$zcoord));
4458
# $line = join(" ",($atomicSymbol[$atomicNumber],$xcoord,$ycoord,$zcoord));
4559
push(@coordlines,$line);
46-
$line = join("-",$atomIndex,($atomicSymbol[$atomicNumber]));
60+
# Renumber sequentially: with dummy centres skipped, Gaussian's own
61+
# centre numbers no longer match the row index (or ECCE's atom
62+
# numbering), which is all this label is for.
63+
$line = join("-",++$rowIndex,($atomicSymbol[$atomicNumber]));
4764
$rowLabels .= " $line";
4865
} else {
4966
last;

‎scripts/parsers/gaussian-94.geomtrace‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,16 @@ while (<STDIN>) {
3939
s/^\s*//; # Get rid of leading blanks
4040
s/D/e/g; # Use "e" in exponentials
4141
($atomIndex,$atomicNumber,$xcoord,$ycoord,$zcoord) = split(/ +/,$_);
42+
# Skip Gaussian dummy centres (atomic number -1) -- see GitHub issue
43+
# #27 and the identical guard in gaussian-{03,09,16}.geomtrace.
44+
next if ($atomicNumber < 0);
4245
$line = join(" ",($xcoord,$ycoord,$zcoord));
4346
# $line = join(" ",($atomicSymbol[$atomicNumber],$xcoord,$ycoord,$zcoord));
4447
push(@coordlines,$line);
45-
$line = join("-",$atomIndex,($atomicSymbol[$atomicNumber]));
48+
# Renumber sequentially: with dummy centres skipped, Gaussian's own
49+
# centre numbers no longer match the row index (or ECCE's atom
50+
# numbering), which is all this label is for.
51+
$line = join("-",++$rowIndex,($atomicSymbol[$atomicNumber]));
4652
$rowLabels .= " $line";
4753
} else {
4854
last;

‎scripts/parsers/gaussian-94.geomtrace-input‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,16 @@ $| = 1;
3939
s/^\s*//; # Get rid of leading blanks
4040
s/D/e/g; # Use "e" in exponentials
4141
($atomIndex,$atomicNumber,$xcoord,$ycoord,$zcoord) = split(/ +/,$_);
42+
# Skip Gaussian dummy centres (atomic number -1) -- see GitHub issue
43+
# #27 and the identical guard in gaussian-{03,09,16}.geomtrace.
44+
next if ($atomicNumber < 0);
4245
$line = join(" ",($xcoord,$ycoord,$zcoord));
4346
# $line = join(" ",($atomicSymbol[$atomicNumber],$xcoord,$ycoord,$zcoord));
4447
push(@coordlines,$line);
45-
$line = join("-",$atomIndex,($atomicSymbol[$atomicNumber]));
48+
# Renumber sequentially: with dummy centres skipped, Gaussian's own
49+
# centre numbers no longer match the row index (or ECCE's atom
50+
# numbering), which is all this label is for.
51+
$line = join("-",++$rowIndex,($atomicSymbol[$atomicNumber]));
4652
$rowLabels .= " $line";
4753
} else {
4854
last;

‎src/tdat/properties/PropTSTable.C‎

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ double PropTSTable::value(int table, int row, int col) const
106106
// Return the value for the given table/row/column index
107107
// Assumes index is within bounds of the vectors
108108

109-
if (table >= p_values->size() || row >= p_numRows || col >= p_numColumns
109+
if (table >= (int)p_values->size() || row >= p_numRows || col >= p_numColumns
110110
|| table < 0 || row < 0 || col < 0) {
111111
// Same fall-through-after-warning bug shape fixed in PropTable::value()
112112
// -- EE_WARNING doesn't stop execution, so return early instead of
@@ -117,13 +117,29 @@ double PropTSTable::value(int table, int row, int col) const
117117
}
118118

119119
int index = row * p_numColumns + col;
120+
// p_numRows/p_numColumns describe the declared shape; setValues() accepts
121+
// whatever the stored document contains, so an individual table can be
122+
// shorter than rows*columns (see GitHub issue #27 and PropTSVecTable.C).
123+
// Check the real vector size too - otherwise this is a silent over-read.
124+
if (index >= (int)(*p_values)[table].size()) {
125+
EE_RT_ASSERT(false, EE_WARNING,
126+
"trying to access out-of-bounds index in PropTSTable (this table is "
127+
"shorter than the declared rows*columns)");
128+
return 0.0;
129+
}
120130
return (*p_values)[table][index];
121131
}
122132

123133
/////////////////////////////////////////
124134

125135
const vector<double>& PropTSTable::values(int table) const
126136
{
137+
static const vector<double> empty;
138+
if (p_values == 0 || table < 0 || table >= (int)p_values->size()) {
139+
EE_RT_ASSERT(false, EE_WARNING,
140+
"trying to access out-of-bounds table in PropTSTable");
141+
return empty;
142+
}
127143
return (*p_values)[table]; // return values for one table
128144
}
129145

@@ -261,16 +277,23 @@ void PropTSTable::appendTable(int rows, int cols,
261277
}
262278

263279
// make sure input vector matches its specified size
264-
if (values.size() != rows * cols)
265-
EE_RT_ASSERT(false, EE_FATAL,
266-
"input vector length does not match specified size");
280+
if (values.size() != (size_t)(rows * cols)) {
281+
// Was EE_FATAL (exit(1)): malformed parser output killed the whole
282+
// application instead of dropping one table. Refuse the append instead.
283+
EE_RT_ASSERT(false, EE_WARNING,
284+
"input vector length does not match specified size - "
285+
"table not appended");
286+
return;
287+
}
267288

268289
// make sure the table being appended is the same size as the rest
269290
// of the tables in the vector
270-
if (rows != p_numRows || cols != p_numColumns)
271-
EE_RT_ASSERT(false, EE_FATAL,
291+
if (rows != p_numRows || cols != p_numColumns) {
292+
EE_RT_ASSERT(false, EE_WARNING,
272293
"trying to append a table that is not the same size as "
273-
"the other tables in the vector");
294+
"the other tables in the vector - table not appended");
295+
return;
296+
}
274297

275298
p_values->push_back(values);
276299
}

0 commit comments

Comments
 (0)