mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-21 23:50:59 +00:00
Fix to_string() on eof marker token (#9463)
* Rewrite to_string() to use switch{} and handle Token_NONE and identifier without as_string()
* Add regression tests for to_string() on tokens without a string form
Cover both halves of the recursion that made a whitespace-only file
segfault: token::to_string() on the EOF marker and on an instance name,
and a parse of input that lexes to zero tokens, which is how the header
parser reaches token::as_string() on the EOF marker.
Generated with the assistance of an AI coding tool.
---------
Co-authored-by: Bruno Postle <bruno@postle.net>
This commit is contained in:
+25
-10
@@ -536,19 +536,34 @@ boost::dynamic_bitset<> token::as_binary() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string token::to_string() {
|
std::string token::to_string() {
|
||||||
std::string result;
|
switch (type) {
|
||||||
if (type == Token_OPERATOR || type == Token_BOOL) {
|
case Token_OPERATOR:
|
||||||
result.push_back(value_char);
|
case Token_BOOL:
|
||||||
} else if (type == Token_INT) {
|
return std::string(1, value_char);
|
||||||
result = std::to_string(value_int);
|
|
||||||
} else if (type == Token_FLOAT) {
|
case Token_INT:
|
||||||
|
return std::to_string(value_int);
|
||||||
|
|
||||||
|
case Token_IDENTIFIER:
|
||||||
|
return "#" + std::to_string(value_int);
|
||||||
|
|
||||||
|
case Token_FLOAT: {
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << std::setprecision(15) << value_double;
|
oss << std::setprecision(15) << value_double;
|
||||||
result = oss.str();
|
return oss.str();
|
||||||
} else {
|
|
||||||
return as_string();
|
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
|
case Token_STRING:
|
||||||
|
case Token_ENUMERATION:
|
||||||
|
case Token_BINARY:
|
||||||
|
case Token_KEYWORD:
|
||||||
|
return as_string();
|
||||||
|
|
||||||
|
case Token_NONE:
|
||||||
|
throw invalid_token_exception(start_pos, "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw exception("Unknown token type");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ifcopenshell::encode_spf_string(const std::string& value) {
|
std::string ifcopenshell::encode_spf_string(const std::string& value) {
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
// This file was generated with the assistance of an AI coding tool.
|
// This file was generated with the assistance of an AI coding tool.
|
||||||
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include <ifcparse/exception.h>
|
||||||
#include <ifcparse/file.h>
|
#include <ifcparse/file.h>
|
||||||
#include <ifcparse/parse.h>
|
#include <ifcparse/parse.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -97,6 +100,37 @@ TEST_CASE("Aggregate inverse updates preserve reference multiplicity", "[ifcpars
|
|||||||
CHECK(inverse_count(segment_d) == 1);
|
CHECK(inverse_count(segment_d) == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Tokens without a string representation do not recurse in to_string()", "[ifcparse]") {
|
||||||
|
// to_string() used to delegate to as_string() for every token type it did
|
||||||
|
// not handle explicitly, while as_string() builds its exception message
|
||||||
|
// with to_string(). An EOF marker or an instance name therefore recursed
|
||||||
|
// between the two until the stack was exhausted.
|
||||||
|
ifcopenshell::token eof;
|
||||||
|
REQUIRE(eof.type == ifcopenshell::token::Token_NONE);
|
||||||
|
CHECK_THROWS_AS(eof.to_string(), ifcopenshell::invalid_token_exception);
|
||||||
|
CHECK_THROWS_AS(eof.as_string(), ifcopenshell::invalid_token_exception);
|
||||||
|
|
||||||
|
ifcopenshell::token identifier(0, ifcopenshell::token::Token_IDENTIFIER, (int64_t)123);
|
||||||
|
CHECK(identifier.to_string() == "#123");
|
||||||
|
CHECK_THROWS_AS(identifier.as_string(), ifcopenshell::invalid_token_exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Files that contain no tokens are rejected rather than crashing", "[ifcparse]") {
|
||||||
|
// The header parser asks the lexer for a keyword before checking for EOF,
|
||||||
|
// so input that lexes to zero tokens reaches token::as_string() on the EOF
|
||||||
|
// marker. Parsing must fail cleanly instead of overflowing the stack.
|
||||||
|
const std::vector<std::string> inputs{" ", "\r\n\t ", "/* only a comment */"};
|
||||||
|
|
||||||
|
for (const auto& contents : inputs) {
|
||||||
|
INFO("input: " << contents);
|
||||||
|
ifcopenshell::logger log;
|
||||||
|
std::istringstream input(contents);
|
||||||
|
ifcopenshell::file file(input, (int)contents.size(), log);
|
||||||
|
|
||||||
|
CHECK(file.good().value() != ifcopenshell::file_open_status::SUCCESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("Inverse lookups stay consistent across interleaved adds, removals and reads", "[ifcparse]") {
|
TEST_CASE("Inverse lookups stay consistent across interleaved adds, removals and reads", "[ifcparse]") {
|
||||||
ifcopenshell::file file(ifcopenshell::schema_by_name("IFC4"));
|
ifcopenshell::file file(ifcopenshell::schema_by_name("IFC4"));
|
||||||
const auto* point_declaration = file.schema()->declaration_by_name("IfcCartesianPoint");
|
const auto* point_declaration = file.schema()->declaration_by_name("IfcCartesianPoint");
|
||||||
|
|||||||
Reference in New Issue
Block a user