diff --git a/tools/svdconv/SVDConv/README.md b/tools/svdconv/SVDConv/README.md index 3ad89c2db..1b10a9737 100644 --- a/tools/svdconv/SVDConv/README.md +++ b/tools/svdconv/SVDConv/README.md @@ -48,6 +48,14 @@ due to backward compatibility reasons. All newly developed/updated SVD files sho in the same directory as `svdconv.exe`. On Linux and macOS, SVDConv reports error M133 and returns exit code 2. The intermediate `.sfd` file may still be generated. Use `--generate=sfd` to generate SFD output on any supported platform. +SVDConv supports Cortex-M devices with register and field ranges within 32 bits. Wider intermediate arithmetic +prevents overflow; it does not add support for 64-bit input or output. SFD/SFR rejects wider ranges with M226 before +SFD file creation. SFR compilation is skipped when SFD generation fails. + +Dimension expansion has a cumulative limit of 1,000,000 generated indices and model copies per input file. +Nested arrays, inherited content, and repeated expansion share this limit. Exceeding it reports M249 and stops +processing that input before any output generator runs. This bounds expansion work, not the XML file size or total +process memory. Invalid dimension index ranges and overflowing expansion addresses report M250. ## Return Codes @@ -287,6 +295,8 @@ The following table shows the errors and warnings issued by svdconv. | M241 | ERROR | Parse Error: 'VALUE' invalid for Array generation | Correct SVD.| | M242 | WARNING | 'LEVEL' 'NAME' `` found, but no `` | Correct SVD.| | M243 | WARNING | 'LEVEL' 'NAME' `` found, but `` does not describe an array | Correct SVD.| +| M249 | ERROR | Dimension expansion exceeds the per-input limit of 'NUM' generated indices and model copies. | Reduce the total expanded model size.| +| M250 | ERROR | Dimension 'NAME' has an invalid or overflowing index/address range. | Correct the dimension range or increment.| ### Data Check Errors diff --git a/tools/svdconv/SVDConv/src/SVDConv.cpp b/tools/svdconv/SVDConv/src/SVDConv.cpp index af71e11de..aa030644f 100644 --- a/tools/svdconv/SVDConv/src/SVDConv.cpp +++ b/tools/svdconv/SVDConv/src/SVDConv.cpp @@ -253,6 +253,13 @@ SVD_ERR SvdConv::CheckSvdFile() if(success) { LogMsg("M040", NAME("Deleting XML Tree"), TIME(t2)); } else { LogMsg("M111", NAME("Deleting XML Tree")); } + // A partial model from an exhausted expansion budget must never reach a generator. + if(m_svdModel->HasExpansionError()) { + delete m_svdModel; + m_svdModel = nullptr; + return SVD_ERR_VERIFY; + } + // ---------------------- Calculate Model ---------------------- t1 = CrossPlatformUtils::ClockInMsec(); success = m_svdModel->CalculateModel(); @@ -334,6 +341,7 @@ SVD_ERR SvdConv::CheckSvdFile() // ---------------------- Generate SFD File ---------------------- if(m_svdOptions.IsGenerateSfd()) { t1 = CrossPlatformUtils::ClockInMsec(); + success = false; if(device) { generator->SetSvdFileName(path); generator->SetProgramInfo(version, descr, copyright); @@ -346,7 +354,7 @@ SVD_ERR SvdConv::CheckSvdFile() } // ---------------------- Generate SFR File ---------------------- - if(m_svdOptions.IsGenerateSfr()) { + if(m_svdOptions.IsGenerateSfr() && success) { t1 = CrossPlatformUtils::ClockInMsec(); if(device) { generator->SetSvdFileName(path); diff --git a/tools/svdconv/SVDConv/src/SVDConv_Msgs.cpp b/tools/svdconv/SVDConv/src/SVDConv_Msgs.cpp index 7344d361e..78fafc55e 100644 --- a/tools/svdconv/SVDConv/src/SVDConv_Msgs.cpp +++ b/tools/svdconv/SVDConv/src/SVDConv_Msgs.cpp @@ -114,6 +114,9 @@ const MsgTable SvdConv::msgTable = { { "M247", { MsgLevel::LEVEL_ERROR, CRLF_B, "C Expression: Error occurred during generation!" } }, { "M248", { MsgLevel::LEVEL_ERROR, CRLF_B, "C Expression: Referenced Object must be Register or Field!" } }, + { "M249", { MsgLevel::LEVEL_ERROR, CRLF_B, "Dimension expansion exceeds the per-input limit of %NUM% generated indices and model copies." } }, + { "M250", { MsgLevel::LEVEL_ERROR, CRLF_B, "Dimension '%NAME%' has an invalid or overflowing index/address range." } }, + // 300... Data Check Errors { "M300", { MsgLevel::LEVEL_ERROR, CRLF_B, "" } }, diff --git a/tools/svdconv/SVDGenerator/include/SfdData.h b/tools/svdconv/SVDGenerator/include/SfdData.h index fabd2aa77..e5a9b9faa 100644 --- a/tools/svdconv/SVDGenerator/include/SfdData.h +++ b/tools/svdconv/SVDGenerator/include/SfdData.h @@ -73,6 +73,9 @@ class SfdData { bool IsValid (SvdItem* item); private: + static bool CheckBitRange (SvdItem* item); + static bool CheckBitRanges (SvdItem* item); + const SvdOptions& m_options; FileIo* m_fileIo; SfdGenerator* m_gen; diff --git a/tools/svdconv/SVDGenerator/src/HeaderData_Field.cpp b/tools/svdconv/SVDGenerator/src/HeaderData_Field.cpp index 473a62870..219b3912c 100644 --- a/tools/svdconv/SVDGenerator/src/HeaderData_Field.cpp +++ b/tools/svdconv/SVDGenerator/src/HeaderData_Field.cpp @@ -40,7 +40,9 @@ bool HeaderData::CreateFields(SvdRegister* reg, string structName) FIELDMAPLIST sortedFields; AddFields(fieldCont, sortedFields); - CreateSortedFields(sortedFields, regSize, structName); + if(!CreateSortedFields(sortedFields, regSize, structName)) { + return false; + } } return true; @@ -74,10 +76,11 @@ bool HeaderData::AddFields(SvdItem* container, FIELDMAPLIST& sortedFields) } } - const auto name = field->GetNameCalculated(); - uint32_t offs = (uint32_t) field->GetOffset(); - uint32_t width = (uint32_t) field->GetBitWidth(); - uint32_t mask = ((1<GetOffset(), field->GetBitWidth(), offset, bitWidth)) { + continue; + } + const auto mask = SvdUtils::CreateBitMask32(bitWidth, offset); ONESTRUCT* oneStruct = nullptr; for(auto& oneStr : sortedFields) { @@ -93,8 +96,8 @@ bool HeaderData::AddFields(SvdItem* container, FIELDMAPLIST& sortedFields) oneStruct = &*sortedFields.rbegin(); } + oneStruct->fields.emplace(offset, field); oneStruct->mask |= mask; - oneStruct->fields[offs] = field; } return true; @@ -104,20 +107,30 @@ bool HeaderData::AddFields(SvdItem* container, FIELDMAPLIST& sortedFields) bool HeaderData::CreateSortedFields(const FIELDMAPLIST& sortedFields, uint32_t regSize, string structName) { m_reservedFieldCnt = 0; + if(regSize > sizeof(uint32_t)) { + m_gen->Generate("Register bitfields exceed 32 bits", -1); + return false; + } + const auto registerBits = regSize * 8; for(const auto& oneStruct : sortedFields) { uint32_t offsCnt = 0; m_gen->Generate(""); for(const auto& [newOffs, field] : oneStruct.fields) { - int32_t res = newOffs - offsCnt; - - GenerateReservedField(res, regSize); - offsCnt += res; - offsCnt += CreateField(field, regSize, offsCnt); + if(newOffs < offsCnt || newOffs > registerBits) { + m_gen->Generate("Reserved bits calculation error", -1); + return false; + } + GenerateReservedField(newOffs - offsCnt, regSize); + const auto fieldWidth = CreateField(field, regSize, newOffs); + if(fieldWidth > registerBits - newOffs) { + m_gen->Generate("Reserved bits calculation error", -1); + return false; + } + offsCnt = newOffs + fieldWidth; } - - GenerateReservedField((regSize * 8) - offsCnt, regSize); // fill up (pad) bitfields + GenerateReservedField(registerBits - offsCnt, regSize); // fill up (pad) bitfields m_gen->Generate("%s", structName.c_str()); } @@ -131,27 +144,23 @@ uint32_t HeaderData::CreateField(SvdField* field, uint32_t regSize, uint32_t off return false; } - uint32_t sizeNeeded = 0; - const auto& dataType = SvdUtils::GetDataTypeString(regSize); const auto name = field->GetNameCalculated(); const auto descr = field->GetDescriptionCalculated(); const auto accType = field->GetEffectiveAccess(); - uint32_t fieldWidth = field->GetBitWidth(); - uint32_t fieldPos = (uint32_t)field->GetOffset(); + uint32_t fieldPos, fieldWidth; + if(!SvdUtils::GetBitRange32(field->GetOffset(), field->GetBitWidth(), fieldPos, fieldWidth)) { + return 0; + } m_gen->Generate("%s", accType, dataType.c_str(), regSize, fieldWidth, name.c_str()); - uint32_t lastBit = fieldPos + fieldWidth -1; + const auto lastBit = fieldPos + fieldWidth -1; m_gen->Generate("%s", fieldPos, lastBit, !descr.empty()? descr.c_str() : name.c_str()); - if(sizeNeeded < fieldWidth) { - sizeNeeded = fieldWidth; - } - if(offsCnt != fieldPos) { m_gen->Generate("Reserved bits calculation error", -1); } - return sizeNeeded; + return fieldWidth; } diff --git a/tools/svdconv/SVDGenerator/src/HeaderData_PosMask.cpp b/tools/svdconv/SVDGenerator/src/HeaderData_PosMask.cpp index 5f27e8444..90f058f54 100644 --- a/tools/svdconv/SVDGenerator/src/HeaderData_PosMask.cpp +++ b/tools/svdconv/SVDGenerator/src/HeaderData_PosMask.cpp @@ -218,9 +218,11 @@ bool HeaderData::CreateFieldPosMask(SvdField* field, PosMaskNames *posMaskNames) const auto& alternateGroup = posMaskNames->alternate; const auto name = field->GetNameCalculated(); const auto fieldName = field->GetHierarchicalNameResulting(); - uint32_t firstBit = (uint32_t)field->GetOffset(); - uint32_t bitWidth = field->GetEffectiveBitWidth(); - uint32_t bitMaxNum = (uint32_t) ((((uint64_t)(1) << bitWidth) -1)); + uint32_t firstBit, bitWidth; + if(!SvdUtils::GetBitRange32(field->GetOffset(), field->GetEffectiveBitWidth(), firstBit, bitWidth)) { + return false; + } + const auto bitMaxNum = SvdUtils::CreateBitMask32(bitWidth); if(!alternateGroup.empty()) m_gen->Generate("%s_%s", name.c_str(), firstBit, bitMaxNum, fieldName.c_str(), alternateGroup.c_str()); diff --git a/tools/svdconv/SVDGenerator/src/SfdData.cpp b/tools/svdconv/SVDGenerator/src/SfdData.cpp index 92f49690b..69f1f21f8 100644 --- a/tools/svdconv/SVDGenerator/src/SfdData.cpp +++ b/tools/svdconv/SVDGenerator/src/SfdData.cpp @@ -10,9 +10,65 @@ #include "SvdItem.h" #include "FileIo.h" #include "SvdDevice.h" +#include "SvdDimension.h" +#include "SvdField.h" +#include "SvdPeripheral.h" +#include "SvdRegister.h" +#include "SvdUtils.h" +#include "ErrLog.h" using namespace std; +bool SfdData::CheckBitRange(SvdItem* item) +{ + const auto reg = dynamic_cast(item); + const auto field = dynamic_cast(item); + if(reg || field) { + uint32_t offset, width; + if(!SvdUtils::GetBitRange32(field ? field->GetOffset() : 0, item->GetEffectiveBitWidth(), offset, width)) { + LogMsg("M226", MSG("SFD supports only bit ranges within 32 bits: '" + item->GetNameCalculated() + "'"), item->GetLineNumber()); + return false; + } + } + return true; +} + +bool SfdData::CheckBitRanges(SvdItem* item) +{ + if(!item->IsValid()) { + return true; + } + if(!CheckBitRange(item)) { + return false; + } + + const auto peripheral = dynamic_cast(item); + const auto condition = peripheral ? peripheral->GetDisableCondition() : nullptr; + if(condition && condition->IsValid()) { + for(const auto& symbol : condition->GetSymbolsList()) { + if(symbol.token.type != SvdCExpressionParser::xme_identi) { + continue; + } + const auto referenced = symbol.svdItem; + if(!referenced || !referenced->IsValid()) { + LogMsg("M226", MSG("Invalid item referenced by an SFD expression: '" + condition->GetText() + "'"), condition->GetLineNumber()); + return false; + } + if(!CheckBitRange(referenced)) { + return false; + } + } + } + + for(const auto child : item->GetChildren()) { + if(!CheckBitRanges(child)) { + return false; + } + } + const auto dim = item->GetDimension(); + return !dim || CheckBitRanges(dim); +} + SfdData::SfdData(const FileHeaderInfo &fileHeaderInfo, const SvdOptions& options): m_options(options), @@ -45,10 +101,21 @@ bool SfdData::Create(SvdItem *item, const string &fileName) return false; } - m_fileIo->Create(fileName); - const auto device = dynamic_cast(item); - if(!device) { + if(!device || !CheckBitRanges(device)) { + return false; + } + for(const auto& [name, reg] : device->GetExpressionRegistersList()) { + if(!reg || !reg->IsValid()) { + LogMsg("M226", MSG("Invalid register referenced by an SFD expression: '" + name + "'")); + return false; + } + if(!CheckBitRanges(reg)) { + return false; + } + } + + if(!m_fileIo->Create(fileName)) { return false; } diff --git a/tools/svdconv/SVDGenerator/src/SfdData_SingleItems.cpp b/tools/svdconv/SVDGenerator/src/SfdData_SingleItems.cpp index 68481d1d0..b7c610ccb 100644 --- a/tools/svdconv/SVDGenerator/src/SfdData_SingleItems.cpp +++ b/tools/svdconv/SVDGenerator/src/SfdData_SingleItems.cpp @@ -768,7 +768,7 @@ bool SfdData::CreateRegisterItem(SvdRegister* reg) if(cont && cont->IsValid() && cont->GetChildCount()) { list fieldsList; CreateFields(cont, fieldsList); - const auto accMaskReadRtree = (uint32_t) reg->GetAccessMask(); + const auto accMaskReadRtree = reg->GetAccessMask(); CreateItemDescription(reg, "RTree"); m_gen->Generate("SFDITEM_REG__%s", itemName.c_str()); @@ -873,10 +873,14 @@ bool SfdData::CreateFieldItem(SvdField* field) const auto dispName = field->GetDisplayNameCalculated(); const auto fieldDescr = SvdUtils::CheckTextGeneric_SfrCC2(field->GetDescriptionCalculated()); const auto regName = field->GetParentRegisterNameHierarchical(); - const auto firstBit = (uint32_t)field->GetOffset(); - const auto bitWidth = (uint32_t)field->GetEffectiveBitWidth(); const auto accessType = field->GetEffectiveAccess(); const auto regAddress = (uint32_t)field->GetAbsoluteAddress(); + uint32_t firstBit = 0; + uint32_t bitWidth = 0; + if(!SvdUtils::GetBitRange32(field->GetOffset(), field->GetEffectiveBitWidth(), firstBit, bitWidth)) { + LogMsg("M226", MSG("SFD supports only bit ranges within 32 bits: '" + fieldName + "'"), field->GetLineNumber()); + return false; + } uint32_t regBitWidth = 32; const auto parent = field->GetParent(); @@ -894,8 +898,8 @@ bool SfdData::CreateFieldItem(SvdField* field) field->GetValuesDescriptionString(valuesDescr); valuesDescr = SvdUtils::CheckTextGeneric_SfrCC2(valuesDescr); - uint32_t lastBit = firstBit + bitWidth -1; - uint32_t bitMaxNum = (uint32_t) (((uint64_t)(1) << bitWidth) -1); + const auto lastBit = firstBit + bitWidth -1; + const auto bitMaxNum = SvdUtils::CreateBitMask32(bitWidth); CreateItemDescription(field, "Item"); m_gen->Generate("SFDITEM_FIELD__%s", itemName.c_str()); @@ -1011,6 +1015,11 @@ bool SfdData::CreateEnumValueSet(SvdEnumContainer *cont, SvdField* field) return true; } + const auto bitWidth = field->GetEffectiveBitWidth(); + if(bitWidth == 0 || bitWidth > MAX_BITWIDTH_FOR_COMBO) { + return false; + } + const auto defaultValue = cont->GetDefaultValue(); map enumValues; @@ -1028,8 +1037,7 @@ bool SfdData::CreateEnumValueSet(SvdEnumContainer *cont, SvdField* field) enumValues[val.u32] = enu; } - const auto bitWidth = field->GetEffectiveBitWidth(); - const auto bitMaxNum = (uint32_t)((1UL << (uint64_t)bitWidth)); + const uint32_t bitMaxNum = 1U << bitWidth; for(uint32_t bitNum=0; bitNumCreate(device, fileName); + const bool success = sfdData->Create(device, fileName); delete sfdData; - return true; + return success; } bool SvdGenerator::SfrFile(SvdDevice *device, const string &path) diff --git a/tools/svdconv/SVDModel/include/SvdDimension.h b/tools/svdconv/SVDModel/include/SvdDimension.h index f4e13eb67..a5619ba7a 100644 --- a/tools/svdconv/SVDModel/include/SvdDimension.h +++ b/tools/svdconv/SVDModel/include/SvdDimension.h @@ -95,8 +95,9 @@ class SvdDimension : public SvdItem bool SetDimIndexList (const std::list& dimIndexList) { m_dimIndexList = dimIndexList; return true; } bool SetDimName (const std::string& dimName ) { m_dimName = dimName ; return true; } - int32_t GetAddressBitsUnits (); - int32_t CalcAddressIncrement (); + uint32_t GetAddressBitsUnits (); + uint64_t CalcAddressIncrement (); + bool CheckExpansionRange (uint64_t start, uint64_t increment); bool IsTagAllowed (const std::string& tag); bool InitAllowedTags (); diff --git a/tools/svdconv/SVDModel/include/SvdItem.h b/tools/svdconv/SVDModel/include/SvdItem.h index 1e026383a..3882e2d7c 100644 --- a/tools/svdconv/SVDModel/include/SvdItem.h +++ b/tools/svdconv/SVDModel/include/SvdItem.h @@ -121,6 +121,11 @@ class SvdItem : public SvdElement { static const uint32_t VALUE32_NOT_INIT; static const uint64_t VALUE64_NOT_INIT; + // Cumulative expansion work per input tree, including indices and copied items. + static constexpr size_t MAX_EXPANSION_ITEMS = 1000000; + bool ReserveExpansion (size_t count); + bool HasExpansionError () const; + virtual bool Construct (XMLTreeElement* xmlElement); virtual bool ProcessXmlChildren (XMLTreeElement* xmlElement); @@ -249,6 +254,8 @@ class SvdItem : public SvdElement { std::string m_description; std::map m_attributes; + size_t m_expansionItems = 0; + bool m_expansionError = false; }; @@ -299,7 +306,10 @@ bool SvdItem::GetDeriveItem(T *&item, const std::list& searchName, switch(lv) { case L_Peripheral: case L_Register: - parent = *(childs.begin()); + if(childs.empty()) { + return false; + } + parent = childs.front(); break; default: break; diff --git a/tools/svdconv/SVDModel/include/SvdRegister.h b/tools/svdconv/SVDModel/include/SvdRegister.h index 7fe478737..bec5d7e77 100644 --- a/tools/svdconv/SVDModel/include/SvdRegister.h +++ b/tools/svdconv/SVDModel/include/SvdRegister.h @@ -84,7 +84,7 @@ class SvdRegister : public SvdItem bool CalcAccessMask (); uint64_t GetAccessMaskRead (); uint64_t GetAccessMaskWrite (); - uint64_t GetAccessMask (); + uint32_t GetAccessMask (); SvdTypes::Access GetAccessCalculated (); bool CheckEnumeratedValues (); bool AddToMap (SvdEnum *enu, std::map &map); diff --git a/tools/svdconv/SVDModel/include/SvdUtils.h b/tools/svdconv/SVDModel/include/SvdUtils.h index ad243bb7e..c162d515e 100644 --- a/tools/svdconv/SVDModel/include/SvdUtils.h +++ b/tools/svdconv/SVDModel/include/SvdUtils.h @@ -56,6 +56,12 @@ class SvdUtils { static bool ConvertNumberXBin (const std::string &text, std::set& numbers); static bool ConvertBitRange (const std::string &text, uint32_t& m_offset, uint32_t& m_size); + // Validate model values before converting them to a 32-bit output range. + static bool GetBitRange32 (uint64_t rawOffset, int64_t rawWidth, uint32_t& offset, uint32_t& width); + // Returns zero when the bit range is empty or does not fit in 32 bits. + static uint32_t CreateBitMask32 (uint32_t width, uint32_t offset = 0); + // Returns zero when the bit range is empty or does not fit in 64 bits. + static uint64_t CreateBitMask (int64_t width, uint64_t offset = 0); static bool DoConvertNumberXBin (const std::string &text, uint32_t value, std::set& numbers); static bool ConvertCpuRevision (const std::string &text, uint32_t& rev); static bool ConvertProtectionStringType (const std::string &text, SvdTypes::ProtectionType& protection, uint32_t lineNo); diff --git a/tools/svdconv/SVDModel/src/SvdCExpression.cpp b/tools/svdconv/SVDModel/src/SvdCExpression.cpp index 3ec913b68..634e79f67 100644 --- a/tools/svdconv/SVDModel/src/SvdCExpression.cpp +++ b/tools/svdconv/SVDModel/src/SvdCExpression.cpp @@ -122,11 +122,15 @@ string SvdCExpression::GetExpressionString() for(const auto& symbol : symbols) { if(symbol.token.type == SvdCExpressionParser::xme_identi) { SvdItem* item = symbol.svdItem; - if(!item) { - return SvdUtils::EMPTY_STRING; // skip generating disable condition. Should not be reached, as LinkSymbols() already generates an error + if(!item || !item->IsValid()) { + return SvdUtils::EMPTY_STRING; } - expression += CreateObjectExpression(item); + const auto operand = CreateObjectExpression(item); + if(operand.empty()) { + return SvdUtils::EMPTY_STRING; + } + expression += operand; } else { expression += symbol.token.text; @@ -174,9 +178,11 @@ string SvdCExpression::CreateFieldExpression(SvdItem* item) } const auto regName = field->GetParentRegisterNameHierarchical(); - const auto firstBit = (uint32_t)field->GetOffset(); - const auto bitWidth = field->GetEffectiveBitWidth(); - const auto bitMaxNum = (uint32_t) ((((uint64_t)(1) << bitWidth) -1)); + uint32_t firstBit, bitWidth; + if(!field->IsValid() || !SvdUtils::GetBitRange32(field->GetOffset(), field->GetEffectiveBitWidth(), firstBit, bitWidth)) { + return SvdUtils::EMPTY_STRING; + } + const auto bitMaxNum = SvdUtils::CreateBitMask32(bitWidth); std::stringstream ss; ss << "0x" << std::hex << bitMaxNum; diff --git a/tools/svdconv/SVDModel/src/SvdCluster.cpp b/tools/svdconv/SVDModel/src/SvdCluster.cpp index 3b5cefd36..38d55020b 100644 --- a/tools/svdconv/SVDModel/src/SvdCluster.cpp +++ b/tools/svdconv/SVDModel/src/SvdCluster.cpp @@ -279,7 +279,9 @@ bool SvdCluster::CalculateDim() dim->ClearChildren(); } - dim->CalculateDim(); + if(!dim->CalculateDim() || !dim->CheckExpansionRange(GetOffset(), dim->GetDimIncrement())) { + return false; + } const auto& dimIndexList = dim->GetDimIndexList(); auto offset = GetOffset(); const auto bitWidth = GetBitWidth(); @@ -287,10 +289,18 @@ bool SvdCluster::CalculateDim() uint32_t dimElementIndex = 0; for(const auto& dimIndexname : dimIndexList) { + if(!dim->ReserveExpansion(1)) { + return false; + } const auto newClust = new SvdCluster(dim); dim->AddItem(newClust); - CopyChilds(this, newClust); + if(!CopyChilds(this, newClust)) { + return false; + } newClust->CopyItem(this); + if(HasExpansionError()) { + return false; + } newClust->SetName(dim->CreateName(dimIndexname)); newClust->SetDisplayName(dim->CreateDisplayName(dimIndexname)); newClust->SetDescription(dim->CreateDescription(dimIndexname)); @@ -298,7 +308,9 @@ bool SvdCluster::CalculateDim() newClust->SetBitWidth(bitWidth); newClust->SetDimElementIndex(dimElementIndex++); - offset += dim->GetDimIncrement(); + if(dimElementIndex < dimIndexList.size()) { + offset += dim->GetDimIncrement(); + } if(dimElementIndex < 8) { if(!dimIndexText.empty()) { diff --git a/tools/svdconv/SVDModel/src/SvdDerivedFrom.cpp b/tools/svdconv/SVDModel/src/SvdDerivedFrom.cpp index d02e73b2d..add8a9616 100644 --- a/tools/svdconv/SVDModel/src/SvdDerivedFrom.cpp +++ b/tools/svdconv/SVDModel/src/SvdDerivedFrom.cpp @@ -116,10 +116,15 @@ bool SvdDerivedFrom::DeriveItem(SvdItem *from) SetDerivedFromItem(from); // Also copy all element's childs - CopyChilds(from, parent); + if(!CopyChilds(from, parent)) { + return false; + } // Create a copy parent->CopyItem(from); + if(HasExpansionError()) { + return false; + } const auto dim = parent->GetDimension(); if(dim) { diff --git a/tools/svdconv/SVDModel/src/SvdDimension.cpp b/tools/svdconv/SVDModel/src/SvdDimension.cpp index 684d51e71..779204ead 100644 --- a/tools/svdconv/SVDModel/src/SvdDimension.cpp +++ b/tools/svdconv/SVDModel/src/SvdDimension.cpp @@ -10,6 +10,8 @@ #include "XMLTree.h" #include "ErrLog.h" +#include + using namespace std; map > SvdDimension::m_allowedTagsDim; @@ -180,7 +182,14 @@ bool SvdDimension::Construct(XMLTreeElement* xmlElement) bool SvdDimension::CalculateDimIndex() { - const auto lineNo = GetParent()->GetLineNumber(); + const auto parent = GetParent(); + const auto lineNo = parent ? parent->GetLineNumber() : GetLineNumber(); + if(m_dim == 0 || m_dim == SvdItem::VALUE32_NOT_INIT) { + return false; + } + if(m_dim > MAX_EXPANSION_ITEMS) { + return ReserveExpansion(m_dim); + } if(m_dim == 1) { const auto& name = GetExpression()->GetName(); @@ -188,6 +197,8 @@ bool SvdDimension::CalculateDimIndex() } m_dimIndexList.clear(); + m_from.clear(); + m_to.clear(); // Read the Value const auto& value = GetDimIndex(); @@ -201,7 +212,9 @@ bool SvdDimension::CalculateDimIndex() } m_to = value.substr(pos, value.length()); SvdUtils::TrimWhitespace(m_to); - CalculateDimIndexFromTo(); + if(!CalculateDimIndexFromTo()) { + return false; + } } else { string index = ""; @@ -211,6 +224,10 @@ bool SvdDimension::CalculateDimIndex() } if(val == ',') { if(!index.empty()) { + if(!ReserveExpansion(1)) { + m_dimIndexList.clear(); + return false; + } m_dimIndexList.push_back(index); } index = ""; @@ -220,6 +237,10 @@ bool SvdDimension::CalculateDimIndex() } } if(!index.empty()) { + if(!ReserveExpansion(1)) { + m_dimIndexList.clear(); + return false; + } m_dimIndexList.push_back(index); // last one } } @@ -246,7 +267,14 @@ bool SvdDimension::CalculateDimIndexFromTo() return true; } - int32_t numFrom=0, numTo=0; + if(m_dim == 0 || m_dim == SvdItem::VALUE32_NOT_INIT) { + return false; + } + if(m_dim > MAX_EXPANSION_ITEMS) { + return ReserveExpansion(m_dim); + } + + uint32_t numFrom=0, numTo=0; bool numOk = true; if(!m_from.empty()) { if(!SvdUtils::ConvertNumber(m_from, numFrom)) { @@ -271,14 +299,16 @@ bool SvdDimension::CalculateDimIndexFromTo() } if(numOk) { - int32_t len = numTo - numFrom +1; - if(len < 0 || (uint32_t)len != m_dim) { + const uint64_t len = uint64_t(numTo) - numFrom + 1; + if(numTo < numFrom || len != m_dim) { + return false; + } + if(!ReserveExpansion(m_dim)) { return false; } - for(int32_t i=numFrom; i<=numTo; i++) { - auto numStr = SvdUtils::CreateDecNum(i); - m_dimIndexList.push_back(numStr); + for(uint64_t i = 0; i < len; i++) { + m_dimIndexList.push_back(SvdUtils::CreateDecNum(uint64_t(numFrom) + i)); } } else { @@ -291,6 +321,9 @@ bool SvdDimension::CalculateDimIndexFromTo() return false; } + if(!ReserveExpansion(m_dim)) { + return false; + } char res[2] = { 0 }; for(int32_t i=0; iIsValid())) { + return false; + } + if(!CalculateDimIndex()) { + if(!HasExpansionError()) { + LogMsg("M250", NAME(parent ? parent->GetName() : GetName()), GetLineNumber()); + } + Invalidate(); + if(parent) { + parent->Invalidate(); + } + m_dimIndexList.clear(); + return false; + } CheckItem(); + return IsValid() && (!parent || parent->IsValid()); +} - return true; +bool SvdDimension::CheckExpansionRange(uint64_t start, uint64_t increment) +{ + const auto count = m_dimIndexList.size(); + if(count > 1 && increment > (numeric_limits::max() - start) / (count - 1)) { + const auto parent = GetParent(); + LogMsg("M250", NAME(parent ? parent->GetName() : GetName()), GetLineNumber()); + Invalidate(); + if(parent) { + parent->Invalidate(); + } + return false; + } + return !HasExpansionError(); } bool SvdDimension::CalculateNameFromExpression() diff --git a/tools/svdconv/SVDModel/src/SvdField.cpp b/tools/svdconv/SVDModel/src/SvdField.cpp index a15894fea..cb60167a8 100644 --- a/tools/svdconv/SVDModel/src/SvdField.cpp +++ b/tools/svdconv/SVDModel/src/SvdField.cpp @@ -205,7 +205,9 @@ bool SvdField::CalculateDim() dim->ClearChildren(); } - dim->CalculateDim(); + if(!dim->CalculateDim() || !dim->CheckExpansionRange(GetOffset(), dim->CalcAddressIncrement())) { + return false; + } const auto dimExpression = dim->GetExpression(); SvdTypes::Expression exprType = SvdTypes::Expression::NONE; @@ -228,10 +230,18 @@ bool SvdField::CalculateDim() string dimIndexText; for(const auto& index : dimIndexList) { + if(!dim->ReserveExpansion(1)) { + return false; + } const auto newField = new SvdField(dim); dim->AddItem(newField); - CopyChilds(this, newField); + if(!CopyChilds(this, newField)) { + return false; + } newField->CopyItem (this); + if(HasExpansionError()) { + return false; + } newField->SetName (dim->CreateName(index)); newField->SetDisplayName (dim->CreateDisplayName(index)); newField->SetDescription (dim->CreateDescription(index)); @@ -240,7 +250,9 @@ bool SvdField::CalculateDim() newField->CheckItem(); // TODO: Field dimIncrement is in bits; CalcAddressIncrement() scales it by addressUnitBits / 8. // With 16-bit address units, increment 1 incorrectly produces bit positions 0, 2, 4. - offset += dim->CalcAddressIncrement(); + if(dimElementIndex < dimIndexList.size()) { + offset += dim->CalcAddressIncrement(); + } if(dimElementIndex < 8) { if(!dimIndexText.empty()) dimIndexText += ","; @@ -495,7 +507,7 @@ bool SvdField::CheckItem() SetOffset(FIELD_MAX_OFFSET); Invalidate(); } - if(width > FIELD_MAX_BITWIDTH) { + if(width == 0 || width > FIELD_MAX_BITWIDTH) { LogMsg("M310", NAME(name), NUM(width), lineNo); SetBitWidth(FIELD_MAX_BITWIDTH); Invalidate(); @@ -505,6 +517,9 @@ bool SvdField::CheckItem() SetBitWidth(1); Invalidate(); } + if(!IsValid()) { + return true; + } auto parent = GetParent(); if(parent) { @@ -515,29 +530,29 @@ bool SvdField::CheckItem() parent = parent->GetParent(); } } - const auto reg = dynamic_cast(parent->GetParent()); + const auto reg = parent ? dynamic_cast(parent->GetParent()) : nullptr; if(reg) { const auto regWidth = reg->GetEffectiveBitWidth(); const auto l = reg->GetLineNumber(); if(width > (int64_t)regWidth || offs+width > regWidth) { LogMsg("M324", NAME(name), BITRANGE(offs+width-1, offs, true), NAME2(reg->GetName()), NUM(regWidth), LINE2(l), lineNo); Invalidate(); + return true; } - } - - const auto regAccess = reg->GetEffectiveAccess(); - const auto fieldAccess = GetEffectiveAccess(); + const auto regAccess = reg->GetEffectiveAccess(); + const auto fieldAccess = GetEffectiveAccess(); - if(!SvdUtils::IsMatchAccess(fieldAccess, regAccess)) { - LogMsg("M367", lineNo); + if(!SvdUtils::IsMatchAccess(fieldAccess, regAccess)) { + LogMsg("M367", lineNo); + } } } map enumContMap; - uint64_t fieldMaxVal = (1ULL << (uint64_t)width) -1; + const auto fieldMaxVal = SvdUtils::CreateBitMask(width); const auto& enumConts = GetEnumContainer(); - if(enumConts.empty() && width < 6) { // message "please add enum values" + if(enumConts.empty() && width < MAX_BITWIDTH_FOR_COMBO) { // message "please add enum values" LogMsg("M347", NAME(name), lineNo); } diff --git a/tools/svdconv/SVDModel/src/SvdInterrupt.cpp b/tools/svdconv/SVDModel/src/SvdInterrupt.cpp index aadcb7528..740dcd31b 100644 --- a/tools/svdconv/SVDModel/src/SvdInterrupt.cpp +++ b/tools/svdconv/SVDModel/src/SvdInterrupt.cpp @@ -65,7 +65,9 @@ bool SvdInterrupt::CalculateDim() if(parent) { const auto parentDim = parent->GetDimension(); if(parentDim) { - CopyDim(this, parent); + if(!CopyDim(this, parent)) { + return false; + } dim = GetDimension(); dim->SetDimIncrement(1); } @@ -83,24 +85,41 @@ bool SvdInterrupt::CalculateDim() dim->ClearChildren(); } - dim->CalculateDim(); + if(!dim->CalculateDim()) { + return false; + } const auto& dimIndexList = dim->GetDimIndexList(); uint32_t value = GetValue(); uint32_t dimElementIndex = 0; string dimIndexText; + const auto increment = dim->GetDimIncrement(); + const auto count = dimIndexList.size(); + if(value == SvdItem::VALUE32_NOT_INIT || + (count > 1 && increment > (uint64_t(UINT32_MAX - 1) - value) / (count - 1))) { + LogMsg("M250", NAME(GetName()), GetLineNumber()); + Invalidate(); + return false; + } for(const auto& dimIndex : dimIndexList) { + if(!dim->ReserveExpansion(1)) { + return false; + } const auto newIrq = new SvdInterrupt(dim); dim->AddItem(newIrq); - CopyChilds(this, newIrq); + if(!CopyChilds(this, newIrq)) { + return false; + } newIrq->CopyItem (this); newIrq->SetName (dim->CreateName(dimIndex)); newIrq->SetDescription (dim->CreateDescription(dimIndex)); newIrq->SetValue (value); newIrq->SetDimElementIndex (dimElementIndex++); - value += dim->GetDimIncrement(); + if(dimElementIndex < dimIndexList.size()) { + value += increment; + } } string description = dim->CreateDescription(""); diff --git a/tools/svdconv/SVDModel/src/SvdItem.cpp b/tools/svdconv/SVDModel/src/SvdItem.cpp index 380973615..c06457cf1 100644 --- a/tools/svdconv/SVDModel/src/SvdItem.cpp +++ b/tools/svdconv/SVDModel/src/SvdItem.cpp @@ -183,7 +183,13 @@ bool SvdItem::Construct(XMLTreeElement* xmlElement) bool success = ProcessXmlAttributes(xmlElement); success = ProcessXmlChildren(xmlElement); + if(HasExpansionError()) { + return false; + } CalculateDim(); + if(HasExpansionError()) { + return false; + } Calculate(); CheckItem(); @@ -199,6 +205,9 @@ bool SvdItem::ProcessXmlChildren(XMLTreeElement* xmlElement) // process children const auto& childs = xmlElement->GetChildren(); for(const auto child : childs) { + if(HasExpansionError()) { + return false; + } if(!ProcessXmlElement(child)) { return false; } @@ -207,6 +216,33 @@ bool SvdItem::ProcessXmlChildren(XMLTreeElement* xmlElement) return true; } +bool SvdItem::ReserveExpansion(size_t count) +{ + auto root = this; + while(root->m_parent) { + root = root->m_parent; + } + if(root->m_expansionError) { + return false; + } + if(count > MAX_EXPANSION_ITEMS - root->m_expansionItems) { + root->m_expansionError = true; + LogMsg("M249", NUM(MAX_EXPANSION_ITEMS), GetLineNumber()); + return false; + } + root->m_expansionItems += count; + return true; +} + +bool SvdItem::HasExpansionError() const +{ + auto root = this; + while(root->m_parent) { + root = root->m_parent; + } + return root->m_expansionError; +} + bool SvdItem::ProcessXmlElement(XMLTreeElement* xmlElement) { static uint32_t tagCnt=0; @@ -919,8 +955,9 @@ bool SvdItem::CopyItem(SvdItem *from) } SetTag (tag); - CopyDerivedFrom (this, from); - CopyDim (this, from); + if(!CopyDerivedFrom(this, from) || !CopyDim(this, from)) { + return false; + } SetCopiedFrom ( from); return true; @@ -940,11 +977,17 @@ bool SvdItem::CopyChilds(SvdItem *from, SvdItem *hook) const auto lv = copy->GetSvdLevel(); + if(!hook->ReserveExpansion(1)) { + return false; + } + // Container if(lv == L_EnumeratedValues) { // more containers can be set! const auto nItem = new SvdEnumContainer(hook); hook->AddItem(nItem); - CopyChilds(copy, nItem); + if(!CopyChilds(copy, nItem)) { + return false; + } nItem->CopyItem(copy); } else if(lv == L_Fields) { @@ -957,7 +1000,9 @@ bool SvdItem::CopyChilds(SvdItem *from, SvdItem *hook) else { nItem = (SvdFieldContainer *)*(hook->GetChildren().begin()); } - CopyChilds(copy, nItem); + if(!CopyChilds(copy, nItem)) { + return false; + } } else if(lv == L_Registers) { SvdRegisterContainer *nItem; @@ -969,40 +1014,55 @@ bool SvdItem::CopyChilds(SvdItem *from, SvdItem *hook) else { nItem = (SvdRegisterContainer *)*(hook->GetChildren().begin()); } - CopyChilds(copy, nItem); + if(!CopyChilds(copy, nItem)) { + return false; + } } // Elements else if(lv == L_EnumeratedValue) { const auto nItem = new SvdEnum(hook); hook->AddItem(nItem); - CopyChilds(copy, nItem); + if(!CopyChilds(copy, nItem)) { + return false; + } nItem->CopyItem(copy); } else if(lv == L_Field) { const auto nItem = new SvdField(hook); hook->AddItem(nItem); - CopyChilds(copy, nItem); + if(!CopyChilds(copy, nItem)) { + return false; + } nItem->CopyItem(copy); } else if(lv == L_Register) { const auto nItem = new SvdRegister(hook); hook->AddItem(nItem); - CopyChilds(copy, nItem); + if(!CopyChilds(copy, nItem)) { + return false; + } nItem->CopyItem(copy); } else if(lv == L_Peripheral) { const auto nItem = new SvdPeripheral(hook); hook->AddItem(nItem); - CopyChilds(copy, nItem); + if(!CopyChilds(copy, nItem)) { + return false; + } nItem->CopyItem(copy); } else if(lv == L_Cluster) { const auto nItem = new SvdCluster(hook); hook->AddItem(nItem); - CopyChilds(copy, nItem); + if(!CopyChilds(copy, nItem)) { + return false; + } nItem->CopyItem(copy); } + if(hook->HasExpansionError()) { + return false; + } } return true; @@ -1018,6 +1078,9 @@ bool SvdItem::CopyDerivedFrom(SvdItem *item, SvdItem *from) if(copyDeriveFrom) { auto deriveFrom = item->GetDerivedFrom(); if(!deriveFrom) { + if(!item->ReserveExpansion(1)) { + return false; + } deriveFrom = new SvdDerivedFrom(item); item->SetDerivedFrom(deriveFrom); auto derivedFromItem = copyDeriveFrom->GetDerivedFromItem(); @@ -1050,6 +1113,9 @@ bool SvdItem::CopyDim(SvdItem *item, SvdItem *from) if(copyDim) { auto dim = item->GetDimension(); if(!dim) { + if(!item->ReserveExpansion(1)) { + return false; + } dim = new SvdDimension(item); item->SetDimension(dim); dim->CopyItem(copyDim); diff --git a/tools/svdconv/SVDModel/src/SvdModel.cpp b/tools/svdconv/SVDModel/src/SvdModel.cpp index f42f68902..bcc0b7e8c 100644 --- a/tools/svdconv/SVDModel/src/SvdModel.cpp +++ b/tools/svdconv/SVDModel/src/SvdModel.cpp @@ -52,6 +52,7 @@ bool SvdModel::Construct(XMLTreeElement* xmlTree) if(!ok) { delete m_device; + m_device = nullptr; success = false; } } diff --git a/tools/svdconv/SVDModel/src/SvdPeripheral.cpp b/tools/svdconv/SVDModel/src/SvdPeripheral.cpp index b91233b70..8c63f9d7f 100644 --- a/tools/svdconv/SVDModel/src/SvdPeripheral.cpp +++ b/tools/svdconv/SVDModel/src/SvdPeripheral.cpp @@ -373,7 +373,9 @@ bool SvdPeripheral::CopyItem(SvdItem *from) if(resetMask == 0 ) { SetResetMask (pFrom->GetResetMask ()); } if(access == SvdTypes::Access::UNDEF) { SetAccess (pFrom->GetAccess ()); } - CopyAddressBlocks(pFrom); + if(!CopyAddressBlocks(pFrom) || HasExpansionError()) { + return false; + } SvdItem::CopyItem(pFrom); CalculateDim(); @@ -391,6 +393,9 @@ bool SvdPeripheral::CopyAddressBlocks(SvdPeripheral *from) continue; } + if(!ReserveExpansion(1)) { + return false; + } const auto addressBlock = new SvdAddressBlock(this); addressBlock->CopyItem(addrBlock); AddAddressBlock(addressBlock); @@ -433,6 +438,9 @@ bool SvdPeripheral::CopyDisableCondition(SvdCExpression* from) return true; } + if(!ReserveExpansion(1)) { + return false; + } m_disableCondition = new SvdCExpression(*from); return true; @@ -457,7 +465,9 @@ bool SvdPeripheral::CalculateDim() dim->ClearChildren(); } - dim->CalculateDim(); + if(!dim->CalculateDim() || !dim->CheckExpansionRange(GetAbsoluteAddress(), dim->GetDimIncrement())) { + return false; + } const auto& dimIndexList = dim->GetDimIndexList(); const auto parent = dynamic_cast(dim->GetParent()); @@ -468,18 +478,27 @@ bool SvdPeripheral::CalculateDim() auto address = parent->GetAbsoluteAddress(); uint32_t dimElementIndex = 0; for(const auto& dimIndexname : dimIndexList) { + if(!dim->ReserveExpansion(1)) { + return false; + } const auto newPeri = new SvdPeripheral(dim); dim->AddItem(newPeri); - CopyDerivedFrom(newPeri, this); - CopyChilds(parent, newPeri); + if(!CopyDerivedFrom(newPeri, this) || !CopyChilds(parent, newPeri)) { + return false; + } newPeri->CopyItem (parent); + if(HasExpansionError()) { + return false; + } newPeri->SetName (dim->CreateName(dimIndexname)); newPeri->SetDisplayName (dim->CreateDisplayName(dimIndexname)); newPeri->SetDescription (dim->CreateDescription(dimIndexname)); newPeri->SetDimElementIndex (dimElementIndex++); newPeri->SetAddress (address); - address += dim->GetDimIncrement(); + if(dimElementIndex < dimIndexList.size()) { + address += dim->GetDimIncrement(); + } } string dimIndexText; @@ -1276,6 +1295,9 @@ bool SvdPeripheral::SortAddressBlocks(map& addrBlock continue; // only use "register" addressBlocks } + if(!ReserveExpansion(1)) { + return false; + } const auto block = new SvdAddressBlock(this); block->CopyItem(addrBlock); diff --git a/tools/svdconv/SVDModel/src/SvdRegister.cpp b/tools/svdconv/SVDModel/src/SvdRegister.cpp index 70b1e6be4..97e1aa5b8 100644 --- a/tools/svdconv/SVDModel/src/SvdRegister.cpp +++ b/tools/svdconv/SVDModel/src/SvdRegister.cpp @@ -85,6 +85,9 @@ SvdRegister::~SvdRegister() bool SvdRegister::Construct(XMLTreeElement* xmlElement) { bool success = SvdItem::Construct(xmlElement); + if(!IsValid()) { + return success; + } const auto bitWidth = GetBitWidth(); if(bitWidth == (int32_t)SvdItem::VALUE32_NOT_INIT) { // default, derive from above levels @@ -236,7 +239,9 @@ bool SvdRegister::CalculateDim() dim->ClearChildren(); } - dim->CalculateDim(); + if(!dim->CalculateDim() || !dim->CheckExpansionRange(GetOffset(), dim->CalcAddressIncrement())) { + return false; + } const auto& dimIndexList = dim->GetDimIndexList(); auto offset = GetOffset(); @@ -244,11 +249,19 @@ bool SvdRegister::CalculateDim() string dimIndexText; for(const auto& dimIndex : dimIndexList) { + if(!dim->ReserveExpansion(1)) { + return false; + } const auto newReg = new SvdRegister(dim); dim->AddItem(newReg); - CopyChilds(this, newReg); + if(!CopyChilds(this, newReg)) { + return false; + } newReg->CopyItem (this); + if(HasExpansionError()) { + return false; + } newReg->SetName (dim->CreateName(dimIndex)); newReg->SetDisplayName (dim->CreateDisplayName(dimIndex)); newReg->SetDescription (dim->CreateDescription(dimIndex)); @@ -257,7 +270,9 @@ bool SvdRegister::CalculateDim() newReg->CheckItem (); // TODO: Register dimIncrement is already in bytes; CalcAddressIncrement() scales it again. // With 16-bit address units, a requested 4-byte stride incorrectly becomes 8 bytes. - offset += dim->CalcAddressIncrement(); + if(dimElementIndex < dimIndexList.size()) { + offset += dim->CalcAddressIncrement(); + } if(dimElementIndex < 8) { if(!dimIndexText.empty()) { @@ -384,18 +399,21 @@ SvdTypes::Access SvdRegister::GetAccessCalculated() } const auto regWidth = GetEffectiveBitWidth(); - const auto mask = ((uint64_t)1 << regWidth) -1; + const auto mask = SvdUtils::CreateBitMask(regWidth); + if(!mask) { + return GetEffectiveAccess(); + } auto bits = (uint64_t)0U; auto access = SvdTypes::Access::UNDEF; for(const auto child : childs) { const auto field = dynamic_cast(child); - if(!field) { + if(!field || !field->IsValid()) { continue; } const auto fieldWidth = field->GetEffectiveBitWidth(); - bits |= ((uint64_t)1 << fieldWidth) -1; + bits |= SvdUtils::CreateBitMask(fieldWidth); SvdTypes::Access acc = field->GetEffectiveAccess(); access = SvdUtils::CalcAccessResult(acc, access); @@ -415,6 +433,13 @@ SvdTypes::Access SvdRegister::GetAccessCalculated() bool SvdRegister::CalcAccessMask() { + const auto regWidth = GetEffectiveBitWidth(); + const auto regMask = SvdUtils::CreateBitMask(regWidth); + if(!regMask) { + m_accessMaskRead = m_accessMaskWrite = 0; + m_accessMaskValid = true; + return false; + } const auto fields = GetFieldContainer(); if(!fields) { return true; @@ -425,21 +450,21 @@ bool SvdRegister::CalcAccessMask() return true; } - const auto regWidth = GetEffectiveBitWidth(); - m_accessMaskRead = (((uint32_t)((uint64_t)1 << regWidth)-1) << 0); + m_accessMaskRead = regMask; m_accessMaskWrite = 0; for(const auto child : childs) { SvdField* field = dynamic_cast(child); - if(!field) { + if(!field || !field->IsValid()) { continue; } const auto bitWidth = field->GetEffectiveBitWidth(); const auto firstBit = field->GetOffset(); - auto accessMask = 0; - accessMask = (((uint32_t)((uint64_t)1 << bitWidth)-1) << firstBit); - accessMask &= ((uint32_t)((uint64_t)1 << regWidth)-1); + if(bitWidth == 0 || bitWidth > 64 || firstBit >= regWidth || bitWidth > regWidth - firstBit) { + continue; + } + const auto accessMask = SvdUtils::CreateBitMask(bitWidth, firstBit) & regMask; const auto accType = field->GetEffectiveAccess(); switch(accType) { @@ -483,11 +508,9 @@ uint64_t SvdRegister::GetAccessMaskWrite() return m_accessMaskWrite; } -uint64_t SvdRegister::GetAccessMask() +uint32_t SvdRegister::GetAccessMask() { - uint64_t accessMask = ((uint32_t)((uint64_t)1 << GetEffectiveBitWidth())-1); - - return accessMask; + return static_cast(SvdUtils::CreateBitMask(GetEffectiveBitWidth())); } bool SvdRegister::AddToMap(SvdField *field, map &map) @@ -630,9 +653,10 @@ bool SvdRegister::CheckFields(SvdItem* fields, uint32_t regWidth, const string& const auto fieldWidth = field->GetEffectiveBitWidth(); const auto lineNo = field->GetLineNumber(); - if(fieldOffs + fieldWidth > regWidth) { + if(fieldWidth == 0 || fieldWidth > 64 || fieldOffs >= regWidth || fieldWidth > regWidth - fieldOffs) { LogMsg("M345", NAME(fieldName), BITRANGE(fieldOffs + fieldWidth -1, fieldOffs, true), NAME2(name), NUM(regWidth), lineNo); field->Invalidate(); + continue; } const auto accType = field->GetEffectiveAccess(); @@ -688,6 +712,12 @@ bool SvdRegister::CheckItem() const auto resetValue = GetResetValue(); const auto resetMask = GetResetMask(); + if(regWidth == 0 || regWidth > 64) { + LogMsg("M302", NAME(name), NUM(regWidth), lineNo); + Invalidate(); + return true; + } + if(offset == SvdItem::VALUE64_NOT_INIT) { const auto& svdLevelStr = GetSvdLevelStr(GetSvdLevel()); LogMsg("M370", LEVEL(svdLevelStr), NAME(name), lineNo); @@ -716,7 +746,7 @@ bool SvdRegister::CheckItem() if(dim) { const auto addressBitsUnits = dim->GetAddressBitsUnits(); const auto dimInc = dim->GetDimIncrement(); - const auto addressInc = addressBitsUnits * dimInc; + const auto addressInc = uint64_t{addressBitsUnits} * dimInc; if(addressInc < regWidth) { LogMsg("M366", NAME(name), NUM(regWidth), NUM2(dimInc), lineNo); @@ -727,8 +757,9 @@ bool SvdRegister::CheckItem() if(expr) { const auto e = expr->GetType(); if(e == SvdTypes::Expression::ARRAY) { - if(dimInc*8 != regWidth) { - LogMsg("M378", NAME(name), NUM(regWidth), NUM2(dimInc*8), lineNo); + const auto incrementBits = uint64_t{dimInc} * 8; + if(incrementBits != regWidth) { + LogMsg("M378", NAME(name), NUM(regWidth), NUM2(incrementBits), lineNo); Invalidate(); } } diff --git a/tools/svdconv/SVDModel/src/SvdUtils.cpp b/tools/svdconv/SVDModel/src/SvdUtils.cpp index 1873936f2..40c5dd8e9 100644 --- a/tools/svdconv/SVDModel/src/SvdUtils.cpp +++ b/tools/svdconv/SVDModel/src/SvdUtils.cpp @@ -446,6 +446,43 @@ bool SvdUtils::ConvertNumber(const string &text, set &numbers) return true; } +// SvdUtils::GetBitRange32() preserves the existing raw getter contracts: +// GetBitWidth() is already signed 32-bit with -1 meaning unset; SvdField::GetOffset() +// retains wide values and its unset sentinel for inheritance and diagnostics. +// Validate the complete range before narrowing: offset 31 with width 2 is invalid, +// and a large offset must not wrap to a valid position. A checked SvdField accessor +// could own this check; changing the raw getters instead requires coordinated +// changes to parsing, storage, setters, dimensions, inheritance and sentinels. +bool SvdUtils::GetBitRange32(uint64_t rawOffset, int64_t rawWidth, uint32_t& offset, uint32_t& width) +{ + if(rawOffset >= 32 || rawWidth <= 0 || rawWidth > 32 || rawOffset + rawWidth > 32) { + return false; + } + + offset = rawOffset; + width = rawWidth; + return true; +} + +uint32_t SvdUtils::CreateBitMask32(uint32_t width, uint32_t offset) +{ + if(width == 0 || width > 32 || offset >= 32 || width > 32 - offset) { + return 0; + } + + return (UINT32_MAX >> (32 - width)) << offset; +} + +uint64_t SvdUtils::CreateBitMask(int64_t width, uint64_t offset) +{ + if(width <= 0 || width > 64 || offset >= 64 || width + offset > 64) { + return 0; + } + + const auto mask = width == 64 ? UINT64_MAX : (uint64_t{1} << width) - 1; + return mask << offset; +} + bool SvdUtils::ConvertNumber(const string &text, uint32_t &number) { uint64_t num = 0; diff --git a/tools/svdconv/docs/todo.md b/tools/svdconv/docs/todo.md new file mode 100644 index 000000000..7b8cc1964 --- /dev/null +++ b/tools/svdconv/docs/todo.md @@ -0,0 +1,26 @@ +# SVDConv TODO + +## Field ranges and 32-bit output + +- [ ] Review whether a checked `SvdField::GetBitRange(uint32_t&, uint32_t&)` accessor should own the validation + currently provided by `SvdUtils::GetBitRange32()`. This is a possible model cleanup; the current helper + preserves the existing raw getter contracts. + +`SvdUtils::GetBitRange32()` in [SvdUtils.cpp](../SVDModel/src/SvdUtils.cpp) validates the complete field range +before converting model values to the 32-bit values used by the generators: + +- `GetBitWidth()` already returns `int32_t`; `-1` represents an unset value used during inheritance. +- `SvdField::GetOffset()` retains a `uint64_t` raw value, including its unset sentinel and invalid inputs needed + for inheritance and diagnostics. Narrowing this getter could turn an invalid offset such as `0x100000000` + into the valid position `0` before validation. +- Offset and width must be checked together: offset `31` and width `2` exceed the supported 32-bit range. + +The helper checks offset, width and their combined range before either conversion. It leaves the output +parameters unchanged on failure. Existing wide model values and intermediate arithmetic do not imply support +for 64-bit SVD input or output. + +Changing the raw getters or field storage requires coordinated changes to parsing, setters, dimension +calculations, inheritance and sentinel handling. Preserve invalid raw values until diagnostics can report them, +and retain the regression coverage in [SvdSentinelModelTest.cpp](../test/unittests/src/SvdSentinelModelTest.cpp) +and [SvdBitWidthTest.cpp](../test/unittests/src/SvdBitWidthTest.cpp), including full-width masks, bit 31, +negative widths and large offsets that would otherwise wrap. diff --git a/tools/svdconv/test/integtests/src/SvdConvIntegTests.cpp b/tools/svdconv/test/integtests/src/SvdConvIntegTests.cpp index 3f8b7f1e9..5ae3879cc 100644 --- a/tools/svdconv/test/integtests/src/SvdConvIntegTests.cpp +++ b/tools/svdconv/test/integtests/src/SvdConvIntegTests.cpp @@ -7,6 +7,7 @@ #include "SvdConvTestUtils.h" #include "SVDConv.h" +#include "SvdItem.h" #include "SfrccInterface.h" #include "ErrLog.h" @@ -293,6 +294,205 @@ TEST_F(SvdConvIntegTests, ExpandsRegisterAndFieldDimensionsInMemoryMap) { } } +TEST_F(SvdConvIntegTests, ExpansionLimitStopsAllGeneratorsAndNextInputStillWorks) { + const string original = SvdConvIntegTestEnv::localtestdata_dir + "/posMaskDim/PosMaskDim.svd"; + string xml; + ASSERT_TRUE(RteFsUtils::ReadFile(original, xml)); + const string marker = "256"; + const auto position = xml.find(marker); + ASSERT_NE(string::npos, position); + + for(const auto count : {SvdItem::MAX_EXPANSION_ITEMS + 1, SvdItem::MAX_EXPANSION_ITEMS / 2}) { + const auto dimension = to_string(count); + SCOPED_TRACE(dimension); + const string directory = SvdConvIntegTestEnv::testoutput_dir + "/expansionLimit/" + dimension; + ASSERT_TRUE(RteFsUtils::CreateDirectories(directory)); + const string input = directory + "/PosMaskDim.svd"; + string oversized = xml; + oversized.replace(position, marker.size(), string("") + dimension + ""); + { + ofstream file(input); + ASSERT_TRUE(file.good()); + file << oversized; + ASSERT_TRUE(file.good()); + } + const string output = directory + "/out"; + Arguments args("SVDConv.exe", input); + args.add({"-o", output, "--generate=header", "--generate=sfd", "--generate=fieldMap", "--create-folder"}); + SvdConv converter; + EXPECT_EQ(2, converter.Check(args, args, nullptr)); + string messages; + for(const auto& message : ErrLog::Get()->GetLogMessages()) { + messages += message; + } + EXPECT_NE(string::npos, messages.find("M249")); + for(const auto* suffix : {".h", ".sfd", "_MapFields.txt"}) { + EXPECT_FALSE(RteFsUtils::Exists(output + "/PosMaskDim" + suffix)); + } + ErrLog::Get()->ClearLogMessages(); + } + + // A rejected conversion must not poison the expansion budget of a new input. + const string output = SvdConvIntegTestEnv::testoutput_dir + "/expansionLimit/valid"; + Arguments args("SVDConv.exe", original); + args.add({"-o", output, "--generate=header", "--create-folder"}); + SvdConv converter; + EXPECT_EQ(0, converter.Check(args, args, nullptr)); + EXPECT_TRUE(RteFsUtils::Exists(output + "/PosMaskDim.h")); +} + +TEST_F(SvdConvIntegTests, RejectedSfdDoesNotCompileStaleOutput) { + string xml; + ASSERT_TRUE(RteFsUtils::ReadFile(SvdConvIntegTestEnv::localtestdata_dir + "/enumComboWidth/EnumComboWidth.svd", xml)); + xml = regex_replace(xml, regex("0x20"), "64"); + xml = regex_replace(xml, regex("0x4"), "0x8"); + const string directory = SvdConvIntegTestEnv::testoutput_dir + "/rejectedSfd"; + ASSERT_TRUE(RteFsUtils::CreateDirectories(directory)); + const string input = directory + "/EnumComboWidth.svd"; + const string output = directory + "/EnumComboWidth.sfd"; + const string previousOutput = "Previous SFD output\n"; + { + ofstream file(input); + file << xml; + ASSERT_TRUE(file.good()); + } + { + ofstream file(output, ios::binary); + file << previousOutput; + ASSERT_TRUE(file.good()); + } + + Arguments args("SVDConv.exe", input); + args.add({"-o", directory, "--generate=sfr", "--nocleanup", "--verbose"}); + SvdConv converter; + EXPECT_EQ(2, converter.Check(args, args, nullptr)); + string messages; + for(const auto& message : ErrLog::Get()->GetLogMessages()) { + messages += message; + } + EXPECT_NE(string::npos, messages.find("M226")); + EXPECT_NE(string::npos, messages.find("M111")); + EXPECT_EQ(string::npos, messages.find("Generate System Viewer SFR File")); + EXPECT_EQ(string::npos, messages.find("M133")); + EXPECT_EQ(string::npos, messages.find("SfrCC2")); + string retained; + ASSERT_TRUE(RteFsUtils::ReadFile(output, retained)); + EXPECT_EQ(previousOutput, retained); + EXPECT_FALSE(RteFsUtils::Exists(directory + "/EnumComboWidth.sfr")); +} + +TEST_F(SvdConvIntegTests, InvalidExpressionFieldDoesNotCompileStaleOutput) { + string xml; + ASSERT_TRUE(RteFsUtils::ReadFile(SvdConvIntegTestEnv::localtestdata_dir + "/option_n/option_n.svd", xml)); + const auto field = xml.find("CDS"); + ASSERT_NE(string::npos, field); + const string width = "1"; + const auto position = xml.find(width, field); + ASSERT_NE(string::npos, position); + ASSERT_LT(position, xml.find("", field)); + xml.replace(position, width.size(), "0"); + + const string directory = SvdConvIntegTestEnv::testoutput_dir + "/invalidExpressionField"; + ASSERT_TRUE(RteFsUtils::CreateDirectories(directory)); + const string input = directory + "/option_n.svd"; + const string output = directory + "/option_n.sfd"; + const string previousOutput = "Previous SFD output\n"; + { + ofstream file(input); + file << xml; + ASSERT_TRUE(file.good()); + } + { + ofstream file(output, ios::binary); + file << previousOutput; + ASSERT_TRUE(file.good()); + } + + Arguments args("SVDConv.exe", input); + args.add({"-o", directory, "--generate=sfr", "--nocleanup", "--verbose"}); + SvdConv converter; + EXPECT_EQ(2, converter.Check(args, args, nullptr)); + string messages; + for(const auto& message : ErrLog::Get()->GetLogMessages()) { + messages += message; + } + EXPECT_NE(string::npos, messages.find("M310")); + EXPECT_NE(string::npos, messages.find("M226")); + EXPECT_NE(string::npos, messages.find("M111")); + EXPECT_EQ(string::npos, messages.find("Generate System Viewer SFR File")); + EXPECT_EQ(string::npos, messages.find("M133")); + EXPECT_EQ(string::npos, messages.find("SfrCC2")); + string retained; + ASSERT_TRUE(RteFsUtils::ReadFile(output, retained)); + EXPECT_EQ(previousOutput, retained); + EXPECT_FALSE(RteFsUtils::Exists(directory + "/option_n.sfr")); +} + +TEST_F(SvdConvIntegTests, FailedSfdCreationDoesNotCompileOutput) { + const string input = SvdConvIntegTestEnv::localtestdata_dir + "/option_n/option_n.svd"; + const string directory = SvdConvIntegTestEnv::testoutput_dir + "/failedSfdCreation"; + const string output = directory + "/option_n.sfd"; + ASSERT_TRUE(RteFsUtils::CreateDirectories(output)); + + Arguments args("SVDConv.exe", input); + args.add({"-o", directory, "--generate=sfr", "--nocleanup", "--verbose"}); + SvdConv converter; + EXPECT_EQ(2, converter.Check(args, args, nullptr)); + string messages; + for(const auto& message : ErrLog::Get()->GetLogMessages()) { + messages += message; + } + EXPECT_NE(string::npos, messages.find("M130")); + EXPECT_NE(string::npos, messages.find("M111")); + EXPECT_EQ(string::npos, messages.find("Generate System Viewer SFR File")); + EXPECT_EQ(string::npos, messages.find("M133")); + EXPECT_EQ(string::npos, messages.find("SfrCC2")); + EXPECT_TRUE(RteFsUtils::IsDirectory(output)); + EXPECT_FALSE(RteFsUtils::Exists(directory + "/option_n.sfr")); +} + +TEST_F(SvdConvIntegTests, ReadOnlySfdDoesNotCompileStaleOutput) { + const string input = SvdConvIntegTestEnv::localtestdata_dir + "/option_n/option_n.svd"; + const string directory = SvdConvIntegTestEnv::testoutput_dir + "/readOnlySfd"; + const string output = directory + "/option_n.sfd"; + const string previousOutput = "Previous SFD output\n"; + ASSERT_TRUE(RteFsUtils::CreateDirectories(directory)); + { + ofstream file(output, ios::binary); + file << previousOutput; + ASSERT_TRUE(file.good()); + } + ASSERT_TRUE(RteFsUtils::SetFileReadOnly(output, true)); + { + ofstream probe(output, ios::binary | ios::app); + if(probe.good()) { + probe.close(); + EXPECT_TRUE(RteFsUtils::SetFileReadOnly(output, false)); + GTEST_SKIP() << "The current process can write read-only files"; + } + } + + Arguments args("SVDConv.exe", input); + args.add({"-o", directory, "--generate=sfr", "--nocleanup", "--verbose"}); + SvdConv converter; + const auto result = converter.Check(args, args, nullptr); + EXPECT_TRUE(RteFsUtils::SetFileReadOnly(output, false)); + EXPECT_EQ(2, result); + string messages; + for(const auto& message : ErrLog::Get()->GetLogMessages()) { + messages += message; + } + EXPECT_NE(string::npos, messages.find("M130")); + EXPECT_NE(string::npos, messages.find("M111")); + EXPECT_EQ(string::npos, messages.find("Generate System Viewer SFR File")); + EXPECT_EQ(string::npos, messages.find("M133")); + EXPECT_EQ(string::npos, messages.find("SfrCC2")); + string retained; + ASSERT_TRUE(RteFsUtils::ReadFile(output, retained)); + EXPECT_EQ(previousOutput, retained); + EXPECT_FALSE(RteFsUtils::Exists(directory + "/option_n.sfr")); +} + #ifndef _WIN32 TEST_F(SvdConvIntegTests, CheckSfrUnsupportedPlatform) { const string inFile = SvdConvIntegTestEnv::localtestdata_dir + "/option_n/option_n.svd"; diff --git a/tools/svdconv/test/unittests/CMakeLists.txt b/tools/svdconv/test/unittests/CMakeLists.txt index fa44fb8c6..180ffb8b0 100644 --- a/tools/svdconv/test/unittests/CMakeLists.txt +++ b/tools/svdconv/test/unittests/CMakeLists.txt @@ -7,6 +7,8 @@ set(TEST_SOURCE_FILES SvdCExpressionParserTest.cpp SvdOptionsTest.cpp SvdModelValidationTest.cpp + SvdBitWidthTest.cpp + SvdDimensionBoundsTest.cpp SvdSentinelModelTest.cpp SvdSentinelConsumerTest.cpp ) diff --git a/tools/svdconv/test/unittests/src/GeneratorTest.cpp b/tools/svdconv/test/unittests/src/GeneratorTest.cpp index fb0721b56..f9347144b 100644 --- a/tools/svdconv/test/unittests/src/GeneratorTest.cpp +++ b/tools/svdconv/test/unittests/src/GeneratorTest.cpp @@ -49,19 +49,19 @@ class TestHeaderData : public HeaderData { using HeaderData::CreateRegisterPosMask; }; -SvdField* AddField(SvdItem* parent, const string& name, uint32_t offset) { - const auto field = new SvdField(parent); - field->SetName(name); - field->SetOffset(offset); - field->SetBitWidth(1); - parent->AddItem(field); - return field; -} - } // namespace TEST(HeaderData, CreateRegisterPosMaskHandlesDimensionVariants) { + const auto addField = [](SvdItem* parent, const string& name, uint32_t offset) { + const auto field = new SvdField(parent); + field->SetName(name); + field->SetOffset(offset); + field->SetBitWidth(1); + parent->AddItem(field); + return field; + }; + SvdOptions options; FileHeaderInfo fileHeaderInfo; TestHeaderData headerData(fileHeaderInfo, options); @@ -73,20 +73,20 @@ TEST(HeaderData, CreateRegisterPosMaskHandlesDimensionVariants) { const auto fields = new SvdFieldContainer(reg); reg->AddItem(fields); - AddField(fields, "PLAIN", 0); + addField(fields, "PLAIN", 0); - const auto arrayField = AddField(fields, "ARRAY", 1); + const auto arrayField = addField(fields, "ARRAY", 1); const auto arrayDim = new SvdDimension(arrayField); arrayDim->GetExpression()->SetType(SvdTypes::Expression::ARRAY); arrayField->SetDimension(arrayDim); - const auto extendField = AddField(fields, "EXTEND%s", 2); + const auto extendField = addField(fields, "EXTEND%s", 2); const auto extendDim = new SvdDimension(extendField); extendDim->GetExpression()->SetType(SvdTypes::Expression::EXTEND); extendField->SetDimension(extendDim); - AddField(extendDim, "EXTEND0", 2); - const auto invalidField = AddField(extendDim, "EXTEND1", 3); + addField(extendDim, "EXTEND0", 2); + const auto invalidField = addField(extendDim, "EXTEND1", 3); invalidField->SetValid(false); extendDim->AddItem(new SvdItem(extendDim)); diff --git a/tools/svdconv/test/unittests/src/SvdBitWidthTest.cpp b/tools/svdconv/test/unittests/src/SvdBitWidthTest.cpp new file mode 100644 index 000000000..c67696898 --- /dev/null +++ b/tools/svdconv/test/unittests/src/SvdBitWidthTest.cpp @@ -0,0 +1,899 @@ +/* + * Copyright (c) 2026 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "SvdItem.h" +#include "SvdCExpression.h" +#include "SvdDevice.h" +#include "SvdDimension.h" +#include "SvdEnum.h" +#include "SvdField.h" +#include "SvdPeripheral.h" +#include "SvdRegister.h" +#include "SvdUtils.h" +#include "HeaderData.h" +#include "SfdData.h" +#include "ErrLog.h" +#include "XMLTree.h" + +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace { + +class BitWidthHeaderData : public HeaderData { +public: + using HeaderData::HeaderData; + using HeaderData::AddFields; + using HeaderData::CreateField; + using HeaderData::CreateFields; + using HeaderData::CreateSortedFields; + using HeaderData::CreateFieldPosMask; +}; + +class BitWidthHeaderGenerator : public HeaderGenerator { +public: + using HeaderGenerator::HeaderGenerator; + using HeaderGenerator::MakeFieldPosMask3; +}; + +class BitWidthSfdData : public SfdData { +public: + using SfdData::SfdData; + using SfdData::CreateFieldItem; + using SfdData::CreateEnumValueSet; +}; + +class SvdBitWidthTest : public testing::Test, public IErrConsumer { +protected: + void SetUp() override { + const auto log = ErrLog::Get(); + m_previousConsumer = log->SetErrConsumer(this); + m_previousQuiet = log->IsQuietMode(); + m_previousErrors = log->GetErrCnt(); + m_previousWarnings = log->GetWarnCnt(); + log->SetQuietMode(false); + log->ResetMsgCount(); + } + + void TearDown() override { + const auto log = ErrLog::Get(); + log->SetErrConsumer(m_previousConsumer); + log->SetQuietMode(m_previousQuiet); + log->ResetMsgCount(); + for(int i = 0; i < m_previousErrors; i++) { + log->IncErrCnt(); + } + for(int i = 0; i < m_previousWarnings; i++) { + log->IncWarnCnt(); + } + if(!m_outputDirectory.empty()) { + error_code ec; + filesystem::remove(m_outputDirectory / "masks.h", ec); + filesystem::remove(m_outputDirectory / "device.sfd", ec); + filesystem::remove(m_outputDirectory / "input.svd", ec); + filesystem::remove(m_outputDirectory, ec); + } + } + + bool Consume(const PdscMsg& msg, const string&) override { + m_messages.push_back(msg.GetMsgNum()); + return true; + } + + bool HasMessage(const string& id) const { + return find(m_messages.begin(), m_messages.end(), id) != m_messages.end(); + } + + void CreateOutputDirectory() { + const auto nonce = chrono::steady_clock::now().time_since_epoch().count(); + for(unsigned i = 0; i < 100; i++) { + const auto candidate = filesystem::temp_directory_path() / + ("svdconv-bit-width-" + to_string(nonce) + "-" + to_string(i)); + if(filesystem::create_directory(candidate)) { + m_outputDirectory = candidate; + break; + } + } + ASSERT_FALSE(m_outputDirectory.empty()); + } + + static string DefineValue(const string& text, const string& name) { + istringstream lines(text); + string line; + while(getline(lines, line)) { + istringstream tokens(line); + string directive, symbol, value; + if(tokens >> directive >> symbol >> value && directive == "#define" && symbol == name) { + return value; + } + } + return {}; + } + + static XMLTreeElement* ConfigureRegister(XMLTreeElement& element, const string& width) { + element.CreateElement("name", "CTRL"); + element.CreateElement("description", "Control register."); + element.CreateElement("size", width); + element.CreateElement("addressOffset", "0"); + element.CreateElement("access", "read-write"); + return element.CreateElement("fields"); + } + + static XMLTreeElement* AddField(XMLTreeElement* fields, const string& name, + const string& offset, const string& width) { + const auto field = fields->CreateElement("field"); + field->CreateElement("name", name); + field->CreateElement("description", "Control field."); + field->CreateElement("bitOffset", offset); + field->CreateElement("bitWidth", width); + return field; + } + + static void AddEnum(XMLTreeElement* field, const string& value) { + const auto container = field->CreateElement("enumeratedValues"); + const auto entry = container->CreateElement("enumeratedValue"); + entry->CreateElement("name", "VALUE"); + entry->CreateElement("description", "Control value."); + entry->CreateElement("value", value); + } + + static SvdField* AddUncheckedField(SvdRegister& reg, uint64_t offset, int32_t width) { + auto fields = reg.GetFieldContainer(); + if(!fields) { + fields = new SvdFieldContainer(®); + reg.AddItem(fields); + } + const auto field = new SvdField(fields); + fields->AddItem(field); + field->SetName("FIELD"); + field->SetOffset(offset); + field->SetBitWidth(width); + field->SetAccess(SvdTypes::Access::READWRITE); + return field; + } + + static pair AddExpressionRegister(SvdDevice& device) { + const auto peripherals = new SvdPeripheralContainer(&device); + device.AddItem(peripherals); + const auto peripheral = new SvdPeripheral(peripherals); + peripherals->AddItem(peripheral); + peripheral->SetName("TEST"); + const auto registers = new SvdRegisterContainer(peripheral); + peripheral->AddItem(registers); + const auto reg = new SvdRegister(registers); + registers->AddItem(reg); + reg->SetName("CTRL"); + reg->SetBitWidth(32); + reg->SetOffset(0); + reg->SetAccess(SvdTypes::Access::READWRITE); + return {peripheral, reg}; + } + + vector m_messages; + filesystem::path m_outputDirectory; + +private: + IErrConsumer* m_previousConsumer = nullptr; + bool m_previousQuiet = false; + int m_previousErrors = 0; + int m_previousWarnings = 0; +}; + +TEST_F(SvdBitWidthTest, BitMasksUseWideIntermediatesWithoutAcceptingInvalidRanges) { + EXPECT_EQ(uint64_t{UINT32_MAX}, SvdUtils::CreateBitMask(32)); + EXPECT_EQ(uint64_t{1} << 31, SvdUtils::CreateBitMask(1, 31)); + // Exercise the arithmetic helper's existing 64-bit preparation, not SVD input support. + EXPECT_EQ(UINT64_MAX, SvdUtils::CreateBitMask(64)); + EXPECT_EQ(uint64_t{1} << 63, SvdUtils::CreateBitMask(1, 63)); + EXPECT_EQ(0U, SvdUtils::CreateBitMask(2, 63)); + EXPECT_EQ(0U, SvdUtils::CreateBitMask(1, UINT64_MAX)); + const int64_t widths[] = {INT64_MIN, -1, 0, 65, int64_t{UINT32_MAX} + 2, INT64_MAX}; + for(const auto width : widths) { + SCOPED_TRACE(width); + EXPECT_EQ(0U, SvdUtils::CreateBitMask(width)); + } +} + +TEST_F(SvdBitWidthTest, OutputMasksStayWithin32Bits) { + EXPECT_EQ(UINT32_MAX, SvdUtils::CreateBitMask32(32)); + EXPECT_EQ(UINT32_C(0x7fffffff), SvdUtils::CreateBitMask32(31)); + EXPECT_EQ(UINT32_C(0x80000000), SvdUtils::CreateBitMask32(1, 31)); + EXPECT_EQ(UINT32_C(0xfffffffe), SvdUtils::CreateBitMask32(31, 1)); + EXPECT_EQ(0U, SvdUtils::CreateBitMask32(0)); + EXPECT_EQ(0U, SvdUtils::CreateBitMask32(33)); + EXPECT_EQ(0U, SvdUtils::CreateBitMask32(UINT32_MAX)); + EXPECT_EQ(0U, SvdUtils::CreateBitMask32(2, 31)); + EXPECT_EQ(0U, SvdUtils::CreateBitMask32(1, 32)); + EXPECT_EQ(0U, SvdUtils::CreateBitMask32(1, UINT32_MAX)); +} + +TEST_F(SvdBitWidthTest, FullWidthFieldsPreserveEnumsAndAccessMasks) { + constexpr int32_t width = 32; + XMLTreeElement element(nullptr, "register"); + const auto fields = ConfigureRegister(element, to_string(width)); + const auto fieldElement = AddField(fields, "FULL", "0", to_string(width)); + fieldElement->CreateElement("access", "read-only"); + AddEnum(fieldElement, "4294967295"); + SvdRegister reg(nullptr); + + ASSERT_TRUE(reg.Construct(&element)); + ASSERT_TRUE(reg.IsValid()); + ASSERT_NE(nullptr, reg.GetFieldContainer()); + ASSERT_EQ(1U, reg.GetFieldContainer()->GetChildCount()); + const auto field = dynamic_cast(reg.GetFieldContainer()->GetChildren().front()); + ASSERT_NE(nullptr, field); + EXPECT_TRUE(field->IsValid()); + ASSERT_EQ(1U, field->GetEnumContainer().size()); + const auto enumContainer = field->GetEnumContainer().front(); + ASSERT_EQ(1U, enumContainer->GetChildCount()); + EXPECT_TRUE(enumContainer->GetChildren().front()->IsValid()); + EXPECT_FALSE(HasMessage("M335")); + + const uint64_t expectedMask = UINT32_MAX; + EXPECT_EQ(expectedMask, reg.GetAccessMask()); + EXPECT_EQ(expectedMask, reg.GetAccessMaskRead()); + EXPECT_EQ(0U, reg.GetAccessMaskWrite()); + EXPECT_EQ(SvdTypes::Access::READONLY, reg.GetAccessCalculated()); +} + +TEST_F(SvdBitWidthTest, UpperBoundaryFieldsProduceOnlyTheirOwnWriteBit) { + constexpr int32_t width = 32; + XMLTreeElement element(nullptr, "register"); + const auto fields = ConfigureRegister(element, to_string(width)); + AddField(fields, "TOP", to_string(width - 1), "1"); + SvdRegister reg(nullptr); + + ASSERT_TRUE(reg.Construct(&element)); + ASSERT_TRUE(reg.IsValid()); + ASSERT_NE(nullptr, reg.GetFieldContainer()); + EXPECT_TRUE(reg.GetFieldContainer()->GetChildren().front()->IsValid()); + EXPECT_EQ(uint64_t{1} << (width - 1), reg.GetAccessMaskWrite()); + EXPECT_EQ(uint64_t{UINT32_MAX}, reg.GetAccessMaskRead()); +} + +TEST_F(SvdBitWidthTest, FullWidth32BitWriteMaskDoesNotSignExtend) { + XMLTreeElement element(nullptr, "register"); + const auto fields = ConfigureRegister(element, "32"); + AddField(fields, "FULL", "0", "32"); + SvdRegister reg(nullptr); + + ASSERT_TRUE(reg.Construct(&element)); + ASSERT_TRUE(reg.IsValid()); + EXPECT_EQ(uint64_t{UINT32_MAX}, reg.GetAccessMask()); + EXPECT_EQ(uint64_t{UINT32_MAX}, reg.GetAccessMaskRead()); + EXPECT_EQ(uint64_t{UINT32_MAX}, reg.GetAccessMaskWrite()); +} + +TEST_F(SvdBitWidthTest, InvalidFieldWidthsAreRejectedBeforeEnumChecks) { + struct WidthCase { + const char* width; + const char* diagnostic; + }; + for(const auto& input : {WidthCase{"0", "M310"}, WidthCase{"65", "M310"}, + WidthCase{"129", "M310"}, WidthCase{"4294967294", "M313"}, + WidthCase{"-2", "M310"}}) { + SCOPED_TRACE(input.width); + m_messages.clear(); + XMLTreeElement element(nullptr, "register"); + const auto fields = ConfigureRegister(element, "32"); + AddEnum(AddField(fields, "BAD", "0", input.width), "1"); + AddField(fields, "VALID", "1", "1"); + SvdRegister reg(nullptr); + + ASSERT_TRUE(reg.Construct(&element)); + ASSERT_TRUE(reg.IsValid()); + ASSERT_NE(nullptr, reg.GetFieldContainer()); + ASSERT_EQ(2U, reg.GetFieldContainer()->GetChildCount()); + EXPECT_FALSE(reg.GetFieldContainer()->GetChildren().front()->IsValid()); + EXPECT_TRUE(reg.GetFieldContainer()->GetChildren().back()->IsValid()); + EXPECT_TRUE(HasMessage(input.diagnostic)); + EXPECT_FALSE(HasMessage("M335")); + EXPECT_EQ(2U, reg.GetAccessMaskWrite()); + } +} + +TEST_F(SvdBitWidthTest, FieldsOutsideOffsetAndRegisterBoundsAreRejected) { + struct RangeCase { + const char* registerWidth; + const char* offset; + const char* width; + const char* diagnostic; + }; + for(const auto& input : {RangeCase{"32", "64", "1", "M309"}, + RangeCase{"32", "18446744073709551614", "1", "M309"}, + RangeCase{"32", "31", "2", "M324"}}) { + SCOPED_TRACE(input.offset); + SCOPED_TRACE(input.width); + m_messages.clear(); + XMLTreeElement element(nullptr, "register"); + const auto fields = ConfigureRegister(element, input.registerWidth); + AddEnum(AddField(fields, "BAD", input.offset, input.width), "1"); + AddField(fields, "VALID", "0", "1"); + SvdRegister reg(nullptr); + + ASSERT_TRUE(reg.Construct(&element)); + ASSERT_NE(nullptr, reg.GetFieldContainer()); + ASSERT_EQ(2U, reg.GetFieldContainer()->GetChildCount()); + EXPECT_FALSE(reg.GetFieldContainer()->GetChildren().front()->IsValid()); + EXPECT_TRUE(reg.GetFieldContainer()->GetChildren().back()->IsValid()); + EXPECT_TRUE(HasMessage(input.diagnostic)); + EXPECT_FALSE(HasMessage("M335")); + EXPECT_EQ(1U, reg.GetAccessMaskWrite()); + } +} + +TEST_F(SvdBitWidthTest, AccessMaskSkipsInvalidAndUncheckedFieldRanges) { + SvdRegister reg(nullptr); + reg.SetBitWidth(32); + reg.SetAccess(SvdTypes::Access::READWRITE); + AddUncheckedField(reg, 31, 1); + AddUncheckedField(reg, 0, 0); + AddUncheckedField(reg, 0, 65); + AddUncheckedField(reg, 0, -2); + AddUncheckedField(reg, 64, 1); + AddUncheckedField(reg, UINT64_MAX - 1, 1); + AddUncheckedField(reg, 31, 2); + AddUncheckedField(reg, 0, 64)->Invalidate(); + + EXPECT_TRUE(reg.CalcAccessMask()); + EXPECT_EQ(uint64_t{1} << 31, reg.GetAccessMaskWrite()); + EXPECT_EQ(uint64_t{UINT32_MAX}, reg.GetAccessMaskRead()); + EXPECT_EQ(SvdTypes::Access::READWRITE, reg.GetAccessCalculated()); +} + +TEST_F(SvdBitWidthTest, InvalidRegisterWidthsAreRejectedBeforeMaskCalculations) { + for(const auto* width : {"0", "65", "4294967294"}) { + SCOPED_TRACE(width); + m_messages.clear(); + XMLTreeElement element(nullptr, "register"); + const auto fields = ConfigureRegister(element, width); + AddField(fields, "BIT", "0", "1"); + SvdRegister reg(nullptr); + + ASSERT_TRUE(reg.Construct(&element)); + EXPECT_FALSE(reg.IsValid()); + EXPECT_EQ(1, count(m_messages.begin(), m_messages.end(), "M302")); + EXPECT_EQ(0U, reg.GetAccessMask()); + EXPECT_EQ(0U, reg.GetAccessMaskRead()); + EXPECT_EQ(0U, reg.GetAccessMaskWrite()); + } +} + +TEST_F(SvdBitWidthTest, MaskQueriesHandleUncheckedInvalidRegisterWidths) { + for(const auto width : {0, 65, -2}) { + SCOPED_TRACE(width); + SvdRegister reg(nullptr); + reg.SetBitWidth(width); + reg.SetAccess(SvdTypes::Access::READWRITE); + AddUncheckedField(reg, 0, 1); + + EXPECT_EQ(0U, reg.GetAccessMask()); + EXPECT_FALSE(reg.CalcAccessMask()); + EXPECT_EQ(0U, reg.GetAccessMaskRead()); + EXPECT_EQ(0U, reg.GetAccessMaskWrite()); + EXPECT_EQ(SvdTypes::Access::READWRITE, reg.GetAccessCalculated()); + } +} + +TEST_F(SvdBitWidthTest, EnumValuesAboveTheFieldWidthRemainInvalid) { + XMLTreeElement element(nullptr, "register"); + const auto fields = ConfigureRegister(element, "32"); + AddEnum(AddField(fields, "VALUE", "0", "31"), "2147483648"); + SvdRegister reg(nullptr); + + ASSERT_TRUE(reg.Construct(&element)); + ASSERT_NE(nullptr, reg.GetFieldContainer()); + const auto field = dynamic_cast(reg.GetFieldContainer()->GetChildren().front()); + ASSERT_NE(nullptr, field); + ASSERT_EQ(1U, field->GetEnumContainer().size()); + const auto enumContainer = field->GetEnumContainer().front(); + ASSERT_EQ(1U, enumContainer->GetChildCount()); + EXPECT_FALSE(enumContainer->GetChildren().front()->IsValid()); + EXPECT_TRUE(HasMessage("M335")); +} + +TEST_F(SvdBitWidthTest, FullWidth32BitRegisterWithoutFieldsPreservesAllAccessBits) { + SvdRegister reg(nullptr); + reg.SetBitWidth(32); + EXPECT_EQ(uint64_t{UINT32_MAX}, reg.GetAccessMaskRead()); + EXPECT_EQ(uint64_t{UINT32_MAX}, reg.GetAccessMaskWrite()); +} + +TEST_F(SvdBitWidthTest, FieldValidationHandlesMissingRegisterAncestors) { + SvdFieldContainer fields(nullptr); + SvdDimension dimension(nullptr); + SvdItem* parents[] = {&fields, &dimension}; + for(const auto parent : parents) { + SvdField field(parent); + field.SetName("BIT"); + field.SetOffset(0); + field.SetBitWidth(1); + EXPECT_TRUE(field.CheckItem()); + EXPECT_TRUE(field.IsValid()); + } +} + +TEST_F(SvdBitWidthTest, HeaderFieldGroupsDetectFullWidthAndUpperBitOverlaps) { + SvdOptions options; + FileHeaderInfo info; + BitWidthHeaderData header(info, options); + SvdRegister reg(nullptr); + reg.SetBitWidth(32); + const auto full = AddUncheckedField(reg, 0, 32); + const auto upper = AddUncheckedField(reg, 31, 1); + const auto lower = AddUncheckedField(reg, 0, 1); + FIELDMAPLIST groups; + ASSERT_TRUE(header.AddFields(reg.GetFieldContainer(), groups)); + ASSERT_EQ(2U, groups.size()); + EXPECT_EQ(UINT32_MAX, groups.front().mask); + EXPECT_EQ(full, groups.front().fields.at(0)); + EXPECT_EQ(UINT32_C(0x80000001), groups.back().mask); + ASSERT_EQ(2U, groups.back().fields.size()); + EXPECT_EQ(lower, groups.back().fields.begin()->second); + EXPECT_EQ(upper, groups.back().fields.rbegin()->second); + EXPECT_EQ(lower, groups.back().fields.at(0)); + EXPECT_EQ(upper, groups.back().fields.at(31)); +} + +TEST_F(SvdBitWidthTest, HeaderFieldPaddingRejectsOverlapsAndOutOfRangePositions) { + SvdOptions options; + FileHeaderInfo info; + BitWidthHeaderData header(info, options); + SvdRegister reg(nullptr); + reg.SetBitWidth(32); + + FIELDMAPLIST groups(1); + auto& fields = groups.front().fields; + fields.emplace(UINT32_MAX, AddUncheckedField(reg, UINT32_MAX, 1)); + EXPECT_FALSE(header.CreateSortedFields(groups, 4, "bits")); + + fields.clear(); + fields.emplace(0, AddUncheckedField(reg, 0, 2)); + fields.emplace(1, AddUncheckedField(reg, 1, 1)); + EXPECT_FALSE(header.CreateSortedFields(groups, 4, "bits")); + + fields.clear(); + fields.emplace(0, AddUncheckedField(reg, 0, 9)); + EXPECT_FALSE(header.CreateSortedFields(groups, 1, "bits")); + EXPECT_FALSE(header.CreateSortedFields(groups, 8, "bits")); +} + +TEST_F(SvdBitWidthTest, HeaderFieldsPropagateRejectedRegisterLayouts) { + SvdOptions options; + FileHeaderInfo info; + BitWidthHeaderData header(info, options); + SvdRegister reg(nullptr); + AddUncheckedField(reg, 7, 2); + + reg.SetBitWidth(8); + EXPECT_FALSE(header.CreateFields(®, "bits")); + reg.SetBitWidth(64); + EXPECT_FALSE(header.CreateFields(®, "bits")); + reg.SetBitWidth(32); + EXPECT_TRUE(header.CreateFields(®, "bits")); +} + +TEST_F(SvdBitWidthTest, HeaderFieldsEmitBoundedPaddingAndRejectInvalidFieldFragments) { + CreateOutputDirectory(); + const auto inputFile = m_outputDirectory / "input.svd"; + { + ofstream input(inputFile); + input << "\n"; + ASSERT_TRUE(input.good()); + } + SvdDevice device(nullptr); + device.SetName("TEST"); + SvdRegister reg(nullptr); + reg.SetBitWidth(32); + AddUncheckedField(reg, 2, 3)->SetName("ACTIVE"); + SvdOptions options; + FileHeaderInfo info; + info.svdFileName = inputFile.string(); + const auto output = m_outputDirectory / "masks.h"; + { + BitWidthHeaderData header(info, options); + ASSERT_TRUE(header.Create(&device, output.string())); + EXPECT_TRUE(header.CreateFields(®, "bits")); + + SvdField field(nullptr); + field.SetName("REJECTED"); + const pair invalidRanges[] = { + {0, 0}, {0, -1}, {0, 33}, {0, 64}, {31, 2}, {32, 1}, + {UINT64_C(0x10000001f), 1}, + }; + for(const auto& [offset, width] : invalidRanges) { + SCOPED_TRACE(to_string(offset) + ":" + to_string(width)); + field.SetOffset(offset); + field.SetBitWidth(width); + EXPECT_EQ(0U, header.CreateField(&field, 4, 0)); + } + field.SetName("UPPER"); + field.SetOffset(31); + field.SetBitWidth(1); + field.SetAccess(SvdTypes::Access::READWRITE); + EXPECT_EQ(1U, header.CreateField(&field, 4, 31)); + } + ifstream input(output); + ASSERT_TRUE(input.is_open()); + const string text{istreambuf_iterator(input), istreambuf_iterator()}; + istringstream tokens(text); + string token, compact; + while(tokens >> token) { + compact += token; + } + const auto leading = compact.find("uint32_t:2;"); + const auto active = compact.find("__IOMuint32_tACTIVE:3;"); + const auto trailing = compact.find("uint32_t:27;"); + ASSERT_NE(string::npos, leading); + ASSERT_NE(string::npos, active); + ASSERT_NE(string::npos, trailing); + EXPECT_LT(leading, active); + EXPECT_LT(active, trailing); + EXPECT_NE(string::npos, compact.find("__IOMuint32_tUPPER:1;")); + EXPECT_EQ(string::npos, text.find("REJECTED")); + EXPECT_EQ(string::npos, text.find("Reserved bits calculation error")); +} + +TEST_F(SvdBitWidthTest, HeaderMasksPreserve32BitBoundaryTextAndRejectWiderRanges) { + CreateOutputDirectory(); + FileIo output; + output.SetFileName((m_outputDirectory / "masks.h").string()); + BitWidthHeaderGenerator generator(&output); + generator.MakeFieldPosMask3("FULL32", "Full 32-bit field", 0, UINT32_MAX); + generator.MakeFieldPosMask3("TOP32", "Top 32-bit field", 31, 1); + ASSERT_TRUE(output.Flush()); + ifstream input(m_outputDirectory / "masks.h"); + ASSERT_TRUE(input.is_open()); + const string text{istreambuf_iterator(input), istreambuf_iterator()}; + EXPECT_EQ("(0xffffffffUL)", DefineValue(text, "FULL32_Msk")); + EXPECT_EQ("(0x80000000UL)", DefineValue(text, "TOP32_Msk")); + EXPECT_EQ("(0UL)", DefineValue(text, "FULL32_Pos")); + EXPECT_EQ("(31UL)", DefineValue(text, "TOP32_Pos")); + + SvdOptions options; + FileHeaderInfo info; + BitWidthHeaderData header(info, options); + SvdRegister reg(nullptr); + reg.SetBitWidth(32); + const auto full = AddUncheckedField(reg, 0, 32); + PosMaskNames names; + EXPECT_TRUE(header.CreateFieldPosMask(full, &names)); + EXPECT_FALSE(HasMessage("M220")); + EXPECT_FALSE(header.CreateFieldPosMask(AddUncheckedField(reg, 31, 2), &names)); + EXPECT_FALSE(header.CreateFieldPosMask(AddUncheckedField(reg, 0, 33), &names)); + EXPECT_FALSE(header.CreateFieldPosMask(AddUncheckedField(reg, 0, 64), &names)); + EXPECT_FALSE(header.CreateFieldPosMask(AddUncheckedField(reg, UINT64_C(0x100000000), 1), &names)); + EXPECT_FALSE(header.CreateFieldPosMask(AddUncheckedField(reg, UINT64_C(0x10000001f), 1), &names)); + EXPECT_FALSE(header.CreateFieldPosMask(AddUncheckedField(reg, UINT64_MAX, 1), &names)); +} + +TEST_F(SvdBitWidthTest, FieldExpressionsPreserve32BitBoundaryTextAndRejectWiderRanges) { + SvdRegister reg(nullptr); + reg.SetName("CTRL"); + reg.SetBitWidth(32); + SvdCExpression expression; + EXPECT_EQ("(CTRL & (0xffffffff << 0))", expression.CreateFieldExpression(AddUncheckedField(reg, 0, 32))); + EXPECT_EQ("(CTRL & (0x1 << 31))", expression.CreateFieldExpression(AddUncheckedField(reg, 31, 1))); + EXPECT_TRUE(expression.CreateFieldExpression(AddUncheckedField(reg, 31, 2)).empty()); + EXPECT_TRUE(expression.CreateFieldExpression(AddUncheckedField(reg, 32, 1)).empty()); + EXPECT_TRUE(expression.CreateFieldExpression(AddUncheckedField(reg, 0, 33)).empty()); + EXPECT_TRUE(expression.CreateFieldExpression(AddUncheckedField(reg, 0, 64)).empty()); + EXPECT_TRUE(expression.CreateFieldExpression(AddUncheckedField(reg, 64, 1)).empty()); + EXPECT_TRUE(expression.CreateFieldExpression(AddUncheckedField(reg, 0, 65)).empty()); + EXPECT_TRUE(expression.CreateFieldExpression(AddUncheckedField(reg, 0, 0)).empty()); + EXPECT_TRUE(expression.CreateFieldExpression(AddUncheckedField(reg, UINT64_C(0x100000000), 1)).empty()); +} + +TEST_F(SvdBitWidthTest, InvalidFieldOperandsRejectTheWholeExpression) { + for(const auto text : {"TEST.CTRL.FIELD == 0", "0 == TEST.CTRL.FIELD", + "TEST.CTRL == 0 || TEST.CTRL.FIELD == 0"}) { + SCOPED_TRACE(text); + SvdDevice device(nullptr); + const auto [peripheral, reg] = AddExpressionRegister(device); + const auto field = AddUncheckedField(*reg, 0, 1); + SvdCExpression expression; + expression.SetText(text); + ASSERT_TRUE(expression.CalcExpression(peripheral)); + ASSERT_FALSE(expression.GetExpressionString().empty()); + + field->SetBitWidth(0); + ASSERT_TRUE(field->IsValid()); + EXPECT_TRUE(expression.GetExpressionString().empty()); + + field->SetBitWidth(1); + field->Invalidate(); + EXPECT_TRUE(expression.GetExpressionString().empty()); + } +} + +TEST_F(SvdBitWidthTest, InvalidRegisterOperandsRejectTheWholeExpression) { + SvdDevice device(nullptr); + const auto [peripheral, reg] = AddExpressionRegister(device); + SvdCExpression expression; + expression.SetText("0 == TEST.CTRL"); + ASSERT_TRUE(expression.CalcExpression(peripheral)); + ASSERT_EQ("0 == TEST_CTRL ", expression.GetExpressionString()); + reg->Invalidate(); + EXPECT_TRUE(expression.GetExpressionString().empty()); +} + +TEST_F(SvdBitWidthTest, ExpressionLookupRejectsRegistersWithoutFieldContainers) { + SvdDevice device(nullptr); + const auto [peripheral, reg] = AddExpressionRegister(device); + ASSERT_TRUE(reg->GetChildren().empty()); + + SvdCExpression expression; + expression.SetText("TEST.CTRL.FIELD == 0"); + EXPECT_FALSE(expression.CalcExpression(peripheral)); + EXPECT_FALSE(expression.IsValid()); + EXPECT_TRUE(HasMessage("M244")); +} + +TEST_F(SvdBitWidthTest, ExpressionLookupRejectsPeripheralsWithoutRegisterContainers) { + SvdDevice device(nullptr); + const auto peripheral = AddExpressionRegister(device).first; + const auto peripherals = peripheral->GetParent(); + ASSERT_NE(peripherals, nullptr); + const auto empty = new SvdPeripheral(peripherals); + peripherals->AddItem(empty); + empty->SetName("EMPTY"); + ASSERT_TRUE(empty->GetChildren().empty()); + + SvdCExpression expression; + expression.SetText("EMPTY.CTRL == 0"); + EXPECT_FALSE(expression.CalcExpression(peripheral)); + EXPECT_FALSE(expression.IsValid()); + EXPECT_TRUE(HasMessage("M244")); +} + +TEST_F(SvdBitWidthTest, SfdChecksActualFieldReferencesBeforeCreatingOutput) { + CreateOutputDirectory(); + for(const bool invalid : {false, true}) { + SCOPED_TRACE(invalid); + m_messages.clear(); + SvdDevice device(nullptr); + const auto [peripheral, reg] = AddExpressionRegister(device); + const auto field = AddUncheckedField(*reg, invalid ? 0 : 32, 1); + if(invalid) { + field->Invalidate(); + } + XMLTreeElement condition(nullptr, "disableCondition"); + condition.SetText("TEST.CTRL.FIELD == 0"); + ASSERT_TRUE(peripheral->ProcessXmlElement(&condition)); + ASSERT_TRUE(peripheral->CalcDisableCondition()); + ASSERT_TRUE(peripheral->GetDisableCondition()->IsValid()); + ASSERT_TRUE(reg->IsValid()); + + SvdOptions options; + FileHeaderInfo info; + SfdData sfd(info, options); + const auto output = m_outputDirectory / "device.sfd"; + EXPECT_FALSE(sfd.Create(&device, output.string())); + EXPECT_TRUE(HasMessage("M226")); + EXPECT_FALSE(filesystem::exists(output)); + } +} + +TEST_F(SvdBitWidthTest, SfdPreservesValidConditionsWithUnreferencedInvalidFields) { + CreateOutputDirectory(); + const auto inputFile = m_outputDirectory / "input.svd"; + { + ofstream input(inputFile); + input << "\n"; + ASSERT_TRUE(input.good()); + } + SvdDevice device(nullptr); + const auto [peripheral, reg] = AddExpressionRegister(device); + AddUncheckedField(*reg, 0, 1); + const auto unused = AddUncheckedField(*reg, 0, 0); + unused->SetName("UNUSED"); + unused->Invalidate(); + XMLTreeElement condition(nullptr, "disableCondition"); + condition.SetText("TEST.CTRL.FIELD == 0"); + ASSERT_TRUE(peripheral->ProcessXmlElement(&condition)); + ASSERT_TRUE(peripheral->CalcDisableCondition()); + ASSERT_TRUE(peripheral->GetDisableCondition()->IsValid()); + EXPECT_EQ("(TEST_CTRL & (0x1 << 0)) == 0 ", peripheral->GetDisableCondition()->GetExpressionString()); + + SvdOptions options; + FileHeaderInfo info; + info.svdFileName = inputFile.string(); + const auto output = m_outputDirectory / "device.sfd"; + { + SfdData sfd(info, options); + EXPECT_TRUE(sfd.Create(&device, output.string())); + } + EXPECT_FALSE(HasMessage("M226")); + ifstream input(output); + ASSERT_TRUE(input.is_open()); + const string text{istreambuf_iterator(input), istreambuf_iterator()}; + EXPECT_NE(string::npos, text.find(" (TEST_CTRL & (0x1 << 0)) == 0")); +} + +TEST_F(SvdBitWidthTest, SfdRejectsUnsupported64BitRegisterBeforeCreatingOutput) { + CreateOutputDirectory(); + SvdDevice device(nullptr); + const auto reg = new SvdRegister(&device); + device.AddItem(reg); + reg->SetName("CTRL"); + reg->SetBitWidth(64); + SvdOptions options; + FileHeaderInfo info; + SfdData sfd(info, options); + const auto output = m_outputDirectory / "device.sfd"; + EXPECT_FALSE(sfd.Create(&device, output.string())); + EXPECT_TRUE(HasMessage("M226")); + EXPECT_FALSE(filesystem::exists(output)); +} + +TEST_F(SvdBitWidthTest, SfdFieldMasksAccept32BitsAndRejectWiderRanges) { + SvdOptions options; + FileHeaderInfo info; + BitWidthSfdData sfd(info, options); + SvdRegister reg(nullptr); + reg.SetBitWidth(32); + EXPECT_TRUE(sfd.CreateFieldItem(AddUncheckedField(reg, 0, 32))); + EXPECT_TRUE(sfd.CreateFieldItem(AddUncheckedField(reg, 31, 1))); + EXPECT_FALSE(HasMessage("M226")); + EXPECT_FALSE(sfd.CreateFieldItem(AddUncheckedField(reg, 0, 64))); + EXPECT_FALSE(sfd.CreateFieldItem(AddUncheckedField(reg, 32, 1))); + EXPECT_TRUE(HasMessage("M226")); +} + +TEST_F(SvdBitWidthTest, SfdEnumListsCheckWidthsBeforeEnumeratingValues) { + SvdOptions options; + FileHeaderInfo info; + BitWidthSfdData sfd(info, options); + SvdField field(nullptr); + SvdEnumContainer values(&field); + field.SetBitWidth(32); + EXPECT_TRUE(sfd.CreateEnumValueSet(&values, &field)); + + const auto value = new SvdEnum(&values); + values.AddItem(value); + value->SetValue(0); + for(const auto width : {0, 7, 31, 32, 64}) { + SCOPED_TRACE(width); + field.SetBitWidth(width); + ASSERT_FALSE(sfd.CreateEnumValueSet(&values, &field)); + } + for(const auto width : {1, 6}) { + SCOPED_TRACE(width); + field.SetBitWidth(width); + EXPECT_TRUE(sfd.CreateEnumValueSet(&values, &field)); + } +} + +TEST_F(SvdBitWidthTest, SfdRejectsInvalidExpressionReferencesBeforeCreatingOutput) { + CreateOutputDirectory(); + SvdDevice device(nullptr); + const auto reg = new SvdRegister(&device); + device.AddItem(reg); + reg->SetName("CTRL"); + reg->SetBitWidth(1024); + reg->Invalidate(); + device.GetExpressionRegistersList()["CTRL"] = reg; + SvdOptions options; + FileHeaderInfo info; + SfdData sfd(info, options); + const auto output = m_outputDirectory / "device.sfd"; + EXPECT_FALSE(sfd.Create(&device, output.string())); + EXPECT_TRUE(HasMessage("M226")); + EXPECT_FALSE(filesystem::exists(output)); +} + +TEST_F(SvdBitWidthTest, HeaderFieldGroupsSkipUncheckedInvalidRanges) { + SvdOptions options; + FileHeaderInfo info; + BitWidthHeaderData header(info, options); + SvdRegister reg(nullptr); + reg.SetBitWidth(32); + AddUncheckedField(reg, 0, 0); + AddUncheckedField(reg, 0, -1); + AddUncheckedField(reg, 0, INT32_MIN); + AddUncheckedField(reg, 0, 33); + AddUncheckedField(reg, 0, 64); + AddUncheckedField(reg, 0, 65); + AddUncheckedField(reg, 32, 1); + AddUncheckedField(reg, 64, 1); + AddUncheckedField(reg, 31, 2); + AddUncheckedField(reg, UINT64_C(0x100000000), 1); + AddUncheckedField(reg, UINT64_C(0x10000001f), 1); + AddUncheckedField(reg, UINT64_MAX, 1); + const auto upper = AddUncheckedField(reg, 31, 1); + + FIELDMAPLIST groups; + ASSERT_TRUE(header.AddFields(reg.GetFieldContainer(), groups)); + ASSERT_EQ(1U, groups.size()); + EXPECT_EQ(UINT32_C(0x80000000), groups.front().mask); + ASSERT_EQ(1U, groups.front().fields.size()); + EXPECT_EQ(upper, groups.front().fields.at(31)); +} + +TEST_F(SvdBitWidthTest, OutputRangesRejectInvalidModelValuesBeforeConversion) { + uint32_t offset = 0, width = 0; + ASSERT_TRUE(SvdUtils::GetBitRange32(0, 32, offset, width)); + EXPECT_EQ(0U, offset); + EXPECT_EQ(32U, width); + ASSERT_TRUE(SvdUtils::GetBitRange32(31, 1, offset, width)); + EXPECT_EQ(31U, offset); + EXPECT_EQ(1U, width); + + const pair invalidRanges[] = { + {0, 0}, {0, -1}, {0, INT64_MIN}, {0, 33}, {0, INT64_MAX}, + {0, int64_t{UINT32_MAX} + 2}, {31, 2}, {32, 1}, + {UINT64_C(0x100000000), 1}, {UINT64_C(0x10000001f), 1}, {UINT64_MAX, 1}, + }; + for(const auto& [rawOffset, rawWidth] : invalidRanges) { + SCOPED_TRACE(to_string(rawOffset) + ":" + to_string(rawWidth)); + EXPECT_FALSE(SvdUtils::GetBitRange32(rawOffset, rawWidth, offset, width)); + EXPECT_EQ(31U, offset); + EXPECT_EQ(1U, width); + } +} + +TEST_F(SvdBitWidthTest, SfdRejectsUnsupportedExpressionReferenceOutsideDeviceChildren) { + CreateOutputDirectory(); + // The separate owner outlives the device's non-owning expression-reference map. + const auto reg = make_unique(nullptr); + SvdDevice device(nullptr); + reg->SetName("CTRL"); + reg->SetBitWidth(64); + device.GetExpressionRegistersList()["CTRL"] = reg.get(); + SvdOptions options; + FileHeaderInfo info; + SfdData sfd(info, options); + const auto output = m_outputDirectory / "device.sfd"; + + ASSERT_TRUE(reg->IsValid()); + ASSERT_TRUE(device.GetChildren().empty()); + EXPECT_FALSE(sfd.Create(&device, output.string())); + EXPECT_TRUE(HasMessage("M226")); + EXPECT_FALSE(filesystem::exists(output)); +} + +TEST_F(SvdBitWidthTest, FullWidthFieldsStillReportIncompatibleRegisterAccess) { + constexpr int32_t width = 32; + SvdRegister reg(nullptr); + reg.SetName("CTRL"); + reg.SetBitWidth(width); + reg.SetAccess(SvdTypes::Access::READONLY); + const auto field = AddUncheckedField(reg, 0, width); + + EXPECT_TRUE(field->CheckItem()); + EXPECT_TRUE(field->IsValid()); + EXPECT_TRUE(HasMessage("M367")); + EXPECT_EQ(SvdTypes::Access::READWRITE, field->GetAccess()); +} + +TEST_F(SvdBitWidthTest, RegisterSkipsRejectedRangesBeforeCheckingSiblingConflicts) { + SvdRegister reg(nullptr); + reg.SetName("CTRL"); + reg.SetBitWidth(32); + const auto invalid = AddUncheckedField(reg, 31, 2); + const auto valid = AddUncheckedField(reg, 31, 1); + + EXPECT_TRUE(reg.CheckFields(reg.GetFieldContainer(), 32, reg.GetName())); + EXPECT_FALSE(invalid->IsValid()); + EXPECT_TRUE(valid->IsValid()); + EXPECT_EQ(1, count(m_messages.begin(), m_messages.end(), "M345")); + EXPECT_FALSE(HasMessage("M336")); + EXPECT_FALSE(HasMessage("M338")); + EXPECT_EQ(uint64_t{1} << 31, reg.GetAccessMaskWrite()); +} + +} // namespace diff --git a/tools/svdconv/test/unittests/src/SvdDimensionBoundsTest.cpp b/tools/svdconv/test/unittests/src/SvdDimensionBoundsTest.cpp new file mode 100644 index 000000000..cf4a3e876 --- /dev/null +++ b/tools/svdconv/test/unittests/src/SvdDimensionBoundsTest.cpp @@ -0,0 +1,793 @@ +/* + * Copyright (c) 2026 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "SvdItem.h" +#include "SvdAddressBlock.h" +#include "SvdCluster.h" +#include "SvdDerivedFrom.h" +#include "SvdDevice.h" +#include "SvdDimension.h" +#include "SvdEnum.h" +#include "SvdField.h" +#include "SvdInterrupt.h" +#include "SvdPeripheral.h" +#include "SvdRegister.h" +#include "ErrLog.h" +#include "XMLTree.h" + +#include "gtest/gtest.h" +#include +#include +#include +#include + +using namespace std; + +namespace { + +class SvdDimensionBoundsTest : public testing::Test, public IErrConsumer { +protected: + void SetUp() override { + auto log = ErrLog::Get(); + m_previousConsumer = log->SetErrConsumer(this); + m_previousQuiet = log->IsQuietMode(); + m_previousErrors = log->GetErrCnt(); + m_previousWarnings = log->GetWarnCnt(); + log->SetQuietMode(false); + log->ResetMsgCount(); + } + + void TearDown() override { + auto log = ErrLog::Get(); + log->SetErrConsumer(m_previousConsumer); + log->SetQuietMode(m_previousQuiet); + log->ResetMsgCount(); + for(int i = 0; i < m_previousErrors; i++) { + log->IncErrCnt(); + } + for(int i = 0; i < m_previousWarnings; i++) { + log->IncWarnCnt(); + } + } + + bool Consume(const PdscMsg& msg, const string&) override { + m_messages.push_back(msg.GetMsgNum()); + return true; + } + + static SvdDimension* Dimension(SvdItem& item, uint32_t count, uint32_t increment = 4, + const string& indices = "") { + item.SetName("ITEM%s"); + auto dim = new SvdDimension(&item); + item.SetDimension(dim); + dim->SetDim(count); + dim->SetDimIncrement(increment); + dim->SetDimIndex(indices); + return dim; + } + + static SvdAddressBlock* AddressBlock(SvdPeripheral& peripheral, uint32_t offset = 0) { + const auto block = new SvdAddressBlock(&peripheral); + block->SetOffset(offset); + block->SetSize(0x100); + block->SetUsage(SvdTypes::AddrBlockUsage::REGISTERS); + peripheral.AddAddressBlock(block); + return block; + } + + bool HasMessage(const string& id) const { + return find(m_messages.begin(), m_messages.end(), id) != m_messages.end(); + } + + vector m_messages; + +private: + IErrConsumer* m_previousConsumer = nullptr; + bool m_previousQuiet = false; + int m_previousErrors = 0; + int m_previousWarnings = 0; +}; + +TEST_F(SvdDimensionBoundsTest, NumericRangesIncludeMaximumEndpointsWithoutOverflow) { + for(const auto end : {uint32_t(INT32_MAX), UINT32_MAX}) { + for(const uint32_t count : {1U, 2U}) { + SCOPED_TRACE(end); + SCOPED_TRACE(count); + SvdRegister reg(nullptr); + const auto first = end - count + 1; + const auto dim = Dimension(reg, count, 4, to_string(first) + "-" + to_string(end)); + ASSERT_TRUE(dim->CalculateDim()); + ASSERT_EQ(count, dim->GetDimIndexList().size()); + EXPECT_EQ(to_string(first), dim->GetDimIndexList().front()); + EXPECT_EQ(to_string(end), dim->GetDimIndexList().back()); + EXPECT_FALSE(reg.HasExpansionError()); + } + } +} + +TEST_F(SvdDimensionBoundsTest, InvalidAndOversizedCountsDoNotAllocateIndices) { + for(const uint32_t count : {0U, UINT32_MAX, uint32_t(SvdItem::MAX_EXPANSION_ITEMS + 1)}) { + SCOPED_TRACE(count); + SvdRegister reg(nullptr); + const auto dim = Dimension(reg, count); + EXPECT_FALSE(reg.CalculateDim()); + EXPECT_TRUE(dim->GetDimIndexList().empty()); + EXPECT_TRUE(dim->GetChildren().empty()); + EXPECT_FALSE(reg.IsValid()); + } +} + +TEST_F(SvdDimensionBoundsTest, OversizedExplicitListIsStoppedBeforeExpansion) { + SvdRegister reg(nullptr); + string indices; + for(size_t i = 0; i <= SvdItem::MAX_EXPANSION_ITEMS; i++) { + indices += "A,"; + } + const auto dim = Dimension(reg, 2, 4, indices); + EXPECT_FALSE(reg.CalculateDim()); + EXPECT_TRUE(reg.HasExpansionError()); + EXPECT_TRUE(dim->GetDimIndexList().empty()); + EXPECT_TRUE(dim->GetChildren().empty()); + EXPECT_EQ(1, count(m_messages.begin(), m_messages.end(), "M249")); +} + +TEST_F(SvdDimensionBoundsTest, FinalExplicitIndexCannotExceedTheSharedBudget) { + SvdRegister reg(nullptr); + ASSERT_TRUE(reg.ReserveExpansion(SvdItem::MAX_EXPANSION_ITEMS - 1)); + const auto dim = Dimension(reg, 2, 4, "A,B"); + + EXPECT_FALSE(reg.CalculateDim()); + EXPECT_TRUE(reg.HasExpansionError()); + EXPECT_TRUE(dim->GetDimIndexList().empty()); + EXPECT_TRUE(dim->GetChildren().empty()); + EXPECT_EQ(1, count(m_messages.begin(), m_messages.end(), "M249")); +} + +TEST_F(SvdDimensionBoundsTest, DirectRangeCalculationRejectsInvalidCountsBeforeAllocation) { + for(const uint32_t count : {0U, UINT32_MAX, uint32_t(SvdItem::MAX_EXPANSION_ITEMS + 1)}) { + SCOPED_TRACE(count); + SvdRegister reg(nullptr); + const auto dim = Dimension(reg, count); + + EXPECT_FALSE(dim->CalculateDimIndexFromTo()); + EXPECT_TRUE(dim->GetDimIndexList().empty()); + EXPECT_EQ(count == SvdItem::MAX_EXPANSION_ITEMS + 1, reg.HasExpansionError()); + } +} + +TEST_F(SvdDimensionBoundsTest, AlphabeticRangeIsReservedBeforeAnyIndexIsAdded) { + for(const size_t remaining : {size_t{1}, size_t{2}}) { + SCOPED_TRACE(remaining); + SvdRegister reg(nullptr); + ASSERT_TRUE(reg.ReserveExpansion(SvdItem::MAX_EXPANSION_ITEMS - remaining)); + const auto dim = Dimension(reg, 2, 4, "A-B"); + + EXPECT_EQ(remaining == 2, dim->CalculateDim()); + EXPECT_EQ(remaining == 1, reg.HasExpansionError()); + if(remaining == 2) { + ASSERT_EQ(2U, dim->GetDimIndexList().size()); + EXPECT_EQ("A", dim->GetDimIndexList().front()); + EXPECT_EQ("B", dim->GetDimIndexList().back()); + } + else { + EXPECT_TRUE(dim->GetDimIndexList().empty()); + } + } +} + +TEST_F(SvdDimensionBoundsTest, ExhaustedBudgetRemainsStickyAcrossDescendants) { + SvdItem root(nullptr); + SvdRegister reg(&root); + ASSERT_TRUE(root.ReserveExpansion(SvdItem::MAX_EXPANSION_ITEMS)); + EXPECT_FALSE(reg.ReserveExpansion(1)); + EXPECT_FALSE(root.ReserveExpansion(0)); + EXPECT_FALSE(reg.ReserveExpansion(1)); + EXPECT_TRUE(root.HasExpansionError()); + EXPECT_TRUE(reg.HasExpansionError()); + EXPECT_EQ(1, count(m_messages.begin(), m_messages.end(), "M249")); +} + +TEST_F(SvdDimensionBoundsTest, InheritedMetadataMustFitTheDestinationBudget) { + SvdItem source(nullptr); + Dimension(source, 2); + source.SetDerivedFrom(new SvdDerivedFrom(&source)); + + for(const size_t remaining : {size_t{0}, size_t{1}, size_t{2}}) { + SCOPED_TRACE(remaining); + SvdItem target(nullptr); + ASSERT_TRUE(target.ReserveExpansion(SvdItem::MAX_EXPANSION_ITEMS - remaining)); + + EXPECT_EQ(remaining == 2, target.CopyItem(&source)); + EXPECT_EQ(remaining < 2, target.HasExpansionError()); + if(remaining > 0) { + ASSERT_NE(nullptr, target.GetDerivedFrom()); + EXPECT_EQ(&source, target.GetDerivedFrom()->GetDerivedFromItem()); + } + else { + EXPECT_EQ(nullptr, target.GetDerivedFrom()); + } + if(remaining == 2) { + ASSERT_NE(nullptr, target.GetDimension()); + EXPECT_EQ(2U, target.GetDimension()->GetDim()); + EXPECT_EQ(&source, target.GetCopiedFrom()); + } + else { + EXPECT_EQ(nullptr, target.GetDimension()); + EXPECT_EQ(nullptr, target.GetCopiedFrom()); + } + } +} + +TEST_F(SvdDimensionBoundsTest, CopiedPeripheralDescendantsShareOneBudget) { + SvdPeripheralContainer source(nullptr); + const auto peripheral = new SvdPeripheral(&source); + source.AddItem(peripheral); + peripheral->SetName("PERIPHERAL"); + peripheral->SetAddress(0x40000000); + const auto registers = new SvdRegisterContainer(peripheral); + peripheral->AddItem(registers); + const auto cluster = new SvdCluster(registers); + registers->AddItem(cluster); + cluster->SetName("GROUP"); + cluster->SetOffset(0); + const auto reg = new SvdRegister(cluster); + cluster->AddItem(reg); + reg->SetName("DATA"); + reg->SetOffset(4); + reg->SetBitWidth(32); + + for(const size_t remaining : {size_t{3}, size_t{4}}) { + SCOPED_TRACE(remaining); + SvdPeripheralContainer target(nullptr); + ASSERT_TRUE(target.ReserveExpansion(SvdItem::MAX_EXPANSION_ITEMS - remaining)); + + EXPECT_EQ(remaining == 4, target.CopyChilds(&source, &target)); + EXPECT_EQ(remaining == 3, target.HasExpansionError()); + ASSERT_EQ(1U, target.GetChildCount()); + const auto copiedPeripheral = target.GetChildren().front(); + ASSERT_EQ(1U, copiedPeripheral->GetChildCount()); + const auto copiedRegisters = copiedPeripheral->GetChildren().front(); + ASSERT_EQ(1U, copiedRegisters->GetChildCount()); + const auto copiedCluster = copiedRegisters->GetChildren().front(); + if(remaining == 4) { + ASSERT_EQ(1U, copiedCluster->GetChildCount()); + const auto copiedRegister = dynamic_cast(copiedCluster->GetChildren().front()); + ASSERT_NE(nullptr, copiedRegister); + EXPECT_EQ("DATA", copiedRegister->GetName()); + EXPECT_EQ(4U, copiedRegister->GetOffset()); + EXPECT_EQ(32U, copiedRegister->GetEffectiveBitWidth()); + EXPECT_EQ(0x40000004U, copiedRegister->GetAbsoluteAddress()); + } + else { + EXPECT_TRUE(copiedCluster->GetChildren().empty()); + } + } +} + +TEST_F(SvdDimensionBoundsTest, DerivationStopsAtChildAndDimensionMetadataBudgetBoundaries) { + SvdItem source(nullptr); + Dimension(source, 2)->SetDimName("SOURCE"); + const auto reg = new SvdRegister(&source); + source.AddItem(reg); + reg->SetName("DATA"); + reg->SetOffset(4); + reg->SetBitWidth(32); + + for(const size_t remaining : {size_t{0}, size_t{1}, size_t{2}}) { + SCOPED_TRACE(remaining); + SvdItem target(nullptr); + const auto derived = new SvdDerivedFrom(&target); + target.SetDerivedFrom(derived); + ASSERT_TRUE(target.ReserveExpansion(SvdItem::MAX_EXPANSION_ITEMS - remaining)); + + EXPECT_EQ(remaining == 2, derived->DeriveItem(&source)); + EXPECT_EQ(remaining < 2, target.HasExpansionError()); + EXPECT_EQ(&source, derived->GetDerivedFromItem()); + ASSERT_EQ(remaining > 0 ? 1U : 0U, target.GetChildCount()); + if(remaining == 2) { + ASSERT_NE(nullptr, target.GetDimension()); + EXPECT_EQ(2U, target.GetDimension()->GetDim()); + EXPECT_TRUE(target.GetDimension()->GetDimName().empty()); + EXPECT_EQ(&source, target.GetCopiedFrom()); + } + else { + EXPECT_EQ(nullptr, target.GetDimension()); + EXPECT_EQ(nullptr, target.GetCopiedFrom()); + } + if(remaining > 0) { + EXPECT_EQ("DATA", target.GetChildren().front()->GetName()); + EXPECT_EQ(reg, target.GetChildren().front()->GetCopiedFrom()); + } + } +} + +TEST_F(SvdDimensionBoundsTest, CopiedFieldsAndRegistersPropagateDescendantBudgetFailures) { + for(const bool isField : {false, true}) { + SCOPED_TRACE(isField); + SvdItem source(nullptr); + SvdItem* child = nullptr; + if(isField) { + child = new SvdField(&source); + child->AddItem(new SvdEnumContainer(child)); + } + else { + child = new SvdRegister(&source); + child->AddItem(new SvdFieldContainer(child)); + } + source.AddItem(child); + child->SetName("DATA"); + child->SetBitWidth(isField ? 1 : 32); + + for(const size_t remaining : {size_t{1}, size_t{2}}) { + SCOPED_TRACE(remaining); + SvdItem target(nullptr); + ASSERT_TRUE(target.ReserveExpansion(SvdItem::MAX_EXPANSION_ITEMS - remaining)); + + EXPECT_EQ(remaining == 2, target.CopyChilds(&source, &target)); + EXPECT_EQ(remaining == 1, target.HasExpansionError()); + ASSERT_EQ(1U, target.GetChildCount()); + const auto copy = target.GetChildren().front(); + EXPECT_EQ(child->GetSvdLevel(), copy->GetSvdLevel()); + ASSERT_EQ(remaining - 1, copy->GetChildCount()); + EXPECT_EQ(remaining == 2 ? child : nullptr, copy->GetCopiedFrom()); + if(remaining == 2) { + EXPECT_EQ("DATA", copy->GetName()); + EXPECT_EQ(child->GetChildren().front(), copy->GetChildren().front()->GetCopiedFrom()); + } + } + } +} + +TEST_F(SvdDimensionBoundsTest, DirectlyBuiltEnumLeafSubtreesStillRespectTheCopyBudget) { + // Direct model callers can attach children to leaf elements outside the XML schema. + SvdField sourceField(nullptr); + SvdEnumContainer source(&sourceField); + const auto value = new SvdEnum(&source); + source.AddItem(value); + value->SetName("OUTER"); + value->SetValue(1); + const auto nested = new SvdEnum(value); + value->AddItem(nested); + nested->SetName("INNER"); + nested->SetValue(2); + + for(const size_t remaining : {size_t{1}, size_t{2}}) { + SCOPED_TRACE(remaining); + SvdField targetField(nullptr); + SvdEnumContainer target(&targetField); + ASSERT_TRUE(target.ReserveExpansion(SvdItem::MAX_EXPANSION_ITEMS - remaining)); + + EXPECT_EQ(remaining == 2, target.CopyChilds(&source, &target)); + EXPECT_EQ(remaining == 1, target.HasExpansionError()); + ASSERT_EQ(1U, target.GetChildCount()); + const auto copy = target.GetChildren().front(); + ASSERT_EQ(remaining - 1, copy->GetChildCount()); + EXPECT_EQ(remaining == 2 ? value : nullptr, copy->GetCopiedFrom()); + if(remaining == 2) { + EXPECT_EQ("INNER", copy->GetChildren().front()->GetName()); + EXPECT_EQ(nested, copy->GetChildren().front()->GetCopiedFrom()); + } + } +} + +TEST_F(SvdDimensionBoundsTest, IndexBudgetIsSharedBySiblingDimensions) { + SvdItem root(nullptr); + SvdRegister first(&root), second(&root), third(&root); + const auto half = uint32_t(SvdItem::MAX_EXPANSION_ITEMS / 2); + ASSERT_TRUE(Dimension(first, half)->CalculateDimIndex()); + ASSERT_TRUE(Dimension(second, half)->CalculateDimIndex()); + EXPECT_FALSE(root.HasExpansionError()); + const auto last = Dimension(third, 1); + EXPECT_FALSE(last->CalculateDimIndex()); + EXPECT_TRUE(last->GetDimIndexList().empty()); + EXPECT_TRUE(first.HasExpansionError()); + EXPECT_TRUE(root.HasExpansionError()); +} + +TEST_F(SvdDimensionBoundsTest, RecalculationDoesNotResetBudgetAndNewInputIsIndependent) { + SvdRegister reg(nullptr); + const auto dim = Dimension(reg, uint32_t(SvdItem::MAX_EXPANSION_ITEMS / 2 + 1)); + ASSERT_TRUE(dim->CalculateDimIndex()); + EXPECT_FALSE(dim->CalculateDimIndex()); + EXPECT_TRUE(reg.HasExpansionError()); + EXPECT_TRUE(dim->GetDimIndexList().empty()); + + SvdRegister nextInput(nullptr); + const auto nextDim = Dimension(nextInput, 2); + EXPECT_TRUE(nextDim->CalculateDimIndex()); + EXPECT_EQ(2U, nextDim->GetDimIndexList().size()); + EXPECT_FALSE(nextInput.HasExpansionError()); +} + +TEST_F(SvdDimensionBoundsTest, NestedDimensionsShareCopiedDescendantBudget) { + XMLTreeElement element(nullptr, "cluster"); + element.CreateElement("name", "CLUSTER[%s]"); + element.CreateElement("description", "Cluster array."); + element.CreateElement("addressOffset", "0"); + element.CreateElement("dim", "1000"); + element.CreateElement("dimIncrement", "4096"); + auto reg = element.CreateElement("register"); + reg->CreateElement("name", "REG[%s]"); + reg->CreateElement("description", "Register array."); + reg->CreateElement("addressOffset", "0"); + reg->CreateElement("size", "32"); + reg->CreateElement("access", "read-write"); + reg->CreateElement("dim", "1000"); + reg->CreateElement("dimIncrement", "4"); + SvdCluster cluster(nullptr); + + EXPECT_FALSE(cluster.Construct(&element)); + EXPECT_TRUE(cluster.HasExpansionError()); + ASSERT_NE(nullptr, cluster.GetDimension()); + EXPECT_LT(cluster.GetDimension()->GetChildCount(), 1000U); + EXPECT_EQ(1, count(m_messages.begin(), m_messages.end(), "M249")); +} + +TEST_F(SvdDimensionBoundsTest, IncrementMultiplicationUsesWideIntermediate) { + SvdDevice device(nullptr); + XMLTreeElement unitBits(nullptr, "addressUnitBits"); + unitBits.SetText("32"); + ASSERT_TRUE(device.ProcessXmlElement(&unitBits)); + SvdRegister reg(&device); + const auto dim = Dimension(reg, 2, UINT32_MAX - 1); + EXPECT_EQ(uint64_t(UINT32_MAX - 1) * 4, dim->CalcAddressIncrement()); +} + +TEST_F(SvdDimensionBoundsTest, RegisterStrideValidationDoesNotWrapProducts) { + SvdDevice device(nullptr); + XMLTreeElement unitBits(nullptr, "addressUnitBits"); + unitBits.SetText("8"); + ASSERT_TRUE(device.ProcessXmlElement(&unitBits)); + + SvdRegister extended(&device); + extended.SetBitWidth(32); + extended.SetOffset(0); + ASSERT_TRUE(Dimension(extended, 2, 0x20000000)->CalculateDim()); + extended.CheckItem(); + EXPECT_TRUE(extended.IsValid()); + EXPECT_FALSE(HasMessage("M366")); + + SvdRegister array(&device); + array.SetBitWidth(32); + array.SetOffset(0); + const auto dim = Dimension(array, 2, 0x20000004); + array.SetName("ITEM[%s]"); + ASSERT_TRUE(dim->CalculateDim()); + array.CheckItem(); + EXPECT_FALSE(array.IsValid()); + EXPECT_TRUE(HasMessage("M378")); +} + +TEST_F(SvdDimensionBoundsTest, LastAddressIsCheckedBeforeGeneratingCopies) { + for(const uint64_t start : {UINT64_MAX - 5, UINT64_MAX - 3}) { + SvdRegister reg(nullptr); + reg.SetBitWidth(32); + reg.SetAccess(SvdTypes::Access::READWRITE); + reg.SetOffset(start); + const auto dim = Dimension(reg, 2, 4); + const bool fits = start == UINT64_MAX - 5; + EXPECT_EQ(fits, reg.CalculateDim()); + EXPECT_EQ(fits ? 2U : 0U, dim->GetChildCount()); + if(fits) { + const auto last = dynamic_cast(dim->GetChildren().back()); + ASSERT_NE(nullptr, last); + EXPECT_EQ(UINT64_MAX - 1, last->GetOffset()); + EXPECT_TRUE(last->IsValid()); + } + else { + EXPECT_FALSE(reg.IsValid()); + EXPECT_TRUE(HasMessage("M250")); + } + } +} + +TEST_F(SvdDimensionBoundsTest, ClusterAndPeripheralAddressWrapIsRejected) { + SvdCluster cluster(nullptr); + cluster.SetOffset(UINT64_MAX - 1); + const auto clusterDim = Dimension(cluster, 2, 4); + EXPECT_FALSE(cluster.CalculateDim()); + EXPECT_TRUE(clusterDim->GetChildren().empty()); + + SvdPeripheral peripheral(nullptr); + peripheral.SetAddress(UINT64_MAX - 1); + const auto peripheralDim = Dimension(peripheral, 2, 4); + EXPECT_FALSE(peripheral.CalculateDim()); + EXPECT_TRUE(peripheralDim->GetChildren().empty()); +} + +TEST_F(SvdDimensionBoundsTest, FieldDimensionsRejectInvalidCountsAndAddressWrapBeforeCopying) { + for(const bool invalidCount : {false, true}) { + SCOPED_TRACE(invalidCount); + SvdField field(nullptr); + field.SetOffset(invalidCount ? 0 : UINT64_MAX - 1); + field.SetBitWidth(1); + const auto dim = Dimension(field, invalidCount ? 0 : 2, 4); + + EXPECT_FALSE(field.CalculateDim()); + EXPECT_FALSE(field.IsValid()); + EXPECT_TRUE(dim->GetChildren().empty()); + EXPECT_FALSE(field.HasExpansionError()); + } +} + +TEST_F(SvdDimensionBoundsTest, FieldAndRegisterExpansionStopsWhenInheritedMetadataCannotBeCopied) { + for(const bool isField : {false, true}) { + SCOPED_TRACE(isField); + SvdRegister reg(nullptr); + reg.SetBitWidth(32); + reg.SetOffset(0); + SvdFieldContainer fields(®); + SvdField field(&fields); + field.SetOffset(0); + field.SetBitWidth(1); + SvdItem& fieldItem = field; + SvdItem& registerItem = reg; + SvdItem& source = isField ? fieldItem : registerItem; + const auto dim = Dimension(source, 2, isField ? 1 : 4); + source.SetDerivedFrom(new SvdDerivedFrom(&source)); + // Two indices and one copied item fit; its inherited metadata does not. + ASSERT_TRUE(source.ReserveExpansion(SvdItem::MAX_EXPANSION_ITEMS - 3)); + + EXPECT_FALSE(source.CalculateDim()); + EXPECT_TRUE(source.HasExpansionError()); + ASSERT_EQ(1U, dim->GetChildCount()); + EXPECT_EQ(nullptr, dim->GetChildren().front()->GetDerivedFrom()); + EXPECT_EQ(nullptr, dim->GetChildren().front()->GetCopiedFrom()); + } +} + +TEST_F(SvdDimensionBoundsTest, ClusterAndPeripheralExpansionCheckInheritedMetadataBudget) { + for(const size_t remaining : {size_t{3}, size_t{6}}) { + SCOPED_TRACE(remaining); + SvdCluster cluster(nullptr); + cluster.SetOffset(0); + SvdPeripheral peripheral(nullptr); + peripheral.SetAddress(0x40000000); + SvdItem* sources[] = {&cluster, &peripheral}; + + for(const auto source : sources) { + SCOPED_TRACE(source->GetSvdLevel()); + const auto dim = Dimension(*source, 2); + source->SetDerivedFrom(new SvdDerivedFrom(source)); + // Each copy needs one item and one inherited metadata object, after the two indices. + ASSERT_TRUE(source->ReserveExpansion(SvdItem::MAX_EXPANSION_ITEMS - remaining)); + + EXPECT_EQ(remaining == 6, source->CalculateDim()); + EXPECT_EQ(remaining == 3, source->HasExpansionError()); + ASSERT_EQ(remaining == 6 ? 2U : 1U, dim->GetChildCount()); + const auto first = dim->GetChildren().front(); + if(remaining == 6) { + for(const auto copy : dim->GetChildren()) { + ASSERT_NE(nullptr, copy->GetDerivedFrom()); + EXPECT_EQ(source, copy->GetDerivedFrom()->GetDerivedFromItem()); + EXPECT_EQ(source, copy->GetCopiedFrom()); + } + EXPECT_EQ("ITEM0", first->GetName()); + EXPECT_EQ("ITEM1", dim->GetChildren().back()->GetName()); + } + else { + EXPECT_EQ(nullptr, first->GetDerivedFrom()); + EXPECT_EQ(nullptr, first->GetCopiedFrom()); + } + } + } +} + +TEST_F(SvdDimensionBoundsTest, PeripheralDimensionStopsWhenAddressBlocksCannotBeCopied) { + for(const size_t remaining : {size_t{3}, size_t{6}}) { + SCOPED_TRACE(remaining); + SvdPeripheral peripheral(nullptr); + peripheral.SetAddress(0x40000000); + const auto block = AddressBlock(peripheral, 0x20); + const auto dim = Dimension(peripheral, 2, 0x1000); + ASSERT_TRUE(peripheral.ReserveExpansion(SvdItem::MAX_EXPANSION_ITEMS - remaining)); + + EXPECT_EQ(remaining == 6, peripheral.CalculateDim()); + EXPECT_EQ(remaining == 3, peripheral.HasExpansionError()); + ASSERT_EQ(remaining == 6 ? 2U : 1U, dim->GetChildCount()); + for(const auto child : dim->GetChildren()) { + const auto copy = dynamic_cast(child); + ASSERT_NE(nullptr, copy); + if(remaining == 6) { + ASSERT_EQ(1U, copy->GetAddressBlock().size()); + const auto copiedBlock = copy->GetAddressBlock().front(); + EXPECT_NE(block, copiedBlock); + EXPECT_EQ(0x20U, copiedBlock->GetOffset()); + EXPECT_EQ(0x100U, copiedBlock->GetSize()); + EXPECT_EQ(block, copiedBlock->GetCopiedFrom()); + EXPECT_EQ(&peripheral, copy->GetCopiedFrom()); + } + else { + EXPECT_TRUE(copy->GetAddressBlock().empty()); + EXPECT_EQ(nullptr, copy->GetCopiedFrom()); + } + } + EXPECT_EQ(1U, peripheral.GetAddressBlock().size()); + } +} + +TEST_F(SvdDimensionBoundsTest, PeripheralCopyPropagatesDisableConditionBudgetFailure) { + SvdCExpression expression; + expression.SetText("CONTROL.ENABLE == 0"); + SvdPeripheral source(nullptr); + source.SetName("PERIPHERAL"); + ASSERT_TRUE(source.CopyDisableCondition(&expression)); + + for(const size_t remaining : {size_t{0}, size_t{1}}) { + SCOPED_TRACE(remaining); + SvdPeripheral target(nullptr); + ASSERT_TRUE(target.ReserveExpansion(SvdItem::MAX_EXPANSION_ITEMS - remaining)); + + target.CopyItem(&source); + EXPECT_EQ(remaining == 0, target.HasExpansionError()); + if(remaining == 1) { + ASSERT_NE(nullptr, target.GetDisableCondition()); + EXPECT_NE(source.GetDisableCondition(), target.GetDisableCondition()); + EXPECT_EQ(expression.GetText(), target.GetDisableCondition()->GetText()); + EXPECT_EQ(&source, target.GetCopiedFrom()); + EXPECT_EQ("PERIPHERAL", target.GetName()); + } + else { + EXPECT_EQ(nullptr, target.GetDisableCondition()); + EXPECT_EQ(nullptr, target.GetCopiedFrom()); + } + } +} + +TEST_F(SvdDimensionBoundsTest, AddressBlockSortingStopsAtTheSharedBudgetBoundary) { + for(const size_t remaining : {size_t{1}, size_t{2}}) { + SCOPED_TRACE(remaining); + SvdPeripheral peripheral(nullptr); + const auto upper = AddressBlock(peripheral, 0x200); + const auto lower = AddressBlock(peripheral, 0); + ASSERT_TRUE(peripheral.ReserveExpansion(SvdItem::MAX_EXPANSION_ITEMS - remaining)); + map sorted; + + EXPECT_EQ(remaining == 2, peripheral.SortAddressBlocks(sorted)); + EXPECT_EQ(remaining == 1, peripheral.HasExpansionError()); + EXPECT_EQ(remaining, sorted.size()); + const auto upperCopy = sorted.find(0x200); + EXPECT_NE(sorted.end(), upperCopy); + if(upperCopy != sorted.end()) { + EXPECT_NE(upper, upperCopy->second); + EXPECT_EQ(upper, upperCopy->second->GetCopiedFrom()); + } + if(remaining == 2 && !sorted.empty()) { + EXPECT_EQ(0U, sorted.begin()->first); + EXPECT_EQ(lower, sorted.begin()->second->GetCopiedFrom()); + } + // The same cleanup used by MergeAddressBlocks releases unmerged temporary copies. + EXPECT_TRUE(peripheral.CopyMergedAddressBlocks(sorted)); + EXPECT_EQ(2U, peripheral.GetAddressBlock().size()); + } +} + +TEST_F(SvdDimensionBoundsTest, PeripheralExpansionPreservesAddressesAndStopsAtTheBudgetBoundary) { + for(const size_t remaining : {size_t{3}, size_t{4}}) { + SCOPED_TRACE(remaining); + SvdPeripheral peripheral(nullptr); + peripheral.SetAddress(0x40000000); + const auto dim = Dimension(peripheral, 2, 0x1000); + ASSERT_TRUE(peripheral.ReserveExpansion(SvdItem::MAX_EXPANSION_ITEMS - remaining)); + + EXPECT_EQ(remaining == 4, peripheral.CalculateDim()); + EXPECT_EQ(remaining == 3, peripheral.HasExpansionError()); + ASSERT_EQ(remaining - 2, dim->GetChildCount()); + const auto first = dynamic_cast(dim->GetChildren().front()); + ASSERT_NE(nullptr, first); + EXPECT_EQ("ITEM0", first->GetName()); + EXPECT_EQ(0x40000000U, first->GetAddress()); + if(remaining == 4) { + const auto last = dynamic_cast(dim->GetChildren().back()); + ASSERT_NE(nullptr, last); + EXPECT_EQ("ITEM1", last->GetName()); + EXPECT_EQ(0x40001000U, last->GetAddress()); + } + } +} + +TEST_F(SvdDimensionBoundsTest, InterruptDimensionPreservesTheLargestInitializedValue) { + SvdPeripheral peripheral(nullptr); + Dimension(peripheral, 2, 4); + SvdInterrupt irq(&peripheral); + irq.SetName("IRQ%s"); + irq.SetValue(UINT32_MAX - 2); + + ASSERT_TRUE(irq.CalculateDim()); + EXPECT_FALSE(irq.HasExpansionError()); + ASSERT_NE(nullptr, irq.GetDimension()); + const auto dim = irq.GetDimension(); + ASSERT_EQ(2U, dim->GetChildCount()); + const auto first = dynamic_cast(dim->GetChildren().front()); + const auto last = dynamic_cast(dim->GetChildren().back()); + ASSERT_NE(nullptr, first); + ASSERT_NE(nullptr, last); + EXPECT_EQ("IRQ0", first->GetName()); + EXPECT_EQ("IRQ1", last->GetName()); + EXPECT_EQ(UINT32_MAX - 2, first->GetValue()); + EXPECT_EQ(UINT32_MAX - 1, last->GetValue()); + EXPECT_FALSE(HasMessage("M250")); +} + +TEST_F(SvdDimensionBoundsTest, InterruptDimensionAccountsForCopiedMetadataIndicesAndItems) { + for(const size_t remaining : {size_t{0}, size_t{2}, size_t{3}, size_t{5}}) { + SCOPED_TRACE(remaining); + SvdPeripheral peripheral(nullptr); + Dimension(peripheral, 2); + SvdInterrupt irq(&peripheral); + irq.SetName("IRQ%s"); + irq.SetValue(16); + ASSERT_TRUE(peripheral.ReserveExpansion(SvdItem::MAX_EXPANSION_ITEMS - remaining)); + + EXPECT_EQ(remaining == 5, irq.CalculateDim()); + EXPECT_EQ(remaining < 5, peripheral.HasExpansionError()); + const auto dim = irq.GetDimension(); + if(remaining == 0) { + EXPECT_EQ(nullptr, dim); + continue; + } + ASSERT_NE(nullptr, dim); + EXPECT_EQ(remaining >= 3 ? 2U : 0U, dim->GetDimIndexList().size()); + ASSERT_EQ(remaining == 5 ? 2U : 0U, dim->GetChildCount()); + if(remaining == 5) { + const auto last = dynamic_cast(dim->GetChildren().back()); + ASSERT_NE(nullptr, last); + EXPECT_EQ("IRQ1", last->GetName()); + EXPECT_EQ(17U, last->GetValue()); + } + } +} + +TEST_F(SvdDimensionBoundsTest, DirectlyBuiltInterruptLeafSubtreesStillRespectTheCopyBudget) { + // The generic model API permits descendants even though interrupts are XML leaf elements. + for(const size_t remaining : {size_t{3}, size_t{6}}) { + SCOPED_TRACE(remaining); + SvdInterrupt irq(nullptr); + irq.SetValue(16); + const auto dim = Dimension(irq, 2, 1); + const auto value = new SvdEnum(&irq); + irq.AddItem(value); + value->SetName("CHILD"); + value->SetValue(1); + ASSERT_TRUE(irq.ReserveExpansion(SvdItem::MAX_EXPANSION_ITEMS - remaining)); + + EXPECT_EQ(remaining == 6, irq.CalculateDim()); + EXPECT_EQ(remaining == 3, irq.HasExpansionError()); + ASSERT_EQ(remaining == 6 ? 2U : 1U, dim->GetChildCount()); + uint32_t index = 0; + for(const auto copy : dim->GetChildren()) { + const auto copiedIrq = dynamic_cast(copy); + ASSERT_NE(nullptr, copiedIrq); + ASSERT_EQ(remaining == 6 ? 1U : 0U, copy->GetChildCount()); + if(remaining == 6) { + EXPECT_EQ("CHILD", copy->GetChildren().front()->GetName()); + EXPECT_EQ(value, copy->GetChildren().front()->GetCopiedFrom()); + EXPECT_EQ("ITEM" + to_string(index), copiedIrq->GetName()); + EXPECT_EQ(16U + index, copiedIrq->GetValue()); + EXPECT_EQ(index, copiedIrq->GetDimElementIndex()); + } + else { + EXPECT_TRUE(copiedIrq->GetName().empty()); + EXPECT_EQ(SvdItem::VALUE32_NOT_INIT, copiedIrq->GetValue()); + EXPECT_EQ(SvdItem::VALUE32_NOT_INIT, copiedIrq->GetDimElementIndex()); + } + index++; + } + } +} + +TEST_F(SvdDimensionBoundsTest, InterruptDimensionCannotWrapItsValue) { + SvdPeripheral peripheral(nullptr); + Dimension(peripheral, 3, 4); + SvdInterrupt irq(&peripheral); + irq.SetName("IRQ%s"); + irq.SetValue(UINT32_MAX - 2); + EXPECT_FALSE(irq.CalculateDim()); + EXPECT_FALSE(irq.IsValid()); + ASSERT_NE(nullptr, irq.GetDimension()); + EXPECT_TRUE(irq.GetDimension()->GetChildren().empty()); +} + +} // namespace