From f60faea2e772b2bd1b849f0b320e9f72284ba529 Mon Sep 17 00:00:00 2001 From: deeaitch Date: Fri, 23 Oct 2026 20:48:21 -0400 Subject: [PATCH] Closes #32 --- README.md | 8 +- changelog.md | 14 + core/dbc_statement_handler.cpp | 127 +- docs/html/classDbcCommentHandler.html | 2 +- docs/html/classDbcFrameHandler.html | 2 +- docs/html/classDbcHeaderHandler.html | 2 +- docs/html/classDbcMultiplexingHandler.html | 2 +- docs/html/classDbcParser.html | 2 +- docs/html/classDbcSignalHandler.html | 2 +- docs/html/classDbcStatementDispatcher.html | 4 +- .../html/classDbcValueDescriptionHandler.html | 2 +- .../html/dbc__coverage__test_8cpp_source.html | 4 +- docs/html/dbc__parser_8cpp_source.html | 2 +- docs/html/dbc__parser__test_8cpp_source.html | 2956 +++++++++-------- .../dbc__statement__handler_8cpp_source.html | 1106 +++--- .../dbc__statement__handler_8h_source.html | 22 +- docs/html/index.qhp | 3 +- docs/html/md_README.html | 2 +- docs/html/md_changelog.html | 4 + docs/html/md_tests_2README.html | 4 + docs/html/navtreedata.js | 3 +- docs/html/navtreeindex1.js | 1 + docs/html/search/all_10.js | 3 +- docs/html/search/all_17.js | 57 +- docs/html/search/all_1a.js | 2 +- docs/html/search/all_8.js | 21 +- docs/html/search/all_9.js | 2 +- docs/html/structDbcDatabase.html | 2 +- docs/html/structDbcParseContext.html | 22 +- docs/html/structFrameInfo.html | 14 +- docs/html/structPendingComment.html | 12 +- docs/html/structPendingValueDescriptions.html | 10 +- docs/html/structSignalInfo.html | 16 +- tests/README.md | 19 + tests/dbc_parser_test.cpp | 52 + 35 files changed, 2361 insertions(+), 2145 deletions(-) diff --git a/README.md b/README.md index ca5b244..242bbd3 100644 --- a/README.md +++ b/README.md @@ -788,8 +788,12 @@ Parsed fields: ### Signal validation and parse errors -`SG_` statements are matched completely. Signal names must be nonempty and -contain no whitespace. Spaces and tabs are accepted between syntax elements; +`SG_` statements are read completely with an iterative scanner. Signal parsing +and numeric conversion do not use recursive regular-expression matching, so long +fields do not consume stack space proportional to their length. No new statement +length limit is imposed; valid long fields are preserved, and malformed statements +return the usual source diagnostics. Signal names must be nonempty and contain no +whitespace. Spaces and tabs are accepted between syntax elements; byte order and signedness remain adjacent (`@0+`, `@0-`, `@1+`, or `@1-`). A quoted unit (which may be empty) and a nonempty comma-separated receiver list are required. Unexpected trailing fields are rejected. diff --git a/changelog.md b/changelog.md index a89e248..540e5c3 100644 --- a/changelog.md +++ b/changelog.md @@ -34,6 +34,20 @@ existing Git tag; subsequent changes are listed as unreleased. #### Fixed +- Replace recursive regular-expression matching for `SG_` statements with an + iterative field scanner. Long unit strings no longer overflow the stack. +- Parse signal physical values directly with locale-independent `from_chars`, + retaining complete-token, finite-value, overflow, and underflow validation + without recursive numeric regex matching. +- Preserve long valid fields without a new length limit, and reject malformed + statements through the existing source-located diagnostic and throwing APIs. +- Add parser regressions for 50,000-character fields, large receiver lists, + malformed syntax, and numeric errors. Verify Release and ASan/UBSan builds + with the optional Qt adapter: 2,409 core checks, seven model checks, and the + qmake tree test pass in each build. One named-locale test skips because its + locale is unavailable; local sanitizer leak checking remains disabled due + to the documented ptrace restriction. + - Read attribute definitions, defaults, and assignments across physical lines. Preserve quoted newlines and semicolons, and report attribute errors and unsupported frame formats at the statement's first physical line. diff --git a/core/dbc_statement_handler.cpp b/core/dbc_statement_handler.cpp index 3bd5d65..0ceb355 100644 --- a/core/dbc_statement_handler.cpp +++ b/core/dbc_statement_handler.cpp @@ -33,9 +33,8 @@ namespace { } double ParsePhysicalValue(const std::string &text, const char *field) { - static const std::regex number( - R"(^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$)"); - if (!std::regex_match(text, number)) + if (text.empty() || (text[0] == '+' && + (text.size() == 1U || text[1] == '+' || text[1] == '-'))) throw std::runtime_error(std::string("Invalid ") + field + ": " + text); // Floating-point from_chars is locale-independent and does not accept // a leading '+', which the DBC numeric grammar permits. @@ -53,6 +52,58 @@ namespace { return value; } + // Iterative scanning keeps stack use independent of the statement length. + class SignalTokens { + public: + SignalTokens(const std::string &text, std::size_t position) : text(text), position(position) {} + + void SkipSpaces() { + while (position < text.size() && (text[position] == ' ' || text[position] == '\t')) + ++position; + } + + char Character() { + if (position == text.size()) + throw std::runtime_error("Incomplete SG_ signal definition."); + return text[position++]; + } + + void Expect(char expected) { + SkipSpaces(); + if (Character() != expected) + throw std::runtime_error("Malformed SG_ signal definition."); + } + + std::string Until(char delimiter) { + const std::size_t end = text.find(delimiter, position); + if (end == std::string::npos) + throw std::runtime_error("Missing delimiter in SG_ signal definition."); + const std::string value = text.substr(position, end - position); + position = end + 1U; + return value; + } + + std::vector Receivers() { + if (position == text.size() || (text[position] != ' ' && text[position] != '\t')) + throw std::runtime_error("Missing receiver separator in SG_ signal definition."); + std::vector receivers; + for (;;) { + const std::size_t end = text.find(',', position); + const std::string receiver = dbc_detail::TrimText(text.substr(position, end - position)); + if (receiver.empty() || receiver.find_first_of(" \t\r\n") != std::string::npos) + throw std::runtime_error("Invalid SG_ receiver list."); + receivers.push_back(receiver); + if (end == std::string::npos) + return receivers; + position = end + 1U; + } + } + + private: + const std::string &text; + std::size_t position = 0U; + }; + Multiplexing ParseMultiplexingMarker(const std::string &marker) { Multiplexing result; if (marker == "M") @@ -142,20 +193,6 @@ namespace { } // namespace namespace { - std::vector SplitReceivers(const std::string &text) { - std::vector receivers; - std::string token; - std::istringstream stream(text); - - while (std::getline(stream, token, ',')) { - token = dbc_detail::TrimText(token); - if (!token.empty()) - receivers.push_back(token); - } - - return receivers; - } - void NormalizeCanId(std::uint32_t rawCanId, std::uint32_t &normalizedCanId, bool &isExtended) { /* * DBC commonly stores extended identifiers with bit 31 set. @@ -206,28 +243,12 @@ FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) { } SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) { - // Match the complete statement before converting any numeric fields. - enum Field { - NameAndMarker = 1, StartBit, BitLength, ByteOrder, Signedness, - Factor, Offset, Minimum, Maximum, Unit, Receivers - }; - static const std::regex syntax( - // Name and optional multiplexing marker. - R"dbc(^SG_[ \t]+([^:]+):[ \t]*)dbc" - // Bit position, length, byte order and signedness. - R"dbc(([^| \t]+)[ \t]*\|[ \t]*([^@ \t]+)[ \t]*@[ \t]*([01])([+-])[ \t]*)dbc" - // Scaling factor and offset. - R"dbc(\([ \t]*([^, \t]+)[ \t]*,[ \t]*([^ )\t]+)[ \t]*\)[ \t]*)dbc" - // Physical range. - R"dbc(\[[ \t]*([^| \t]+)[ \t]*\|[ \t]*([^\] \t]+)[ \t]*\][ \t]*)dbc" - // Unit and receivers. - R"dbc("([^"\r\n]*)"[ \t]+([^ \t,]+(?:[ \t]*,[ \t]*[^ \t,]+)*)[ \t]*$)dbc"); - std::smatch fields; - if (!std::regex_match(line, fields, syntax)) - throw std::runtime_error("Malformed or unsupported SG_ signal definition."); - + if (line.size() < 4U || line.compare(0U, 3U, "SG_") != 0 || + (line[3] != ' ' && line[3] != '\t')) + throw std::runtime_error("Malformed SG_ signal definition."); + SignalTokens tokens(line, 3U); SignalInfo signal; - std::istringstream nameFields(fields[NameAndMarker].str()); + std::istringstream nameFields(tokens.Until(':')); std::string marker; std::string extra; nameFields >> signal.name >> marker; @@ -235,22 +256,32 @@ SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) { throw std::runtime_error("Invalid signal name or multiplexing marker."); signal.multiplexing = ParseMultiplexingMarker(marker); - signal.startBit = ParseDecimal(fields[StartBit].str(), + signal.startBit = ParseDecimal(dbc_detail::TrimText(tokens.Until('|')), std::numeric_limits::max(), "signal start bit"); - signal.length = ParseDecimal(fields[BitLength].str(), 64U, "signal length"); + signal.length = ParseDecimal(dbc_detail::TrimText(tokens.Until('@')), 64U, "signal length"); if (signal.length == 0U) throw std::runtime_error("Signal length must be within 1..64 bits."); - signal.isLittleEndian = fields[ByteOrder].str() == "1"; - signal.isSigned = fields[Signedness].str() == "-"; + tokens.SkipSpaces(); + const char byteOrder = tokens.Character(); + const char signedness = tokens.Character(); + if ((byteOrder != '0' && byteOrder != '1') || (signedness != '+' && signedness != '-')) + throw std::runtime_error("Invalid SG_ byte order or signedness."); + signal.isLittleEndian = byteOrder == '1'; + signal.isSigned = signedness == '-'; - signal.factor = ParsePhysicalValue(fields[Factor].str(), "signal factor"); - signal.offset = ParsePhysicalValue(fields[Offset].str(), "signal offset"); - signal.minimum = ParsePhysicalValue(fields[Minimum].str(), "signal minimum"); - signal.maximum = ParsePhysicalValue(fields[Maximum].str(), "signal maximum"); + tokens.Expect('('); + signal.factor = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until(',')), "signal factor"); + signal.offset = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until(')')), "signal offset"); + tokens.Expect('['); + signal.minimum = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until('|')), "signal minimum"); + signal.maximum = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until(']')), "signal maximum"); if (signal.minimum > signal.maximum) throw std::runtime_error("Signal minimum exceeds maximum."); - signal.unit = fields[Unit].str(); - signal.receivers = SplitReceivers(fields[Receivers].str()); + tokens.Expect('"'); + signal.unit = tokens.Until('"'); + if (signal.unit.find_first_of("\r\n") != std::string::npos) + throw std::runtime_error("Invalid newline in SG_ unit."); + signal.receivers = tokens.Receivers(); return signal; } diff --git a/docs/html/classDbcCommentHandler.html b/docs/html/classDbcCommentHandler.html index b840e0c..0bcec11 100644 --- a/docs/html/classDbcCommentHandler.html +++ b/docs/html/classDbcCommentHandler.html @@ -158,7 +158,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 308 of file dbc_statement_handler.cpp.

+

Definition at line 339 of file dbc_statement_handler.cpp.

References PendingComment::canId, PendingComment::comment, PendingComment::isExtended, PendingComment::isSignal, PendingComment::lineNumber, DbcParseContext::lineNumber, DbcParseContext::pendingComments, and PendingComment::signalName.

diff --git a/docs/html/classDbcFrameHandler.html b/docs/html/classDbcFrameHandler.html index 69d28ab..87ed90a 100644 --- a/docs/html/classDbcFrameHandler.html +++ b/docs/html/classDbcFrameHandler.html @@ -158,7 +158,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 280 of file dbc_statement_handler.cpp.

+

Definition at line 311 of file dbc_statement_handler.cpp.

References FrameInfo::canId, DbcParseContext::currentFrame, DbcParseContext::database, DbcParseContext::enableJ1939, DbcDatabase::frames, FrameInfo::hasPgn, FrameInfo::isExtended, FrameInfo::pgn, and DbcParseContext::signalLines.

diff --git a/docs/html/classDbcHeaderHandler.html b/docs/html/classDbcHeaderHandler.html index 2aff14e..c4c48f7 100644 --- a/docs/html/classDbcHeaderHandler.html +++ b/docs/html/classDbcHeaderHandler.html @@ -155,7 +155,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 390 of file dbc_statement_handler.cpp.

+

Definition at line 421 of file dbc_statement_handler.cpp.

diff --git a/docs/html/classDbcMultiplexingHandler.html b/docs/html/classDbcMultiplexingHandler.html index 5d108f0..1d9fb2e 100644 --- a/docs/html/classDbcMultiplexingHandler.html +++ b/docs/html/classDbcMultiplexingHandler.html @@ -158,7 +158,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 453 of file dbc_statement_handler.cpp.

+

Definition at line 484 of file dbc_statement_handler.cpp.

References DbcParseContext::lineNumber.

diff --git a/docs/html/classDbcParser.html b/docs/html/classDbcParser.html index dc0ce7c..2fe4a02 100644 --- a/docs/html/classDbcParser.html +++ b/docs/html/classDbcParser.html @@ -221,7 +221,7 @@ Public Member Functions

Definition at line 103 of file dbc_parser.cpp.

-

References DbcParseResult::database, DbcDiagnosticError::diagnostic, DbcParseResult::diagnostics, DbcStatementDispatcher::Dispatch(), and DbcParserOptions::enableJ1939.

+

References DbcParseResult::database, DbcDiagnosticError::diagnostic, DbcParseResult::diagnostics, DbcStatementDispatcher::Dispatch(), and DbcParserOptions::enableJ1939.

Referenced by ParseFile().

diff --git a/docs/html/classDbcSignalHandler.html b/docs/html/classDbcSignalHandler.html index 12c321e..1f6198c 100644 --- a/docs/html/classDbcSignalHandler.html +++ b/docs/html/classDbcSignalHandler.html @@ -158,7 +158,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 294 of file dbc_statement_handler.cpp.

+

Definition at line 325 of file dbc_statement_handler.cpp.

References DbcParseContext::currentFrame, FrameInfo::dlc, SignalInfo::isLittleEndian, SignalInfo::length, DbcParseContext::lineNumber, FrameInfo::name, SignalInfo::name, DbcParseContext::signalLines, and SignalInfo::startBit.

diff --git a/docs/html/classDbcStatementDispatcher.html b/docs/html/classDbcStatementDispatcher.html index 920c039..0dfcab4 100644 --- a/docs/html/classDbcStatementDispatcher.html +++ b/docs/html/classDbcStatementDispatcher.html @@ -133,7 +133,7 @@ Public Member Functions

Register supported statement handlers through TypeFactory.

-

Definition at line 372 of file dbc_statement_handler.cpp.

+

Definition at line 403 of file dbc_statement_handler.cpp.

@@ -199,7 +199,7 @@ template<class Handler >

Returns false for an unregistered keyword; input must be trimmed.

-

Definition at line 392 of file dbc_statement_handler.cpp.

+

Definition at line 423 of file dbc_statement_handler.cpp.

Referenced by DbcParser::ParseFileWithDiagnostics().

diff --git a/docs/html/classDbcValueDescriptionHandler.html b/docs/html/classDbcValueDescriptionHandler.html index 04d7e90..0c881b0 100644 --- a/docs/html/classDbcValueDescriptionHandler.html +++ b/docs/html/classDbcValueDescriptionHandler.html @@ -158,7 +158,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 404 of file dbc_statement_handler.cpp.

+

Definition at line 435 of file dbc_statement_handler.cpp.

References PendingValueDescriptions::canId, PendingValueDescriptions::descriptions, PendingValueDescriptions::isExtended, PendingValueDescriptions::lineNumber, DbcParseContext::lineNumber, DbcParseContext::pendingValues, and PendingValueDescriptions::signalName.

