LCOV - code coverage report
Current view: top level - core - dbc_statement_handler.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 99.5 % 370 368
Test Date: 2026-10-20 13:34:58 Functions: 100.0 % 31 31
Branches: 68.4 % 820 561

             Branch data     Line data    Source code
       1                 :             : // SPDX-License-Identifier: MIT
       2                 :             : #include "dbc_statement_handler.h"
       3                 :             : #include "j1939.h"
       4                 :             : #include "text_utils.h"
       5                 :             : #include "signal_layout.h"
       6                 :             : #include <charconv>
       7                 :             : #include <cmath>
       8                 :             : #include <limits>
       9                 :             : #include <regex>
      10                 :             : #include <sstream>
      11                 :             : #include <stdexcept>
      12                 :             : 
      13                 :             : namespace {
      14                 :        2466 :     std::uint32_t ParseDecimal(const std::string &text,
      15                 :             :                                std::uint32_t maximum,
      16                 :             :                                const char *field) {
      17                 :        2466 :         std::uint32_t value = 0U;
      18         [ +  - ]:        2466 :         const std::from_chars_result parsed = std::from_chars(text.data(), text.data() + text.size(), value);
      19         [ +  + ]:        4932 :         if (text.empty() || parsed.ec != std::errc() ||
      20   [ +  -  +  +  :        4932 :             parsed.ptr != text.data() + text.size() || value > maximum)
             +  +  +  + ]
      21   [ +  -  +  -  :         174 :             throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
          +  -  +  -  +  
                      - ]
      22                 :        2408 :         return value;
      23                 :             :     }
      24                 :             : 
      25                 :         784 :     std::uint32_t ParseCanId(const std::string &text) {
      26                 :         784 :         const std::uint32_t rawId = ParseDecimal(
      27                 :             :                 text, std::numeric_limits<std::uint32_t>::max(), "CAN ID");
      28                 :             :         // Only bit 31 may accompany the actual 29-bit identifier.
      29         [ +  + ]:         750 :         if ((rawId & 0x60000000U) != 0U)
      30         [ +  - ]:          20 :             throw std::runtime_error("CAN ID exceeds the 29-bit identifier range.");
      31                 :         730 :         return rawId;
      32                 :             :     }
      33                 :             : 
      34                 :        1666 :     double ParsePhysicalValue(const std::string &text, const char *field) {
      35                 :             :         static const std::regex number(
      36   [ +  +  +  -  :        1666 :                 R"(^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$)");
             +  -  -  - ]
      37   [ +  -  +  + ]:        1666 :         if (!std::regex_match(text, number))
      38   [ +  -  +  -  :         336 :             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                 :        1554 :         const char *begin = text.data();
      42                 :        1554 :         const char *end = begin + text.size();
      43         [ +  + ]:        1554 :         if (*begin == '+')
      44                 :          18 :             ++begin;
      45                 :        1554 :         double value = 0.0;
      46                 :             :         const std::from_chars_result parsed =
      47                 :        1554 :                 std::from_chars(begin, end, value, std::chars_format::general);
      48                 :             :         // Keep the existing rejection of conversion underflow into subnormal values.
      49   [ +  +  +  -  :        3084 :         if (parsed.ec != std::errc() || parsed.ptr != end || !std::isfinite(value) ||
          +  -  +  +  +  
                      + ]
      50                 :        1530 :             std::fpclassify(value) == FP_SUBNORMAL)
      51   [ +  -  +  -  :         120 :             throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
          +  -  +  -  +  
                      - ]
      52                 :        1514 :         return value;
      53                 :             :     }
      54                 :             : 
      55                 :         496 :     Multiplexing ParseMultiplexingMarker(const std::string &marker) {
      56                 :         496 :         Multiplexing result;
      57   [ +  -  +  + ]:         496 :         if (marker == "M")
      58                 :          34 :             result.role = MultiplexRole::Selector;
      59         [ +  + ]:         462 :         else if (!marker.empty()) {
      60         [ +  + ]:          38 :             if (marker[0] != 'm')
      61         [ +  - ]:           1 :                 throw std::runtime_error("Unsupported multiplexing marker.");
      62         [ +  - ]:          37 :             const std::string value = marker.substr(1U);
      63                 :             :             const std::from_chars_result parsed =
      64         [ +  - ]:          37 :                 std::from_chars(value.data(), value.data() + value.size(), result.value);
      65   [ +  +  +  +  :          37 :             if (value.empty() || parsed.ec != std::errc() || parsed.ptr != value.data() + value.size())
             +  +  +  + ]
      66         [ +  - ]:           7 :                 throw std::runtime_error("Invalid or unsupported multiplexing marker.");
      67                 :          30 :             result.role = MultiplexRole::Branch;
      68                 :          37 :         }
      69                 :             : 
      70                 :         488 :         return result;
      71                 :             :     }
      72                 :             : 
      73                 :         104 :     RawSignalValue ParseValueKey(const std::string &number) {
      74                 :         104 :         RawSignalValue key;
      75   [ +  -  +  +  :         104 :         if (!number.empty() && number[0] == '-') {
                   +  + ]
      76                 :          16 :             std::int64_t value = 0;
      77                 :             :             const std::from_chars_result parsed =
      78         [ +  - ]:          16 :                 std::from_chars(number.data(), number.data() + number.size(), value);
      79   [ +  +  -  +  :          16 :             if (parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
                   +  + ]
      80   [ +  -  +  - ]:           2 :                 throw std::runtime_error("Invalid VAL_ integer: " + number);
      81                 :          14 :             key = NormalizeValueKey(RawSignalValue(value));
      82                 :             :         } else {
      83                 :          88 :             std::uint64_t value = 0;
      84                 :             :             const std::from_chars_result parsed =
      85         [ +  - ]:          88 :                 std::from_chars(number.data(), number.data() + number.size(), value);
      86   [ +  -  +  +  :          88 :             if (number.empty() || parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
             +  +  +  + ]
      87   [ +  -  +  - ]:           6 :                 throw std::runtime_error("Invalid VAL_ integer: " + number);
      88                 :          82 :             key = value;
      89                 :             :         }
      90                 :          96 :         return key;
      91                 :             :     }
      92                 :             : 
      93                 :          96 :     std::string UnescapeDescription(const std::string &quoted) {
      94                 :          96 :         std::string description;
      95         [ +  + ]:         724 :         for (std::size_t i = 0; i < quoted.size(); ++i) {
      96         [ +  + ]:         628 :             if (quoted[i] == '\\')
      97                 :           7 :                 ++i;
      98         [ +  - ]:         628 :             description += quoted[i];
      99                 :             :         }
     100                 :          96 :         return description;
     101                 :           0 :     }
     102                 :             : 
     103                 :          67 :     FrameInfo *FindUniqueFrame(DbcDatabase &database, std::uint32_t canId, bool isExtended) {
     104                 :          67 :         FrameInfo *frame = nullptr;
     105         [ +  + ]:         178 :         for (FrameInfo &candidate : database.frames) {
     106   [ +  +  +  + ]:         113 :             if (candidate.canId == canId && candidate.isExtended == isExtended) {
     107         [ +  + ]:          66 :                 if (frame)
     108         [ +  - ]:           2 :                     throw std::runtime_error("Ambiguous VAL_ frame reference.");
     109                 :          64 :                 frame = &candidate;
     110                 :             :             }
     111                 :             :         }
     112         [ +  + ]:          65 :         if (!frame)
     113         [ +  - ]:           3 :             throw std::runtime_error("Unresolved VAL_ frame reference.");
     114                 :          62 :         return frame;
     115                 :             :     }
     116                 :             : 
     117                 :          62 :     SignalInfo *FindUniqueSignal(FrameInfo &frame, const std::string &signalName) {
     118                 :          62 :         SignalInfo *signal = nullptr;
     119         [ +  + ]:         149 :         for (SignalInfo &candidate : frame.signals) {
     120         [ +  + ]:          89 :             if (candidate.name == signalName) {
     121         [ +  + ]:          59 :                 if (signal)
     122   [ +  -  +  - ]:           2 :                     throw std::runtime_error("Ambiguous VAL_ signal reference: " + signalName);
     123                 :          57 :                 signal = &candidate;
     124                 :             :             }
     125                 :             :         }
     126         [ +  + ]:          60 :         if (!signal)
     127   [ +  -  +  - ]:           5 :             throw std::runtime_error("Unresolved VAL_ signal reference: " + signalName);
     128                 :          55 :         return signal;
     129                 :             :     }
     130                 :             : 
     131                 :         884 :     std::uint64_t NextSignalBit(std::uint64_t bit, bool isLittleEndian) {
     132         [ +  + ]:         884 :         if (isLittleEndian)
     133                 :         724 :             return bit + 1U;
     134                 :             :         // From bit 0, Motorola continues at bit 7 of the next byte (0 -> 15).
     135         [ +  + ]:         160 :         return bit % 8U == 0U ? bit + 15U : bit - 1U;
     136                 :             :     }
     137                 :             : 
     138                 :             :     FrameInfo *FindFrameById(DbcDatabase &, std::uint32_t, bool);
     139                 :             :     SignalInfo *FindSignalByName(FrameInfo &, const std::string &);
     140                 :             : } // namespace
     141                 :             : 
     142                 :             : namespace {
     143                 :         317 :     std::vector<std::string> SplitReceivers(const std::string &text) {
     144                 :         317 :         std::vector<std::string> receivers;
     145                 :         317 :         std::string token;
     146         [ +  - ]:         317 :         std::istringstream stream(text);
     147                 :             : 
     148   [ +  -  +  -  :         956 :         while (std::getline(stream, token, ',')) {
                   +  + ]
     149         [ +  - ]:         322 :             token = dbc_detail::TrimText(token);
     150         [ -  + ]:         322 :             if (!token.empty())
     151         [ +  - ]:         322 :                 receivers.push_back(token);
     152                 :             :         }
     153                 :             : 
     154                 :         317 :         return receivers;
     155                 :         317 :     }
     156                 :             : 
     157                 :         803 :     void NormalizeCanId(std::uint32_t rawCanId, std::uint32_t &normalizedCanId, bool &isExtended) {
     158                 :             :         /*
     159                 :             :          * DBC commonly stores extended identifiers with bit 31 set.
     160                 :             :          * Example:
     161                 :             :          * raw id = 0x80000000 | actual_29_bit_id
     162                 :             :          */
     163   [ +  +  +  + ]:         803 :         isExtended = (rawCanId & 0x80000000U) != 0U || rawCanId > 0x7FFU;
     164                 :         803 :         normalizedCanId = rawCanId & 0x1FFFFFFFU;
     165                 :         803 :     }
     166                 :             : } // namespace
     167                 :             : 
     168                 :         649 : FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) {
     169                 :             :     /*
     170                 :             :    * Example:
     171                 :             :    * BO_ 256 EngineData: 8 EEC1
     172                 :             :    */
     173                 :             : 
     174         [ +  - ]:         649 :     std::istringstream stream(line);
     175                 :         649 :     std::string keyword;
     176                 :         649 :     std::string idText;
     177   [ +  -  +  -  :         649 :     if (!(stream >> keyword >> idText) || keyword != "BO_")
          +  -  +  +  +  
             -  -  +  +  
                      + ]
     178         [ +  - ]:           3 :         throw std::runtime_error("Missing CAN ID in frame definition.");
     179                 :             : 
     180         [ +  + ]:         646 :     const std::uint32_t rawCanId = ParseCanId(idText);
     181                 :             : 
     182                 :         628 :     std::string nameText;
     183   [ +  -  +  -  :         628 :     if (!std::getline(stream, nameText, ':') || stream.eof())
          +  +  +  -  +  
                +  +  + ]
     184         [ +  - ]:           2 :         throw std::runtime_error("Missing ':' in frame definition.");
     185         [ +  - ]:         626 :     std::istringstream nameStream(nameText);
     186                 :         626 :     FrameInfo frame;
     187                 :         626 :     std::string extra;
     188   [ +  -  +  -  :         626 :     if (!(nameStream >> frame.name) || (nameStream >> extra))
          +  +  +  -  +  
             -  +  +  +  
                      + ]
     189         [ +  - ]:           2 :         throw std::runtime_error("Missing or invalid frame name.");
     190                 :             : 
     191                 :         624 :     std::string lengthText;
     192   [ +  -  +  -  :         624 :     if (!(stream >> lengthText))
                   +  + ]
     193         [ +  - ]:           1 :         throw std::runtime_error("Missing payload length in frame definition.");
     194         [ +  + ]:         623 :     frame.dlc = static_cast<std::uint8_t>(ParseDecimal(
     195                 :         623 :             lengthText, std::numeric_limits<std::uint8_t>::max(), "payload length"));
     196   [ +  -  +  -  :         617 :     if (!(stream >> frame.transmitter))
                   +  + ]
     197         [ +  - ]:           1 :         throw std::runtime_error("Missing transmitter in frame definition.");
     198   [ +  -  +  -  :         616 :     if (stream >> extra)
                   +  + ]
     199   [ +  -  +  - ]:           1 :         throw std::runtime_error("Unexpected trailing field in frame definition: " + extra);
     200                 :             : 
     201                 :         615 :     NormalizeCanId(rawCanId, frame.canId, frame.isExtended);
     202                 :             : 
     203                 :         615 :     return frame;
     204                 :         772 : }
     205                 :             : 
     206                 :         521 : SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) {
     207                 :             :     // Match the complete statement before converting any numeric fields.
     208                 :             :     enum Field {
     209                 :             :         NameAndMarker = 1, StartBit, BitLength, ByteOrder, Signedness,
     210                 :             :         Factor, Offset, Minimum, Maximum, Unit, Receivers
     211                 :             :     };
     212                 :             :     static const std::regex syntax(
     213                 :             :             // Name and optional multiplexing marker.
     214                 :             :             R"dbc(^SG_[ \t]+([^:]+):[ \t]*)dbc"
     215                 :             :             // Bit position, length, byte order and signedness.
     216                 :             :             R"dbc(([^| \t]+)[ \t]*\|[ \t]*([^@ \t]+)[ \t]*@[ \t]*([01])([+-])[ \t]*)dbc"
     217                 :             :             // Scaling factor and offset.
     218                 :             :             R"dbc(\([ \t]*([^, \t]+)[ \t]*,[ \t]*([^ )\t]+)[ \t]*\)[ \t]*)dbc"
     219                 :             :             // Physical range.
     220                 :             :             R"dbc(\[[ \t]*([^| \t]+)[ \t]*\|[ \t]*([^\] \t]+)[ \t]*\][ \t]*)dbc"
     221                 :             :             // Unit and receivers.
     222   [ +  +  +  -  :         521 :             R"dbc("([^"\r\n]*)"[ \t]+([^ \t,]+(?:[ \t]*,[ \t]*[^ \t,]+)*)[ \t]*$)dbc");
             +  -  -  - ]
     223                 :         521 :     std::smatch fields;
     224   [ +  -  +  + ]:         521 :     if (!std::regex_match(line, fields, syntax))
     225         [ +  - ]:          23 :         throw std::runtime_error("Malformed or unsupported SG_ signal definition.");
     226                 :             : 
     227                 :         498 :     SignalInfo signal;
     228   [ +  -  +  -  :         498 :     std::istringstream nameFields(fields[NameAndMarker].str());
                   +  - ]
     229                 :         498 :     std::string marker;
     230                 :         498 :     std::string extra;
     231   [ +  -  +  - ]:         498 :     nameFields >> signal.name >> marker;
     232   [ +  +  +  -  :         498 :     if (signal.name.empty() || (nameFields >> extra))
          +  -  +  +  +  
                      + ]
     233         [ +  - ]:           2 :         throw std::runtime_error("Invalid signal name or multiplexing marker.");
     234         [ +  + ]:         496 :     signal.multiplexing = ParseMultiplexingMarker(marker);
     235                 :             : 
     236   [ +  -  +  -  :         495 :     signal.startBit = ParseDecimal(fields[StartBit].str(),
                   +  + ]
     237                 :             :                                    std::numeric_limits<std::uint32_t>::max(), "signal start bit");
     238   [ +  -  +  -  :         489 :     signal.length = ParseDecimal(fields[BitLength].str(), 64U, "signal length");
                   +  + ]
     239         [ +  + ]:         473 :     if (signal.length == 0U)
     240         [ +  - ]:           1 :         throw std::runtime_error("Signal length must be within 1..64 bits.");
     241   [ +  -  +  -  :         472 :     signal.isLittleEndian = fields[ByteOrder].str() == "1";
                   +  - ]
     242   [ +  -  +  -  :         472 :     signal.isSigned = fields[Signedness].str() == "-";
                   +  - ]
     243                 :             : 
     244   [ +  -  +  -  :         504 :     signal.factor = ParsePhysicalValue(fields[Factor].str(), "signal factor");
                   +  + ]
     245   [ +  -  +  -  :         484 :     signal.offset = ParsePhysicalValue(fields[Offset].str(), "signal offset");
                   +  + ]
     246   [ +  -  +  -  :         434 :     signal.minimum = ParsePhysicalValue(fields[Minimum].str(), "signal minimum");
                   +  + ]
     247   [ +  -  +  -  :         396 :     signal.maximum = ParsePhysicalValue(fields[Maximum].str(), "signal maximum");
                   +  + ]
     248         [ +  + ]:         320 :     if (signal.minimum > signal.maximum)
     249         [ +  - ]:           3 :         throw std::runtime_error("Signal minimum exceeds maximum.");
     250   [ +  -  +  - ]:         317 :     signal.unit = fields[Unit].str();
     251   [ +  -  +  -  :         317 :     signal.receivers = SplitReceivers(fields[Receivers].str());
                   +  - ]
     252                 :         317 :     return signal;
     253                 :        1245 : }
     254                 :             : 
     255                 :             : 
     256                 :             : namespace {
     257                 :         698 :     FrameInfo *FindFrameById(DbcDatabase &database,
     258                 :             :                              std::uint32_t canId,
     259                 :             :                              bool isExtended) {
     260         [ +  + ]:         757 :         for (FrameInfo &frame : database.frames) {
     261   [ +  +  +  + ]:         144 :             if (frame.canId == canId && frame.isExtended == isExtended)
     262                 :          85 :                 return &frame;
     263                 :             :         }
     264                 :             : 
     265                 :         613 :         return nullptr;
     266                 :             :     }
     267                 :             : 
     268                 :         364 :     SignalInfo *FindSignalByName(FrameInfo &frame, const std::string &signalName) {
     269         [ +  + ]:         441 :         for (SignalInfo &signal : frame.signals) {
     270         [ +  + ]:         126 :             if (signal.name == signalName)
     271                 :          49 :                 return &signal;
     272                 :             :         }
     273                 :             : 
     274                 :         315 :         return nullptr;
     275                 :             :     }
     276                 :             : }// namespace
     277                 :             : 
     278                 :         649 : void DbcFrameHandler::Handle(const std::string &line, DbcParseContext &context) const {
     279         [ +  + ]:         649 :     FrameInfo frame = ParseFrameLine(line);
     280   [ +  +  +  + ]:         615 :     if (context.enableJ1939 && frame.isExtended) {
     281                 :          27 :         frame.hasPgn = true;
     282                 :          27 :         frame.pgn = ExtractJ1939Pgn(frame.canId);
     283                 :             :     }
     284         [ +  + ]:         615 :     if (FindFrameById(context.database, frame.canId, frame.isExtended) != nullptr)
     285   [ +  +  +  -  :          16 :         throw std::runtime_error("Duplicate " + std::string(frame.isExtended ? "extended" : "standard") +
                   +  - ]
     286   [ +  -  +  -  :          24 :                                  " frame CAN ID: " + std::to_string(frame.canId));
                   +  - ]
     287         [ +  - ]:         607 :     context.database.frames.push_back(std::move(frame));
     288                 :         607 :     context.currentFrame = &context.database.frames.back();
     289         [ +  - ]:         607 :     context.signalLines.emplace_back();
     290                 :         615 : }
     291                 :             : 
     292                 :         526 : void DbcSignalHandler::Handle(const std::string &line, DbcParseContext &context) const {
     293         [ +  + ]:         526 :     if (context.currentFrame == nullptr)
     294         [ +  - ]:           5 :         throw std::runtime_error("Signal found before any frame definition.");
     295         [ +  + ]:         521 :     SignalInfo signal = ParseSignalLine(line);
     296         [ +  + ]:         317 :     if (FindSignalByName(*context.currentFrame, signal.name) != nullptr)
     297                 :           6 :         throw std::runtime_error("Duplicate signal name '" + signal.name +
     298   [ +  -  +  -  :          12 :                                  "' in frame '" + context.currentFrame->name + "'.");
          +  -  +  -  +  
                      - ]
     299         [ +  + ]:         311 :     if (!IsValidSignalLayout(signal.startBit, signal.length, signal.isLittleEndian,
     300                 :         311 :                             context.currentFrame->dlc))
     301         [ +  - ]:           7 :         throw std::runtime_error("Signal exceeds the frame payload length.");
     302         [ +  - ]:         304 :     context.currentFrame->signals.push_back(std::move(signal));
     303         [ +  - ]:         304 :     context.signalLines.back().push_back(context.lineNumber);
     304                 :         317 : }
     305                 :             : 
     306                 :         149 : void DbcCommentHandler::Handle(const std::string &line, DbcParseContext &context) const {
     307         [ +  - ]:         149 :     std::istringstream stream(line);
     308                 :         149 :     std::string keyword;
     309                 :         149 :     std::string target;
     310   [ +  -  +  - ]:         149 :     stream >> keyword >> target;
     311   [ +  -  +  +  :         149 :     if (keyword != "CM_" || (target != "BO_" && target != "SG_"))
          +  -  +  +  +  
             -  +  +  +  
                      + ]
     312                 :           9 :         return;
     313                 :             : 
     314                 :         140 :     PendingComment pending;
     315         [ +  - ]:         140 :     pending.isSignal = target == "SG_";
     316                 :         140 :     pending.lineNumber = context.lineNumber;
     317                 :         140 :     std::string idText;
     318   [ +  -  +  -  :         140 :     if (!(stream >> idText))
                   +  + ]
     319         [ +  - ]:           2 :         throw std::runtime_error("Missing CAN ID in CM_ statement.");
     320         [ +  + ]:         138 :     NormalizeCanId(ParseCanId(idText), pending.canId, pending.isExtended);
     321   [ +  +  +  -  :         157 :     if (pending.isSignal && (!(stream >> pending.signalName) ||
          +  -  +  -  +  
                +  +  + ]
     322                 :          55 :                              pending.signalName.find_first_of("\";") != std::string::npos))
     323         [ +  - ]:           2 :         throw std::runtime_error("Missing or invalid signal name in CM_ statement.");
     324                 :             : 
     325         [ +  - ]:         100 :     stream >> std::ws;
     326   [ +  -  +  + ]:         100 :     if (stream.get() != '"')
     327         [ +  - ]:           4 :         throw std::runtime_error("Missing opening quote in CM_ statement.");
     328                 :          96 :     bool closed = false;
     329                 :             :     char character;
     330   [ +  -  +  -  :        1096 :     while (stream.get(character)) {
                   +  + ]
     331         [ +  + ]:        1094 :         if (character == '"') {
     332                 :          90 :             closed = true;
     333                 :          90 :             break;
     334                 :             :         }
     335         [ +  + ]:        1004 :         if (character == '\\') {
     336   [ +  -  +  -  :           7 :             if (!stream.get(character) || (character != '"' && character != '\\'))
          +  +  +  +  +  
                +  +  + ]
     337         [ +  - ]:           4 :                 throw std::runtime_error("Invalid escape in CM_ statement.");
     338                 :             :         }
     339         [ +  - ]:        1000 :         pending.comment += character;
     340                 :             :     }
     341         [ +  + ]:          92 :     if (!closed)
     342         [ +  - ]:           2 :         throw std::runtime_error("Unterminated CM_ comment.");
     343         [ +  - ]:          90 :     stream >> std::ws;
     344   [ +  -  +  + ]:          90 :     if (stream.get() != ';')
     345         [ +  - ]:           4 :         throw std::runtime_error("Missing semicolon in CM_ statement.");
     346         [ +  - ]:          86 :     stream >> std::ws;
     347   [ +  -  +  + ]:          86 :     if (!stream.eof())
     348         [ +  - ]:           2 :         throw std::runtime_error("Unexpected trailing text in CM_ statement.");
     349         [ +  - ]:          84 :     context.pendingComments.push_back(std::move(pending));
     350   [ +  +  +  +  :         391 : }
                   +  + ]
     351                 :             : 
     352                 :         597 : void DbcParseContext::ResolveComments(const std::string &filePath) {
     353         [ +  + ]:         670 :     for (const PendingComment &pending: pendingComments) {
     354                 :          83 :         FrameInfo *frame = FindFrameById(database, pending.canId, pending.isExtended);
     355         [ +  + ]:          83 :         if (!frame)
     356                 :             :             throw DbcDiagnosticError(DbcDiagnosticCode::InvalidStatement, filePath,
     357   [ +  -  +  - ]:          18 :                                      pending.lineNumber, "Unresolved CM_ frame reference.");
     358         [ +  + ]:          77 :         if (pending.isSignal) {
     359                 :          47 :             SignalInfo *signal = FindSignalByName(*frame, pending.signalName);
     360         [ +  + ]:          47 :             if (!signal)
     361                 :             :                 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidStatement, filePath,
     362   [ +  -  +  - ]:           4 :                                          pending.lineNumber, "Unresolved CM_ signal reference: " + pending.signalName);
     363         [ +  - ]:          43 :             signal->comment = pending.comment;
     364                 :             :         } else {
     365         [ +  - ]:          30 :             frame->comment = pending.comment;
     366                 :             :         }
     367                 :             :     }
     368                 :         587 : }
     369                 :             : 
     370                 :         949 : DbcStatementDispatcher::DbcStatementDispatcher() {
     371   [ +  -  +  - ]:        1898 :     RegisterType<DbcHeaderHandler>("VERSION");
     372   [ +  -  +  - ]:        1898 :     RegisterType<DbcHeaderHandler>("NS_");
     373   [ +  -  +  - ]:        1898 :     RegisterType<DbcHeaderHandler>("NS_:");
     374   [ +  -  +  - ]:        1898 :     RegisterType<DbcHeaderHandler>("BS_");
     375   [ +  -  +  - ]:        1898 :     RegisterType<DbcHeaderHandler>("BS_:");
     376   [ +  -  +  - ]:        1898 :     RegisterType<DbcHeaderHandler>("BU_");
     377   [ +  -  +  - ]:        1898 :     RegisterType<DbcHeaderHandler>("BU_:");
     378   [ +  -  +  - ]:        1898 :     RegisterType<DbcUnsupportedMultiplexingHandler>("SG_MUL_VAL_");
     379   [ +  -  +  - ]:        1898 :     RegisterType<DbcFrameHandler>("BO_");
     380   [ +  -  +  - ]:        1898 :     RegisterType<DbcSignalHandler>("SG_");
     381   [ +  -  +  - ]:        1898 :     RegisterType<DbcCommentHandler>("CM_");
     382   [ +  -  +  - ]:        1898 :     RegisterType<DbcValueDescriptionHandler>("VAL_");
     383   [ +  -  +  - ]:        1898 :     RegisterType<DbcAttributeHandler>("BA_DEF_");
     384   [ +  -  +  - ]:        1898 :     RegisterType<DbcAttributeHandler>("BA_DEF_DEF_");
     385   [ +  -  +  - ]:         949 :     RegisterType<DbcAttributeHandler>("BA_");
     386                 :         949 : }
     387                 :             : 
     388                 :          62 : void DbcHeaderHandler::Handle(const std::string &, DbcParseContext &) const {}
     389                 :             : 
     390                 :        2132 : bool DbcStatementDispatcher::Dispatch(const std::string &line,
     391                 :             :                                       DbcParseContext &context) const {
     392         [ +  - ]:        2132 :     std::istringstream stream(line);
     393                 :        2132 :     std::string keyword;
     394         [ +  - ]:        2132 :     stream >> keyword;
     395         [ +  - ]:        2132 :     const HandlerMap::const_iterator handler = m_handlers.find(keyword);
     396         [ +  + ]:        2132 :     if (handler == m_handlers.end())
     397                 :          40 :         return false;
     398         [ +  + ]:        2092 :     handler->second->Handle(line, context);
     399                 :        1743 :     return true;
     400                 :        2481 : }
     401                 :             : 
     402                 :          94 : void DbcValueDescriptionHandler::Handle(const std::string &line, DbcParseContext &context) const {
     403   [ +  +  +  -  :          94 :     static const std::regex header(R"(^VAL_[ \t]+([^ \t]+)[ \t]+([^ \t;]+)[ \t]+(.*)$)");
             +  -  -  - ]
     404   [ +  +  +  -  :          94 :     static const std::regex pair(R"dbc(^([^ \t]+)[ \t]+"((?:[^"\\]|\\["\\])*)")dbc");
             +  -  -  - ]
     405                 :          94 :     std::smatch fields;
     406   [ +  -  +  + ]:          94 :     if (!std::regex_match(line, fields, header))
     407         [ +  - ]:           4 :         throw std::runtime_error("Malformed or unsupported VAL_ statement.");
     408   [ +  -  +  -  :          93 :     const std::uint32_t rawId = ParseDecimal(fields[1].str(), std::numeric_limits<std::uint32_t>::max(), "VAL_ CAN ID");
                   +  + ]
     409         [ +  + ]:          87 :     if ((rawId & 0x60000000U) != 0U)
     410         [ +  - ]:           1 :         throw std::runtime_error("VAL_ CAN ID exceeds the 29-bit identifier range.");
     411                 :          86 :     PendingValueDescriptions pending;
     412                 :          86 :     NormalizeCanId(rawId, pending.canId, pending.isExtended);
     413   [ +  -  +  - ]:          86 :     pending.signalName = fields[2].str();
     414                 :          86 :     pending.lineNumber = context.lineNumber;
     415   [ +  -  +  - ]:          86 :     std::string rest = fields[3].str();
     416   [ +  -  +  + ]:         179 :     while (rest != ";") {
     417   [ +  -  +  + ]:         109 :         if (!std::regex_search(rest, fields, pair))
     418         [ +  - ]:           5 :             throw std::runtime_error("Malformed VAL_ value/description pair or missing semicolon.");
     419   [ +  -  +  - ]:         104 :         const std::string number = fields[1].str();
     420         [ +  + ]:         104 :         const RawSignalValue key = ParseValueKey(number);
     421   [ +  -  +  -  :          96 :         const std::string description = UnescapeDescription(fields[2].str());
                   +  - ]
     422   [ +  -  +  + ]:          96 :         if (!pending.descriptions.emplace(key, description).second)
     423   [ +  -  +  - ]:           2 :             throw std::runtime_error("Duplicate VAL_ value key: " + number);
     424         [ +  - ]:          94 :         const std::size_t consumed = static_cast<std::size_t>(fields.length());
     425         [ +  - ]:          94 :         rest.erase(0, consumed);
     426   [ +  +  +  -  :          94 :         if (!rest.empty() && rest[0] != ';' && rest[0] != ' ' && rest[0] != '\t')
          +  +  +  -  +  
          +  +  -  +  +  
                   +  + ]
     427         [ +  - ]:           1 :             throw std::runtime_error("Missing whitespace between VAL_ pairs.");
     428         [ +  - ]:          93 :         rest = dbc_detail::TrimText(rest);
     429                 :         107 :     }
     430         [ +  + ]:          70 :     if (pending.descriptions.empty())
     431         [ +  - ]:           1 :         throw std::runtime_error("VAL_ requires at least one value/description pair.");
     432         [ +  - ]:          69 :     context.pendingValues.push_back(std::move(pending));
     433                 :         128 : }
     434                 :             : 
     435                 :         592 : void DbcParseContext::ResolveValueDescriptions(const std::string &filePath) {
     436         [ +  + ]:         644 :     for (const PendingValueDescriptions &pending: pendingValues) {
     437                 :             :         try {
     438         [ +  + ]:          67 :             FrameInfo *frame = FindUniqueFrame(database, pending.canId, pending.isExtended);
     439         [ +  + ]:          62 :             SignalInfo *signal = FindUniqueSignal(*frame, pending.signalName);
     440         [ +  + ]:         127 :             for (const ValueDescriptions::value_type &entry: pending.descriptions) {
     441   [ +  -  +  + ]:          75 :                 if (!signal->valueDescriptions.insert(entry).second)
     442   [ +  -  +  - ]:           3 :                     throw std::runtime_error("Duplicate VAL_ value key for signal: " + pending.signalName);
     443                 :             :             }
     444         [ -  + ]:          15 :         } catch (const std::exception &error) {
     445                 :             :             throw DbcDiagnosticError(DbcDiagnosticCode::InvalidValueDescription,
     446   [ +  -  +  - ]:          45 :                                      filePath, pending.lineNumber, error.what());
     447                 :          15 :         }
     448                 :             :     }
     449                 :         577 : }
     450                 :             : 
     451                 :           4 : void DbcUnsupportedMultiplexingHandler::Handle(const std::string &, DbcParseContext &) const {
     452         [ +  - ]:           4 :     throw std::runtime_error("Extended and nested multiplexing are unsupported.");
     453                 :             : }
     454                 :             : 
     455                 :             : namespace {
     456                 :         114 :     std::vector<std::uint64_t> SignalBits(const SignalInfo &signal) {
     457                 :         114 :         std::vector<std::uint64_t> bits;
     458                 :         114 :         std::uint64_t bit = signal.startBit;
     459         [ +  + ]:         998 :         for (std::uint32_t index = 0U; index < signal.length; ++index) {
     460         [ +  - ]:         884 :             bits.push_back(bit);
     461                 :         884 :             bit = NextSignalBit(bit, signal.isLittleEndian);
     462                 :             :         }
     463                 :         114 :         return bits;
     464                 :           0 :     }
     465                 :             : 
     466                 :          58 :     bool AreMutuallyExclusive(const SignalInfo &first, const SignalInfo &second) {
     467                 :          79 :         return first.multiplexing.role == MultiplexRole::Branch &&
     468   [ +  +  +  + ]:          62 :                second.multiplexing.role == MultiplexRole::Branch &&
     469         [ +  + ]:          62 :                first.multiplexing.value != second.multiplexing.value;
     470                 :             :     }
     471                 :             : 
     472                 :         193 :     std::string ValidateBranch(const SignalInfo &signal, const SignalInfo *selector) {
     473         [ +  + ]:         193 :         if (signal.multiplexing.role != MultiplexRole::Branch)
     474                 :         164 :             return {};
     475         [ +  + ]:          29 :         if (selector == nullptr)
     476         [ +  - ]:           8 :             return "Multiplexed branch has no selector.";
     477   [ +  +  +  + ]:          25 :         if (selector->length < 64U && signal.multiplexing.value >= (std::uint64_t(1U) << selector->length))
     478         [ +  - ]:          10 :             return "Branch value exceeds selector raw range.";
     479                 :          20 :         return {};
     480                 :             :     }
     481                 :             : 
     482                 :         280 :     const SignalInfo *FindSelector(const FrameInfo &frame,
     483                 :             :                                    const std::vector<std::size_t> &lines,
     484                 :             :                                    const std::string &filePath) {
     485                 :         280 :         const SignalInfo *selector = nullptr;
     486         [ +  + ]:         479 :         for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
     487                 :         207 :             const SignalInfo &signal = frame.signals[index];
     488         [ +  + ]:         207 :             if (signal.multiplexing.role != MultiplexRole::Selector)
     489                 :         173 :                 continue;
     490   [ +  +  +  + ]:          34 :             if (selector != nullptr || signal.isSigned)
     491                 :             :                 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing,
     492                 :           8 :                                          filePath, lines[index],
     493   [ +  -  +  - ]:          32 :                                          "Multiplexing requires one unsigned selector per frame.");
     494                 :          26 :             selector = &signal;
     495                 :             :         }
     496                 :         272 :         return selector;
     497                 :             :     }
     498                 :             : 
     499                 :          57 :     bool SignalsOverlap(const SignalInfo &first, const SignalInfo &second) {
     500         [ +  - ]:          57 :         const std::vector<std::uint64_t> firstBits = SignalBits(first);
     501         [ +  - ]:          57 :         const std::vector<std::uint64_t> secondBits = SignalBits(second);
     502         [ +  + ]:         489 :         for (const std::uint64_t firstBit : firstBits) {
     503         [ +  + ]:        3380 :             for (const std::uint64_t secondBit : secondBits) {
     504         [ +  + ]:        2948 :                 if (firstBit == secondBit)
     505                 :          10 :                     return true;
     506                 :             :             }
     507                 :             :         }
     508                 :          47 :         return false;
     509                 :          57 :     }
     510                 :             : }
     511                 :             : 
     512                 :         576 : void DbcParseContext::ValidateMultiplexing(const std::string &filePath) const {
     513         [ +  + ]:         829 :     for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) {
     514                 :         280 :         const FrameInfo &frame = database.frames[frameIndex];
     515                 :         280 :         const SignalInfo *selector = FindSelector(frame, signalLines[frameIndex], filePath);
     516         [ +  + ]:         446 :         for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
     517                 :         193 :             const SignalInfo &signal = frame.signals[index];
     518         [ +  - ]:         193 :             std::string error = ValidateBranch(signal, selector);
     519   [ +  +  +  +  :         251 :             for (std::size_t previous = 0U; previous < index && error.empty(); ++previous) {
                   +  + ]
     520                 :          58 :                 const SignalInfo &other = frame.signals[previous];
     521   [ +  +  +  -  :          58 :                 if (!AreMutuallyExclusive(signal, other) && SignalsOverlap(signal, other))
             +  +  +  + ]
     522         [ +  - ]:          10 :                     error = "Signals overlap without mutually exclusive multiplexing conditions.";
     523                 :             :             }
     524         [ +  + ]:         193 :             if (!error.empty())
     525                 :             :                 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing,
     526         [ +  - ]:          19 :                                          filePath, signalLines[frameIndex][index], error);
     527                 :         193 :         }
     528                 :             :     }
     529                 :         549 : }
        

Generated by: LCOV version 2.0-1