LCOV - code coverage report
Current view: top level - core - dbc_attributes.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 98.8 % 260 257
Test Date: 2026-10-20 13:34:58 Functions: 100.0 % 20 20
Branches: 73.5 % 604 444

             Branch data     Line data    Source code
       1                 :             : // SPDX-License-Identifier: MIT
       2                 :             : #include "dbc_statement_handler.h"
       3                 :             : #include <charconv>
       4                 :             : #include <cmath>
       5                 :             : #include <limits>
       6                 :             : #include <set>
       7                 :             : 
       8                 :             : namespace {
       9                 :             :     struct Token {
      10                 :             :         std::string text;
      11                 :             :         bool quoted = false;
      12                 :             :     };
      13                 :             : 
      14                 :         621 :     std::vector<Token> Tokenize(const std::string &text) {
      15                 :         621 :         std::vector<Token> tokens;
      16                 :         621 :         std::size_t index = 0U;
      17         [ +  + ]:        6141 :         while (index < text.size()) {
      18                 :        5528 :             const char character = text[index++];
      19   [ +  +  -  + ]:        5528 :             if (character == ' ' || character == '\t')
      20                 :        2138 :                 continue;
      21                 :        3390 :             Token token;
      22         [ +  + ]:        3390 :             if (character == '"') {
      23                 :         652 :                 token.quoted = true;
      24                 :         652 :                 bool closed = false;
      25         [ +  + ]:        1938 :                 while (index < text.size()) {
      26                 :        1936 :                     char next = text[index++];
      27         [ +  + ]:        1936 :                     if (next == '"') {
      28                 :         648 :                         closed = true;
      29                 :         648 :                         break;
      30                 :             :                     }
      31         [ +  + ]:        1288 :                     if (next == '\\') {
      32         [ -  + ]:           4 :                         if (index == text.size())
      33         [ #  # ]:           0 :                             throw std::runtime_error("Incomplete attribute escape.");
      34                 :           4 :                         next = text[index++];
      35   [ +  +  +  + ]:           4 :                         if (next != '"' && next != '\\')
      36         [ +  - ]:           2 :                             throw std::runtime_error("Invalid attribute escape.");
      37                 :             :                     }
      38         [ +  - ]:        1286 :                     token.text += next;
      39                 :             :                 }
      40         [ +  + ]:         650 :                 if (!closed)
      41         [ +  - ]:           2 :                     throw std::runtime_error("Unterminated attribute string.");
      42   [ +  +  +  - ]:        1296 :                 if (index < text.size() && text[index] != ' ' && text[index] != '\t' &&
      43   [ +  -  +  +  :        1296 :                     text[index] != ',' && text[index] != ';')
             +  +  +  + ]
      44         [ +  - ]:           2 :                     throw std::runtime_error("Missing separator after attribute string.");
      45   [ +  +  +  + ]:        2738 :             } else if (character == ';' || character == ',') {
      46         [ +  - ]:         623 :                 token.text = character;
      47                 :             :             } else {
      48         [ +  - ]:        2115 :                 token.text = character;
      49   [ +  +  +  - ]:       18280 :                 while (index < text.size() && text[index] != ' ' && text[index] != '\t' &&
      50   [ +  +  +  +  :       18280 :                        text[index] != ';' && text[index] != ',') {
             +  -  +  + ]
      51         [ +  + ]:        7028 :                     if (text[index] == '"')
      52         [ +  - ]:           2 :                         throw std::runtime_error("Missing separator before attribute string.");
      53         [ +  - ]:        7026 :                     token.text += text[index++];
      54                 :             :                 }
      55                 :             :             }
      56         [ +  - ]:        3382 :             tokens.push_back(token);
      57                 :        3390 :         }
      58   [ +  -  +  -  :         613 :         if (tokens.empty() || tokens.back().quoted || tokens.back().text != ";")
          +  -  +  +  +  
                      + ]
      59         [ +  - ]:           2 :             throw std::runtime_error("Attribute statement requires a semicolon.");
      60                 :         611 :         tokens.pop_back();
      61                 :         611 :         return tokens;
      62                 :          10 :     }
      63                 :             : 
      64                 :             :     class AttributeTokens {
      65                 :             :     public:
      66                 :         621 :         explicit AttributeTokens(const std::string &line) : tokens(Tokenize(line)) {}
      67                 :        2639 :         Token Take() {
      68         [ +  + ]:        2639 :             if (position == tokens.size())
      69         [ +  - ]:           4 :                 throw std::runtime_error("Incomplete attribute statement.");
      70                 :        2635 :             return tokens[position++];
      71                 :             :         }
      72                 :        1681 :         std::string Word() {
      73         [ +  - ]:        1681 :             const Token token = Take();
      74   [ +  +  +  -  :        1681 :             if (token.quoted || token.text == "," || token.text == ";")
          +  -  +  -  -  
                +  +  + ]
      75         [ +  - ]:           2 :                 throw std::runtime_error("Expected an attribute keyword or number.");
      76         [ +  - ]:        3358 :             return token.text;
      77                 :        1681 :         }
      78                 :         315 :         std::string Quoted() {
      79         [ +  + ]:         315 :             const Token token = Take();
      80         [ +  + ]:         311 :             if (!token.quoted)
      81         [ +  - ]:           2 :                 throw std::runtime_error("Expected a quoted attribute name or string.");
      82         [ +  - ]:         618 :             return token.text;
      83                 :         311 :         }
      84                 :         262 :         bool Empty() const { return position == tokens.size(); }
      85                 :         245 :         void End() const {
      86         [ -  + ]:         245 :             if (!Empty())
      87         [ #  # ]:           0 :                 throw std::runtime_error("Unexpected trailing attribute fields.");
      88                 :         245 :         }
      89                 :             : 
      90                 :             :     private:
      91                 :             :         std::vector<Token> tokens;
      92                 :             :         std::size_t position = 0U;
      93                 :             :     };
      94                 :             : 
      95                 :         115 :     bool IsScope(const std::string &text) {
      96   [ +  +  +  +  :         115 :         return text == "BO_" || text == "SG_" || text == "BU_" || text == "EV_";
             +  +  +  + ]
      97                 :             :     }
      98                 :             : 
      99                 :         110 :     AttributeScope ParseScope(const std::string &text) {
     100         [ +  + ]:         110 :         if (text == "BO_") return AttributeScope::Frame;
     101         [ +  + ]:          17 :         if (text == "SG_") return AttributeScope::Signal;
     102         [ +  + ]:           6 :         if (text == "BU_") return AttributeScope::Node;
     103         [ +  + ]:           4 :         if (text == "EV_") return AttributeScope::Environment;
     104   [ +  -  +  - ]:           2 :         throw std::runtime_error("Unsupported attribute scope: " + text);
     105                 :             :     }
     106                 :             : 
     107                 :             :     template<class Numeric>
     108                 :         866 :     Numeric ParseNumber(const std::string &text) {
     109                 :         866 :         const char *begin = text.data();
     110                 :         866 :         const char *end = begin + text.size();
     111   [ +  -  +  + ]:         866 :         if (begin != end && *begin == '+') {
     112                 :          78 :             ++begin;
     113   [ +  +  +  +  :          78 :             if (begin != end && (*begin == '-' || *begin == '+'))
                   +  + ]
     114   [ +  -  +  - ]:          48 :                 throw std::runtime_error("Invalid attribute number: " + text);
     115                 :             :         }
     116                 :         818 :         Numeric value = 0;
     117         [ +  - ]:         818 :         const std::from_chars_result parsed = std::from_chars(begin, end, value);
     118   [ +  +  +  +  :         818 :         if (begin == end || parsed.ec != std::errc() || parsed.ptr != end || !std::isfinite(value))
          +  +  +  +  +  
                      + ]
     119   [ +  -  +  - ]:         270 :             throw std::runtime_error("Invalid attribute number: " + text);
     120                 :         548 :         return value;
     121                 :             :     }
     122                 :             : 
     123                 :         204 :     AttributeValue Value(const Token &token, const AttributeDefinition &definition, bool isDefault) {
     124         [ +  + ]:         204 :         if (definition.type == AttributeType::String) {
     125   [ +  +  +  - ]:           6 :             if (!token.quoted) throw std::runtime_error("Attribute STRING requires quoted text.");
     126         [ +  - ]:           4 :             return token.text;
     127                 :             :         }
     128   [ +  +  +  +  :         198 :         if (definition.type == AttributeType::Enum && isDefault && token.quoted) {
                   +  + ]
     129         [ +  + ]:           9 :             for (std::size_t index = 0U; index < definition.enumValues.size(); ++index) {
     130         [ +  + ]:           7 :                 if (definition.enumValues[index] == token.text)
     131                 :           2 :                     return static_cast<std::int64_t>(index);
     132                 :             :             }
     133         [ +  - ]:           2 :             throw std::runtime_error("Unknown attribute ENUM default label.");
     134                 :             :         }
     135   [ +  +  +  - ]:         194 :         if (token.quoted) throw std::runtime_error("Numeric attribute requires an unquoted number.");
     136         [ +  + ]:         192 :         if (definition.type == AttributeType::Float) {
     137         [ +  + ]:          53 :             const double value = ParseNumber<double>(token.text);
     138   [ +  -  +  + ]:           5 :             if (value < definition.minimum || value > definition.maximum)
     139         [ +  - ]:           2 :                 throw std::runtime_error("Attribute FLOAT value outside definition range.");
     140                 :           3 :             return value;
     141                 :             :         }
     142         [ +  + ]:         139 :         const std::int64_t value = ParseNumber<std::int64_t>(token.text);
     143   [ +  -  +  + ]:          31 :         if (value < definition.integerMinimum || value > definition.integerMaximum)
     144         [ +  - ]:           4 :             throw std::runtime_error("Attribute integer/ENUM value outside definition range.");
     145                 :          27 :         return value;
     146                 :             :     }
     147                 :             : 
     148                 :         393 :     void Define(AttributeTokens &tokens, DbcDatabase &database) {
     149                 :         393 :         AttributeDefinition definition;
     150         [ +  - ]:         393 :         const Token first = tokens.Take();
     151         [ +  - ]:         393 :         std::string name = first.text;
     152         [ +  + ]:         393 :         if (!first.quoted) {
     153         [ +  + ]:          76 :             definition.scope = ParseScope(first.text);
     154         [ +  + ]:          74 :             name = tokens.Quoted();
     155                 :             :         }
     156   [ -  +  -  - ]:         389 :         if (name.empty()) throw std::runtime_error("Empty attribute name.");
     157         [ +  + ]:         389 :         const std::string type = tokens.Word();
     158   [ +  -  +  +  :         387 :         if (type == "INT" || type == "HEX") {
          +  -  +  +  +  
                      + ]
     159   [ +  -  +  + ]:         255 :             definition.type = type == "INT" ? AttributeType::Integer : AttributeType::Hex;
     160   [ +  -  +  + ]:         309 :             definition.integerMinimum = ParseNumber<std::int64_t>(tokens.Word());
     161   [ +  -  +  + ]:         257 :             definition.integerMaximum = ParseNumber<std::int64_t>(tokens.Word());
     162         [ +  + ]:         145 :             if (definition.integerMinimum > definition.integerMaximum)
     163         [ +  - ]:           2 :                 throw std::runtime_error("Reversed attribute integer range.");
     164   [ +  -  +  + ]:         132 :         } else if (type == "FLOAT") {
     165                 :         106 :             definition.type = AttributeType::Float;
     166   [ +  -  +  + ]:         132 :             definition.minimum = ParseNumber<double>(tokens.Word());
     167   [ +  -  +  + ]:         106 :             definition.maximum = ParseNumber<double>(tokens.Word());
     168         [ +  + ]:          54 :             if (definition.minimum > definition.maximum)
     169         [ +  - ]:           2 :                 throw std::runtime_error("Reversed attribute FLOAT range.");
     170   [ +  -  +  + ]:          26 :         } else if (type == "STRING") {
     171                 :          11 :             definition.type = AttributeType::String;
     172   [ +  -  +  + ]:          15 :         } else if (type == "ENUM") {
     173                 :          13 :             definition.type = AttributeType::Enum;
     174                 :          13 :             std::set<std::string> labels;
     175                 :             :             for (;;) {
     176         [ +  + ]:          23 :                 const std::string label = tokens.Quoted();
     177   [ +  -  +  + ]:          19 :                 if (!labels.insert(label).second)
     178         [ +  - ]:           2 :                     throw std::runtime_error("Duplicate attribute ENUM label.");
     179         [ +  - ]:          17 :                 definition.enumValues.push_back(label);
     180         [ +  + ]:          17 :                 if (tokens.Empty()) break;
     181         [ +  - ]:          10 :                 const Token separator = tokens.Take();
     182   [ +  -  +  -  :          10 :                 if (separator.quoted || separator.text != ",") throw std::runtime_error("Expected ENUM comma.");
          -  +  -  +  -  
                      - ]
     183         [ +  + ]:          29 :             }
     184                 :           7 :             definition.integerMaximum = static_cast<std::int64_t>(definition.enumValues.size() - 1U);
     185                 :          13 :         } else {
     186   [ +  -  +  - ]:           2 :             throw std::runtime_error("Unsupported attribute definition type: " + type);
     187                 :             :         }
     188         [ +  - ]:         213 :         tokens.End();
     189   [ +  -  +  +  :         213 :         if (name == "VFrameFormat" && (definition.scope != AttributeScope::Frame || definition.type != AttributeType::Enum))
          +  -  +  +  +  
                      + ]
     190         [ +  - ]:           2 :             throw std::runtime_error("VFrameFormat requires a BO_ ENUM definition.");
     191   [ +  -  +  +  :         217 :         if (name == "GenMsgCycleTime" && (definition.scope != AttributeScope::Frame ||
             +  +  +  + ]
     192   [ -  +  -  - ]:           6 :                                           (definition.type != AttributeType::Integer && definition.type != AttributeType::Hex)))
     193         [ +  - ]:           2 :             throw std::runtime_error("GenMsgCycleTime requires a BO_ integer definition.");
     194   [ +  -  +  + ]:         209 :         if (!database.attributeDefinitions.emplace(name, definition).second)
     195   [ +  -  +  - ]:           2 :             throw std::runtime_error("Duplicate attribute definition: " + name);
     196                 :         945 :     }
     197                 :             : 
     198                 :         218 :     AttributeDefinition &FindDefinition(DbcDatabase &database, const std::string &name) {
     199         [ +  - ]:         218 :         const std::map<std::string, AttributeDefinition>::iterator found = database.attributeDefinitions.find(name);
     200         [ +  + ]:         218 :         if (found == database.attributeDefinitions.end())
     201   [ +  -  +  - ]:           4 :             throw std::runtime_error("Undefined attribute: " + name);
     202                 :         428 :         return found->second;
     203                 :             :     }
     204                 :             : 
     205                 :          32 :     void ParseId(const std::string &text, AttributeAssignment &assignment) {
     206                 :          32 :         const std::int64_t raw = ParseNumber<std::int64_t>(text);
     207   [ +  +  +  +  :          32 :         if (raw < 0 || raw > std::numeric_limits<std::uint32_t>::max() || (raw & 0x60000000U) != 0)
             +  +  +  + ]
     208         [ +  - ]:           6 :             throw std::runtime_error("Invalid attribute CAN ID.");
     209                 :          26 :         const std::uint32_t id = static_cast<std::uint32_t>(raw);
     210                 :          26 :         assignment.canId = id & 0x1fffffffU;
     211   [ +  +  -  + ]:          26 :         assignment.isExtended = (id & 0x80000000U) != 0U || id > 0x7ffU;
     212                 :          26 :     }
     213                 :             : 
     214                 :          15 :     AttributeMap &Target(DbcDatabase &database, const AttributeAssignment &assignment) {
     215         [ +  + ]:          15 :         if (assignment.scope == AttributeScope::Database) return database.attributes;
     216         [ +  + ]:          16 :         for (FrameInfo &frame: database.frames) {
     217   [ +  +  +  + ]:          14 :             if (frame.canId != assignment.canId || frame.isExtended != assignment.isExtended) continue;
     218         [ +  + ]:          11 :             if (assignment.scope == AttributeScope::Frame) return frame.attributes;
     219         [ +  + ]:           3 :             for (SignalInfo &signal: frame.signals) {
     220         [ +  - ]:           1 :                 if (signal.name == assignment.targetName) return signal.attributes;
     221                 :             :             }
     222   [ +  -  +  - ]:           2 :             throw std::runtime_error("Unresolved attribute signal: " + assignment.targetName);
     223                 :             :         }
     224         [ +  - ]:           2 :         throw std::runtime_error("Unresolved attribute frame.");
     225                 :             :     }
     226                 :             : 
     227                 :          36 :     void ValidateCycleTime(const std::string &name, const AttributeValue &value) {
     228         [ +  + ]:          36 :         if (name != "GenMsgCycleTime") return;
     229                 :           8 :         const std::int64_t milliseconds = std::get<std::int64_t>(value);
     230   [ +  +  +  +  :           8 :         if (milliseconds < 0 || milliseconds > std::numeric_limits<std::uint32_t>::max())
                   +  + ]
     231         [ +  - ]:           4 :             throw std::runtime_error("GenMsgCycleTime must fit unsigned 32-bit milliseconds.");
     232                 :             :     }
     233                 :             : 
     234                 :          15 :     void ApplyDefault(DbcDatabase &database, const std::string &name, const AttributeDefinition &definition) {
     235         [ -  + ]:          15 :         if (!definition.defaultValue) return;
     236         [ +  + ]:          15 :         if (definition.scope == AttributeScope::Database) {
     237                 :           5 :             database.attributes[name] = *definition.defaultValue;
     238                 :           5 :             return;
     239                 :             :         }
     240   [ +  +  -  + ]:          10 :         if (definition.scope != AttributeScope::Frame && definition.scope != AttributeScope::Signal)
     241                 :           0 :             return;
     242         [ +  + ]:          28 :         for (FrameInfo &frame: database.frames) {
     243   [ +  +  +  -  :          18 :             if (definition.scope == AttributeScope::Frame) frame.attributes[name] = *definition.defaultValue;
                   +  - ]
     244         [ +  + ]:          18 :             if (definition.scope == AttributeScope::Signal) {
     245   [ +  -  +  -  :           6 :                 for (SignalInfo &signal: frame.signals) signal.attributes[name] = *definition.defaultValue;
                   +  + ]
     246                 :             :             }
     247                 :             :         }
     248                 :             :     }
     249                 :             : }// namespace
     250                 :             : 
     251                 :         621 : void DbcAttributeHandler::Handle(const std::string &line, DbcParseContext &context) const {
     252                 :         621 :     context.pendingAttributes.push_back({line, context.lineNumber});
     253   [ +  -  +  -  :        1242 : }
                   -  - ]
     254                 :             : 
     255                 :         549 : void DbcParseContext::ResolveAttributes(const std::string &filePath, std::vector<DbcDiagnostic> &diagnostics) {
     256                 :         549 :     std::map<std::string, std::size_t> defaultLines;
     257                 :         549 :     std::vector<std::size_t> assignmentLines;
     258                 :             :     // Resolve each phase independently of source ordering.
     259                 :         549 :     const char *keywords[] = {"BA_DEF_", "BA_DEF_DEF_", "BA_"};
     260         [ +  + ]:        1340 :     for (const char *phase: keywords) {
     261         [ +  + ]:        2094 :         for (const PendingAttribute &pending: pendingAttributes) {
     262         [ +  - ]:        1303 :             const std::string keyword = pending.statement.substr(0U, pending.statement.find_first_of(" \t"));
     263   [ +  -  +  + ]:        1303 :             if (keyword != phase) continue;
     264                 :             :             try {
     265         [ +  + ]:         621 :                 AttributeTokens tokens(pending.statement);
     266         [ +  - ]:         611 :                 tokens.Word();
     267   [ +  -  +  + ]:         611 :                 if (keyword == "BA_DEF_") {
     268         [ +  + ]:         393 :                     Define(tokens, database);
     269                 :         207 :                     continue;
     270                 :             :                 }
     271         [ +  - ]:         218 :                 const std::string name = tokens.Quoted();
     272         [ +  + ]:         218 :                 AttributeDefinition &definition = FindDefinition(database, name);
     273   [ +  -  +  + ]:         214 :                 if (keyword == "BA_DEF_DEF_") {
     274   [ +  +  +  -  :          99 :                     if (definition.defaultValue) throw std::runtime_error("Duplicate attribute default: " + name);
                   +  - ]
     275   [ +  -  +  + ]:         177 :                     definition.defaultValue = Value(tokens.Take(), definition, true);
     276         [ +  + ]:          17 :                     ValidateCycleTime(name, *definition.defaultValue);
     277         [ +  - ]:          15 :                     tokens.End();
     278         [ +  - ]:          15 :                     defaultLines[name] = pending.lineNumber;
     279         [ +  - ]:          15 :                     ApplyDefault(database, name, definition);
     280                 :          15 :                     continue;
     281                 :             :                 }
     282                 :         115 :                 AttributeAssignment assignment;
     283         [ +  - ]:         115 :                 assignment.name = name;
     284         [ +  - ]:         115 :                 Token value = tokens.Take();
     285   [ +  -  +  -  :         115 :                 if (!value.quoted && IsScope(value.text)) {
             +  +  +  + ]
     286         [ +  - ]:          34 :                     assignment.scope = ParseScope(value.text);
     287   [ +  +  +  + ]:          34 :                     if (assignment.scope == AttributeScope::Frame || assignment.scope == AttributeScope::Signal)
     288   [ +  -  +  + ]:          38 :                         ParseId(tokens.Word(), assignment);
     289         [ +  + ]:          28 :                     if (assignment.scope != AttributeScope::Frame)
     290         [ +  - ]:           7 :                         assignment.targetName = tokens.Word();
     291         [ +  - ]:          28 :                     value = tokens.Take();
     292                 :             :                 }
     293   [ +  +  +  - ]:         109 :                 if (assignment.scope != definition.scope) throw std::runtime_error("Attribute assignment scope differs from definition.");
     294         [ +  + ]:         107 :                 assignment.value = Value(value, definition, false);
     295         [ +  + ]:          19 :                 ValidateCycleTime(name, assignment.value);
     296         [ +  - ]:          17 :                 tokens.End();
     297                 :             :                 // Repeated explicit assignments use the last value, matching comments.
     298   [ +  +  +  + ]:          17 :                 if (assignment.scope != AttributeScope::Node && assignment.scope != AttributeScope::Environment)
     299   [ +  +  +  -  :          15 :                     Target(database, assignment)[name] = assignment.value;
                   +  - ]
     300         [ +  - ]:          13 :                 database.attributeAssignments.push_back(assignment);
     301         [ +  - ]:          13 :                 assignmentLines.push_back(pending.lineNumber);
     302   [ +  +  +  +  :        1406 :             } catch (const std::exception &error) {
                   -  + ]
     303   [ +  -  +  - ]:        1158 :                 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidAttribute, filePath, pending.lineNumber, error.what());
     304                 :         386 :             }
     305         [ +  + ]:        1303 :         }
     306                 :             :     }
     307         [ +  + ]:         342 :     for (FrameInfo &frame: database.frames) {
     308         [ +  + ]:         191 :         for (const AttributeMap::value_type &attribute: frame.attributes) {
     309   [ +  -  +  +  :          16 :             if (attribute.first != "VFrameFormat" && attribute.first != "GenMsgCycleTime") continue;
          +  -  +  +  +  
                      + ]
     310         [ +  - ]:           9 :             std::size_t line = defaultLines[attribute.first];
     311         [ +  + ]:          37 :             for (std::size_t index = 0U; index < database.attributeAssignments.size(); ++index) {
     312                 :          28 :                 const AttributeAssignment &assignment = database.attributeAssignments[index];
     313         [ +  - ]:          40 :                 if (assignment.name == attribute.first && assignment.scope == AttributeScope::Frame &&
     314   [ +  +  +  +  :          40 :                     assignment.canId == frame.canId && assignment.isExtended == frame.isExtended)
             +  +  +  + ]
     315                 :           5 :                     line = assignmentLines[index];
     316                 :             :             }
     317         [ +  - ]:           9 :             const std::int64_t value = std::get<std::int64_t>(attribute.second);
     318   [ +  -  +  + ]:           9 :             if (attribute.first == "GenMsgCycleTime") {
     319                 :           4 :                 frame.cycleTimeMs = static_cast<std::uint32_t>(value);
     320                 :           4 :                 continue;
     321                 :             :             }
     322   [ +  -  +  - ]:           5 :             frame.frameFormat = database.attributeDefinitions.at(attribute.first).enumValues[static_cast<std::size_t>(value)];
     323                 :           5 :             const std::string &format = *frame.frameFormat;
     324                 :           5 :             std::string warning;
     325   [ +  -  +  +  :           5 :             if (format != "StandardCAN" && format != "ExtendedCAN")
          +  -  +  +  +  
                      + ]
     326   [ +  -  +  - ]:           2 :                 warning = "Unsupported VFrameFormat '" + format + "' is preserved; decoding may be incomplete.";
     327   [ +  -  +  + ]:           3 :             else if ((format == "ExtendedCAN") != frame.isExtended)
     328   [ +  -  +  - ]:           1 :                 warning = "VFrameFormat '" + format + "' conflicts with the BO_ CAN ID; ID interpretation is unchanged.";
     329         [ +  + ]:           5 :             if (!warning.empty())
     330                 :           3 :                 diagnostics.push_back({DbcDiagnosticSeverity::Warning, DbcDiagnosticCode::UnsupportedAttribute, filePath, line, warning});
     331                 :           5 :         }
     332                 :             :     }
     333   [ +  -  +  -  :         938 : }
             +  -  -  - ]
        

Generated by: LCOV version 2.0-1