From cb64b5f66dbfb259874c2cc798a5c48ca73559e5 Mon Sep 17 00:00:00 2001 From: Roy Stogner Date: Tue, 21 Apr 2026 12:11:33 -0500 Subject: [PATCH 1/3] Added restrict_int for opt-checked casts We need this for things like file input where we really can't just assume that the file contents are compatible with our data types. --- include/base/libmesh_common.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/include/base/libmesh_common.h b/include/base/libmesh_common.h index 1c7ace587e..d907f4a5fe 100644 --- a/include/base/libmesh_common.h +++ b/include/base/libmesh_common.h @@ -649,6 +649,32 @@ inline Tnew libmesh_cast_int (Told oldvar) return cast_int(oldvar); } + +/** + * restrict_int checks that the value of the castee is within the + * bounds which are exactly representable by the output type, even in + * optimized modes. + * + * Use this cast when you suspect that the input may not succeed in + * correct code (e.g. when an input file is being read from a format + * that may allow wider integer types than the current libMesh + * configuration). + */ +template +inline Tnew restrict_int (Told oldvar) +{ + if constexpr (!std::is_same_v) + { + const Tnew returnval = static_cast(oldvar); + + libmesh_error_msg_if (oldvar != static_cast(returnval), + "restrict_int failed: " << oldvar << " does not fit in type " << typeid(returnval).name()); + } + + return oldvar; +} + + /** * This is a helper variable template for cases when we want to use a default compile-time * error with constexpr-based if conditions. The templating delays the triggering From 70dd9f05f61ffe549c9bdd96e55c675e8a51bac4 Mon Sep 17 00:00:00 2001 From: Roy Stogner Date: Tue, 21 Apr 2026 12:13:07 -0500 Subject: [PATCH 2/3] Use restrict_int where appropriate for file input --- src/mesh/checkpoint_io.C | 2 +- src/mesh/exodusII_io_helper.C | 2 +- src/mesh/gmsh_io.C | 6 +++--- src/mesh/nemesis_io.C | 2 +- src/mesh/nemesis_io_helper.C | 4 ++-- src/mesh/tetgen_io.C | 2 +- src/mesh/ucd_io.C | 2 +- src/mesh/unv_io.C | 4 ++-- src/mesh/xdr_io.C | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/mesh/checkpoint_io.C b/src/mesh/checkpoint_io.C index d25dac6ea5..7e7ef2b2df 100644 --- a/src/mesh/checkpoint_io.C +++ b/src/mesh/checkpoint_io.C @@ -1194,7 +1194,7 @@ void CheckpointIO::read_connectivity (Xdr & io) cast_int (elem_data[2] % mesh.n_processors()); const subdomain_id_type subdomain_id = - cast_int(elem_data[3]); + restrict_int(elem_data[3]); // Old broken files used processsor_id_type(-1)... // But we *know* our first element will be level 0 diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 365c030630..13f74055cf 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -3435,7 +3435,7 @@ void ExodusII_IO_Helper::initialize_element_variables(std::vector n std::set current_set; if (vars_active_subdomains[var_num].empty()) for (auto block_id : block_ids) - current_set.insert(cast_int(block_id)); + current_set.insert(restrict_int(block_id)); else current_set = vars_active_subdomains[var_num]; diff --git a/src/mesh/gmsh_io.C b/src/mesh/gmsh_io.C index 11a0d08c49..9684b4ed00 100644 --- a/src/mesh/gmsh_io.C +++ b/src/mesh/gmsh_io.C @@ -274,12 +274,12 @@ void GmshIO::read_mesh(std::istream & in) // conditions. if (s.find("lower_dimensional_block") != std::string::npos) { - lower_dimensional_blocks.insert(cast_int(phys_id)); + lower_dimensional_blocks.insert(restrict_int(phys_id)); // The user has explicitly told us that this // block is a subdomain, so set that association // in the Mesh. - mesh.subdomain_name(cast_int(phys_id)) = phys_name; + mesh.subdomain_name(restrict_int(phys_id)) = phys_name; } } } @@ -795,7 +795,7 @@ void GmshIO::read_mesh(std::istream & in) // If the physical's dimension matches the largest // dimension we've seen, it's a subdomain name. if (phys_dim == max_elem_dimension_seen) - mesh.subdomain_name(cast_int(phys_id)) = phys_name; + mesh.subdomain_name(restrict_int(phys_id)) = phys_name; // If it's zero-dimensional then it's a nodeset else if (phys_dim == 0) diff --git a/src/mesh/nemesis_io.C b/src/mesh/nemesis_io.C index f05892c357..1c83c38728 100644 --- a/src/mesh/nemesis_io.C +++ b/src/mesh/nemesis_io.C @@ -857,7 +857,7 @@ void Nemesis_IO::read (const std::string & base_filename) // Set subdomain ID based on the block ID. subdomain_id_type subdomain_id = - cast_int(nemhelper->block_ids[i]); + restrict_int(nemhelper->block_ids[i]); // Create a type string (this uses the null-terminated string ctor). const std::string type_str ( nemhelper->elem_type.data() ); diff --git a/src/mesh/nemesis_io_helper.C b/src/mesh/nemesis_io_helper.C index 46400016ee..c116232393 100644 --- a/src/mesh/nemesis_io_helper.C +++ b/src/mesh/nemesis_io_helper.C @@ -2285,7 +2285,7 @@ void Nemesis_IO_Helper::write_elements(const MeshBase & mesh, bool /*use_discont // empty string if there is no name associated with the current // block. names_table.push_back_entry - (mesh.subdomain_name(cast_int(this->global_elem_blk_ids[i]))); + (mesh.subdomain_name(restrict_int(this->global_elem_blk_ids[i]))); // Search for the current global block ID in the map if (const auto it = this->block_id_to_elem_connectivity.find( this->global_elem_blk_ids[i] ); @@ -2661,7 +2661,7 @@ Nemesis_IO_Helper::write_element_values(const MeshBase & mesh, for (const int sbd_id_int : global_elem_blk_ids) { const subdomain_id_type sbd_id = - cast_int(sbd_id_int); + restrict_int(sbd_id_int); auto it = subdomain_map.find(sbd_id); const std::vector empty_vec; const std::vector & elem_ids = diff --git a/src/mesh/tetgen_io.C b/src/mesh/tetgen_io.C index b61169d75a..9010d01a33 100644 --- a/src/mesh/tetgen_io.C +++ b/src/mesh/tetgen_io.C @@ -257,7 +257,7 @@ void TetGenIO::element_in (std::istream & ele_stream) // Make sure that the id we read can be successfully cast to // an integral value of type subdomain_id_type. - elem->subdomain_id() = cast_int(region); + elem->subdomain_id() = restrict_int(region); } } } diff --git a/src/mesh/ucd_io.C b/src/mesh/ucd_io.C index e6f6cd565c..b0b6feb745 100644 --- a/src/mesh/ucd_io.C +++ b/src/mesh/ucd_io.C @@ -221,7 +221,7 @@ void UCDIO::read_implementation (std::istream & in) elems_of_dimension[elem->dim()] = true; // Set the element's subdomain ID based on the material_id. - elem->subdomain_id() = cast_int(material_id); + elem->subdomain_id() = restrict_int(material_id); // Add the element to the mesh elem->set_id(i); diff --git a/src/mesh/unv_io.C b/src/mesh/unv_io.C index 0d333b2daf..4f4344502a 100644 --- a/src/mesh/unv_io.C +++ b/src/mesh/unv_io.C @@ -519,7 +519,7 @@ void UNVIO::groups_in (std::istream & in_file) // Set the current group number as the lower-dimensional element's subdomain ID. // We will use this later to set a boundary ID. group_elem->subdomain_id() = - cast_int(group_number); + restrict_int(group_number); // Store the lower-dimensional element in the provide_bcs container. provide_bcs.emplace(group_elem->key(), group_elem); @@ -530,7 +530,7 @@ void UNVIO::groups_in (std::istream & in_file) { is_subdomain_group = true; group_elem->subdomain_id() = - cast_int(group_number); + restrict_int(group_number); } else diff --git a/src/mesh/xdr_io.C b/src/mesh/xdr_io.C index 862313e844..097d7c2f46 100644 --- a/src/mesh/xdr_io.C +++ b/src/mesh/xdr_io.C @@ -1897,7 +1897,7 @@ XdrIO::read_serialized_connectivity (Xdr & io, cast_int(*it++); const subdomain_id_type subdomain_id = - cast_int(*it++); + restrict_int(*it++); tmp = *it++; #ifdef LIBMESH_ENABLE_AMR From 23b01011b275365a26dc2d12cfae69660515cb05 Mon Sep 17 00:00:00 2001 From: Roy Stogner Date: Tue, 21 Apr 2026 12:13:34 -0500 Subject: [PATCH 3/3] Refactor Exodus subdomain_id reads, add checking --- src/mesh/exodusII_io.C | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index cc73e98508..4bf7bcf164 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -426,7 +426,7 @@ void ExodusII_IO::read (const std::string & fname) // We'll set any spline NodeElem subdomain_id() values to exceed the // maximum of subdomain_id() values set via Exodus block ids. - int max_subdomain_id = std::numeric_limits::min(); + subdomain_id_type max_subdomain_id = std::numeric_limits::min(); // We've already added all the nodes explicitly specified in the // file, but if we have spline nodes we may need to add assembly @@ -440,13 +440,14 @@ void ExodusII_IO::read (const std::string & fname) { // Read the information for block i exio_helper->read_elem_in_block (i); - const int subdomain_id = exio_helper->get_block_id(i); + const subdomain_id_type subdomain_id = + restrict_int(exio_helper->get_block_id(i)); max_subdomain_id = std::max(max_subdomain_id, subdomain_id); // populate the map of names std::string subdomain_name = exio_helper->get_block_name(i); if (!subdomain_name.empty()) - mesh.subdomain_name(static_cast(subdomain_id)) = subdomain_name; + mesh.subdomain_name(subdomain_id) = subdomain_name; // Set any relevant node/edge maps for this element const std::string type_str (exio_helper->get_elem_type()); @@ -473,7 +474,7 @@ void ExodusII_IO::read (const std::string & fname) << " has " << uelem->n_nodes() << " nodes."); // Assign the current subdomain to this Elem - uelem->subdomain_id() = static_cast(subdomain_id); + uelem->subdomain_id() = subdomain_id; // Determine the libmesh elem id implied by "j". The // ExodusII_IO_Helper::get_libmesh_elem_id() helper function