You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 83a6c04
Browse filesBrowse the repository at this point in the historyBrowse files
* Bounds-check substrate IC csv rows before indexing them
get_row_from_substrate_initial_condition_csv indexed data[0..2] and
data[ci + 3] without consulting data.size(). Both reads are out of
bounds for a malformed row:
- substrate_csv_to_vector always emits a final field, so a blank line
parses to a single 0.0 and data[1]/data[2] read past the end.
- substrate_indices is sized from the header's column count, so a row
with fewer columns than the header runs data[ci + 3] off the end.
Reproduced with libc++ bounds checking on a 4-voxel microenvironment: a
trailing blank line, a whitespace-only line, a CRLF blank line, a
2-column row, and a row supplying 1 of 3 header substrates all abort
with "vector[] index out of bounds" before this change and are handled
cleanly after it. Note these reads are not new here -- development
crashes on the same five inputs. Its coverage check ran after the read
loop, so it never guarded them.
Blank lines are now skipped, matching load_cells_csv_v1 and
process_csv_v2_line, and a short row is a hard error rather than a
silent partial write. Ordering the guards ahead of the existing warning
also stops data.size() - 3 from underflowing on a short row.
Also correct that warning: it reported number_of_voxels() where it
means number_of_densities(), and the block comment still described the
one-row-per-voxel, no-header format this branch replaced.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Revive the headerless csv path and require well-formed rows
The headerless branch of load_initial_conditions_from_csv could never
have worked. Two bugs had to be fixed together to see either one:
- "if (i<3) {continue;}" jumped past the "i++" below it, so i never
advanced and substrate_indices came out empty.
- the reopened "std::ifstream file(filename, ...)" shadowed the
enclosing stream, which had just been closed, so the row loop read a
closed stream and processed no rows. -Wshadow flags this.
A headerless csv therefore loaded nothing. On development that at least
failed loudly, because the deleted coverage check saw voxel_set.size()
== 0 and exited; without it the run continued silently on the config
file's uniform initial conditions. Reading the first row's column count
and rewinding the stream fixes both, and the row loop now counts lines
so every diagnostic can name the row it rejected.
Rows are now held to being well formed rather than parsed as far as
they go:
- a field must be empty or a complete finite number. strtod's endptr
was discarded, so "NA" and "1.5abc" silently became 0 and "inf"
became a density.
- a row must have exactly the column count the header (or the first
row) established. Extra columns were silently dropped.
- x, y and z must all be present, so ",,,," no longer resolves to the
origin.
- a position must lie inside the domain, since nearest_voxel_index
clamps and would otherwise snap a typo onto an edge voxel.
- a header may not name the same substrate twice, and must name at
least one.
- an empty file is an error rather than a header sniff on nothing.
An omitted entry now travels as NaN instead of 0, which is what lets the
row loop tell "the user left this blank" from "the user asked for zero".
It still resolves to 0, as before; the distinction only makes the checks
above possible.
The header sniff also no longer indexes line.c_str()[2] and [4] without
knowing they exist -- a first line of "x" read past the end. It splits
the row and compares fields instead, which also makes the sniff
whitespace tolerant.
Verified against real BioFVM on a 2x2x1 microenvironment with libc++
bounds checking: 36 cases covering header/headerless, subsets,
reordering, whitespace, CRLF, blank lines, and every rejection above.
All 36 behave as intended with no out-of-bounds reads. Headerless files
load correctly for the first time.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
std::cout << "ERROR : Column " << bad_field << " of line " << line_number << " of the .csv file specifying BioFVM initial conditions is not a number." << std::endl
1610
+
<< "\tEvery column must hold a finite number, or nothing at all to omit a substrate value." << std::endl
1611
+
<< "\tOffending row: " << line << std::endl;
1612
+
exit(-1);
1613
+
}
1541
1614
1542
-
if (!(warning_issued) && !(header_provided) && (data.size() != (microenvironment.number_of_densities() + 3)))
1615
+
// holding every row to the expected column count is also what keeps every data[...] access below in bounds
1616
+
if (data.size() != substrate_indices.size() + 3)
1543
1617
{
1544
-
std::cout << "WARNING: Wrong number of density values supplied in the .csv file specifying BioFVM initial conditions." << std::endl
<< "\tRemember, save your csv with columns as: x, y, z, substrate_0, substrate_1,...." << std::endl
1548
-
<< "\tThis could also be resolved by including a header row \"x,y,z,[substrate_i0,substrate_i1]\"" << std::endl;
1549
-
warning_issued = true;
1618
+
std::cout << "ERROR : Line " << line_number << " of the .csv file specifying BioFVM initial conditions has the wrong number of columns." << std::endl
1619
+
<< "\tExpected: " << substrate_indices.size() + 3 << " (x, y, z and " << substrate_indices.size() << " substrate value(s))" << std::endl
1620
+
<< "\tFound: " << data.size() << std::endl
1621
+
<< "\tTo omit a value, leave the field empty but keep the comma (e.g. x,y,z,,3.5)." << std::endl
1622
+
<< "\tOffending row: " << line << std::endl;
1623
+
exit(-1);
1624
+
}
1625
+
1626
+
for (unsignedint i = 0; i < 3; i++) // a position cannot be omitted the way a density value can
1627
+
{
1628
+
if (std::isnan(data[i]))
1629
+
{
1630
+
std::cout << "ERROR : Line " << line_number << " of the .csv file specifying BioFVM initial conditions omits an x, y or z coordinate." << std::endl
1631
+
<< "\tOffending row: " << line << std::endl;
1632
+
exit(-1);
1633
+
}
1550
1634
}
1551
1635
1552
1636
std::vector<double> position = {data[0], data[1], data[2]};
1637
+
if (!microenvironment.mesh.is_position_valid(position[0], position[1], position[2])) // otherwise it would silently snap to an edge voxel
1638
+
{
1639
+
std::cout << "ERROR : Line " << line_number << " of the .csv file specifying BioFVM initial conditions lies outside the microenvironment domain." << std::endl
1640
+
<< "\tPosition: " << position << std::endl
1641
+
<< "\tDomain: x in [" << microenvironment.mesh.bounding_box[0] << ", " << microenvironment.mesh.bounding_box[3] << "]"
1642
+
<< ", y in [" << microenvironment.mesh.bounding_box[1] << ", " << microenvironment.mesh.bounding_box[4] << "]"
1643
+
<< ", z in [" << microenvironment.mesh.bounding_box[2] << ", " << microenvironment.mesh.bounding_box[5] << "]" << std::endl;
1644
+
exit(-1);
1645
+
}
1646
+
1553
1647
int voxel_ind = microenvironment.mesh.nearest_voxel_index(position);
1554
1648
if (voxel_is_set[voxel_ind])
1555
1649
{
1556
1650
std::cout << "ERROR : the csv-supplied initial conditions for BioFVM repeat the same voxel. Fix the .csv file and try again." << std::endl
1557
-
<< "\tPosition that was repeated: " << position << std::endl;
1651
+
<< "\tPosition that was repeated: " << position << std::endl
1652
+
<< "\tRepeated on line: " << line_number << std::endl;
1558
1653
exit(-1);
1559
1654
}
1560
1655
voxel_is_set[voxel_ind] = true;
1561
1656
for (unsignedint ci = 0; ci < substrate_indices.size(); ci++) // column index, counting from the first substrate (or just the index of the vector substrate_indices)
0 commit comments