diff --git a/src/ifcopenshell-python/test/test_parse.py b/src/ifcopenshell-python/test/test_parse.py index 992f842bf7..15de45fc29 100644 --- a/src/ifcopenshell-python/test/test_parse.py +++ b/src/ifcopenshell-python/test/test_parse.py @@ -1,3 +1,5 @@ +import math + import ifcopenshell @@ -21,7 +23,9 @@ END-ISO-10303-21; def test_reference_to_undefined_owning_instance(): - data = "ISO-10303-21;HEADER;FILE_DESCRIPTION();FILE_NAME();FILE_SCHEMA(('IFC4'));#=IFCRELAGGREGATES((#))#5=IFCPOINT)" + data = ( + "ISO-10303-21;HEADER;FILE_DESCRIPTION();FILE_NAME();FILE_SCHEMA(('IFC4'));#=IFCRELAGGREGATES((#))#5=IFCPOINT)" + ) ifcopenshell.file.from_string(data) print(ifcopenshell.get_log()) @@ -30,3 +34,29 @@ def test_reference_to_undefined_owning_instance_simple_type(): data = "ISO-10303-21;HEADER;FILE_DESCRIPTION();FILE_NAME();FILE_SCHEMA(('IFC4'));#=IFCPROJECT((#))#4=IFCSIUNIT(" ifcopenshell.file.from_string(data) print(ifcopenshell.get_log()) + + +def test_non_finite_reals_with_trailing_dot(): + # Legacy serialization appended '.' to non-finite doubles ("nan.", "inf."), + # which tokenized as unknown keywords and left zero-argument typed values + # that raised on attribute access (#6409). + data = """ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION((''),'2;1'); +FILE_NAME('','',(''),(''),'','',''); +FILE_SCHEMA(('IFC2X3')); +ENDSEC; +DATA; +#1=IFCPROPERTYSINGLEVALUE('test',$,IFCREAL(nan.),$); +#2=IFCCARTESIANPOINT((0.,inf.,-inf.)); +#3=IFCDIRECTION((-nan.,-nan.,-nan.)); +ENDSEC; +END-ISO-10303-21; +""" + f = ifcopenshell.file.from_string(data) + log = ifcopenshell.get_log() + assert math.isnan(f.by_id(1).NominalValue.wrappedValue) + assert f.by_id(2).Coordinates == (0.0, math.inf, -math.inf) + assert all(math.isnan(ratio) for ratio in f.by_id(3).DirectionRatios) + assert "Non-finite value" in log diff --git a/src/ifcparse/IfcParse.cpp b/src/ifcparse/IfcParse.cpp index bc6df697af..1b7ed28bd3 100644 --- a/src/ifcparse/IfcParse.cpp +++ b/src/ifcparse/IfcParse.cpp @@ -30,6 +30,7 @@ #include "utils.h" #include +#include #include #include #include @@ -283,7 +284,13 @@ bool ParseFloat(const char* pStart, double& val) { double result = strtod_l(pStart, &pEnd, locale); #endif if (*pEnd != 0) { - return false; + // Historically REALs were serialized by appending a '.' to any formatted + // double lacking one, producing non-finite spellings such as "nan.", + // "-nan(ind)." and "inf." that strtod stops in front of. Accept a single + // trailing '.' after a successful partial parse so these load again. + if (pEnd == pStart || pEnd[0] != '.' || pEnd[1] != 0) { + return false; + } } val = result; return true; @@ -339,6 +346,9 @@ Token IfcParse::GeneralTokenPtr(IfcSpfLexer* lexer, size_t start, const std::str token.type = Token_INT; } else if (ParseFloat(tokenStr.c_str(), token.value_double)) { token.type = Token_FLOAT; + if (!std::isfinite(token.value_double)) { + lexer->logger().Message(Logger::LOG_ERROR, "SYN", 41, "Non-finite value '" + tokenStr + "' at offset " + std::to_string(token.startPos)); + } } else { token.type = Token_KEYWORD; }