From 69f5f2d8eaa2a4d861687b796a064df26432acfd Mon Sep 17 00:00:00 2001 From: belous-dp Date: Fri, 2 Sep 2022 16:47:47 +0300 Subject: [PATCH] bit fields support added (#383) * refactored klee constraints printing * changed all types sizes from bytes to bits * bit fields basic support added * fixed link to utbot * bit fields examples added * fixed unnamed bit fields klee wrapper generation & tests generation * unit tests added --- .../c-example/lib/structures/bitfields.c | 148 +++++++ .../c-example/lib/structures/bitfields.h | 103 +++++ .../cpp-example/cxx_lib/structs/bitfields.cpp | 36 ++ .../cpp-example/cxx_lib/structs/bitfields.hpp | 29 ++ server/CMakeLists.txt | 2 +- server/src/Tests.cpp | 317 +++++++------- server/src/Tests.h | 174 +++++--- server/src/fetchers/Fetcher.cpp | 4 +- server/src/fetchers/Fetcher.h | 8 +- .../src/printers/KleeConstraintsPrinter.cpp | 34 +- server/src/printers/KleeConstraintsPrinter.h | 2 + server/src/printers/KleePrinter.cpp | 28 +- server/src/types/Types.cpp | 194 ++++----- server/src/types/Types.h | 32 +- server/src/types/TypesResolver.cpp | 55 ++- server/src/types/TypesResolver.h | 3 +- server/src/utils/SizeUtils.cpp | 9 + server/src/utils/SizeUtils.h | 14 + server/src/utils/StringUtils.cpp | 46 +++ server/src/utils/StringUtils.h | 5 + .../src/visitors/AbstractValueViewVisitor.cpp | 3 + .../FunctionPointerForStubsVisitor.cpp | 3 +- server/test/framework/Syntax_Tests.cpp | 115 ++++++ server/test/framework/Utils_Tests.cpp | 385 ++++++++++++++++++ server/test/suites/syntax/CMakeLists.txt | 3 +- server/test/suites/syntax/bitfields.c | 148 +++++++ server/test/suites/syntax/bitfields.h | 103 +++++ 27 files changed, 1606 insertions(+), 397 deletions(-) create mode 100644 integration-tests/c-example/lib/structures/bitfields.c create mode 100644 integration-tests/c-example/lib/structures/bitfields.h create mode 100644 integration-tests/cpp-example/cxx_lib/structs/bitfields.cpp create mode 100644 integration-tests/cpp-example/cxx_lib/structs/bitfields.hpp create mode 100644 server/src/utils/SizeUtils.cpp create mode 100644 server/src/utils/SizeUtils.h create mode 100644 server/test/suites/syntax/bitfields.c create mode 100644 server/test/suites/syntax/bitfields.h diff --git a/integration-tests/c-example/lib/structures/bitfields.c b/integration-tests/c-example/lib/structures/bitfields.c new file mode 100644 index 00000000..30da29fa --- /dev/null +++ b/integration-tests/c-example/lib/structures/bitfields.c @@ -0,0 +1,148 @@ +#include "bitfields.h" +#include +#include +#include +#include +#include + +static int ALIGN = -30; + +#define print_sizeof(S) printf("size of %*s : %zu bytes\n", ALIGN, #S, sizeof((S){})) + +void print_sizeof_structs() { + print_sizeof(SimpleSignedStr); + print_sizeof(SimpleUnsignedStr); + print_sizeof(ImplementationDefinedStr); + print_sizeof(PossiblySmallStr); + print_sizeof(SimpleUnsignedUnion); + print_sizeof(ComplexStr); + print_sizeof(StrWithBool); + print_sizeof(StrWithUnnamedBitfields); + print_sizeof(StrWithUnnamedZeroBitfield); + print_sizeof(StrWithBreak); +} + +int check_simple_signed_str(SimpleSignedStr s) { + if (s.a == 1024 && s.b == -1 && s.d == -16) { + return 1; + } else if (s.b == 0) { + return -1; + } + return 0; +} + +int is_sum_greater_10(SimpleUnsignedStr s) { + return (s.a + s.b + s.c + s.d > 10 && s.d > 5); +} + +int is_all_greater_2(PossiblySmallStr s) { + if (s.a > 2 && s.b > 2) { + return 1; + } + return 0; +} + +int union_greater_2(SimpleUnsignedUnion u) { + if (u.c > 2) { + return 1; + } + return 0; +} + +int union_greater_2_short(SimpleUnsignedUnion u) { + return u.c > 2; +} + +int sum_of_fields(ComplexStr s) { + if (!s.a) { + return -1; + } + return s.a + s.b + s.c + s.d + s.e; +} + +int decode_from_bool(StrWithBool s) { + if (!s.a && !s.b && !s.c) { + return 0; + } else if (s.a && !s.b && !s.c) { + return 1; + } else if (!s.a && s.b && !s.c) { + return 2; + } else if (s.a && s.b && !s.c) { + return 3; + } else if (!s.a && !s.b && s.c) { + return 4; + } else if (s.a && !s.b && s.c) { + return 5; + } else if (!s.a && s.b && s.c) { + return 6; + } + return 7; +} + +int decode_from_bool_simple(StrWithBool s) { + if (!s.a && !s.b && !s.c) { + return 0; + } else if (s.a && !s.b && s.c) { + return 5; + } + return 7; +} + + StrWithUnnamedBitfields mult_by_two(StrWithUnnamedBitfields s) { + s.b1 *= 2; + s.b2 *= 2; + s.b3 *= 2; + return s; + } + + int is_nice(StrWithUnnamedZeroBitfield s) { + if (s.b1 == 69 && s.b2 == 42 && s.b3 == 1488) { + return 13; + } + return 0; + } + +int check_fields_bounds(StrWithBreak s) { + assert(s.b1 >= 0 && s.b1 <= 127); + assert(s.breaking >= LLONG_MIN && s.breaking <= LLONG_MAX); + assert(s.b2 >= -65536 && s.b2 <= 65535); + assert(s.b3 == true || s.b3 == false); + assert(s.b4 >= -2097152 && s.b4 <= 2097151); + if (s.b1 >= 123 && s.b3) { + return 1; + } else if (s.b1 >= 123 && s.b4 < 0) { + return 2; + } else if (s.breaking > 42) { + return 3; + } + return 4; +} + +void simple_modify(SimpleSignedStr* s) { + s->a++; + s->b = ~s->b; + if (s->c >= 0) { + s->c *= 2; + } + s->d /= 2; +} + +SimpleSignedStr* create_on_heap(int a, int b, int c, int d) { + SimpleSignedStr* s = malloc(sizeof(SimpleSignedStr)); + if (s) { + s->a = s->b = s->c = s->d = -1; + if (a >= -8388608 && a <= 8388607) { + s->a = a; + } + if (b >= -1 && b <= 0) { + s->b = b; + } + if (c >= -2 && c <= 1) { + s->c = c; + } + if (d >= -16 && d <= 15) { + s->d = d; + } + } + return s; +} diff --git a/integration-tests/c-example/lib/structures/bitfields.h b/integration-tests/c-example/lib/structures/bitfields.h new file mode 100644 index 00000000..07e87342 --- /dev/null +++ b/integration-tests/c-example/lib/structures/bitfields.h @@ -0,0 +1,103 @@ +#ifndef C_EXAMPLE_BITFIELDS_H +#define C_EXAMPLE_BITFIELDS_H + +#include + +typedef struct { + signed a : 24; + signed b : 1; + signed int c : 2; + signed int d : 5; +} SimpleSignedStr; + +typedef struct { + unsigned a : 2; + unsigned b : 5; + unsigned int c : 1; + unsigned int d : 24; +} SimpleUnsignedStr; + +typedef struct { + int a : 24; + int b : 2; + int c : 1; + int d : 5; +} ImplementationDefinedStr; + +typedef struct { + signed a : 3; + signed b : 3; +} PossiblySmallStr; + +typedef union { + unsigned a : 2; + unsigned b : 1; + unsigned c : 6; + unsigned d : 23; +} SimpleUnsignedUnion; + +typedef struct { + // will **usually** occupy 8 bytes: + bool a : 1; + unsigned b : 2; + signed c : 1; + unsigned d : 3; + // 25 bits: unused + int e; +} ComplexStr; + +typedef struct { + // will **usually** occupy 1 byte + bool a : 1, b : 1, c : 1; +} StrWithBool; + +// struct InvalidNumberOfBits { // should produce an error in C +// bool a : 2; +// unsigned b : 50; +// signed c : 1; +// unsigned d : 3; +// }; + +typedef struct { + // will **usually** occupy 4 bytes: + // 5 bits: value of b1 + // 11 bits: unused -- explicitly specified padding + // 6 bits: value of b2 + // 2 bits: value of b3 + // 3 bits: unused -- explicitly specified padding + // 5 bits: unused -- implicitly + unsigned b1 : 5, : 11, b2 : 6, b3 : 2; + signed : 3; +} StrWithUnnamedBitfields; + +typedef struct { + // will **usually** occupy 8 bytes: + // 7 bits: value of b1 + // 25 bits: unused + // 6 bits: value of b2 + // 15 bits: value of b3 + // 11 bits: unused + unsigned b1 : 7; + unsigned : 0; // start a new allocation unit + unsigned b2 : 6; + unsigned b3 : 15; +} StrWithUnnamedZeroBitfield; + +typedef struct { + // will **usually** occupy 24 bytes: + // 7 bits: value of b1 + // 57 bits: unused + // 64 bits: value of breaking + // 17 bits: value of b2 + // 1 bit: value of b3 + // 22 bits: value of b4 + // 24 bits: unused + unsigned b1 : 7; // from 0 to 127 + long long breaking; // from LLONG_MIN to LLONG_MAX + signed b2 : 17; // from -65536 to 65535 + bool b3 : 1; // from 0 to 1 + int b4 : 22; // usually from -2097152 to 2097151 +} StrWithBreak; + + +#endif //C_EXAMPLE_BITFIELDS_H diff --git a/integration-tests/cpp-example/cxx_lib/structs/bitfields.cpp b/integration-tests/cpp-example/cxx_lib/structs/bitfields.cpp new file mode 100644 index 00000000..8b5a6079 --- /dev/null +++ b/integration-tests/cpp-example/cxx_lib/structs/bitfields.cpp @@ -0,0 +1,36 @@ +#include "bitfields.hpp" +#include +#include +#include + +static int ALIGN = -30; + +#define print_sizeof(S) printf("size of %*s : %zu bytes\n", ALIGN, #S, sizeof((S){})) + +void print_sizeof_structs() { + print_sizeof(CppSpecificStruct); +} + +CppSpecificStruct modify_and_return_copy(CppSpecificStruct& s) { + assert(s.b1 >= 0 && s.b1 <= 127); + assert(s.breaking >= std::numeric_limits::min()); + assert(s.breaking <= std::numeric_limits::max()); + assert(s.b2 >= -65536 && s.b2 <= 65535); + assert(s.b4 >= -2097152 && s.b4 <= 2097151); + assert(s.b5 >= std::numeric_limits::min()); + assert(s.b5 <= std::numeric_limits::max()); + assert(s.b6 >= -8 && s.b6 <= 7); + s.b1 = s.b1 * 2 + 1; + s.breaking = std::numeric_limits::max(); + if (s.b2 != -65536) { + s.b2 = -s.b2; + } + s.b3 ^= 1; + if (s.b5 > 0) { + s.b5 = 10; + } else { + s.b5 = -10; + } + s.b6 = (s.b1 > 0) + (s.b2 > 0) + (s.b3 > 0) + (s.b4 > 0) + (s.b5 > 0) + (s.b6 > 0); + return CppSpecificStruct(s); +} diff --git a/integration-tests/cpp-example/cxx_lib/structs/bitfields.hpp b/integration-tests/cpp-example/cxx_lib/structs/bitfields.hpp new file mode 100644 index 00000000..268c6f09 --- /dev/null +++ b/integration-tests/cpp-example/cxx_lib/structs/bitfields.hpp @@ -0,0 +1,29 @@ +#ifndef CPP_EXAMPLE_BITFIELDS_H +#define CPP_EXAMPLE_BITFIELDS_H + +struct CppSpecificStruct { + // will **usually** occupy 40 bytes: + // 7 bits: value of b1 + // 57 bits: unused + // 64 bits: value of breaking + // 17 bits: value of b2 + // 1 bit: value of b3 + // 22 bits: value of b4 + // 24 bits: unused + // 32 bits: value of b5 + // 34 bits: unused + // 4 bits: value of b6 + // 58 bits: unused + unsigned b1 : 7; // from 0 to 127 + long long breaking; // from LLONG_MIN to LLONG_MAX + signed b2 : 17; // from -65536 to 65535 + bool b3 : 1; // from 0 to 1 + int b4 : 22; // from -2097152 to 2097151 + unsigned : 0; // starts a new allocation unit + signed b5 : 66; // from INT_MIN to INT_MAX + signed b6 : 4; // from -8 to 7 +}; + +CppSpecificStruct modify_and_return_copy(CppSpecificStruct& s); + +#endif //CPP_EXAMPLE_BITFIELDS_H diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 69d3b30b..26ce0ad1 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -17,7 +17,7 @@ if (NOT DEFINED RUN_INFO) endif () project(UTBotCpp DESCRIPTION "Tool that generates unit test by C/C++ source code, trying to reach all branches and maximize code coverage" - HOMEPAGE_URL "https://unittestbot.org" VERSION ${VERSION}) + HOMEPAGE_URL "https://www.utbot.org" VERSION ${VERSION}) set(PROJECT_ORG "UnitTestBot") message("Project: ${PROJECT_NAME}") diff --git a/server/src/Tests.cpp b/server/src/Tests.cpp index 7e24b3ee..355fdced 100644 --- a/server/src/Tests.cpp +++ b/server/src/Tests.cpp @@ -4,9 +4,13 @@ #include "exceptions/UnImplementedException.h" #include "printers/TestsPrinter.h" #include "utils/KleeUtils.h" +#include "utils/StringUtils.h" #include "loguru.h" +#include +#include + using namespace tests; using namespace types; @@ -18,16 +22,16 @@ const std::string Tests::ERROR_SUITE_NAME = "error"; const Tests::MethodParam &tests::Tests::getStdinMethodParam() { static const Tests::MethodParam stdinMethodParam = - MethodParam(types::Type::CStringType(), types::Type::getStdinParamName(), std::nullopt); + MethodParam(types::Type::CStringType(), types::Type::getStdinParamName(), std::nullopt); return stdinMethodParam; } Tests::MethodDescription::MethodDescription() - : suiteTestCases{ { Tests::DEFAULT_SUITE_NAME, std::vector() }, - { Tests::ERROR_SUITE_NAME, std::vector() } }, - codeText{ { Tests::DEFAULT_SUITE_NAME, std::string() }, - { Tests::ERROR_SUITE_NAME, std::string() } } { -} + : suiteTestCases{{ Tests::DEFAULT_SUITE_NAME, std::vector() }, + { Tests::ERROR_SUITE_NAME, std::vector() }}, + codeText{{ Tests::DEFAULT_SUITE_NAME, std::string() }, + { Tests::ERROR_SUITE_NAME, std::string() }}, + modifiers{} { } static std::string makeDecimalConstant(std::string value, const std::string &typeName) { if (typeName == "long") { @@ -63,7 +67,7 @@ static const std::unordered_map FPSpecialValuesMapping namespace tests { /** - * The function checks for precense of argument in values as it is + * The function checks for presence of argument in values as it is * called by the time processFPSpecialValue is already applied */ bool isFPSpecialValue(const std::string& value) { @@ -84,10 +88,10 @@ std::string processFPSpecialValue(const std::string &value) { std::shared_ptr KTestObjectParser::primitiveView(const std::vector &byteArray, const types::Type &type, - size_t offset, - size_t len) { + size_t offsetInBits, + size_t lenInBits) { Type readType = types::TypesHandler::isVoid(type) ? Type::minimalScalarType() : type; - std::string value = readBytesAsValueForType(byteArray, readType.baseType(), offset, len); + std::string value = readBytesAsValueForType(byteArray, readType.baseType(), offsetInBits, lenInBits); value = makeDecimalConstant(value, type.baseType()); value = processFPSpecialValue(value); if (types::TypesHandler::isBoolType(type)) { @@ -99,9 +103,9 @@ std::shared_ptr KTestObjectParser::primitiveView(const std:: std::shared_ptr KTestObjectParser::enumView(const std::vector &byteArray, const types::EnumInfo &enumInfo, - size_t offset, - size_t len) { - std::string value = readBytesAsValue(byteArray, offset, len); + size_t offsetInBits, + size_t lenInBits) { + std::string value = readBytesAsValue(byteArray, offsetInBits, lenInBits); if (CollectionUtils::containsKey(enumInfo.valuesToEntries, value)) { auto name = enumInfo.getEntryName(value, utbot::Language::CXX); value = NameDecorator::decorate(name); @@ -136,28 +140,26 @@ std::shared_ptr KTestObjectParser::stringLiteralView(const std: std::shared_ptr KTestObjectParser::multiArrayView(const std::vector &byteArray, const types::Type &type, - size_t arraySize, - size_t offset, - PointerUsage usage) -{ + size_t arraySizeInBits, + size_t offsetInBits, + PointerUsage usage) { std::vector> views; const types::Type baseType = type.baseTypeObj(); - size_t len = (types::TypesHandler::isVoid(baseType)) - ? typesHandler.typeSize(Type::minimalScalarType()) - : typesHandler.typeSize(baseType); + size_t elementLenInBits = (types::TypesHandler::isVoid(baseType)) + ? typesHandler.typeSize(Type::minimalScalarType()) + : typesHandler.typeSize(baseType); - for (size_t curPos = 0; curPos < arraySize; curPos += len) { + for (size_t curPos = offsetInBits; curPos < offsetInBits + arraySizeInBits; curPos += elementLenInBits) { switch (typesHandler.getTypeKind(baseType)) { case TypeKind::STRUCT_LIKE: - views.push_back(structView(byteArray, typesHandler.getStructInfo(baseType), curPos + offset, usage)); + views.push_back(structView(byteArray, typesHandler.getStructInfo(baseType), curPos, usage)); break; case TypeKind::ENUM: - views.push_back(enumView(byteArray, typesHandler.getEnumInfo(type), curPos + offset, len)); + views.push_back(enumView(byteArray, typesHandler.getEnumInfo(type), curPos, elementLenInBits)); break; - case TypeKind::PRIMITIVE: - views.push_back(primitiveView(byteArray, baseType, curPos + offset, len)); + views.push_back(primitiveView(byteArray, baseType, curPos, elementLenInBits)); break; case TypeKind::OBJECT_POINTER: case TypeKind::ARRAY: @@ -189,7 +191,7 @@ std::shared_ptr KTestObjectParser::multiArrayView(const std::vec return std::make_shared(views); } -std::shared_ptr KTestObjectParser::functionPointerView(const std::optional& scopeName, +std::shared_ptr KTestObjectParser::functionPointerView(const std::optional &scopeName, const std::string &methodName, const std::string ¶mName) { std::string value = PrinterUtils::getFunctionPointerStubName(scopeName, methodName, paramName).substr(1); @@ -204,24 +206,24 @@ std::shared_ptr KTestObjectParser::functionPointerView(cons std::shared_ptr KTestObjectParser::arrayView(const std::vector &byteArray, const types::Type &type, - size_t arraySize, - unsigned int offset, + size_t arraySizeInBits, + size_t offsetInBits, PointerUsage usage) { std::vector> subViews; - size_t len = (types::TypesHandler::isVoid(type)) - ? typesHandler.typeSize(Type::minimalScalarType()) - : typesHandler.typeSize(type); - for (size_t curPos = 0; curPos < arraySize; curPos += len) { + size_t elementLenInBits = (types::TypesHandler::isVoid(type)) + ? typesHandler.typeSize(Type::minimalScalarType()) + : typesHandler.typeSize(type); + + for (size_t curPos = offsetInBits; curPos < offsetInBits + arraySizeInBits; curPos += elementLenInBits) { switch (typesHandler.getTypeKind(type)) { case TypeKind::STRUCT_LIKE: - subViews.push_back(structView(byteArray, typesHandler.getStructInfo(type), curPos + offset, usage)); + subViews.push_back(structView(byteArray, typesHandler.getStructInfo(type), curPos, usage)); break; case TypeKind::ENUM: - subViews.push_back(enumView(byteArray, typesHandler.getEnumInfo(type), curPos + offset, len)); + subViews.push_back(enumView(byteArray, typesHandler.getEnumInfo(type), curPos, elementLenInBits)); break; - case TypeKind::PRIMITIVE: - subViews.push_back(primitiveView(byteArray, type.baseTypeObj(), curPos + offset, len)); + subViews.push_back(primitiveView(byteArray, type.baseTypeObj(), curPos, elementLenInBits)); break; case TypeKind::OBJECT_POINTER: case TypeKind::ARRAY: @@ -240,47 +242,49 @@ std::shared_ptr KTestObjectParser::arrayView(const std::vector KTestObjectParser::structView(const std::vector &byteArray, const types::StructInfo &curStruct, - unsigned int offset, + size_t offsetInBits, types::PointerUsage usage) { std::vector tmpInitReferences; - return structView(byteArray, curStruct, offset, usage, {}, "", {}, tmpInitReferences); + return structView(byteArray, curStruct, offsetInBits, usage, {}, "", {}, tmpInitReferences); } std::shared_ptr KTestObjectParser::structView(const std::vector &byteArray, const StructInfo &curStruct, - unsigned int offset, + size_t offsetInBits, PointerUsage usage, const std::optional &testingMethod, const std::string &name, const MapAddressName &fromAddressToName, std::vector &initReferences) { std::vector> subViews; - unsigned int curPos = offset; for (const auto &field: curStruct.fields) { - size_t len = typesHandler.typeSize(field.type); - unsigned int offsetField = field.offset; - std::string res; + if (field.isUnnamedBitfield()) { + continue; + } + size_t fieldLen = typesHandler.typeSize(field.type); + size_t fieldOffset = offsetInBits + field.offset; switch (typesHandler.getTypeKind(field.type)) { case TypeKind::STRUCT_LIKE: - subViews.push_back(structView(byteArray, typesHandler.getStructInfo(field.type), curPos + offsetField, usage, testingMethod, + subViews.push_back(structView(byteArray, typesHandler.getStructInfo(field.type), fieldOffset, usage, testingMethod, PrinterUtils::getFieldAccess(name, field), fromAddressToName, initReferences)); break; case TypeKind::ENUM: - subViews.push_back(enumView(byteArray, typesHandler.getEnumInfo(field.type), curPos + offsetField, len)); + subViews.push_back(enumView(byteArray, typesHandler.getEnumInfo(field.type), fieldOffset, fieldLen)); break; - case TypeKind::PRIMITIVE: - subViews.push_back(primitiveView(byteArray, field.type.baseTypeObj(), curPos + offsetField, len)); + subViews.push_back(primitiveView(byteArray, field.type.baseTypeObj(), fieldOffset, + std::min(field.size, fieldLen))); break; - case TypeKind::ARRAY: - if (field.type.pointerArrayKinds().size() > 1) { + case TypeKind::ARRAY: { + const std::vector> pointerArrayKinds = field.type.pointerArrayKinds(); + if (pointerArrayKinds.size() > 1) { size_t size = 1; bool onlyArrays = true; - for (size_t i = 0; i < field.type.pointerArrayKinds().size(); i++) { - if (field.type.pointerArrayKinds()[i]->getKind() == AbstractType::ARRAY) { - size *= field.type.pointerArrayKinds()[i]->getSize(); + for (const auto &pointerArrayKind : pointerArrayKinds) { + if (pointerArrayKind->getKind() == AbstractType::ARRAY) { + size *= pointerArrayKind->getSize(); } else { onlyArrays = false; break; @@ -289,22 +293,24 @@ std::shared_ptr KTestObjectParser::structView(const std::vector if (onlyArrays) { size *= typesHandler.typeSize(field.type.baseTypeObj()); subViews.push_back(multiArrayView(byteArray, field.type, size, - curPos + offsetField, usage)); + fieldOffset, 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(), len, curPos + offsetField, usage); + auto view = arrayView(byteArray, field.type.baseTypeObj(), fieldLen, fieldOffset, usage); subViews.push_back(view); } + } break; - case TypeKind::OBJECT_POINTER: - res = readBytesAsValueForType(byteArray, PointerWidthType, curPos + offsetField, - PointerWidthSize); + case TypeKind::OBJECT_POINTER: { + std::string res = readBytesAsValueForType(byteArray, PointerWidthType, fieldOffset, + PointerWidthSizeInBits); subViews.push_back(getLazyPointerView(fromAddressToName, initReferences, PrinterUtils::getFieldAccess(name, field), res, field.type)); + } break; case TypeKind::FUNCTION_POINTER: subViews.push_back(functionPointerView(curStruct.name, field.name)); @@ -323,9 +329,9 @@ std::shared_ptr KTestObjectParser::structView(const std::vector } std::optional entryValue; - if(curStruct.hasUnnamedFields) { + if (curStruct.hasAnonymousStructOrUnion) { auto bytesType = types::Type::createSimpleTypeFromName("utbot_byte"); - const std::shared_ptr rawDataView = arrayView(byteArray, bytesType, curStruct.size, offset, usage); + const std::shared_ptr rawDataView = arrayView(byteArray, bytesType, curStruct.size, offsetInBits, usage); entryValue = PrinterUtils::convertBytesToUnion(curStruct.name, rawDataView->getEntryValue(nullptr)); } return std::make_shared(curStruct, subViews, entryValue); @@ -347,59 +353,59 @@ std::string KTestObjectParser::primitiveBoolView(const std::string &value) { std::string readBytesAsValueForType(const std::vector &byteArray, const std::string &typeName, - unsigned int offset, - unsigned int len) { + size_t offsetInBits, + size_t lenInBits) { if (typeName == "utbot_byte") { //we use different name to not trigger char processing - return readBytesAsValue(byteArray, offset, len); + return readBytesAsValue(byteArray, offsetInBits, lenInBits); } if (typeName == "short") { - return readBytesAsValue(byteArray, offset, len); + return readBytesAsValue(byteArray, offsetInBits, lenInBits); } if (typeName == "int") { - return readBytesAsValue(byteArray, offset, len); + return readBytesAsValue(byteArray, offsetInBits, lenInBits); } if (typeName == "long") { - return readBytesAsValue(byteArray, offset, len); + return readBytesAsValue(byteArray, offsetInBits, lenInBits); } if (typeName == "long long") { - return readBytesAsValue(byteArray, offset, len); + return readBytesAsValue(byteArray, offsetInBits, lenInBits); } if (typeName == "unsigned short") { - return readBytesAsValue(byteArray, offset, len); + return readBytesAsValue(byteArray, offsetInBits, lenInBits); } if (typeName == "unsigned int") { - return readBytesAsValue(byteArray, offset, len); + return readBytesAsValue(byteArray, offsetInBits, lenInBits); } if (typeName == "unsigned long") { - return readBytesAsValue(byteArray, offset, len); + return readBytesAsValue(byteArray, offsetInBits, lenInBits); } if (typeName == "unsigned long long") { - return readBytesAsValue(byteArray, offset, len); + return readBytesAsValue(byteArray, offsetInBits, lenInBits); } if (typeName == "char") { - return readBytesAsValue(byteArray, offset, len); + return readBytesAsValue(byteArray, offsetInBits, lenInBits); } if (typeName == "signed char") { - return readBytesAsValue(byteArray, offset, len); + return readBytesAsValue(byteArray, offsetInBits, lenInBits); } if (typeName == "unsigned char") { - return readBytesAsValue(byteArray, offset, len); + return readBytesAsValue(byteArray, offsetInBits, lenInBits); } if (typeName == "bool" || typeName == "_Bool") { - return readBytesAsValue(byteArray, offset, len); + return readBytesAsValue(byteArray, offsetInBits, lenInBits); } if (typeName == "float") { - return readBytesAsValue(byteArray, offset, len); + return readBytesAsValue(byteArray, offsetInBits, lenInBits); } if (typeName == "double") { - return readBytesAsValue(byteArray, offset, len); + return readBytesAsValue(byteArray, offsetInBits, lenInBits); } if (typeName == "long double") { - return readBytesAsValue(byteArray, offset, len); + return readBytesAsValue(byteArray, offsetInBits, lenInBits); } - if (typeName == "int") { - return readBytesAsValue(byteArray, offset, len); + if (typeName == "std::uintptr_t" || typeName == "uintptr_t") { + return readBytesAsValue(byteArray, offsetInBits, lenInBits); } return ""; } @@ -425,14 +431,8 @@ namespace { //Predicate utilities. } } - bool stobool(const std::string& s) { - if (s == "false") return false; - if (s == "true") return true; - ABORT_F("Wrong bool value: %s", s.c_str()); - } - bool predicateMatch(const std::string &value, const LineInfo::PredicateInfo &info) { - switch(info.type) { + switch (info.type) { case testsgen::CHAR: return compareSimpleValues(info.predicate, value, "\'" + info.returnValue + "\'"); case testsgen::STRING: @@ -441,16 +441,20 @@ namespace { //Predicate utilities. case testsgen::INT16_T: case testsgen::INT32_T: case testsgen::INT64_T: - return compareSimpleValues(info.predicate, std::stoll(value), std::stoll(info.returnValue)); + return compareSimpleValues(info.predicate, StringUtils::stot(value), + StringUtils::stot(info.returnValue)); case testsgen::UINT8_T: case testsgen::UINT16_T: case testsgen::UINT32_T: case testsgen::UINT64_T: - return compareSimpleValues(info.predicate, std::stoull(value), std::stoull(info.returnValue)); + return compareSimpleValues(info.predicate, StringUtils::stot(value), + StringUtils::stot(info.returnValue)); case testsgen::BOOL: - return compareSimpleValues(info.predicate, stobool(value), stobool(info.returnValue)); + return compareSimpleValues(info.predicate, StringUtils::stot(value), + StringUtils::stot(info.returnValue)); case testsgen::FLOAT: - return compareSimpleValues(info.predicate, std::stof(value), std::stof(info.returnValue)); + return compareSimpleValues(info.predicate, StringUtils::stot(value), + StringUtils::stot(info.returnValue)); default: ABORT_F("Unsupported ValidationType: %s", ValidationType_Name(info.type).c_str()); } @@ -459,9 +463,9 @@ namespace { //Predicate utilities. void KTestObjectParser::parseKTest(const MethodKtests &batch, tests::Tests &tests, - const std::unordered_map& methodNameToReturnTypeMap, + const std::unordered_map &methodNameToReturnTypeMap, bool filterByLineFlag, - std::shared_ptr lineInfo) { + const std::shared_ptr &lineInfo) { LOG_SCOPE_FUNCTION(DEBUG); sourceFilePath = tests.sourceFilePath; for (auto &[testMethod, testCases] : batch) { @@ -473,7 +477,7 @@ void KTestObjectParser::parseKTest(const MethodKtests &batch, } static std::string getSuiteName(const UTBotKTest::Status &status, - const std::shared_ptr lineInfo) { + const std::shared_ptr &lineInfo) { bool forAssert = lineInfo != nullptr && lineInfo->forAssert; if (status == UTBotKTest::Status::FAILED || forAssert) { return Tests::ERROR_SUITE_NAME; @@ -481,18 +485,16 @@ static std::string getSuiteName(const UTBotKTest::Status &status, return Tests::DEFAULT_SUITE_NAME; } - -size_t KTestObjectParser::findFieldIndex(const StructInfo &structInfo, size_t offset) { - size_t indField = std::upper_bound(structInfo.fields.begin(), structInfo.fields.end(), offset, [] (int offset, const Field &field) { +size_t KTestObjectParser::findFieldIndex(const StructInfo &structInfo, size_t offsetInBits) { + size_t indField = std::upper_bound(structInfo.fields.begin(), structInfo.fields.end(), offsetInBits, [] (int offset, const Field &field) { return offset < field.offset; }) - structInfo.fields.begin(); - indField--; - if (indField < 0) { + if (indField == 0) { std::string message = "Wrong offset"; LOG_S(ERROR) << message; throw IncorrectIndexException(message); } - return indField; + return indField - 1; } void KTestObjectParser::addToOrder(const std::vector &objects, @@ -554,10 +556,10 @@ void KTestObjectParser::assignTypeUnnamedVar(Tests::MethodTestCase &testCase, curType.paramValue.lazyValues.emplace_back(name, std::nullopt, testParamView); } - for (auto const &[offset, indObj] : testCase.objects[curType.jsonInd].offsets) { + for (auto const &[offset, indObj] : testCase.objects[curType.jsonInd].offsetsInBytes) { if (!visited[indObj]) { types::Type fieldType = - traverseLazyInStruct(visited, paramType, offset, testCase, methodDescription); + traverseLazyInStruct(visited, paramType, SizeUtils::bytesToBits(offset), testCase, methodDescription); Tests::MethodParam param = { fieldType, "", std::nullopt }; order.emplace(indObj, param, curType.paramValue); visited[indObj] = true; @@ -568,20 +570,19 @@ void KTestObjectParser::assignTypeUnnamedVar(Tests::MethodTestCase &testCase, types::Type KTestObjectParser::traverseLazyInStruct(std::vector &visited, const types::Type &curVarType, - size_t offset, + size_t offsetInBits, const Tests::MethodTestCase &testCase, const Tests::MethodDescription &methodDescription) { switch (typesHandler.getTypeKind(curVarType)) { case TypeKind::STRUCT_LIKE: { const types::StructInfo &structInfo = typesHandler.getStructInfo(curVarType); - size_t indField = findFieldIndex(structInfo, offset); + size_t indField = findFieldIndex(structInfo, offsetInBits); const types::Field &next = structInfo.fields[indField]; - return traverseLazyInStruct(visited, next.type, offset - next.offset, testCase, + return traverseLazyInStruct(visited, next.type, offsetInBits - next.offset, testCase, methodDescription); } - case TypeKind::OBJECT_POINTER: { - LOG_IF_S(ERROR, offset != 0) << "Offset not zero" << offset; + LOG_IF_S(ERROR, offsetInBits != 0) << "Offset not zero" << offsetInBits; return curVarType.baseTypeObj(1); } case TypeKind::PRIMITIVE: { @@ -593,11 +594,11 @@ types::Type KTestObjectParser::traverseLazyInStruct(std::vector &visited, case TypeKind::UNKNOWN: default: { std::string message = - "Unsupported type in lazy initialization BFS: " + curVarType.typeName(); + "Unsupported type in lazy initialization BFS: " + curVarType.typeName(); LOG_S(ERROR) << message; throw NoSuchTypeException(message); } - } + } } void KTestObjectParser::assignTypeStubVar(Tests::MethodTestCase &testCase, @@ -624,7 +625,7 @@ void KTestObjectParser::parseTestCases(const UTBotKTestList &cases, bool filterByLineFlag, Tests::MethodDescription &methodDescription, const std::unordered_map& methodNameToReturnTypeMap, - std::shared_ptr lineInfo) { + const std::shared_ptr &lineInfo) { /* Replace the return type for predicate scenario * to treat strings in specific way. This is done to retrieve * correct value from KTests and print the test. @@ -740,7 +741,7 @@ KTestObjectParser::parseTestCaseParameters(const UTBotKTest &testCases, Tests::TestCaseDescription KTestObjectParser::parseTestCaseParams(const UTBotKTest &ktest, const Tests::MethodDescription &methodDescription, - const std::unordered_map& methodNameToReturnTypeMap, + const std::unordered_map &methodNameToReturnTypeMap, const std::stringstream &traceStream) { std::vector rawKleeParams; for (auto const ¶m : ktest.objects) { @@ -759,11 +760,13 @@ KTestObjectParser::parseTestCaseParams(const UTBotKTest &ktest, int cnt = 0; std::vector visited(testCaseDescription.objects.size(), false); for (const auto &obj : testCaseDescription.objects) { - for (const auto &off : obj.offsets) { + for (const auto &off : obj.offsetsInBytes) { if (visited[off.index]) continue; visited[off.index] = true; testCaseDescription.objects[off.index].name = PrinterUtils::generateNewVar(++cnt); - uint64_t address = std::stoull(readBytesAsValueForType(obj.bytes, PointerWidthType, off.offset, PointerWidthSize)); + size_t address = std::stoull( + readBytesAsValueForType(obj.bytes, PointerWidthType, SizeUtils::bytesToBits(off.offset), + PointerWidthSizeInBits)); testCaseDescription.lazyAddressToName.emplace(address, testCaseDescription.objects[off.index].name); } } @@ -773,35 +776,16 @@ KTestObjectParser::parseTestCaseParams(const UTBotKTest &ktest, if (methodDescription.isClassMethod()) { auto methodParam = methodDescription.classObj.value(); std::shared_ptr testParamView; - auto paramType = methodParam.type.maybeJustPointer() ? methodParam.type.baseTypeObj() : methodParam.type; - if (CollectionUtils::containsKey(methodDescription.functionPointers, methodParam.name)) { - testParamView = testParameterView( - emptyKleeParam, { paramType, methodParam.name }, PointerUsage::PARAMETER, testCaseDescription.lazyAddressToName, - testCaseDescription.lazyReferences, methodDescription); - } else { - const auto kleeParam = getKleeParamOrThrow(rawKleeParams, methodParam.name); - testParamView = testParameterView(kleeParam, {paramType, methodParam.name }, PointerUsage::PARAMETER, - testCaseDescription.lazyAddressToName, testCaseDescription.lazyReferences, - methodDescription); - } + getTestParamView(methodDescription, rawKleeParams, emptyKleeParam, testCaseDescription, methodParam, + testParamView); testCaseDescription.classPreValues = { methodParam.name, methodParam.alignment, testParamView }; processClassPostValue(testCaseDescription, methodParam, rawKleeParams); } for (auto &methodParam : methodDescription.params) { std::shared_ptr testParamView; - auto paramType = methodParam.type.maybeJustPointer() ? methodParam.type.baseTypeObj() : methodParam.type; - if (CollectionUtils::containsKey(methodDescription.functionPointers, methodParam.name)) { - testParamView = testParameterView( - emptyKleeParam, { paramType, methodParam.name }, PointerUsage::PARAMETER, testCaseDescription.lazyAddressToName, - testCaseDescription.lazyReferences, methodDescription); - } else { - const auto kleeParam = getKleeParamOrThrow(rawKleeParams, methodParam.name); - - testParamView = testParameterView(kleeParam, {paramType, methodParam.name }, PointerUsage::PARAMETER, - testCaseDescription.lazyAddressToName, testCaseDescription.lazyReferences, - methodDescription); - } + getTestParamView(methodDescription, rawKleeParams, emptyKleeParam, testCaseDescription, methodParam, + testParamView); testCaseDescription.funcParamValues.emplace_back(methodParam.name, methodParam.alignment, testParamView); @@ -853,6 +837,25 @@ KTestObjectParser::parseTestCaseParams(const UTBotKTest &ktest, return testCaseDescription; } +void KTestObjectParser::getTestParamView(const Tests::MethodDescription &methodDescription, + const std::vector &rawKleeParams, + const KTestObjectParser::RawKleeParam &emptyKleeParam, + Tests::TestCaseDescription &testCaseDescription, + const Tests::MethodParam& methodParam, + std::shared_ptr &testParamView) { + auto paramType = methodParam.type.maybeJustPointer() ? methodParam.type.baseTypeObj() : methodParam.type; + if (CollectionUtils::containsKey(methodDescription.functionPointers, methodParam.name)) { + testParamView = testParameterView( + emptyKleeParam, { paramType, methodParam.name }, PointerUsage::PARAMETER, testCaseDescription.lazyAddressToName, + testCaseDescription.lazyReferences, methodDescription); + } else { + const auto kleeParam = getKleeParamOrThrow(rawKleeParams, methodParam.name); + testParamView = testParameterView(kleeParam, { paramType, methodParam.name }, PointerUsage::PARAMETER, + testCaseDescription.lazyAddressToName, testCaseDescription.lazyReferences, + methodDescription); + } +} + void KTestObjectParser::processGlobalParamPreValue(Tests::TestCaseDescription &testCaseDescription, const Tests::MethodParam &globalParam, std::vector &rawKleeParams) { @@ -926,7 +929,7 @@ void KTestObjectParser::processParamPostValue(Tests::TestCaseDescription &testCa void KTestObjectParser::processStubParamValue(Tests::TestCaseDescription &testCaseDescription, const std::unordered_map& methodNameToReturnTypeMap, std::vector &rawKleeParams) { - for (const auto& kleeParam: rawKleeParams) { + for (const auto &kleeParam: rawKleeParams) { if (StringUtils::endsWith(kleeParam.paramName, PrinterUtils::KLEE_SYMBOLIC_SUFFIX)) { std::string methodName = kleeParam.paramName.substr(0, kleeParam.paramName.size() - PrinterUtils::KLEE_SYMBOLIC_SUFFIX.size()); if (!CollectionUtils::containsKey(methodNameToReturnTypeMap, methodName)) { @@ -944,13 +947,12 @@ void KTestObjectParser::processStubParamValue(Tests::TestCaseDescription &testCa } std::shared_ptr KTestObjectParser::testParameterView( - const KTestObjectParser::RawKleeParam &kleeParam, - const Tests::TypeAndVarName ¶m, - PointerUsage usage, - const MapAddressName &fromAddressToName, - std::vector &initReferences, - const std::optional &testingMethod) -{ + const KTestObjectParser::RawKleeParam &kleeParam, + const Tests::TypeAndVarName ¶m, + PointerUsage usage, + const MapAddressName &fromAddressToName, + std::vector &initReferences, + const std::optional &testingMethod) { const auto &rawData = kleeParam.rawData; const auto ¶mType = param.type; switch (typesHandler.getTypeKind(paramType)) { @@ -958,21 +960,19 @@ std::shared_ptr KTestObjectParser::testParameterView( return structView(rawData, typesHandler.getStructInfo(paramType), 0, usage, testingMethod, param.varName, fromAddressToName, initReferences); case TypeKind::ENUM: - return enumView(rawData, typesHandler.getEnumInfo(paramType), 0, rawData.size()); - + return enumView(rawData, typesHandler.getEnumInfo(paramType), 0, SizeUtils::bytesToBits(rawData.size())); case TypeKind::PRIMITIVE: - return primitiveView(rawData, paramType.baseTypeObj(), 0, rawData.size()); + return primitiveView(rawData, paramType.baseTypeObj(), 0, SizeUtils::bytesToBits(rawData.size())); case TypeKind::OBJECT_POINTER: if (usage == types::PointerUsage::LAZY) { - std::string res = readBytesAsValueForType(rawData, PointerWidthType, 0, PointerWidthSize); - return getLazyPointerView(fromAddressToName, initReferences, param.varName, res, - paramType); + std::string res = readBytesAsValueForType(rawData, PointerWidthType, 0, PointerWidthSizeInBits); + return getLazyPointerView(fromAddressToName, initReferences, param.varName, res, paramType); } else if (types::TypesHandler::isCStringType(paramType)) { return stringLiteralView(rawData); } else if (paramType.kinds().size() > 2) { - return multiArrayView(rawData, paramType, rawData.size(), 0, usage); + return multiArrayView(rawData, paramType, SizeUtils::bytesToBits(rawData.size()), 0, usage); } else { - return arrayView(rawData, paramType.baseTypeObj(), rawData.size(), 0, usage); + return arrayView(rawData, paramType.baseTypeObj(), SizeUtils::bytesToBits(rawData.size()), 0, usage); } case TypeKind::FUNCTION_POINTER: if (!testingMethod.has_value()) { @@ -981,9 +981,9 @@ std::shared_ptr KTestObjectParser::testParameterView( return functionPointerView(testingMethod->getClassTypeName(), testingMethod->name, param.varName); case TypeKind::ARRAY: if (paramType.kinds().size() > 2) { - return multiArrayView(rawData, paramType, rawData.size(), 0, usage); + return multiArrayView(rawData, paramType, SizeUtils::bytesToBits(rawData.size()), 0, usage); } else { - return arrayView(rawData, paramType.baseTypeObj(), rawData.size(), 0, usage); + return arrayView(rawData, paramType.baseTypeObj(), SizeUtils::bytesToBits(rawData.size()), 0, usage); } case TypeKind::UNKNOWN: throw UnImplementedException("No such type"); @@ -1036,14 +1036,11 @@ Tests::MethodDescriptionHash::operator()(const Tests::MethodDescription &methodD } TestMethod::TestMethod(std::string methodName, fs::path bitcodeFile, fs::path sourceFilename, bool is32) - : methodName(std::move(methodName)) - , bitcodeFilePath(std::move(bitcodeFile)) - , sourceFilePath(std::move(sourceFilename)) - , is32bits(is32) -{} + : methodName(std::move(methodName)), bitcodeFilePath(std::move(bitcodeFile)), + sourceFilePath(std::move(sourceFilename)), is32bits(is32) {} bool TestMethod::operator==(const TestMethod &rhs) const { - return std::tie( methodName, bitcodeFilePath, sourceFilePath, is32bits) + return std::tie( methodName, bitcodeFilePath, sourceFilePath, is32bits) == std::tie(rhs.methodName, rhs.bitcodeFilePath, rhs.sourceFilePath, rhs.is32bits); } bool TestMethod::operator!=(const TestMethod &rhs) const { @@ -1052,7 +1049,7 @@ bool TestMethod::operator!=(const TestMethod &rhs) const { UTBotKTestObject::UTBotKTestObject(std::string name, std::vector bytes, std::vector offsets, uint64_t address, bool is_lazy) : name(std::move(name)), bytes(std::move(bytes)), - offsets(std::move(offsets)), address(address), is_lazy(is_lazy) { + offsetsInBytes(std::move(offsets)), address(address), is_lazy(is_lazy) { } UTBotKTestObject::UTBotKTestObject(const ConcretizedObject &kTestObject) diff --git a/server/src/Tests.h b/server/src/Tests.h index 06aec40a..4fa12c4d 100644 --- a/server/src/Tests.h +++ b/server/src/Tests.h @@ -6,6 +6,7 @@ #include "types/Types.h" #include "utils/CollectionUtils.h" #include "utils/PrinterUtils.h" +#include "utils/SizeUtils.h" #include "json.hpp" #include @@ -13,9 +14,14 @@ #include #include +#include +#include +#include +#include #include #include #include +#include #include #include #include @@ -44,10 +50,18 @@ namespace tests { struct UTBotKTestObject { std::string name; std::vector bytes; - std::vector offsets; + std::vector offsetsInBytes; size_t address; bool is_lazy = false; + /** + * Constructs UTBotKTestObject + * @param name object's name + * @param bytes byte array associated with object + * @param offsets in bytes + * @param address object's address + * @param is_lazy whether object is lazy + */ UTBotKTestObject(std::string name, std::vector bytes, std::vector offsets, @@ -65,15 +79,14 @@ namespace tests { Status status; std::vector errorDescriptors; - UTBotKTest(const std::vector &objects, + UTBotKTest(std::vector objects, const Status &status, std::vector &errorDescriptors) : - objects(objects), - status(status), - errorDescriptors(errorDescriptors) - {} + objects(std::move(objects)), + status(status), + errorDescriptors(errorDescriptors) {} - bool isError() { + [[nodiscard]] bool isError() const { return !errorDescriptors.empty(); } @@ -303,24 +316,24 @@ namespace tests { struct MethodParam { types::Type type; std::string name; - std::optional alignment; + std::optional alignment; bool hasIncompleteType = false; MethodParam(types::Type type, std::string name, - std::optional alignment, + std::optional alignment, bool hasIncompleteType = false) - : type(std::move(type)), name(std::move(name)), alignment(std::move(alignment)), + : type(std::move(type)), name(std::move(name)), alignment(alignment), hasIncompleteType(hasIncompleteType) { } - std::string underscoredName() const { + [[nodiscard]] std::string underscoredName() const { return "_" + name; } - bool isChangeable() const { + [[nodiscard]] bool isChangeable() const { if((type.isObjectPointer() || type.isLValueReference()) && !type.isTypeContainsFunctionPointer() && !type.isConstQualifiedValue() && !types::TypesHandler::baseTypeIsVoid(type)) { @@ -329,7 +342,7 @@ namespace tests { return false; } - std::string dataVariableName() const { + [[nodiscard]] std::string dataVariableName() const { return this->type.isTwoDimensionalPointer() ? this->underscoredName() : this->name; @@ -338,18 +351,18 @@ namespace tests { struct TestCaseParamValue { std::string name; - std::optional alignment; + std::optional alignment; std::shared_ptr view; std::vector lazyParams; std::vector lazyValues; TestCaseParamValue() = default; - TestCaseParamValue(const std::string &_name, - const std::optional &_alignment, - const std::shared_ptr &_view) - : name(_name), + TestCaseParamValue(std::string _name, + const std::optional &_alignment, + std::shared_ptr _view) + : name(std::move(_name)), alignment(_alignment), - view(_view) {} + view(std::move(_view)) {} }; struct TestCaseDescription { @@ -470,7 +483,7 @@ namespace tests { } [[nodiscard]] bool hasChangeable() const { - for(const auto& i : params) { + for (const auto& i : params) { if (i.isChangeable()) { return true; } @@ -568,9 +581,9 @@ namespace tests { */ void parseKTest(const MethodKtests &batch, tests::Tests &tests, - const std::unordered_map& methodNameToReturnTypeMap, + const std::unordered_map &methodNameToReturnTypeMap, bool filterByLineFlag, - std::shared_ptr lineInfo); + const std::shared_ptr &lineInfo); private: fs::path sourceFilePath; @@ -609,8 +622,8 @@ namespace tests { void parseTestCases(const UTBotKTestList &cases, bool filterByLineFlag, Tests::MethodDescription &methodDescription, - const std::unordered_map& methodNameToReturnTypeMap, - std::shared_ptr lineInfo); + const std::unordered_map &methodNameToReturnTypeMap, + const std::shared_ptr &lineInfo); /** * Parses parameters that are stored in given objects. Then parameters * are written into paramValues. @@ -636,20 +649,20 @@ namespace tests { std::shared_ptr multiArrayView(const std::vector &byteArray, const types::Type &type, - size_t arraySize, - size_t offset, + size_t arraySizeInBits, + size_t offsetInBits, types::PointerUsage usage); std::shared_ptr arrayView(const std::vector &byteArray, const types::Type &type, - size_t arraySize, - unsigned int offset, + size_t arraySizeInBits, + size_t offsetInBits, types::PointerUsage usage); static std::shared_ptr stringLiteralView(const std::vector &byteArray, size_t length = 0); - std::shared_ptr functionPointerView(const std::optional& scopeName, + std::shared_ptr functionPointerView(const std::optional &scopeName, const std::string &methodName, const std::string ¶mName); @@ -658,12 +671,12 @@ namespace tests { std::shared_ptr structView(const std::vector &byteArray, const types::StructInfo &curStruct, - unsigned int offset, + size_t offsetInBits, types::PointerUsage usage); std::shared_ptr structView(const std::vector &byteArray, const types::StructInfo &curStruct, - unsigned int offset, + size_t offsetInBits, types::PointerUsage usage, const std::optional &testingMethod, const std::string &name, @@ -672,21 +685,21 @@ namespace tests { static std::shared_ptr enumView(const std::vector &byteArray, const types::EnumInfo &enumInfo, - size_t offset, - size_t len); + size_t offsetInBits, + size_t lenInBits); std::shared_ptr primitiveView(const std::vector &byteArray, const types::Type &type, - size_t offset, - size_t len); + size_t offsetInBits, + size_t lenInBits); std::string primitiveCharView(const types::Type &type, std::string value); static std::string primitiveBoolView(const std::string &value); constexpr static const char *const KLEE_PATH_FLAG = "kleePathFlag"; - const std::string PointerWidthType = "unsigned long long"; - const size_t PointerWidthSize = 8; + const std::string PointerWidthType = "std::uintptr_t"; + const size_t PointerWidthSizeInBits = SizeUtils::bytesToBits(sizeof(std::uintptr_t)); constexpr static const char *const KLEE_PATH_FLAG_SYMBOLIC = "kleePathFlagSymbolic"; static std::vector::const_iterator @@ -696,8 +709,9 @@ namespace tests { Tests::TestCaseDescription parseTestCaseParams(const UTBotKTest &ktest, const Tests::MethodDescription &methodDescription, - const std::unordered_map& methodNameToReturnTypeMap, + const std::unordered_map &methodNameToReturnTypeMap, const std::stringstream &traceStream); + void processGlobalParamPreValue(Tests::TestCaseDescription &testCaseDescription, const Tests::MethodParam &globalParam, std::vector &rawKleeParams); @@ -726,7 +740,7 @@ namespace tests { const types::Type ¶mType, Tests::TestCaseParamValue ¶mValue, std::vector &visited, - std::queue& order); + std::queue &order); void assignTypeUnnamedVar(Tests::MethodTestCase &testCase, const Tests::MethodDescription &methodDescription); @@ -734,11 +748,11 @@ namespace tests { void assignTypeStubVar(Tests::MethodTestCase &testCase, const Tests::MethodDescription &methodDescription); - size_t findFieldIndex(const types::StructInfo &structInfo, size_t offset); + size_t findFieldIndex(const types::StructInfo &structInfo, size_t offsetInBits); types::Type traverseLazyInStruct(std::vector &visited, const types::Type &curVarType, - size_t offset, + size_t offsetInBits, const Tests::MethodTestCase &testCase, const Tests::MethodDescription &methodDescription); @@ -747,9 +761,14 @@ namespace tests { const std::string &name, std::string res, const types::Type ¶mType) const; + + void + getTestParamView(const Tests::MethodDescription &methodDescription, const std::vector &rawKleeParams, + const RawKleeParam &emptyKleeParam, Tests::TestCaseDescription &testCaseDescription, + const Tests::MethodParam& methodParam, std::shared_ptr &testParamView); }; /** - * @brief This function is used for converting primiive value of a specific type + * @brief This function is used for converting primitive value of a specific type * To a string value which we can print to .cpp file. */ template @@ -766,37 +785,86 @@ namespace tests { ss << value; return ss.str(); } + + /** + * Sign extension of number stored in @b bytes with sign bit at @b signPos + * @tparam T type: @a char or @a unsigned @a char + * @param bytes byte array + * @param len length of bytes + * @param signPos 0-based index of sign bit in two's complement + */ + template + void sext(T* bytes, size_t len, size_t signPos) { + int bit = (bytes[signPos / CHAR_BIT] >> (signPos % CHAR_BIT)) & 1; + if (bit) { + T mask = static_cast((1 << CHAR_BIT) - 1); + bytes[signPos / CHAR_BIT] |= mask ^ ((1 << (signPos % CHAR_BIT)) - 1); + for (size_t i = signPos / CHAR_BIT + 1; i < len; ++i) { + bytes[i] = mask; + } + } + } + /** * This function is used for converting sequence of bytes to specific type. - * Returns string representation of a value recorded in byteArray. + * Returns string representation of a value recorded in @b byteArray. * @tparam T - type of value * @param byteArray - * @param offset - initial position of a value in byteArray - * @param len - size of T + * @param offset - initial position in bits of a value in byteArray + * @param len - number of bits to read * @return string representation of value */ template std::string readBytesAsValue(const std::vector &byteArray, size_t offset, size_t len) { - char bytes[len]; - for (int j = 0; j < len; j++) { - bytes[j] = byteArray[offset + j]; + char bytes[sizeof(T)] = {}; + if (offset % CHAR_BIT != 0 || len % CHAR_BIT != 0) { + // WARNING: assuming little endian and two's complement + size_t lo = offset % CHAR_BIT; + size_t hi = CHAR_BIT - lo; + size_t stop = (offset + len + CHAR_BIT - 1) / CHAR_BIT; + for (size_t i = offset / CHAR_BIT, j = 0; i < stop; ++i, ++j) { + auto byte = static_cast(byteArray[i]); + if (i + 1 == stop) { + size_t top = (offset + len) % CHAR_BIT; + if (top == 0) { + top = CHAR_BIT; + } + byte &= ((1 << top) - 1); + } + unsigned char low = byte & ((1 << lo) - 1); + unsigned char high = byte >> lo; + if (j > 0) { + bytes[j - 1] |= low << hi; + } + if (j < sizeof(T)) { + bytes[j] = high; + } + } + } else { + for (size_t j = 0; j < len / CHAR_BIT; j++) { + bytes[j] = byteArray[offset / CHAR_BIT + j]; + } } - T *pTypeValue = (T *)bytes; + if constexpr(std::is_signed_v) { + sext(bytes, sizeof(T), len - 1); + } + T *pTypeValue = (T *) bytes; T pValue = *pTypeValue; return primitiveValueToString(pValue); } + /** * Same as readBytesAsValue, but returns result of readBytesAsValue that is already * parametrized by type that corresponds to given typeName. * @param typeName - string name of type * @param byteArray - array of bytes - * @param offset - initial position - * @param len - size of type that corresponds to typeName + * @param offsetInBits - initial position in bits + * @param lenInBits - number of bits to read * @return string representation of value. */ std::string readBytesAsValueForType(const std::vector &byteArray, const std::string &typeName, - unsigned int offset, - unsigned int len); + size_t offsetInBits, + size_t lenInBits); } #endif // UNITTESTBOT_TESTS_H diff --git a/server/src/fetchers/Fetcher.cpp b/server/src/fetchers/Fetcher.cpp index 06f4ac94..2958b14b 100644 --- a/server/src/fetchers/Fetcher.cpp +++ b/server/src/fetchers/Fetcher.cpp @@ -24,8 +24,8 @@ Fetcher::Fetcher(Options options, const std::shared_ptr &compilationDatabase, tests::TestsMap &tests, types::TypeMaps *types, - uint64_t *pointerSize, - uint64_t *maximumAlignment, + size_t *pointerSize, + size_t *maximumAlignment, const fs::path &compileCommandsJsonPath, bool fetchFunctionBodies) : options(options), projectTests(&tests), projectTypes(types), diff --git a/server/src/fetchers/Fetcher.h b/server/src/fetchers/Fetcher.h index f354c55d..3ea6a1b2 100644 --- a/server/src/fetchers/Fetcher.h +++ b/server/src/fetchers/Fetcher.h @@ -59,8 +59,8 @@ class Fetcher { const std::shared_ptr &compilationDatabase, tests::TestsMap &tests, types::TypeMaps *types, - uint64_t *pointerSize, - uint64_t *maximumAlignment, + size_t *pointerSize, + size_t *maximumAlignment, const fs::path &compileCommandsJsonPath, bool fetchFunctionBodies); @@ -76,8 +76,8 @@ class Fetcher { tests::TestsMap *const projectTests; types::TypeMaps *const projectTypes; - uint64_t *const pointerSize; - uint64_t *const maximumAlignment; + size_t *const pointerSize; + size_t *const maximumAlignment; fs::path buildRootPath; std::shared_ptr structsToDeclare = std::make_shared(); diff --git a/server/src/printers/KleeConstraintsPrinter.cpp b/server/src/printers/KleeConstraintsPrinter.cpp index 891c08dd..93059121 100644 --- a/server/src/printers/KleeConstraintsPrinter.cpp +++ b/server/src/printers/KleeConstraintsPrinter.cpp @@ -5,6 +5,8 @@ #include "loguru.h" +#include + using namespace types; using printer::KleeConstraintsPrinter; @@ -47,10 +49,14 @@ void KleeConstraintsPrinter::genConstraintsForPrimitive(const ConstraintsState & if (!cons.empty()) { strFunctionCall(PrinterUtils::KLEE_PREFER_CEX, { state.paramName, cons }); } else { - ss << LINE_INDENT() << "// No constraints for " << state.curElement << NL; + noConstraints(state.curElement); } } +void KleeConstraintsPrinter::noConstraints(const std::string &cause) { + ss << LINE_INDENT() << "// No constraints for " << cause << NL; +} + void KleeConstraintsPrinter::genConstraintsForEnum(const ConstraintsState &state) { types::EnumInfo enumInfo = typesHandler->getEnumInfo(state.curType); @@ -113,24 +119,24 @@ void KleeConstraintsPrinter::genConstraintsForStruct(const ConstraintsState &sta StructInfo curStruct = typesHandler->getStructInfo(state.curType); bool isStruct = curStruct.subType == SubType::Struct; for (const auto &field : curStruct.fields) { - std::string errorMessage = "Unrecognized field of type '" + field.type.typeName() + - "' in struct '" + curStruct.name + "'."; + if (field.isUnnamedBitfield()) { + noConstraints("unnamed bit fields"); + continue; + } auto access = PrinterUtils::getFieldAccess(state.curElement, field); ConstraintsState newState = { state.paramName, access, field.type, isStruct ? state.endString : false, state.depth + 1 }; - TypeKind kind = typesHandler->getTypeKind(field.type); std::string stubFunctionName = PrinterUtils::getFunctionPointerAsStructFieldStubName(curStruct.name, field.name); - switch (kind) { + switch (typesHandler->getTypeKind(field.type)) { case TypeKind::STRUCT_LIKE: genConstraintsForStruct(newState); break; case TypeKind::ENUM: genConstraintsForEnum(newState); break; - case TypeKind::PRIMITIVE: genConstraintsForPrimitive(newState); break; @@ -148,8 +154,11 @@ void KleeConstraintsPrinter::genConstraintsForStruct(const ConstraintsState &sta field, stubFunctionName); break; - case TypeKind::UNKNOWN: + case TypeKind::UNKNOWN: { + std::string errorMessage = "Unrecognized field of type '" + field.type.typeName() + + "' in struct '" + curStruct.name + "'."; throw UnImplementedException(errorMessage); + } default: std::string message = "Missing case for this TypeKind in switch"; LOG_S(ERROR) << message; @@ -157,15 +166,16 @@ void KleeConstraintsPrinter::genConstraintsForStruct(const ConstraintsState &sta } } } + std::string KleeConstraintsPrinter::cexConstraints(const std::string &name, const types::Type &type) { - std::stringstream ssCex; if (!CollectionUtils::containsKey(TypesHandler::preferredConstraints(), type.baseType())) { return ""; } - for (int i = 0; i < TypesHandler::preferredConstraints().at(type.baseType()).size(); i++) { - ssCex << name; - ssCex << TypesHandler::preferredConstraints().at(type.baseType())[i]; - if (i < (int)TypesHandler::preferredConstraints().at(type.baseType()).size() - 1) { + std::stringstream ssCex; + const std::vector &constraints = TypesHandler::preferredConstraints().at(type.baseType()); + for (size_t i = 0; i < constraints.size(); i++) { + ssCex << name << " " << constraints[i]; + if (i + 1 < constraints.size()) { ssCex << " & "; } } diff --git a/server/src/printers/KleeConstraintsPrinter.h b/server/src/printers/KleeConstraintsPrinter.h index 7d65852f..3c7c9c88 100644 --- a/server/src/printers/KleeConstraintsPrinter.h +++ b/server/src/printers/KleeConstraintsPrinter.h @@ -45,6 +45,8 @@ namespace printer { void genConstraintsForPointerInStruct(const ConstraintsState &state); static std::string cexConstraints(const std::string &name, const types::Type &type); + + void noConstraints(const std::string &cause); }; } diff --git a/server/src/printers/KleePrinter.cpp b/server/src/printers/KleePrinter.cpp index 6227d518..79551ae9 100644 --- a/server/src/printers/KleePrinter.cpp +++ b/server/src/printers/KleePrinter.cpp @@ -72,15 +72,15 @@ void KleePrinter::writeTestedFunction(const Tests &tests, } fs::path KleePrinter::writeTmpKleeFile( - const Tests &tests, - const std::string &buildDir, - const PathSubstitution &pathSubstitution, - const std::optional &predicateInfo, - const std::string &testedMethod, - const std::optional &testedClass, - bool onlyForOneFunction, - bool onlyForOneClass, - const std::function &methodFilter) { + const Tests &tests, + const std::string &buildDir, + const PathSubstitution &pathSubstitution, + const std::optional &predicateInfo, + const std::string &testedMethod, + const std::optional &testedClass, + bool onlyForOneFunction, + bool onlyForOneClass, + const std::function &methodFilter) { resetStream(); writeCopyrightHeader(); @@ -401,7 +401,7 @@ void KleePrinter::makeBracketsForStrPredicate(const std::optional &inf void KleePrinter::genReturnDeclaration(const Tests::MethodDescription &testMethod, const std::optional &predicateInfo) { - // If return type is a pointer, we compare values that are stored at this pointers, + // If return type is a pointer, we compare values that are stored at these pointers, // not the pointers themselves Type returnType = types::TypesHandler::isVoid(testMethod.returnType.baseTypeObj()) ? Type::minimalScalarType() @@ -424,10 +424,10 @@ void KleePrinter::genReturnDeclaration(const Tests::MethodDescription &testMetho } void KleePrinter::genParamsKleeAssumes( - const Tests::MethodDescription &testMethod, - const std::optional &predicateInfo, - const std::string &testedMethod, - bool onlyForOneEntity) { + const Tests::MethodDescription &testMethod, + const std::optional &predicateInfo, + const std::string &testedMethod, + bool onlyForOneEntity) { visitor::KleeAssumeReturnValueVisitor(typesHandler, this).visit(testMethod, predicateInfo); if (!onlyForOneEntity && !testedMethod.empty() && !predicateInfo.has_value()) { std::string assumption = concat("(", PrinterUtils::KLEE_PATH_FLAG, PrinterUtils::EQ_OPERATOR, PrinterUtils::KLEE_PATH_FLAG_SYMBOLIC, ") & (", diff --git a/server/src/types/Types.cpp b/server/src/types/Types.cpp index 7599a460..faa0806d 100644 --- a/server/src/types/Types.cpp +++ b/server/src/types/Types.cpp @@ -6,8 +6,12 @@ #include "TypeVisitor.h" #include "exceptions/UnImplementedException.h" #include "utils/PrinterUtils.h" +#include "utils/SizeUtils.h" #include "loguru.h" + +#include + /* * class Type */ @@ -146,11 +150,9 @@ bool types::Type::maybeReturnArray() const { size_t types::Type::countReturnPointers(bool decrementIfArray) const { size_t returnPointer = 0; - for (size_t i = 0; i < this->pointerArrayKinds().size(); ++i) { - returnPointer += - this->pointerArrayKinds()[i]->getKind() == AbstractType::OBJECT_POINTER - ? 1 - : 0; + const std::vector> pointerArrayKinds = this->pointerArrayKinds(); + for (const auto &pointerArrayKind: pointerArrayKinds) { + returnPointer += pointerArrayKind->getKind() == AbstractType::OBJECT_POINTER; } if (decrementIfArray && maybeReturnArray()) { returnPointer--; @@ -167,7 +169,7 @@ std::vector types::Type::arraysSizes(PointerUsage usage) const { return {}; } std::vector sizes; - for (const auto& kind: pointerArrayKinds()) { + for (const auto &kind: pointerArrayKinds()) { switch (kind->getKind()) { case AbstractType::ARRAY: sizes.push_back(kind->getSize()); @@ -279,9 +281,10 @@ const std::string &types::Type::getStdinParamName() { } bool types::Type::isPointerToPointer() const { - return pointerArrayKinds().size() > 1 && - pointerArrayKinds()[0]->getKind() == AbstractType::OBJECT_POINTER && - pointerArrayKinds()[1]->getKind() == AbstractType::OBJECT_POINTER; + const std::vector> pointerArrayKinds = this->pointerArrayKinds(); + return pointerArrayKinds.size() > 1 && + pointerArrayKinds[0]->getKind() == AbstractType::OBJECT_POINTER && + pointerArrayKinds[1]->getKind() == AbstractType::OBJECT_POINTER; } @@ -290,13 +293,14 @@ bool types::Type::isTwoDimensionalPointer() const { } bool types::Type::isPointerToArray() const { - return pointerArrayKinds().size() > 1 && - pointerArrayKinds()[0]->getKind() == AbstractType::OBJECT_POINTER && - pointerArrayKinds()[1]->getKind() == AbstractType::ARRAY; + const std::vector> pointerArrayKinds = this->pointerArrayKinds(); + return pointerArrayKinds.size() > 1 && + pointerArrayKinds[0]->getKind() == AbstractType::OBJECT_POINTER && + pointerArrayKinds[1]->getKind() == AbstractType::ARRAY; } bool types::Type::isConstQualifiedValue() const { - for(const auto& kind : mKinds) { + for (const auto &kind: mKinds) { if(kind->getKind() == AbstractType::SIMPLE) { if(dynamic_cast(kind.get())->isConstQualified()) { return true; @@ -312,21 +316,14 @@ bool types::Type::isConstQualifiedValue() const { } bool types::Type::isTypeContainsPointer() const { - for (const auto &kind : pointerArrayKinds()) { - if (kind->getKind() == AbstractType::OBJECT_POINTER) { - return true; - } - } - return false; + const std::vector> pointerArrayKinds = this->pointerArrayKinds(); + return std::any_of(pointerArrayKinds.cbegin(), pointerArrayKinds.cend(), + [](auto const &kind){ return kind->getKind() == AbstractType::OBJECT_POINTER; }); } bool types::Type::isTypeContainsFunctionPointer() const { - for (const auto &kind : mKinds) { - if (kind->getKind() == AbstractType::FUNCTION_POINTER) { - return true; - } - } - return false; + return std::any_of(mKinds.cbegin(), mKinds.cend(), + [](auto const &kind){ return kind->getKind() == AbstractType::FUNCTION_POINTER; }); } int types::Type::indexOfFirstPointerInTypeKinds() const { @@ -387,6 +384,19 @@ void types::Type::replaceUsedType(const types::TypeName &newUsedType) { /* * Integer types */ +static const std::unordered_map integerTypesToSizes = { + {"utbot_byte", SizeUtils::bytesToBits(sizeof(char))}, // we use different name to not trigger char processing + {"short", SizeUtils::bytesToBits(sizeof(short))}, + {"int", SizeUtils::bytesToBits(sizeof(int))}, + {"long", SizeUtils::bytesToBits(sizeof(long))}, + {"long long", SizeUtils::bytesToBits(sizeof(long long))}, + {"unsigned short", SizeUtils::bytesToBits(sizeof(unsigned short))}, + {"unsigned int", SizeUtils::bytesToBits(sizeof(unsigned int))}, + {"unsigned long", SizeUtils::bytesToBits(sizeof(unsigned long))}, + {"unsigned long long", SizeUtils::bytesToBits(sizeof(unsigned long long))}, + {"unsigned char", SizeUtils::bytesToBits(sizeof(unsigned char))} // we do not want to treat an unsigned char as character literal +}; + bool types::TypesHandler::isIntegerType(const Type &type) { return type.isSimple() && isIntegerType(type.baseType()); } @@ -396,40 +406,56 @@ bool types::TypesHandler::isUnsignedType(const Type &type) { } bool types::TypesHandler::isIntegerType(const TypeName &typeName) { - return CollectionUtils::containsKey(integerTypesToSizes(), typeName); + return CollectionUtils::containsKey(integerTypesToSizes, typeName); } /* * Floating point types */ +static const std::unordered_map floatingPointTypesToSizes = { + {"float", SizeUtils::bytesToBits(sizeof(float))}, + {"double", SizeUtils::bytesToBits(sizeof(double))}, + {"long double", SizeUtils::bytesToBits(sizeof(long double))} +}; + bool types::TypesHandler::isFloatingPointType(const Type &type) { return type.isSimple() && isFloatingPointType(type.baseType()); } bool types::TypesHandler::isFloatingPointType(const TypeName &type) { - return CollectionUtils::containsKey(floatingPointTypesToSizes(), type); + return CollectionUtils::containsKey(floatingPointTypesToSizes, type); } /* * Character types */ +static const std::unordered_map characterTypesToSizes = { + {"char", SizeUtils::bytesToBits(sizeof(char))}, + {"signed char", SizeUtils::bytesToBits(sizeof(signed char))}, +}; + bool types::TypesHandler::isCharacterType(const Type &type) { return type.isSimple() && isCharacterType(type.baseType()); } bool types::TypesHandler::isCharacterType(const TypeName &type) { - return CollectionUtils::containsKey(characterTypesToSizes(), type); + return CollectionUtils::containsKey(characterTypesToSizes, type); } /* * Boolean types */ +static const std::unordered_map boolTypesToSizes = { + {"bool", SizeUtils::bytesToBits(sizeof(bool))}, + {"_Bool", SizeUtils::bytesToBits(sizeof(bool))} +}; + bool types::TypesHandler::isBoolType(const Type &type) { return type.isSimple() && isBoolType(type.baseType()); } bool types::TypesHandler::isBoolType(const TypeName &type) { - return CollectionUtils::containsKey(boolTypesToSizes(), type); + return CollectionUtils::containsKey(boolTypesToSizes, type); } /* @@ -462,6 +488,10 @@ bool types::TypesHandler::isStructLike(uint64_t id) const { return typeIsInMap(id, typeMaps.structs); } +bool types::Field::isUnnamedBitfield() const { + return name.empty() && TypesHandler::isPrimitiveType(type); +} + /* * Enum types */ @@ -527,15 +557,15 @@ types::EnumInfo types::TypesHandler::getEnumInfo(uint64_t id) const { return typeFromMap(id, typeMaps.enums); } -/* - * Check if type is a pointer +/** + * Checks whether type is a pointer */ bool types::TypesHandler::isObjectPointerType(const Type &type) { return type.isObjectPointer(); } -/* - * Check if type is an array +/** + * Checks whether type is an array */ bool types::TypesHandler::isArrayType(const Type &type) { return type.isArray(); @@ -551,19 +581,19 @@ bool types::TypesHandler::isOneDimensionPointer(const types::Type &type) { size_t types::TypesHandler::typeSize(const types::Type &type) const { if (isIntegerType(type)) { - return integerTypesToSizes().at(type.baseType()); + return integerTypesToSizes.at(type.baseType()); } if (isBoolType(type)) { - return boolTypesToSizes().at(type.baseType()); + return boolTypesToSizes.at(type.baseType()); } if (isFloatingPointType(type)) { - return floatingPointTypesToSizes().at(type.baseType()); + return floatingPointTypesToSizes.at(type.baseType()); } if (isCharacterType(type)) { - return characterTypesToSizes().at(type.baseType()); + return characterTypesToSizes.at(type.baseType()); } if (isStructLike(type)) { @@ -581,11 +611,11 @@ size_t types::TypesHandler::typeSize(const types::Type &type) const { } if (isObjectPointerType(type)) { - return getPointerSize(); + return SizeUtils::bytesToBits(getPointerSize()); } if (isPointerToFunction(type)) { - return sizeof(char *); + return SizeUtils::bytesToBits(sizeof(char *)); } throw UnImplementedException("Type is unknown for: " + type.typeName()); @@ -595,9 +625,9 @@ std::string types::TypesHandler::removeConstPrefix(const TypeName &type) { std::vector tmp = StringUtils::split(type); if (tmp[0] == CONST_QUALIFIER) { std::string res; - for (int i = 1; i < (int) tmp.size(); i++) { + for (size_t i = 1; i < tmp.size(); i++) { res += tmp[i]; - if (i < (int) tmp.size() - 1) { + if (i + 1 < tmp.size()) { res.push_back(' '); } } @@ -639,88 +669,44 @@ std::string types::TypesHandler::removeArrayBrackets(TypeName type) { return type; } -std::unordered_map types::TypesHandler::integerTypesToSizes() noexcept { - static std::unordered_map integerTypes = { - {"utbot_byte", sizeof(char)}, //we use different name to not trigger char processing - {"short", sizeof(short)}, - {"int", sizeof(int)}, - {"long", sizeof(long)}, - {"long long", sizeof(long long)}, - {"unsigned short", sizeof(unsigned short)}, - {"unsigned int", sizeof(unsigned int)}, - {"unsigned long", sizeof(unsigned long)}, - {"unsigned long long", sizeof(unsigned long long)}, - {"unsigned char", sizeof(unsigned char)} // we do not want to treat an unsigned char as character literal - }; - return integerTypes; -} - testsgen::ValidationType types::TypesHandler::getIntegerValidationType(const Type &type) { size_t size; if (isIntegerType(type)) { - size = integerTypesToSizes().at(type.baseType()); + size = integerTypesToSizes.at(type.baseType()); } else { ABORT_F("type is not an integerType: %s", type.baseType().c_str()); } bool isUnsigned = isUnsignedType(type); - if (size == 1) { + if (size == 8) { return (isUnsigned) ? testsgen::UINT8_T : testsgen::INT8_T; - } else if (size == 2) { + } else if (size == 16) { return (isUnsigned) ? testsgen::UINT16_T : testsgen::INT16_T; - } else if (size == 4) { + } else if (size == 32) { return (isUnsigned) ? testsgen::UINT32_T : testsgen::INT32_T; - } else if (size == 8) { + } else if (size == 64) { return (isUnsigned) ? testsgen::UINT64_T : testsgen::INT64_T; } else { - ABORT_F("Unknown integer size: %d", (int)size); + ABORT_F("Unknown integer size: %zu", size); } } -std::unordered_map types::TypesHandler::floatingPointTypesToSizes() noexcept { - static std::unordered_map floatingPointTypes = { - {"float", sizeof(float)}, - {"double", sizeof(double)}, - {"long double", sizeof(long double)} - }; - - return floatingPointTypes; -} - -std::unordered_map types::TypesHandler::characterTypesToSizes() noexcept { - static std::unordered_map characterTypes = { - {"char", sizeof(char)}, - {"signed char", sizeof(signed char)}, - }; - - return characterTypes; -} - -std::unordered_map types::TypesHandler::boolTypesToSizes() noexcept { - static std::unordered_map boolTypes = { - {"bool", sizeof(bool)}, - {"_Bool", sizeof(bool)} - }; - - return boolTypes; -} - -std::unordered_map> types::TypesHandler::preferredConstraints() noexcept { - static std::unordered_map> constraints = { - {"char", {" >= 'a'", " <= 'z'", " != '\\0'"}}, - {"signed char", {" >= 'a'", " <= 'z'", " != '\\0'"}}, - {"unsigned char", {" >= 'a'", " <= 'z'", " != '\\0'"}}, - {"short", {" >= -10 ", " <= 10"}}, - {"int", {" >= -10 ", " <= 10"}}, - {"long", {" >= -10 ", " <= 10"}}, - {"long long", {" >= -10 ", " <= 10"}}, +const std::unordered_map> &types::TypesHandler::preferredConstraints() noexcept { + static const std::unordered_map> constraints = { + {"char", {">= 'a'", "<= 'z'", "!= '\\0'"}}, + {"signed char", {">= 'a'", "<= 'z'", "!= '\\0'"}}, + {"unsigned char", {">= 'a'", "<= 'z'", "!= '\\0'"}}, + {"short", {">= -10", "<= 10"}}, + {"int", {">= -10", "<= 10"}}, + {"long", {">= -10", "<= 10"}}, + {"long long", {">= -10", "<= 10"}}, {"unsigned short", {"<= 10"}}, {"unsigned int", {"<= 10"}}, {"unsigned long", {"<= 10"}}, {"unsigned long long", {"<= 10"}}, - {"float", {" >= -10 ", " <= 10"}}, - {"double", {" >= -10 ", " <= 10"}}, - {"long double", {" >= -10 ", " <= 10"}}, - {"void", {" <= 10"}}, + {"float", {">= -10", "<= 10"}}, + {"double", {">= -10", "<= 10"}}, + {"long double", {">= -10", "<= 10"}}, + {"void", {"<= 10"}}, }; return constraints; diff --git a/server/src/types/Types.h b/server/src/types/Types.h index ef832433..fbd4027e 100644 --- a/server/src/types/Types.h +++ b/server/src/types/Types.h @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -258,9 +259,10 @@ namespace types { struct Field { types::Type type; std::string name; - unsigned int size; - // reassigned in structFields - unsigned int offset = 0; + /// size in @b bits + size_t size; + /// offset in @b bits, reassigned in structFields + size_t offset = 0; enum AccessSpecifier { AS_pubic, AS_protected, @@ -268,14 +270,17 @@ namespace types { AS_none }; AccessSpecifier accessSpecifier = AS_pubic; + [[nodiscard]] bool isUnnamedBitfield() const; }; struct TypeInfo { fs::path filePath; std::string name; std::string definition; - uint64_t size; - uint64_t alignment; + /// size in @b bits + size_t size; + /// alignment in @b bytes + size_t alignment; }; typedef std::unordered_map> FPointerMap; @@ -289,7 +294,7 @@ namespace types { std::vector fields{}; size_t longestFieldIndexForUnionInit; FPointerMap functionFields{}; - bool hasUnnamedFields; + bool hasAnonymousStructOrUnion; bool isCLike; SubType subType; }; @@ -337,8 +342,8 @@ namespace types { class TypesHandler { public: struct SizeContext { - uint64_t pointerSize = 8; - uint64_t maximumAlignment = 16; + size_t pointerSize = 8; /// pointerSize in @b bytes + size_t maximumAlignment = 16; /// maximumAlignment in @b bytes }; explicit TypesHandler(TypeMaps &types, SizeContext sizeContext) @@ -347,7 +352,7 @@ namespace types { /** * This functions calculates size of a given type. For structs in it calculates sum of sizes of its fields, * ignoring alignment. - * @return size of given type. + * @return size of given type in @b bits. */ size_t typeSize(const types::Type &type) const; @@ -532,13 +537,13 @@ namespace types { * 'klee_prefer_cex' function. * @return map type -> constraints. */ - static std::unordered_map> preferredConstraints() noexcept; + static const std::unordered_map> &preferredConstraints() noexcept; size_t getPointerSize() const noexcept { return sizeContext.pointerSize; } - uint64_t getMaximumAlignment() const noexcept { + size_t getMaximumAlignment() const noexcept { return sizeContext.maximumAlignment; } @@ -628,11 +633,6 @@ namespace types { IsSupportedTypeArgumentsHash> isSupportedTypeHash{}; - static std::unordered_map integerTypesToSizes() noexcept; - static std::unordered_map floatingPointTypesToSizes() noexcept; - static std::unordered_map characterTypesToSizes() noexcept; - static std::unordered_map boolTypesToSizes() noexcept; - template bool typeIsInMap(uint64_t id, const std::unordered_map& someMap) const { if (CollectionUtils::containsKey(someMap, id)) { diff --git a/server/src/types/TypesResolver.cpp b/server/src/types/TypesResolver.cpp index 8074498b..4f7f0fe5 100644 --- a/server/src/types/TypesResolver.cpp +++ b/server/src/types/TypesResolver.cpp @@ -33,14 +33,14 @@ bool isCandidateToReplace(uint64_t id, } static size_t getRecordSize(const clang::RecordDecl *D) { - return D->getASTContext().getTypeSize(D->getASTContext().getRecordType(D)) / 8; + return D->getASTContext().getTypeSize(D->getASTContext().getRecordType(D)); } static size_t getDeclAlignment(const clang::TagDecl *T) { return T->getASTContext().getTypeAlign(T->getTypeForDecl()) / 8; } -template +template static void addInfo(uint64_t id, std::unordered_map &someMap, Info info) { auto [iterator, inserted] = someMap.emplace(id, info); LOG_IF_S(MAX, !inserted) << "Type with id=" << id << " already existed"; @@ -66,7 +66,7 @@ std::string TypesResolver::getFullname(const clang::TagDecl *TD, const clang::Qu fullname.insert(std::make_pair(id, currentStructName)); if (Paths::getSourceLanguage(sourceFilePath) == utbot::Language::C) { - if (const clang::RecordDecl *parentNode = llvm::dyn_cast(TD->getLexicalParent())) { + if (const auto *parentNode = llvm::dyn_cast(TD->getLexicalParent())) { clang::QualType parentCanonicalType = parentNode->getASTContext().getTypeDeclType(parentNode).getCanonicalType(); uint64_t parentID = types::Type::getIdFromCanonicalType(parentCanonicalType); if (!fullname[parentID].empty()) { @@ -97,13 +97,14 @@ void TypesResolver::resolveStructEx(const clang::RecordDecl *D, const std::strin types::StructInfo structInfo; fs::path filename = - sourceManager.getFilename(sourceManager.getSpellingLoc(D->getLocation())).str(); - fs::path sourceFilePath = sourceManager.getFileEntryForID(sourceManager.getMainFileID())->tryGetRealPathName().str(); + sourceManager.getFilename(sourceManager.getSpellingLoc(D->getLocation())).str(); + fs::path sourceFilePath = sourceManager.getFileEntryForID( + sourceManager.getMainFileID())->tryGetRealPathName().str(); structInfo.filePath = Paths::getCCJsonFileFullPath(filename, parent->buildRootPath); structInfo.name = getFullname(D, canonicalType, id, sourceFilePath); - structInfo.hasUnnamedFields = false; + structInfo.hasAnonymousStructOrUnion = false; if (Paths::getSourceLanguage(sourceFilePath) == utbot::Language::CXX) { - const clang::CXXRecordDecl *cppD = dynamic_cast(D); + const auto *cppD = llvm::dyn_cast(D); structInfo.isCLike = cppD != nullptr && cppD->isCLike(); } else { @@ -122,11 +123,9 @@ void TypesResolver::resolveStructEx(const clang::RecordDecl *D, const std::strin structInfo.longestFieldIndexForUnionInit = SIZE_MAX; size_t i = 0; - uint64_t maxFieldSize = 0; + size_t maxFieldSize = 0; for (const clang::FieldDecl *F : D->fields()) { - if (F->isUnnamedBitfield()) { - continue; - } + structInfo.hasAnonymousStructOrUnion |= F->isAnonymousStructOrUnion(); types::Field field; field.name = F->getNameAsString(); const clang::QualType paramType = F->getType().getCanonicalType(); @@ -149,26 +148,25 @@ void TypesResolver::resolveStructEx(const clang::RecordDecl *D, const std::strin F->getType()->getPointeeType()->getPointeeType()->getAs(), field.name, sourceManager, field.type.isArrayOfPointersToFunction()); } - field.size = context.getTypeSize(F->getType()) / 8; - field.offset = context.getFieldOffset(F) / 8; + field.size = F->isBitField() ? F->getBitWidthValue(context) : context.getTypeSize(F->getType()); + field.offset = context.getFieldOffset(F); if (LogUtils::isMaxVerbosity()) { ss << "\n\t" << field.type.typeName() << " " << field.name << ";"; } - structInfo.hasUnnamedFields |= F->isAnonymousStructOrUnion(); if (Paths::getSourceLanguage(sourceFilePath) == utbot::Language::CXX) { switch (F->getAccess()) { - case clang::AccessSpecifier::AS_private : - field.accessSpecifier = types::Field::AS_private; - break; - case clang::AccessSpecifier::AS_protected : - field.accessSpecifier = types::Field::AS_protected; - break; - case clang::AccessSpecifier::AS_public : - field.accessSpecifier = types::Field::AS_pubic; - break; - case clang::AccessSpecifier::AS_none : - field.accessSpecifier = types::Field::AS_none; - break; + case clang::AccessSpecifier::AS_private : + field.accessSpecifier = types::Field::AS_private; + break; + case clang::AccessSpecifier::AS_protected : + field.accessSpecifier = types::Field::AS_protected; + break; + case clang::AccessSpecifier::AS_public : + field.accessSpecifier = types::Field::AS_pubic; + break; + case clang::AccessSpecifier::AS_none : + field.accessSpecifier = types::Field::AS_none; + break; } } else { field.accessSpecifier = types::Field::AS_pubic; @@ -254,8 +252,9 @@ void TypesResolver::resolveEnum(const clang::EnumDecl *EN, const std::string &na << "\tFile path: " << enumInfo.filePath.string(); LOG_S(DEBUG) << ss.str(); } -void TypesResolver::updateMaximumAlignment(uint64_t alignment) const { - uint64_t &maximumAlignment = *(this->parent->maximumAlignment); + +void TypesResolver::updateMaximumAlignment(size_t alignment) const { + size_t &maximumAlignment = *(this->parent->maximumAlignment); maximumAlignment = std::max(maximumAlignment, alignment); } diff --git a/server/src/types/TypesResolver.h b/server/src/types/TypesResolver.h index c343c62d..badbc0b4 100644 --- a/server/src/types/TypesResolver.h +++ b/server/src/types/TypesResolver.h @@ -5,6 +5,7 @@ #include +#include #include #include @@ -33,7 +34,7 @@ class TypesResolver { std::string getFullname(const clang::TagDecl *TD, const clang::QualType &canonicalType, uint64_t id, const fs::path &sourceFilePath); - void updateMaximumAlignment(uint64_t alignment) const; + void updateMaximumAlignment(size_t alignment) const; }; diff --git a/server/src/utils/SizeUtils.cpp b/server/src/utils/SizeUtils.cpp new file mode 100644 index 00000000..85d7e516 --- /dev/null +++ b/server/src/utils/SizeUtils.cpp @@ -0,0 +1,9 @@ +#include "SizeUtils.h" + +size_t SizeUtils::bytesToBits(size_t bytes) { + return bytes * CHAR_BIT; +} + +size_t SizeUtils::bitsToBytes(size_t bits) { + return bits / CHAR_BIT; +} diff --git a/server/src/utils/SizeUtils.h b/server/src/utils/SizeUtils.h new file mode 100644 index 00000000..8f14ca9c --- /dev/null +++ b/server/src/utils/SizeUtils.h @@ -0,0 +1,14 @@ +#ifndef UTBOTCPP_SIZEUTILS_H +#define UTBOTCPP_SIZEUTILS_H + +#include +#include + +namespace SizeUtils { + size_t bytesToBits(size_t bytes); + + size_t bitsToBytes(size_t bits); +} + + +#endif //UTBOTCPP_SIZEUTILS_H diff --git a/server/src/utils/StringUtils.cpp b/server/src/utils/StringUtils.cpp index f9bb3cea..01e89330 100644 --- a/server/src/utils/StringUtils.cpp +++ b/server/src/utils/StringUtils.cpp @@ -150,4 +150,50 @@ namespace StringUtils { s.erase(std::remove(s.begin(), s.end(), '\n'), s.end()); } + template<> + int stot<>(const std::string& s) { + return std::stoi(s); + } + template<> + long stot(const std::string& s) { + return std::stol(s); + } + template<> + long long stot(const std::string& s) { + return std::stoll(s); + } + template<> + unsigned int stot(const std::string& s) { + return std::stoul(s); + } + template<> + unsigned long stot(const std::string& s) { + return std::stoul(s); + } + template<> + unsigned long long stot(const std::string& s) { + return std::stoull(s); + } + template<> + float stot(const std::string& s) { + return std::stof(s); + } + template<> + double stot(const std::string& s) { + return std::stod(s); + } + template<> + long double stot(const std::string& s) { + return std::stold(s); + } + template<> + bool stot(const std::string& s) { + if (s == "false") { + return false; + } else if (s == "true") { + return true; + } + throw std::invalid_argument("Wrong bool value: " + s); + } + } diff --git a/server/src/utils/StringUtils.h b/server/src/utils/StringUtils.h index 3e24a758..58439274 100644 --- a/server/src/utils/StringUtils.h +++ b/server/src/utils/StringUtils.h @@ -80,6 +80,11 @@ namespace StringUtils { std::string repeat(const std::string &s, int n); bool contains(std::string_view s, std::string_view t); + + template + T stot(const std::string&) { + return T(); + } } #endif //UNITTESTBOT_STRINGUTIL_H diff --git a/server/src/visitors/AbstractValueViewVisitor.cpp b/server/src/visitors/AbstractValueViewVisitor.cpp index b91b7a6b..15183199 100644 --- a/server/src/visitors/AbstractValueViewVisitor.cpp +++ b/server/src/visitors/AbstractValueViewVisitor.cpp @@ -96,6 +96,9 @@ namespace visitor { inUnion = structInfo.subType == types::SubType::Union; for (int i = 0; i < structInfo.fields.size(); ++i) { auto const &field = structInfo.fields[i]; + if (field.isUnnamedBitfield()) { + continue; + } auto newName = PrinterUtils::getFieldAccess(name, field); auto const *newView = (subViews && i < subViews->size()) ? (*subViews)[i].get() : nullptr; auto newAccess = PrinterUtils::getFieldAccess(access, field); diff --git a/server/src/visitors/FunctionPointerForStubsVisitor.cpp b/server/src/visitors/FunctionPointerForStubsVisitor.cpp index bae82b5a..2d311f74 100644 --- a/server/src/visitors/FunctionPointerForStubsVisitor.cpp +++ b/server/src/visitors/FunctionPointerForStubsVisitor.cpp @@ -44,7 +44,8 @@ namespace visitor { } for (auto &field : structInfo.fields) { if (!types::TypesHandler::isPointerToFunction(field.type) && - !types::TypesHandler::isArrayOfPointersToFunction(field.type)) { + !types::TypesHandler::isArrayOfPointersToFunction(field.type) && + !field.isUnnamedBitfield()) { visitAny(field.type, name, nullptr, access, depth + 1); } } diff --git a/server/test/framework/Syntax_Tests.cpp b/server/test/framework/Syntax_Tests.cpp index 55c4d846..dab0f5cd 100644 --- a/server/test/framework/Syntax_Tests.cpp +++ b/server/test/framework/Syntax_Tests.cpp @@ -8,6 +8,9 @@ #include "coverage/CoverageAndResultsGenerator.h" #include "utils/path/FileSystemPath.h" +#include "utils/StringUtils.h" +#include "utils/SizeUtils.h" +#include "Tests.h" #include namespace { @@ -52,6 +55,7 @@ namespace { fs::path stubs_c = getTestFilePath("stubs.c"); fs::path namespace_cpp = getTestFilePath("namespace.cpp"); fs::path input_output_c = getTestFilePath("input_output.c"); + fs::path bitfields_c = getTestFilePath("bitfields.c"); void SetUp() override { clearEnv(CompilationUtils::CompilerName::CLANG); @@ -3051,4 +3055,115 @@ namespace { }) ); } + template + bool checkBitfieldFit(const std::shared_ptr &fieldView, size_t size) { + T val = StringUtils::stot(fieldView->getEntryValue(nullptr)); + T minVal, maxVal, one = 1; + if constexpr (std::is_signed_v) { + minVal = -(one << (size - 1)); + maxVal = -(minVal + 1); + } else { + minVal = 0; + maxVal = (one << size) - 1; + } + return val >= minVal && val <= maxVal; + } + + TEST_F(Syntax_Test, bitfields_check_simple_signed_str) { + auto [testGen, status] = createTestForFunction(bitfields_c, 26); + + ASSERT_TRUE(status.ok()) << status.error_message(); + + checkTestCasePredicates( + testGen.tests.at(bitfields_c).methods.begin().value().testCases, + std::vector( + { + [](const tests::Tests::MethodTestCase &testCase) { + auto &subViews = testCase.paramValues.front().view->getSubViews(); + return checkBitfieldFit(subViews[0], 24) && + checkBitfieldFit(subViews[1], 1) && + checkBitfieldFit(subViews[2], 2) && + checkBitfieldFit(subViews[3], 5) && + testCase.returnValue.view->getEntryValue(nullptr) == "1"; + }, + [](const tests::Tests::MethodTestCase &testCase) { + auto &subViews = testCase.paramValues.front().view->getSubViews(); + return checkBitfieldFit(subViews[0], 24) && + checkBitfieldFit(subViews[1], 1) && + checkBitfieldFit(subViews[2], 2) && + checkBitfieldFit(subViews[3], 5) && + testCase.returnValue.view->getEntryValue(nullptr) == "-1"; + }, + [](const tests::Tests::MethodTestCase &testCase) { + auto &subViews = testCase.paramValues.front().view->getSubViews(); + return checkBitfieldFit(subViews[0], 24) && + checkBitfieldFit(subViews[1], 1) && + checkBitfieldFit(subViews[2], 2) && + checkBitfieldFit(subViews[3], 5) && + testCase.returnValue.view->getEntryValue(nullptr) == "0"; + } + }) + ); + } + + TEST_F(Syntax_Test, bitfields_check_fields_bounds) { + auto [testGen, status] = createTestForFunction(bitfields_c, 106); + + ASSERT_TRUE(status.ok()) << status.error_message(); + + for (const auto &testCase: testGen.tests.at(bitfields_c).methods.begin().value().testCases) { + ASSERT_EQ(testCase.suiteName, tests::Tests::DEFAULT_SUITE_NAME); + } + + checkTestCasePredicates( + testGen.tests.at(bitfields_c).methods.begin().value().testCases, + std::vector( + { + [](const tests::Tests::MethodTestCase &testCase) { + auto &subViews = testCase.paramValues.front().view->getSubViews(); + return checkBitfieldFit(subViews[0], 7) && + checkBitfieldFit(subViews[1], + SizeUtils::bytesToBits( + sizeof(long long))) && + checkBitfieldFit(subViews[2], 17) && + checkBitfieldFit(subViews[3], 1) && + checkBitfieldFit(subViews[4], 22) && + testCase.returnValue.view->getEntryValue(nullptr) == "1"; + }, + [](const tests::Tests::MethodTestCase &testCase) { + auto &subViews = testCase.paramValues.front().view->getSubViews(); + return checkBitfieldFit(subViews[0], 7) && + checkBitfieldFit(subViews[1], + SizeUtils::bytesToBits( + sizeof(long long))) && + checkBitfieldFit(subViews[2], 17) && + checkBitfieldFit(subViews[3], 1) && + checkBitfieldFit(subViews[4], 22) && + testCase.returnValue.view->getEntryValue(nullptr) == "2"; + }, + [](const tests::Tests::MethodTestCase &testCase) { + auto &subViews = testCase.paramValues.front().view->getSubViews(); + return checkBitfieldFit(subViews[0], 7) && + checkBitfieldFit(subViews[1], + SizeUtils::bytesToBits( + sizeof(long long))) && + checkBitfieldFit(subViews[2], 17) && + checkBitfieldFit(subViews[3], 1) && + checkBitfieldFit(subViews[4], 22) && + testCase.returnValue.view->getEntryValue(nullptr) == "3"; + }, + [](const tests::Tests::MethodTestCase &testCase) { + auto &subViews = testCase.paramValues.front().view->getSubViews(); + return checkBitfieldFit(subViews[0], 7) && + checkBitfieldFit(subViews[1], + SizeUtils::bytesToBits( + sizeof(long long))) && + checkBitfieldFit(subViews[2], 17) && + checkBitfieldFit(subViews[3], 1) && + checkBitfieldFit(subViews[4], 22) && + testCase.returnValue.view->getEntryValue(nullptr) == "4"; + } + }) + ); + } } diff --git a/server/test/framework/Utils_Tests.cpp b/server/test/framework/Utils_Tests.cpp index b176a0f4..68a1a662 100644 --- a/server/test/framework/Utils_Tests.cpp +++ b/server/test/framework/Utils_Tests.cpp @@ -7,10 +7,395 @@ #include "utils/StringUtils.h" #include +#include +#include +#include +#include namespace { auto projectPath = fs::current_path().parent_path() / testUtils::getRelativeTestSuitePath("server"); + TEST(ReadBytesAsValueTest, Unsigned1) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 0; + bytes[0] ^= 1 << 5; + size_t offset = 3; + size_t len = 15; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "8196"); + } + + TEST(ReadBytesAsValueTest, Unsigned2) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 0; + bytes[0] ^= 1 << 5; + size_t offset = 3; + size_t len = 14; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "8196"); + } + + TEST(ReadBytesAsValueTest, Signed1) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 0; + bytes[0] ^= 1 << 5; + size_t offset = 3; + size_t len = 14; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "-8188"); + } + + TEST(ReadBytesAsValueTest, Signed2) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 0; + bytes[1] ^= 1 << 4; + bytes[0] ^= 1 << 5; + size_t offset = 3; + size_t len = 14; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "-7676"); + } + + TEST(ReadBytesAsValueTest, Signed3) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 0; + bytes[1] ^= 1 << 4; + bytes[0] ^= 1 << 5; + size_t offset = 0; + size_t len = 14; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "4128"); + } + + TEST(ReadBytesAsValueTest, Signed4) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 0; + bytes[1] ^= 1 << 4; + bytes[0] ^= 1 << 5; + size_t offset = 4; + size_t len = 3; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "2"); + } + + TEST(ReadBytesAsValueTest, Signed5) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 0; + bytes[1] ^= 1 << 4; + bytes[0] ^= 1 << 5; + size_t offset = 4; + size_t len = 2; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "-2"); + } + + TEST(ReadBytesAsValueTest, Signed6) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 0; + bytes[1] ^= 1 << 4; + bytes[0] ^= 1 << 5; + size_t offset = 4; + size_t len = 1; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "0"); + } + + TEST(ReadBytesAsValueTest, Signed7) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 0; + bytes[1] ^= 1 << 4; + bytes[0] ^= 1 << 5; + bytes[0] ^= 1 << 3; + size_t offset = 2; + size_t len = 2; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "-2"); + } + + TEST(ReadBytesAsValueTest, Signed8) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 0; + bytes[1] ^= 1 << 4; + bytes[0] ^= 1 << 5; + bytes[0] ^= 1 << 3; + size_t offset = 2; + size_t len = 6; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "10"); + } + + TEST(ReadBytesAsValueTest, Signed9) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 0; + bytes[1] ^= 1 << 4; + bytes[0] ^= 1 << 5; + bytes[0] ^= 1 << 3; + size_t offset = 1; + size_t len = 15; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "2068"); + } + + TEST(ReadBytesAsValueTest, Unsigned3) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 0; + bytes[1] ^= 1 << 4; + bytes[0] ^= 1 << 5; + bytes[0] ^= 1 << 3; + size_t offset = 1; + size_t len = 15; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "2068"); + } + + TEST(ReadBytesAsValueTest, Unsigned4) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 2; + bytes[2] ^= 1 << 0; + bytes[1] ^= 1 << 4; + bytes[0] ^= 1 << 5; + bytes[0] ^= 1 << 3; + size_t offset = 3; + size_t len = 15; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "8709"); + } + + TEST(ReadBytesAsValueTest, Unsigned5) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 2; + bytes[2] ^= 1 << 0; + bytes[1] ^= 1 << 4; + bytes[0] ^= 1 << 5; + bytes[0] ^= 1 << 3; + size_t offset = 3; + size_t len = 14; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "8709"); + } + + TEST(ReadBytesAsValueTest, Unsigned6) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 2; + bytes[2] ^= 1 << 0; + bytes[1] ^= 1 << 4; + bytes[0] ^= 1 << 5; + bytes[0] ^= 1 << 3; + size_t offset = 3; + size_t len = 16; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "41477"); + } + + TEST(ReadBytesAsValueTest, Unsigned7) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 2; + bytes[2] ^= 1 << 0; + bytes[1] ^= 1 << 4; + bytes[0] ^= 1 << 5; + bytes[0] ^= 1 << 3; + size_t offset = 3; + size_t len = 17; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "41477"); + } + + TEST(ReadBytesAsValueTest, Unsigned8) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 2; + bytes[2] ^= 1 << 0; + bytes[1] ^= 1 << 4; + bytes[0] ^= 1 << 5; + bytes[0] ^= 1 << 3; + size_t offset = 3; + size_t len = 26; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "41477"); + } + + TEST(ReadBytesAsValueTest, Unsigned9) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 2; + bytes[2] ^= 1 << 0; + bytes[1] ^= 1 << 4; + bytes[0] ^= 1 << 5; + bytes[0] ^= 1 << 3; + size_t offset = 15; + size_t len = 2; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "2"); + } + + TEST(ReadBytesAsValueTest, Bool1) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 2; + bytes[2] ^= 1 << 0; + bytes[1] ^= 1 << 4; + bytes[0] ^= 1 << 5; + bytes[0] ^= 1 << 3; + size_t offset = 15; + size_t len = 1; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "0"); + } + + TEST(ReadBytesAsValueTest, Bool2) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 2; + bytes[2] ^= 1 << 0; + bytes[1] ^= 1 << 4; + bytes[0] ^= 1 << 5; + bytes[0] ^= 1 << 3; + size_t offset = 16; + size_t len = 1; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "1"); + } + + TEST(ReadBytesAsValueTest, Bool3) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[2] ^= 1 << 2; + bytes[2] ^= 1 << 0; + bytes[1] ^= 1 << 4; + bytes[0] ^= 1 << 5; + bytes[0] ^= 1 << 3; + size_t offset = 12; + size_t len = 1; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "1"); + } + + TEST(ReadBytesAsValueTest, ByteOffsetInt) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[3] = 1; + bytes[2] = bytes[1] = bytes[0] = -1; + size_t offset = 0; + size_t len = 24; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "-1"); + } + + TEST(ReadBytesAsValueTest, ByteOffsetInt2) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[3] = 1; + bytes[2] = bytes[1] = bytes[0] = -1; + size_t offset = 8; + size_t len = 24; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "131071"); + } + + TEST(ReadBytesAsValueTest, ByteOffsetUnsignedInt) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[3] = 1; + bytes[2] = bytes[1] = bytes[0] = -1; + size_t offset = 0; + size_t len = 24; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "16777215"); + } + + TEST(ReadBytesAsValueTest, ByteOffsetUnsignedInt2) { + size_t const LEN = 4; + std::vector bytes(LEN); + bytes[3] = 1; + bytes[2] = bytes[1] = bytes[0] = -1; + size_t offset = 8; + size_t len = 24; + EXPECT_EQ(tests::readBytesAsValue(bytes, offset, len), "131071"); + } + + template + void readBytesAsValueTestTemplate(T val) { + srand(42); + for (size_t tcount = 0; tcount < 5; ++tcount) { + auto add = static_cast(rand() % 10); + auto start = static_cast(rand() % add); + size_t const len = sizeof(T); + std::vector bytes(len + add); + for (size_t i = 1; i <= len; ++i) { + bytes[start + i - 1] = (val >> (CHAR_BIT * (i - 1))) & ((1 << CHAR_BIT) - 1); + } + EXPECT_EQ(tests::readBytesAsValue(bytes, start * CHAR_BIT, len * CHAR_BIT), std::to_string(val)); + } + } + + TEST(ReadBytesAsValueTest, CommonInt) { + readBytesAsValueTestTemplate(13); + readBytesAsValueTestTemplate(26); + readBytesAsValueTestTemplate(42); + readBytesAsValueTestTemplate(0); + readBytesAsValueTestTemplate(1); + readBytesAsValueTestTemplate(-13); + readBytesAsValueTestTemplate(-26); + readBytesAsValueTestTemplate(-42); + readBytesAsValueTestTemplate(-1); + readBytesAsValueTestTemplate(std::numeric_limits::max()); + readBytesAsValueTestTemplate(std::numeric_limits::min()); + } + + TEST(ReadBytesAsValueTest, CommonUnsignedInt) { + readBytesAsValueTestTemplate(13); + readBytesAsValueTestTemplate(26); + readBytesAsValueTestTemplate(42); + readBytesAsValueTestTemplate(0); + readBytesAsValueTestTemplate(1); + readBytesAsValueTestTemplate(std::numeric_limits::min()); + readBytesAsValueTestTemplate(std::numeric_limits::max()); + } + + TEST(ReadBytesAsValueTest, CommonChar) { + readBytesAsValueTestTemplate(13); + readBytesAsValueTestTemplate(26); + readBytesAsValueTestTemplate(42); + readBytesAsValueTestTemplate(0); + readBytesAsValueTestTemplate(1); + readBytesAsValueTestTemplate(-13); + readBytesAsValueTestTemplate(-26); + readBytesAsValueTestTemplate(-42); + readBytesAsValueTestTemplate(-1); + readBytesAsValueTestTemplate(std::numeric_limits::min()); + readBytesAsValueTestTemplate(std::numeric_limits::max()); + } + + TEST(ReadBytesAsValueTest, CommonUnsignedChar) { + readBytesAsValueTestTemplate(13); + readBytesAsValueTestTemplate(26); + readBytesAsValueTestTemplate(42); + readBytesAsValueTestTemplate(0); + readBytesAsValueTestTemplate(1); + readBytesAsValueTestTemplate(std::numeric_limits::min()); + readBytesAsValueTestTemplate(std::numeric_limits::max()); + } + + TEST(ReadBytesAsValueTest, CommonShort) { + readBytesAsValueTestTemplate(13); + readBytesAsValueTestTemplate(26); + readBytesAsValueTestTemplate(42); + readBytesAsValueTestTemplate(0); + readBytesAsValueTestTemplate(1); + readBytesAsValueTestTemplate(-13); + readBytesAsValueTestTemplate(-26); + readBytesAsValueTestTemplate(-42); + readBytesAsValueTestTemplate(-1); + readBytesAsValueTestTemplate(std::numeric_limits::min()); + readBytesAsValueTestTemplate(std::numeric_limits::max()); + } + + TEST(ReadBytesAsValueTest, CommonUnsignedShort) { + readBytesAsValueTestTemplate(13); + readBytesAsValueTestTemplate(26); + readBytesAsValueTestTemplate(42); + readBytesAsValueTestTemplate(0); + readBytesAsValueTestTemplate(1); + readBytesAsValueTestTemplate(-13); + readBytesAsValueTestTemplate(-26); + readBytesAsValueTestTemplate(-42); + readBytesAsValueTestTemplate(-1); + readBytesAsValueTestTemplate(std::numeric_limits::min()); + readBytesAsValueTestTemplate(std::numeric_limits::max()); + } + TEST(Utils_Test, Split) { std::string s = "a,b,c,d,"; std::vector vs = StringUtils::split(s, ','); diff --git a/server/test/suites/syntax/CMakeLists.txt b/server/test/suites/syntax/CMakeLists.txt index 885a3e82..d9f03df7 100644 --- a/server/test/suites/syntax/CMakeLists.txt +++ b/server/test/suites/syntax/CMakeLists.txt @@ -38,4 +38,5 @@ add_executable(syntax1 array_sort.c stubs.c namespace.cpp - input_output.c) + input_output.c + bitfields.c) diff --git a/server/test/suites/syntax/bitfields.c b/server/test/suites/syntax/bitfields.c new file mode 100644 index 00000000..30da29fa --- /dev/null +++ b/server/test/suites/syntax/bitfields.c @@ -0,0 +1,148 @@ +#include "bitfields.h" +#include +#include +#include +#include +#include + +static int ALIGN = -30; + +#define print_sizeof(S) printf("size of %*s : %zu bytes\n", ALIGN, #S, sizeof((S){})) + +void print_sizeof_structs() { + print_sizeof(SimpleSignedStr); + print_sizeof(SimpleUnsignedStr); + print_sizeof(ImplementationDefinedStr); + print_sizeof(PossiblySmallStr); + print_sizeof(SimpleUnsignedUnion); + print_sizeof(ComplexStr); + print_sizeof(StrWithBool); + print_sizeof(StrWithUnnamedBitfields); + print_sizeof(StrWithUnnamedZeroBitfield); + print_sizeof(StrWithBreak); +} + +int check_simple_signed_str(SimpleSignedStr s) { + if (s.a == 1024 && s.b == -1 && s.d == -16) { + return 1; + } else if (s.b == 0) { + return -1; + } + return 0; +} + +int is_sum_greater_10(SimpleUnsignedStr s) { + return (s.a + s.b + s.c + s.d > 10 && s.d > 5); +} + +int is_all_greater_2(PossiblySmallStr s) { + if (s.a > 2 && s.b > 2) { + return 1; + } + return 0; +} + +int union_greater_2(SimpleUnsignedUnion u) { + if (u.c > 2) { + return 1; + } + return 0; +} + +int union_greater_2_short(SimpleUnsignedUnion u) { + return u.c > 2; +} + +int sum_of_fields(ComplexStr s) { + if (!s.a) { + return -1; + } + return s.a + s.b + s.c + s.d + s.e; +} + +int decode_from_bool(StrWithBool s) { + if (!s.a && !s.b && !s.c) { + return 0; + } else if (s.a && !s.b && !s.c) { + return 1; + } else if (!s.a && s.b && !s.c) { + return 2; + } else if (s.a && s.b && !s.c) { + return 3; + } else if (!s.a && !s.b && s.c) { + return 4; + } else if (s.a && !s.b && s.c) { + return 5; + } else if (!s.a && s.b && s.c) { + return 6; + } + return 7; +} + +int decode_from_bool_simple(StrWithBool s) { + if (!s.a && !s.b && !s.c) { + return 0; + } else if (s.a && !s.b && s.c) { + return 5; + } + return 7; +} + + StrWithUnnamedBitfields mult_by_two(StrWithUnnamedBitfields s) { + s.b1 *= 2; + s.b2 *= 2; + s.b3 *= 2; + return s; + } + + int is_nice(StrWithUnnamedZeroBitfield s) { + if (s.b1 == 69 && s.b2 == 42 && s.b3 == 1488) { + return 13; + } + return 0; + } + +int check_fields_bounds(StrWithBreak s) { + assert(s.b1 >= 0 && s.b1 <= 127); + assert(s.breaking >= LLONG_MIN && s.breaking <= LLONG_MAX); + assert(s.b2 >= -65536 && s.b2 <= 65535); + assert(s.b3 == true || s.b3 == false); + assert(s.b4 >= -2097152 && s.b4 <= 2097151); + if (s.b1 >= 123 && s.b3) { + return 1; + } else if (s.b1 >= 123 && s.b4 < 0) { + return 2; + } else if (s.breaking > 42) { + return 3; + } + return 4; +} + +void simple_modify(SimpleSignedStr* s) { + s->a++; + s->b = ~s->b; + if (s->c >= 0) { + s->c *= 2; + } + s->d /= 2; +} + +SimpleSignedStr* create_on_heap(int a, int b, int c, int d) { + SimpleSignedStr* s = malloc(sizeof(SimpleSignedStr)); + if (s) { + s->a = s->b = s->c = s->d = -1; + if (a >= -8388608 && a <= 8388607) { + s->a = a; + } + if (b >= -1 && b <= 0) { + s->b = b; + } + if (c >= -2 && c <= 1) { + s->c = c; + } + if (d >= -16 && d <= 15) { + s->d = d; + } + } + return s; +} diff --git a/server/test/suites/syntax/bitfields.h b/server/test/suites/syntax/bitfields.h new file mode 100644 index 00000000..07e87342 --- /dev/null +++ b/server/test/suites/syntax/bitfields.h @@ -0,0 +1,103 @@ +#ifndef C_EXAMPLE_BITFIELDS_H +#define C_EXAMPLE_BITFIELDS_H + +#include + +typedef struct { + signed a : 24; + signed b : 1; + signed int c : 2; + signed int d : 5; +} SimpleSignedStr; + +typedef struct { + unsigned a : 2; + unsigned b : 5; + unsigned int c : 1; + unsigned int d : 24; +} SimpleUnsignedStr; + +typedef struct { + int a : 24; + int b : 2; + int c : 1; + int d : 5; +} ImplementationDefinedStr; + +typedef struct { + signed a : 3; + signed b : 3; +} PossiblySmallStr; + +typedef union { + unsigned a : 2; + unsigned b : 1; + unsigned c : 6; + unsigned d : 23; +} SimpleUnsignedUnion; + +typedef struct { + // will **usually** occupy 8 bytes: + bool a : 1; + unsigned b : 2; + signed c : 1; + unsigned d : 3; + // 25 bits: unused + int e; +} ComplexStr; + +typedef struct { + // will **usually** occupy 1 byte + bool a : 1, b : 1, c : 1; +} StrWithBool; + +// struct InvalidNumberOfBits { // should produce an error in C +// bool a : 2; +// unsigned b : 50; +// signed c : 1; +// unsigned d : 3; +// }; + +typedef struct { + // will **usually** occupy 4 bytes: + // 5 bits: value of b1 + // 11 bits: unused -- explicitly specified padding + // 6 bits: value of b2 + // 2 bits: value of b3 + // 3 bits: unused -- explicitly specified padding + // 5 bits: unused -- implicitly + unsigned b1 : 5, : 11, b2 : 6, b3 : 2; + signed : 3; +} StrWithUnnamedBitfields; + +typedef struct { + // will **usually** occupy 8 bytes: + // 7 bits: value of b1 + // 25 bits: unused + // 6 bits: value of b2 + // 15 bits: value of b3 + // 11 bits: unused + unsigned b1 : 7; + unsigned : 0; // start a new allocation unit + unsigned b2 : 6; + unsigned b3 : 15; +} StrWithUnnamedZeroBitfield; + +typedef struct { + // will **usually** occupy 24 bytes: + // 7 bits: value of b1 + // 57 bits: unused + // 64 bits: value of breaking + // 17 bits: value of b2 + // 1 bit: value of b3 + // 22 bits: value of b4 + // 24 bits: unused + unsigned b1 : 7; // from 0 to 127 + long long breaking; // from LLONG_MIN to LLONG_MAX + signed b2 : 17; // from -65536 to 65535 + bool b3 : 1; // from 0 to 1 + int b4 : 22; // usually from -2097152 to 2097151 +} StrWithBreak; + + +#endif //C_EXAMPLE_BITFIELDS_H