diff --git a/server/src/Tests.cpp b/server/src/Tests.cpp index 7026bdb7..e8cb5d9a 100644 --- a/server/src/Tests.cpp +++ b/server/src/Tests.cpp @@ -252,7 +252,7 @@ std::shared_ptr KTestObjectParser::structView(const std::vector size_t offsetInBits, types::PointerUsage usage) { std::vector tmpInitReferences; - return structView(byteArray, curStruct, offsetInBits, usage, {}, "", {}, tmpInitReferences); + return structView(byteArray, curStruct, offsetInBits, usage, {}, false, "", {}, tmpInitReferences); } std::shared_ptr KTestObjectParser::structView(const std::vector &byteArray, @@ -260,25 +260,73 @@ std::shared_ptr KTestObjectParser::structView(const std::vector size_t offsetInBits, PointerUsage usage, const std::optional &testingMethod, + const bool anonymousField, const std::string &name, const MapAddressName &fromAddressToName, std::vector &initReferences) { std::vector> subViews; + size_t fieldIndexToInitUnion = SIZE_MAX; + size_t sizeOfFieldToInitUnion = 0; + size_t prevFieldEndOffset = offsetInBits; + size_t structEndOffset = offsetInBits + curStruct.size; + size_t fieldIndex = 0; + bool dirtyInitializedStruct = false; for (const auto &field: curStruct.fields) { + bool dirtyInitializedField = false; size_t fieldLen = typesHandler.typeSize(field.type); - size_t fieldOffset = offsetInBits + field.offset; + size_t fieldStartOffset = offsetInBits + field.offset; + size_t fieldEndOffset = fieldStartOffset + fieldLen; + if (curStruct.subType == types::SubType::Union) { + prevFieldEndOffset = offsetInBits; + } + + auto dirtyCheck = [&](int i) { + if (i >= byteArray.size()) { + LOG_S(ERROR) << "Bad type size info: " << field.name << " index: " << fieldIndex; + } else if (byteArray[i] == 0) { + return false; + } + // the field cannot init the union in this state + dirtyInitializedField = true; + return true; + }; + + if (prevFieldEndOffset < fieldStartOffset) { + // check an alignment gap + for (int i = prevFieldEndOffset/8; i < fieldStartOffset/8; ++i) { + if (dirtyCheck(i)) { + break; + } + } + } + if (!dirtyInitializedField && curStruct.subType == types::SubType::Union) { + // check the rest of the union + for (int i = fieldEndOffset/8; i < structEndOffset/8; ++i) { + if (dirtyCheck(i)) { + break; + } + } + } switch (typesHandler.getTypeKind(field.type)) { case TypeKind::STRUCT_LIKE: - subViews.push_back(structView(byteArray, typesHandler.getStructInfo(field.type), fieldOffset, usage, testingMethod, - PrinterUtils::getFieldAccess(name, field), fromAddressToName, initReferences)); + { + auto sv = structView(byteArray, typesHandler.getStructInfo(field.type), + fieldStartOffset, usage, testingMethod, field.anonymous, + PrinterUtils::getFieldAccess(name, field), fromAddressToName, + initReferences); + dirtyInitializedField |= sv->isDirtyInit(); + subViews.push_back(sv); + } break; case TypeKind::ENUM: - subViews.push_back(enumView(byteArray, typesHandler.getEnumInfo(field.type), fieldOffset, fieldLen)); + subViews.push_back(enumView(byteArray, typesHandler.getEnumInfo(field.type), + fieldStartOffset, fieldLen)); break; case TypeKind::PRIMITIVE: - subViews.push_back(primitiveView(byteArray, field.type.baseTypeObj(), fieldOffset, + subViews.push_back(primitiveView(byteArray, field.type.baseTypeObj(), + fieldStartOffset, std::min(field.size, fieldLen))); break; case TypeKind::ARRAY: { @@ -296,21 +344,22 @@ std::shared_ptr KTestObjectParser::structView(const std::vector } if (onlyArrays) { size *= typesHandler.typeSize(field.type.baseTypeObj()); - subViews.push_back(multiArrayView(byteArray, field.type, size, - fieldOffset, usage)); + subViews.push_back(multiArrayView(byteArray, field.type, size, fieldStartOffset, usage)); } else { std::vector> nullViews( size, std::make_shared(PrinterUtils::C_NULL)); subViews.push_back(std::make_shared(nullViews)); } } else { - auto view = arrayView(byteArray, field.type.baseTypeObj(), fieldLen, fieldOffset, usage); + auto view = arrayView(byteArray, field.type.baseTypeObj(), fieldLen, + fieldStartOffset, usage); subViews.push_back(view); } } break; case TypeKind::OBJECT_POINTER: { - std::string res = readBytesAsValueForType(byteArray, PointerWidthType, fieldOffset, + std::string res = readBytesAsValueForType(byteArray, PointerWidthType, + fieldStartOffset, PointerWidthSizeInBits); subViews.push_back(getLazyPointerView(fromAddressToName, initReferences, PrinterUtils::getFieldAccess(name, field), res, field.type)); @@ -330,15 +379,35 @@ std::shared_ptr KTestObjectParser::structView(const std::vector LOG_S(ERROR) << message; throw NoSuchTypeException(message); } + + if (!dirtyInitializedField && sizeOfFieldToInitUnion < fieldLen) { + fieldIndexToInitUnion = fieldIndex; + sizeOfFieldToInitUnion = fieldLen; + } else { + dirtyInitializedStruct = true; + } + prevFieldEndOffset = fieldEndOffset; + ++fieldIndex; } std::optional entryValue; - if (curStruct.hasAnonymousStructOrUnion) { - auto bytesType = types::Type::createSimpleTypeFromName("utbot_byte"); - const std::shared_ptr rawDataView = arrayView(byteArray, bytesType, curStruct.size, offsetInBits, usage); - entryValue = PrinterUtils::convertBytesToUnion(curStruct.name, rawDataView->getEntryValue(nullptr)); + if (curStruct.subType == types::SubType::Union) { + if (fieldIndexToInitUnion == SIZE_MAX && !curStruct.name.empty()) { + // init by memory copy + entryValue = PrinterUtils::convertBytesToUnion( + curStruct.name, + arrayView(byteArray, + types::Type::createSimpleTypeFromName("utbot_byte"), + curStruct.size, + offsetInBits, usage)->getEntryValue(nullptr)); + dirtyInitializedStruct = false; + } + if (fieldIndexToInitUnion != SIZE_MAX) { + dirtyInitializedStruct = false; + } } - return std::make_shared(curStruct, subViews, entryValue); + return std::make_shared(curStruct, subViews, entryValue, + anonymousField, dirtyInitializedStruct, fieldIndexToInitUnion); } std::string KTestObjectParser::primitiveCharView(const types::Type &type, std::string value) { @@ -962,7 +1031,7 @@ std::shared_ptr KTestObjectParser::testParameterView( switch (typesHandler.getTypeKind(paramType)) { case TypeKind::STRUCT_LIKE: return structView(rawData, typesHandler.getStructInfo(paramType), 0, - usage, testingMethod, param.varName, fromAddressToName, initReferences); + usage, testingMethod, false, param.varName, fromAddressToName, initReferences); case TypeKind::ENUM: return enumView(rawData, typesHandler.getEnumInfo(paramType), 0, SizeUtils::bytesToBits(rawData.size())); case TypeKind::PRIMITIVE: diff --git a/server/src/Tests.h b/server/src/Tests.h index 4fa12c4d..4ebd9a4e 100644 --- a/server/src/Tests.h +++ b/server/src/Tests.h @@ -3,12 +3,12 @@ #include "Include.h" #include "LineInfo.h" +#include "NameDecorator.h" #include "types/Types.h" #include "utils/CollectionUtils.h" #include "utils/PrinterUtils.h" #include "utils/SizeUtils.h" #include "json.hpp" - #include #include #include @@ -21,8 +21,8 @@ #include #include #include -#include #include +#include #include #include @@ -233,9 +233,27 @@ namespace tests { struct StructValueView : AbstractValueView { explicit StructValueView(const types::StructInfo &_structInfo, std::vector> _subViews, - std::optional _entryValue) - : AbstractValueView(std::move(_subViews)), entryValue(std::move(_entryValue)), - structInfo(_structInfo){ + std::optional _entryValue, + bool _anonymous, + bool _dirtyInit, + size_t _fieldIndexToInitUnion) + : AbstractValueView(std::move(_subViews)) + , entryValue(std::move(_entryValue)) + , structInfo(_structInfo) + , anonymous(_anonymous) + , dirtyInit(_dirtyInit) + , fieldIndexToInitUnion(_fieldIndexToInitUnion){} + + bool isDirtyInit() const { + return dirtyInit; + } + + bool isAnonymous() const { + return anonymous; + } + + size_t getFieldIndexToInitUnion() const { + return fieldIndexToInitUnion; } [[nodiscard]] const std::vector> &getSubViews() const override { @@ -254,7 +272,7 @@ namespace tests { std::vector entries; size_t i = 0; for (const auto &subView : subViews) { - if (structInfo.subType == types::SubType::Struct || structInfo.longestFieldIndexForUnionInit == i) { + if (structInfo.subType == types::SubType::Struct || fieldIndexToInitUnion == i) { entries.push_back(subView->getEntryValue(nullptr)); } ++i; @@ -273,6 +291,9 @@ namespace tests { } [[nodiscard]] std::string getFieldPrefix(int i) const { + if (structInfo.fields[i].name.empty()) + return ""; + std::string prefix = "." + structInfo.fields[i].name + " = "; if (structInfo.isCLike) { return prefix; @@ -291,6 +312,10 @@ namespace tests { private: const types::StructInfo structInfo; std::optional entryValue; + + bool anonymous; + bool dirtyInit; + size_t fieldIndexToInitUnion; }; struct InitReference { @@ -679,6 +704,7 @@ namespace tests { size_t offsetInBits, types::PointerUsage usage, const std::optional &testingMethod, + const bool anonymous, const std::string &name, const MapAddressName &fromAddressToName, std::vector &initReferences); diff --git a/server/src/printers/TestsPrinter.cpp b/server/src/printers/TestsPrinter.cpp index 6cd36b83..b87b209b 100644 --- a/server/src/printers/TestsPrinter.cpp +++ b/server/src/printers/TestsPrinter.cpp @@ -713,12 +713,12 @@ std::string printer::MultiLinePrinter::print(TestsPrinter *printer, const tests::StructValueView *view) { auto subViews = view->getSubViews(); std::stringstream structuredValuesWithPrefixes; - structuredValuesWithPrefixes << "{" << NL; + + structuredValuesWithPrefixes << (view->isAnonymous() ? "/* { */" : "{") << NL; ++printer->tabsDepth; - const types::StructInfo &structInfo = view->getStructInfo(); - const size_t longestFieldIndexForUnionInit = structInfo.longestFieldIndexForUnionInit; - const bool isStruct = structInfo.subType == types::SubType::Struct; + const size_t fieldIndexToInitUnion = view->getFieldIndexToInitUnion(); + const bool isStruct = view->getStructInfo().subType == types::SubType::Struct; size_t i = 0; for (const auto &sview : subViews) { @@ -728,7 +728,7 @@ std::string printer::MultiLinePrinter::print(TestsPrinter *printer, structuredValuesWithPrefixes << NL; } - bool printInComment = !(isStruct || longestFieldIndexForUnionInit == i); + bool printInComment = !(isStruct || fieldIndexToInitUnion == i); if (printInComment) { ++printer->commentDepth; } @@ -743,7 +743,9 @@ std::string printer::MultiLinePrinter::print(TestsPrinter *printer, } --printer->tabsDepth; - structuredValuesWithPrefixes << NL << printer->LINE_INDENT() << "}"; + structuredValuesWithPrefixes << NL + << printer->LINE_INDENT() + << (view->isAnonymous() ? "/* } */" : "}"); return structuredValuesWithPrefixes.str(); } diff --git a/server/src/types/Types.h b/server/src/types/Types.h index aca6a4ca..1ff7deac 100644 --- a/server/src/types/Types.h +++ b/server/src/types/Types.h @@ -258,6 +258,7 @@ namespace types { struct Field { types::Type type; + bool anonymous; std::string name; /// size in @b bits size_t size; @@ -291,7 +292,6 @@ namespace types { struct StructInfo: TypeInfo { std::vector fields{}; - size_t longestFieldIndexForUnionInit; FPointerMap functionFields{}; bool hasAnonymousStructOrUnion; bool isCLike; diff --git a/server/src/types/TypesResolver.cpp b/server/src/types/TypesResolver.cpp index 549f516a..fcacabe3 100644 --- a/server/src/types/TypesResolver.cpp +++ b/server/src/types/TypesResolver.cpp @@ -121,16 +121,15 @@ void TypesResolver::resolveStructEx(const clang::RecordDecl *D, const std::strin << "\tFile path: " << structInfo.filePath.string() << ""; std::vector fields; - structInfo.longestFieldIndexForUnionInit = SIZE_MAX; - size_t i = 0; - size_t maxFieldSize = 0; for (const clang::FieldDecl *F : D->fields()) { if (F->isUnnamedBitfield()) { continue; } - structInfo.hasAnonymousStructOrUnion |= F->isAnonymousStructOrUnion(); types::Field field; + field.anonymous = F->isAnonymousStructOrUnion(); field.name = F->getNameAsString(); + structInfo.hasAnonymousStructOrUnion |= field.anonymous; + const clang::QualType paramType = F->getType().getCanonicalType(); field.type = types::Type(paramType, paramType.getAsString(), sourceManager); if (field.type.isPointerToFunction()) { @@ -175,11 +174,6 @@ void TypesResolver::resolveStructEx(const clang::RecordDecl *D, const std::strin field.accessSpecifier = types::Field::AS_pubic; } fields.push_back(field); - if (subType == types::SubType::Union && maxFieldSize < field.size) { - structInfo.longestFieldIndexForUnionInit = i; - maxFieldSize = field.size; - } - ++i; } structInfo.fields = fields; structInfo.size = getRecordSize(D); diff --git a/server/test/framework/Syntax_Tests.cpp b/server/test/framework/Syntax_Tests.cpp index d886775c..3c8cd581 100644 --- a/server/test/framework/Syntax_Tests.cpp +++ b/server/test/framework/Syntax_Tests.cpp @@ -1825,7 +1825,8 @@ namespace { [] (const tests::Tests::MethodTestCase& testCase) { return stoi(testCase.paramValues[0].view->getEntryValue(nullptr)) == stoi(testCase.paramValues[1].view->getEntryValue(nullptr)) && - testCase.returnValue.view->getEntryValue(nullptr) == "{{{'k', 1.010100e+00}}}"; + StringUtils::startsWith(testCase.returnValue.view->getEntryValue(nullptr), + "{from_bytes({"); }, [] (const tests::Tests::MethodTestCase& testCase) { return stoi(testCase.paramValues[0].view->getEntryValue(nullptr)) > @@ -2557,11 +2558,12 @@ namespace { std::vector({[](const tests::Tests::MethodTestCase &testCase) { std::stringstream ss; EXPECT_EQ(testCase.paramValues.front().view->getEntryValue(nullptr).size(), 3); - ss << "from_bytes({" + ss << "{{" + // "'x'"[1] => int('x') << int(testCase.paramValues.front().view->getEntryValue(nullptr)[1]) - << ", 0, 0, 0, " + << "}, " << int(testCase.paramValues.front().view->getEntryValue(nullptr)[1]) - << ", 0, 0, 0})"; + << "}"; return testCase.returnValue.view->getEntryValue(nullptr) == ss.str(); }})); } @@ -2574,15 +2576,11 @@ namespace { checkTestCasePredicates( testGen.tests.at(inner_unnamed_c).methods.begin().value().testCases, std::vector({[](const tests::Tests::MethodTestCase &testCase) { - return "from_bytes({0, 0, 0, 0, 0, 0, 0, 0})" == - testCase.paramValues.front().view->getEntryValue(nullptr) && - "from_bytes({42, 0, 0, 0, 42, 0, 0, 0})" == - testCase.returnValue.view->getEntryValue(nullptr); + return "{{0}, 0}" == testCase.paramValues.front().view->getEntryValue(nullptr) && + "{{42}, 42}" == testCase.returnValue.view->getEntryValue(nullptr); }, [](const tests::Tests::MethodTestCase &testCase) { - return "from_bytes({0, 0, 0, 0, 0, 0, 0, 0})" != - testCase.paramValues.front().view->getEntryValue(nullptr) && - "from_bytes({24, 0, 0, 0, 24, 0, 0, 0})" == - testCase.returnValue.view->getEntryValue(nullptr); + return "{{0}, 0}" != testCase.paramValues.front().view->getEntryValue(nullptr) && + "{{24}, 24}" == testCase.returnValue.view->getEntryValue(nullptr); }})); } @@ -2597,11 +2595,11 @@ namespace { std::vector({[](const tests::Tests::MethodTestCase &testCase) { std::stringstream ss; EXPECT_EQ(testCase.paramValues.front().view->getEntryValue(nullptr).size(), 3); - ss << "from_bytes({" - << int(testCase.paramValues.front().view->getEntryValue(nullptr)[1]) - << ", 0, 0, 0, " + ss << "{{'" + << char(testCase.paramValues.front().view->getEntryValue(nullptr)[1]) + << "', " << int(testCase.paramValues.front().view->getEntryValue(nullptr)[1]) - << ", 0, 0, 0})"; + << "}}"; return testCase.returnValue.view->getEntryValue(nullptr) == ss.str(); }})); } @@ -2615,14 +2613,14 @@ namespace { checkTestCasePredicates( testGen.tests.at(inner_unnamed_c).methods.begin().value().testCases, std::vector({[](const tests::Tests::MethodTestCase &testCase) { - return "from_bytes({0, 0, 0, 0, 0, 0, 0, 0})" == + return "{{'\\0', 0}}" == testCase.paramValues.front().view->getEntryValue(nullptr) && - "from_bytes({42, 0, 0, 0, 42, 0, 0, 0})" == + "{{'*', 42}}" == testCase.returnValue.view->getEntryValue(nullptr); }, [](const tests::Tests::MethodTestCase &testCase) { - return "from_bytes({0, 0, 0, 0, 0, 0, 0, 0})" != + return "{{'\\0', 0}}" != testCase.paramValues.front().view->getEntryValue(nullptr) && - "from_bytes({24, 0, 0, 0, 24, 0, 0, 0})" == + "{{'\\x18', 24}}" == testCase.returnValue.view->getEntryValue(nullptr); }}));