// // This file is distributed under the MIT License. See LICENSE.md for details. // #include #define BOOST_TEST_MODULE ModelType bool init_unit_test(); #include "boost/test/unit_test.hpp" #include "revng/Model/ABI.h" #include "revng/Model/Binary.h" #include "revng/Model/Types.h" using namespace model; using llvm::cast; using llvm::Twine; using model::PrimitiveTypeKind::Signed; using model::PrimitiveTypeKind::Void; static TupleTree serializeDeserialize(const TupleTree &T) { std::string Buffer; T.serialize(Buffer); llvm::dbgs() << "Serialized\n" << Buffer; auto Deserialized = TupleTree::deserialize(Buffer); std::string OtherBuffer; Deserialized->serialize(OtherBuffer); llvm::dbgs() << "Deserialized\n" << OtherBuffer; return std::move(Deserialized.get()); } static bool checkSerialization(const TupleTree &T) { revng_check(T->verify(true)); auto Deserialized = serializeDeserialize(T); revng_check(Deserialized->verify(true)); return T->Types == Deserialized->Types; } BOOST_AUTO_TEST_CASE(PrimitiveTypes) { revng_check(PrimitiveType(PrimitiveTypeKind::Void, 0).verify(true)); revng_check(PrimitiveType(PrimitiveTypeKind::Unsigned, 1).verify(true)); revng_check(PrimitiveType(PrimitiveTypeKind::Unsigned, 2).verify(true)); revng_check(PrimitiveType(PrimitiveTypeKind::Unsigned, 4).verify(true)); revng_check(PrimitiveType(PrimitiveTypeKind::Unsigned, 8).verify(true)); revng_check(PrimitiveType(PrimitiveTypeKind::Unsigned, 16).verify(true)); revng_check(PrimitiveType(PrimitiveTypeKind::Signed, 1).verify(true)); revng_check(PrimitiveType(PrimitiveTypeKind::Signed, 2).verify(true)); revng_check(PrimitiveType(PrimitiveTypeKind::Signed, 4).verify(true)); revng_check(PrimitiveType(PrimitiveTypeKind::Signed, 8).verify(true)); revng_check(PrimitiveType(PrimitiveTypeKind::Signed, 16).verify(true)); revng_check(PrimitiveType(PrimitiveTypeKind::Float, 2).verify(true)); revng_check(PrimitiveType(PrimitiveTypeKind::Float, 4).verify(true)); revng_check(PrimitiveType(PrimitiveTypeKind::Float, 8).verify(true)); revng_check(PrimitiveType(PrimitiveTypeKind::Float, 10).verify(true)); revng_check(PrimitiveType(PrimitiveTypeKind::Float, 16).verify(true)); auto Unsigned = PrimitiveType(PrimitiveTypeKind::Unsigned, 1); auto Signed = PrimitiveType(PrimitiveTypeKind::Signed, 1); for (uint8_t ByteSize = 0; ByteSize < 20; ++ByteSize) { using namespace std::string_literals; Unsigned = PrimitiveType(PrimitiveTypeKind::Unsigned, ByteSize); Signed = PrimitiveType(PrimitiveTypeKind::Signed, ByteSize); if (std::has_single_bit(ByteSize)) { revng_check(Signed.verify(true)); revng_check(Unsigned.verify(true)); auto ExpectedName = ("uint" + Twine(8 * ByteSize) + "_t").str(); revng_check(Unsigned.name() == ExpectedName); ExpectedName = ("int" + Twine(8 * ByteSize) + "_t").str(); revng_check(Signed.name() == ExpectedName); } else { revng_check(not Signed.verify(false)); revng_check(not Unsigned.verify(false)); } } auto Float = PrimitiveType(PrimitiveTypeKind::Float, 2); for (uint8_t ByteSize = 0; ByteSize < 20; ++ByteSize) { using namespace std::string_literals; Float = PrimitiveType(PrimitiveTypeKind::Float, ByteSize); if (ByteSize == 2 or ByteSize == 4 or ByteSize == 8 or ByteSize == 10 or ByteSize == 12 or ByteSize == 16) { revng_check(Float.verify(true)); revng_check(Float.name() == ("float" + Twine(8 * ByteSize) + "_t").str()); } else { revng_check(not Float.verify(false)); } } } BOOST_AUTO_TEST_CASE(EnumTypes) { revng_check(not EnumType().verify(false)); TupleTree T; auto Int32 = T->getPrimitiveType(Signed, 4); TypePath EnumPath = T->recordNewType(makeType()); auto *Enum = cast(EnumPath.get()); revng_check(T->Types.size() == 2); // The enum does not verify if we don't define a valid underlying type and // at least one enum entry auto Int32QT = model::QualifiedType(Int32, {}); Enum->UnderlyingType = Int32QT; revng_check(not Enum->verify(false)); revng_check(not T->verify(false)); // With a valid underlying type and at least one entry we're good, but we // have to initialize all the cross references in the tree. EnumEntry Entry = EnumEntry{ 0 }; Entry.CustomName = "value0"; revng_check(Entry.verify(true)); revng_check(Enum->Entries.insert(Entry).second); revng_check(Enum->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // We cannot insert other entries with the same value, but we can insert new // entries with different values. revng_check(Enum->Entries.size() == 1); revng_check(not Enum->Entries.insert(EnumEntry{ 0 }).second); revng_check(Enum->Entries.size() == 1); revng_check(Enum->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); revng_check(Enum->verify(true)); revng_check(T->verify(true)); revng_check(Enum->Entries.insert(EnumEntry{ 1 }).second); revng_check(Enum->Entries.size() == 2); revng_check(Enum->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // Inserting two entries with the same name succceds but it's bad. EnumEntry Entry1{ 5 }; Entry1.CustomName = "some_value"; revng_check(Enum->Entries.insert(Entry1).second); revng_check(Enum->Entries.size() == 3); revng_check(Enum->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); EnumEntry Entry2{ 7 }; Entry2.CustomName = "some_value"; revng_check(Enum->Entries.insert(Entry2).second); revng_check(Enum->Entries.size() == 4); revng_check(not Enum->verify(false)); revng_check(not T->verify(false)); // But if we remove the dupicated entry we're good again revng_check(Enum->Entries.erase(7)); revng_check(Enum->Entries.size() == 3); revng_check(Enum->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // But if we break the underlying, making it point to a type that does not // exist, we're not good anymore auto BrokenPath = TypePath::fromString(T.get(), "/Types/TypedefType-42"); Enum->UnderlyingType = { BrokenPath, {} }; revng_check(not Enum->verify(false)); revng_check(not T->verify(false)); // Also we set the underlying type to a valid type, but that is not a // primitive integer type, we are not good auto PathToNonInt = T->getTypePath(Enum); Enum->UnderlyingType = { PathToNonInt, {} }; revng_check(not Enum->verify(false)); revng_check(not T->verify(false)); // If we put back the proper underlying type it verifies. Enum->UnderlyingType = Int32QT; revng_check(Enum->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // But if we clear the entries it does not verify anymore Enum->Entries.clear(); revng_check(not Enum->verify(false)); revng_check(not T->verify(false)); } BOOST_AUTO_TEST_CASE(TypedefTypes) { TupleTree T; auto Int32 = T->getPrimitiveType(Signed, 4); // Insert the typedef TypePath TypedefPath = T->recordNewType(makeType()); auto *Typedef = cast(TypedefPath.get()); revng_check(T->Types.size() == 2); // The pid_t typedef refers to the int32_t Typedef->UnderlyingType = { Int32, {} }; revng_check(Typedef->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // Adding qualifiers the typedef still verifies Typedef->UnderlyingType.Qualifiers.push_back(Qualifier::createConst()); revng_check(Typedef->verify(true)); revng_check(T->verify(true)); Typedef->UnderlyingType.Qualifiers.push_back(Qualifier::createArray(42)); revng_check(Typedef->verify(true)); revng_check(T->verify(true)); Typedef->UnderlyingType.Qualifiers.push_back(Qualifier::createPointer(8)); revng_check(Typedef->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // Removing qualifiers, the typedef still verifies Typedef->UnderlyingType.Qualifiers.clear(); revng_check(Typedef->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // If the underlying type is the type itself something is broken Typedef->UnderlyingType.UnqualifiedType = T->getTypePath(Typedef); revng_check(not Typedef->verify(false)); revng_check(not T->verify(false)); } BOOST_AUTO_TEST_CASE(StructTypes) { revng_check(not StructType().verify(false)); TupleTree T; auto Int32 = T->getPrimitiveType(Signed, 4); auto VoidT = T->getPrimitiveType(Void, 0); // Insert the struct TypePath StructPath = T->recordNewType(makeType()); auto *Struct = cast(StructPath.get()); revng_check(T->Types.size() == 3); // Let's make it large, so that we can play around with fields. Struct->Size = 1024; // Insert field in the struct StructField Field0 = StructField{ 0 }; Field0.Type = { Int32, {} }; revng_check(Struct->Fields.insert(Field0).second); revng_check(Struct->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // Adding a new field is valid StructField Field1 = StructField{ 4 }; Field1.Type = { Int32, {} }; revng_check(Struct->Fields.insert(Field1).second); revng_check(Struct->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // Inserting fails if the index is already present StructField Field1Bis = StructField{ 4 }; Field1Bis.Type = { Int32, {} }; revng_check(not Struct->Fields.insert(Field1Bis).second); revng_check(Struct->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // Assigning succeeds if even if an index is already present StructField Field1Ter = StructField{ 4 }; Field1Ter.Type = { Int32, {} }; Field1Ter.CustomName = "fld1ter"; revng_check(not Struct->Fields.insert_or_assign(Field1Ter).second); revng_check(Struct->verify(true)); revng_check(Struct->Fields.at(4).CustomName == "fld1ter"); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // Adding a new field whose position is not consecutive to others builds a // struct that is valid StructField AnotherField = StructField{ 128 }; AnotherField.Type = { Int32, {} }; revng_check(Struct->Fields.insert(AnotherField).second); revng_check(Struct->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // Adding a new field that overlaps with another is not valid StructField Overlap = StructField{ 129 }; Overlap.Type = { Int32, {} }; revng_check(Struct->Fields.insert(Overlap).second); revng_check(not Struct->verify(false)); revng_check(not T->verify(false)); // Removing the overlapping field fixes the struct revng_check(Struct->Fields.erase(129)); revng_check(Struct->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // Erasing a field that's not there fails revng_check(not Struct->Fields.erase(129)); revng_check(Struct->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // Shrinking the size does not break the struct Struct->Size = 132; revng_check(Struct->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); for (int I = 0; I < 132; ++I) { // But shrinking too much breaks it again Struct->Size = I; revng_check(not Struct->verify(false)); revng_check(not T->verify(false)); } // Fixing the size fixes the struct Struct->Size = 132; revng_check(Struct->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // Struct without fields are valid as long as their size is not zero Struct->Fields.clear(); revng_check(Struct->verify(false)); revng_check(T->verify(false)); Struct->Size = 0; revng_check(not Struct->verify(false)); revng_check(not T->verify(false)); // Put the size back to a large value for the other tests. Struct->Size = 100; revng_check(Struct->verify(false)); revng_check(T->verify(false)); // Struct x cannot have a field with type x Struct->Fields.clear(); StructField Same = StructField{ 0 }; Same.Type = { T->getTypePath(Struct), {} }; revng_check(Struct->Fields.insert(Same).second); revng_check(not Struct->verify(false)); revng_check(not T->verify(false)); // Adding a void field is not valid Struct->Fields.clear(); StructField VoidField = StructField{ 0 }; VoidField.Type = { VoidT, {} }; revng_check(Struct->Fields.insert(VoidField).second); revng_check(not Struct->verify(false)); revng_check(not T->verify(false)); } BOOST_AUTO_TEST_CASE(UnionTypes) { revng_check(not UnionType().verify(false)); TupleTree T; auto Int32 = T->getPrimitiveType(Signed, 4); auto Int64 = T->getPrimitiveType(Signed, 8); auto VoidT = T->getPrimitiveType(Void, 0); // Insert the union TypePath UnionPath = T->recordNewType(makeType()); auto *Union = cast(UnionPath.get()); revng_check(T->Types.size() == 4); // Insert field in the struct UnionField Field0(0); Field0.Type = { Int32, {} }; revng_check(Union->Fields.insert(Field0).second); revng_check(Union->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // Adding a new field is valid { UnionField Field1(1); Field1.Type = { Int64, {} }; Field1.CustomName = "fld1"; const auto [It, New] = Union->Fields.insert(std::move(Field1)); revng_check(New); } revng_check(Union->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); { // Assigning another field in a different position with a duplicated name // succeeds, but verification fails. UnionField Field1(2); Field1.Type = { Int32, {} }; Field1.CustomName = "fld1"; const auto [It, New] = Union->Fields.insert(std::move(Field1)); revng_check(New); revng_check(Union->Fields.at(It->Index).CustomName == "fld1"); revng_check(not Union->verify(false)); revng_check(not T->verify(false)); // But removing goes back to good again revng_check(Union->Fields.erase(It->Index)); revng_check(Union->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); } // Union without fields are invalid Union->Fields.clear(); revng_check(not Union->verify(false)); revng_check(not T->verify(false)); // Union x cannot have a field with type x Union->Fields.clear(); UnionField Same; Same.Type = { T->getTypePath(Union), {} }; revng_check(Union->Fields.insert(Same).second); revng_check(not Union->verify(false)); revng_check(not T->verify(false)); // Adding a void field is not valid Union->Fields.clear(); UnionField VoidField; VoidField.Type = { VoidT, {} }; revng_check(Union->Fields.insert(VoidField).second); revng_check(not Union->verify(false)); revng_check(not T->verify(false)); } BOOST_AUTO_TEST_CASE(CABIFunctionTypes) { TupleTree T; auto Int32 = T->getPrimitiveType(Signed, 4); auto VoidT = T->getPrimitiveType(Void, 0); // Create a C-like function type TypePath FunctionPath = T->recordNewType(makeType()); auto *FunctionType = cast(FunctionPath.get()); FunctionType->ABI = model::ABI::SystemV_x86_64; revng_check(T->Types.size() == 3); revng_check(not FunctionType->size().has_value()); // Insert argument in the function type Argument Arg0{ 0 }; Arg0.Type = { Int32, {} }; const auto &[InsertedArgIt, New] = FunctionType->Arguments.insert(Arg0); revng_check(InsertedArgIt != FunctionType->Arguments.end()); revng_check(New); // Verification fails due to missing return type revng_check(not FunctionType->verify(false)); revng_check(not T->verify(false)); QualifiedType RetTy{ Int32, {} }; FunctionType->ReturnType = RetTy; revng_check(FunctionType->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // Adding a new field is valid, and we can have a function type with an // argument of the same type of itself. Argument Arg1{ 1 }; Arg1.Type = { Int32, {} }; revng_check(FunctionType->Arguments.insert(Arg1).second); revng_check(FunctionType->verify(true)); revng_check(checkSerialization(T)); // Inserting an ArgumentType in a position that is already taken fails Argument Arg1Bis{ 1 }; Arg1Bis.Type = { Int32, {} }; revng_check(not FunctionType->Arguments.insert(Arg1Bis).second); revng_check(FunctionType->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // Assigning an ArgumentType in a position that is already taken succeeds revng_check(not FunctionType->Arguments.insert_or_assign(Arg1Bis).second); revng_check(FunctionType->verify(true)); auto &ArgT = FunctionType->Arguments.at(1); revng_check(ArgT.Type.UnqualifiedType == Int32); revng_check(T->verify(true)); revng_check(checkSerialization(T)); // FunctionType without argument are valid FunctionType->Arguments.clear(); revng_check(FunctionType->verify(true)); revng_check(T->verify(true)); revng_check(checkSerialization(T)); } BOOST_AUTO_TEST_CASE(RawFunctionTypes) { TupleTree T; auto Primitive64 = T->getPrimitiveType(model::PrimitiveTypeKind::Generic, 4); QualifiedType Generic64 = { Primitive64, {} }; auto RAFPointer = makeType(); auto *RAF = cast(RAFPointer.get()); revng_check(RAF->verify(true)); // // Test non-scalar argument // { model::TypedRegister RAXArgument(model::Register::rax_x86_64); RAXArgument.Type = { Primitive64, { { QualifierKind::Array, 10 } } }; revng_check(not RAXArgument.verify(false)); } // // Add two arguments // { model::NamedTypedRegister RDIArgument(model::Register::rdi_x86_64); RDIArgument.Type = Generic64; revng_check(RDIArgument.verify(true)); RAF->Arguments.insert(RDIArgument); revng_check(RAF->verify(true)); model::NamedTypedRegister RSIArgument(model::Register::rsi_x86_64); RSIArgument.Type = Generic64; RSIArgument.CustomName = "Second"; revng_check(RSIArgument.verify(true)); RAF->Arguments.insert(RSIArgument); revng_check(RAF->verify(true)); } // Add a return value { model::TypedRegister RAXReturnValue(model::Register::rax_x86_64); RAXReturnValue.Type = Generic64; revng_check(RAXReturnValue.verify(true)); RAF->ReturnValues.insert(RAXReturnValue); revng_check(RAF->verify(true)); } } BOOST_AUTO_TEST_CASE(QualifiedTypes) { TupleTree T; auto Void = T->getPrimitiveType(model::PrimitiveTypeKind::Void, 0); auto Generic64 = T->getPrimitiveType(model::PrimitiveTypeKind::Generic, 8); revng_check(Void.get()->verify(true)); revng_check(not Void.get()->size().has_value()); revng_check(Generic64.get()->verify(true)); revng_check(*Generic64.get()->size() == 8); QualifiedType VoidPointer = { Void, { { model::QualifierKind::Pointer, 4 } } }; revng_check(VoidPointer.verify(true)); model::Qualifier Pointer64Qualifier{ model::QualifierKind::Pointer, 8 }; QualifiedType Generic64Pointer = { Void, { Pointer64Qualifier } }; revng_check(Generic64Pointer.verify(true)); QualifiedType DoublePointer = { Void, { Pointer64Qualifier, Pointer64Qualifier } }; revng_check(DoublePointer.verify(true)); QualifiedType WeirdSizedPointer = { Void, { { model::QualifierKind::Pointer, 7 } } }; revng_check(not WeirdSizedPointer.verify(false)); model::Qualifier ConstQualifier{ model::QualifierKind::Const, 0 }; QualifiedType ConstVoid = { Void, { ConstQualifier } }; revng_check(ConstVoid.verify(true)); QualifiedType ConstConstVoid = { Void, { ConstQualifier, ConstQualifier } }; revng_check(not ConstConstVoid.verify(false)); QualifiedType ConstPointerConstVoid = { Void, { ConstQualifier, Pointer64Qualifier, ConstQualifier } }; revng_check(ConstPointerConstVoid.verify(true)); model::Qualifier TenElementsArray{ model::QualifierKind::Array, 10 }; QualifiedType VoidArray = { Void, { TenElementsArray } }; revng_check(not VoidArray.verify(false)); QualifiedType VoidPointerArray = { Void, { TenElementsArray, Pointer64Qualifier } }; revng_check(VoidPointerArray.verify(true)); model::Qualifier ZeroElementsArray{ model::QualifierKind::Array, 0 }; QualifiedType ZeroSizedVoidArray = { Void, { ZeroElementsArray } }; revng_check(not ZeroSizedVoidArray.verify(false)); }