ifcparse: store integer attribute values as int64_t to allow out-of-range timestamps

Setting an IfcInteger/IfcTimeStamp typed attribute (e.g. IfcOwnerHistory.CreationDate)
outside the signed 32-bit range corrupted the value instead of raising, since the
Python wrapper's set_attribute_value_py() truncated it with a plain static_cast<int>
before handing it to the C++ storage. Unix timestamps before 1901-12-13 or after
2038-01-19 silently wrapped around (e.g. 3000000000 became -1294967296) rather than
being rejected or stored correctly. Fixes #3058, equivalent to PR #8683 but ported to
this branch's rewritten ifcparse (snake_case files, variant_array/instance_data
storage, SWIG PyObject-based attribute setter) instead of the old IfcEntityInstanceData
sources, which no longer exist here.

The scalar slot of the attribute variant (Argument_INT) becomes int64_t. Integer
aggregates (Argument_AGGREGATE_OF_INT, e.g. CoordIndex) and instance/reference
identifiers stay 32-bit, since neither is the value that overflows here; this narrow
scope is kept on its own technical merits (aggregates and identifiers were never the
source of the bug, and widening them would be a much larger, riskier change for no
benefit) even though aothms said compatibility isn't a concern on this v0.9-track
branch. express::Base::set_attribute_value promotes the schema-generated int to
int64_t at a single choke point, so the generated setters keep compiling unchanged.
The STEP lexer, writer, and SWIG wrapper (set_attribute_value_py, pythonize) are all
widened together, since widening only the Python-facing setter would have silently
wrapped the value on file write instead of raising.

Verified in a build (IFC2X3 and IFC4, BUILD_IFCGEOM off, no kernels): pre-1901,
post-2038, both 32-bit boundaries, and a 9e12 value all round trip exactly both in
memory and through STEP text serialization (write then reopen). A value outside the
64-bit range now raises a clean exception instead of corrupting data. Ordinary
in-range integers and integer aggregates (e.g. IfcTriangulatedFaceSet.CoordIndex) are
unaffected. The existing util/test_attribute.py and test_file.py suites pass
unchanged; test_entity_instance.py has 5 pre-existing failures unrelated to this
change (confirmed identical on an unfixed build of this branch, caused by a missing
get_info_2 binding and _patch_swig_comparisons never being implemented here).

Generated with the assistance of an AI coding tool.
This commit is contained in:
Petru Conduraru
2026-07-19 11:44:31 +03:00
committed by Thomas Krijnen
parent d8799d799e
commit d5076bded3
6 changed files with 58 additions and 17 deletions
+10 -2
View File
@@ -89,6 +89,11 @@ namespace impl {
static std::string get() { return "int"; }
};
template <>
struct VariantTypeName<int64_t> {
static std::string get() { return "int"; }
};
template <>
struct VariantTypeName<bool> {
static std::string get() { return "bool"; }
@@ -154,7 +159,9 @@ typedef parameter_pack <
// An integer argument, e.g. 123
// SCALARS:
int,
// Stored as int64_t so integer attributes (IfcInteger, IfcTimeStamp)
// can hold values outside the signed 32-bit range.
int64_t,
// A boolean argument, it will serialize to either .T. or .F.
bool,
// A logical argument, it will serialize to either .T. or .F. or .U.
@@ -395,6 +402,7 @@ public:
{}
operator int() const;
operator int64_t() const;
operator bool() const;
operator boost::logic::tribool() const;
operator double() const;
@@ -425,7 +433,7 @@ public:
case ifcopenshell::Argument_DERIVED:
return visitor(derived{});
case ifcopenshell::Argument_INT:
return visitor((int)*this);
return visitor((int64_t)*this);
case ifcopenshell::Argument_BOOL:
return visitor((bool)*this);
case ifcopenshell::Argument_LOGICAL: {