Skip to content

UTBotCpp-253 Better structure fields initialization in generated tests #253 #263

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions server/src/Tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,9 +294,11 @@ std::shared_ptr<StructValueView> KTestObjectParser::structView(const std::vector
const MapAddressName &fromAddressToName,
std::vector<InitReference> &initReferences) {
std::vector<std::shared_ptr<AbstractValueView>> subViews;
std::vector<std::string> fields;
unsigned int curPos = offset;

for (const auto &field: curStruct.fields) {
fields.push_back(field.name);
size_t len = typesHandler.typeSize(field.type);
unsigned int offsetField = field.offset;
types::EnumInfo innerEnum;
Expand Down Expand Up @@ -373,9 +375,9 @@ std::shared_ptr<StructValueView> KTestObjectParser::structView(const std::vector
if(curStruct.hasUnnamedFields) {
auto bytesType = types::Type::createSimpleTypeFromName("utbot_byte");
const std::shared_ptr<AbstractValueView> rawDataView = arrayView(byteArray, bytesType, curStruct.size, offset, usage);
entryValue = PrinterUtils::convertBytesToUnion(curStruct.name, rawDataView->getEntryValue());
entryValue = PrinterUtils::convertBytesToUnion(curStruct.name, rawDataView->getEntryValue(nullptr));
}
return std::make_shared<StructValueView>(subViews, entryValue);
return std::make_shared<StructValueView>(curStruct.isCLike, fields, subViews, entryValue);
}

std::string KTestObjectParser::primitiveCharView(const types::Type &type, std::string value) {
Expand Down Expand Up @@ -595,7 +597,7 @@ void KTestObjectParser::assignTypeUnnamedVar(Tests::MethodTestCase &testCase,
{ name, byteValue }, typeAndVarName,
PointerUsage::LAZY, testCase.lazyAddressToName,
testCase.lazyReferences, methodDescription);
LOG_S(MAX) << "Fetch lazy object: " << name << " = " << testParamView->getEntryValue();
LOG_S(MAX) << "Fetch lazy object: " << name << " = " << testParamView->getEntryValue(nullptr);
curType.paramValue.lazyParams.emplace_back(paramType, name, std::nullopt);
curType.paramValue.lazyValues.emplace_back(name, std::nullopt, testParamView);
}
Expand Down Expand Up @@ -712,13 +714,13 @@ void KTestObjectParser::parseTestCases(const UTBotKTestList &cases,
std::swap(testCase.lazyReferences, testCaseDescription.lazyReferences);
if (filterByLineFlag) {
auto view = testCaseDescription.kleePathFlagSymbolicValue.view;
if (!view || view->getEntryValue() != "1") {
if (!view || view->getEntryValue(nullptr) != "1") {
continue;
}
}
auto const &predicateInfo = lineInfo ? lineInfo->predicateInfo : std::nullopt;
if (predicateInfo.has_value() &&
!predicateMatch(testCaseDescription.returnValue.view->getEntryValue(), predicateInfo.value())) {
!predicateMatch(testCaseDescription.returnValue.view->getEntryValue(nullptr), predicateInfo.value())) {
continue;
}

Expand All @@ -731,10 +733,10 @@ void KTestObjectParser::parseTestCases(const UTBotKTestList &cases,

if (methodDescription.returnType.isObjectPointer() && !methodDescription.returnType.maybeArray
&& testCaseDescription.functionReturnNotNullValue.view &&
testCaseDescription.functionReturnNotNullValue.view->getEntryValue() == "0") {
testCaseDescription.functionReturnNotNullValue.view->getEntryValue(nullptr) == "0") {
testCase.returnValue.view = std::make_shared<PrimitiveValueView>(PrinterUtils::C_NULL);
}
traceStream << "\treturn: " << testCase.returnValue.view->getEntryValue();
traceStream << "\treturn: " << testCase.returnValue.view->getEntryValue(nullptr);
LOG_S(MAX) << traceStream.str();

assignTypeUnnamedVar(testCase, methodDescription);
Expand Down Expand Up @@ -906,7 +908,7 @@ void KTestObjectParser::processGlobalParamPreValue(Tests::TestCaseDescription &t
void KTestObjectParser::processSymbolicStdin(Tests::TestCaseDescription &testCaseDescription, std::vector<RawKleeParam> &rawKleeParams) {
auto &&read = getKleeParamOrThrow(rawKleeParams, "stdin-read");
std::string &&view = testParameterView(read, {types::Type::longlongType(), "stdin-read"}, types::PointerUsage::PARAMETER,
testCaseDescription.lazyAddressToName, testCaseDescription.lazyReferences)->getEntryValue();
testCaseDescription.lazyAddressToName, testCaseDescription.lazyReferences)->getEntryValue(nullptr);
if (view == "0LL") {
return;
} else {
Expand Down Expand Up @@ -1132,7 +1134,7 @@ UnionValueView::UnionValueView(
const std::shared_ptr<AbstractValueView> &rawDataView,
std::vector<std::shared_ptr<AbstractValueView>, std::allocator<std::shared_ptr<AbstractValueView>>> subViews)
: AbstractValueView(std::move(subViews)),
entryValue(PrinterUtils::convertBytesToUnion(typeName, rawDataView->getEntryValue())) {
entryValue(PrinterUtils::convertBytesToUnion(typeName, rawDataView->getEntryValue(nullptr))) {
}

TestMethod::TestMethod(std::string methodName, fs::path bitcodeFile, fs::path sourceFilename)
Expand Down
78 changes: 44 additions & 34 deletions server/src/Tests.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@
#include <queue>
#include <optional>

namespace tests {
class StructValueView;
}

namespace printer {
class TestsPrinter;
struct MultiLinePrinter {
static std::string print(TestsPrinter *printer, const tests::StructValueView *view);
};
}

namespace tests {

const std::string LAZYNAME = "unnamed";
Expand Down Expand Up @@ -85,7 +96,7 @@ namespace tests {
/**
* Returns string representation of the value.
*/
[[nodiscard]] virtual std::string getEntryValue() const = 0;
[[nodiscard]] virtual std::string getEntryValue(printer::TestsPrinter *printer) const = 0;

virtual bool containsFPSpecialValue() {
return false;
Expand All @@ -98,12 +109,6 @@ namespace tests {
return this->subViews;
};

/**
* For StructValueView should return vector of string representations of its field values.
*/
virtual std::vector<std::string> fieldEntryValues() {
return {};
}

protected:
explicit AbstractValueView(std::vector<std::shared_ptr<AbstractValueView>> subViews) : subViews(std::move(subViews)) {}
Expand All @@ -117,7 +122,7 @@ namespace tests {
struct JustValueView : AbstractValueView {
explicit JustValueView(std::string value) : AbstractValueView(), entryValue(std::move(value)) {}

[[nodiscard]] std::string getEntryValue() const override {
[[nodiscard]] std::string getEntryValue(printer::TestsPrinter *printer) const override {
return entryValue;
}

Expand All @@ -135,7 +140,7 @@ namespace tests {
struct VoidValueView : AbstractValueView {
explicit VoidValueView() = default;

[[nodiscard]] std::string getEntryValue() const override {
[[nodiscard]] std::string getEntryValue(printer::TestsPrinter *printer) const override {
return "";
}

Expand Down Expand Up @@ -182,10 +187,10 @@ namespace tests {
explicit ArrayValueView(std::vector<std::shared_ptr<AbstractValueView>> &subViews)
: AbstractValueView(subViews) {}

[[nodiscard]] std::string getEntryValue() const override {
[[nodiscard]] std::string getEntryValue(printer::TestsPrinter *printer) const override {
std::vector<std::string> entries;
for (const auto &subView : subViews) {
entries.push_back(subView->getEntryValue());
entries.push_back(subView->getEntryValue(printer));
}

return "{" + StringUtils::joinWith(entries, ", ") + "}";
Expand All @@ -203,42 +208,36 @@ namespace tests {

/**
* Representation of struct value. It's value is stored as a string. Subviews of StructValueView are its fields.
* In order to get fields and subfields values (leaves in terms of trees) method fieldEntryValues().
*/
struct StructValueView : AbstractValueView {
explicit StructValueView(std::vector<std::shared_ptr<AbstractValueView>> subViews,
explicit StructValueView(bool _isCLike,
std::vector<std::string> _fields,
std::vector<std::shared_ptr<AbstractValueView>> subViews,
std::optional<std::string> entryValue)
: AbstractValueView(std::move(subViews)), entryValue(std::move(entryValue)) {
: AbstractValueView(std::move(subViews)), entryValue(std::move(entryValue)),
isCLike(_isCLike),
fields(std::move(_fields)){
}

[[nodiscard]] const std::vector<std::shared_ptr<AbstractValueView>> &getSubViews() const override {
return this->subViews;
}

[[nodiscard]] std::string getEntryValue() const override {
[[nodiscard]] std::string getEntryValue(printer::TestsPrinter *printer) const override {
if (entryValue.has_value()) {
return entryValue.value();
}

std::vector<std::string> entries;
for (const auto &subView : subViews) {
entries.push_back(subView->getEntryValue());
if (printer != nullptr) {
return printer::MultiLinePrinter::print(printer, this);
}

return "{" + StringUtils::joinWith(entries, ", ") + "}";
}

std::vector<std::string> fieldEntryValues() override {
std::vector<std::string> result;
std::vector<std::string> entries;
for (const auto &subView : subViews) {
std::vector<std::string> subFieldEntryValues = subView->fieldEntryValues();
CollectionUtils::extend(result, subFieldEntryValues);
if (subFieldEntryValues.empty()) {
result.push_back(subView->getEntryValue());
}
entries.push_back(subView->getEntryValue(nullptr));
}

return result;
return "{" + StringUtils::joinWith(entries, ", ") + "}";
}

bool containsFPSpecialValue() override {
Expand All @@ -249,7 +248,22 @@ namespace tests {
}
return false;
}

[[nodiscard]] std::string getFieldPrefix(int i) const {
std::string prefix = "." + fields[i] + " = ";
if (isCLike) {
return prefix;
}
// it is not C Struct-initialization, but C++ List-initialization.
// The `designation` isn't allowed.
// https://en.cppreference.com/w/c/language/struct_initialization
// https://en.cppreference.com/w/cpp/language/list_initialization
return "/*" + prefix + "*/";
}

private:
bool isCLike;
std::vector<std::string> fields;
std::optional<std::string> entryValue;
};

Expand All @@ -263,18 +277,14 @@ namespace tests {
std::vector<std::shared_ptr<AbstractValueView>,
std::allocator<std::shared_ptr<AbstractValueView>>> subViews);

[[nodiscard]] std::string getEntryValue() const override {
[[nodiscard]] std::string getEntryValue(printer::TestsPrinter *printer) const override {
return entryValue;
}

bool containsFPSpecialValue() override {
return false;
}

std::vector<std::string> fieldEntryValues() override {
return { getEntryValue() };
}

private:
std::string entryValue;
};
Expand Down
39 changes: 30 additions & 9 deletions server/src/printers/TestsPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ void TestsPrinter::printLazyVariables(const std::vector<Tests::MethodParam> &laz
const std::vector<Tests::TestCaseParamValue> &lazyValues) {
for (size_t i = 0; i < lazyParams.size(); ++i) {
printLazyVariables(lazyValues[i].lazyParams, lazyValues[i].lazyValues);
strDeclareVar(lazyParams[i].type.baseType(), lazyValues[i].name, lazyValues[i].view->getEntryValue(),
strDeclareVar(lazyParams[i].type.baseType(), lazyValues[i].name, lazyValues[i].view->getEntryValue(this),
std::nullopt, true, lazyParams[i].type.getDimension());
}
}
Expand All @@ -210,7 +210,7 @@ void TestsPrinter::printStubVariables(const Tests::MethodDescription &methodDesc
types::Type stubType = testCase.stubParamTypes[i].type;
std::string bufferSuffix = "_buffer";
std::string buffer = stub.name + bufferSuffix;
strDeclareArrayVar(stubType, buffer, types::PointerUsage::PARAMETER, stub.view->getEntryValue());
strDeclareArrayVar(stubType, buffer, types::PointerUsage::PARAMETER, stub.view->getEntryValue(this));
strMemcpy(stub.name, buffer, false);
}
}
Expand Down Expand Up @@ -396,7 +396,7 @@ void TestsPrinter::verboseOutputVariable(const Tests::MethodDescription &methodD
types::TypesHandler::isArrayOfPointersToFunction(methodDescription.returnType)) {
strComment("No output variable check for function returning pointer to function");
} else if (methodDescription.returnType.isObjectPointer() &&
testCase.returnValue.view->getEntryValue() == PrinterUtils::C_NULL) {
testCase.returnValue.view->getEntryValue(nullptr) == PrinterUtils::C_NULL) {
strComment("No output variable check for function returning null");
} else {
visitor::VerboseParameterVisitor(typesHandler, this, true, types::PointerUsage::RETURN)
Expand All @@ -415,7 +415,7 @@ void TestsPrinter::verboseFunctionCall(const Tests::MethodDescription &methodDes
std::string methodCall = constrVisitorFunctionCall(methodDescription, testCase, true);
if (!types::TypesHandler::skipTypeInReturn(methodDescription.returnType) && !testCase.isError()) {
size_t returnPointersCount = 0;
if (testCase.returnValue.view->getEntryValue() == PrinterUtils::C_NULL) {
if (testCase.returnValue.view->getEntryValue(nullptr) == PrinterUtils::C_NULL) {
returnPointersCount = methodDescription.returnType.countReturnPointers(true);
}
auto type = Printer::getConstQualifier(expectedType) + expectedType.usedType();
Expand Down Expand Up @@ -520,17 +520,17 @@ void TestsPrinter::parametrizedArrayParameters(const Tests::MethodDescription &m
methodDescription.getClassTypeName(), methodDescription.name, param.name);
strDeclareArrayOfFunctionPointerVar(type, param.name, stubName);
} else if (types::TypesHandler::isCStringType(param.type)) {
strDeclareArrayVar(param.type, param.name, types::PointerUsage::PARAMETER, value.view->getEntryValue(), param.alignment);
strDeclareArrayVar(param.type, param.name, types::PointerUsage::PARAMETER, value.view->getEntryValue(this), param.alignment);
} else if (param.type.isObjectPointer() || param.type.isArray()) {
auto arrayType = types::TypesHandler::isVoid(param.type.baseTypeObj())
? types::Type::minimalScalarPointerType(param.type.arraysSizes(types::PointerUsage::PARAMETER).size())
: param.type;
if (param.type.maybeJustPointer()) {
strDeclareVar(arrayType.baseType(), param.name, value.view->getEntryValue(), param.alignment);
strDeclareVar(arrayType.baseType(), param.name, value.view->getEntryValue(this), param.alignment);
} else {
auto paramName = param.type.isTwoDimensionalPointer() ? param.underscoredName() : param.name;
strDeclareArrayVar(arrayType, paramName, types::PointerUsage::PARAMETER,
value.view->getEntryValue(), param.alignment, true);
value.view->getEntryValue(this), param.alignment, true);
}
}
if (param.type.isTwoDimensionalPointer()) {
Expand Down Expand Up @@ -569,7 +569,7 @@ std::vector<std::string> TestsPrinter::methodParametersListParametrized(const Te
} else if (!testCase.paramValues[i].lazyValues.empty()) {
args.push_back(param.name);
} else {
args.push_back(testCase.paramValues[i].view->getEntryValue());
args.push_back(testCase.paramValues[i].view->getEntryValue(this));
}
}
return args;
Expand Down Expand Up @@ -607,7 +607,7 @@ std::string TestsPrinter::constrVisitorFunctionCall(const Tests::MethodDescripti
}
auto classObjName = methodDescription.getClassName();
size_t returnPointersCount = 0;
if (testCase.returnValue.view && testCase.returnValue.view->getEntryValue() != PrinterUtils::C_NULL) {
if (testCase.returnValue.view && testCase.returnValue.view->getEntryValue(nullptr) != PrinterUtils::C_NULL) {
returnPointersCount = methodDescription.returnType.countReturnPointers(true);
}
return constrFunctionCall(methodDescription.name, methodArgs, "", classObjName, false, returnPointersCount,
Expand All @@ -632,6 +632,27 @@ void printer::TestsPrinter::parametrizedInitializeSymbolicStubs(const Tests::Met
}
}

std::string printer::MultiLinePrinter::print(TestsPrinter *printer,
const tests::StructValueView *view) {
auto subViews = view->getSubViews();
std::stringstream structuredValuesWithPrefixes;
structuredValuesWithPrefixes << "{" << NL;
++printer->tabsDepth;
int i = 0;
for (const auto &sview : subViews) {
if (i != 0) {
structuredValuesWithPrefixes << "," << NL;
}
structuredValuesWithPrefixes << printer->TAB_N() << view->getFieldPrefix(i)
<< sview->getEntryValue(printer);
++i;
}
--printer->tabsDepth;
structuredValuesWithPrefixes << "}";

return structuredValuesWithPrefixes.str();
}

Tests::MethodParam printer::TestsPrinter::getValueParam(const Tests::MethodParam &param) {
if (param.type.isTwoDimensionalPointer()) {
return { param.type, param.underscoredName(), param.alignment };
Expand Down
2 changes: 1 addition & 1 deletion server/src/printers/TestsPrinter.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ namespace printer {
const Tests::MethodTestCase &testCase,
const std::optional<LineInfo::PredicateInfo>& predicateInfo);

static std::vector<std::string>
std::vector<std::string>
methodParametersListParametrized(const tests::Tests::MethodDescription &methodDescription,
const Tests::MethodTestCase &testCase);

Expand Down
1 change: 1 addition & 0 deletions server/src/types/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ namespace types {

FPointerMap functionFields{};
bool hasUnnamedFields;
bool isCLike;
};

struct UnionInfo: TypeInfo {
Expand Down
7 changes: 7 additions & 0 deletions server/src/types/TypesResolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ void TypesResolver::resolveStruct(const clang::RecordDecl *D, const std::string
structInfo.filePath = Paths::getCCJsonFileFullPath(filename, parent->buildRootPath);
structInfo.name = getFullname(D, canonicalType, id, sourceFilePath);
structInfo.hasUnnamedFields = false;
if (Paths::getSourceLanguage(sourceFilePath) == utbot::Language::CXX) {
const clang::CXXRecordDecl *cppD = dynamic_cast<const clang::CXXRecordDecl *>(D);
structInfo.isCLike = cppD != nullptr && cppD->isCLike();
}
else {
structInfo.isCLike = true;
}

if (Paths::isGtest(structInfo.filePath)) {
return;
Expand Down
Loading