diff --git a/docs/html/dbc__coverage__test_8cpp_source.html b/docs/html/dbc__coverage__test_8cpp_source.html index 5e2c401..5bb5f21 100644 --- a/docs/html/dbc__coverage__test_8cpp_source.html +++ b/docs/html/dbc__coverage__test_8cpp_source.html @@ -993,7 +993,7 @@ $(document).ready(function(){initNavTree('dbc__coverage__test_8cpp_source.html',
893}
894#include "dbc_coverage_test.moc"
Defer frame and signal comments for target resolution.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
Converts parsed DBC data into runtime decode database.
DecodeDatabase Build(const DbcDatabase &source) const
Build runtime decode database.
Runtime CAN decoder using prebuilt decode database.
Definition dbc_decoder.h:87
@@ -1004,7 +1004,7 @@ $(document).ready(function(){initNavTree('dbc__coverage__test_8cpp_source.html',
Builds a simple tree from parsed DBC database.
std::unique_ptr< TreeNode > Build(const DbcDatabase &database) const
Build tree representation of parsed DBC data.
Defer raw value descriptions for target resolution.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
Immutable runtime snapshot. Copies share storage; reload replaces a snapshot. Owning lookup handles a...
std::shared_ptr< const DecodeFrame > FindFrame(const FrameKey &key) const
const std::vector< DecodeFrame > & GetFrames() const
diff --git a/docs/html/dbc__parser_8cpp_source.html b/docs/html/dbc__parser_8cpp_source.html index 53110dd..97b765b 100644 --- a/docs/html/dbc__parser_8cpp_source.html +++ b/docs/html/dbc__parser_8cpp_source.html @@ -282,7 +282,7 @@ $(document).ready(function(){initNavTree('dbc__parser_8cpp_source.html',''); ini
DbcParseResult ParseFileWithDiagnostics(const std::string &filePath) const
Parse with warnings and a structured first error instead of throwing.
DbcDatabase ParseFile(const std::string &filePath) const
Parse DBC file.
-
bool Dispatch(const std::string &line, DbcParseContext &context) const
+
bool Dispatch(const std::string &line, DbcParseContext &context) const
DBC file parser and interpretation options.
Parsed DBC content stored in a simple internal form.
Diagnostic message and physical source location.
diff --git a/docs/html/dbc__parser__test_8cpp_source.html b/docs/html/dbc__parser__test_8cpp_source.html index 323a829..06ab445 100644 --- a/docs/html/dbc__parser__test_8cpp_source.html +++ b/docs/html/dbc__parser__test_8cpp_source.html @@ -216,1463 +216,1515 @@ $(document).ready(function(){initNavTree('dbc__parser__test_8cpp_source.html',''
116 void integration();
117 void invalidSignals();
118 void validSignals();
-
119 void physicalNumbersCppLocale();
-
120 void physicalNumbersCLocale();
-
121 void dispatch();
-
122 void independentSessions();
-
123 void valueDescriptions();
-
124 void invalidValueDescriptions();
-
125};
-
126
-
127void DbcParserTest::attributes() {
-
128 Fixture fixture;
-
129 fixture.Write(
-
130 "BA_ \"GenMsgCycleTime\" BO_ 1 25;\n"
-
131 "BA_ \"VFrameFormat\" BO_ 2147483649 1;\n"
-
132 "BA_ \"Label\" SG_ 1 Value \"a\\\"b\\\\c\";\n"
-
133 "BA_ \"NodeLabel\" BU_ ECU \"node\";\n"
-
134 "BA_ \"Environment\" EV_ Env 2;\n"
-
135 "BA_DEF_DEF_ \"GenMsgCycleTime\" 100;\n"
-
136 "BA_DEF_DEF_ \"VFrameFormat\" \"StandardCAN\";\n"
-
137 "BA_DEF_DEF_ \"Label\" \"default\";\n"
-
138 "BA_DEF_DEF_ \"BusLabel\" \"CAN\";\n"
-
139 "BA_DEF_DEF_ \"Ratio\" 1.5e-1;\n"
-
140 "BA_DEF_ BO_ \"GenMsgCycleTime\" INT 0 10000;\n"
-
141 "BA_DEF_ BO_ \"VFrameFormat\" ENUM \"StandardCAN\",\"ExtendedCAN\",\"StandardCAN_FD\";\n"
-
142 "BA_DEF_ SG_ \"Label\" STRING;\n"
-
143 "BA_DEF_ \"BusLabel\" STRING;\n"
-
144 "BA_DEF_ BO_ \"Ratio\" FLOAT -1 1;\n"
-
145 "BA_DEF_ BU_ \"NodeLabel\" STRING;\n"
-
146 "BA_DEF_ EV_ \"Environment\" HEX 0 255;\n"
-
147 "BO_ 1 Standard: 1 ECU\n"
-
148 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
-
149 "BO_ 2147483649 Extended: 1 ECU\n"
-
150 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n");
-
151 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
152 QVERIFY(!result.HasErrors());
-
153 QVERIFY(result.diagnostics.empty());
-
154 const DbcDatabase &database = result.database;
-
155 QCOMPARE(database.attributeDefinitions.size(), std::size_t(7));
-
156 QCOMPARE(database.attributeAssignments.size(), std::size_t(5));
-
157 QCOMPARE(std::get<std::string>(database.attributes.at("BusLabel")), std::string("CAN"));
-
158 QCOMPARE(database.frames[0].cycleTimeMs.value(), 25U);
-
159 QCOMPARE(database.frames[1].cycleTimeMs.value(), 100U);
-
160 QCOMPARE(database.frames[1].frameFormat.value(), std::string("ExtendedCAN"));
-
161 QVERIFY(!database.frames[1].hasPgn);
-
162 QCOMPARE(std::get<double>(database.frames[0].attributes.at("Ratio")), 0.15);
-
163 QCOMPARE(std::get<std::string>(database.frames[0].signals[0].attributes.at("Label")), std::string("a\"b\\c"));
-
164 QCOMPARE(std::get<std::string>(database.frames[1].signals[0].attributes.at("Label")), std::string("default"));
-
165 const DecodeDatabase runtime = DbcDecodeBuilder().Build(database);
-
166 QCOMPARE(runtime.GetFrames()[0].cycleTimeMs.value(), 25U);
-
167 QCOMPARE(runtime.GetFrames()[1].frameFormat.value(), std::string("ExtendedCAN"));
-
168 QCOMPARE(runtime.GetFrames()[0].signals[0].attributes, database.frames[0].signals[0].attributes);
-
169 const std::unique_ptr<TreeNode> tree = DbcTreeBuilder().Build(database);
-
170 QCOMPARE(tree->GetChild(0)->GetFrame()->cycleTimeMs.value(), 25U);
-
171 QCOMPARE(tree->GetChild(0)->GetChild(0)->GetSignal()->attributes, database.frames[0].signals[0].attributes);
-
172 fixture.Write("BA_DEF_ BO_ \"Period\" INT 0 10;\nBA_DEF_DEF_ \"Period\" 1;\n"
-
173 "BA_ \"Period\" BO_ 1 2;\nBA_ \"Period\" BO_ 1 3;\nBO_ 1 F: 0 ECU\n");
-
174 const DbcDatabase repeated = DbcParser().ParseFile(fixture.path.string());
-
175 QCOMPARE(std::get<std::int64_t>(repeated.frames[0].attributes.at("Period")), std::int64_t(3));
-
176
-
177 fixture.Write("BA_DEF_ BO_ \"GenMsgCycleTime\" INT 0 4294967295;\n"
-
178 "BA_DEF_DEF_ \"GenMsgCycleTime\" 0;\n"
-
179 "BA_ \"GenMsgCycleTime\" BO_ 1 4294967295;\n"
-
180 "BA_DEF_ \"Wide\" INT -9223372036854775808 9223372036854775807;\n"
-
181 "BA_DEF_DEF_ \"Wide\" -9223372036854775808;\n"
-
182 "BA_DEF_ SG_ \"State\" ENUM \"off\",\"on\";\n"
-
183 "BA_DEF_DEF_ \"State\" 1;\n"
-
184 "BO_ 1 F: 0 ECU\nBO_ 2 Empty: 0 ECU\n");
-
185 const DbcDatabase boundaries = DbcParser().ParseFile(fixture.path.string());
-
186 QCOMPARE(boundaries.frames[0].cycleTimeMs.value(), std::numeric_limits<std::uint32_t>::max());
-
187 QCOMPARE(boundaries.frames[1].cycleTimeMs.value(), 0U);
-
188 QVERIFY(!boundaries.frames[0].frameFormat);
-
189 QCOMPARE(std::get<std::int64_t>(boundaries.attributes.at("Wide")), std::numeric_limits<std::int64_t>::min());
-
190 QCOMPARE(std::get<std::int64_t>(*boundaries.attributeDefinitions.at("State").defaultValue), std::int64_t(1));
-
191}
-
192
-
193void DbcParserTest::multilineAttributes() {
-
194 Fixture fixture;
-
195 fixture.Write(R"DBC(BA_
-
196 "GenMsgCycleTime" BO_ 1
-
197 25;
-
198BA_DEF_ BO_ "VFrameFormat" ENUM
-
199 "StandardCAN",
-
200 "StandardCAN_FD";
-
201BA_DEF_DEF_
-
202 "VFrameFormat"
-
203 "StandardCAN_FD";
-
204BA_DEF_ BO_ "GenMsgCycleTime"
-
205 INT 0 100;
-
206BA_DEF_DEF_ "GenMsgCycleTime"
-
207 50;
-
208BA_DEF_ SG_ "Label" STRING;
-
209BA_ "Label" SG_ 1 Value
-
210 "first; \"quoted\"
-
211second\\path";
-
212BA_ "VFrameFormat" BO_ 1
-
213 0;
-
214BO_ 1 F: 1 ECU
-
215SG_ Value : 0|8@1+ (1,0) [0|255] "" ECU
-
216BO_ 2 Default: 0 ECU
-
217)DBC");
-
218 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
219 QVERIFY(!result.HasErrors());
-
220 QCOMPARE(result.database.frames.size(), std::size_t(2));
-
221 QCOMPARE(result.database.frames[0].cycleTimeMs.value(), 25U);
-
222 QCOMPARE(result.database.frames[1].cycleTimeMs.value(), 50U);
-
223 QCOMPARE(result.database.frames[0].frameFormat.value(), std::string("StandardCAN"));
-
224 QCOMPARE(result.database.frames[1].frameFormat.value(), std::string("StandardCAN_FD"));
-
225 QCOMPARE(std::get<std::string>(result.database.frames[0].signals[0].attributes.at("Label")),
-
226 std::string("first; \"quoted\"\nsecond\\path"));
-
227 QCOMPARE(result.diagnostics.size(), std::size_t(1));
-
228 QCOMPARE(result.diagnostics[0].code, DbcDiagnosticCode::UnsupportedAttribute);
-
229 QCOMPARE(result.diagnostics[0].lineNumber, std::size_t(7));
-
230
-
231 fixture.Write("BA_DEF_ BO_ \"VFrameFormat\" ENUM\r\n \"StandardCAN\",\r\n \"ExtendedCAN_FD\";\r\n"
-
232 "BA_\r\n \"VFrameFormat\" BO_ 1\r\n 1;\r\nBO_ 1 F: 0 ECU\r\n");
-
233 const DbcParseResult explicitFormat = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
234 QVERIFY(!explicitFormat.HasErrors());
-
235 QCOMPARE(explicitFormat.diagnostics.size(), std::size_t(1));
-
236 QCOMPARE(explicitFormat.diagnostics[0].code, DbcDiagnosticCode::UnsupportedAttribute);
-
237 QCOMPARE(explicitFormat.diagnostics[0].lineNumber, std::size_t(4));
-
238 QCOMPARE(explicitFormat.database.frames[0].frameFormat.value(), std::string("ExtendedCAN_FD"));
-
239
-
240 fixture.Write("BA_DEF_ \"Text\" STRING;\n BA_ \"Text\" \"first \nsecond\";\n"
-
241 "UnsupportedStatement\n");
-
242 const DbcParseResult whitespace = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
243 QVERIFY(!whitespace.HasErrors());
-
244 QCOMPARE(std::get<std::string>(whitespace.database.attributes.at("Text")), std::string("first \nsecond"));
-
245 QCOMPARE(whitespace.diagnostics.size(), std::size_t(1));
-
246 QCOMPARE(whitespace.diagnostics[0].code, DbcDiagnosticCode::UnsupportedStatement);
-
247 QCOMPARE(whitespace.diagnostics[0].lineNumber, std::size_t(4));
-
248}
-
249
-
250void DbcParserTest::invalidMultilineAttributes() {
-
251 Fixture fixture;
-
252 const std::vector<std::string> invalid = {
-
253 "BA_DEF_\n BO_ \"A\" INT\n 10 0;\n",
-
254 "BA_DEF_ BO_ \"A\" ENUM\n \"one\",\n;\n",
-
255 "BA_DEF_ BO_ \"A\" STRING\n",
-
256 "BA_DEF_ BO_ \"A\" ENUM\n \"unterminated;\n",
-
257 "BA_DEF_DEF_\n \"Unknown\"\n 0;\n",
-
258 "BA_\n \"Unknown\" BO_ 1\n 0;\n",
-
259 "BA_DEF_ BO_ \"A\" STRING\n ; trailing\n"};
-
260 for (const std::string &statement: invalid) {
-
261 fixture.Write("BO_ 1 F: 0 ECU\n" + statement);
-
262 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
263 QVERIFY2(result.HasErrors(), statement.c_str());
-
264 QVERIFY(result.database.frames.empty());
-
265 QCOMPARE(result.diagnostics.back().code, DbcDiagnosticCode::InvalidAttribute);
-
266 QCOMPARE(result.diagnostics.back().lineNumber, std::size_t(2));
-
267 QVERIFY_THROWS_EXCEPTION(std::runtime_error, DbcParser().ParseFile(fixture.path.string()));
-
268 }
-
269}
-
270
-
271void DbcParserTest::invalidAttributes() {
-
272 Fixture fixture;
-
273 const std::vector<std::string> invalid = {
-
274 "BA_DEF_ BO_ \"A\"STRING;",
-
275 "BA_DEF_ BO_\"A\" STRING;",
-
276 "BA_DEF_ BO_ \"A\" \"STRING\";",
-
277 "BA_DEF_ BO_ A STRING;",
-
278 "BA_DEF_ ZZ_ \"A\" STRING;",
-
279 "BA_DEF_ BO_ \"A\" BLOB;",
-
280 "BA_DEF_ BO_ \"A\" FLOAT 2 1;",
-
281 "BA_DEF_ BO_ \"A\" STRING;\nBA_ \"A\" BO_ 1 0;",
-
282 "BA_DEF_ BO_ \"A\" FLOAT 0 1;\nBA_ \"A\" BO_ 1 2;",
-
283 "BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" BO_ -1 0;",
-
284 "BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" BO_ 4294967296 0;",
-
285 "BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" BO_ 536870912 0;",
-
286 "BA_DEF_ BO_ \"A\" INT 2 1;",
-
287 "BA_DEF_ BO_ \"A\" FLOAT nan 1;",
-
288 "BA_DEF_ BO_ \"A\" FLOAT 0 inf;",
-
289 "BA_DEF_ BO_ \"A\" INT 0 9223372036854775808;",
-
290 "BA_DEF_ BO_ \"A\" STRING; trailing",
-
291 "BA_DEF_ BO_ \"A\" ENUM;",
-
292 "BA_DEF_ BO_ \"A\" ENUM \"one\",;",
-
293 "BA_DEF_ BO_ \"A\" ENUM \"one\",\"one\";",
-
294 "BA_DEF_ BO_ \"VFrameFormat\" STRING;",
-
295 "BA_DEF_ SG_ \"GenMsgCycleTime\" INT 0 10;",
-
296 "BA_ \"Unknown\" BO_ 1 0;",
-
297 "BA_DEF_DEF_ \"Unknown\" 0;",
-
298 "BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" BO_ 1 2;",
-
299 "BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" BO_ 1 \"0\";",
-
300 "BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" SG_ 1 S 0;",
-
301 "BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" BO_ 2 0;",
-
302 "BA_DEF_ SG_ \"A\" INT 0 1;\nBA_ \"A\" SG_ 1 Missing 0;",
-
303 "BA_DEF_ BO_ \"A\" INT 0 1;\nBA_DEF_ BO_ \"A\" INT 0 1;",
-
304 "BA_DEF_ BO_ \"A\" INT 0 1;\nBA_DEF_DEF_ \"A\" 0;\nBA_DEF_DEF_ \"A\" 1;",
-
305 "BA_DEF_ BO_ \"A\" STRING;\nBA_ \"A\" BO_ 1 \"bad\\q\";",
-
306 "BA_DEF_ BO_ \"A\" STRING;\nBA_ \"A\" BO_ 1 \"unterminated;",
-
307 "BA_DEF_ BO_ \"A\" ENUM \"one\";\nBA_DEF_DEF_ \"A\" \"missing\";",
-
308 "BA_DEF_ BO_ \"A\" ENUM \"one\";\nBA_ \"A\" BO_ 1 1;",
-
309 "BA_DEF_ BO_ \"GenMsgCycleTime\" INT -1 4294967296;\nBA_DEF_DEF_ \"GenMsgCycleTime\" -1;",
-
310 "BA_DEF_ BO_ \"GenMsgCycleTime\" INT 0 4294967296;\nBA_ \"GenMsgCycleTime\" BO_ 1 4294967296;"};
-
311 for (const std::string &statement: invalid) {
-
312 fixture.Write("BO_ 1 F: 0 ECU\n" + statement + '\n');
-
313 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
314 QVERIFY2(result.HasErrors(), statement.c_str());
-
315 QVERIFY(result.database.frames.empty());
-
316 QCOMPARE(result.diagnostics.back().code, DbcDiagnosticCode::InvalidAttribute);
-
317 QCOMPARE(result.diagnostics.back().lineNumber, std::size_t(2 + std::count(statement.begin(), statement.end(), '\n')));
-
318 QVERIFY_THROWS_EXCEPTION(std::runtime_error, DbcParser().ParseFile(fixture.path.string()));
-
319 }
-
320}
-
321
-
322void DbcParserTest::attributeNumbers() {
-
323 ApplicationLocaleGuard guard;
-
324 std::locale::global(std::locale(std::locale::classic(), new CommaDecimalPunctuation));
-
325 Fixture fixture;
-
326 fixture.Write("BA_DEF_ \"I\" INT -9223372036854775808 +9223372036854775807;\n"
-
327 "BA_DEF_DEF_ \"I\" -9223372036854775808;\n"
-
328 "BA_ \"I\" +9223372036854775807;\n"
-
329 "BA_DEF_ \"H\" HEX -10 +10;\n"
-
330 "BA_DEF_DEF_ \"H\" +0;\n"
-
331 "BA_ \"H\" -10;\n"
-
332 "BA_DEF_ \"F\" FLOAT -1.25e+2 +2.5E2;\n"
-
333 "BA_DEF_DEF_ \"F\" +.5;\n"
-
334 "BA_ \"F\" -1.25e+2;\n"
-
335 "BA_DEF_ BU_ \"NodeDefault\" INT 0 10;\n"
-
336 "BA_DEF_DEF_ \"NodeDefault\" 5;\n"
-
337 "BA_DEF_ EV_ \"EnvironmentDefault\" STRING;\n"
-
338 "BA_DEF_DEF_ \"EnvironmentDefault\" \"default\";\n");
-
339 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
340 QVERIFY(!result.HasErrors());
-
341 QVERIFY(result.diagnostics.empty());
-
342 QCOMPARE(std::get<std::int64_t>(*result.database.attributeDefinitions.at("I").defaultValue), std::numeric_limits<std::int64_t>::min());
-
343 QCOMPARE(std::get<std::int64_t>(result.database.attributes.at("I")), std::numeric_limits<std::int64_t>::max());
-
344 QCOMPARE(std::get<std::int64_t>(*result.database.attributeDefinitions.at("H").defaultValue), std::int64_t(0));
-
345 QCOMPARE(std::get<std::int64_t>(result.database.attributes.at("H")), std::int64_t(-10));
-
346 QCOMPARE(std::get<double>(*result.database.attributeDefinitions.at("F").defaultValue), 0.5);
-
347 QCOMPARE(std::get<double>(result.database.attributes.at("F")), -125.0);
-
348 QCOMPARE(result.database.attributes.size(), std::size_t(3));
-
349 QCOMPARE(std::get<std::int64_t>(*result.database.attributeDefinitions.at("NodeDefault").defaultValue), std::int64_t(5));
-
350 QCOMPARE(std::get<std::string>(*result.database.attributeDefinitions.at("EnvironmentDefault").defaultValue), std::string("default"));
-
351}
-
352
-
353void DbcParserTest::invalidAttributeNumbers_data() {
-
354 QTest::addColumn<QString>("type");
-
355 QTest::addColumn<QString>("number");
-
356 const char *types[] = {"INT", "HEX", "FLOAT"};
-
357 const char *invalid[] = {"+", "-", "++1", "+-1", "--1", "1x", "0x10",
-
358 "nan", "inf", "-inf", "1e9999", "1e-9999"};
-
359 for (const char *type: types) {
-
360 for (const char *number: invalid) {
-
361 const std::string name = std::string(type) + '-' + number;
-
362 QTest::newRow(name.c_str()) << QString(type) << QString(number);
-
363 }
-
364 }
-
365 QTest::newRow("integer-overflow") << QString("INT") << QString("9223372036854775808");
-
366 QTest::newRow("integer-underflow") << QString("INT") << QString("-9223372036854775809");
-
367 QTest::newRow("integer-fraction") << QString("INT") << QString("1.5");
-
368}
-
369
-
370void DbcParserTest::invalidAttributeNumbers() {
-
371 QFETCH(QString, type);
-
372 QFETCH(QString, number);
-
373 const std::string kind = type.toStdString();
-
374 const std::string text = number.toStdString();
-
375 const std::string definition = "BA_DEF_ \"A\" " + kind + " -10 10;\n";
-
376 const std::string statements[] = {
-
377 "BA_DEF_ \"A\" " + kind + ' ' + text + " 10;\n",
-
378 "BA_DEF_ \"A\" " + kind + " -10 " + text + ";\n",
-
379 definition + "BA_DEF_DEF_ \"A\" " + text + ";\n",
-
380 definition + "BA_ \"A\" " + text + ";\n"};
-
381 Fixture fixture;
-
382 for (const std::string &statement: statements) {
-
383 fixture.Write(statement);
-
384 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
385 QVERIFY2(result.HasErrors(), statement.c_str());
-
386 QVERIFY(result.database.attributeDefinitions.empty());
-
387 QCOMPARE(result.diagnostics.back().code, DbcDiagnosticCode::InvalidAttribute);
-
388 QCOMPARE(result.diagnostics.back().lineNumber,
-
389 static_cast<std::size_t>(std::count(statement.begin(), statement.end(), '\n')));
-
390 QVERIFY_THROWS_EXCEPTION(std::runtime_error, DbcParser().ParseFile(fixture.path.string()));
-
391 }
-
392}
-
393
-
394void DbcParserTest::attributeWarnings() {
-
395 Fixture fixture;
-
396 fixture.Write("BA_DEF_ BO_ \"VFrameFormat\" ENUM \"StandardCAN\",\"ExtendedCAN\",\"J1939PG\",\"StandardCAN_FD\";\n"
-
397 "BA_DEF_DEF_ \"VFrameFormat\" \"StandardCAN_FD\";\n"
-
398 "BA_ \"VFrameFormat\" BO_ 1 2;\n"
-
399 "BA_ \"VFrameFormat\" BO_ 2147483649 0;\n"
-
400 "BO_ 1 F: 0 ECU\nBO_ 2147483649 E: 0 ECU\nBO_ 2 Default: 0 ECU\n");
-
401 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
402 QVERIFY(!result.HasErrors());
-
403 QCOMPARE(result.diagnostics.size(), std::size_t(3));
-
404 const std::size_t lines[] = {3U, 4U, 2U};
-
405 for (std::size_t index = 0; index < 3U; ++index) {
-
406 QCOMPARE(result.diagnostics[index].code, DbcDiagnosticCode::UnsupportedAttribute);
-
407 QCOMPARE(result.diagnostics[index].severity, DbcDiagnosticSeverity::Warning);
-
408 QCOMPARE(result.diagnostics[index].lineNumber, lines[index]);
-
409 QCOMPARE(result.diagnostics[index].filePath, fixture.path.string());
-
410 }
-
411 QCOMPARE(result.database.frames[0].frameFormat.value(), std::string("J1939PG"));
-
412 QVERIFY(!result.database.frames[0].hasPgn);
-
413 QVERIFY(result.database.frames[1].isExtended);
-
414}
-
415
-
416void DbcParserTest::diagnostics() {
-
417 Fixture fixture;
-
418 fixture.Write("VERSION \"test\"\n"
-
419 "NS_ :\n"
-
420 " CM_\n"
-
421 " BA_\n"
-
422 " SIG_VALTYPE_\n"
-
423 " SG_MUL_VAL_\n\n"
-
424 "BS_:\n"
-
425 "BU_: ECU\n"
-
426 "BO_ 1 Frame: 1 ECU\n"
-
427 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
-
428 "SIG_VALTYPE_ 1 Value : 1;\n"
-
429 "BA_REL_ \"SignalType\" SG_ 1 Value 1;\n"
-
430 "VENDOR_EXTENSION_ data;\n");
-
431 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
432 CHECK(!result.HasErrors() && result.database.frames.size() == 1U,
-
433 "Warnings preserve the supported database");
-
434 QCOMPARE(result.diagnostics.size(), std::size_t(3U));
-
435 for (std::size_t index = 0U; index < result.diagnostics.size(); ++index) {
-
436 const DbcDiagnostic &diagnostic = result.diagnostics[index];
-
437 CHECK(diagnostic.severity == DbcDiagnosticSeverity::Warning &&
-
438 diagnostic.code == DbcDiagnosticCode::UnsupportedStatement,
-
439 "Unsupported statements have structured warning severity and code");
-
440 QCOMPARE(diagnostic.filePath, fixture.path.string());
-
441 QCOMPARE(diagnostic.lineNumber, 12U + index);
-
442 CHECK(diagnostic.message.find("ignored") != std::string::npos,
-
443 "Warning explains unsupported syntax");
-
444 }
-
445 CHECK(DbcParser().ParseFile(fixture.path.string()).frames.size() == 1U,
-
446 "Legacy API accepts files with warnings");
-
447 fixture.Write("VERSION \"\"\nNS_:\nSIG_VALTYPE_\n\tBA_\n BS_ :\n BU_ : ECU\n");
-
448 const DbcParseResult headers = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
449 CHECK(headers.diagnostics.empty(), "Common header variants stay quiet");
-
450 fixture.Write("NS_ :\n BA_\n BO_\n");
-
451 CHECK(DbcParser().ParseFileWithDiagnostics(fixture.path.string()).HasErrors(),
-
452 "Namespace handling does not swallow a malformed frame");
-
453}
-
454
-
455void DbcParserTest::diagnosticErrors() {
-
456 Fixture fixture;
-
457 struct ErrorCase {
-
458 const char *text;
-
459 DbcDiagnosticCode code;
-
460 std::size_t line;
-
461 };
-
462 const ErrorCase cases[] = {
-
463 {"UNKNOWN_ data;\n\nBO_ invalid Frame: 1 ECU\n",
-
464 DbcDiagnosticCode::InvalidStatement, 3U},
-
465 {"BO_ 1 Frame: 1 ECU\nSG_MUL_VAL_ 1 Value Mode 1-2;\n",
-
466 DbcDiagnosticCode::InvalidMultiplexing, 2U},
-
467 {"BO_ 1 Frame: 1 ECU\nVAL_ 1 Missing 0 \"Off\";\n",
-
468 DbcDiagnosticCode::InvalidValueDescription, 2U},
-
469 {"BO_ 1 Frame: 1 ECU\nSG_ Value m1 : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
-
470 DbcDiagnosticCode::InvalidMultiplexing, 2U},
-
471 {"BO_ 1 Frame: 1 ECU\nSG_ Mode M : 0|8@1- (1,0) [-128|127] \"\" ECU\n",
+
119 void longSignalDefinitions_data();
+
120 void longSignalDefinitions();
+
121 void physicalNumbersCppLocale();
+
122 void physicalNumbersCLocale();
+
123 void dispatch();
+
124 void independentSessions();
+
125 void valueDescriptions();
+
126 void invalidValueDescriptions();
+
127};
+
128
+
129void DbcParserTest::attributes() {
+
130 Fixture fixture;
+
131 fixture.Write(
+
132 "BA_ \"GenMsgCycleTime\" BO_ 1 25;\n"
+
133 "BA_ \"VFrameFormat\" BO_ 2147483649 1;\n"
+
134 "BA_ \"Label\" SG_ 1 Value \"a\\\"b\\\\c\";\n"
+
135 "BA_ \"NodeLabel\" BU_ ECU \"node\";\n"
+
136 "BA_ \"Environment\" EV_ Env 2;\n"
+
137 "BA_DEF_DEF_ \"GenMsgCycleTime\" 100;\n"
+
138 "BA_DEF_DEF_ \"VFrameFormat\" \"StandardCAN\";\n"
+
139 "BA_DEF_DEF_ \"Label\" \"default\";\n"
+
140 "BA_DEF_DEF_ \"BusLabel\" \"CAN\";\n"
+
141 "BA_DEF_DEF_ \"Ratio\" 1.5e-1;\n"
+
142 "BA_DEF_ BO_ \"GenMsgCycleTime\" INT 0 10000;\n"
+
143 "BA_DEF_ BO_ \"VFrameFormat\" ENUM \"StandardCAN\",\"ExtendedCAN\",\"StandardCAN_FD\";\n"
+
144 "BA_DEF_ SG_ \"Label\" STRING;\n"
+
145 "BA_DEF_ \"BusLabel\" STRING;\n"
+
146 "BA_DEF_ BO_ \"Ratio\" FLOAT -1 1;\n"
+
147 "BA_DEF_ BU_ \"NodeLabel\" STRING;\n"
+
148 "BA_DEF_ EV_ \"Environment\" HEX 0 255;\n"
+
149 "BO_ 1 Standard: 1 ECU\n"
+
150 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
+
151 "BO_ 2147483649 Extended: 1 ECU\n"
+
152 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n");
+
153 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
154 QVERIFY(!result.HasErrors());
+
155 QVERIFY(result.diagnostics.empty());
+
156 const DbcDatabase &database = result.database;
+
157 QCOMPARE(database.attributeDefinitions.size(), std::size_t(7));
+
158 QCOMPARE(database.attributeAssignments.size(), std::size_t(5));
+
159 QCOMPARE(std::get<std::string>(database.attributes.at("BusLabel")), std::string("CAN"));
+
160 QCOMPARE(database.frames[0].cycleTimeMs.value(), 25U);
+
161 QCOMPARE(database.frames[1].cycleTimeMs.value(), 100U);
+
162 QCOMPARE(database.frames[1].frameFormat.value(), std::string("ExtendedCAN"));
+
163 QVERIFY(!database.frames[1].hasPgn);
+
164 QCOMPARE(std::get<double>(database.frames[0].attributes.at("Ratio")), 0.15);
+
165 QCOMPARE(std::get<std::string>(database.frames[0].signals[0].attributes.at("Label")), std::string("a\"b\\c"));
+
166 QCOMPARE(std::get<std::string>(database.frames[1].signals[0].attributes.at("Label")), std::string("default"));
+
167 const DecodeDatabase runtime = DbcDecodeBuilder().Build(database);
+
168 QCOMPARE(runtime.GetFrames()[0].cycleTimeMs.value(), 25U);
+
169 QCOMPARE(runtime.GetFrames()[1].frameFormat.value(), std::string("ExtendedCAN"));
+
170 QCOMPARE(runtime.GetFrames()[0].signals[0].attributes, database.frames[0].signals[0].attributes);
+
171 const std::unique_ptr<TreeNode> tree = DbcTreeBuilder().Build(database);
+
172 QCOMPARE(tree->GetChild(0)->GetFrame()->cycleTimeMs.value(), 25U);
+
173 QCOMPARE(tree->GetChild(0)->GetChild(0)->GetSignal()->attributes, database.frames[0].signals[0].attributes);
+
174 fixture.Write("BA_DEF_ BO_ \"Period\" INT 0 10;\nBA_DEF_DEF_ \"Period\" 1;\n"
+
175 "BA_ \"Period\" BO_ 1 2;\nBA_ \"Period\" BO_ 1 3;\nBO_ 1 F: 0 ECU\n");
+
176 const DbcDatabase repeated = DbcParser().ParseFile(fixture.path.string());
+
177 QCOMPARE(std::get<std::int64_t>(repeated.frames[0].attributes.at("Period")), std::int64_t(3));
+
178
+
179 fixture.Write("BA_DEF_ BO_ \"GenMsgCycleTime\" INT 0 4294967295;\n"
+
180 "BA_DEF_DEF_ \"GenMsgCycleTime\" 0;\n"
+
181 "BA_ \"GenMsgCycleTime\" BO_ 1 4294967295;\n"
+
182 "BA_DEF_ \"Wide\" INT -9223372036854775808 9223372036854775807;\n"
+
183 "BA_DEF_DEF_ \"Wide\" -9223372036854775808;\n"
+
184 "BA_DEF_ SG_ \"State\" ENUM \"off\",\"on\";\n"
+
185 "BA_DEF_DEF_ \"State\" 1;\n"
+
186 "BO_ 1 F: 0 ECU\nBO_ 2 Empty: 0 ECU\n");
+
187 const DbcDatabase boundaries = DbcParser().ParseFile(fixture.path.string());
+
188 QCOMPARE(boundaries.frames[0].cycleTimeMs.value(), std::numeric_limits<std::uint32_t>::max());
+
189 QCOMPARE(boundaries.frames[1].cycleTimeMs.value(), 0U);
+
190 QVERIFY(!boundaries.frames[0].frameFormat);
+
191 QCOMPARE(std::get<std::int64_t>(boundaries.attributes.at("Wide")), std::numeric_limits<std::int64_t>::min());
+
192 QCOMPARE(std::get<std::int64_t>(*boundaries.attributeDefinitions.at("State").defaultValue), std::int64_t(1));
+
193}
+
194
+
195void DbcParserTest::multilineAttributes() {
+
196 Fixture fixture;
+
197 fixture.Write(R"DBC(BA_
+
198 "GenMsgCycleTime" BO_ 1
+
199 25;
+
200BA_DEF_ BO_ "VFrameFormat" ENUM
+
201 "StandardCAN",
+
202 "StandardCAN_FD";
+
203BA_DEF_DEF_
+
204 "VFrameFormat"
+
205 "StandardCAN_FD";
+
206BA_DEF_ BO_ "GenMsgCycleTime"
+
207 INT 0 100;
+
208BA_DEF_DEF_ "GenMsgCycleTime"
+
209 50;
+
210BA_DEF_ SG_ "Label" STRING;
+
211BA_ "Label" SG_ 1 Value
+
212 "first; \"quoted\"
+
213second\\path";
+
214BA_ "VFrameFormat" BO_ 1
+
215 0;
+
216BO_ 1 F: 1 ECU
+
217SG_ Value : 0|8@1+ (1,0) [0|255] "" ECU
+
218BO_ 2 Default: 0 ECU
+
219)DBC");
+
220 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
221 QVERIFY(!result.HasErrors());
+
222 QCOMPARE(result.database.frames.size(), std::size_t(2));
+
223 QCOMPARE(result.database.frames[0].cycleTimeMs.value(), 25U);
+
224 QCOMPARE(result.database.frames[1].cycleTimeMs.value(), 50U);
+
225 QCOMPARE(result.database.frames[0].frameFormat.value(), std::string("StandardCAN"));
+
226 QCOMPARE(result.database.frames[1].frameFormat.value(), std::string("StandardCAN_FD"));
+
227 QCOMPARE(std::get<std::string>(result.database.frames[0].signals[0].attributes.at("Label")),
+
228 std::string("first; \"quoted\"\nsecond\\path"));
+
229 QCOMPARE(result.diagnostics.size(), std::size_t(1));
+
230 QCOMPARE(result.diagnostics[0].code, DbcDiagnosticCode::UnsupportedAttribute);
+
231 QCOMPARE(result.diagnostics[0].lineNumber, std::size_t(7));
+
232
+
233 fixture.Write("BA_DEF_ BO_ \"VFrameFormat\" ENUM\r\n \"StandardCAN\",\r\n \"ExtendedCAN_FD\";\r\n"
+
234 "BA_\r\n \"VFrameFormat\" BO_ 1\r\n 1;\r\nBO_ 1 F: 0 ECU\r\n");
+
235 const DbcParseResult explicitFormat = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
236 QVERIFY(!explicitFormat.HasErrors());
+
237 QCOMPARE(explicitFormat.diagnostics.size(), std::size_t(1));
+
238 QCOMPARE(explicitFormat.diagnostics[0].code, DbcDiagnosticCode::UnsupportedAttribute);
+
239 QCOMPARE(explicitFormat.diagnostics[0].lineNumber, std::size_t(4));
+
240 QCOMPARE(explicitFormat.database.frames[0].frameFormat.value(), std::string("ExtendedCAN_FD"));
+
241
+
242 fixture.Write("BA_DEF_ \"Text\" STRING;\n BA_ \"Text\" \"first \nsecond\";\n"
+
243 "UnsupportedStatement\n");
+
244 const DbcParseResult whitespace = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
245 QVERIFY(!whitespace.HasErrors());
+
246 QCOMPARE(std::get<std::string>(whitespace.database.attributes.at("Text")), std::string("first \nsecond"));
+
247 QCOMPARE(whitespace.diagnostics.size(), std::size_t(1));
+
248 QCOMPARE(whitespace.diagnostics[0].code, DbcDiagnosticCode::UnsupportedStatement);
+
249 QCOMPARE(whitespace.diagnostics[0].lineNumber, std::size_t(4));
+
250}
+
251
+
252void DbcParserTest::invalidMultilineAttributes() {
+
253 Fixture fixture;
+
254 const std::vector<std::string> invalid = {
+
255 "BA_DEF_\n BO_ \"A\" INT\n 10 0;\n",
+
256 "BA_DEF_ BO_ \"A\" ENUM\n \"one\",\n;\n",
+
257 "BA_DEF_ BO_ \"A\" STRING\n",
+
258 "BA_DEF_ BO_ \"A\" ENUM\n \"unterminated;\n",
+
259 "BA_DEF_DEF_\n \"Unknown\"\n 0;\n",
+
260 "BA_\n \"Unknown\" BO_ 1\n 0;\n",
+
261 "BA_DEF_ BO_ \"A\" STRING\n ; trailing\n"};
+
262 for (const std::string &statement: invalid) {
+
263 fixture.Write("BO_ 1 F: 0 ECU\n" + statement);
+
264 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
265 QVERIFY2(result.HasErrors(), statement.c_str());
+
266 QVERIFY(result.database.frames.empty());
+
267 QCOMPARE(result.diagnostics.back().code, DbcDiagnosticCode::InvalidAttribute);
+
268 QCOMPARE(result.diagnostics.back().lineNumber, std::size_t(2));
+
269 QVERIFY_THROWS_EXCEPTION(std::runtime_error, DbcParser().ParseFile(fixture.path.string()));
+
270 }
+
271}
+
272
+
273void DbcParserTest::invalidAttributes() {
+
274 Fixture fixture;
+
275 const std::vector<std::string> invalid = {
+
276 "BA_DEF_ BO_ \"A\"STRING;",
+
277 "BA_DEF_ BO_\"A\" STRING;",
+
278 "BA_DEF_ BO_ \"A\" \"STRING\";",
+
279 "BA_DEF_ BO_ A STRING;",
+
280 "BA_DEF_ ZZ_ \"A\" STRING;",
+
281 "BA_DEF_ BO_ \"A\" BLOB;",
+
282 "BA_DEF_ BO_ \"A\" FLOAT 2 1;",
+
283 "BA_DEF_ BO_ \"A\" STRING;\nBA_ \"A\" BO_ 1 0;",
+
284 "BA_DEF_ BO_ \"A\" FLOAT 0 1;\nBA_ \"A\" BO_ 1 2;",
+
285 "BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" BO_ -1 0;",
+
286 "BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" BO_ 4294967296 0;",
+
287 "BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" BO_ 536870912 0;",
+
288 "BA_DEF_ BO_ \"A\" INT 2 1;",
+
289 "BA_DEF_ BO_ \"A\" FLOAT nan 1;",
+
290 "BA_DEF_ BO_ \"A\" FLOAT 0 inf;",
+
291 "BA_DEF_ BO_ \"A\" INT 0 9223372036854775808;",
+
292 "BA_DEF_ BO_ \"A\" STRING; trailing",
+
293 "BA_DEF_ BO_ \"A\" ENUM;",
+
294 "BA_DEF_ BO_ \"A\" ENUM \"one\",;",
+
295 "BA_DEF_ BO_ \"A\" ENUM \"one\",\"one\";",
+
296 "BA_DEF_ BO_ \"VFrameFormat\" STRING;",
+
297 "BA_DEF_ SG_ \"GenMsgCycleTime\" INT 0 10;",
+
298 "BA_ \"Unknown\" BO_ 1 0;",
+
299 "BA_DEF_DEF_ \"Unknown\" 0;",
+
300 "BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" BO_ 1 2;",
+
301 "BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" BO_ 1 \"0\";",
+
302 "BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" SG_ 1 S 0;",
+
303 "BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" BO_ 2 0;",
+
304 "BA_DEF_ SG_ \"A\" INT 0 1;\nBA_ \"A\" SG_ 1 Missing 0;",
+
305 "BA_DEF_ BO_ \"A\" INT 0 1;\nBA_DEF_ BO_ \"A\" INT 0 1;",
+
306 "BA_DEF_ BO_ \"A\" INT 0 1;\nBA_DEF_DEF_ \"A\" 0;\nBA_DEF_DEF_ \"A\" 1;",
+
307 "BA_DEF_ BO_ \"A\" STRING;\nBA_ \"A\" BO_ 1 \"bad\\q\";",
+
308 "BA_DEF_ BO_ \"A\" STRING;\nBA_ \"A\" BO_ 1 \"unterminated;",
+
309 "BA_DEF_ BO_ \"A\" ENUM \"one\";\nBA_DEF_DEF_ \"A\" \"missing\";",
+
310 "BA_DEF_ BO_ \"A\" ENUM \"one\";\nBA_ \"A\" BO_ 1 1;",
+
311 "BA_DEF_ BO_ \"GenMsgCycleTime\" INT -1 4294967296;\nBA_DEF_DEF_ \"GenMsgCycleTime\" -1;",
+
312 "BA_DEF_ BO_ \"GenMsgCycleTime\" INT 0 4294967296;\nBA_ \"GenMsgCycleTime\" BO_ 1 4294967296;"};
+
313 for (const std::string &statement: invalid) {
+
314 fixture.Write("BO_ 1 F: 0 ECU\n" + statement + '\n');
+
315 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
316 QVERIFY2(result.HasErrors(), statement.c_str());
+
317 QVERIFY(result.database.frames.empty());
+
318 QCOMPARE(result.diagnostics.back().code, DbcDiagnosticCode::InvalidAttribute);
+
319 QCOMPARE(result.diagnostics.back().lineNumber, std::size_t(2 + std::count(statement.begin(), statement.end(), '\n')));
+
320 QVERIFY_THROWS_EXCEPTION(std::runtime_error, DbcParser().ParseFile(fixture.path.string()));
+
321 }
+
322}
+
323
+
324void DbcParserTest::attributeNumbers() {
+
325 ApplicationLocaleGuard guard;
+
326 std::locale::global(std::locale(std::locale::classic(), new CommaDecimalPunctuation));
+
327 Fixture fixture;
+
328 fixture.Write("BA_DEF_ \"I\" INT -9223372036854775808 +9223372036854775807;\n"
+
329 "BA_DEF_DEF_ \"I\" -9223372036854775808;\n"
+
330 "BA_ \"I\" +9223372036854775807;\n"
+
331 "BA_DEF_ \"H\" HEX -10 +10;\n"
+
332 "BA_DEF_DEF_ \"H\" +0;\n"
+
333 "BA_ \"H\" -10;\n"
+
334 "BA_DEF_ \"F\" FLOAT -1.25e+2 +2.5E2;\n"
+
335 "BA_DEF_DEF_ \"F\" +.5;\n"
+
336 "BA_ \"F\" -1.25e+2;\n"
+
337 "BA_DEF_ BU_ \"NodeDefault\" INT 0 10;\n"
+
338 "BA_DEF_DEF_ \"NodeDefault\" 5;\n"
+
339 "BA_DEF_ EV_ \"EnvironmentDefault\" STRING;\n"
+
340 "BA_DEF_DEF_ \"EnvironmentDefault\" \"default\";\n");
+
341 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
342 QVERIFY(!result.HasErrors());
+
343 QVERIFY(result.diagnostics.empty());
+
344 QCOMPARE(std::get<std::int64_t>(*result.database.attributeDefinitions.at("I").defaultValue), std::numeric_limits<std::int64_t>::min());
+
345 QCOMPARE(std::get<std::int64_t>(result.database.attributes.at("I")), std::numeric_limits<std::int64_t>::max());
+
346 QCOMPARE(std::get<std::int64_t>(*result.database.attributeDefinitions.at("H").defaultValue), std::int64_t(0));
+
347 QCOMPARE(std::get<std::int64_t>(result.database.attributes.at("H")), std::int64_t(-10));
+
348 QCOMPARE(std::get<double>(*result.database.attributeDefinitions.at("F").defaultValue), 0.5);
+
349 QCOMPARE(std::get<double>(result.database.attributes.at("F")), -125.0);
+
350 QCOMPARE(result.database.attributes.size(), std::size_t(3));
+
351 QCOMPARE(std::get<std::int64_t>(*result.database.attributeDefinitions.at("NodeDefault").defaultValue), std::int64_t(5));
+
352 QCOMPARE(std::get<std::string>(*result.database.attributeDefinitions.at("EnvironmentDefault").defaultValue), std::string("default"));
+
353}
+
354
+
355void DbcParserTest::invalidAttributeNumbers_data() {
+
356 QTest::addColumn<QString>("type");
+
357 QTest::addColumn<QString>("number");
+
358 const char *types[] = {"INT", "HEX", "FLOAT"};
+
359 const char *invalid[] = {"+", "-", "++1", "+-1", "--1", "1x", "0x10",
+
360 "nan", "inf", "-inf", "1e9999", "1e-9999"};
+
361 for (const char *type: types) {
+
362 for (const char *number: invalid) {
+
363 const std::string name = std::string(type) + '-' + number;
+
364 QTest::newRow(name.c_str()) << QString(type) << QString(number);
+
365 }
+
366 }
+
367 QTest::newRow("integer-overflow") << QString("INT") << QString("9223372036854775808");
+
368 QTest::newRow("integer-underflow") << QString("INT") << QString("-9223372036854775809");
+
369 QTest::newRow("integer-fraction") << QString("INT") << QString("1.5");
+
370}
+
371
+
372void DbcParserTest::invalidAttributeNumbers() {
+
373 QFETCH(QString, type);
+
374 QFETCH(QString, number);
+
375 const std::string kind = type.toStdString();
+
376 const std::string text = number.toStdString();
+
377 const std::string definition = "BA_DEF_ \"A\" " + kind + " -10 10;\n";
+
378 const std::string statements[] = {
+
379 "BA_DEF_ \"A\" " + kind + ' ' + text + " 10;\n",
+
380 "BA_DEF_ \"A\" " + kind + " -10 " + text + ";\n",
+
381 definition + "BA_DEF_DEF_ \"A\" " + text + ";\n",
+
382 definition + "BA_ \"A\" " + text + ";\n"};
+
383 Fixture fixture;
+
384 for (const std::string &statement: statements) {
+
385 fixture.Write(statement);
+
386 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
387 QVERIFY2(result.HasErrors(), statement.c_str());
+
388 QVERIFY(result.database.attributeDefinitions.empty());
+
389 QCOMPARE(result.diagnostics.back().code, DbcDiagnosticCode::InvalidAttribute);
+
390 QCOMPARE(result.diagnostics.back().lineNumber,
+
391 static_cast<std::size_t>(std::count(statement.begin(), statement.end(), '\n')));
+
392 QVERIFY_THROWS_EXCEPTION(std::runtime_error, DbcParser().ParseFile(fixture.path.string()));
+
393 }
+
394}
+
395
+
396void DbcParserTest::attributeWarnings() {
+
397 Fixture fixture;
+
398 fixture.Write("BA_DEF_ BO_ \"VFrameFormat\" ENUM \"StandardCAN\",\"ExtendedCAN\",\"J1939PG\",\"StandardCAN_FD\";\n"
+
399 "BA_DEF_DEF_ \"VFrameFormat\" \"StandardCAN_FD\";\n"
+
400 "BA_ \"VFrameFormat\" BO_ 1 2;\n"
+
401 "BA_ \"VFrameFormat\" BO_ 2147483649 0;\n"
+
402 "BO_ 1 F: 0 ECU\nBO_ 2147483649 E: 0 ECU\nBO_ 2 Default: 0 ECU\n");
+
403 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
404 QVERIFY(!result.HasErrors());
+
405 QCOMPARE(result.diagnostics.size(), std::size_t(3));
+
406 const std::size_t lines[] = {3U, 4U, 2U};
+
407 for (std::size_t index = 0; index < 3U; ++index) {
+
408 QCOMPARE(result.diagnostics[index].code, DbcDiagnosticCode::UnsupportedAttribute);
+
409 QCOMPARE(result.diagnostics[index].severity, DbcDiagnosticSeverity::Warning);
+
410 QCOMPARE(result.diagnostics[index].lineNumber, lines[index]);
+
411 QCOMPARE(result.diagnostics[index].filePath, fixture.path.string());
+
412 }
+
413 QCOMPARE(result.database.frames[0].frameFormat.value(), std::string("J1939PG"));
+
414 QVERIFY(!result.database.frames[0].hasPgn);
+
415 QVERIFY(result.database.frames[1].isExtended);
+
416}
+
417
+
418void DbcParserTest::diagnostics() {
+
419 Fixture fixture;
+
420 fixture.Write("VERSION \"test\"\n"
+
421 "NS_ :\n"
+
422 " CM_\n"
+
423 " BA_\n"
+
424 " SIG_VALTYPE_\n"
+
425 " SG_MUL_VAL_\n\n"
+
426 "BS_:\n"
+
427 "BU_: ECU\n"
+
428 "BO_ 1 Frame: 1 ECU\n"
+
429 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
+
430 "SIG_VALTYPE_ 1 Value : 1;\n"
+
431 "BA_REL_ \"SignalType\" SG_ 1 Value 1;\n"
+
432 "VENDOR_EXTENSION_ data;\n");
+
433 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
434 CHECK(!result.HasErrors() && result.database.frames.size() == 1U,
+
435 "Warnings preserve the supported database");
+
436 QCOMPARE(result.diagnostics.size(), std::size_t(3U));
+
437 for (std::size_t index = 0U; index < result.diagnostics.size(); ++index) {
+
438 const DbcDiagnostic &diagnostic = result.diagnostics[index];
+
439 CHECK(diagnostic.severity == DbcDiagnosticSeverity::Warning &&
+
440 diagnostic.code == DbcDiagnosticCode::UnsupportedStatement,
+
441 "Unsupported statements have structured warning severity and code");
+
442 QCOMPARE(diagnostic.filePath, fixture.path.string());
+
443 QCOMPARE(diagnostic.lineNumber, 12U + index);
+
444 CHECK(diagnostic.message.find("ignored") != std::string::npos,
+
445 "Warning explains unsupported syntax");
+
446 }
+
447 CHECK(DbcParser().ParseFile(fixture.path.string()).frames.size() == 1U,
+
448 "Legacy API accepts files with warnings");
+
449 fixture.Write("VERSION \"\"\nNS_:\nSIG_VALTYPE_\n\tBA_\n BS_ :\n BU_ : ECU\n");
+
450 const DbcParseResult headers = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
451 CHECK(headers.diagnostics.empty(), "Common header variants stay quiet");
+
452 fixture.Write("NS_ :\n BA_\n BO_\n");
+
453 CHECK(DbcParser().ParseFileWithDiagnostics(fixture.path.string()).HasErrors(),
+
454 "Namespace handling does not swallow a malformed frame");
+
455}
+
456
+
457void DbcParserTest::diagnosticErrors() {
+
458 Fixture fixture;
+
459 struct ErrorCase {
+
460 const char *text;
+
461 DbcDiagnosticCode code;
+
462 std::size_t line;
+
463 };
+
464 const ErrorCase cases[] = {
+
465 {"UNKNOWN_ data;\n\nBO_ invalid Frame: 1 ECU\n",
+
466 DbcDiagnosticCode::InvalidStatement, 3U},
+
467 {"BO_ 1 Frame: 1 ECU\nSG_MUL_VAL_ 1 Value Mode 1-2;\n",
+
468 DbcDiagnosticCode::InvalidMultiplexing, 2U},
+
469 {"BO_ 1 Frame: 1 ECU\nVAL_ 1 Missing 0 \"Off\";\n",
+
470 DbcDiagnosticCode::InvalidValueDescription, 2U},
+
471 {"BO_ 1 Frame: 1 ECU\nSG_ Value m1 : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
472 DbcDiagnosticCode::InvalidMultiplexing, 2U},
-
473 {"VAL_ broken\n", DbcDiagnosticCode::InvalidValueDescription, 1U},
-
474 {"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
-
475 DbcDiagnosticCode::InvalidStatement, 1U},
-
476 {"BO_ 1 Frame: 1 ECU\nBO_ 1 Duplicate: 1 ECU\n",
-
477 DbcDiagnosticCode::InvalidStatement, 2U},
-
478 {"BO_ 1 Frame: 1 ECU\nSG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
-
479 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
-
480 DbcDiagnosticCode::InvalidStatement, 3U},
-
481 {"BO_ 1 Frame: 1 ECU\nSG_ Value : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
-
482 DbcDiagnosticCode::InvalidStatement, 2U},
-
483 {"BO_ 1 Frame: 1 ECU\nVAL_ 2 Missing 0 \"Off\";\n",
-
484 DbcDiagnosticCode::InvalidValueDescription, 2U},
-
485 {"BO_ 1 Frame: 1 ECU\nSG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
-
486 "VAL_ 1 Value 0 \"Off\";\nVAL_ 1 Value 0 \"Duplicate\";\n",
-
487 DbcDiagnosticCode::InvalidValueDescription, 4U},
-
488 {"BO_ 1 Frame: 2 ECU\nSG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
-
489 "SG_ Value m2 : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
-
490 DbcDiagnosticCode::InvalidMultiplexing, 3U},
-
491 {"BO_ 1 Frame: 2 ECU\nSG_ Mode M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
-
492 "SG_ Other M : 8|8@1- (1,0) [-128|127] \"\" ECU\n",
-
493 DbcDiagnosticCode::InvalidMultiplexing, 3U},
-
494 {"BO_ 1 Frame: 1 ECU\nSG_ First : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
-
495 "SG_ Second : 7|8@0+ (1,0) [0|255] \"\" ECU\n",
-
496 DbcDiagnosticCode::InvalidMultiplexing, 3U}};
-
497 for (const ErrorCase &test : cases) {
-
498 fixture.Write(test.text);
-
499 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
500 CHECK(result.HasErrors() && result.database.frames.empty(),
-
501 "Failed parse never returns a partial database");
-
502 const DbcDiagnostic &diagnostic = result.diagnostics.back();
-
503 CHECK(diagnostic.severity == DbcDiagnosticSeverity::Error && diagnostic.code == test.code,
-
504 "Error has expected severity and code");
-
505 QCOMPARE(diagnostic.filePath, fixture.path.string());
-
506 QCOMPARE(diagnostic.lineNumber, test.line);
-
507 CHECK(!diagnostic.message.empty(), "Error includes a message");
-
508 const bool hasWarning = std::string(test.text).find("UNKNOWN_") == 0U;
-
509 QCOMPARE(result.diagnostics.size(), hasWarning ? std::size_t(2U) : std::size_t(1U));
-
510 try {
-
511 DbcParser().ParseFile(fixture.path.string());
-
512 CHECK(false, "Legacy parser must throw");
-
513 } catch (const std::runtime_error &error) {
-
514 QCOMPARE(std::string(error.what()), fixture.path.string() + ":" +
-
515 std::to_string(test.line) + ": " + diagnostic.message);
-
516 }
-
517 }
-
518 const std::string missingPath = fixture.path.string() + ".missing";
-
519 const DbcParseResult missing = DbcParser().ParseFileWithDiagnostics(missingPath);
-
520 CHECK(missing.HasErrors() && missing.database.frames.empty(), "Missing file returns error");
-
521 QCOMPARE(missing.diagnostics.size(), std::size_t(1U));
-
522 CHECK(missing.diagnostics[0].code == DbcDiagnosticCode::FileOpenError,
-
523 "Missing file has a distinct code");
-
524 QCOMPARE(missing.diagnostics[0].lineNumber, std::size_t(0U));
-
525 QCOMPARE(missing.diagnostics[0].filePath, missingPath);
-
526 QCOMPARE(missing.diagnostics[0].message, "Failed to open DBC file: " + missingPath);
-
527 try {
-
528 DbcParser().ParseFile(missingPath);
-
529 CHECK(false, "Legacy API throws on missing file");
-
530 } catch (const std::runtime_error &error) {
-
531 QCOMPARE(std::string(error.what()), missing.diagnostics[0].message);
-
532 }
-
533 const std::string directoryPath = fixture.path.parent_path().string();
-
534 const DbcParseResult directory = DbcParser().ParseFileWithDiagnostics(directoryPath);
-
535 CHECK(directory.HasErrors() && directory.database.frames.empty(),
-
536 "Directory cannot be parsed as a file");
-
537 QCOMPARE(directory.diagnostics.size(), std::size_t(1U));
-
538 CHECK(directory.diagnostics[0].code == DbcDiagnosticCode::FileReadError ||
-
539 directory.diagnostics[0].code == DbcDiagnosticCode::FileOpenError,
-
540 "Directory produces a file-level error on supported platforms");
-
541 QCOMPARE(directory.diagnostics[0].lineNumber, std::size_t(0U));
-
542 QCOMPARE(directory.diagnostics[0].filePath, directoryPath);
-
543#ifdef __linux__
-
544 CHECK(directory.diagnostics[0].code == DbcDiagnosticCode::FileReadError,
-
545 "Linux directory input exercises the read-error path");
-
546 QCOMPARE(directory.diagnostics[0].message, "Failed to read DBC file: " + directoryPath);
-
547#endif
-
548 try {
-
549 DbcParser().ParseFile(directoryPath);
-
550 CHECK(false, "Legacy API throws on unreadable input");
-
551 } catch (const std::runtime_error &error) {
-
552 QCOMPARE(std::string(error.what()), directory.diagnostics[0].message);
-
553 }
-
554 fixture.Write("BO_ 1 Frame: 1 ECU\n");
-
555 CHECK(DbcParser().ParseFileWithDiagnostics(fixture.path.string()).diagnostics.empty(),
-
556 "Diagnostics belong to an independent parse session");
-
557}
-
558
-
559void DbcParserTest::diagnosticUnsupportedStatements() {
-
560 Fixture fixture;
-
561 const std::string definitions =
-
562 "BO_ 1 Frame: 1 ECU\nSG_ Value : 0|8@1+ (2,10) [10|520] \"V\" ECU\n"
-
563 "CM_ SG_ 1 Value \"Signal comment\";\nVAL_ 1 Value 1 \"On\";\n";
-
564 const std::vector<std::string> statements = {
-
565 "SIG_VALTYPE_ 1 Value : 1;", "SIG_VALTYPE_ 1 Value : 2;",
-
566 "BA_REL_ \"SignalType\" SG_ 1 Value 1;",
-
567 "BA_DEF_REL_ BU_SG_REL_ \"Relation\" INT 0 1;",
-
568 "BA_REL_ \"Relation\" BU_SG_REL_ ECU SG_ 1 Value 1;",
-
569 "SGTYPE_ Type : 8@1+ (1,0) [0|255] \"\" 0, Table;",
-
570 "SIG_TYPE_REF_ 1 Value : Type;",
-
571 "EV_ Environment : 0 [0|1] \"\" 0 1 DUMMY_NODE_VECTOR0 ECU;",
-
572 "ENVVAR_DATA_ Environment : 4;",
-
573 "VAL_TABLE_ Table 0 \"Off\" 1 \"On\";",
-
574 "BO_TX_BU_ 1 : ECU;", "VENDOR_EXTENSION_ data;", "UNKNOWN_"
-
575 };
-
576 for (const std::string &statement : statements) {
-
577 fixture.Write(definitions + statement + '\n');
-
578 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
579 CHECK(!result.HasErrors(), statement);
-
580 QCOMPARE(result.diagnostics.size(), std::size_t(1U));
-
581 const DbcDiagnostic &warning = result.diagnostics[0];
-
582 CHECK(warning.severity == DbcDiagnosticSeverity::Warning &&
-
583 warning.code == DbcDiagnosticCode::UnsupportedStatement, statement);
-
584 QCOMPARE(warning.filePath, fixture.path.string());
-
585 QCOMPARE(warning.lineNumber, std::size_t(5U));
-
586 const std::string keyword = statement.substr(0U, statement.find(' '));
-
587 CHECK(warning.message.find("'" + keyword + "'") != std::string::npos, statement);
-
588 const DecodeDatabase runtime = DbcDecodeBuilder().Build(result.database);
-
589 RawCanFrame raw;
-
590 raw.canId = 1U;
-
591 raw.data = {1U};
-
592 const DecodedFrameValue decoded = DbcDecoder().Decode(runtime, raw);
-
593 CHECK(decoded.valid && decoded.signals.size() == 1U && decoded.signals[0].valid,
-
594 "Warnings preserve supported signal decoding");
-
595 QCOMPARE(decoded.signals[0].physicalValue, 12.0);
-
596 CHECK(decoded.signals[0].GetValueDescription() &&
-
597 *decoded.signals[0].GetValueDescription() == "On",
-
598 "Warnings preserve VAL_ descriptions");
-
599 QCOMPARE(runtime.GetFrames()[0].signals[0].comment, std::string("Signal comment"));
-
600 }
-
601}
-
602
-
603void DbcParserTest::diagnosticNamespaceBoundaries() {
-
604 Fixture fixture;
-
605 const std::vector<std::string> endings = {
-
606 "BS_\n", "BS_:\n", "BS_ :\n", "BU_\n", "BU_:\n", "BU_ : ECU\n",
-
607 "BO_ 1 Frame: 1 ECU\n", "VENDOR_EXTENSION_ data;\n"
-
608 };
-
609 for (const std::string &ending : endings) {
-
610 fixture.Write("NS_ :\n\tSG_MUL_VAL_\n\nBA_\n" + ending + "SIG_VALTYPE_\n");
-
611 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
612 CHECK(!result.HasErrors(), ending);
-
613 const std::size_t expectedWarnings = ending.find("VENDOR_") == 0U ? 2U : 1U;
-
614 QCOMPARE(result.diagnostics.size(), expectedWarnings);
-
615 QCOMPARE(result.diagnostics.back().lineNumber, std::size_t(6U));
-
616 CHECK(result.diagnostics.back().message.find("SIG_VALTYPE_") != std::string::npos,
-
617 "Unsupported keyword outside NS_ must produce a warning");
-
618 }
-
619 for (const std::string &definition : {std::string("BO_"), std::string("SG_")}) {
-
620 fixture.Write("NS_:\n\tBA_\n\t" + definition + '\n');
-
621 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
622 CHECK(result.HasErrors(), "Malformed definition ends NS_ instead of being hidden");
-
623 QCOMPARE(result.diagnostics.size(), std::size_t(1U));
-
624 QCOMPARE(result.diagnostics[0].lineNumber, std::size_t(3U));
-
625 }
-
626}
-
627
-
628void DbcParserTest::diagnosticFormatting() {
-
629 Fixture fixture;
-
630 fixture.path += " with spaces";
-
631 const DbcParser parser;
-
632 for (const std::string &text : {std::string(), std::string(" \t\r\n\n")}) {
-
633 fixture.Write(text);
-
634 const DbcParseResult empty = parser.ParseFileWithDiagnostics(fixture.path.string());
-
635 CHECK(!empty.HasErrors() && empty.diagnostics.empty() && empty.database.frames.empty(),
-
636 "Empty and whitespace-only inputs are valid");
-
637 }
-
638 fixture.Write("\r\n\tVENDOR_EXTENSION_ data;\r\n\r\nUNKNOWN_");
-
639 const std::string relativePath = std::filesystem::relative(fixture.path).string();
-
640 const DbcParseResult warnings = parser.ParseFileWithDiagnostics(relativePath);
-
641 CHECK(!warnings.HasErrors() && warnings.database.frames.empty(), "Warnings-only input");
-
642 QCOMPARE(warnings.diagnostics.size(), std::size_t(2U));
-
643 QCOMPARE(warnings.diagnostics[0].lineNumber, std::size_t(2U));
-
644 QCOMPARE(warnings.diagnostics[1].lineNumber, std::size_t(4U));
-
645 for (const DbcDiagnostic &warning : warnings.diagnostics)
-
646 QCOMPARE(warning.filePath, relativePath);
-
647
-
648 fixture.Write("UNKNOWN_ first;\nBO_ invalid Frame: 1 ECU\n"
-
649 "UNKNOWN_ after_error;\nSG_ broken\n");
-
650 const DbcParseResult stopped = parser.ParseFileWithDiagnostics(relativePath);
-
651 CHECK(stopped.HasErrors() && stopped.database.frames.empty(), "First error stops parsing");
-
652 QCOMPARE(stopped.diagnostics.size(), std::size_t(2U));
-
653 QCOMPARE(stopped.diagnostics[0].lineNumber, std::size_t(1U));
-
654 QCOMPARE(stopped.diagnostics[1].lineNumber, std::size_t(2U));
-
655 QCOMPARE(stopped.diagnostics[1].filePath, relativePath);
-
656
-
657 fixture.Write("VAL_ 1 Missing 0 \"Off\";\nBO_ 1 Frame: 1 ECU\nUNKNOWN_ data;\n");
-
658 const DbcParseResult deferred = parser.ParseFileWithDiagnostics(relativePath);
-
659 CHECK(deferred.HasErrors() && deferred.database.frames.empty(), "Deferred error rejects database");
-
660 QCOMPARE(deferred.diagnostics.size(), std::size_t(2U));
-
661 CHECK(deferred.diagnostics[0].severity == DbcDiagnosticSeverity::Warning &&
-
662 deferred.diagnostics[1].severity == DbcDiagnosticSeverity::Error,
-
663 "Deferred errors follow warnings in detection order");
-
664 QCOMPARE(deferred.diagnostics[0].lineNumber, std::size_t(3U));
-
665 QCOMPARE(deferred.diagnostics[1].lineNumber, std::size_t(1U));
-
666 QCOMPARE(deferred.diagnostics[1].filePath, relativePath);
-
667 fixture.Write("BO_ 1 Frame: 1 ECU");
-
668 const DbcParseResult recovered = parser.ParseFileWithDiagnostics(relativePath);
-
669 CHECK(!recovered.HasErrors() && recovered.diagnostics.empty() && recovered.database.frames.size() == 1U,
-
670 "Same parser has clean state after errors and accepts missing final newline");
-
671}
-
672
-
673void DbcParserTest::validFrames() {
-
674 Fixture fixture;
-
675 struct ValidCase {
-
676 const char *line;
-
677 std::uint32_t id;
-
678 bool extended;
-
679 unsigned length;
-
680 };
-
681 const ValidCase valid[] = {
-
682 {"BO_ 0 Frame: 0 ECU", 0U, false, 0U},
-
683 {"BO_ 2047 Frame: 8 ECU", 2047U, false, 8U},
-
684 {"BO_ 2048 Frame: 64 ECU", 2048U, true, 64U},
-
685 {"BO_ 536870911 Frame: 255 ECU", 0x1FFFFFFFU, true, 255U},
-
686 {"BO_ 2147483649 Frame: 8 ECU", 1U, true, 8U},
-
687 {"BO_ 2684354559 Frame: 8 ECU", 0x1FFFFFFFU, true, 8U},
-
688 {" BO_\t256\tFrame :\t8\tECU \r", 256U, false, 8U}};
-
689 for (const ValidCase &test: valid) {
-
690 fixture.Write(std::string(test.line) + '\n');
-
691 try {
-
692 const DbcDatabase database = DbcParser().ParseFile(fixture.path.string());
-
693 QCOMPARE(database.frames.size(), std::size_t(1U));
-
694 const FrameInfo &frame = database.frames[0];
-
695 QCOMPARE(frame.name, std::string("Frame"));
-
696 QCOMPARE(frame.canId, test.id);
-
697 QCOMPARE(frame.isExtended, test.extended);
-
698 QCOMPARE(static_cast<unsigned>(frame.dlc), test.length);
-
699 QCOMPARE(frame.transmitter, std::string("ECU"));
-
700 } catch (const std::exception &error) { CHECK(false, error.what()); }
-
701 }
-
702}
-
703
-
704void DbcParserTest::invalidFrames() {
-
705 Fixture fixture;
-
706 const char *invalid[] = {
-
707 "BO_ invalid", "BO_", "BO_ 1", "BO_ 1 : 8 ECU", "BO_ 1 Frame 8 ECU",
-
708 "BO_ 1 Frame:", "BO_ 1 Frame: 8", "BO_ 1 Frame: 8 ECU extra",
-
709 "BO_ 1 Two Names: 8 ECU", "BO_ -1 Frame: 8 ECU", "BO_ +1 Frame: 8 ECU",
-
710 "BO_ 1x Frame: 8 ECU", "BO_ 0x123 Frame: 8 ECU", "BO_ 1.5 Frame: 8 ECU",
-
711 "BO_ 4294967296 Frame: 8 ECU", "BO_ 999999999999999999999 Frame: 8 ECU",
-
712 "BO_ 536870912 Frame: 8 ECU", "BO_ 1073741824 Frame: 8 ECU",
-
713 "BO_ 2684354560 Frame: 8 ECU", "BO_ 4294967295 Frame: 8 ECU",
-
714 "BO_ 1 Frame: -1 ECU", "BO_ 1 Frame: +8 ECU", "BO_ 1 Frame: 8x ECU",
-
715 "BO_ 1 Frame: 8.5 ECU", "BO_ 1 Frame: 256 ECU",
-
716 "BO_ 1 Frame: 4294967296 ECU", "BO_\tinvalid"};
-
717 for (const char *line: invalid) {
-
718 fixture.Write(std::string("VERSION \"test\"\n\n") + line + '\n');
-
719 try {
-
720 DbcParser().ParseFile(fixture.path.string());
-
721 CHECK(false, line);
-
722 } catch (const std::runtime_error &error) {
-
723 const std::string prefix = fixture.path.string() + ":3: ";
-
724 CHECK(std::string(error.what()).find(prefix) == 0U &&
-
725 std::string(error.what()).size() > prefix.size(),
-
726 line);
-
727 }
-
728 }
-
729}
-
730
-
731void DbcParserTest::duplicateFrames() {
-
732 Fixture fixture;
-
733 struct DuplicateCase {
-
734 const char *firstId;
-
735 const char *duplicateId;
-
736 const char *message;
-
737 };
-
738 const DuplicateCase cases[] = {
-
739 {"1", "1", "Duplicate standard frame CAN ID: 1"},
-
740 {"2147483649", "2147483649", "Duplicate extended frame CAN ID: 1"},
-
741 {"2048", "2147485696", "Duplicate extended frame CAN ID: 2048"},
-
742 {"2147485696", "2048", "Duplicate extended frame CAN ID: 2048"},
-
743 {"536870911", "2684354559", "Duplicate extended frame CAN ID: 536870911"}
-
744 };
-
745 const DbcParser parser;
-
746 for (const DuplicateCase &test : cases) {
-
747 fixture.Write("BO_ " + std::string(test.firstId) + " First: 8 ECU\n"
-
748 "BO_ 2 Other: 8 ECU\n\n"
-
749 " BO_\t" + test.duplicateId + "\tSecond: 8 ECU\n");
-
750 try {
-
751 parser.ParseFile(fixture.path.string());
-
752 CHECK(false, "Duplicate frame must be rejected");
-
753 } catch (const std::runtime_error &error) {
-
754 QCOMPARE(std::string(error.what()), fixture.path.string() + ":4: " + test.message);
-
755 }
-
756 }
-
757 fixture.Write("BO_ 1 First: 8 ECU\n");
-
758 QCOMPARE(parser.ParseFile(fixture.path.string()).frames.size(), std::size_t(1U));
-
759}
-
760
-
761void DbcParserTest::duplicateSignals() {
-
762 Fixture fixture;
-
763 const std::string first = "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n";
-
764 const std::string duplicates[] = {
-
765 first,
-
766 " SG_\tState : 16|8@1+ (2,1) [0|511] \"V\" ECU\n",
-
767 "SG_ State m1 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
-
768 };
-
769 for (const std::string &duplicate : duplicates) {
-
770 fixture.Write("BO_ 1 Frame: 8 ECU\n" + first +
-
771 "SG_ Other : 8|8@1+ (1,0) [0|255] \"\" ECU\n\n" + duplicate);
-
772 try {
-
773 DbcParser().ParseFile(fixture.path.string());
-
774 CHECK(false, "Duplicate signal must be rejected");
-
775 } catch (const std::runtime_error &error) {
-
776 QCOMPARE(std::string(error.what()), fixture.path.string() +
-
777 ":5: Duplicate signal name 'State' in frame 'Frame'.");
-
778 }
-
779 }
-
780}
-
781
-
782void DbcParserTest::distinctDefinitions() {
-
783 Fixture fixture;
-
784 fixture.Write("BO_ 1 Shared: 1 ECU\n"
-
785 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
-
786 "BO_ 2147483649 Shared: 1 ECU\n"
+
473 {"BO_ 1 Frame: 1 ECU\nSG_ Mode M : 0|8@1- (1,0) [-128|127] \"\" ECU\n",
+
474 DbcDiagnosticCode::InvalidMultiplexing, 2U},
+
475 {"VAL_ broken\n", DbcDiagnosticCode::InvalidValueDescription, 1U},
+
476 {"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
+
477 DbcDiagnosticCode::InvalidStatement, 1U},
+
478 {"BO_ 1 Frame: 1 ECU\nBO_ 1 Duplicate: 1 ECU\n",
+
479 DbcDiagnosticCode::InvalidStatement, 2U},
+
480 {"BO_ 1 Frame: 1 ECU\nSG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
+
481 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
+
482 DbcDiagnosticCode::InvalidStatement, 3U},
+
483 {"BO_ 1 Frame: 1 ECU\nSG_ Value : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
+
484 DbcDiagnosticCode::InvalidStatement, 2U},
+
485 {"BO_ 1 Frame: 1 ECU\nVAL_ 2 Missing 0 \"Off\";\n",
+
486 DbcDiagnosticCode::InvalidValueDescription, 2U},
+
487 {"BO_ 1 Frame: 1 ECU\nSG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
+
488 "VAL_ 1 Value 0 \"Off\";\nVAL_ 1 Value 0 \"Duplicate\";\n",
+
489 DbcDiagnosticCode::InvalidValueDescription, 4U},
+
490 {"BO_ 1 Frame: 2 ECU\nSG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
+
491 "SG_ Value m2 : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
+
492 DbcDiagnosticCode::InvalidMultiplexing, 3U},
+
493 {"BO_ 1 Frame: 2 ECU\nSG_ Mode M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
+
494 "SG_ Other M : 8|8@1- (1,0) [-128|127] \"\" ECU\n",
+
495 DbcDiagnosticCode::InvalidMultiplexing, 3U},
+
496 {"BO_ 1 Frame: 1 ECU\nSG_ First : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
+
497 "SG_ Second : 7|8@0+ (1,0) [0|255] \"\" ECU\n",
+
498 DbcDiagnosticCode::InvalidMultiplexing, 3U}};
+
499 for (const ErrorCase &test : cases) {
+
500 fixture.Write(test.text);
+
501 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
502 CHECK(result.HasErrors() && result.database.frames.empty(),
+
503 "Failed parse never returns a partial database");
+
504 const DbcDiagnostic &diagnostic = result.diagnostics.back();
+
505 CHECK(diagnostic.severity == DbcDiagnosticSeverity::Error && diagnostic.code == test.code,
+
506 "Error has expected severity and code");
+
507 QCOMPARE(diagnostic.filePath, fixture.path.string());
+
508 QCOMPARE(diagnostic.lineNumber, test.line);
+
509 CHECK(!diagnostic.message.empty(), "Error includes a message");
+
510 const bool hasWarning = std::string(test.text).find("UNKNOWN_") == 0U;
+
511 QCOMPARE(result.diagnostics.size(), hasWarning ? std::size_t(2U) : std::size_t(1U));
+
512 try {
+
513 DbcParser().ParseFile(fixture.path.string());
+
514 CHECK(false, "Legacy parser must throw");
+
515 } catch (const std::runtime_error &error) {
+
516 QCOMPARE(std::string(error.what()), fixture.path.string() + ":" +
+
517 std::to_string(test.line) + ": " + diagnostic.message);
+
518 }
+
519 }
+
520 const std::string missingPath = fixture.path.string() + ".missing";
+
521 const DbcParseResult missing = DbcParser().ParseFileWithDiagnostics(missingPath);
+
522 CHECK(missing.HasErrors() && missing.database.frames.empty(), "Missing file returns error");
+
523 QCOMPARE(missing.diagnostics.size(), std::size_t(1U));
+
524 CHECK(missing.diagnostics[0].code == DbcDiagnosticCode::FileOpenError,
+
525 "Missing file has a distinct code");
+
526 QCOMPARE(missing.diagnostics[0].lineNumber, std::size_t(0U));
+
527 QCOMPARE(missing.diagnostics[0].filePath, missingPath);
+
528 QCOMPARE(missing.diagnostics[0].message, "Failed to open DBC file: " + missingPath);
+
529 try {
+
530 DbcParser().ParseFile(missingPath);
+
531 CHECK(false, "Legacy API throws on missing file");
+
532 } catch (const std::runtime_error &error) {
+
533 QCOMPARE(std::string(error.what()), missing.diagnostics[0].message);
+
534 }
+
535 const std::string directoryPath = fixture.path.parent_path().string();
+
536 const DbcParseResult directory = DbcParser().ParseFileWithDiagnostics(directoryPath);
+
537 CHECK(directory.HasErrors() && directory.database.frames.empty(),
+
538 "Directory cannot be parsed as a file");
+
539 QCOMPARE(directory.diagnostics.size(), std::size_t(1U));
+
540 CHECK(directory.diagnostics[0].code == DbcDiagnosticCode::FileReadError ||
+
541 directory.diagnostics[0].code == DbcDiagnosticCode::FileOpenError,
+
542 "Directory produces a file-level error on supported platforms");
+
543 QCOMPARE(directory.diagnostics[0].lineNumber, std::size_t(0U));
+
544 QCOMPARE(directory.diagnostics[0].filePath, directoryPath);
+
545#ifdef __linux__
+
546 CHECK(directory.diagnostics[0].code == DbcDiagnosticCode::FileReadError,
+
547 "Linux directory input exercises the read-error path");
+
548 QCOMPARE(directory.diagnostics[0].message, "Failed to read DBC file: " + directoryPath);
+
549#endif
+
550 try {
+
551 DbcParser().ParseFile(directoryPath);
+
552 CHECK(false, "Legacy API throws on unreadable input");
+
553 } catch (const std::runtime_error &error) {
+
554 QCOMPARE(std::string(error.what()), directory.diagnostics[0].message);
+
555 }
+
556 fixture.Write("BO_ 1 Frame: 1 ECU\n");
+
557 CHECK(DbcParser().ParseFileWithDiagnostics(fixture.path.string()).diagnostics.empty(),
+
558 "Diagnostics belong to an independent parse session");
+
559}
+
560
+
561void DbcParserTest::diagnosticUnsupportedStatements() {
+
562 Fixture fixture;
+
563 const std::string definitions =
+
564 "BO_ 1 Frame: 1 ECU\nSG_ Value : 0|8@1+ (2,10) [10|520] \"V\" ECU\n"
+
565 "CM_ SG_ 1 Value \"Signal comment\";\nVAL_ 1 Value 1 \"On\";\n";
+
566 const std::vector<std::string> statements = {
+
567 "SIG_VALTYPE_ 1 Value : 1;", "SIG_VALTYPE_ 1 Value : 2;",
+
568 "BA_REL_ \"SignalType\" SG_ 1 Value 1;",
+
569 "BA_DEF_REL_ BU_SG_REL_ \"Relation\" INT 0 1;",
+
570 "BA_REL_ \"Relation\" BU_SG_REL_ ECU SG_ 1 Value 1;",
+
571 "SGTYPE_ Type : 8@1+ (1,0) [0|255] \"\" 0, Table;",
+
572 "SIG_TYPE_REF_ 1 Value : Type;",
+
573 "EV_ Environment : 0 [0|1] \"\" 0 1 DUMMY_NODE_VECTOR0 ECU;",
+
574 "ENVVAR_DATA_ Environment : 4;",
+
575 "VAL_TABLE_ Table 0 \"Off\" 1 \"On\";",
+
576 "BO_TX_BU_ 1 : ECU;", "VENDOR_EXTENSION_ data;", "UNKNOWN_"
+
577 };
+
578 for (const std::string &statement : statements) {
+
579 fixture.Write(definitions + statement + '\n');
+
580 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
581 CHECK(!result.HasErrors(), statement);
+
582 QCOMPARE(result.diagnostics.size(), std::size_t(1U));
+
583 const DbcDiagnostic &warning = result.diagnostics[0];
+
584 CHECK(warning.severity == DbcDiagnosticSeverity::Warning &&
+
585 warning.code == DbcDiagnosticCode::UnsupportedStatement, statement);
+
586 QCOMPARE(warning.filePath, fixture.path.string());
+
587 QCOMPARE(warning.lineNumber, std::size_t(5U));
+
588 const std::string keyword = statement.substr(0U, statement.find(' '));
+
589 CHECK(warning.message.find("'" + keyword + "'") != std::string::npos, statement);
+
590 const DecodeDatabase runtime = DbcDecodeBuilder().Build(result.database);
+
591 RawCanFrame raw;
+
592 raw.canId = 1U;
+
593 raw.data = {1U};
+
594 const DecodedFrameValue decoded = DbcDecoder().Decode(runtime, raw);
+
595 CHECK(decoded.valid && decoded.signals.size() == 1U && decoded.signals[0].valid,
+
596 "Warnings preserve supported signal decoding");
+
597 QCOMPARE(decoded.signals[0].physicalValue, 12.0);
+
598 CHECK(decoded.signals[0].GetValueDescription() &&
+
599 *decoded.signals[0].GetValueDescription() == "On",
+
600 "Warnings preserve VAL_ descriptions");
+
601 QCOMPARE(runtime.GetFrames()[0].signals[0].comment, std::string("Signal comment"));
+
602 }
+
603}
+
604
+
605void DbcParserTest::diagnosticNamespaceBoundaries() {
+
606 Fixture fixture;
+
607 const std::vector<std::string> endings = {
+
608 "BS_\n", "BS_:\n", "BS_ :\n", "BU_\n", "BU_:\n", "BU_ : ECU\n",
+
609 "BO_ 1 Frame: 1 ECU\n", "VENDOR_EXTENSION_ data;\n"
+
610 };
+
611 for (const std::string &ending : endings) {
+
612 fixture.Write("NS_ :\n\tSG_MUL_VAL_\n\nBA_\n" + ending + "SIG_VALTYPE_\n");
+
613 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
614 CHECK(!result.HasErrors(), ending);
+
615 const std::size_t expectedWarnings = ending.find("VENDOR_") == 0U ? 2U : 1U;
+
616 QCOMPARE(result.diagnostics.size(), expectedWarnings);
+
617 QCOMPARE(result.diagnostics.back().lineNumber, std::size_t(6U));
+
618 CHECK(result.diagnostics.back().message.find("SIG_VALTYPE_") != std::string::npos,
+
619 "Unsupported keyword outside NS_ must produce a warning");
+
620 }
+
621 for (const std::string &definition : {std::string("BO_"), std::string("SG_")}) {
+
622 fixture.Write("NS_:\n\tBA_\n\t" + definition + '\n');
+
623 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
624 CHECK(result.HasErrors(), "Malformed definition ends NS_ instead of being hidden");
+
625 QCOMPARE(result.diagnostics.size(), std::size_t(1U));
+
626 QCOMPARE(result.diagnostics[0].lineNumber, std::size_t(3U));
+
627 }
+
628}
+
629
+
630void DbcParserTest::diagnosticFormatting() {
+
631 Fixture fixture;
+
632 fixture.path += " with spaces";
+
633 const DbcParser parser;
+
634 for (const std::string &text : {std::string(), std::string(" \t\r\n\n")}) {
+
635 fixture.Write(text);
+
636 const DbcParseResult empty = parser.ParseFileWithDiagnostics(fixture.path.string());
+
637 CHECK(!empty.HasErrors() && empty.diagnostics.empty() && empty.database.frames.empty(),
+
638 "Empty and whitespace-only inputs are valid");
+
639 }
+
640 fixture.Write("\r\n\tVENDOR_EXTENSION_ data;\r\n\r\nUNKNOWN_");
+
641 const std::string relativePath = std::filesystem::relative(fixture.path).string();
+
642 const DbcParseResult warnings = parser.ParseFileWithDiagnostics(relativePath);
+
643 CHECK(!warnings.HasErrors() && warnings.database.frames.empty(), "Warnings-only input");
+
644 QCOMPARE(warnings.diagnostics.size(), std::size_t(2U));
+
645 QCOMPARE(warnings.diagnostics[0].lineNumber, std::size_t(2U));
+
646 QCOMPARE(warnings.diagnostics[1].lineNumber, std::size_t(4U));
+
647 for (const DbcDiagnostic &warning : warnings.diagnostics)
+
648 QCOMPARE(warning.filePath, relativePath);
+
649
+
650 fixture.Write("UNKNOWN_ first;\nBO_ invalid Frame: 1 ECU\n"
+
651 "UNKNOWN_ after_error;\nSG_ broken\n");
+
652 const DbcParseResult stopped = parser.ParseFileWithDiagnostics(relativePath);
+
653 CHECK(stopped.HasErrors() && stopped.database.frames.empty(), "First error stops parsing");
+
654 QCOMPARE(stopped.diagnostics.size(), std::size_t(2U));
+
655 QCOMPARE(stopped.diagnostics[0].lineNumber, std::size_t(1U));
+
656 QCOMPARE(stopped.diagnostics[1].lineNumber, std::size_t(2U));
+
657 QCOMPARE(stopped.diagnostics[1].filePath, relativePath);
+
658
+
659 fixture.Write("VAL_ 1 Missing 0 \"Off\";\nBO_ 1 Frame: 1 ECU\nUNKNOWN_ data;\n");
+
660 const DbcParseResult deferred = parser.ParseFileWithDiagnostics(relativePath);
+
661 CHECK(deferred.HasErrors() && deferred.database.frames.empty(), "Deferred error rejects database");
+
662 QCOMPARE(deferred.diagnostics.size(), std::size_t(2U));
+
663 CHECK(deferred.diagnostics[0].severity == DbcDiagnosticSeverity::Warning &&
+
664 deferred.diagnostics[1].severity == DbcDiagnosticSeverity::Error,
+
665 "Deferred errors follow warnings in detection order");
+
666 QCOMPARE(deferred.diagnostics[0].lineNumber, std::size_t(3U));
+
667 QCOMPARE(deferred.diagnostics[1].lineNumber, std::size_t(1U));
+
668 QCOMPARE(deferred.diagnostics[1].filePath, relativePath);
+
669 fixture.Write("BO_ 1 Frame: 1 ECU");
+
670 const DbcParseResult recovered = parser.ParseFileWithDiagnostics(relativePath);
+
671 CHECK(!recovered.HasErrors() && recovered.diagnostics.empty() && recovered.database.frames.size() == 1U,
+
672 "Same parser has clean state after errors and accepts missing final newline");
+
673}
+
674
+
675void DbcParserTest::validFrames() {
+
676 Fixture fixture;
+
677 struct ValidCase {
+
678 const char *line;
+
679 std::uint32_t id;
+
680 bool extended;
+
681 unsigned length;
+
682 };
+
683 const ValidCase valid[] = {
+
684 {"BO_ 0 Frame: 0 ECU", 0U, false, 0U},
+
685 {"BO_ 2047 Frame: 8 ECU", 2047U, false, 8U},
+
686 {"BO_ 2048 Frame: 64 ECU", 2048U, true, 64U},
+
687 {"BO_ 536870911 Frame: 255 ECU", 0x1FFFFFFFU, true, 255U},
+
688 {"BO_ 2147483649 Frame: 8 ECU", 1U, true, 8U},
+
689 {"BO_ 2684354559 Frame: 8 ECU", 0x1FFFFFFFU, true, 8U},
+
690 {" BO_\t256\tFrame :\t8\tECU \r", 256U, false, 8U}};
+
691 for (const ValidCase &test: valid) {
+
692 fixture.Write(std::string(test.line) + '\n');
+
693 try {
+
694 const DbcDatabase database = DbcParser().ParseFile(fixture.path.string());
+
695 QCOMPARE(database.frames.size(), std::size_t(1U));
+
696 const FrameInfo &frame = database.frames[0];
+
697 QCOMPARE(frame.name, std::string("Frame"));
+
698 QCOMPARE(frame.canId, test.id);
+
699 QCOMPARE(frame.isExtended, test.extended);
+
700 QCOMPARE(static_cast<unsigned>(frame.dlc), test.length);
+
701 QCOMPARE(frame.transmitter, std::string("ECU"));
+
702 } catch (const std::exception &error) { CHECK(false, error.what()); }
+
703 }
+
704}
+
705
+
706void DbcParserTest::invalidFrames() {
+
707 Fixture fixture;
+
708 const char *invalid[] = {
+
709 "BO_ invalid", "BO_", "BO_ 1", "BO_ 1 : 8 ECU", "BO_ 1 Frame 8 ECU",
+
710 "BO_ 1 Frame:", "BO_ 1 Frame: 8", "BO_ 1 Frame: 8 ECU extra",
+
711 "BO_ 1 Two Names: 8 ECU", "BO_ -1 Frame: 8 ECU", "BO_ +1 Frame: 8 ECU",
+
712 "BO_ 1x Frame: 8 ECU", "BO_ 0x123 Frame: 8 ECU", "BO_ 1.5 Frame: 8 ECU",
+
713 "BO_ 4294967296 Frame: 8 ECU", "BO_ 999999999999999999999 Frame: 8 ECU",
+
714 "BO_ 536870912 Frame: 8 ECU", "BO_ 1073741824 Frame: 8 ECU",
+
715 "BO_ 2684354560 Frame: 8 ECU", "BO_ 4294967295 Frame: 8 ECU",
+
716 "BO_ 1 Frame: -1 ECU", "BO_ 1 Frame: +8 ECU", "BO_ 1 Frame: 8x ECU",
+
717 "BO_ 1 Frame: 8.5 ECU", "BO_ 1 Frame: 256 ECU",
+
718 "BO_ 1 Frame: 4294967296 ECU", "BO_\tinvalid"};
+
719 for (const char *line: invalid) {
+
720 fixture.Write(std::string("VERSION \"test\"\n\n") + line + '\n');
+
721 try {
+
722 DbcParser().ParseFile(fixture.path.string());
+
723 CHECK(false, line);
+
724 } catch (const std::runtime_error &error) {
+
725 const std::string prefix = fixture.path.string() + ":3: ";
+
726 CHECK(std::string(error.what()).find(prefix) == 0U &&
+
727 std::string(error.what()).size() > prefix.size(),
+
728 line);
+
729 }
+
730 }
+
731}
+
732
+
733void DbcParserTest::duplicateFrames() {
+
734 Fixture fixture;
+
735 struct DuplicateCase {
+
736 const char *firstId;
+
737 const char *duplicateId;
+
738 const char *message;
+
739 };
+
740 const DuplicateCase cases[] = {
+
741 {"1", "1", "Duplicate standard frame CAN ID: 1"},
+
742 {"2147483649", "2147483649", "Duplicate extended frame CAN ID: 1"},
+
743 {"2048", "2147485696", "Duplicate extended frame CAN ID: 2048"},
+
744 {"2147485696", "2048", "Duplicate extended frame CAN ID: 2048"},
+
745 {"536870911", "2684354559", "Duplicate extended frame CAN ID: 536870911"}
+
746 };
+
747 const DbcParser parser;
+
748 for (const DuplicateCase &test : cases) {
+
749 fixture.Write("BO_ " + std::string(test.firstId) + " First: 8 ECU\n"
+
750 "BO_ 2 Other: 8 ECU\n\n"
+
751 " BO_\t" + test.duplicateId + "\tSecond: 8 ECU\n");
+
752 try {
+
753 parser.ParseFile(fixture.path.string());
+
754 CHECK(false, "Duplicate frame must be rejected");
+
755 } catch (const std::runtime_error &error) {
+
756 QCOMPARE(std::string(error.what()), fixture.path.string() + ":4: " + test.message);
+
757 }
+
758 }
+
759 fixture.Write("BO_ 1 First: 8 ECU\n");
+
760 QCOMPARE(parser.ParseFile(fixture.path.string()).frames.size(), std::size_t(1U));
+
761}
+
762
+
763void DbcParserTest::duplicateSignals() {
+
764 Fixture fixture;
+
765 const std::string first = "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n";
+
766 const std::string duplicates[] = {
+
767 first,
+
768 " SG_\tState : 16|8@1+ (2,1) [0|511] \"V\" ECU\n",
+
769 "SG_ State m1 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
+
770 };
+
771 for (const std::string &duplicate : duplicates) {
+
772 fixture.Write("BO_ 1 Frame: 8 ECU\n" + first +
+
773 "SG_ Other : 8|8@1+ (1,0) [0|255] \"\" ECU\n\n" + duplicate);
+
774 try {
+
775 DbcParser().ParseFile(fixture.path.string());
+
776 CHECK(false, "Duplicate signal must be rejected");
+
777 } catch (const std::runtime_error &error) {
+
778 QCOMPARE(std::string(error.what()), fixture.path.string() +
+
779 ":5: Duplicate signal name 'State' in frame 'Frame'.");
+
780 }
+
781 }
+
782}
+
783
+
784void DbcParserTest::distinctDefinitions() {
+
785 Fixture fixture;
+
786 fixture.Write("BO_ 1 Shared: 1 ECU\n"
787 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
-
788 "BO_ 2 Shared: 2 ECU\n"
+
788 "BO_ 2147483649 Shared: 1 ECU\n"
789 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
-
790 "SG_ state : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
-
791 "CM_ BO_ 1 \"Standard\";\n"
-
792 "CM_ BO_ 2147483649 \"Extended\";\n"
-
793 "CM_ SG_ 1 State \"Standard state\";\n"
-
794 "CM_ SG_ 2147483649 State \"Extended state\";\n");
-
795 const DbcDatabase parsed = DbcParser().ParseFile(fixture.path.string());
-
796 QCOMPARE(parsed.frames.size(), std::size_t(3U));
-
797 QCOMPARE(parsed.frames[0].canId, parsed.frames[1].canId);
-
798 CHECK(!parsed.frames[0].isExtended && parsed.frames[1].isExtended,
-
799 "Standard and extended frames with the same numeric ID remain distinct");
-
800 QCOMPARE(parsed.frames[0].comment, std::string("Standard"));
-
801 QCOMPARE(parsed.frames[1].comment, std::string("Extended"));
-
802 QCOMPARE(parsed.frames[0].signals[0].comment, std::string("Standard state"));
-
803 QCOMPARE(parsed.frames[1].signals[0].comment, std::string("Extended state"));
-
804 QCOMPARE(parsed.frames[2].signals.size(), std::size_t(2U));
-
805 const DecodeDatabase runtime = DbcDecodeBuilder().Build(parsed);
-
806 RawCanFrame raw;
-
807 raw.canId = 1U;
-
808 raw.data = {7U};
-
809 const DecodedFrameValue standard = DbcDecoder().Decode(runtime, raw);
-
810 QVERIFY(standard.valid);
-
811 QCOMPARE(standard.definition->comment, std::string("Standard"));
-
812 raw.isExtended = true;
-
813 const DecodedFrameValue extended = DbcDecoder().Decode(runtime, raw);
-
814 QVERIFY(extended.valid);
-
815 QCOMPARE(extended.definition->comment, std::string("Extended"));
-
816}
-
817
-
818void DbcParserTest::comments() {
-
819 Fixture fixture;
-
820 fixture.Write(
-
821 "CM_\tBO_\t1\t\"First\";\r\n"
-
822 "CM_\tSG_\t1\tState\t\" Line one \r\n"
-
823 "\r\n"
-
824 "\tBO_ is text; \\\"quoted\\\" and \\\\slash\"\t;\r\n"
-
825 "CM_ BO_ 2147483649 \"Extended\";\r\n"
-
826 "CM_ SG_ 2147483649 State \"Extended signal\";\r\n"
-
827 "BO_ 1 Standard: 1 ECU\r\n"
-
828 "CM_ BO_ 1 \"Last\";\r\n"
-
829 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\r\n"
-
830 "BO_ 2147483649 Extended: 1 ECU\r\n"
-
831 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU");
-
832 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
833 QVERIFY(!result.HasErrors());
-
834 QVERIFY(result.diagnostics.empty());
-
835 QCOMPARE(result.database.frames.size(), std::size_t(2));
-
836 QCOMPARE(result.database.frames[0].comment, std::string("Last"));
-
837 QCOMPARE(result.database.frames[0].signals[0].comment,
-
838 std::string(" Line one \n\n\tBO_ is text; \"quoted\" and \\slash"));
-
839 QCOMPARE(result.database.frames[1].comment, std::string("Extended"));
-
840 QCOMPARE(result.database.frames[1].signals[0].comment, std::string("Extended signal"));
-
841
-
842 const std::string ids[][2] = {
-
843 {"0", "0"},
-
844 {"2047", "2047"},
-
845 {"2048", "2147485696"},
-
846 {"536870911", "2684354559"},
-
847 {"2684354559", "536870911"}};
-
848 for (const std::string(&id)[2]: ids) {
-
849 fixture.Write("CM_ BO_ " + id[0] + " \"Frame\";\nCM_ SG_ " + id[0] +
-
850 " State \"Signal\";\nBO_ " + id[1] + " Frame: 1 ECU\n"
-
851 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n");
-
852 const DbcDatabase database = DbcParser().ParseFile(fixture.path.string());
-
853 QCOMPARE(database.frames[0].comment, std::string("Frame"));
-
854 QCOMPARE(database.frames[0].signals[0].comment, std::string("Signal"));
-
855 }
-
856 fixture.Write("CM_ BO_ 1 \"Line one\nLine two\";\n"
-
857 "BO_ 1 Frame: 0 ECU\nUNKNOWN_ value\nBO_ bad Frame: 0 ECU\n");
-
858 const DbcParseResult following = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
859 QVERIFY(following.HasErrors());
-
860 QCOMPARE(following.diagnostics.size(), std::size_t(2));
-
861 QCOMPARE(following.diagnostics[0].lineNumber, std::size_t(4));
-
862 QCOMPARE(following.diagnostics[1].lineNumber, std::size_t(5));
-
863}
-
864
-
865void DbcParserTest::invalidComments_data() {
-
866 QTest::addColumn<QString>("statement");
-
867 const char *ids[] = {"-1", "+1", "1x", "0x1", "4294967296", "536870912",
-
868 "1073741824", "2684354560", "4294967295"};
-
869 for (const char *id: ids) {
-
870 const std::string frameRow = std::string("frame-id-") + id;
-
871 const std::string signalRow = std::string("signal-id-") + id;
-
872 QTest::newRow(frameRow.c_str()) << QString::fromStdString("CM_ BO_ " + std::string(id) + " \"Text\";");
-
873 QTest::newRow(signalRow.c_str()) << QString::fromStdString("CM_ SG_ " + std::string(id) + " State \"Text\";");
-
874 }
-
875 QTest::newRow("missing-id") << QStringLiteral("CM_ BO_");
-
876 QTest::newRow("missing-signal") << QStringLiteral("CM_ SG_ 1 \"Text\";");
-
877 QTest::newRow("missing-quote") << QStringLiteral("CM_ BO_ 1 Text;");
-
878 QTest::newRow("unclosed") << QStringLiteral("CM_ SG_ 1 State \"Text\n More");
-
879 QTest::newRow("invalid-escape") << QStringLiteral("CM_ BO_ 1 \"bad\\q\";");
-
880 QTest::newRow("dangling-escape") << QStringLiteral("CM_ BO_ 1 \"bad\\");
-
881 QTest::newRow("missing-semicolon") << QStringLiteral("CM_ SG_ 1 State \"Text\"");
-
882 QTest::newRow("trailing-text") << QStringLiteral("CM_ BO_ 1 \"Text\"; garbage");
-
883 QTest::newRow("extra-header") << QStringLiteral("CM_ BO_ 1 extra \"Text\";");
-
884 QTest::newRow("extra-string") << QStringLiteral("CM_ BO_ 1 \"Text\" \"More\";");
-
885 QTest::newRow("missing-frame") << QStringLiteral("CM_ BO_ 2 \"Text\";");
-
886 QTest::newRow("missing-signal-frame") << QStringLiteral("CM_ SG_ 2 State \"Text\";");
-
887 QTest::newRow("missing-signal-reference") << QStringLiteral("CM_ SG_ 1 Missing \"Text\";");
-
888 QTest::newRow("different-frame-type") << QStringLiteral("CM_ BO_ 2147483649 \"Text\";");
-
889 QTest::newRow("missing-multiline-reference") << QStringLiteral("CM_ SG_ 1 Missing \"Text\nMore\";");
-
890}
-
891
-
892void DbcParserTest::invalidComments() {
-
893 QFETCH(QString, statement);
-
894 Fixture fixture;
-
895 fixture.Write("BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n\n" +
-
896 statement.toStdString());
-
897 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
898 QVERIFY(result.HasErrors());
-
899 QVERIFY(result.database.frames.empty());
-
900 QCOMPARE(result.diagnostics.size(), std::size_t(1));
-
901 const DbcDiagnostic &error = result.diagnostics[0];
-
902 QCOMPARE(error.severity, DbcDiagnosticSeverity::Error);
-
903 QCOMPARE(error.code, DbcDiagnosticCode::InvalidStatement);
-
904 QCOMPARE(error.filePath, fixture.path.string());
-
905 QCOMPARE(error.lineNumber, std::size_t(4));
-
906 QVERIFY(!error.message.empty());
-
907 try {
-
908 DbcParser().ParseFile(fixture.path.string());
-
909 QFAIL("Malformed or unresolved comment must throw.");
-
910 } catch (const std::runtime_error &exception) {
-
911 QVERIFY(std::string(exception.what()).find(fixture.path.string() + ":4: ") == 0U);
-
912 }
-
913}
-
914
-
915void DbcParserTest::integration() {
-
916 Fixture fixture;
-
917 fixture.Write(
-
918 "BO_TX_BU_ 256 : ECU;\nBO_ 256 Frame: 8 ECU\n"
-
919 " SG_ Speed : 0|16@1+ (0.5,1) [0|100] \"rpm\" ECU\n"
-
920 "CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 Speed \"Signal comment\";\n"
-
921 "BO_ 257 Second: 8 ECU\n");
-
922 const DbcDatabase database = DbcParser().ParseFile(fixture.path.string());
-
923 CHECK(database.frames.size() == 2U && database.frames[0].signals.size() == 1U &&
-
924 database.frames[0].comment == "Frame comment" &&
-
925 database.frames[0].signals[0].comment == "Signal comment",
-
926 "Integration");
-
927 fixture.Write("\nSG_ Speed : invalid\n");
-
928 try {
-
929 DbcParser().ParseFile(fixture.path.string());
-
930 CHECK(false, "Signal error location");
-
931 } catch (const std::runtime_error &error) {
-
932 CHECK(std::string(error.what()).find(fixture.path.string() + ":2: ") == 0U,
-
933 "Signal error location");
-
934 }
-
935}
-
936
-
937void DbcParserTest::multiplexing() {
-
938 Fixture fixture;
-
939 const std::string selector = "SG_ Mode M : 0|8@1+ (10,3) [0|255] \"\" ECU\n";
-
940 const std::string first = "SG_ First m1 : 8|8@1- (2,0) [-128|127] \"V\" ECU\n";
-
941 const std::string second = "SG_ Second m2 : 15|8@0+ (1,0) [0|255] \"\" ECU\n";
-
942 const std::string common = "SG_ Common : 16|8@1+ (1,0) [0|255] \"\" ECU\n";
-
943 fixture.Write("BO_ 1 Frame: 3 ECU\n" + first + second + common + selector +
-
944 "VAL_ 1 First -1 \"Negative\";\n");
-
945 const DbcDatabase parsed = DbcParser().ParseFile(fixture.path.string());
-
946 const DecodeDatabase runtime = DbcDecodeBuilder().Build(parsed);
-
947 CHECK(parsed.frames[0].signals[3].multiplexing.role == MultiplexRole::Selector,
-
948 "Selector declared after branches");
-
949 const std::unique_ptr<TreeNode> tree = DbcTreeBuilder().Build(parsed);
-
950 CHECK(tree->GetChild(0U)->GetChild(0U)->GetSignal()->multiplexing.value == 1U,
-
951 "Tree preserves multiplexing");
-
952 RawCanFrame raw;
-
953 raw.canId = 1U;
-
954 raw.data = {1U, 255U, 7U};
-
955 DecodedFrameValue result = DbcDecoder().Decode(runtime, raw);
-
956 CHECK(result.signals[0].valid && std::get<std::int64_t>(result.signals[0].rawValue) == -1 &&
-
957 result.signals[0].physicalValue == -2 && *result.signals[0].GetValueDescription() == "Negative",
-
958 "Active signed branch and VAL_ use exact raw selector");
-
959 CHECK(!result.signals[1].valid && result.signals[1].status == SignalDecodeStatus::Inactive &&
-
960 result.signals[1].GetValueDescription() == nullptr && result.signals[2].valid,
-
961 "Inactive branch and unconditional signal");
-
962 raw.data[0] = 2U;
-
963 result = DbcDecoder().Decode(runtime, raw);
-
964 CHECK(result.signals[0].status == SignalDecodeStatus::Inactive && result.signals[1].valid &&
-
965 std::get<std::uint64_t>(result.signals[1].rawValue) == 255U,
-
966 "Motorola branch becomes active");
-
967 raw.data = {3U};
-
968 result = DbcDecoder().Decode(runtime, raw);
-
969 CHECK(result.signals[0].status == SignalDecodeStatus::Inactive &&
-
970 result.signals[1].status == SignalDecodeStatus::Inactive &&
-
971 result.signals[2].status == SignalDecodeStatus::InsufficientPayload,
-
972 "Unmapped selector leaves branches inactive even with missing bytes");
-
973 raw.data = {1U};
-
974 result = DbcDecoder().Decode(runtime, raw);
-
975 CHECK(result.signals[0].status == SignalDecodeStatus::InsufficientPayload,
-
976 "Active branch requires payload");
-
977 raw.data.clear();
-
978 result = DbcDecoder().Decode(runtime, raw);
-
979 CHECK(result.signals[0].status == SignalDecodeStatus::SelectorUnavailable &&
-
980 result.signals[3].status == SignalDecodeStatus::InsufficientPayload,
-
981 "Unavailable selector does not imply inactive branch");
-
982
-
983 const std::vector<std::string> invalid = {
-
984 first,
-
985 selector + "SG_ Other M : 16|8@1- (1,0) [-128|127] \"\" ECU\n",
-
986 "SG_ Mode M : 0|8@1- (1,0) [-128|127] \"\" ECU\n",
-
987 selector + "SG_ TooLarge m256 : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
-
988 selector + first + "SG_ Same m1 : 15|8@0+ (1,0) [0|255] \"\" ECU\n",
-
989 selector + "SG_ Collision m1 : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
-
990 selector + first + "SG_ Always : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
-
991 "SG_MUL_VAL_ 1 First Mode 1-2;\n"};
-
992 for (const std::string &signals : invalid) {
-
993 fixture.Write("BO_ 1 Frame: 3 ECU\n" + signals);
-
994 bool rejected = false;
-
995 try {
-
996 DbcParser().ParseFile(fixture.path.string());
-
997 } catch (const std::runtime_error &error) {
-
998 rejected = std::string(error.what()).find(fixture.path.string() + ":") == 0U;
-
999 }
-
1000 CHECK(rejected, "Invalid multiplexing rejected with location");
-
1001 }
-
1002 for (const std::string marker : {"m", "m-1", "m+1", "m1M", "m18446744073709551616", "M m1"})
-
1003 RejectSignal(fixture, "SG_ Bad " + marker + " : 8|8@1+ (1,0) [0|255] \"\" ECU");
-
1004}
-
1005
-
1006void DbcParserTest::multiplexingBoundaries() {
-
1007 Fixture fixture;
-
1008 fixture.Write("BO_ 1 Frame: 9 ECU\n"
-
1009 "SG_ Branch m18446744073709551615 : 64|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1010 "SG_ Mode M : 7|64@0+ (0,5) [0|1] \"\" ECU\n");
-
1011 const DbcDatabase parsed = DbcParser().ParseFile(fixture.path.string());
-
1012 DecodeDatabase runtime = DbcDecodeBuilder().Build(parsed);
-
1013 CHECK(runtime.GetFrames()[0].signals[0].multiplexing.value == std::numeric_limits<std::uint64_t>::max(),
-
1014 "Exact uint64 branch metadata");
-
1015 RawCanFrame raw;
-
1016 raw.canId = 1U;
-
1017 raw.data = std::vector<std::uint8_t>(9U, 255U);
-
1018 DecodedFrameValue result = DbcDecoder().Decode(runtime, raw);
-
1019 CHECK(result.signals[0].valid && result.signals[1].valid &&
-
1020 std::get<std::uint64_t>(result.signals[1].rawValue) == std::numeric_limits<std::uint64_t>::max(),
-
1021 "Motorola uint64 selector compares raw value before zero scaling");
-
1022 raw.data[7] = 254U;
-
1023 result = DbcDecoder().Decode(runtime, raw);
-
1024 CHECK(result.signals[0].status == SignalDecodeStatus::Inactive,
-
1025 "Adjacent large integers remain distinct");
-
1026 std::vector<DecodeFrame> definitions = runtime.GetFrames();
-
1027 definitions[0].signals[1].length = 0U;
-
1028 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, runtime = DecodeDatabase(definitions));
-
1029 definitions[0].signals.pop_back();
+
790 "BO_ 2 Shared: 2 ECU\n"
+
791 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
+
792 "SG_ state : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
+
793 "CM_ BO_ 1 \"Standard\";\n"
+
794 "CM_ BO_ 2147483649 \"Extended\";\n"
+
795 "CM_ SG_ 1 State \"Standard state\";\n"
+
796 "CM_ SG_ 2147483649 State \"Extended state\";\n");
+
797 const DbcDatabase parsed = DbcParser().ParseFile(fixture.path.string());
+
798 QCOMPARE(parsed.frames.size(), std::size_t(3U));
+
799 QCOMPARE(parsed.frames[0].canId, parsed.frames[1].canId);
+
800 CHECK(!parsed.frames[0].isExtended && parsed.frames[1].isExtended,
+
801 "Standard and extended frames with the same numeric ID remain distinct");
+
802 QCOMPARE(parsed.frames[0].comment, std::string("Standard"));
+
803 QCOMPARE(parsed.frames[1].comment, std::string("Extended"));
+
804 QCOMPARE(parsed.frames[0].signals[0].comment, std::string("Standard state"));
+
805 QCOMPARE(parsed.frames[1].signals[0].comment, std::string("Extended state"));
+
806 QCOMPARE(parsed.frames[2].signals.size(), std::size_t(2U));
+
807 const DecodeDatabase runtime = DbcDecodeBuilder().Build(parsed);
+
808 RawCanFrame raw;
+
809 raw.canId = 1U;
+
810 raw.data = {7U};
+
811 const DecodedFrameValue standard = DbcDecoder().Decode(runtime, raw);
+
812 QVERIFY(standard.valid);
+
813 QCOMPARE(standard.definition->comment, std::string("Standard"));
+
814 raw.isExtended = true;
+
815 const DecodedFrameValue extended = DbcDecoder().Decode(runtime, raw);
+
816 QVERIFY(extended.valid);
+
817 QCOMPARE(extended.definition->comment, std::string("Extended"));
+
818}
+
819
+
820void DbcParserTest::comments() {
+
821 Fixture fixture;
+
822 fixture.Write(
+
823 "CM_\tBO_\t1\t\"First\";\r\n"
+
824 "CM_\tSG_\t1\tState\t\" Line one \r\n"
+
825 "\r\n"
+
826 "\tBO_ is text; \\\"quoted\\\" and \\\\slash\"\t;\r\n"
+
827 "CM_ BO_ 2147483649 \"Extended\";\r\n"
+
828 "CM_ SG_ 2147483649 State \"Extended signal\";\r\n"
+
829 "BO_ 1 Standard: 1 ECU\r\n"
+
830 "CM_ BO_ 1 \"Last\";\r\n"
+
831 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\r\n"
+
832 "BO_ 2147483649 Extended: 1 ECU\r\n"
+
833 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU");
+
834 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
835 QVERIFY(!result.HasErrors());
+
836 QVERIFY(result.diagnostics.empty());
+
837 QCOMPARE(result.database.frames.size(), std::size_t(2));
+
838 QCOMPARE(result.database.frames[0].comment, std::string("Last"));
+
839 QCOMPARE(result.database.frames[0].signals[0].comment,
+
840 std::string(" Line one \n\n\tBO_ is text; \"quoted\" and \\slash"));
+
841 QCOMPARE(result.database.frames[1].comment, std::string("Extended"));
+
842 QCOMPARE(result.database.frames[1].signals[0].comment, std::string("Extended signal"));
+
843
+
844 const std::string ids[][2] = {
+
845 {"0", "0"},
+
846 {"2047", "2047"},
+
847 {"2048", "2147485696"},
+
848 {"536870911", "2684354559"},
+
849 {"2684354559", "536870911"}};
+
850 for (const std::string(&id)[2]: ids) {
+
851 fixture.Write("CM_ BO_ " + id[0] + " \"Frame\";\nCM_ SG_ " + id[0] +
+
852 " State \"Signal\";\nBO_ " + id[1] + " Frame: 1 ECU\n"
+
853 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n");
+
854 const DbcDatabase database = DbcParser().ParseFile(fixture.path.string());
+
855 QCOMPARE(database.frames[0].comment, std::string("Frame"));
+
856 QCOMPARE(database.frames[0].signals[0].comment, std::string("Signal"));
+
857 }
+
858 fixture.Write("CM_ BO_ 1 \"Line one\nLine two\";\n"
+
859 "BO_ 1 Frame: 0 ECU\nUNKNOWN_ value\nBO_ bad Frame: 0 ECU\n");
+
860 const DbcParseResult following = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
861 QVERIFY(following.HasErrors());
+
862 QCOMPARE(following.diagnostics.size(), std::size_t(2));
+
863 QCOMPARE(following.diagnostics[0].lineNumber, std::size_t(4));
+
864 QCOMPARE(following.diagnostics[1].lineNumber, std::size_t(5));
+
865}
+
866
+
867void DbcParserTest::invalidComments_data() {
+
868 QTest::addColumn<QString>("statement");
+
869 const char *ids[] = {"-1", "+1", "1x", "0x1", "4294967296", "536870912",
+
870 "1073741824", "2684354560", "4294967295"};
+
871 for (const char *id: ids) {
+
872 const std::string frameRow = std::string("frame-id-") + id;
+
873 const std::string signalRow = std::string("signal-id-") + id;
+
874 QTest::newRow(frameRow.c_str()) << QString::fromStdString("CM_ BO_ " + std::string(id) + " \"Text\";");
+
875 QTest::newRow(signalRow.c_str()) << QString::fromStdString("CM_ SG_ " + std::string(id) + " State \"Text\";");
+
876 }
+
877 QTest::newRow("missing-id") << QStringLiteral("CM_ BO_");
+
878 QTest::newRow("missing-signal") << QStringLiteral("CM_ SG_ 1 \"Text\";");
+
879 QTest::newRow("missing-quote") << QStringLiteral("CM_ BO_ 1 Text;");
+
880 QTest::newRow("unclosed") << QStringLiteral("CM_ SG_ 1 State \"Text\n More");
+
881 QTest::newRow("invalid-escape") << QStringLiteral("CM_ BO_ 1 \"bad\\q\";");
+
882 QTest::newRow("dangling-escape") << QStringLiteral("CM_ BO_ 1 \"bad\\");
+
883 QTest::newRow("missing-semicolon") << QStringLiteral("CM_ SG_ 1 State \"Text\"");
+
884 QTest::newRow("trailing-text") << QStringLiteral("CM_ BO_ 1 \"Text\"; garbage");
+
885 QTest::newRow("extra-header") << QStringLiteral("CM_ BO_ 1 extra \"Text\";");
+
886 QTest::newRow("extra-string") << QStringLiteral("CM_ BO_ 1 \"Text\" \"More\";");
+
887 QTest::newRow("missing-frame") << QStringLiteral("CM_ BO_ 2 \"Text\";");
+
888 QTest::newRow("missing-signal-frame") << QStringLiteral("CM_ SG_ 2 State \"Text\";");
+
889 QTest::newRow("missing-signal-reference") << QStringLiteral("CM_ SG_ 1 Missing \"Text\";");
+
890 QTest::newRow("different-frame-type") << QStringLiteral("CM_ BO_ 2147483649 \"Text\";");
+
891 QTest::newRow("missing-multiline-reference") << QStringLiteral("CM_ SG_ 1 Missing \"Text\nMore\";");
+
892}
+
893
+
894void DbcParserTest::invalidComments() {
+
895 QFETCH(QString, statement);
+
896 Fixture fixture;
+
897 fixture.Write("BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n\n" +
+
898 statement.toStdString());
+
899 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
900 QVERIFY(result.HasErrors());
+
901 QVERIFY(result.database.frames.empty());
+
902 QCOMPARE(result.diagnostics.size(), std::size_t(1));
+
903 const DbcDiagnostic &error = result.diagnostics[0];
+
904 QCOMPARE(error.severity, DbcDiagnosticSeverity::Error);
+
905 QCOMPARE(error.code, DbcDiagnosticCode::InvalidStatement);
+
906 QCOMPARE(error.filePath, fixture.path.string());
+
907 QCOMPARE(error.lineNumber, std::size_t(4));
+
908 QVERIFY(!error.message.empty());
+
909 try {
+
910 DbcParser().ParseFile(fixture.path.string());
+
911 QFAIL("Malformed or unresolved comment must throw.");
+
912 } catch (const std::runtime_error &exception) {
+
913 QVERIFY(std::string(exception.what()).find(fixture.path.string() + ":4: ") == 0U);
+
914 }
+
915}
+
916
+
917void DbcParserTest::integration() {
+
918 Fixture fixture;
+
919 fixture.Write(
+
920 "BO_TX_BU_ 256 : ECU;\nBO_ 256 Frame: 8 ECU\n"
+
921 " SG_ Speed : 0|16@1+ (0.5,1) [0|100] \"rpm\" ECU\n"
+
922 "CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 Speed \"Signal comment\";\n"
+
923 "BO_ 257 Second: 8 ECU\n");
+
924 const DbcDatabase database = DbcParser().ParseFile(fixture.path.string());
+
925 CHECK(database.frames.size() == 2U && database.frames[0].signals.size() == 1U &&
+
926 database.frames[0].comment == "Frame comment" &&
+
927 database.frames[0].signals[0].comment == "Signal comment",
+
928 "Integration");
+
929 fixture.Write("\nSG_ Speed : invalid\n");
+
930 try {
+
931 DbcParser().ParseFile(fixture.path.string());
+
932 CHECK(false, "Signal error location");
+
933 } catch (const std::runtime_error &error) {
+
934 CHECK(std::string(error.what()).find(fixture.path.string() + ":2: ") == 0U,
+
935 "Signal error location");
+
936 }
+
937}
+
938
+
939void DbcParserTest::multiplexing() {
+
940 Fixture fixture;
+
941 const std::string selector = "SG_ Mode M : 0|8@1+ (10,3) [0|255] \"\" ECU\n";
+
942 const std::string first = "SG_ First m1 : 8|8@1- (2,0) [-128|127] \"V\" ECU\n";
+
943 const std::string second = "SG_ Second m2 : 15|8@0+ (1,0) [0|255] \"\" ECU\n";
+
944 const std::string common = "SG_ Common : 16|8@1+ (1,0) [0|255] \"\" ECU\n";
+
945 fixture.Write("BO_ 1 Frame: 3 ECU\n" + first + second + common + selector +
+
946 "VAL_ 1 First -1 \"Negative\";\n");
+
947 const DbcDatabase parsed = DbcParser().ParseFile(fixture.path.string());
+
948 const DecodeDatabase runtime = DbcDecodeBuilder().Build(parsed);
+
949 CHECK(parsed.frames[0].signals[3].multiplexing.role == MultiplexRole::Selector,
+
950 "Selector declared after branches");
+
951 const std::unique_ptr<TreeNode> tree = DbcTreeBuilder().Build(parsed);
+
952 CHECK(tree->GetChild(0U)->GetChild(0U)->GetSignal()->multiplexing.value == 1U,
+
953 "Tree preserves multiplexing");
+
954 RawCanFrame raw;
+
955 raw.canId = 1U;
+
956 raw.data = {1U, 255U, 7U};
+
957 DecodedFrameValue result = DbcDecoder().Decode(runtime, raw);
+
958 CHECK(result.signals[0].valid && std::get<std::int64_t>(result.signals[0].rawValue) == -1 &&
+
959 result.signals[0].physicalValue == -2 && *result.signals[0].GetValueDescription() == "Negative",
+
960 "Active signed branch and VAL_ use exact raw selector");
+
961 CHECK(!result.signals[1].valid && result.signals[1].status == SignalDecodeStatus::Inactive &&
+
962 result.signals[1].GetValueDescription() == nullptr && result.signals[2].valid,
+
963 "Inactive branch and unconditional signal");
+
964 raw.data[0] = 2U;
+
965 result = DbcDecoder().Decode(runtime, raw);
+
966 CHECK(result.signals[0].status == SignalDecodeStatus::Inactive && result.signals[1].valid &&
+
967 std::get<std::uint64_t>(result.signals[1].rawValue) == 255U,
+
968 "Motorola branch becomes active");
+
969 raw.data = {3U};
+
970 result = DbcDecoder().Decode(runtime, raw);
+
971 CHECK(result.signals[0].status == SignalDecodeStatus::Inactive &&
+
972 result.signals[1].status == SignalDecodeStatus::Inactive &&
+
973 result.signals[2].status == SignalDecodeStatus::InsufficientPayload,
+
974 "Unmapped selector leaves branches inactive even with missing bytes");
+
975 raw.data = {1U};
+
976 result = DbcDecoder().Decode(runtime, raw);
+
977 CHECK(result.signals[0].status == SignalDecodeStatus::InsufficientPayload,
+
978 "Active branch requires payload");
+
979 raw.data.clear();
+
980 result = DbcDecoder().Decode(runtime, raw);
+
981 CHECK(result.signals[0].status == SignalDecodeStatus::SelectorUnavailable &&
+
982 result.signals[3].status == SignalDecodeStatus::InsufficientPayload,
+
983 "Unavailable selector does not imply inactive branch");
+
984
+
985 const std::vector<std::string> invalid = {
+
986 first,
+
987 selector + "SG_ Other M : 16|8@1- (1,0) [-128|127] \"\" ECU\n",
+
988 "SG_ Mode M : 0|8@1- (1,0) [-128|127] \"\" ECU\n",
+
989 selector + "SG_ TooLarge m256 : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
+
990 selector + first + "SG_ Same m1 : 15|8@0+ (1,0) [0|255] \"\" ECU\n",
+
991 selector + "SG_ Collision m1 : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
+
992 selector + first + "SG_ Always : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
+
993 "SG_MUL_VAL_ 1 First Mode 1-2;\n"};
+
994 for (const std::string &signals : invalid) {
+
995 fixture.Write("BO_ 1 Frame: 3 ECU\n" + signals);
+
996 bool rejected = false;
+
997 try {
+
998 DbcParser().ParseFile(fixture.path.string());
+
999 } catch (const std::runtime_error &error) {
+
1000 rejected = std::string(error.what()).find(fixture.path.string() + ":") == 0U;
+
1001 }
+
1002 CHECK(rejected, "Invalid multiplexing rejected with location");
+
1003 }
+
1004 for (const std::string marker : {"m", "m-1", "m+1", "m1M", "m18446744073709551616", "M m1"})
+
1005 RejectSignal(fixture, "SG_ Bad " + marker + " : 8|8@1+ (1,0) [0|255] \"\" ECU");
+
1006}
+
1007
+
1008void DbcParserTest::multiplexingBoundaries() {
+
1009 Fixture fixture;
+
1010 fixture.Write("BO_ 1 Frame: 9 ECU\n"
+
1011 "SG_ Branch m18446744073709551615 : 64|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1012 "SG_ Mode M : 7|64@0+ (0,5) [0|1] \"\" ECU\n");
+
1013 const DbcDatabase parsed = DbcParser().ParseFile(fixture.path.string());
+
1014 DecodeDatabase runtime = DbcDecodeBuilder().Build(parsed);
+
1015 CHECK(runtime.GetFrames()[0].signals[0].multiplexing.value == std::numeric_limits<std::uint64_t>::max(),
+
1016 "Exact uint64 branch metadata");
+
1017 RawCanFrame raw;
+
1018 raw.canId = 1U;
+
1019 raw.data = std::vector<std::uint8_t>(9U, 255U);
+
1020 DecodedFrameValue result = DbcDecoder().Decode(runtime, raw);
+
1021 CHECK(result.signals[0].valid && result.signals[1].valid &&
+
1022 std::get<std::uint64_t>(result.signals[1].rawValue) == std::numeric_limits<std::uint64_t>::max(),
+
1023 "Motorola uint64 selector compares raw value before zero scaling");
+
1024 raw.data[7] = 254U;
+
1025 result = DbcDecoder().Decode(runtime, raw);
+
1026 CHECK(result.signals[0].status == SignalDecodeStatus::Inactive,
+
1027 "Adjacent large integers remain distinct");
+
1028 std::vector<DecodeFrame> definitions = runtime.GetFrames();
+
1029 definitions[0].signals[1].length = 0U;
1030 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, runtime = DecodeDatabase(definitions));
-
1031 fixture.Write("BO_ 1 Frame: 2 ECU\n\n"
-
1032 "SG_ Branch m2 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1033 "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n");
-
1034 bool rejected = false;
-
1035 try {
-
1036 DbcParser().ParseFile(fixture.path.string());
-
1037 } catch (const std::runtime_error &error) {
-
1038 rejected = std::string(error.what()).find(fixture.path.string() + ":3: ") == 0U;
-
1039 }
-
1040 CHECK(rejected, "Deferred validation reports branch source line");
-
1041 fixture.Write("BO_ 1 Frame: 2 ECU\n"
-
1042 "SG_ A : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1043 "SG_ B : 15|8@0+ (1,0) [0|255] \"\" ECU\n");
-
1044 rejected = false;
-
1045 try {
-
1046 DbcParser().ParseFile(fixture.path.string());
-
1047 } catch (const std::runtime_error &) {
-
1048 rejected = true;
-
1049 }
-
1050 CHECK(rejected, "Ordinary overlapping signals rejected");
-
1051}
-
1052
-
1053void DbcParserTest::invalidSignals() {
-
1054 Fixture fixture;
-
1055 const std::string ordinary = SignalLine("0", "8", "1+", "1", "0", "0", "255");
-
1056 for (const std::string value: {"-1", "+1", "0junk", "0x10", "1.5",
-
1057 "4294967296", "999999999999999999999"}) {
-
1058 RejectSignal(fixture, SignalLine(value, "8", "1+", "1", "0", "0", "255"));
-
1059 RejectSignal(fixture, SignalLine("0", value, "1+", "1", "0", "0", "255"));
-
1060 }
-
1061 for (const std::string length: {"0", "65"})
-
1062 RejectSignal(fixture, SignalLine("0", length, "1+", "1", "0", "0", "255"));
-
1063 for (const std::string order: {"2+", "9-", "1", "1x", "1++", "01+"})
-
1064 RejectSignal(fixture, SignalLine("0", "8", order, "1", "0", "0", "255"));
-
1065 for (const std::string value: {"1junk", "nan", "NaN", "inf", "-inf", "1e999", "1e-999", "0x1p2", "", "1e", "1.2.3"}) {
-
1066 RejectSignal(fixture, SignalLine("0", "8", "1+", value, "0", "0", "255"));
-
1067 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", value, "0", "255"));
-
1068 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", value, "255"));
-
1069 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", "0", value));
-
1070 }
-
1071 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", "2", "1"));
-
1072 for (const std::string name: {"", "Two Names", "Value m1M"}) {
-
1073 std::string line = ordinary;
-
1074 line.replace(4U, 5U, name);
-
1075 RejectSignal(fixture, line);
-
1076 }
-
1077 for (const std::string broken: {
-
1078 "SG_", "SG_ Value", "SG_ Value : 0|8@1+ (1,0) [0|255] ECU",
-
1079 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\"",
-
1080 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU,",
-
1081 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU extra"})
-
1082 RejectSignal(fixture, broken);
-
1083 RejectSignal(fixture, SignalLine("64", "1", "1+", "1", "0", "0", "1"));
-
1084 RejectSignal(fixture, SignalLine("63", "2", "1+", "1", "0", "0", "3"));
-
1085 RejectSignal(fixture, SignalLine("56", "2", "0+", "1", "0", "0", "3"));
-
1086 RejectSignal(fixture, SignalLine("4294967295", "64", "1+", "1", "0", "0", "1"));
-
1087 RejectSignal(fixture, ordinary, 0U);
-
1088 for (const char delimiter: {'|', '@', '(', ')', '[', ']', '"'}) {
-
1089 std::string line = ordinary;
-
1090 line.erase(line.find(delimiter), 1U);
-
1091 RejectSignal(fixture, line);
-
1092 }
-
1093}
-
1094
-
1095void DbcParserTest::validSignals() {
-
1096 Fixture fixture;
-
1097 const DbcParser signalParser;
-
1098 fixture.Write("BO_ 1 Frame: 8 ECU\n" +
-
1099 SignalLine("0", "8", "1+", "0", "0", "0", "0") + "\n" +
-
1100 SignalLine("8", "8", "1-", "-1", "0", "-255", "0", "Negative") + "\n");
-
1101 const DbcDatabase factors = signalParser.ParseFile(fixture.path.string());
-
1102 CHECK(factors.frames[0].signals.size() == 2U &&
-
1103 factors.frames[0].signals[0].factor == 0.0 &&
-
1104 factors.frames[0].signals[1].factor == -1.0,
-
1105 "Zero and negative factors and equal range boundaries");
-
1106
-
1107 struct SignalCase {
-
1108 const char *start;
-
1109 const char *length;
-
1110 const char *order;
-
1111 unsigned payload;
-
1112 };
-
1113 const SignalCase validSignals[] = {
-
1114 {"0", "1", "1+", 1U},
-
1115 {"7", "1", "0-", 1U},
-
1116 {"0", "64", "1+", 8U},
-
1117 {"7", "64", "0-", 8U},
-
1118 {"63", "1", "1+", 8U},
-
1119 {"56", "1", "0+", 8U},
-
1120 {"3", "12", "0+", 2U},
-
1121 {"2039", "1", "1+", 255U}};
-
1122 for (const SignalCase &test: validSignals) {
-
1123 fixture.Write("BO_ 1 Frame: " + std::to_string(test.payload) + " ECU\n" +
-
1124 SignalLine(test.start, test.length, test.order, ".5e+1", "-2.5E-1", "-1.", "+2e2") + "\n");
-
1125 try {
-
1126 const DbcDatabase parsed = signalParser.ParseFile(fixture.path.string());
-
1127 const SignalInfo &signal = parsed.frames.at(0).signals.at(0);
-
1128 QCOMPARE(signal.startBit, static_cast<std::uint32_t>(std::stoul(test.start)));
-
1129 QCOMPARE(signal.length, static_cast<std::uint32_t>(std::stoul(test.length)));
-
1130 QCOMPARE(signal.isLittleEndian, test.order[0] == '1');
-
1131 QCOMPARE(signal.isSigned, test.order[1] == '-');
-
1132 QCOMPARE(signal.factor, 5.0);
-
1133 QCOMPARE(signal.offset, -0.25);
-
1134 QCOMPARE(signal.minimum, -1.0);
-
1135 QCOMPARE(signal.maximum, 200.0);
-
1136 } catch (const std::exception &error) { CHECK(false, error.what()); }
-
1137 }
-
1138 fixture.Write("BO_ 1 Frame: 8 ECU\n"
-
1139 "SG_\tValue\t:\t0 | 8 @1+ ( 1 , 0 ) [ 0 | 255 ] \"rpm\" ECU1,\tECU2\n");
-
1140 try {
-
1141 const DbcDatabase parsed = signalParser.ParseFile(fixture.path.string());
-
1142 const SignalInfo &signal = parsed.frames.at(0).signals.at(0);
-
1143 CHECK(signal.name == "Value" && signal.unit == "rpm" &&
-
1144 signal.receivers == std::vector<std::string>({"ECU1", "ECU2"}),
-
1145 "Signal tabs, spaces, and receiver list");
-
1146 } catch (const std::exception &error) { CHECK(false, error.what()); }
-
1147}
-
1148
-
1149void DbcParserTest::CheckPhysicalNumbers() {
-
1150 Fixture fixture;
-
1151 struct PhysicalCase {
-
1152 const char *factor;
-
1153 const char *offset;
-
1154 const char *minimum;
-
1155 const char *maximum;
-
1156 double expectedFactor;
-
1157 double expectedOffset;
-
1158 double expectedMinimum;
-
1159 double expectedMaximum;
-
1160 };
-
1161 const PhysicalCase valid[] = {
-
1162 {"+1.25", "-0.5", "-2.75", "255.5", 1.25, -0.5, -2.75, 255.5},
-
1163 {"+.5e+1", "-2.5E-1", "-1.", "+2e2", 5.0, -0.25, -1.0, 200.0},
-
1164 {"0e-999", "+0.", "-.0", ".0e+999", 0.0, 0.0, 0.0, 0.0},
-
1165 {"1.7976931348623157e308", "2.2250738585072014e-308", "-1e308", "+1e308",
-
1166 std::numeric_limits<double>::max(), std::numeric_limits<double>::min(), -1e308, 1e308}};
-
1167 for (const PhysicalCase &test: valid) {
-
1168 fixture.Write("BO_ 1 Frame: 1 ECU\n" +
-
1169 SignalLine("0", "8", "1+", test.factor, test.offset, test.minimum, test.maximum) + "\n");
-
1170 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
1171 QVERIFY(!result.HasErrors());
-
1172 QVERIFY(result.diagnostics.empty());
-
1173 QCOMPARE(result.database.frames.size(), std::size_t(1));
-
1174 const SignalInfo &signal = result.database.frames[0].signals[0];
-
1175 QCOMPARE(signal.factor, test.expectedFactor);
-
1176 QCOMPARE(signal.offset, test.expectedOffset);
-
1177 QCOMPARE(signal.minimum, test.expectedMinimum);
-
1178 QCOMPARE(signal.maximum, test.expectedMaximum);
-
1179 }
-
1180
-
1181 const char *invalid[] = {"1,5", "1.234,5", "1,234.5", "1junk", "nan", "inf", "-inf",
-
1182 "1e999", "1e-999", "1e-320", "-1e-320", "0x1p2", "1e", "1.2.3"};
-
1183 for (const char *value: invalid) {
-
1184 RejectSignal(fixture, SignalLine("0", "8", "1+", value, "0", "0", "255"));
-
1185 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", value, "0", "255"));
-
1186 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", value, "255"));
-
1187 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", "0", value));
-
1188 }
-
1189 RejectSignal(fixture, SignalLine("0", "8", "1+", "1.25", "0", "2.5", "1.5"));
-
1190}
-
1191
-
1192void DbcParserTest::physicalNumbersCppLocale() {
-
1193 const ApplicationLocaleGuard guard;
-
1194 std::locale::global(std::locale(std::locale::classic(), new CommaDecimalPunctuation));
-
1195 QCOMPARE(std::use_facet<std::numpunct<char>>(std::locale()).decimal_point(), ',');
-
1196 CheckPhysicalNumbers();
-
1197 QCOMPARE(std::use_facet<std::numpunct<char>>(std::locale()).decimal_point(), ',');
-
1198}
-
1199
-
1200void DbcParserTest::physicalNumbersCLocale() {
-
1201 const ApplicationLocaleGuard guard;
-
1202 bool found = false;
-
1203 const char *names[] = {"de_DE.UTF-8", "de_DE.utf8", "fr_FR.UTF-8", "fr_FR.utf8",
-
1204 "German_Germany.1252", "French_France.1252"};
-
1205 for (const char *name: names) {
-
1206 if (std::setlocale(LC_NUMERIC, name) && std::string(std::localeconv()->decimal_point) == ",") {
-
1207 found = true;
-
1208 break;
-
1209 }
-
1210 }
-
1211 if (!found)
-
1212 QSKIP("No named comma-decimal C locale is installed; generate de_DE.UTF-8 and set LOCPATH.");
-
1213 QCOMPARE(std::string(std::localeconv()->decimal_point), std::string(","));
-
1214 CheckPhysicalNumbers();
-
1215 QCOMPARE(std::string(std::localeconv()->decimal_point), std::string(","));
-
1216}
-
1217
-
1218void DbcParserTest::dispatch() {
-
1219 CountingHandler::instances = 0;
-
1220 CountingHandler::calls = 0;
-
1221 DbcDatabase dispatchDatabase;
-
1222 DbcParseContext context{dispatchDatabase, nullptr};
-
1223 DbcStatementDispatcher dispatcher;
-
1224 dispatcher.RegisterType<CountingHandler>("TEST_");
-
1225 CHECK(dispatcher.Dispatch("TEST_ first", context) &&
-
1226 dispatcher.Dispatch("TEST_ second", context) &&
-
1227 CountingHandler::instances == 1 && CountingHandler::calls == 2 &&
-
1228 dispatchDatabase.frames.size() == 2U,
-
1229 "Factory dispatch and handler reuse");
-
1230 CHECK(!dispatcher.Dispatch("TEST_OTHER data", context) &&
-
1231 !dispatcher.Dispatch("BO_TX_BU_ 1 : ECU;", context) &&
-
1232 !dispatcher.Dispatch("VAL_TABLE_ States 0 \"Stopped\";", context) &&
-
1233 CountingHandler::calls == 2,
-
1234 "Exact and unknown keyword dispatch");
-
1235}
-
1236
-
1237void DbcParserTest::independentSessions() {
-
1238 Fixture fixture;
-
1239 const DbcParser parser;
-
1240 fixture.Write("BO_ 1 First: 8 ECU\n");
-
1241 CHECK(parser.ParseFile(fixture.path.string()).frames.size() == 1U, "First parse");
-
1242 fixture.Write("SG_ Speed : invalid\n");
-
1243 try {
-
1244 parser.ParseFile(fixture.path.string());
-
1245 CHECK(false, "Independent parse context");
-
1246 } catch (const std::runtime_error &error) {
-
1247 CHECK(std::string(error.what()).find("Signal found before any frame") !=
-
1248 std::string::npos,
-
1249 "Independent parse context");
-
1250 }
-
1251 fixture.Write("BO_ 2 Second: 8 ECU\n");
-
1252 const DbcDatabase recovered = parser.ParseFile(fixture.path.string());
-
1253 CHECK(recovered.frames.size() == 1U && recovered.frames[0].canId == 2U,
-
1254 "Fresh parse after error");
-
1255}
-
1256
-
1257
-
1258void DbcParserTest::valueDescriptions() {
-
1259 Fixture fixture;
-
1260 fixture.Write(
-
1261 "VAL_\t256\tState\t0\t\"Off\"\t1 \"On\" 2 \"Error with spaces; and \\\"quotes\\\"\";\n"
-
1262 "BO_ 256 Status: 8 ECU\n"
-
1263 "SG_ State : 0|8@1+ (2,10) [0|520] \"\" ECU\n"
-
1264 "VAL_ 256 State 3 \"\";\n"
-
1265 "VAL_ 2147483904 State -9223372036854775808 \"Minimum\" -1 \"Negative\" 9223372036854775807 \"Maximum\";\n"
-
1266 "BO_ 2147483904 Extended: 8 ECU\n"
-
1267 "SG_ State : 0|64@1- (1,0) [-1e20|1e20] \"\" ECU\n"
-
1268 "BO_ 2048 Unsigned: 8 ECU\n"
-
1269 "SG_ State : 0|64@1+ (1,0) [0|2e20] \"\" ECU\n"
-
1270 "VAL_ 2147485696 State 9223372036854775808 \"High\" 18446744073709551615 \"Full\";\n");
-
1271 const DbcDatabase parsed = DbcParser().ParseFile(fixture.path.string());
-
1272 const DecodeDatabase runtime = DbcDecodeBuilder().Build(parsed);
-
1273 const std::unique_ptr<TreeNode> tree = DbcTreeBuilder().Build(parsed);
-
1274 CHECK(tree->GetChild(0)->GetSignal() == nullptr, "Frame node");
-
1275 for (std::size_t index = 0; index < parsed.frames.size(); ++index) {
-
1276 CHECK(tree->GetChild(index)->GetChild(0)->GetSignal()->valueDescriptions ==
-
1277 parsed.frames[index].signals[0].valueDescriptions &&
-
1278 runtime.GetFrames()[index].signals[0].valueDescriptions ==
-
1279 parsed.frames[index].signals[0].valueDescriptions,
-
1280 "All mappings survive both builders");
-
1281 }
-
1282 CHECK(tree->GetChild(0)->GetChild(0)->GetSignal()->valueDescriptions ==
-
1283 runtime.GetFrames()[0].signals[0].valueDescriptions,
-
1284 "Both builders preserve descriptions");
-
1285 RawCanFrame frame;
-
1286 frame.canId = 256;
-
1287 frame.data.resize(8);
-
1288 const std::string expected[] = {"Off", "On", "Error with spaces; and \"quotes\"", ""};
-
1289 for (unsigned value = 0; value < 5; ++value) {
-
1290 frame.data[0] = value;
-
1291 const DecodedSignalValue decoded = DbcDecoder().Decode(runtime, frame).signals[0];
-
1292 CHECK(decoded.valid && decoded.physicalValue == value * 2 + 10, "Numeric scaling unchanged");
-
1293 const std::string *description = decoded.GetValueDescription();
-
1294 CHECK(value < 4 ? description && *description == expected[value] : !description,
-
1295 "Raw lookup and unknown value");
-
1296 }
-
1297 frame.isExtended = true;
-
1298 struct Boundary {
-
1299 std::uint64_t bits;
-
1300 const char *description;
-
1301 };
-
1302 for (const Boundary &test: {Boundary{0x8000000000000000ULL, "Minimum"},
-
1303 Boundary{0xFFFFFFFFFFFFFFFFULL, "Negative"},
-
1304 Boundary{0x7FFFFFFFFFFFFFFFULL, "Maximum"}}) {
-
1305 for (unsigned byte = 0; byte < 8; ++byte)
-
1306 frame.data[byte] = static_cast<std::uint8_t>(test.bits >> (byte * 8));
-
1307 const DecodedSignalValue decoded = DbcDecoder().Decode(runtime, frame).signals[0];
-
1308 CHECK(decoded.GetValueDescription() && *decoded.GetValueDescription() == test.description,
-
1309 "Signed boundaries and distinct extended frame");
-
1310 }
-
1311 frame.canId = 2048;
-
1312 for (const Boundary &test: {Boundary{0x8000000000000000ULL, "High"},
-
1313 Boundary{0xFFFFFFFFFFFFFFFFULL, "Full"}}) {
-
1314 for (unsigned byte = 0; byte < 8; ++byte)
-
1315 frame.data[byte] = static_cast<std::uint8_t>(test.bits >> (byte * 8));
-
1316 const DecodedSignalValue decoded = DbcDecoder().Decode(runtime, frame).signals[0];
-
1317 CHECK(decoded.GetValueDescription() && *decoded.GetValueDescription() == test.description,
-
1318 "Unsigned boundaries");
-
1319 }
-
1320 CHECK(FindValueDescription(runtime.GetFrames()[0].signals[0].valueDescriptions, std::int64_t(1)) != nullptr,
-
1321 "Nonnegative signed lookup matches unsigned key");
-
1322 frame.data.clear();
-
1323 CHECK(!DbcDecoder().Decode(runtime, frame).signals[0].GetValueDescription(), "Invalid decode has no description");
-
1324}
-
1325
-
1326void DbcParserTest::invalidValueDescriptions() {
-
1327 Fixture fixture;
-
1328 const std::string definitions = "BO_ 1 Frame: 8 ECU\nSG_ State : 0|64@1+ (1,0) [0|2e20] \"\" ECU\n";
-
1329 for (const std::string statement: {
-
1330 "VAL_", "VAL_ 1 State;", "VAL_ 1 State ;", "VAL_ 1 State 0 \"Off\"",
-
1331 "VAL_ 1 State 0 \"Off\"; junk", "VAL_ 1 State 0 Off;",
-
1332 "VAL_ 1 State 0 \"Unclosed;", "VAL_ 1 State 0 \"Off\"1 \"On\";",
-
1333 "VAL_ 1 State 0 \"Off\" 00 \"Duplicate\";",
-
1334 "VAL_ 1 State 0 \"Off\" -0 \"Duplicate\";",
-
1335 "VAL_ 1 State 0 \"Off\";\nVAL_ 1 State 0 \"Duplicate\";",
-
1336 "VAL_ 2 State 0 \"Missing frame\";", "VAL_ 1 Missing 0 \"Missing signal\";",
-
1337 "VAL_ -1 State 0 \"Bad ID\";", "VAL_ 536870912 State 0 \"Bad ID\";",
-
1338 "VAL_ 4294967296 State 0 \"Bad ID\";", "VAL_ Environment 0 \"Unsupported\";",
-
1339 "VAL_ 1 State 0 \"Bad\\q escape\";"}) {
-
1340 fixture.Write(definitions + statement + '\n');
-
1341 const int line = statement.find('\n') == std::string::npos ? 3 : 4;
-
1342 try {
-
1343 DbcParser().ParseFile(fixture.path.string());
-
1344 CHECK(false, statement);
-
1345 } catch (const std::runtime_error &error) {
-
1346 CHECK(std::string(error.what()).find(fixture.path.string() + ":" + std::to_string(line) + ": ") == 0,
-
1347 statement + " " + error.what());
-
1348 }
-
1349 }
-
1350 for (const std::string suffix: {
-
1351 "BO_ 1 Other: 8 ECU\n", // Duplicate frame definition.
-
1352 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n",// Duplicate signal definition.
-
1353 ""}) {
-
1354 fixture.Write("VAL_ 1 " + std::string(suffix.empty() ? "Missing" : "State") +
-
1355 " 0 \"Description\";\n" + definitions + suffix);
-
1356 try {
-
1357 DbcParser().ParseFile(fixture.path.string());
-
1358 CHECK(false, "Missing reference or duplicate definition");
-
1359 } catch (const std::runtime_error &error) {
-
1360 const std::string line = suffix.empty() ? "1" : "4";
-
1361 CHECK(std::string(error.what()).find(fixture.path.string() + ":" + line + ": ") == 0,
-
1362 "Error retains the reference or duplicate definition source location");
-
1363 }
-
1364 }
-
1365 for (const std::string number: {"+1", "1x", "0x10", "1.5", "1e2", "--1",
-
1366 "-9223372036854775809", "18446744073709551616"}) {
-
1367 fixture.Write("VAL_ 1 State " + number + " \"Bad\";\n" + definitions);
-
1368 try {
-
1369 DbcParser().ParseFile(fixture.path.string());
-
1370 CHECK(false, number);
-
1371 } catch (const std::runtime_error &error) {
-
1372 CHECK(std::string(error.what()).find(fixture.path.string() + ":1: ") == 0, number);
-
1373 }
-
1374 }
-
1375}
-
1376
+
1031 definitions[0].signals.pop_back();
+
1032 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, runtime = DecodeDatabase(definitions));
+
1033 fixture.Write("BO_ 1 Frame: 2 ECU\n\n"
+
1034 "SG_ Branch m2 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1035 "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n");
+
1036 bool rejected = false;
+
1037 try {
+
1038 DbcParser().ParseFile(fixture.path.string());
+
1039 } catch (const std::runtime_error &error) {
+
1040 rejected = std::string(error.what()).find(fixture.path.string() + ":3: ") == 0U;
+
1041 }
+
1042 CHECK(rejected, "Deferred validation reports branch source line");
+
1043 fixture.Write("BO_ 1 Frame: 2 ECU\n"
+
1044 "SG_ A : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1045 "SG_ B : 15|8@0+ (1,0) [0|255] \"\" ECU\n");
+
1046 rejected = false;
+
1047 try {
+
1048 DbcParser().ParseFile(fixture.path.string());
+
1049 } catch (const std::runtime_error &) {
+
1050 rejected = true;
+
1051 }
+
1052 CHECK(rejected, "Ordinary overlapping signals rejected");
+
1053}
+
1054
+
1055void DbcParserTest::invalidSignals() {
+
1056 Fixture fixture;
+
1057 const std::string ordinary = SignalLine("0", "8", "1+", "1", "0", "0", "255");
+
1058 for (const std::string value: {"-1", "+1", "0junk", "0x10", "1.5",
+
1059 "4294967296", "999999999999999999999"}) {
+
1060 RejectSignal(fixture, SignalLine(value, "8", "1+", "1", "0", "0", "255"));
+
1061 RejectSignal(fixture, SignalLine("0", value, "1+", "1", "0", "0", "255"));
+
1062 }
+
1063 for (const std::string length: {"0", "65"})
+
1064 RejectSignal(fixture, SignalLine("0", length, "1+", "1", "0", "0", "255"));
+
1065 for (const std::string order: {"2+", "9-", "1", "1x", "1++", "01+"})
+
1066 RejectSignal(fixture, SignalLine("0", "8", order, "1", "0", "0", "255"));
+
1067 for (const std::string value: {"1junk", "nan", "NaN", "inf", "-inf", "1e999", "1e-999", "0x1p2", "", "1e", "1.2.3"}) {
+
1068 RejectSignal(fixture, SignalLine("0", "8", "1+", value, "0", "0", "255"));
+
1069 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", value, "0", "255"));
+
1070 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", value, "255"));
+
1071 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", "0", value));
+
1072 }
+
1073 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", "2", "1"));
+
1074 for (const std::string name: {"", "Two Names", "Value m1M"}) {
+
1075 std::string line = ordinary;
+
1076 line.replace(4U, 5U, name);
+
1077 RejectSignal(fixture, line);
+
1078 }
+
1079 for (const std::string broken: {
+
1080 "SG_", "SG_ Value", "SG_ Value : 0|8@1+ (1,0) [0|255] ECU",
+
1081 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\"",
+
1082 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU,",
+
1083 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU extra"})
+
1084 RejectSignal(fixture, broken);
+
1085 RejectSignal(fixture, SignalLine("64", "1", "1+", "1", "0", "0", "1"));
+
1086 RejectSignal(fixture, SignalLine("63", "2", "1+", "1", "0", "0", "3"));
+
1087 RejectSignal(fixture, SignalLine("56", "2", "0+", "1", "0", "0", "3"));
+
1088 RejectSignal(fixture, SignalLine("4294967295", "64", "1+", "1", "0", "0", "1"));
+
1089 RejectSignal(fixture, ordinary, 0U);
+
1090 for (const char delimiter: {'|', '@', '(', ')', '[', ']', '"'}) {
+
1091 std::string line = ordinary;
+
1092 line.erase(line.find(delimiter), 1U);
+
1093 RejectSignal(fixture, line);
+
1094 }
+
1095}
+
1096
+
1097void DbcParserTest::validSignals() {
+
1098 Fixture fixture;
+
1099 const DbcParser signalParser;
+
1100 fixture.Write("BO_ 1 Frame: 8 ECU\n" +
+
1101 SignalLine("0", "8", "1+", "0", "0", "0", "0") + "\n" +
+
1102 SignalLine("8", "8", "1-", "-1", "0", "-255", "0", "Negative") + "\n");
+
1103 const DbcDatabase factors = signalParser.ParseFile(fixture.path.string());
+
1104 CHECK(factors.frames[0].signals.size() == 2U &&
+
1105 factors.frames[0].signals[0].factor == 0.0 &&
+
1106 factors.frames[0].signals[1].factor == -1.0,
+
1107 "Zero and negative factors and equal range boundaries");
+
1108
+
1109 struct SignalCase {
+
1110 const char *start;
+
1111 const char *length;
+
1112 const char *order;
+
1113 unsigned payload;
+
1114 };
+
1115 const SignalCase validSignals[] = {
+
1116 {"0", "1", "1+", 1U},
+
1117 {"7", "1", "0-", 1U},
+
1118 {"0", "64", "1+", 8U},
+
1119 {"7", "64", "0-", 8U},
+
1120 {"63", "1", "1+", 8U},
+
1121 {"56", "1", "0+", 8U},
+
1122 {"3", "12", "0+", 2U},
+
1123 {"2039", "1", "1+", 255U}};
+
1124 for (const SignalCase &test: validSignals) {
+
1125 fixture.Write("BO_ 1 Frame: " + std::to_string(test.payload) + " ECU\n" +
+
1126 SignalLine(test.start, test.length, test.order, ".5e+1", "-2.5E-1", "-1.", "+2e2") + "\n");
+
1127 try {
+
1128 const DbcDatabase parsed = signalParser.ParseFile(fixture.path.string());
+
1129 const SignalInfo &signal = parsed.frames.at(0).signals.at(0);
+
1130 QCOMPARE(signal.startBit, static_cast<std::uint32_t>(std::stoul(test.start)));
+
1131 QCOMPARE(signal.length, static_cast<std::uint32_t>(std::stoul(test.length)));
+
1132 QCOMPARE(signal.isLittleEndian, test.order[0] == '1');
+
1133 QCOMPARE(signal.isSigned, test.order[1] == '-');
+
1134 QCOMPARE(signal.factor, 5.0);
+
1135 QCOMPARE(signal.offset, -0.25);
+
1136 QCOMPARE(signal.minimum, -1.0);
+
1137 QCOMPARE(signal.maximum, 200.0);
+
1138 } catch (const std::exception &error) { CHECK(false, error.what()); }
+
1139 }
+
1140 fixture.Write("BO_ 1 Frame: 8 ECU\n"
+
1141 "SG_\tValue\t:\t0 | 8 @1+ ( 1 , 0 ) [ 0 | 255 ] \"rpm\" ECU1,\tECU2\n");
+
1142 try {
+
1143 const DbcDatabase parsed = signalParser.ParseFile(fixture.path.string());
+
1144 const SignalInfo &signal = parsed.frames.at(0).signals.at(0);
+
1145 CHECK(signal.name == "Value" && signal.unit == "rpm" &&
+
1146 signal.receivers == std::vector<std::string>({"ECU1", "ECU2"}),
+
1147 "Signal tabs, spaces, and receiver list");
+
1148 } catch (const std::exception &error) { CHECK(false, error.what()); }
+
1149}
+
1150
+
1151void DbcParserTest::longSignalDefinitions_data() {
+
1152 QTest::addColumn<std::string>("line");
+
1153 QTest::addColumn<bool>("valid");
+
1154 QTest::addColumn<std::string>("unit");
+
1155 const std::string longText(50000U, 'x');
+
1156 const std::string prefix = "SG_ Value : 0|8@1+ (1,0) [0|255] ";
+
1157 QTest::newRow("long-unit") << prefix + "\"" + longText + "\" ECU" << true << longText;
+
1158 QTest::newRow("unterminated-unit") << prefix + "\"" + longText << false << std::string();
+
1159 QTest::newRow("missing-receiver-separator") << prefix + "\"" + longText + "\"ECU" << false << std::string();
+
1160 QTest::newRow("long-name") << SignalLine("0", "8", "1+", "1", "0", "0", "255", longText) << true << std::string();
+
1161 QTest::newRow("long-receiver") << prefix + "\"\" " + longText << true << std::string();
+
1162 std::string receivers = "ECU";
+
1163 for (unsigned index = 0U; index < 10000U; ++index)
+
1164 receivers += ",ECU";
+
1165 QTest::newRow("many-receivers") << prefix + "\"\" " + receivers << true << std::string();
+
1166 QTest::newRow("trailing-receiver-comma") << prefix + "\"\" " + receivers + "," << false << std::string();
+
1167 QTest::newRow("long-factor") << SignalLine("0", "8", "1+", std::string(50000U, '0') + "1", "0", "0", "255") << true << std::string();
+
1168 QTest::newRow("invalid-long-factor") << SignalLine("0", "8", "1+", std::string(50000U, '0') + "1x", "0", "0", "255") << false << std::string();
+
1169 QTest::newRow("overflowing-start-bit") << SignalLine(std::string(50000U, '9'), "8", "1+", "1", "0", "0", "255") << false << std::string();
+
1170 QTest::newRow("missing-colon") << "SG_ " + longText << false << std::string();
+
1171}
+
1172
+
1173void DbcParserTest::longSignalDefinitions() {
+
1174 QFETCH(std::string, line);
+
1175 QFETCH(bool, valid);
+
1176 QFETCH(std::string, unit);
+
1177 Fixture fixture;
+
1178 fixture.Write("BO_ 1 Frame: 1 ECU\n" + line + "\n");
+
1179 const DbcParser parser;
+
1180 const DbcParseResult result = parser.ParseFileWithDiagnostics(fixture.path.string());
+
1181 QCOMPARE(result.HasErrors(), !valid);
+
1182 if (valid) {
+
1183 QVERIFY(result.diagnostics.empty());
+
1184 const DbcDatabase database = parser.ParseFile(fixture.path.string());
+
1185 QCOMPARE(database.frames.size(), std::size_t(1));
+
1186 QCOMPARE(database.frames[0].signals.size(), std::size_t(1));
+
1187 QCOMPARE(database.frames[0].signals[0].unit, unit);
+
1188 QCOMPARE(result.database.frames[0].signals[0].unit, unit);
+
1189 QCOMPARE(database.frames[0].signals[0].factor, 1.0);
+
1190 QVERIFY(!database.frames[0].signals[0].receivers.empty());
+
1191 } else {
+
1192 QVERIFY(result.database.frames.empty());
+
1193 QCOMPARE(result.diagnostics.size(), std::size_t(1));
+
1194 QCOMPARE(result.diagnostics[0].code, DbcDiagnosticCode::InvalidStatement);
+
1195 QCOMPARE(result.diagnostics[0].lineNumber, std::size_t(2));
+
1196 QCOMPARE(result.diagnostics[0].filePath, fixture.path.string());
+
1197 RejectSignal(fixture, line, 1U);
+
1198 }
+
1199}
+
1200
+
1201void DbcParserTest::CheckPhysicalNumbers() {
+
1202 Fixture fixture;
+
1203 struct PhysicalCase {
+
1204 const char *factor;
+
1205 const char *offset;
+
1206 const char *minimum;
+
1207 const char *maximum;
+
1208 double expectedFactor;
+
1209 double expectedOffset;
+
1210 double expectedMinimum;
+
1211 double expectedMaximum;
+
1212 };
+
1213 const PhysicalCase valid[] = {
+
1214 {"+1.25", "-0.5", "-2.75", "255.5", 1.25, -0.5, -2.75, 255.5},
+
1215 {"+.5e+1", "-2.5E-1", "-1.", "+2e2", 5.0, -0.25, -1.0, 200.0},
+
1216 {"0e-999", "+0.", "-.0", ".0e+999", 0.0, 0.0, 0.0, 0.0},
+
1217 {"1.7976931348623157e308", "2.2250738585072014e-308", "-1e308", "+1e308",
+
1218 std::numeric_limits<double>::max(), std::numeric_limits<double>::min(), -1e308, 1e308}};
+
1219 for (const PhysicalCase &test: valid) {
+
1220 fixture.Write("BO_ 1 Frame: 1 ECU\n" +
+
1221 SignalLine("0", "8", "1+", test.factor, test.offset, test.minimum, test.maximum) + "\n");
+
1222 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
1223 QVERIFY(!result.HasErrors());
+
1224 QVERIFY(result.diagnostics.empty());
+
1225 QCOMPARE(result.database.frames.size(), std::size_t(1));
+
1226 const SignalInfo &signal = result.database.frames[0].signals[0];
+
1227 QCOMPARE(signal.factor, test.expectedFactor);
+
1228 QCOMPARE(signal.offset, test.expectedOffset);
+
1229 QCOMPARE(signal.minimum, test.expectedMinimum);
+
1230 QCOMPARE(signal.maximum, test.expectedMaximum);
+
1231 }
+
1232
+
1233 const char *invalid[] = {"1,5", "1.234,5", "1,234.5", "1junk", "nan", "inf", "-inf",
+
1234 "1e999", "1e-999", "1e-320", "-1e-320", "0x1p2", "1e", "1.2.3"};
+
1235 for (const char *value: invalid) {
+
1236 RejectSignal(fixture, SignalLine("0", "8", "1+", value, "0", "0", "255"));
+
1237 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", value, "0", "255"));
+
1238 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", value, "255"));
+
1239 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", "0", value));
+
1240 }
+
1241 RejectSignal(fixture, SignalLine("0", "8", "1+", "1.25", "0", "2.5", "1.5"));
+
1242}
+
1243
+
1244void DbcParserTest::physicalNumbersCppLocale() {
+
1245 const ApplicationLocaleGuard guard;
+
1246 std::locale::global(std::locale(std::locale::classic(), new CommaDecimalPunctuation));
+
1247 QCOMPARE(std::use_facet<std::numpunct<char>>(std::locale()).decimal_point(), ',');
+
1248 CheckPhysicalNumbers();
+
1249 QCOMPARE(std::use_facet<std::numpunct<char>>(std::locale()).decimal_point(), ',');
+
1250}
+
1251
+
1252void DbcParserTest::physicalNumbersCLocale() {
+
1253 const ApplicationLocaleGuard guard;
+
1254 bool found = false;
+
1255 const char *names[] = {"de_DE.UTF-8", "de_DE.utf8", "fr_FR.UTF-8", "fr_FR.utf8",
+
1256 "German_Germany.1252", "French_France.1252"};
+
1257 for (const char *name: names) {
+
1258 if (std::setlocale(LC_NUMERIC, name) && std::string(std::localeconv()->decimal_point) == ",") {
+
1259 found = true;
+
1260 break;
+
1261 }
+
1262 }
+
1263 if (!found)
+
1264 QSKIP("No named comma-decimal C locale is installed; generate de_DE.UTF-8 and set LOCPATH.");
+
1265 QCOMPARE(std::string(std::localeconv()->decimal_point), std::string(","));
+
1266 CheckPhysicalNumbers();
+
1267 QCOMPARE(std::string(std::localeconv()->decimal_point), std::string(","));
+
1268}
+
1269
+
1270void DbcParserTest::dispatch() {
+
1271 CountingHandler::instances = 0;
+
1272 CountingHandler::calls = 0;
+
1273 DbcDatabase dispatchDatabase;
+
1274 DbcParseContext context{dispatchDatabase, nullptr};
+
1275 DbcStatementDispatcher dispatcher;
+
1276 dispatcher.RegisterType<CountingHandler>("TEST_");
+
1277 CHECK(dispatcher.Dispatch("TEST_ first", context) &&
+
1278 dispatcher.Dispatch("TEST_ second", context) &&
+
1279 CountingHandler::instances == 1 && CountingHandler::calls == 2 &&
+
1280 dispatchDatabase.frames.size() == 2U,
+
1281 "Factory dispatch and handler reuse");
+
1282 CHECK(!dispatcher.Dispatch("TEST_OTHER data", context) &&
+
1283 !dispatcher.Dispatch("BO_TX_BU_ 1 : ECU;", context) &&
+
1284 !dispatcher.Dispatch("VAL_TABLE_ States 0 \"Stopped\";", context) &&
+
1285 CountingHandler::calls == 2,
+
1286 "Exact and unknown keyword dispatch");
+
1287}
+
1288
+
1289void DbcParserTest::independentSessions() {
+
1290 Fixture fixture;
+
1291 const DbcParser parser;
+
1292 fixture.Write("BO_ 1 First: 8 ECU\n");
+
1293 CHECK(parser.ParseFile(fixture.path.string()).frames.size() == 1U, "First parse");
+
1294 fixture.Write("SG_ Speed : invalid\n");
+
1295 try {
+
1296 parser.ParseFile(fixture.path.string());
+
1297 CHECK(false, "Independent parse context");
+
1298 } catch (const std::runtime_error &error) {
+
1299 CHECK(std::string(error.what()).find("Signal found before any frame") !=
+
1300 std::string::npos,
+
1301 "Independent parse context");
+
1302 }
+
1303 fixture.Write("BO_ 2 Second: 8 ECU\n");
+
1304 const DbcDatabase recovered = parser.ParseFile(fixture.path.string());
+
1305 CHECK(recovered.frames.size() == 1U && recovered.frames[0].canId == 2U,
+
1306 "Fresh parse after error");
+
1307}
+
1308
+
1309
+
1310void DbcParserTest::valueDescriptions() {
+
1311 Fixture fixture;
+
1312 fixture.Write(
+
1313 "VAL_\t256\tState\t0\t\"Off\"\t1 \"On\" 2 \"Error with spaces; and \\\"quotes\\\"\";\n"
+
1314 "BO_ 256 Status: 8 ECU\n"
+
1315 "SG_ State : 0|8@1+ (2,10) [0|520] \"\" ECU\n"
+
1316 "VAL_ 256 State 3 \"\";\n"
+
1317 "VAL_ 2147483904 State -9223372036854775808 \"Minimum\" -1 \"Negative\" 9223372036854775807 \"Maximum\";\n"
+
1318 "BO_ 2147483904 Extended: 8 ECU\n"
+
1319 "SG_ State : 0|64@1- (1,0) [-1e20|1e20] \"\" ECU\n"
+
1320 "BO_ 2048 Unsigned: 8 ECU\n"
+
1321 "SG_ State : 0|64@1+ (1,0) [0|2e20] \"\" ECU\n"
+
1322 "VAL_ 2147485696 State 9223372036854775808 \"High\" 18446744073709551615 \"Full\";\n");
+
1323 const DbcDatabase parsed = DbcParser().ParseFile(fixture.path.string());
+
1324 const DecodeDatabase runtime = DbcDecodeBuilder().Build(parsed);
+
1325 const std::unique_ptr<TreeNode> tree = DbcTreeBuilder().Build(parsed);
+
1326 CHECK(tree->GetChild(0)->GetSignal() == nullptr, "Frame node");
+
1327 for (std::size_t index = 0; index < parsed.frames.size(); ++index) {
+
1328 CHECK(tree->GetChild(index)->GetChild(0)->GetSignal()->valueDescriptions ==
+
1329 parsed.frames[index].signals[0].valueDescriptions &&
+
1330 runtime.GetFrames()[index].signals[0].valueDescriptions ==
+
1331 parsed.frames[index].signals[0].valueDescriptions,
+
1332 "All mappings survive both builders");
+
1333 }
+
1334 CHECK(tree->GetChild(0)->GetChild(0)->GetSignal()->valueDescriptions ==
+
1335 runtime.GetFrames()[0].signals[0].valueDescriptions,
+
1336 "Both builders preserve descriptions");
+
1337 RawCanFrame frame;
+
1338 frame.canId = 256;
+
1339 frame.data.resize(8);
+
1340 const std::string expected[] = {"Off", "On", "Error with spaces; and \"quotes\"", ""};
+
1341 for (unsigned value = 0; value < 5; ++value) {
+
1342 frame.data[0] = value;
+
1343 const DecodedSignalValue decoded = DbcDecoder().Decode(runtime, frame).signals[0];
+
1344 CHECK(decoded.valid && decoded.physicalValue == value * 2 + 10, "Numeric scaling unchanged");
+
1345 const std::string *description = decoded.GetValueDescription();
+
1346 CHECK(value < 4 ? description && *description == expected[value] : !description,
+
1347 "Raw lookup and unknown value");
+
1348 }
+
1349 frame.isExtended = true;
+
1350 struct Boundary {
+
1351 std::uint64_t bits;
+
1352 const char *description;
+
1353 };
+
1354 for (const Boundary &test: {Boundary{0x8000000000000000ULL, "Minimum"},
+
1355 Boundary{0xFFFFFFFFFFFFFFFFULL, "Negative"},
+
1356 Boundary{0x7FFFFFFFFFFFFFFFULL, "Maximum"}}) {
+
1357 for (unsigned byte = 0; byte < 8; ++byte)
+
1358 frame.data[byte] = static_cast<std::uint8_t>(test.bits >> (byte * 8));
+
1359 const DecodedSignalValue decoded = DbcDecoder().Decode(runtime, frame).signals[0];
+
1360 CHECK(decoded.GetValueDescription() && *decoded.GetValueDescription() == test.description,
+
1361 "Signed boundaries and distinct extended frame");
+
1362 }
+
1363 frame.canId = 2048;
+
1364 for (const Boundary &test: {Boundary{0x8000000000000000ULL, "High"},
+
1365 Boundary{0xFFFFFFFFFFFFFFFFULL, "Full"}}) {
+
1366 for (unsigned byte = 0; byte < 8; ++byte)
+
1367 frame.data[byte] = static_cast<std::uint8_t>(test.bits >> (byte * 8));
+
1368 const DecodedSignalValue decoded = DbcDecoder().Decode(runtime, frame).signals[0];
+
1369 CHECK(decoded.GetValueDescription() && *decoded.GetValueDescription() == test.description,
+
1370 "Unsigned boundaries");
+
1371 }
+
1372 CHECK(FindValueDescription(runtime.GetFrames()[0].signals[0].valueDescriptions, std::int64_t(1)) != nullptr,
+
1373 "Nonnegative signed lookup matches unsigned key");
+
1374 frame.data.clear();
+
1375 CHECK(!DbcDecoder().Decode(runtime, frame).signals[0].GetValueDescription(), "Invalid decode has no description");
+
1376}
1377
-
1378void DbcParserTest::extendedMultiplexing() {
+
1378void DbcParserTest::invalidValueDescriptions() {
1379 Fixture fixture;
-
1380 // Forward references, multiple ranges, repeated range statements, and nested siblings.
-
1381 fixture.Write(
-
1382 "SG_MUL_VAL_ 1 Child Root 1-2, 4-4;\n"
-
1383 "SG_MUL_VAL_ 1 Leaf Child 3-5;\n"
-
1384 "SG_MUL_VAL_ 1 Leaf Child 7-7;\n"
-
1385 "SG_MUL_VAL_ 1 Sibling Root 6-6;\n"
-
1386 "SG_MUL_VAL_ 1 Other Sibling 3-5;\n"
-
1387 "BO_ 1 Frame: 4 ECU\n"
-
1388 "SG_ Leaf m99 : 16|8@1- (2,1) [-128|127] \"\" ECU\n"
-
1389 "SG_ Other m0 : 23|8@0+ (1,0) [0|255] \"\" ECU\n"
-
1390 "SG_ Child m1M : 8|8@1+ (10,3) [0|255] \"\" ECU\n"
-
1391 "SG_ Sibling m6M : 15|8@0+ (1,0) [0|255] \"\" ECU\n"
-
1392 "SG_ Root M : 0|8@1+ (10,3) [0|255] \"\" ECU\n"
-
1393 "SG_ Common : 24|8@1+ (1,0) [0|255] \"\" ECU\n");
-
1394 const DbcDatabase parsed = DbcParser().ParseFile(fixture.path.string());
-
1395 const DecodeDatabase database = DbcDecodeBuilder().Build(parsed);
-
1396 const std::unique_ptr<TreeNode> tree = DbcTreeBuilder().Build(parsed);
-
1397 const Multiplexing &metadata = tree->GetChild(0U)->GetChild(0U)->GetSignal()->multiplexing;
-
1398 CHECK(metadata.selector == "Child" && metadata.ranges.size() == 2U &&
-
1399 metadata.ranges[1].minimum == 7U,
-
1400 "Tree preserves ranges and selector name");
-
1401 CHECK(database.GetFrames()[0].signals[2].multiplexing.role == MultiplexRole::BranchSelector,
-
1402 "Runtime preserves nested selector role");
-
1403 RawCanFrame raw;
-
1404 raw.canId = 1U;
-
1405 raw.data = {2U, 4U, 255U, 8U};
-
1406 DecodedFrameValue result = DbcDecoder().Decode(database, raw);
-
1407 CHECK(result.signals[0].valid && result.signals[0].physicalValue == -1.0 &&
-
1408 result.signals[1].status == SignalDecodeStatus::Inactive && result.signals[2].valid &&
-
1409 result.signals[3].status == SignalDecodeStatus::Inactive && result.signals[5].valid,
-
1410 "Nested raw conditions select one path and preserve definition order");
-
1411 const std::vector<SignalKey> selection = {SignalKey(FrameKey(1U, false), "Leaf")};
-
1412 result = DbcDecoder().Decode(database, raw, selection);
-
1413 CHECK(result.signals.size() == 1U && result.signals[0].valid,
-
1414 "Selection reads all ancestors internally");
-
1415 for (std::uint8_t root: {1U, 2U, 4U}) {
-
1416 for (std::uint8_t child: {3U, 5U, 7U}) {
-
1417 raw.data = {root, child, 9U, 8U};
-
1418 CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].valid,
-
1419 "Inclusive endpoints and separated ranges");
-
1420 }
-
1421 }
-
1422 raw.data = {6U, 4U, 9U, 8U};
-
1423 result = DbcDecoder().Decode(database, raw);
-
1424 CHECK(result.signals[0].status == SignalDecodeStatus::Inactive && result.signals[1].valid &&
-
1425 result.signals[3].valid,
-
1426 "Overlapping sibling selector path becomes active");
-
1427 raw.data = {0U};
-
1428 CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::Inactive,
-
1429 "Inactive root does not require child selector bytes");
-
1430 raw.data = {1U};
-
1431 CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::SelectorUnavailable,
-
1432 "Active root with missing child selector");
-
1433 raw.data = {1U, 3U};
-
1434 CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::InsufficientPayload,
-
1435 "Active nested leaf needs its bytes");
-
1436 raw.data = {1U, 6U};
-
1437 CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::Inactive,
-
1438 "Gap between explicit ranges does not fall back to mN");
-
1439 raw.data.clear();
-
1440 CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::SelectorUnavailable,
-
1441 "Missing root selector");
-
1442
-
1443 fixture.Write("BO_ 1 Frame: 9 ECU\n"
-
1444 "SG_ Root M : 0|64@1+ (1,0) [0|1] \"\" ECU\n"
-
1445 "SG_ Leaf m0 : 64|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1446 "SG_MUL_VAL_ 1 Leaf Root 18446744073709551614-18446744073709551615;\n");
-
1447 raw.data.assign(9U, 255U);
-
1448 CHECK(DbcDecoder().Decode(DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string())), raw).signals[1].valid,
-
1449 "64-bit ranges retain exact UINT64_MAX without expansion");
-
1450
-
1451 fixture.Write("BO_ 1 Frame: 4 ECU\n"
-
1452 "SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1453 "SG_ Second M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1454 "SG_ A m0 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1455 "SG_ B m0 : 24|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1456 "SG_MUL_VAL_ 1 A Root 1-1;\nSG_MUL_VAL_ 1 B Second 2-2;\n");
-
1457 raw.data = {1U, 2U, 9U, 10U};
-
1458 result = DbcDecoder().Decode(DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string())), raw);
-
1459 CHECK(result.signals[2].valid && result.signals[3].valid, "Independent explicit selectors");
-
1460
-
1461 fixture.Write("BO_ 1 Deep: 5 ECU\n"
-
1462 "SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1463 "SG_ Child m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1464 "SG_ Grandchild m2M : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1465 "SG_ Leaf : 24|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1466 "SG_MUL_VAL_ 1 Grandchild Child 2-2;\n"
-
1467 "SG_MUL_VAL_ 1 Leaf Grandchild 3-3;\n");
-
1468 const DecodeDatabase deep = DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string()));
-
1469 raw.data = {1U, 2U, 3U, 9U, 0U};
-
1470 CHECK(DbcDecoder().Decode(deep, raw).signals[3].valid, "Three selector levels and an unmarked extended branch");
-
1471 raw.data = {1U, 0U};
-
1472 CHECK(DbcDecoder().Decode(deep, raw).signals[3].status == SignalDecodeStatus::Inactive,
-
1473 "Inactive middle ancestor skips missing descendant bytes");
-
1474
-
1475 // Marker fallback also works on a nested selector without its own SG_MUL_VAL_.
-
1476 fixture.Write("BO_ 1 Frame: 3 ECU\n"
-
1477 "SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1478 "SG_ Child m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1479 "SG_ Leaf m2 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1480 "SG_MUL_VAL_ 1 Leaf Child 2-2;\n");
-
1481 raw.data = {1U, 2U, 9U};
-
1482 CHECK(DbcDecoder().Decode(DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string())), raw).signals[2].valid,
-
1483 "mNM retains implicit root fallback");
-
1484}
-
1485
-
1486void DbcParserTest::extendedMultiplexingErrors() {
-
1487 Fixture fixture;
-
1488 const std::string definitions =
-
1489 "BO_ 1 Frame: 3 ECU\n"
-
1490 "SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1491 "SG_ Child m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1492 "SG_ Leaf m2 : 16|8@1+ (1,0) [0|255] \"\" ECU\n";
-
1493 for (const std::string statement: {
-
1494 "SG_MUL_VAL_ 1 Leaf Root 3-2;", "SG_MUL_VAL_ 1 Leaf Root -1-2;",
-
1495 "SG_MUL_VAL_ 1 Leaf Root 1-256;", "SG_MUL_VAL_ 1 Leaf Root 1-2,;",
-
1496 "SG_MUL_VAL_ 1 Leaf Root 1-2 trailing;", "SG_MUL_VAL_ 1 Leaf Root 1-2",
-
1497 "SG_MUL_VAL_ 1 Leaf Root 0-18446744073709551616;",
-
1498 "SG_MUL_VAL_ 1 Leaf Missing 1-2;", "SG_MUL_VAL_ 1 Missing Root 1-2;",
-
1499 "SG_MUL_VAL_ 2 Leaf Root 1-2;", "SG_MUL_VAL_ 1 Leaf Leaf 1-2;"}) {
-
1500 fixture.Write(statement + "\n" + definitions);
-
1501 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
1502 CHECK(result.HasErrors() && result.database.frames.empty(), statement);
-
1503 CHECK(result.diagnostics.back().code == DbcDiagnosticCode::InvalidMultiplexing &&
-
1504 result.diagnostics.back().lineNumber == 1U,
-
1505 "Extended error retains source statement location");
-
1506 }
-
1507 for (const std::string statements: {
-
1508 "SG_MUL_VAL_ 1 Root Child 1-1;\nSG_MUL_VAL_ 1 Child Root 1-1;\n",
-
1509 "SG_MUL_VAL_ 1 Child Child 1-1;\n",
-
1510 "SG_MUL_VAL_ 1 Leaf Root 1-1;\nSG_MUL_VAL_ 1 Leaf Child 1-1;\n"}) {
-
1511 fixture.Write(definitions + statements);
-
1512 CHECK(DbcParser().ParseFileWithDiagnostics(fixture.path.string()).HasErrors(), "Cyclic or conflicting parents rejected");
-
1513 }
-
1514 // Range intersection and inherited root conditions must both protect overlaps.
-
1515 const std::string overlapping =
-
1516 "BO_ 1 Frame: 4 ECU\n"
-
1517 "SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1518 "SG_ Child m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1519 "SG_ A m0 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1520 "SG_ B m0 : 23|8@0+ (1,0) [0|255] \"\" ECU\n"
-
1521 "SG_MUL_VAL_ 1 A Child 2-4;\n"
-
1522 "SG_MUL_VAL_ 1 Child Root 1-1;\n";
-
1523 for (const std::string condition: {"SG_MUL_VAL_ 1 B Child 4-6;\n",
-
1524 "SG_MUL_VAL_ 1 B Root 1-1;\n"}) {
-
1525 fixture.Write(overlapping + condition);
-
1526 CHECK(DbcParser().ParseFileWithDiagnostics(fixture.path.string()).HasErrors(),
-
1527 "Overlapping ranges or compatible ancestors cannot share bits");
-
1528 }
-
1529 fixture.Write(overlapping + "SG_MUL_VAL_ 1 B Child 5-6;\n");
-
1530 CHECK(!DbcParser().ParseFileWithDiagnostics(fixture.path.string()).HasErrors(),
-
1531 "Disjoint adjacent ranges may share bits");
-
1532 fixture.Write(overlapping + "SG_ Second M : 24|8@1+ (1,0) [0|255] \"\" ECU\n"
-
1533 "SG_MUL_VAL_ 1 B Second 2-4;\n");
-
1534 const DbcParseResult independent = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
-
1535 CHECK(independent.HasErrors() && independent.diagnostics.back().message ==
-
1536 "Signals overlap without mutually exclusive multiplexing conditions.",
-
1537 "Independent selector conditions do not imply exclusion");
-
1538
-
1539 // Validate manually constructed runtime graphs with the same rules.
-
1540 fixture.Write(definitions + "SG_MUL_VAL_ 1 Leaf Child 2-3;\n");
-
1541 const DecodeDatabase valid = DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string()));
-
1542 for (unsigned kind = 0U; kind < 6U; ++kind) {
-
1543 std::vector<DecodeFrame> frames = valid.GetFrames();
-
1544 Multiplexing &leaf = frames[0].signals[2].multiplexing;
-
1545 switch (kind) {
-
1546 case 0:
-
1547 leaf.selector = "Missing";
-
1548 break;
-
1549 case 1:
-
1550 leaf.ranges[0] = {4U, 3U};
-
1551 break;
-
1552 case 2:
-
1553 leaf.ranges[0] = {0U, 256U};
-
1554 break;
-
1555 case 3:
-
1556 leaf.ranges.clear();
-
1557 break;
-
1558 case 4:
-
1559 frames[0].signals[0].multiplexing.selector = "Child";
-
1560 frames[0].signals[0].multiplexing.ranges = {{1U, 1U}};
-
1561 break;
-
1562 case 5:
-
1563 frames[0].signals[1].valueType = ValueType::Signed;
-
1564 break;
-
1565 }
-
1566 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase(frames));
-
1567 }
-
1568}
-
1569
-
1570int RunParserTests(int argc, char **argv) {
-
1571 DbcParserTest tests;
-
1572 return QTest::qExec(&tests, argc, argv);
-
1573}
-
1574
-
1575#include "dbc_parser_test.moc"
+
1380 const std::string definitions = "BO_ 1 Frame: 8 ECU\nSG_ State : 0|64@1+ (1,0) [0|2e20] \"\" ECU\n";
+
1381 for (const std::string statement: {
+
1382 "VAL_", "VAL_ 1 State;", "VAL_ 1 State ;", "VAL_ 1 State 0 \"Off\"",
+
1383 "VAL_ 1 State 0 \"Off\"; junk", "VAL_ 1 State 0 Off;",
+
1384 "VAL_ 1 State 0 \"Unclosed;", "VAL_ 1 State 0 \"Off\"1 \"On\";",
+
1385 "VAL_ 1 State 0 \"Off\" 00 \"Duplicate\";",
+
1386 "VAL_ 1 State 0 \"Off\" -0 \"Duplicate\";",
+
1387 "VAL_ 1 State 0 \"Off\";\nVAL_ 1 State 0 \"Duplicate\";",
+
1388 "VAL_ 2 State 0 \"Missing frame\";", "VAL_ 1 Missing 0 \"Missing signal\";",
+
1389 "VAL_ -1 State 0 \"Bad ID\";", "VAL_ 536870912 State 0 \"Bad ID\";",
+
1390 "VAL_ 4294967296 State 0 \"Bad ID\";", "VAL_ Environment 0 \"Unsupported\";",
+
1391 "VAL_ 1 State 0 \"Bad\\q escape\";"}) {
+
1392 fixture.Write(definitions + statement + '\n');
+
1393 const int line = statement.find('\n') == std::string::npos ? 3 : 4;
+
1394 try {
+
1395 DbcParser().ParseFile(fixture.path.string());
+
1396 CHECK(false, statement);
+
1397 } catch (const std::runtime_error &error) {
+
1398 CHECK(std::string(error.what()).find(fixture.path.string() + ":" + std::to_string(line) + ": ") == 0,
+
1399 statement + " " + error.what());
+
1400 }
+
1401 }
+
1402 for (const std::string suffix: {
+
1403 "BO_ 1 Other: 8 ECU\n", // Duplicate frame definition.
+
1404 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n",// Duplicate signal definition.
+
1405 ""}) {
+
1406 fixture.Write("VAL_ 1 " + std::string(suffix.empty() ? "Missing" : "State") +
+
1407 " 0 \"Description\";\n" + definitions + suffix);
+
1408 try {
+
1409 DbcParser().ParseFile(fixture.path.string());
+
1410 CHECK(false, "Missing reference or duplicate definition");
+
1411 } catch (const std::runtime_error &error) {
+
1412 const std::string line = suffix.empty() ? "1" : "4";
+
1413 CHECK(std::string(error.what()).find(fixture.path.string() + ":" + line + ": ") == 0,
+
1414 "Error retains the reference or duplicate definition source location");
+
1415 }
+
1416 }
+
1417 for (const std::string number: {"+1", "1x", "0x10", "1.5", "1e2", "--1",
+
1418 "-9223372036854775809", "18446744073709551616"}) {
+
1419 fixture.Write("VAL_ 1 State " + number + " \"Bad\";\n" + definitions);
+
1420 try {
+
1421 DbcParser().ParseFile(fixture.path.string());
+
1422 CHECK(false, number);
+
1423 } catch (const std::runtime_error &error) {
+
1424 CHECK(std::string(error.what()).find(fixture.path.string() + ":1: ") == 0, number);
+
1425 }
+
1426 }
+
1427}
+
1428
+
1429
+
1430void DbcParserTest::extendedMultiplexing() {
+
1431 Fixture fixture;
+
1432 // Forward references, multiple ranges, repeated range statements, and nested siblings.
+
1433 fixture.Write(
+
1434 "SG_MUL_VAL_ 1 Child Root 1-2, 4-4;\n"
+
1435 "SG_MUL_VAL_ 1 Leaf Child 3-5;\n"
+
1436 "SG_MUL_VAL_ 1 Leaf Child 7-7;\n"
+
1437 "SG_MUL_VAL_ 1 Sibling Root 6-6;\n"
+
1438 "SG_MUL_VAL_ 1 Other Sibling 3-5;\n"
+
1439 "BO_ 1 Frame: 4 ECU\n"
+
1440 "SG_ Leaf m99 : 16|8@1- (2,1) [-128|127] \"\" ECU\n"
+
1441 "SG_ Other m0 : 23|8@0+ (1,0) [0|255] \"\" ECU\n"
+
1442 "SG_ Child m1M : 8|8@1+ (10,3) [0|255] \"\" ECU\n"
+
1443 "SG_ Sibling m6M : 15|8@0+ (1,0) [0|255] \"\" ECU\n"
+
1444 "SG_ Root M : 0|8@1+ (10,3) [0|255] \"\" ECU\n"
+
1445 "SG_ Common : 24|8@1+ (1,0) [0|255] \"\" ECU\n");
+
1446 const DbcDatabase parsed = DbcParser().ParseFile(fixture.path.string());
+
1447 const DecodeDatabase database = DbcDecodeBuilder().Build(parsed);
+
1448 const std::unique_ptr<TreeNode> tree = DbcTreeBuilder().Build(parsed);
+
1449 const Multiplexing &metadata = tree->GetChild(0U)->GetChild(0U)->GetSignal()->multiplexing;
+
1450 CHECK(metadata.selector == "Child" && metadata.ranges.size() == 2U &&
+
1451 metadata.ranges[1].minimum == 7U,
+
1452 "Tree preserves ranges and selector name");
+
1453 CHECK(database.GetFrames()[0].signals[2].multiplexing.role == MultiplexRole::BranchSelector,
+
1454 "Runtime preserves nested selector role");
+
1455 RawCanFrame raw;
+
1456 raw.canId = 1U;
+
1457 raw.data = {2U, 4U, 255U, 8U};
+
1458 DecodedFrameValue result = DbcDecoder().Decode(database, raw);
+
1459 CHECK(result.signals[0].valid && result.signals[0].physicalValue == -1.0 &&
+
1460 result.signals[1].status == SignalDecodeStatus::Inactive && result.signals[2].valid &&
+
1461 result.signals[3].status == SignalDecodeStatus::Inactive && result.signals[5].valid,
+
1462 "Nested raw conditions select one path and preserve definition order");
+
1463 const std::vector<SignalKey> selection = {SignalKey(FrameKey(1U, false), "Leaf")};
+
1464 result = DbcDecoder().Decode(database, raw, selection);
+
1465 CHECK(result.signals.size() == 1U && result.signals[0].valid,
+
1466 "Selection reads all ancestors internally");
+
1467 for (std::uint8_t root: {1U, 2U, 4U}) {
+
1468 for (std::uint8_t child: {3U, 5U, 7U}) {
+
1469 raw.data = {root, child, 9U, 8U};
+
1470 CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].valid,
+
1471 "Inclusive endpoints and separated ranges");
+
1472 }
+
1473 }
+
1474 raw.data = {6U, 4U, 9U, 8U};
+
1475 result = DbcDecoder().Decode(database, raw);
+
1476 CHECK(result.signals[0].status == SignalDecodeStatus::Inactive && result.signals[1].valid &&
+
1477 result.signals[3].valid,
+
1478 "Overlapping sibling selector path becomes active");
+
1479 raw.data = {0U};
+
1480 CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::Inactive,
+
1481 "Inactive root does not require child selector bytes");
+
1482 raw.data = {1U};
+
1483 CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::SelectorUnavailable,
+
1484 "Active root with missing child selector");
+
1485 raw.data = {1U, 3U};
+
1486 CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::InsufficientPayload,
+
1487 "Active nested leaf needs its bytes");
+
1488 raw.data = {1U, 6U};
+
1489 CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::Inactive,
+
1490 "Gap between explicit ranges does not fall back to mN");
+
1491 raw.data.clear();
+
1492 CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::SelectorUnavailable,
+
1493 "Missing root selector");
+
1494
+
1495 fixture.Write("BO_ 1 Frame: 9 ECU\n"
+
1496 "SG_ Root M : 0|64@1+ (1,0) [0|1] \"\" ECU\n"
+
1497 "SG_ Leaf m0 : 64|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1498 "SG_MUL_VAL_ 1 Leaf Root 18446744073709551614-18446744073709551615;\n");
+
1499 raw.data.assign(9U, 255U);
+
1500 CHECK(DbcDecoder().Decode(DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string())), raw).signals[1].valid,
+
1501 "64-bit ranges retain exact UINT64_MAX without expansion");
+
1502
+
1503 fixture.Write("BO_ 1 Frame: 4 ECU\n"
+
1504 "SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1505 "SG_ Second M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1506 "SG_ A m0 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1507 "SG_ B m0 : 24|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1508 "SG_MUL_VAL_ 1 A Root 1-1;\nSG_MUL_VAL_ 1 B Second 2-2;\n");
+
1509 raw.data = {1U, 2U, 9U, 10U};
+
1510 result = DbcDecoder().Decode(DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string())), raw);
+
1511 CHECK(result.signals[2].valid && result.signals[3].valid, "Independent explicit selectors");
+
1512
+
1513 fixture.Write("BO_ 1 Deep: 5 ECU\n"
+
1514 "SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1515 "SG_ Child m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1516 "SG_ Grandchild m2M : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1517 "SG_ Leaf : 24|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1518 "SG_MUL_VAL_ 1 Grandchild Child 2-2;\n"
+
1519 "SG_MUL_VAL_ 1 Leaf Grandchild 3-3;\n");
+
1520 const DecodeDatabase deep = DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string()));
+
1521 raw.data = {1U, 2U, 3U, 9U, 0U};
+
1522 CHECK(DbcDecoder().Decode(deep, raw).signals[3].valid, "Three selector levels and an unmarked extended branch");
+
1523 raw.data = {1U, 0U};
+
1524 CHECK(DbcDecoder().Decode(deep, raw).signals[3].status == SignalDecodeStatus::Inactive,
+
1525 "Inactive middle ancestor skips missing descendant bytes");
+
1526
+
1527 // Marker fallback also works on a nested selector without its own SG_MUL_VAL_.
+
1528 fixture.Write("BO_ 1 Frame: 3 ECU\n"
+
1529 "SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1530 "SG_ Child m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1531 "SG_ Leaf m2 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1532 "SG_MUL_VAL_ 1 Leaf Child 2-2;\n");
+
1533 raw.data = {1U, 2U, 9U};
+
1534 CHECK(DbcDecoder().Decode(DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string())), raw).signals[2].valid,
+
1535 "mNM retains implicit root fallback");
+
1536}
+
1537
+
1538void DbcParserTest::extendedMultiplexingErrors() {
+
1539 Fixture fixture;
+
1540 const std::string definitions =
+
1541 "BO_ 1 Frame: 3 ECU\n"
+
1542 "SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1543 "SG_ Child m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1544 "SG_ Leaf m2 : 16|8@1+ (1,0) [0|255] \"\" ECU\n";
+
1545 for (const std::string statement: {
+
1546 "SG_MUL_VAL_ 1 Leaf Root 3-2;", "SG_MUL_VAL_ 1 Leaf Root -1-2;",
+
1547 "SG_MUL_VAL_ 1 Leaf Root 1-256;", "SG_MUL_VAL_ 1 Leaf Root 1-2,;",
+
1548 "SG_MUL_VAL_ 1 Leaf Root 1-2 trailing;", "SG_MUL_VAL_ 1 Leaf Root 1-2",
+
1549 "SG_MUL_VAL_ 1 Leaf Root 0-18446744073709551616;",
+
1550 "SG_MUL_VAL_ 1 Leaf Missing 1-2;", "SG_MUL_VAL_ 1 Missing Root 1-2;",
+
1551 "SG_MUL_VAL_ 2 Leaf Root 1-2;", "SG_MUL_VAL_ 1 Leaf Leaf 1-2;"}) {
+
1552 fixture.Write(statement + "\n" + definitions);
+
1553 const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
1554 CHECK(result.HasErrors() && result.database.frames.empty(), statement);
+
1555 CHECK(result.diagnostics.back().code == DbcDiagnosticCode::InvalidMultiplexing &&
+
1556 result.diagnostics.back().lineNumber == 1U,
+
1557 "Extended error retains source statement location");
+
1558 }
+
1559 for (const std::string statements: {
+
1560 "SG_MUL_VAL_ 1 Root Child 1-1;\nSG_MUL_VAL_ 1 Child Root 1-1;\n",
+
1561 "SG_MUL_VAL_ 1 Child Child 1-1;\n",
+
1562 "SG_MUL_VAL_ 1 Leaf Root 1-1;\nSG_MUL_VAL_ 1 Leaf Child 1-1;\n"}) {
+
1563 fixture.Write(definitions + statements);
+
1564 CHECK(DbcParser().ParseFileWithDiagnostics(fixture.path.string()).HasErrors(), "Cyclic or conflicting parents rejected");
+
1565 }
+
1566 // Range intersection and inherited root conditions must both protect overlaps.
+
1567 const std::string overlapping =
+
1568 "BO_ 1 Frame: 4 ECU\n"
+
1569 "SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1570 "SG_ Child m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1571 "SG_ A m0 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1572 "SG_ B m0 : 23|8@0+ (1,0) [0|255] \"\" ECU\n"
+
1573 "SG_MUL_VAL_ 1 A Child 2-4;\n"
+
1574 "SG_MUL_VAL_ 1 Child Root 1-1;\n";
+
1575 for (const std::string condition: {"SG_MUL_VAL_ 1 B Child 4-6;\n",
+
1576 "SG_MUL_VAL_ 1 B Root 1-1;\n"}) {
+
1577 fixture.Write(overlapping + condition);
+
1578 CHECK(DbcParser().ParseFileWithDiagnostics(fixture.path.string()).HasErrors(),
+
1579 "Overlapping ranges or compatible ancestors cannot share bits");
+
1580 }
+
1581 fixture.Write(overlapping + "SG_MUL_VAL_ 1 B Child 5-6;\n");
+
1582 CHECK(!DbcParser().ParseFileWithDiagnostics(fixture.path.string()).HasErrors(),
+
1583 "Disjoint adjacent ranges may share bits");
+
1584 fixture.Write(overlapping + "SG_ Second M : 24|8@1+ (1,0) [0|255] \"\" ECU\n"
+
1585 "SG_MUL_VAL_ 1 B Second 2-4;\n");
+
1586 const DbcParseResult independent = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
+
1587 CHECK(independent.HasErrors() && independent.diagnostics.back().message ==
+
1588 "Signals overlap without mutually exclusive multiplexing conditions.",
+
1589 "Independent selector conditions do not imply exclusion");
+
1590
+
1591 // Validate manually constructed runtime graphs with the same rules.
+
1592 fixture.Write(definitions + "SG_MUL_VAL_ 1 Leaf Child 2-3;\n");
+
1593 const DecodeDatabase valid = DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string()));
+
1594 for (unsigned kind = 0U; kind < 6U; ++kind) {
+
1595 std::vector<DecodeFrame> frames = valid.GetFrames();
+
1596 Multiplexing &leaf = frames[0].signals[2].multiplexing;
+
1597 switch (kind) {
+
1598 case 0:
+
1599 leaf.selector = "Missing";
+
1600 break;
+
1601 case 1:
+
1602 leaf.ranges[0] = {4U, 3U};
+
1603 break;
+
1604 case 2:
+
1605 leaf.ranges[0] = {0U, 256U};
+
1606 break;
+
1607 case 3:
+
1608 leaf.ranges.clear();
+
1609 break;
+
1610 case 4:
+
1611 frames[0].signals[0].multiplexing.selector = "Child";
+
1612 frames[0].signals[0].multiplexing.ranges = {{1U, 1U}};
+
1613 break;
+
1614 case 5:
+
1615 frames[0].signals[1].valueType = ValueType::Signed;
+
1616 break;
+
1617 }
+
1618 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase(frames));
+
1619 }
+
1620}
+
1621
+
1622int RunParserTests(int argc, char **argv) {
+
1623 DbcParserTest tests;
+
1624 return QTest::qExec(&tests, argc, argv);
+
1625}
+
1626
+
1627#include "dbc_parser_test.moc"
Converts parsed DBC data into runtime decode database.
DecodeDatabase Build(const DbcDatabase &source) const
Build runtime decode database.
Runtime CAN decoder using prebuilt decode database.
Definition dbc_decoder.h:87
@@ -1682,7 +1734,7 @@ $(document).ready(function(){initNavTree('dbc__parser__test_8cpp_source.html',''
DbcDatabase ParseFile(const std::string &filePath) const
Parse DBC file.
void RegisterType(const std::string &keyword)
Register or replace a handler and create its reusable instance.
-
bool Dispatch(const std::string &line, DbcParseContext &context) const
+
bool Dispatch(const std::string &line, DbcParseContext &context) const
Interface for processing a registered DBC statement.
virtual void Handle(const std::string &line, DbcParseContext &context) const =0
Process a trimmed statement using state belonging to this parse.
Builds a simple tree from parsed DBC database.
diff --git a/docs/html/dbc__statement__handler_8cpp_source.html b/docs/html/dbc__statement__handler_8cpp_source.html index 2855aa5..4cf2b4b 100644 --- a/docs/html/dbc__statement__handler_8cpp_source.html +++ b/docs/html/dbc__statement__handler_8cpp_source.html @@ -133,569 +133,599 @@ $(document).ready(function(){initNavTree('dbc__statement__handler_8cpp_source.ht
33 }
34
35 double ParsePhysicalValue(const std::string &text, const char *field) {
-
36 static const std::regex number(
-
37 R"(^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$)");
-
38 if (!std::regex_match(text, number))
-
39 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
-
40 // Floating-point from_chars is locale-independent and does not accept
-
41 // a leading '+', which the DBC numeric grammar permits.
-
42 const char *begin = text.data();
-
43 const char *end = begin + text.size();
-
44 if (*begin == '+')
-
45 ++begin;
-
46 double value = 0.0;
-
47 const std::from_chars_result parsed =
-
48 std::from_chars(begin, end, value, std::chars_format::general);
-
49 // Keep the existing rejection of conversion underflow into subnormal values.
-
50 if (parsed.ec != std::errc() || parsed.ptr != end || !std::isfinite(value) ||
-
51 std::fpclassify(value) == FP_SUBNORMAL)
-
52 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
-
53 return value;
-
54 }
-
55
-
56 Multiplexing ParseMultiplexingMarker(const std::string &marker) {
-
57 Multiplexing result;
-
58 if (marker == "M")
-
59 result.role = MultiplexRole::Selector;
-
60 else if (!marker.empty()) {
-
61 if (marker[0] != 'm')
-
62 throw std::runtime_error("Unsupported multiplexing marker.");
-
63 const bool nested = marker.back() == 'M';
-
64 const std::string value = marker.substr(1U, marker.size() - (nested ? 2U : 1U));
-
65 const std::from_chars_result parsed =
-
66 std::from_chars(value.data(), value.data() + value.size(), result.value);
-
67 if (value.empty() || parsed.ec != std::errc() || parsed.ptr != value.data() + value.size())
-
68 throw std::runtime_error("Invalid or unsupported multiplexing marker.");
-
69 result.role = nested ? MultiplexRole::BranchSelector : MultiplexRole::Branch;
-
70 }
-
71
-
72 return result;
-
73 }
-
74
-
75 RawSignalValue ParseValueKey(const std::string &number) {
-
76 RawSignalValue key;
-
77 if (!number.empty() && number[0] == '-') {
-
78 std::int64_t value = 0;
-
79 const std::from_chars_result parsed =
-
80 std::from_chars(number.data(), number.data() + number.size(), value);
-
81 if (parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
-
82 throw std::runtime_error("Invalid VAL_ integer: " + number);
-
83 key = NormalizeValueKey(RawSignalValue(value));
-
84 } else {
-
85 std::uint64_t value = 0;
-
86 const std::from_chars_result parsed =
-
87 std::from_chars(number.data(), number.data() + number.size(), value);
-
88 if (number.empty() || parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
-
89 throw std::runtime_error("Invalid VAL_ integer: " + number);
-
90 key = value;
-
91 }
-
92 return key;
-
93 }
-
94
-
95 std::string UnescapeDescription(const std::string &quoted) {
-
96 std::string description;
-
97 for (std::size_t i = 0; i < quoted.size(); ++i) {
-
98 if (quoted[i] == '\\')
-
99 ++i;
-
100 description += quoted[i];
-
101 }
-
102 return description;
-
103 }
-
104
-
105 FrameInfo *FindUniqueFrame(DbcDatabase &database, std::uint32_t canId, bool isExtended, const std::string &statement = "VAL_") {
-
106 FrameInfo *frame = nullptr;
-
107 for (FrameInfo &candidate : database.frames) {
-
108 if (candidate.canId == canId && candidate.isExtended == isExtended) {
-
109 if (frame)
-
110 throw std::runtime_error("Ambiguous " + statement + " frame reference.");
-
111 frame = &candidate;
-
112 }
-
113 }
-
114 if (!frame)
-
115 throw std::runtime_error("Unresolved " + statement + " frame reference.");
-
116 return frame;
-
117 }
-
118
-
119 SignalInfo *FindUniqueSignal(FrameInfo &frame, const std::string &signalName, const std::string &statement = "VAL_") {
-
120 SignalInfo *signal = nullptr;
-
121 for (SignalInfo &candidate : frame.signals) {
-
122 if (candidate.name == signalName) {
-
123 if (signal)
-
124 throw std::runtime_error("Ambiguous " + statement + " signal reference: " + signalName);
-
125 signal = &candidate;
-
126 }
-
127 }
-
128 if (!signal)
-
129 throw std::runtime_error("Unresolved " + statement + " signal reference: " + signalName);
-
130 return signal;
-
131 }
-
132
-
133 std::uint64_t NextSignalBit(std::uint64_t bit, bool isLittleEndian) {
-
134 if (isLittleEndian)
-
135 return bit + 1U;
-
136 // From bit 0, Motorola continues at bit 7 of the next byte (0 -> 15).
-
137 return bit % 8U == 0U ? bit + 15U : bit - 1U;
-
138 }
-
139
-
140 FrameInfo *FindFrameById(DbcDatabase &, std::uint32_t, bool);
-
141 SignalInfo *FindSignalByName(FrameInfo &, const std::string &);
-
142} // namespace
-
143
-
144namespace {
-
145 std::vector<std::string> SplitReceivers(const std::string &text) {
-
146 std::vector<std::string> receivers;
-
147 std::string token;
-
148 std::istringstream stream(text);
-
149
-
150 while (std::getline(stream, token, ',')) {
-
151 token = dbc_detail::TrimText(token);
-
152 if (!token.empty())
-
153 receivers.push_back(token);
-
154 }
+
36 if (text.empty() || (text[0] == '+' &&
+
37 (text.size() == 1U || text[1] == '+' || text[1] == '-')))
+
38 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
+
39 // Floating-point from_chars is locale-independent and does not accept
+
40 // a leading '+', which the DBC numeric grammar permits.
+
41 const char *begin = text.data();
+
42 const char *end = begin + text.size();
+
43 if (*begin == '+')
+
44 ++begin;
+
45 double value = 0.0;
+
46 const std::from_chars_result parsed =
+
47 std::from_chars(begin, end, value, std::chars_format::general);
+
48 // Keep the existing rejection of conversion underflow into subnormal values.
+
49 if (parsed.ec != std::errc() || parsed.ptr != end || !std::isfinite(value) ||
+
50 std::fpclassify(value) == FP_SUBNORMAL)
+
51 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
+
52 return value;
+
53 }
+
54
+
55 // Iterative scanning keeps stack use independent of the statement length.
+
56 class SignalTokens {
+
57 public:
+
58 SignalTokens(const std::string &text, std::size_t position) : text(text), position(position) {}
+
59
+
60 void SkipSpaces() {
+
61 while (position < text.size() && (text[position] == ' ' || text[position] == '\t'))
+
62 ++position;
+
63 }
+
64
+
65 char Character() {
+
66 if (position == text.size())
+
67 throw std::runtime_error("Incomplete SG_ signal definition.");
+
68 return text[position++];
+
69 }
+
70
+
71 void Expect(char expected) {
+
72 SkipSpaces();
+
73 if (Character() != expected)
+
74 throw std::runtime_error("Malformed SG_ signal definition.");
+
75 }
+
76
+
77 std::string Until(char delimiter) {
+
78 const std::size_t end = text.find(delimiter, position);
+
79 if (end == std::string::npos)
+
80 throw std::runtime_error("Missing delimiter in SG_ signal definition.");
+
81 const std::string value = text.substr(position, end - position);
+
82 position = end + 1U;
+
83 return value;
+
84 }
+
85
+
86 std::vector<std::string> Receivers() {
+
87 if (position == text.size() || (text[position] != ' ' && text[position] != '\t'))
+
88 throw std::runtime_error("Missing receiver separator in SG_ signal definition.");
+
89 std::vector<std::string> receivers;
+
90 for (;;) {
+
91 const std::size_t end = text.find(',', position);
+
92 const std::string receiver = dbc_detail::TrimText(text.substr(position, end - position));
+
93 if (receiver.empty() || receiver.find_first_of(" \t\r\n") != std::string::npos)
+
94 throw std::runtime_error("Invalid SG_ receiver list.");
+
95 receivers.push_back(receiver);
+
96 if (end == std::string::npos)
+
97 return receivers;
+
98 position = end + 1U;
+
99 }
+
100 }
+
101
+
102 private:
+
103 const std::string &text;
+
104 std::size_t position = 0U;
+
105 };
+
106
+
107 Multiplexing ParseMultiplexingMarker(const std::string &marker) {
+
108 Multiplexing result;
+
109 if (marker == "M")
+
110 result.role = MultiplexRole::Selector;
+
111 else if (!marker.empty()) {
+
112 if (marker[0] != 'm')
+
113 throw std::runtime_error("Unsupported multiplexing marker.");
+
114 const bool nested = marker.back() == 'M';
+
115 const std::string value = marker.substr(1U, marker.size() - (nested ? 2U : 1U));
+
116 const std::from_chars_result parsed =
+
117 std::from_chars(value.data(), value.data() + value.size(), result.value);
+
118 if (value.empty() || parsed.ec != std::errc() || parsed.ptr != value.data() + value.size())
+
119 throw std::runtime_error("Invalid or unsupported multiplexing marker.");
+
120 result.role = nested ? MultiplexRole::BranchSelector : MultiplexRole::Branch;
+
121 }
+
122
+
123 return result;
+
124 }
+
125
+
126 RawSignalValue ParseValueKey(const std::string &number) {
+
127 RawSignalValue key;
+
128 if (!number.empty() && number[0] == '-') {
+
129 std::int64_t value = 0;
+
130 const std::from_chars_result parsed =
+
131 std::from_chars(number.data(), number.data() + number.size(), value);
+
132 if (parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
+
133 throw std::runtime_error("Invalid VAL_ integer: " + number);
+
134 key = NormalizeValueKey(RawSignalValue(value));
+
135 } else {
+
136 std::uint64_t value = 0;
+
137 const std::from_chars_result parsed =
+
138 std::from_chars(number.data(), number.data() + number.size(), value);
+
139 if (number.empty() || parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
+
140 throw std::runtime_error("Invalid VAL_ integer: " + number);
+
141 key = value;
+
142 }
+
143 return key;
+
144 }
+
145
+
146 std::string UnescapeDescription(const std::string &quoted) {
+
147 std::string description;
+
148 for (std::size_t i = 0; i < quoted.size(); ++i) {
+
149 if (quoted[i] == '\\')
+
150 ++i;
+
151 description += quoted[i];
+
152 }
+
153 return description;
+
154 }
155
-
156 return receivers;
-
157 }
-
158
-
159 void NormalizeCanId(std::uint32_t rawCanId, std::uint32_t &normalizedCanId, bool &isExtended) {
-
160 /*
-
161 * DBC commonly stores extended identifiers with bit 31 set.
-
162 * Example:
-
163 * raw id = 0x80000000 | actual_29_bit_id
-
164 */
-
165 isExtended = (rawCanId & 0x80000000U) != 0U || rawCanId > 0x7FFU;
-
166 normalizedCanId = rawCanId & 0x1FFFFFFFU;
-
167 }
-
168} // namespace
+
156 FrameInfo *FindUniqueFrame(DbcDatabase &database, std::uint32_t canId, bool isExtended, const std::string &statement = "VAL_") {
+
157 FrameInfo *frame = nullptr;
+
158 for (FrameInfo &candidate : database.frames) {
+
159 if (candidate.canId == canId && candidate.isExtended == isExtended) {
+
160 if (frame)
+
161 throw std::runtime_error("Ambiguous " + statement + " frame reference.");
+
162 frame = &candidate;
+
163 }
+
164 }
+
165 if (!frame)
+
166 throw std::runtime_error("Unresolved " + statement + " frame reference.");
+
167 return frame;
+
168 }
169
-
170FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) {
-
171 /*
-
172 * Example:
-
173 * BO_ 256 EngineData: 8 EEC1
-
174 */
-
175
-
176 std::istringstream stream(line);
-
177 std::string keyword;
-
178 std::string idText;
-
179 if (!(stream >> keyword >> idText) || keyword != "BO_")
-
180 throw std::runtime_error("Missing CAN ID in frame definition.");
-
181
-
182 const std::uint32_t rawCanId = ParseCanId(idText);
+
170 SignalInfo *FindUniqueSignal(FrameInfo &frame, const std::string &signalName, const std::string &statement = "VAL_") {
+
171 SignalInfo *signal = nullptr;
+
172 for (SignalInfo &candidate : frame.signals) {
+
173 if (candidate.name == signalName) {
+
174 if (signal)
+
175 throw std::runtime_error("Ambiguous " + statement + " signal reference: " + signalName);
+
176 signal = &candidate;
+
177 }
+
178 }
+
179 if (!signal)
+
180 throw std::runtime_error("Unresolved " + statement + " signal reference: " + signalName);
+
181 return signal;
+
182 }
183
-
184 std::string nameText;
-
185 if (!std::getline(stream, nameText, ':') || stream.eof())
-
186 throw std::runtime_error("Missing ':' in frame definition.");
-
187 std::istringstream nameStream(nameText);
-
188 FrameInfo frame;
-
189 std::string extra;
-
190 if (!(nameStream >> frame.name) || (nameStream >> extra))
-
191 throw std::runtime_error("Missing or invalid frame name.");
-
192
-
193 std::string lengthText;
-
194 if (!(stream >> lengthText))
-
195 throw std::runtime_error("Missing payload length in frame definition.");
-
196 frame.dlc = static_cast<std::uint8_t>(ParseDecimal(
-
197 lengthText, std::numeric_limits<std::uint8_t>::max(), "payload length"));
-
198 if (!(stream >> frame.transmitter))
-
199 throw std::runtime_error("Missing transmitter in frame definition.");
-
200 if (stream >> extra)
-
201 throw std::runtime_error("Unexpected trailing field in frame definition: " + extra);
-
202
-
203 NormalizeCanId(rawCanId, frame.canId, frame.isExtended);
-
204
-
205 return frame;
-
206}
-
207
-
208SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) {
-
209 // Match the complete statement before converting any numeric fields.
-
210 enum Field {
-
211 NameAndMarker = 1, StartBit, BitLength, ByteOrder, Signedness,
-
212 Factor, Offset, Minimum, Maximum, Unit, Receivers
-
213 };
-
214 static const std::regex syntax(
-
215 // Name and optional multiplexing marker.
-
216 R"dbc(^SG_[ \t]+([^:]+):[ \t]*)dbc"
-
217 // Bit position, length, byte order and signedness.
-
218 R"dbc(([^| \t]+)[ \t]*\|[ \t]*([^@ \t]+)[ \t]*@[ \t]*([01])([+-])[ \t]*)dbc"
-
219 // Scaling factor and offset.
-
220 R"dbc(\‍([ \t]*([^, \t]+)[ \t]*,[ \t]*([^ )\t]+)[ \t]*\)[ \t]*)dbc"
-
221 // Physical range.
-
222 R"dbc(\[[ \t]*([^| \t]+)[ \t]*\|[ \t]*([^\] \t]+)[ \t]*\][ \t]*)dbc"
-
223 // Unit and receivers.
-
224 R"dbc("([^"\r\n]*)"[ \t]+([^ \t,]+(?:[ \t]*,[ \t]*[^ \t,]+)*)[ \t]*$)dbc");
-
225 std::smatch fields;
-
226 if (!std::regex_match(line, fields, syntax))
-
227 throw std::runtime_error("Malformed or unsupported SG_ signal definition.");
-
228
-
229 SignalInfo signal;
-
230 std::istringstream nameFields(fields[NameAndMarker].str());
-
231 std::string marker;
-
232 std::string extra;
-
233 nameFields >> signal.name >> marker;
-
234 if (signal.name.empty() || (nameFields >> extra))
-
235 throw std::runtime_error("Invalid signal name or multiplexing marker.");
-
236 signal.multiplexing = ParseMultiplexingMarker(marker);
-
237
-
238 signal.startBit = ParseDecimal(fields[StartBit].str(),
-
239 std::numeric_limits<std::uint32_t>::max(), "signal start bit");
-
240 signal.length = ParseDecimal(fields[BitLength].str(), 64U, "signal length");
-
241 if (signal.length == 0U)
-
242 throw std::runtime_error("Signal length must be within 1..64 bits.");
-
243 signal.isLittleEndian = fields[ByteOrder].str() == "1";
-
244 signal.isSigned = fields[Signedness].str() == "-";
-
245
-
246 signal.factor = ParsePhysicalValue(fields[Factor].str(), "signal factor");
-
247 signal.offset = ParsePhysicalValue(fields[Offset].str(), "signal offset");
-
248 signal.minimum = ParsePhysicalValue(fields[Minimum].str(), "signal minimum");
-
249 signal.maximum = ParsePhysicalValue(fields[Maximum].str(), "signal maximum");
-
250 if (signal.minimum > signal.maximum)
-
251 throw std::runtime_error("Signal minimum exceeds maximum.");
-
252 signal.unit = fields[Unit].str();
-
253 signal.receivers = SplitReceivers(fields[Receivers].str());
-
254 return signal;
-
255}
-
256
-
257
-
258namespace {
-
259 FrameInfo *FindFrameById(DbcDatabase &database,
-
260 std::uint32_t canId,
-
261 bool isExtended) {
-
262 for (FrameInfo &frame : database.frames) {
-
263 if (frame.canId == canId && frame.isExtended == isExtended)
-
264 return &frame;
-
265 }
-
266
-
267 return nullptr;
-
268 }
-
269
-
270 SignalInfo *FindSignalByName(FrameInfo &frame, const std::string &signalName) {
-
271 for (SignalInfo &signal : frame.signals) {
-
272 if (signal.name == signalName)
-
273 return &signal;
-
274 }
-
275
-
276 return nullptr;
-
277 }
-
278}// namespace
-
279
-
-
280void DbcFrameHandler::Handle(const std::string &line, DbcParseContext &context) const {
-
281 FrameInfo frame = ParseFrameLine(line);
-
282 if (context.enableJ1939 && frame.isExtended) {
-
283 frame.hasPgn = true;
-
284 frame.pgn = ExtractJ1939Pgn(frame.canId);
-
285 }
-
286 if (FindFrameById(context.database, frame.canId, frame.isExtended) != nullptr)
-
287 throw std::runtime_error("Duplicate " + std::string(frame.isExtended ? "extended" : "standard") +
-
288 " frame CAN ID: " + std::to_string(frame.canId));
-
289 context.database.frames.push_back(std::move(frame));
-
290 context.currentFrame = &context.database.frames.back();
-
291 context.signalLines.emplace_back();
-
292}
+
184 std::uint64_t NextSignalBit(std::uint64_t bit, bool isLittleEndian) {
+
185 if (isLittleEndian)
+
186 return bit + 1U;
+
187 // From bit 0, Motorola continues at bit 7 of the next byte (0 -> 15).
+
188 return bit % 8U == 0U ? bit + 15U : bit - 1U;
+
189 }
+
190
+
191 FrameInfo *FindFrameById(DbcDatabase &, std::uint32_t, bool);
+
192 SignalInfo *FindSignalByName(FrameInfo &, const std::string &);
+
193} // namespace
+
194
+
195namespace {
+
196 void NormalizeCanId(std::uint32_t rawCanId, std::uint32_t &normalizedCanId, bool &isExtended) {
+
197 /*
+
198 * DBC commonly stores extended identifiers with bit 31 set.
+
199 * Example:
+
200 * raw id = 0x80000000 | actual_29_bit_id
+
201 */
+
202 isExtended = (rawCanId & 0x80000000U) != 0U || rawCanId > 0x7FFU;
+
203 normalizedCanId = rawCanId & 0x1FFFFFFFU;
+
204 }
+
205} // namespace
+
206
+
207FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) {
+
208 /*
+
209 * Example:
+
210 * BO_ 256 EngineData: 8 EEC1
+
211 */
+
212
+
213 std::istringstream stream(line);
+
214 std::string keyword;
+
215 std::string idText;
+
216 if (!(stream >> keyword >> idText) || keyword != "BO_")
+
217 throw std::runtime_error("Missing CAN ID in frame definition.");
+
218
+
219 const std::uint32_t rawCanId = ParseCanId(idText);
+
220
+
221 std::string nameText;
+
222 if (!std::getline(stream, nameText, ':') || stream.eof())
+
223 throw std::runtime_error("Missing ':' in frame definition.");
+
224 std::istringstream nameStream(nameText);
+
225 FrameInfo frame;
+
226 std::string extra;
+
227 if (!(nameStream >> frame.name) || (nameStream >> extra))
+
228 throw std::runtime_error("Missing or invalid frame name.");
+
229
+
230 std::string lengthText;
+
231 if (!(stream >> lengthText))
+
232 throw std::runtime_error("Missing payload length in frame definition.");
+
233 frame.dlc = static_cast<std::uint8_t>(ParseDecimal(
+
234 lengthText, std::numeric_limits<std::uint8_t>::max(), "payload length"));
+
235 if (!(stream >> frame.transmitter))
+
236 throw std::runtime_error("Missing transmitter in frame definition.");
+
237 if (stream >> extra)
+
238 throw std::runtime_error("Unexpected trailing field in frame definition: " + extra);
+
239
+
240 NormalizeCanId(rawCanId, frame.canId, frame.isExtended);
+
241
+
242 return frame;
+
243}
+
244
+
245SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) {
+
246 if (line.size() < 4U || line.compare(0U, 3U, "SG_") != 0 ||
+
247 (line[3] != ' ' && line[3] != '\t'))
+
248 throw std::runtime_error("Malformed SG_ signal definition.");
+
249 SignalTokens tokens(line, 3U);
+
250 SignalInfo signal;
+
251 std::istringstream nameFields(tokens.Until(':'));
+
252 std::string marker;
+
253 std::string extra;
+
254 nameFields >> signal.name >> marker;
+
255 if (signal.name.empty() || (nameFields >> extra))
+
256 throw std::runtime_error("Invalid signal name or multiplexing marker.");
+
257 signal.multiplexing = ParseMultiplexingMarker(marker);
+
258
+
259 signal.startBit = ParseDecimal(dbc_detail::TrimText(tokens.Until('|')),
+
260 std::numeric_limits<std::uint32_t>::max(), "signal start bit");
+
261 signal.length = ParseDecimal(dbc_detail::TrimText(tokens.Until('@')), 64U, "signal length");
+
262 if (signal.length == 0U)
+
263 throw std::runtime_error("Signal length must be within 1..64 bits.");
+
264 tokens.SkipSpaces();
+
265 const char byteOrder = tokens.Character();
+
266 const char signedness = tokens.Character();
+
267 if ((byteOrder != '0' && byteOrder != '1') || (signedness != '+' && signedness != '-'))
+
268 throw std::runtime_error("Invalid SG_ byte order or signedness.");
+
269 signal.isLittleEndian = byteOrder == '1';
+
270 signal.isSigned = signedness == '-';
+
271
+
272 tokens.Expect('(');
+
273 signal.factor = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until(',')), "signal factor");
+
274 signal.offset = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until(')')), "signal offset");
+
275 tokens.Expect('[');
+
276 signal.minimum = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until('|')), "signal minimum");
+
277 signal.maximum = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until(']')), "signal maximum");
+
278 if (signal.minimum > signal.maximum)
+
279 throw std::runtime_error("Signal minimum exceeds maximum.");
+
280 tokens.Expect('"');
+
281 signal.unit = tokens.Until('"');
+
282 if (signal.unit.find_first_of("\r\n") != std::string::npos)
+
283 throw std::runtime_error("Invalid newline in SG_ unit.");
+
284 signal.receivers = tokens.Receivers();
+
285 return signal;
+
286}
+
287
+
288
+
289namespace {
+
290 FrameInfo *FindFrameById(DbcDatabase &database,
+
291 std::uint32_t canId,
+
292 bool isExtended) {
+
293 for (FrameInfo &frame : database.frames) {
+
294 if (frame.canId == canId && frame.isExtended == isExtended)
+
295 return &frame;
+
296 }
+
297
+
298 return nullptr;
+
299 }
+
300
+
301 SignalInfo *FindSignalByName(FrameInfo &frame, const std::string &signalName) {
+
302 for (SignalInfo &signal : frame.signals) {
+
303 if (signal.name == signalName)
+
304 return &signal;
+
305 }
+
306
+
307 return nullptr;
+
308 }
+
309}// namespace
+
310
+
+
311void DbcFrameHandler::Handle(const std::string &line, DbcParseContext &context) const {
+
312 FrameInfo frame = ParseFrameLine(line);
+
313 if (context.enableJ1939 && frame.isExtended) {
+
314 frame.hasPgn = true;
+
315 frame.pgn = ExtractJ1939Pgn(frame.canId);
+
316 }
+
317 if (FindFrameById(context.database, frame.canId, frame.isExtended) != nullptr)
+
318 throw std::runtime_error("Duplicate " + std::string(frame.isExtended ? "extended" : "standard") +
+
319 " frame CAN ID: " + std::to_string(frame.canId));
+
320 context.database.frames.push_back(std::move(frame));
+
321 context.currentFrame = &context.database.frames.back();
+
322 context.signalLines.emplace_back();
+
323}
-
293
-
-
294void DbcSignalHandler::Handle(const std::string &line, DbcParseContext &context) const {
-
295 if (context.currentFrame == nullptr)
-
296 throw std::runtime_error("Signal found before any frame definition.");
-
297 SignalInfo signal = ParseSignalLine(line);
-
298 if (FindSignalByName(*context.currentFrame, signal.name) != nullptr)
-
299 throw std::runtime_error("Duplicate signal name '" + signal.name +
-
300 "' in frame '" + context.currentFrame->name + "'.");
-
301 if (!IsValidSignalLayout(signal.startBit, signal.length, signal.isLittleEndian,
-
302 context.currentFrame->dlc))
-
303 throw std::runtime_error("Signal exceeds the frame payload length.");
-
304 context.currentFrame->signals.push_back(std::move(signal));
-
305 context.signalLines.back().push_back(context.lineNumber);
-
306}
+
324
+
+
325void DbcSignalHandler::Handle(const std::string &line, DbcParseContext &context) const {
+
326 if (context.currentFrame == nullptr)
+
327 throw std::runtime_error("Signal found before any frame definition.");
+
328 SignalInfo signal = ParseSignalLine(line);
+
329 if (FindSignalByName(*context.currentFrame, signal.name) != nullptr)
+
330 throw std::runtime_error("Duplicate signal name '" + signal.name +
+
331 "' in frame '" + context.currentFrame->name + "'.");
+
332 if (!IsValidSignalLayout(signal.startBit, signal.length, signal.isLittleEndian,
+
333 context.currentFrame->dlc))
+
334 throw std::runtime_error("Signal exceeds the frame payload length.");
+
335 context.currentFrame->signals.push_back(std::move(signal));
+
336 context.signalLines.back().push_back(context.lineNumber);
+
337}
-
307
-
-
308void DbcCommentHandler::Handle(const std::string &line, DbcParseContext &context) const {
-
309 std::istringstream stream(line);
-
310 std::string keyword;
-
311 std::string target;
-
312 stream >> keyword >> target;
-
313 if (keyword != "CM_" || (target != "BO_" && target != "SG_"))
-
314 return;
-
315
-
316 PendingComment pending;
-
317 pending.isSignal = target == "SG_";
-
318 pending.lineNumber = context.lineNumber;
-
319 std::string idText;
-
320 if (!(stream >> idText))
-
321 throw std::runtime_error("Missing CAN ID in CM_ statement.");
-
322 NormalizeCanId(ParseCanId(idText), pending.canId, pending.isExtended);
-
323 if (pending.isSignal && (!(stream >> pending.signalName) ||
-
324 pending.signalName.find_first_of("\";") != std::string::npos))
-
325 throw std::runtime_error("Missing or invalid signal name in CM_ statement.");
-
326
-
327 stream >> std::ws;
-
328 if (stream.get() != '"')
-
329 throw std::runtime_error("Missing opening quote in CM_ statement.");
-
330 bool closed = false;
-
331 char character;
-
332 while (stream.get(character)) {
-
333 if (character == '"') {
-
334 closed = true;
-
335 break;
-
336 }
-
337 if (character == '\\') {
-
338 if (!stream.get(character) || (character != '"' && character != '\\'))
-
339 throw std::runtime_error("Invalid escape in CM_ statement.");
-
340 }
-
341 pending.comment += character;
-
342 }
-
343 if (!closed)
-
344 throw std::runtime_error("Unterminated CM_ comment.");
-
345 stream >> std::ws;
-
346 if (stream.get() != ';')
-
347 throw std::runtime_error("Missing semicolon in CM_ statement.");
-
348 stream >> std::ws;
-
349 if (!stream.eof())
-
350 throw std::runtime_error("Unexpected trailing text in CM_ statement.");
-
351 context.pendingComments.push_back(std::move(pending));
-
352}
+
338
+
+
339void DbcCommentHandler::Handle(const std::string &line, DbcParseContext &context) const {
+
340 std::istringstream stream(line);
+
341 std::string keyword;
+
342 std::string target;
+
343 stream >> keyword >> target;
+
344 if (keyword != "CM_" || (target != "BO_" && target != "SG_"))
+
345 return;
+
346
+
347 PendingComment pending;
+
348 pending.isSignal = target == "SG_";
+
349 pending.lineNumber = context.lineNumber;
+
350 std::string idText;
+
351 if (!(stream >> idText))
+
352 throw std::runtime_error("Missing CAN ID in CM_ statement.");
+
353 NormalizeCanId(ParseCanId(idText), pending.canId, pending.isExtended);
+
354 if (pending.isSignal && (!(stream >> pending.signalName) ||
+
355 pending.signalName.find_first_of("\";") != std::string::npos))
+
356 throw std::runtime_error("Missing or invalid signal name in CM_ statement.");
+
357
+
358 stream >> std::ws;
+
359 if (stream.get() != '"')
+
360 throw std::runtime_error("Missing opening quote in CM_ statement.");
+
361 bool closed = false;
+
362 char character;
+
363 while (stream.get(character)) {
+
364 if (character == '"') {
+
365 closed = true;
+
366 break;
+
367 }
+
368 if (character == '\\') {
+
369 if (!stream.get(character) || (character != '"' && character != '\\'))
+
370 throw std::runtime_error("Invalid escape in CM_ statement.");
+
371 }
+
372 pending.comment += character;
+
373 }
+
374 if (!closed)
+
375 throw std::runtime_error("Unterminated CM_ comment.");
+
376 stream >> std::ws;
+
377 if (stream.get() != ';')
+
378 throw std::runtime_error("Missing semicolon in CM_ statement.");
+
379 stream >> std::ws;
+
380 if (!stream.eof())
+
381 throw std::runtime_error("Unexpected trailing text in CM_ statement.");
+
382 context.pendingComments.push_back(std::move(pending));
+
383}
-
353
-
-
354void DbcParseContext::ResolveComments(const std::string &filePath) {
-
355 for (const PendingComment &pending: pendingComments) {
-
356 FrameInfo *frame = FindFrameById(database, pending.canId, pending.isExtended);
-
357 if (!frame)
-
358 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidStatement, filePath,
-
359 pending.lineNumber, "Unresolved CM_ frame reference.");
-
360 if (pending.isSignal) {
-
361 SignalInfo *signal = FindSignalByName(*frame, pending.signalName);
-
362 if (!signal)
-
363 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidStatement, filePath,
-
364 pending.lineNumber, "Unresolved CM_ signal reference: " + pending.signalName);
-
365 signal->comment = pending.comment;
-
366 } else {
-
367 frame->comment = pending.comment;
-
368 }
-
369 }
-
370}
+
384
+
+
385void DbcParseContext::ResolveComments(const std::string &filePath) {
+
386 for (const PendingComment &pending: pendingComments) {
+
387 FrameInfo *frame = FindFrameById(database, pending.canId, pending.isExtended);
+
388 if (!frame)
+
389 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidStatement, filePath,
+
390 pending.lineNumber, "Unresolved CM_ frame reference.");
+
391 if (pending.isSignal) {
+
392 SignalInfo *signal = FindSignalByName(*frame, pending.signalName);
+
393 if (!signal)
+
394 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidStatement, filePath,
+
395 pending.lineNumber, "Unresolved CM_ signal reference: " + pending.signalName);
+
396 signal->comment = pending.comment;
+
397 } else {
+
398 frame->comment = pending.comment;
+
399 }
+
400 }
+
401}
-
371
-
- -
373 RegisterType<DbcHeaderHandler>("VERSION");
-
374 RegisterType<DbcHeaderHandler>("NS_");
-
375 RegisterType<DbcHeaderHandler>("NS_:");
-
376 RegisterType<DbcHeaderHandler>("BS_");
-
377 RegisterType<DbcHeaderHandler>("BS_:");
-
378 RegisterType<DbcHeaderHandler>("BU_");
-
379 RegisterType<DbcHeaderHandler>("BU_:");
-
380 RegisterType<DbcMultiplexingHandler>("SG_MUL_VAL_");
-
381 RegisterType<DbcFrameHandler>("BO_");
-
382 RegisterType<DbcSignalHandler>("SG_");
-
383 RegisterType<DbcCommentHandler>("CM_");
-
384 RegisterType<DbcValueDescriptionHandler>("VAL_");
-
385 RegisterType<DbcAttributeHandler>("BA_DEF_");
-
386 RegisterType<DbcAttributeHandler>("BA_DEF_DEF_");
-
387 RegisterType<DbcAttributeHandler>("BA_");
-
388}
+
402
+
+ +
404 RegisterType<DbcHeaderHandler>("VERSION");
+
405 RegisterType<DbcHeaderHandler>("NS_");
+
406 RegisterType<DbcHeaderHandler>("NS_:");
+
407 RegisterType<DbcHeaderHandler>("BS_");
+
408 RegisterType<DbcHeaderHandler>("BS_:");
+
409 RegisterType<DbcHeaderHandler>("BU_");
+
410 RegisterType<DbcHeaderHandler>("BU_:");
+
411 RegisterType<DbcMultiplexingHandler>("SG_MUL_VAL_");
+
412 RegisterType<DbcFrameHandler>("BO_");
+
413 RegisterType<DbcSignalHandler>("SG_");
+
414 RegisterType<DbcCommentHandler>("CM_");
+
415 RegisterType<DbcValueDescriptionHandler>("VAL_");
+
416 RegisterType<DbcAttributeHandler>("BA_DEF_");
+
417 RegisterType<DbcAttributeHandler>("BA_DEF_DEF_");
+
418 RegisterType<DbcAttributeHandler>("BA_");
+
419}
-
389
-
390void DbcHeaderHandler::Handle(const std::string &, DbcParseContext &) const {}
-
391
-
-
392bool DbcStatementDispatcher::Dispatch(const std::string &line,
-
393 DbcParseContext &context) const {
-
394 std::istringstream stream(line);
-
395 std::string keyword;
-
396 stream >> keyword;
-
397 const HandlerMap::const_iterator handler = m_handlers.find(keyword);
-
398 if (handler == m_handlers.end())
-
399 return false;
-
400 handler->second->Handle(line, context);
-
401 return true;
-
402}
+
420
+
421void DbcHeaderHandler::Handle(const std::string &, DbcParseContext &) const {}
+
422
+
+
423bool DbcStatementDispatcher::Dispatch(const std::string &line,
+
424 DbcParseContext &context) const {
+
425 std::istringstream stream(line);
+
426 std::string keyword;
+
427 stream >> keyword;
+
428 const HandlerMap::const_iterator handler = m_handlers.find(keyword);
+
429 if (handler == m_handlers.end())
+
430 return false;
+
431 handler->second->Handle(line, context);
+
432 return true;
+
433}
-
403
-
-
404void DbcValueDescriptionHandler::Handle(const std::string &line, DbcParseContext &context) const {
-
405 static const std::regex header(R"(^VAL_[ \t]+([^ \t]+)[ \t]+([^ \t;]+)[ \t]+(.*)$)");
-
406 static const std::regex pair(R"dbc(^([^ \t]+)[ \t]+"((?:[^"\\]|\\["\\])*)")dbc");
-
407 std::smatch fields;
-
408 if (!std::regex_match(line, fields, header))
-
409 throw std::runtime_error("Malformed or unsupported VAL_ statement.");
-
410 const std::uint32_t rawId = ParseDecimal(fields[1].str(), std::numeric_limits<std::uint32_t>::max(), "VAL_ CAN ID");
-
411 if ((rawId & 0x60000000U) != 0U)
-
412 throw std::runtime_error("VAL_ CAN ID exceeds the 29-bit identifier range.");
- -
414 NormalizeCanId(rawId, pending.canId, pending.isExtended);
-
415 pending.signalName = fields[2].str();
-
416 pending.lineNumber = context.lineNumber;
-
417 std::string rest = fields[3].str();
-
418 while (rest != ";") {
-
419 if (!std::regex_search(rest, fields, pair))
-
420 throw std::runtime_error("Malformed VAL_ value/description pair or missing semicolon.");
-
421 const std::string number = fields[1].str();
-
422 const RawSignalValue key = ParseValueKey(number);
-
423 const std::string description = UnescapeDescription(fields[2].str());
-
424 if (!pending.descriptions.emplace(key, description).second)
-
425 throw std::runtime_error("Duplicate VAL_ value key: " + number);
-
426 const std::size_t consumed = static_cast<std::size_t>(fields.length());
-
427 rest.erase(0, consumed);
-
428 if (!rest.empty() && rest[0] != ';' && rest[0] != ' ' && rest[0] != '\t')
-
429 throw std::runtime_error("Missing whitespace between VAL_ pairs.");
-
430 rest = dbc_detail::TrimText(rest);
-
431 }
-
432 if (pending.descriptions.empty())
-
433 throw std::runtime_error("VAL_ requires at least one value/description pair.");
-
434 context.pendingValues.push_back(std::move(pending));
-
435}
+
434
+
+
435void DbcValueDescriptionHandler::Handle(const std::string &line, DbcParseContext &context) const {
+
436 static const std::regex header(R"(^VAL_[ \t]+([^ \t]+)[ \t]+([^ \t;]+)[ \t]+(.*)$)");
+
437 static const std::regex pair(R"dbc(^([^ \t]+)[ \t]+"((?:[^"\\]|\\["\\])*)")dbc");
+
438 std::smatch fields;
+
439 if (!std::regex_match(line, fields, header))
+
440 throw std::runtime_error("Malformed or unsupported VAL_ statement.");
+
441 const std::uint32_t rawId = ParseDecimal(fields[1].str(), std::numeric_limits<std::uint32_t>::max(), "VAL_ CAN ID");
+
442 if ((rawId & 0x60000000U) != 0U)
+
443 throw std::runtime_error("VAL_ CAN ID exceeds the 29-bit identifier range.");
+ +
445 NormalizeCanId(rawId, pending.canId, pending.isExtended);
+
446 pending.signalName = fields[2].str();
+
447 pending.lineNumber = context.lineNumber;
+
448 std::string rest = fields[3].str();
+
449 while (rest != ";") {
+
450 if (!std::regex_search(rest, fields, pair))
+
451 throw std::runtime_error("Malformed VAL_ value/description pair or missing semicolon.");
+
452 const std::string number = fields[1].str();
+
453 const RawSignalValue key = ParseValueKey(number);
+
454 const std::string description = UnescapeDescription(fields[2].str());
+
455 if (!pending.descriptions.emplace(key, description).second)
+
456 throw std::runtime_error("Duplicate VAL_ value key: " + number);
+
457 const std::size_t consumed = static_cast<std::size_t>(fields.length());
+
458 rest.erase(0, consumed);
+
459 if (!rest.empty() && rest[0] != ';' && rest[0] != ' ' && rest[0] != '\t')
+
460 throw std::runtime_error("Missing whitespace between VAL_ pairs.");
+
461 rest = dbc_detail::TrimText(rest);
+
462 }
+
463 if (pending.descriptions.empty())
+
464 throw std::runtime_error("VAL_ requires at least one value/description pair.");
+
465 context.pendingValues.push_back(std::move(pending));
+
466}
-
436
-
-
437void DbcParseContext::ResolveValueDescriptions(const std::string &filePath) {
-
438 for (const PendingValueDescriptions &pending: pendingValues) {
-
439 try {
-
440 FrameInfo *frame = FindUniqueFrame(database, pending.canId, pending.isExtended);
-
441 SignalInfo *signal = FindUniqueSignal(*frame, pending.signalName);
-
442 for (const ValueDescriptions::value_type &entry: pending.descriptions) {
-
443 if (!signal->valueDescriptions.insert(entry).second)
-
444 throw std::runtime_error("Duplicate VAL_ value key for signal: " + pending.signalName);
-
445 }
-
446 } catch (const std::exception &error) {
-
447 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidValueDescription,
-
448 filePath, pending.lineNumber, error.what());
-
449 }
-
450 }
-
451}
+
467
+
+
468void DbcParseContext::ResolveValueDescriptions(const std::string &filePath) {
+
469 for (const PendingValueDescriptions &pending: pendingValues) {
+
470 try {
+
471 FrameInfo *frame = FindUniqueFrame(database, pending.canId, pending.isExtended);
+
472 SignalInfo *signal = FindUniqueSignal(*frame, pending.signalName);
+
473 for (const ValueDescriptions::value_type &entry: pending.descriptions) {
+
474 if (!signal->valueDescriptions.insert(entry).second)
+
475 throw std::runtime_error("Duplicate VAL_ value key for signal: " + pending.signalName);
+
476 }
+
477 } catch (const std::exception &error) {
+
478 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidValueDescription,
+
479 filePath, pending.lineNumber, error.what());
+
480 }
+
481 }
+
482}
-
452
-
-
453void DbcMultiplexingHandler::Handle(const std::string &line, DbcParseContext &context) const {
-
454 static const std::regex syntax(R"(^SG_MUL_VAL_[ \t]+([0-9]+)[ \t]+([^ \t;]+)[ \t]+([^ \t;]+)[ \t]+(.+);[ \t]*$)");
-
455 static const std::regex rangeSyntax(R"(^[ \t]*([0-9]+)[ \t]*-[ \t]*([0-9]+)[ \t]*$)");
-
456 std::smatch fields;
-
457 if (!std::regex_match(line, fields, syntax))
-
458 throw std::runtime_error("Malformed SG_MUL_VAL_ statement.");
-
459 PendingMultiplexing pending;
-
460 NormalizeCanId(ParseCanId(fields[1].str()), pending.canId, pending.isExtended);
-
461 pending.signalName = fields[2].str();
-
462 pending.selectorName = fields[3].str();
-
463 pending.lineNumber = context.lineNumber;
-
464 const std::string list = fields[4].str();
-
465 std::size_t begin = 0U;
-
466 for (;;) {
-
467 const std::size_t comma = list.find(',', begin);
-
468 const std::string item = list.substr(begin, comma == std::string::npos ? comma : comma - begin);
-
469 if (!std::regex_match(item, fields, rangeSyntax))
-
470 throw std::runtime_error("Malformed multiplex range.");
-
471 MultiplexRange range;
-
472 const std::string lower = fields[1].str();
-
473 const std::string upper = fields[2].str();
-
474 const std::from_chars_result low = std::from_chars(lower.data(), lower.data() + lower.size(), range.minimum);
-
475 const std::from_chars_result high = std::from_chars(upper.data(), upper.data() + upper.size(), range.maximum);
-
476 if (low.ec != std::errc() || high.ec != std::errc() || range.minimum > range.maximum)
-
477 throw std::runtime_error("Invalid multiplex range.");
-
478 pending.ranges.push_back(range);
-
479 if (comma == std::string::npos)
-
480 break;
-
481 begin = comma + 1U;
-
482 }
-
483 context.pendingMultiplexing.push_back(std::move(pending));
-
484}
+
483
+
+
484void DbcMultiplexingHandler::Handle(const std::string &line, DbcParseContext &context) const {
+
485 static const std::regex syntax(R"(^SG_MUL_VAL_[ \t]+([0-9]+)[ \t]+([^ \t;]+)[ \t]+([^ \t;]+)[ \t]+(.+);[ \t]*$)");
+
486 static const std::regex rangeSyntax(R"(^[ \t]*([0-9]+)[ \t]*-[ \t]*([0-9]+)[ \t]*$)");
+
487 std::smatch fields;
+
488 if (!std::regex_match(line, fields, syntax))
+
489 throw std::runtime_error("Malformed SG_MUL_VAL_ statement.");
+
490 PendingMultiplexing pending;
+
491 NormalizeCanId(ParseCanId(fields[1].str()), pending.canId, pending.isExtended);
+
492 pending.signalName = fields[2].str();
+
493 pending.selectorName = fields[3].str();
+
494 pending.lineNumber = context.lineNumber;
+
495 const std::string list = fields[4].str();
+
496 std::size_t begin = 0U;
+
497 for (;;) {
+
498 const std::size_t comma = list.find(',', begin);
+
499 const std::string item = list.substr(begin, comma == std::string::npos ? comma : comma - begin);
+
500 if (!std::regex_match(item, fields, rangeSyntax))
+
501 throw std::runtime_error("Malformed multiplex range.");
+
502 MultiplexRange range;
+
503 const std::string lower = fields[1].str();
+
504 const std::string upper = fields[2].str();
+
505 const std::from_chars_result low = std::from_chars(lower.data(), lower.data() + lower.size(), range.minimum);
+
506 const std::from_chars_result high = std::from_chars(upper.data(), upper.data() + upper.size(), range.maximum);
+
507 if (low.ec != std::errc() || high.ec != std::errc() || range.minimum > range.maximum)
+
508 throw std::runtime_error("Invalid multiplex range.");
+
509 pending.ranges.push_back(range);
+
510 if (comma == std::string::npos)
+
511 break;
+
512 begin = comma + 1U;
+
513 }
+
514 context.pendingMultiplexing.push_back(std::move(pending));
+
515}
-
485
-
486void DbcParseContext::ResolveMultiplexing(const std::string &filePath) {
-
487 for (const PendingMultiplexing &pending: pendingMultiplexing) {
-
488 try {
-
489 FrameInfo *frame = FindUniqueFrame(database, pending.canId, pending.isExtended, "SG_MUL_VAL_");
-
490 SignalInfo *signal = FindUniqueSignal(*frame, pending.signalName, "SG_MUL_VAL_");
-
491 SignalInfo *selector = FindUniqueSignal(*frame, pending.selectorName, "SG_MUL_VAL_");
-
492 if (!IsMultiplexSelector(selector->multiplexing.role) || selector->isSigned)
-
493 throw std::runtime_error("Referenced signal must be an unsigned multiplex selector.");
-
494 if (!signal->multiplexing.selector.empty() && signal->multiplexing.selector != pending.selectorName)
-
495 throw std::runtime_error("Multiple parent selectors for one signal are unsupported.");
-
496 for (const MultiplexRange &range: pending.ranges) {
-
497 if (selector->length < 64U && range.maximum >= (std::uint64_t(1U) << selector->length))
-
498 throw std::runtime_error("Branch value exceeds selector raw range.");
-
499 }
-
500 signal->multiplexing.selector = pending.selectorName;
-
501 signal->multiplexing.ranges.insert(signal->multiplexing.ranges.end(), pending.ranges.begin(), pending.ranges.end());
-
502 if (!IsMultiplexSelector(signal->multiplexing.role))
-
503 signal->multiplexing.role = MultiplexRole::Branch;
-
504 else
-
505 signal->multiplexing.role = MultiplexRole::BranchSelector;
-
506 } catch (const std::exception &error) {
-
507 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing, filePath, pending.lineNumber, error.what());
-
508 }
-
509 }
-
510}
-
511
-
512namespace {
-
513 std::vector<std::uint64_t> SignalBits(const SignalInfo &signal) {
-
514 std::vector<std::uint64_t> bits;
-
515 std::uint64_t bit = signal.startBit;
-
516 for (std::uint32_t index = 0U; index < signal.length; ++index) {
-
517 bits.push_back(bit);
-
518 bit = NextSignalBit(bit, signal.isLittleEndian);
-
519 }
-
520 return bits;
-
521 }
-
522
-
523 bool SignalsOverlap(const SignalInfo &first, const SignalInfo &second) {
-
524 const std::vector<std::uint64_t> firstBits = SignalBits(first);
-
525 const std::vector<std::uint64_t> secondBits = SignalBits(second);
-
526 for (const std::uint64_t firstBit : firstBits) {
-
527 for (const std::uint64_t secondBit : secondBits) {
-
528 if (firstBit == secondBit)
-
529 return true;
+
516
+
517void DbcParseContext::ResolveMultiplexing(const std::string &filePath) {
+
518 for (const PendingMultiplexing &pending: pendingMultiplexing) {
+
519 try {
+
520 FrameInfo *frame = FindUniqueFrame(database, pending.canId, pending.isExtended, "SG_MUL_VAL_");
+
521 SignalInfo *signal = FindUniqueSignal(*frame, pending.signalName, "SG_MUL_VAL_");
+
522 SignalInfo *selector = FindUniqueSignal(*frame, pending.selectorName, "SG_MUL_VAL_");
+
523 if (!IsMultiplexSelector(selector->multiplexing.role) || selector->isSigned)
+
524 throw std::runtime_error("Referenced signal must be an unsigned multiplex selector.");
+
525 if (!signal->multiplexing.selector.empty() && signal->multiplexing.selector != pending.selectorName)
+
526 throw std::runtime_error("Multiple parent selectors for one signal are unsupported.");
+
527 for (const MultiplexRange &range: pending.ranges) {
+
528 if (selector->length < 64U && range.maximum >= (std::uint64_t(1U) << selector->length))
+
529 throw std::runtime_error("Branch value exceeds selector raw range.");
530 }
-
531 }
-
532 return false;
-
533 }
-
534}
-
535
-
-
536void DbcParseContext::ValidateMultiplexing(const std::string &filePath) const {
-
537 for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) {
-
538 const FrameInfo &frame = database.frames[frameIndex];
-
539 try {
-
540 const dbc_detail::MultiplexGraph graph(frame.signals);
-
541 for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
-
542 const SignalInfo &signal = frame.signals[index];
-
543 if (IsMultiplexSelector(signal.multiplexing.role) && signal.isSigned)
-
544 throw dbc_detail::MultiplexError(index, "Multiplex selectors must be unsigned.");
-
545 for (std::size_t previous = 0U; previous < index; ++previous) {
-
546 if (!graph.AreMutuallyExclusive(index, previous) && SignalsOverlap(signal, frame.signals[previous]))
-
547 throw dbc_detail::MultiplexError(index, "Signals overlap without mutually exclusive multiplexing conditions.");
-
548 }
-
549 }
-
550 } catch (const dbc_detail::MultiplexError &error) {
-
551 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing, filePath,
-
552 signalLines[frameIndex][error.index], error.what());
-
553 }
-
554 }
-
555}
+
531 signal->multiplexing.selector = pending.selectorName;
+
532 signal->multiplexing.ranges.insert(signal->multiplexing.ranges.end(), pending.ranges.begin(), pending.ranges.end());
+
533 if (!IsMultiplexSelector(signal->multiplexing.role))
+
534 signal->multiplexing.role = MultiplexRole::Branch;
+
535 else
+
536 signal->multiplexing.role = MultiplexRole::BranchSelector;
+
537 } catch (const std::exception &error) {
+
538 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing, filePath, pending.lineNumber, error.what());
+
539 }
+
540 }
+
541}
+
542
+
543namespace {
+
544 std::vector<std::uint64_t> SignalBits(const SignalInfo &signal) {
+
545 std::vector<std::uint64_t> bits;
+
546 std::uint64_t bit = signal.startBit;
+
547 for (std::uint32_t index = 0U; index < signal.length; ++index) {
+
548 bits.push_back(bit);
+
549 bit = NextSignalBit(bit, signal.isLittleEndian);
+
550 }
+
551 return bits;
+
552 }
+
553
+
554 bool SignalsOverlap(const SignalInfo &first, const SignalInfo &second) {
+
555 const std::vector<std::uint64_t> firstBits = SignalBits(first);
+
556 const std::vector<std::uint64_t> secondBits = SignalBits(second);
+
557 for (const std::uint64_t firstBit : firstBits) {
+
558 for (const std::uint64_t secondBit : secondBits) {
+
559 if (firstBit == secondBit)
+
560 return true;
+
561 }
+
562 }
+
563 return false;
+
564 }
+
565}
+
566
+
+
567void DbcParseContext::ValidateMultiplexing(const std::string &filePath) const {
+
568 for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) {
+
569 const FrameInfo &frame = database.frames[frameIndex];
+
570 try {
+
571 const dbc_detail::MultiplexGraph graph(frame.signals);
+
572 for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
+
573 const SignalInfo &signal = frame.signals[index];
+
574 if (IsMultiplexSelector(signal.multiplexing.role) && signal.isSigned)
+
575 throw dbc_detail::MultiplexError(index, "Multiplex selectors must be unsigned.");
+
576 for (std::size_t previous = 0U; previous < index; ++previous) {
+
577 if (!graph.AreMutuallyExclusive(index, previous) && SignalsOverlap(signal, frame.signals[previous]))
+
578 throw dbc_detail::MultiplexError(index, "Signals overlap without mutually exclusive multiplexing conditions.");
+
579 }
+
580 }
+
581 } catch (const dbc_detail::MultiplexError &error) {
+
582 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing, filePath,
+
583 signalLines[frameIndex][error.index], error.what());
+
584 }
+
585 }
+
586}
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
-
DbcStatementDispatcher()
Register supported statement handlers through TypeFactory.
-
bool Dispatch(const std::string &line, DbcParseContext &context) const
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
DbcStatementDispatcher()
Register supported statement handlers through TypeFactory.
+
bool Dispatch(const std::string &line, DbcParseContext &context) const
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
-
ByteOrder
Signal byte order used for runtime decoding.
Parsed DBC content stored in a simple internal form.
std::vector< FrameInfo > frames
std::vector< PendingComment > pendingComments
-
void ValidateMultiplexing(const std::string &filePath) const
Validate selectors, branch ranges, and signal overlap.
+
void ValidateMultiplexing(const std::string &filePath) const
Validate selectors, branch ranges, and signal overlap.
-
void ResolveValueDescriptions(const std::string &filePath)
Resolve deferred raw descriptions against unique targets.
+
void ResolveValueDescriptions(const std::string &filePath)
Resolve deferred raw descriptions against unique targets.
std::vector< PendingValueDescriptions > pendingValues
-
void ResolveComments(const std::string &filePath)
Resolve deferred comments after all definitions are known.
+
void ResolveComments(const std::string &filePath)
Resolve deferred comments after all definitions are known.
std::vector< std::vector< std::size_t > > signalLines
Describes one CAN frame from a DBC file.
Definition frame_info.h:25
diff --git a/docs/html/dbc__statement__handler_8h_source.html b/docs/html/dbc__statement__handler_8h_source.html index 9bc0d9f..f6d1fb1 100644 --- a/docs/html/dbc__statement__handler_8h_source.html +++ b/docs/html/dbc__statement__handler_8h_source.html @@ -274,26 +274,26 @@ $(document).ready(function(){initNavTree('dbc__statement__handler_8h_source.html
Defer typed attribute statements for resolution.
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
Defer frame and signal comments for target resolution.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
DbcDiagnosticError(DbcDiagnosticCode code, const std::string &filePath, std::size_t lineNumber, const std::string &message)
Create an exception with a structured source diagnostic.
Parse and append frame definitions.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
Parse extended multiplexing conditions for deferred resolution.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
Parse and append signals to the current frame.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
void RegisterType(const std::string &keyword)
Register or replace a handler and create its reusable instance.
-
DbcStatementDispatcher()
Register supported statement handlers through TypeFactory.
-
bool Dispatch(const std::string &line, DbcParseContext &context) const
+
DbcStatementDispatcher()
Register supported statement handlers through TypeFactory.
+
bool Dispatch(const std::string &line, DbcParseContext &context) const
Interface for processing a registered DBC statement.
virtual void Handle(const std::string &line, DbcParseContext &context) const =0
Process a trimmed statement using state belonging to this parse.
Defer raw value descriptions for target resolution.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
Registry-based factory for creating objects by runtime identifier.
Definition typefactory.h:59
BasePtr create(const ClassId &id, Args... args) const
Creates an instance for the given id using provided arguments.
Definition typefactory.h:99
void registerType(const ClassId &id)
Registers a Derived type under the given id.
Definition typefactory.h:78
@@ -305,11 +305,11 @@ $(document).ready(function(){initNavTree('dbc__statement__handler_8h_source.html
std::vector< PendingComment > pendingComments
-
void ValidateMultiplexing(const std::string &filePath) const
Validate selectors, branch ranges, and signal overlap.
+
void ValidateMultiplexing(const std::string &filePath) const
Validate selectors, branch ranges, and signal overlap.
-
void ResolveValueDescriptions(const std::string &filePath)
Resolve deferred raw descriptions against unique targets.
+
void ResolveValueDescriptions(const std::string &filePath)
Resolve deferred raw descriptions against unique targets.
std::vector< PendingValueDescriptions > pendingValues
-
void ResolveComments(const std::string &filePath)
Resolve deferred comments after all definitions are known.
+
void ResolveComments(const std::string &filePath)
Resolve deferred comments after all definitions are known.
std::vector< std::vector< std::size_t > > signalLines
std::vector< PendingAttribute > pendingAttributes
diff --git a/docs/html/index.qhp b/docs/html/index.qhp index ada877b..953c55f 100644 --- a/docs/html/index.qhp +++ b/docs/html/index.qhp @@ -168,7 +168,8 @@
-
+
+
diff --git a/docs/html/md_README.html b/docs/html/md_README.html index 2648996..b8d8994 100644 --- a/docs/html/md_README.html +++ b/docs/html/md_README.html @@ -621,7 +621,7 @@ Signal definition

Signal validation and parse errors

-

SG_ statements are matched completely. Signal names must be nonempty and contain no whitespace. Spaces and tabs are accepted between syntax elements; byte order and signedness remain adjacent (@0+, @0-, @1+, or @1-). A quoted unit (which may be empty) and a nonempty comma-separated receiver list are required. Unexpected trailing fields are rejected.

+

SG_ statements are read completely with an iterative scanner. Signal parsing and numeric conversion do not use recursive regular-expression matching, so long fields do not consume stack space proportional to their length. No new statement length limit is imposed; valid long fields are preserved, and malformed statements return the usual source diagnostics. Signal names must be nonempty and contain no whitespace. Spaces and tabs are accepted between syntax elements; byte order and signedness remain adjacent (@0+, @0-, @1+, or @1-). A quoted unit (which may be empty) and a nonempty comma-separated receiver list are required. Unexpected trailing fields are rejected.

Start bits must be unsigned decimal integers fitting uint32_t; lengths must be within 1..64 bits. Signs, fractional values, numeric suffixes, and overflow are rejected for these fields. Every signal bit must fit within the frame's declared payload length, using Intel increasing bit positions or Motorola sawtooth traversal. This uses the stored payload length without introducing an additional CAN or CAN FD payload limit.

Factor, offset, minimum, and maximum accept signed decimal numbers, fractions, and scientific notation. Conversion is independent of the application's C and C++ locales: the decimal separator is always a dot (.), and comma decimals and digit grouping are rejected. The entire numeric token must be valid; hexadecimal numbers, NaN, infinity, conversion overflow, and conversion underflow (including subnormal values) are rejected. Minimum must not exceed maximum. Zero and negative factors are allowed.

Basic and extended multiplexing markers are supported as described below. Overlapping signals are rejected unless their selector conditions prove mutual exclusion. Duplicate frame keys and signal names within a frame are rejected.

diff --git a/docs/html/md_changelog.html b/docs/html/md_changelog.html index 7a3206a..46a887f 100644 --- a/docs/html/md_changelog.html +++ b/docs/html/md_changelog.html @@ -115,6 +115,10 @@ Testing

Fixed

    +
  • Replace recursive regular-expression matching for SG_ statements with an iterative field scanner. Long unit strings no longer overflow the stack.
  • +
  • Parse signal physical values directly with locale-independent from_chars, retaining complete-token, finite-value, overflow, and underflow validation without recursive numeric regex matching.
  • +
  • Preserve long valid fields without a new length limit, and reject malformed statements through the existing source-located diagnostic and throwing APIs.
  • +
  • Add parser regressions for 50,000-character fields, large receiver lists, malformed syntax, and numeric errors. Verify Release and ASan/UBSan builds with the optional Qt adapter: 2,409 core checks, seven model checks, and the qmake tree test pass in each build. One named-locale test skips because its locale is unavailable; local sanitizer leak checking remains disabled due to the documented ptrace restriction.
  • Read attribute definitions, defaults, and assignments across physical lines. Preserve quoted newlines and semicolons, and report attribute errors and unsupported frame formats at the statement's first physical line.
  • Add regressions for multiline frame formats and cycle times, deferred values, CRLF input, escaped strings, malformed statements, and diagnostic locations.
diff --git a/docs/html/md_tests_2README.html b/docs/html/md_tests_2README.html index af3ecce..251b7d3 100644 --- a/docs/html/md_tests_2README.html +++ b/docs/html/md_tests_2README.html @@ -160,6 +160,10 @@ Attribute coverage

Parser slots attributes, invalidAttributes, and attributeWarnings cover all five scopes and types, deferred definitions/defaults/targets, explicit values overriding defaults, repeated assignments, escaped strings, typed range/scope errors, duplicate definitions/defaults, unknown attributes and missing targets. They check source diagnostics and the throwing API, distinct standard/extended targets, frame cycle times and enum labels, unsupported formats and ID conflicts, unchanged J1939 opt-in, and runtime/tree propagation. Existing unsupported-statement checks now use relation forms, which remain unsupported, instead of supported attribute forms.

Attribute changes were verified with qmake6 ../dbc.pro, make -j4, and make check in build/: all six QtTest suites and the qmake tree target passed. The standalone C++17 tree test also passed. The existing named comma-decimal C locale test skipped because the locale is not installed.

+Long signal definitions

+

longSignalDefinitions reproduces the former stack overflow with a 50,000-character unit string. Both parser APIs are exercised with long units, signal names, receivers, decimal numbers, and a list of 10,001 receivers. Valid input must parse without truncating units; malformed input (including missing quotes, delimiters, receiver separators, numeric suffixes, overflow, and trailing receiver commas) must return an empty database and an InvalidStatement diagnostic at the signal's source line, or throw a source-located error through ParseFile(). These cases run as part of the existing parser target and sanitizer builds.

+

Verified with Qt 6.8.2 in separate Release and ASan/UBSan builds, both with CONFIG+=dbc_qt: make check passed 2,409 core QtTest checks, seven model checks, and the qmake tree test in each build. The named comma-decimal C locale test skipped because the required locale is not installed. The sanitizer run used ASAN_OPTIONS=detect_leaks=0:halt_on_error=1 and UBSAN_OPTIONS=halt_on_error=1; address and undefined-behavior checks passed, while leak checking was disabled because of the environment's previously documented ptrace restriction.

+

Extended validation

attributeNumbers checks signed 64-bit limits, leading plus signs, decimal HEX values, FLOAT fractions/exponents, and default overrides under a comma-decimal C++ locale. Node/environment defaults remain on definitions without becoming database attributes. invalidAttributeNumbers supplies 39 data rows, each checked in both definition bounds, a default, and an explicit assignment. The throwing and diagnostic APIs must reject malformed signs, trailing text, hexadecimal notation, non-finite values, overflow, and underflow with the correct error line. Additional malformed-attribute cases cover token separators, scope/type names, reversed FLOAT bounds, value types/ranges, and invalid CAN references.

Run AddressSanitizer and UndefinedBehaviorSanitizer in a separate directory:

diff --git a/docs/html/navtreedata.js b/docs/html/navtreedata.js index 584e63e..8726d34 100644 --- a/docs/html/navtreedata.js +++ b/docs/html/navtreedata.js @@ -181,7 +181,8 @@ var NAVTREE = [ "Database validation coverage", "md_tests_2README.html#autotoc_md167", null ], [ "End-to-end pipeline coverage", "md_tests_2README.html#autotoc_md168", null ], [ "Attribute coverage", "md_tests_2README.html#autotoc_md169", null ], - [ "Extended validation", "md_tests_2README.html#autotoc_md170", null ] + [ "Long signal definitions", "md_tests_2README.html#autotoc_md170", null ], + [ "Extended validation", "md_tests_2README.html#autotoc_md171", null ] ] ], [ "Classes", "annotated.html", [ [ "Class List", "annotated.html", "annotated_dup" ], diff --git a/docs/html/navtreeindex1.js b/docs/html/navtreeindex1.js index e194942..a8723a7 100644 --- a/docs/html/navtreeindex1.js +++ b/docs/html/navtreeindex1.js @@ -59,6 +59,7 @@ var NAVTREEINDEX1 = "md_tests_2README.html#autotoc_md168":[3,7], "md_tests_2README.html#autotoc_md169":[3,8], "md_tests_2README.html#autotoc_md170":[3,9], +"md_tests_2README.html#autotoc_md171":[3,10], "multiplex__graph_8h_source.html":[5,0,0,19], "multiplexing_8h_source.html":[5,0,0,20], "pages.html":[], diff --git a/docs/html/search/all_10.js b/docs/html/search/all_10.js index 0766dae..175845f 100644 --- a/docs/html/search/all_10.js +++ b/docs/html/search/all_10.js @@ -13,5 +13,6 @@ var searchData= ['live_20path_10',['live path',['../md_README.html#autotoc_md127',1,'']]], ['local_20core_20regression_20checks_11',['Local core regression checks',['../md_README.html#autotoc_md147',1,'']]], ['local_20regression_20checks_12',['Local regression checks',['../md_tests_2README.html#autotoc_md161',1,'']]], - ['lookup_13',['J1939 Opt-in and PGN Lookup',['../md_README.html#autotoc_md114',1,'']]] + ['long_20signal_20definitions_13',['Long signal definitions',['../md_tests_2README.html#autotoc_md170',1,'']]], + ['lookup_14',['J1939 Opt-in and PGN Lookup',['../md_README.html#autotoc_md114',1,'']]] ]; diff --git a/docs/html/search/all_17.js b/docs/html/search/all_17.js index 7a40ca7..d4c68f6 100644 --- a/docs/html/search/all_17.js +++ b/docs/html/search/all_17.js @@ -5,32 +5,33 @@ var searchData= ['settree_2',['SetTree',['../classDbcTreeModel.html#a8cb5a81d456cdead42b75fd3462ee00a',1,'DbcTreeModel']]], ['severity_3',['severity',['../structDbcDiagnostic.html#a75040a94f0313a606478020b0491f81d',1,'DbcDiagnostic']]], ['signal_20definition_4',['Signal definition',['../md_README.html#autotoc_md104',1,'']]], - ['signal_20identifiers_5',['Stable frame and signal identifiers',['../md_README.html#autotoc_md91',1,'']]], - ['signal_20validation_20and_20parse_20errors_6',['Signal validation and parse errors',['../md_README.html#autotoc_md106',1,'']]], - ['signal_20value_20descriptions_20tt_20val_5f_20tt_7',['Signal value descriptions (<tt>VAL_</tt>)',['../md_README.html#autotoc_md136',1,'']]], - ['signal_5finfo_2eh_8',['signal_info.h',['../signal__info_8h.html',1,'']]], - ['signaldecodestatus_9',['SignalDecodeStatus',['../dbc__decoder_8h.html#a5f4a41ee2810487020135b45e8d7559a',1,'dbc_decoder.h']]], - ['signalinfo_10',['SignalInfo',['../structSignalInfo.html',1,'']]], - ['signalinfo_20tt_11',['<tt>SignalInfo</tt>',['../md_README.html#autotoc_md58',1,'']]], - ['signalkey_12',['signalkey',['../structSignalKey.html',1,'SignalKey'],['../structSignalKey.html#aa4221b34e75469bf5aa939976c334f46',1,'SignalKey::SignalKey()']]], - ['signalkeyhasher_13',['SignalKeyHasher',['../structSignalKeyHasher.html',1,'']]], - ['signallines_14',['signalLines',['../structDbcParseContext.html#af8b411c938a2ea9100c4aaae452e6546',1,'DbcParseContext']]], - ['signalname_15',['signalname',['../structPendingComment.html#ae7b5aea9c252a4ab87e02915d24fee27',1,'PendingComment::signalName'],['../structPendingValueDescriptions.html#ac35446db10ebda5a12349759613f8c90',1,'PendingValueDescriptions::signalName']]], - ['stable_20frame_20and_20signal_20identifiers_16',['Stable frame and signal identifiers',['../md_README.html#autotoc_md91',1,'']]], - ['stage_201_20already_20implemented_17',['Stage 1 - already implemented',['../md_README.html#autotoc_md141',1,'']]], - ['stage_202_18',['Stage 2',['../md_README.html#autotoc_md142',1,'']]], - ['stage_203_19',['Stage 3',['../md_README.html#autotoc_md143',1,'']]], - ['stage_204_20',['Stage 4',['../md_README.html#autotoc_md144',1,'']]], - ['standalone_20tree_20tests_20without_20qt_21',['Standalone tree tests without Qt',['../md_README.html#autotoc_md150',1,'']]], - ['startbit_22',['startbit',['../structSignalInfo.html#ace290724f9801521609aedaa946d61f1',1,'SignalInfo::startBit'],['../structDecodeSignal.html#aaf5d7527ddf65b6355c2b4f3626df22d',1,'DecodeSignal::startBit']]], - ['statement_23',['statement',['../structPendingAttribute.html#a0ec62fc577e3a2d5f63d926f829e2ab8',1,'PendingAttribute']]], - ['statement_20handler_20factory_24',['Statement Handler Factory',['../md_README.html#autotoc_md134',1,'']]], - ['status_25',['status',['../structDecodedFrameValue.html#a645f433798d48a2787e7f00dfbb7dff9',1,'DecodedFrameValue::status'],['../structDecodedSignalValue.html#a94161a80412b3961da8fd2fec43c25f6',1,'DecodedSignalValue::status']]], - ['steps_26',['Recommended Next Steps',['../md_README.html#autotoc_md140',1,'']]], - ['strategy_27',['Unified Decode Strategy',['../md_README.html#autotoc_md126',1,'']]], - ['structured_20diagnostics_28',['Structured diagnostics',['../md_README.html#autotoc_md96',1,'']]], - ['structures_29',['Parsed Data Structures',['../md_README.html#autotoc_md57',1,'']]], - ['support_30',['Parser Support',['../md_README.html#autotoc_md95',1,'']]], - ['supported_20dbc_20syntax_31',['Supported DBC Syntax',['../md_README.html#autotoc_md99',1,'']]], - ['syntax_32',['Supported DBC Syntax',['../md_README.html#autotoc_md99',1,'']]] + ['signal_20definitions_5',['Long signal definitions',['../md_tests_2README.html#autotoc_md170',1,'']]], + ['signal_20identifiers_6',['Stable frame and signal identifiers',['../md_README.html#autotoc_md91',1,'']]], + ['signal_20validation_20and_20parse_20errors_7',['Signal validation and parse errors',['../md_README.html#autotoc_md106',1,'']]], + ['signal_20value_20descriptions_20tt_20val_5f_20tt_8',['Signal value descriptions (<tt>VAL_</tt>)',['../md_README.html#autotoc_md136',1,'']]], + ['signal_5finfo_2eh_9',['signal_info.h',['../signal__info_8h.html',1,'']]], + ['signaldecodestatus_10',['SignalDecodeStatus',['../dbc__decoder_8h.html#a5f4a41ee2810487020135b45e8d7559a',1,'dbc_decoder.h']]], + ['signalinfo_11',['SignalInfo',['../structSignalInfo.html',1,'']]], + ['signalinfo_20tt_12',['<tt>SignalInfo</tt>',['../md_README.html#autotoc_md58',1,'']]], + ['signalkey_13',['signalkey',['../structSignalKey.html',1,'SignalKey'],['../structSignalKey.html#aa4221b34e75469bf5aa939976c334f46',1,'SignalKey::SignalKey()']]], + ['signalkeyhasher_14',['SignalKeyHasher',['../structSignalKeyHasher.html',1,'']]], + ['signallines_15',['signalLines',['../structDbcParseContext.html#af8b411c938a2ea9100c4aaae452e6546',1,'DbcParseContext']]], + ['signalname_16',['signalname',['../structPendingValueDescriptions.html#ac35446db10ebda5a12349759613f8c90',1,'PendingValueDescriptions::signalName'],['../structPendingComment.html#ae7b5aea9c252a4ab87e02915d24fee27',1,'PendingComment::signalName']]], + ['stable_20frame_20and_20signal_20identifiers_17',['Stable frame and signal identifiers',['../md_README.html#autotoc_md91',1,'']]], + ['stage_201_20already_20implemented_18',['Stage 1 - already implemented',['../md_README.html#autotoc_md141',1,'']]], + ['stage_202_19',['Stage 2',['../md_README.html#autotoc_md142',1,'']]], + ['stage_203_20',['Stage 3',['../md_README.html#autotoc_md143',1,'']]], + ['stage_204_21',['Stage 4',['../md_README.html#autotoc_md144',1,'']]], + ['standalone_20tree_20tests_20without_20qt_22',['Standalone tree tests without Qt',['../md_README.html#autotoc_md150',1,'']]], + ['startbit_23',['startbit',['../structDecodeSignal.html#aaf5d7527ddf65b6355c2b4f3626df22d',1,'DecodeSignal::startBit'],['../structSignalInfo.html#ace290724f9801521609aedaa946d61f1',1,'SignalInfo::startBit']]], + ['statement_24',['statement',['../structPendingAttribute.html#a0ec62fc577e3a2d5f63d926f829e2ab8',1,'PendingAttribute']]], + ['statement_20handler_20factory_25',['Statement Handler Factory',['../md_README.html#autotoc_md134',1,'']]], + ['status_26',['status',['../structDecodedSignalValue.html#a94161a80412b3961da8fd2fec43c25f6',1,'DecodedSignalValue::status'],['../structDecodedFrameValue.html#a645f433798d48a2787e7f00dfbb7dff9',1,'DecodedFrameValue::status']]], + ['steps_27',['Recommended Next Steps',['../md_README.html#autotoc_md140',1,'']]], + ['strategy_28',['Unified Decode Strategy',['../md_README.html#autotoc_md126',1,'']]], + ['structured_20diagnostics_29',['Structured diagnostics',['../md_README.html#autotoc_md96',1,'']]], + ['structures_30',['Parsed Data Structures',['../md_README.html#autotoc_md57',1,'']]], + ['support_31',['Parser Support',['../md_README.html#autotoc_md95',1,'']]], + ['supported_20dbc_20syntax_32',['Supported DBC Syntax',['../md_README.html#autotoc_md99',1,'']]], + ['syntax_33',['Supported DBC Syntax',['../md_README.html#autotoc_md99',1,'']]] ]; diff --git a/docs/html/search/all_1a.js b/docs/html/search/all_1a.js index f67f2e4..89a474f 100644 --- a/docs/html/search/all_1a.js +++ b/docs/html/search/all_1a.js @@ -4,7 +4,7 @@ var searchData= ['val_5f_20tt_1',['Signal value descriptions (<tt>VAL_</tt>)',['../md_README.html#autotoc_md136',1,'']]], ['valid_2',['valid',['../structDecodedFrameValue.html#a4f51c31110dba1f73a763c596372beb0',1,'DecodedFrameValue::valid'],['../structDecodedSignalValue.html#aac0ed361af545cde48d0e6f41a859686',1,'DecodedSignalValue::valid']]], ['validatemultiplexing_3',['ValidateMultiplexing',['../structDbcParseContext.html#a908056de0a97316a4ebecb3f99cc400b',1,'DbcParseContext']]], - ['validation_4',['Extended validation',['../md_tests_2README.html#autotoc_md170',1,'']]], + ['validation_4',['Extended validation',['../md_tests_2README.html#autotoc_md171',1,'']]], ['validation_20and_20parse_20errors_5',['validation and parse errors',['../md_README.html#autotoc_md102',1,'Frame validation and parse errors'],['../md_README.html#autotoc_md106',1,'Signal validation and parse errors']]], ['validation_20coverage_6',['Database validation coverage',['../md_tests_2README.html#autotoc_md167',1,'']]], ['value_7',['value',['../structAttributeAssignment.html#a93de8c51c5154219bf18beadcec5708f',1,'AttributeAssignment::value'],['../structMultiplexing.html#a79158d4fdbfd68a3d51a8ce79d34fd67',1,'Multiplexing::value']]], diff --git a/docs/html/search/all_8.js b/docs/html/search/all_8.js index 40a7440..60c8199 100644 --- a/docs/html/search/all_8.js +++ b/docs/html/search/all_8.js @@ -49,7 +49,7 @@ var searchData= ['decode_20path_46',['2. Runtime decode path',['../md_README.html#autotoc_md51',1,'']]], ['decode_20strategy_47',['Unified Decode Strategy',['../md_README.html#autotoc_md126',1,'']]], ['decode_5fdatabase_2eh_48',['decode_database.h',['../decode__database_8h.html',1,'']]], - ['decodedatabase_49',['decodedatabase',['../classDecodeDatabase.html',1,'DecodeDatabase'],['../classDecodeDatabase.html#a51a44cf7c3f23c98ec8781afe0097c0c',1,'DecodeDatabase::DecodeDatabase()']]], + ['decodedatabase_49',['decodedatabase',['../classDecodeDatabase.html#a51a44cf7c3f23c98ec8781afe0097c0c',1,'DecodeDatabase::DecodeDatabase()'],['../classDecodeDatabase.html',1,'DecodeDatabase']]], ['decodedatabase_20tt_50',['<tt>DecodeDatabase</tt>',['../md_README.html#autotoc_md80',1,'']]], ['decodedframevalue_51',['DecodedFrameValue',['../structDecodedFrameValue.html',1,'']]], ['decodedframevalue_20tt_52',['<tt>DecodedFrameValue</tt>',['../md_README.html#autotoc_md89',1,'']]], @@ -65,13 +65,14 @@ var searchData= ['definition_62',['definition',['../structDecodedSignalValue.html#a828b5ef7faefc14fbf5dbd46249bcad2',1,'DecodedSignalValue::definition'],['../structDecodedFrameValue.html#a33e971ba10030506007aa201c89f351d',1,'DecodedFrameValue::definition'],['../structPgnLookupResult.html#aa5b79b61d2aef48fd6ae070cf5f901c1',1,'PgnLookupResult::definition'],['../md_README.html#autotoc_md100',1,'Frame definition'],['../md_README.html#autotoc_md104',1,'Signal definition']]], ['definition_20lifetime_20and_20reload_20coverage_63',['Definition lifetime and reload coverage',['../md_tests_2README.html#autotoc_md166',1,'']]], ['definition_20ownership_20and_20reload_64',['Definition ownership and reload',['../md_README.html#autotoc_md90',1,'']]], - ['demo_65',['Build and run the demo',['../md_README.html#autotoc_md151',1,'']]], - ['descriptions_66',['descriptions',['../structPendingValueDescriptions.html#a10ecbaeaed2cc3ac82fada73e7e041dd',1,'PendingValueDescriptions']]], - ['descriptions_20tt_20val_5f_20tt_67',['Signal value descriptions (<tt>VAL_</tt>)',['../md_README.html#autotoc_md136',1,'']]], - ['diagnostic_68',['diagnostic',['../classDbcDiagnosticError.html#ab3965082f01d66b6d5c20525af44235d',1,'DbcDiagnosticError']]], - ['diagnostics_69',['diagnostics',['../structDbcParseResult.html#a3a1987b4381bb1e0a34dc09e31483679',1,'DbcParseResult::diagnostics'],['../md_README.html#autotoc_md96',1,'Structured diagnostics']]], - ['dispatch_70',['Dispatch',['../classDbcStatementDispatcher.html#a495c2d5c7ebb8103b1987cdd87c142ed',1,'DbcStatementDispatcher']]], - ['dlc_71',['dlc',['../structDecodeFrame.html#a51111ac13988be372d5673d12cf3e060',1,'DecodeFrame::dlc'],['../structFrameInfo.html#aa2b198ea0f55e560c123d35e7b3ecab9',1,'FrameInfo::dlc']]], - ['documentation_72',['documentation',['../md_README.html',1,'DBC Parser and Runtime Decode Module Documentation'],['../md_changelog.html#autotoc_md42',1,'Documentation'],['../md_README.html#autotoc_md152',1,'Generate API documentation']]], - ['documentation_20and_20maintenance_73',['documentation and maintenance',['../md_changelog.html#autotoc_md37',1,'Documentation and maintenance'],['../md_changelog.html#autotoc_md45',1,'Documentation and maintenance']]] + ['definitions_65',['Long signal definitions',['../md_tests_2README.html#autotoc_md170',1,'']]], + ['demo_66',['Build and run the demo',['../md_README.html#autotoc_md151',1,'']]], + ['descriptions_67',['descriptions',['../structPendingValueDescriptions.html#a10ecbaeaed2cc3ac82fada73e7e041dd',1,'PendingValueDescriptions']]], + ['descriptions_20tt_20val_5f_20tt_68',['Signal value descriptions (<tt>VAL_</tt>)',['../md_README.html#autotoc_md136',1,'']]], + ['diagnostic_69',['diagnostic',['../classDbcDiagnosticError.html#ab3965082f01d66b6d5c20525af44235d',1,'DbcDiagnosticError']]], + ['diagnostics_70',['diagnostics',['../structDbcParseResult.html#a3a1987b4381bb1e0a34dc09e31483679',1,'DbcParseResult::diagnostics'],['../md_README.html#autotoc_md96',1,'Structured diagnostics']]], + ['dispatch_71',['Dispatch',['../classDbcStatementDispatcher.html#a495c2d5c7ebb8103b1987cdd87c142ed',1,'DbcStatementDispatcher']]], + ['dlc_72',['dlc',['../structDecodeFrame.html#a51111ac13988be372d5673d12cf3e060',1,'DecodeFrame::dlc'],['../structFrameInfo.html#aa2b198ea0f55e560c123d35e7b3ecab9',1,'FrameInfo::dlc']]], + ['documentation_73',['documentation',['../md_README.html',1,'DBC Parser and Runtime Decode Module Documentation'],['../md_changelog.html#autotoc_md42',1,'Documentation'],['../md_README.html#autotoc_md152',1,'Generate API documentation']]], + ['documentation_20and_20maintenance_74',['documentation and maintenance',['../md_changelog.html#autotoc_md45',1,'Documentation and maintenance'],['../md_changelog.html#autotoc_md37',1,'Documentation and maintenance']]] ]; diff --git a/docs/html/search/all_9.js b/docs/html/search/all_9.js index 7fb6c21..0a6b693 100644 --- a/docs/html/search/all_9.js +++ b/docs/html/search/all_9.js @@ -9,6 +9,6 @@ var searchData= ['example_20usage_6',['Example Usage',['../md_README.html#autotoc_md120',1,'']]], ['examples_2fmain_2ecpp_7',['main.cpp',['../examples_2main_8cpp.html',1,'']]], ['exists_8',['Why the Runtime Decode Layer Exists',['../md_README.html#autotoc_md53',1,'']]], - ['extended_20validation_9',['Extended validation',['../md_tests_2README.html#autotoc_md170',1,'']]], + ['extended_20validation_9',['Extended validation',['../md_tests_2README.html#autotoc_md171',1,'']]], ['extraction_10',['Intel and Motorola Extraction',['../md_README.html#autotoc_md118',1,'']]] ]; diff --git a/docs/html/structDbcDatabase.html b/docs/html/structDbcDatabase.html index 13310b9..952d29f 100644 --- a/docs/html/structDbcDatabase.html +++ b/docs/html/structDbcDatabase.html @@ -133,7 +133,7 @@ Public Attributes

Definition at line 23 of file dbc_database.h.

-

Referenced by DbcTreeBuilder::Build(), DbcDecodeBuilder::Build(), DbcFrameHandler::Handle(), DbcParseContext::ResolveAttributes(), and DbcParseContext::ValidateMultiplexing().

+

Referenced by DbcTreeBuilder::Build(), DbcDecodeBuilder::Build(), DbcFrameHandler::Handle(), DbcParseContext::ResolveAttributes(), and DbcParseContext::ValidateMultiplexing().

diff --git a/docs/html/structDbcParseContext.html b/docs/html/structDbcParseContext.html index 4c10ee2..254e54a 100644 --- a/docs/html/structDbcParseContext.html +++ b/docs/html/structDbcParseContext.html @@ -159,7 +159,7 @@ Public Attributes
-

Definition at line 486 of file dbc_statement_handler.cpp.

+

Definition at line 517 of file dbc_statement_handler.cpp.

@@ -181,7 +181,7 @@ Public Attributes

Validate selectors, branch ranges, and signal overlap.

-

Definition at line 536 of file dbc_statement_handler.cpp.

+

Definition at line 567 of file dbc_statement_handler.cpp.

References database, DbcDatabase::frames, SignalInfo::isSigned, SignalInfo::multiplexing, and signalLines.

@@ -205,7 +205,7 @@ Public Attributes

Resolve deferred comments after all definitions are known.

-

Definition at line 354 of file dbc_statement_handler.cpp.

+

Definition at line 385 of file dbc_statement_handler.cpp.

References FrameInfo::comment, SignalInfo::comment, database, and pendingComments.

@@ -263,7 +263,7 @@ Public Attributes

Resolve deferred raw descriptions against unique targets.

-

Definition at line 437 of file dbc_statement_handler.cpp.

+

Definition at line 468 of file dbc_statement_handler.cpp.

References database, pendingValues, and SignalInfo::valueDescriptions.

@@ -285,7 +285,7 @@ Public Attributes

Definition at line 61 of file dbc_statement_handler.h.

-

Referenced by DbcFrameHandler::Handle(), ResolveAttributes(), ResolveComments(), ResolveValueDescriptions(), and ValidateMultiplexing().

+

Referenced by DbcFrameHandler::Handle(), ResolveAttributes(), ResolveComments(), ResolveValueDescriptions(), and ValidateMultiplexing().

@@ -304,7 +304,7 @@ Public Attributes

Definition at line 62 of file dbc_statement_handler.h.

-

Referenced by DbcFrameHandler::Handle(), and DbcSignalHandler::Handle().

+

Referenced by DbcFrameHandler::Handle(), and DbcSignalHandler::Handle().

@@ -323,7 +323,7 @@ Public Attributes

Definition at line 63 of file dbc_statement_handler.h.

-

Referenced by DbcFrameHandler::Handle().

+

Referenced by DbcFrameHandler::Handle().

@@ -342,7 +342,7 @@ Public Attributes

Definition at line 64 of file dbc_statement_handler.h.

-

Referenced by DbcSignalHandler::Handle(), DbcCommentHandler::Handle(), DbcAttributeHandler::Handle(), DbcValueDescriptionHandler::Handle(), and DbcMultiplexingHandler::Handle().

+

Referenced by DbcSignalHandler::Handle(), DbcCommentHandler::Handle(), DbcAttributeHandler::Handle(), DbcValueDescriptionHandler::Handle(), and DbcMultiplexingHandler::Handle().

@@ -361,7 +361,7 @@ Public Attributes

Definition at line 65 of file dbc_statement_handler.h.

-

Referenced by DbcValueDescriptionHandler::Handle(), and ResolveValueDescriptions().

+

Referenced by DbcValueDescriptionHandler::Handle(), and ResolveValueDescriptions().

@@ -396,7 +396,7 @@ Public Attributes

Definition at line 70 of file dbc_statement_handler.h.

-

Referenced by DbcFrameHandler::Handle(), DbcSignalHandler::Handle(), and ValidateMultiplexing().

+

Referenced by DbcFrameHandler::Handle(), DbcSignalHandler::Handle(), and ValidateMultiplexing().

@@ -415,7 +415,7 @@ Public Attributes

Definition at line 74 of file dbc_statement_handler.h.

-

Referenced by DbcCommentHandler::Handle(), and ResolveComments().

+

Referenced by DbcCommentHandler::Handle(), and ResolveComments().

diff --git a/docs/html/structFrameInfo.html b/docs/html/structFrameInfo.html index a89d843..376e421 100644 --- a/docs/html/structFrameInfo.html +++ b/docs/html/structFrameInfo.html @@ -149,7 +149,7 @@ Public Attributes

Definition at line 26 of file frame_info.h.

-

Referenced by DbcSignalHandler::Handle().

+

Referenced by DbcSignalHandler::Handle().

@@ -168,7 +168,7 @@ Public Attributes

Definition at line 27 of file frame_info.h.

-

Referenced by DbcTreeModel::data(), and DbcFrameHandler::Handle().

+

Referenced by DbcTreeModel::data(), and DbcFrameHandler::Handle().

@@ -187,7 +187,7 @@ Public Attributes

Definition at line 28 of file frame_info.h.

-

Referenced by DbcTreeModel::data(), and DbcFrameHandler::Handle().

+

Referenced by DbcTreeModel::data(), and DbcFrameHandler::Handle().

@@ -206,7 +206,7 @@ Public Attributes

Definition at line 29 of file frame_info.h.

-

Referenced by DbcFrameHandler::Handle().

+

Referenced by DbcFrameHandler::Handle().

@@ -225,7 +225,7 @@ Public Attributes

Definition at line 30 of file frame_info.h.

-

Referenced by DbcFrameHandler::Handle().

+

Referenced by DbcFrameHandler::Handle().

@@ -244,7 +244,7 @@ Public Attributes

Definition at line 31 of file frame_info.h.

-

Referenced by DbcTreeModel::data(), and DbcSignalHandler::Handle().

+

Referenced by DbcTreeModel::data(), and DbcSignalHandler::Handle().

@@ -280,7 +280,7 @@ Public Attributes

Definition at line 33 of file frame_info.h.

-

Referenced by DbcTreeModel::data(), and DbcParseContext::ResolveComments().

+

Referenced by DbcTreeModel::data(), and DbcParseContext::ResolveComments().

diff --git a/docs/html/structPendingComment.html b/docs/html/structPendingComment.html index 4a493e7..cf55ae3 100644 --- a/docs/html/structPendingComment.html +++ b/docs/html/structPendingComment.html @@ -137,7 +137,7 @@ Public Attributes

Definition at line 23 of file dbc_statement_handler.h.

-

Referenced by DbcCommentHandler::Handle().

+

Referenced by DbcCommentHandler::Handle().

@@ -156,7 +156,7 @@ Public Attributes

Definition at line 24 of file dbc_statement_handler.h.

-

Referenced by DbcCommentHandler::Handle().

+

Referenced by DbcCommentHandler::Handle().

@@ -175,7 +175,7 @@ Public Attributes

Definition at line 25 of file dbc_statement_handler.h.

-

Referenced by DbcCommentHandler::Handle().

+

Referenced by DbcCommentHandler::Handle().

@@ -194,7 +194,7 @@ Public Attributes

Definition at line 26 of file dbc_statement_handler.h.

-

Referenced by DbcCommentHandler::Handle().

+

Referenced by DbcCommentHandler::Handle().

@@ -213,7 +213,7 @@ Public Attributes

Definition at line 27 of file dbc_statement_handler.h.

-

Referenced by DbcCommentHandler::Handle().

+

Referenced by DbcCommentHandler::Handle().

@@ -232,7 +232,7 @@ Public Attributes

Definition at line 28 of file dbc_statement_handler.h.

-

Referenced by DbcCommentHandler::Handle().

+

Referenced by DbcCommentHandler::Handle().

diff --git a/docs/html/structPendingValueDescriptions.html b/docs/html/structPendingValueDescriptions.html index 9cce7b7..1a0e24e 100644 --- a/docs/html/structPendingValueDescriptions.html +++ b/docs/html/structPendingValueDescriptions.html @@ -135,7 +135,7 @@ Public Attributes

Definition at line 14 of file dbc_statement_handler.h.

-

Referenced by DbcValueDescriptionHandler::Handle().

+

Referenced by DbcValueDescriptionHandler::Handle().

@@ -154,7 +154,7 @@ Public Attributes

Definition at line 15 of file dbc_statement_handler.h.

-

Referenced by DbcValueDescriptionHandler::Handle().

+

Referenced by DbcValueDescriptionHandler::Handle().

@@ -173,7 +173,7 @@ Public Attributes

Definition at line 16 of file dbc_statement_handler.h.

-

Referenced by DbcValueDescriptionHandler::Handle().

+

Referenced by DbcValueDescriptionHandler::Handle().

@@ -192,7 +192,7 @@ Public Attributes

Definition at line 17 of file dbc_statement_handler.h.

-

Referenced by DbcValueDescriptionHandler::Handle().

+

Referenced by DbcValueDescriptionHandler::Handle().

@@ -211,7 +211,7 @@ Public Attributes

Definition at line 18 of file dbc_statement_handler.h.

-

Referenced by DbcValueDescriptionHandler::Handle().

+

Referenced by DbcValueDescriptionHandler::Handle().

diff --git a/docs/html/structSignalInfo.html b/docs/html/structSignalInfo.html index 54d3a20..c2aab15 100644 --- a/docs/html/structSignalInfo.html +++ b/docs/html/structSignalInfo.html @@ -155,7 +155,7 @@ Public Attributes

Definition at line 26 of file signal_info.h.

-

Referenced by DbcParseContext::ValidateMultiplexing().

+

Referenced by DbcParseContext::ValidateMultiplexing().

@@ -174,7 +174,7 @@ Public Attributes

Definition at line 27 of file signal_info.h.

-

Referenced by DbcSignalHandler::Handle().

+

Referenced by DbcSignalHandler::Handle().

@@ -193,7 +193,7 @@ Public Attributes

Definition at line 28 of file signal_info.h.

-

Referenced by DbcTreeModel::data(), and DbcSignalHandler::Handle().

+

Referenced by DbcTreeModel::data(), and DbcSignalHandler::Handle().

@@ -212,7 +212,7 @@ Public Attributes

Definition at line 29 of file signal_info.h.

-

Referenced by DbcTreeModel::data(), and DbcSignalHandler::Handle().

+

Referenced by DbcTreeModel::data(), and DbcSignalHandler::Handle().

@@ -231,7 +231,7 @@ Public Attributes

Definition at line 30 of file signal_info.h.

-

Referenced by DbcTreeModel::data(), and DbcSignalHandler::Handle().

+

Referenced by DbcTreeModel::data(), and DbcSignalHandler::Handle().

@@ -250,7 +250,7 @@ Public Attributes

Definition at line 31 of file signal_info.h.

-

Referenced by DbcTreeModel::data(), and DbcParseContext::ValidateMultiplexing().

+

Referenced by DbcTreeModel::data(), and DbcParseContext::ValidateMultiplexing().

@@ -373,7 +373,7 @@ Public Attributes

Definition at line 38 of file signal_info.h.

-

Referenced by DbcTreeModel::data(), and DbcParseContext::ResolveValueDescriptions().

+

Referenced by DbcTreeModel::data(), and DbcParseContext::ResolveValueDescriptions().

@@ -392,7 +392,7 @@ Public Attributes

Definition at line 39 of file signal_info.h.

-

Referenced by DbcTreeModel::data(), and DbcParseContext::ResolveComments().

+

Referenced by DbcTreeModel::data(), and DbcParseContext::ResolveComments().

diff --git a/tests/README.md b/tests/README.md index 714ca0d..18b8671 100644 --- a/tests/README.md +++ b/tests/README.md @@ -295,6 +295,25 @@ Attribute changes were verified with `qmake6 ../dbc.pro`, `make -j4`, and passed. The standalone C++17 tree test also passed. The existing named comma-decimal C locale test skipped because the locale is not installed. +## Long signal definitions + +`longSignalDefinitions` reproduces the former stack overflow with a 50,000-character +unit string. Both parser APIs are exercised with long units, signal names, +receivers, decimal numbers, and a list of 10,001 receivers. Valid input must parse +without truncating units; malformed input (including missing quotes, delimiters, +receiver separators, numeric suffixes, overflow, and trailing receiver commas) +must return an empty database and an `InvalidStatement` diagnostic at the signal's +source line, or throw a source-located error through `ParseFile()`. +These cases run as part of the existing parser target and sanitizer builds. + +Verified with Qt 6.8.2 in separate Release and ASan/UBSan builds, both with +`CONFIG+=dbc_qt`: `make check` passed 2,409 core QtTest checks, seven model checks, +and the qmake tree test in each build. The named comma-decimal C locale test +skipped because the required locale is not installed. The sanitizer run used +`ASAN_OPTIONS=detect_leaks=0:halt_on_error=1` and `UBSAN_OPTIONS=halt_on_error=1`; +address and undefined-behavior checks passed, while leak checking was disabled +because of the environment's previously documented ptrace restriction. + ## Extended validation `attributeNumbers` checks signed 64-bit limits, leading plus signs, decimal HEX diff --git a/tests/dbc_parser_test.cpp b/tests/dbc_parser_test.cpp index 495caa4..55bddc3 100644 --- a/tests/dbc_parser_test.cpp +++ b/tests/dbc_parser_test.cpp @@ -116,6 +116,8 @@ private Q_SLOTS: void integration(); void invalidSignals(); void validSignals(); + void longSignalDefinitions_data(); + void longSignalDefinitions(); void physicalNumbersCppLocale(); void physicalNumbersCLocale(); void dispatch(); @@ -1146,6 +1148,56 @@ void DbcParserTest::validSignals() { } catch (const std::exception &error) { CHECK(false, error.what()); } } +void DbcParserTest::longSignalDefinitions_data() { + QTest::addColumn("line"); + QTest::addColumn("valid"); + QTest::addColumn("unit"); + const std::string longText(50000U, 'x'); + const std::string prefix = "SG_ Value : 0|8@1+ (1,0) [0|255] "; + QTest::newRow("long-unit") << prefix + "\"" + longText + "\" ECU" << true << longText; + QTest::newRow("unterminated-unit") << prefix + "\"" + longText << false << std::string(); + QTest::newRow("missing-receiver-separator") << prefix + "\"" + longText + "\"ECU" << false << std::string(); + QTest::newRow("long-name") << SignalLine("0", "8", "1+", "1", "0", "0", "255", longText) << true << std::string(); + QTest::newRow("long-receiver") << prefix + "\"\" " + longText << true << std::string(); + std::string receivers = "ECU"; + for (unsigned index = 0U; index < 10000U; ++index) + receivers += ",ECU"; + QTest::newRow("many-receivers") << prefix + "\"\" " + receivers << true << std::string(); + QTest::newRow("trailing-receiver-comma") << prefix + "\"\" " + receivers + "," << false << std::string(); + QTest::newRow("long-factor") << SignalLine("0", "8", "1+", std::string(50000U, '0') + "1", "0", "0", "255") << true << std::string(); + QTest::newRow("invalid-long-factor") << SignalLine("0", "8", "1+", std::string(50000U, '0') + "1x", "0", "0", "255") << false << std::string(); + QTest::newRow("overflowing-start-bit") << SignalLine(std::string(50000U, '9'), "8", "1+", "1", "0", "0", "255") << false << std::string(); + QTest::newRow("missing-colon") << "SG_ " + longText << false << std::string(); +} + +void DbcParserTest::longSignalDefinitions() { + QFETCH(std::string, line); + QFETCH(bool, valid); + QFETCH(std::string, unit); + Fixture fixture; + fixture.Write("BO_ 1 Frame: 1 ECU\n" + line + "\n"); + const DbcParser parser; + const DbcParseResult result = parser.ParseFileWithDiagnostics(fixture.path.string()); + QCOMPARE(result.HasErrors(), !valid); + if (valid) { + QVERIFY(result.diagnostics.empty()); + const DbcDatabase database = parser.ParseFile(fixture.path.string()); + QCOMPARE(database.frames.size(), std::size_t(1)); + QCOMPARE(database.frames[0].signals.size(), std::size_t(1)); + QCOMPARE(database.frames[0].signals[0].unit, unit); + QCOMPARE(result.database.frames[0].signals[0].unit, unit); + QCOMPARE(database.frames[0].signals[0].factor, 1.0); + QVERIFY(!database.frames[0].signals[0].receivers.empty()); + } else { + QVERIFY(result.database.frames.empty()); + QCOMPARE(result.diagnostics.size(), std::size_t(1)); + QCOMPARE(result.diagnostics[0].code, DbcDiagnosticCode::InvalidStatement); + QCOMPARE(result.diagnostics[0].lineNumber, std::size_t(2)); + QCOMPARE(result.diagnostics[0].filePath, fixture.path.string()); + RejectSignal(fixture, line, 1U); + } +} + void DbcParserTest::CheckPhysicalNumbers() { Fixture fixture; struct PhysicalCase {