diff --git a/include/revng/ADT/UpcastablePointer.h b/include/revng/ADT/UpcastablePointer.h index 33f9ebe72..ac739ddd4 100644 --- a/include/revng/ADT/UpcastablePointer.h +++ b/include/revng/ADT/UpcastablePointer.h @@ -54,6 +54,9 @@ concept UpcastablePointerLike = PointerLike and Upcastable>; template concept NotVoid = not std::is_void_v; +template +concept DerivesFrom = std::is_base_of_v; + template ReturnT upcast(P &Upcastable, const L &Callable, const ReturnT &IfNull) { using pointee = std::remove_reference_t; @@ -127,6 +130,12 @@ public: Pointer(P, deleter) {} explicit UpcastablePointer(pointer P) noexcept : Pointer(P, deleter) {} +public: + template Q, typename... Args> + static UpcastablePointer make(Args &&...TheArgs) { + return UpcastablePointer(new Q(std::forward(TheArgs)...)); + } + public: UpcastablePointer &operator=(const UpcastablePointer &Other) { if (&Other != this) { diff --git a/include/revng/Model/Binary.h b/include/revng/Model/Binary.h index f07b04f24..d08ff5c98 100644 --- a/include/revng/Model/Binary.h +++ b/include/revng/Model/Binary.h @@ -13,17 +13,18 @@ #include "revng/ADT/UpcastablePointer/YAMLTraits.h" #include "revng/Model/Register.h" #include "revng/Model/TupleTree.h" +#include "revng/Model/Type.h" #include "revng/Support/MetaAddress.h" #include "revng/Support/MetaAddress/YAMLTraits.h" #include "revng/Support/YAMLTraits.h" // Forward declarations namespace model { +class VerifyHelper; class Function; class Binary; class FunctionEdge; class CallEdge; -class FunctionABIRegister; class BasicBlock; } // namespace model @@ -34,40 +35,6 @@ template<> struct KeyedObjectTraits : public IdentityKeyedObjectTraits {}; -class model::FunctionABIRegister { -public: - Register::Values Register; - RegisterState::Values Argument = RegisterState::Invalid; - RegisterState::Values ReturnValue = RegisterState::Invalid; - -public: - FunctionABIRegister(const Register::Values &Register) : Register(Register) {} - bool operator==(const FunctionABIRegister &Other) const = default; - -public: - bool verify() const debug_function { - return (Register != Register::Invalid and Argument != RegisterState::Invalid - and ReturnValue != RegisterState::Invalid); - } -}; -INTROSPECTION_NS(model, FunctionABIRegister, Register, Argument, ReturnValue); - -template<> -struct llvm::yaml::MappingTraits - : public TupleLikeMappingTraits {}; - -template<> -struct KeyedObjectTraits { - static model::Register::Values key(const model::FunctionABIRegister &Obj) { - return Obj.Register; - } - - static model::FunctionABIRegister - fromKey(const model::Register::Values &Register) { - return model::FunctionABIRegister(Register); - } -}; - // // FunctionEdgeType // @@ -193,12 +160,12 @@ public: public: /// Edge target. If invalid, it's an indirect edge + // TODO: switch to TupleTreeReference MetaAddress Destination; - FunctionEdgeType::Values Type; + FunctionEdgeType::Values Type = FunctionEdgeType::Invalid; public: - FunctionEdge() : - Destination(MetaAddress::invalid()), Type(FunctionEdgeType::Invalid) {} + FunctionEdge() : Destination(MetaAddress::invalid()) {} FunctionEdge(MetaAddress Destination, FunctionEdgeType::Values Type) : Destination(Destination), Type(Type) {} @@ -218,6 +185,8 @@ public: } bool verify() const debug_function; + bool verify(bool Assert) const debug_function; + bool verify(VerifyHelper &VH) const; }; INTROSPECTION_NS(model, FunctionEdge, Destination, Type); @@ -226,7 +195,7 @@ public: using Key = std::pair; public: - SortedVector Registers; + TypePath Prototype; public: CallEdge() : @@ -244,8 +213,10 @@ public: public: bool verify() const debug_function; + bool verify(bool Assert) const debug_function; + bool verify(VerifyHelper &VH) const; }; -INTROSPECTION_NS(model, CallEdge, Destination, Type, Registers); +INTROSPECTION_NS(model, CallEdge, Destination, Type, Prototype); template<> struct concrete_types_traits { @@ -307,17 +278,17 @@ enum Values { NoReturn, ///< A noreturn function Fake ///< A fake function }; -} +} // namespace model::FunctionType namespace llvm::yaml { template<> struct ScalarEnumerationTraits { - static void enumeration(IO &io, model::FunctionType::Values &V) { + static void enumeration(IO &IO, model::FunctionType::Values &V) { using namespace model::FunctionType; - io.enumCase(V, "Invalid", Invalid); - io.enumCase(V, "Regular", Regular); - io.enumCase(V, "NoReturn", NoReturn); - io.enumCase(V, "Fake", Fake); + IO.enumCase(V, "Invalid", Invalid); + IO.enumCase(V, "Regular", Regular); + IO.enumCase(V, "NoReturn", NoReturn); + IO.enumCase(V, "Fake", Fake); } }; } // namespace llvm::yaml @@ -326,18 +297,29 @@ class model::BasicBlock { public: MetaAddress Start; MetaAddress End; - std::string Name; + Identifier CustomName; SortedVector> Successors; public: + BasicBlock() : Start(MetaAddress::invalid()) {} BasicBlock(const MetaAddress &Start) : Start(Start) {} + bool operator==(const model::BasicBlock &Other) const = default; + +public: + Identifier name() const; + +public: + bool verify() const debug_function; + bool verify(bool Assert) const debug_function; + bool verify(VerifyHelper &VH) const; }; -INTROSPECTION_NS(model, BasicBlock, Start, End, Name, Successors); +INTROSPECTION_NS(model, BasicBlock, Start, End, CustomName, Successors); template<> struct llvm::yaml::MappingTraits - : public TupleLikeMappingTraits {}; + : public TupleLikeMappingTraits::CustomName> {}; template<> struct KeyedObjectTraits { @@ -354,24 +336,32 @@ struct KeyedObjectTraits { class model::Function { public: MetaAddress Entry; - std::string Name; - FunctionType::Values Type; + Identifier CustomName; + FunctionType::Values Type = FunctionType::Invalid; SortedVector CFG; - SortedVector Registers; + TypePath Prototype; public: Function(const MetaAddress &Entry) : Entry(Entry) {} bool operator==(const model::Function &Other) const = default; +public: + Identifier name() const; + public: bool verify() const debug_function; + bool verify(bool Assert) const debug_function; + bool verify(VerifyHelper &VH) const; + +public: void dumpCFG() const debug_function; }; -INTROSPECTION_NS(model, Function, Entry, Name, Type, CFG, Registers) +INTROSPECTION_NS(model, Function, Entry, CustomName, Type, CFG, Prototype) template<> struct llvm::yaml::MappingTraits - : public TupleLikeMappingTraits {}; + : public TupleLikeMappingTraits::CustomName> {}; template<> struct KeyedObjectTraits { @@ -381,19 +371,35 @@ struct KeyedObjectTraits { }; }; -static_assert(IsKeyedObjectContainer>); - // // Binary // class model::Binary { public: - MutableSet Functions; + SortedVector Functions; + SortedVector> Types; + +public: + model::TypePath getTypePath(const model::Type *T) { + return TypePath::fromString(this, + "/Types/" + getNameFromYAMLScalar(T->key())); + } + + TypePath recordNewType(UpcastablePointer &&T); + + model::TypePath + getPrimitiveType(PrimitiveTypeKind::Values V, uint8_t ByteSize); + + bool verifyTypes() const debug_function; + bool verifyTypes(bool Assert) const debug_function; + bool verifyTypes(VerifyHelper &VH) const; public: bool verify() const debug_function; + bool verify(bool Assert) const debug_function; + bool verify(VerifyHelper &VH) const; }; -INTROSPECTION_NS(model, Binary, Functions) +INTROSPECTION_NS(model, Binary, Functions, Types) template<> struct llvm::yaml::MappingTraits diff --git a/include/revng/Model/TupleTree.h b/include/revng/Model/TupleTree.h index 8fb98fc48..4dee35307 100644 --- a/include/revng/Model/TupleTree.h +++ b/include/revng/Model/TupleTree.h @@ -1021,14 +1021,15 @@ public: TupleTreePath Path; public: - static TupleTreeReference fromPath(const TupleTreePath &Path) { + static TupleTreeReference fromPath(RootT *Root, const TupleTreePath &Path) { TupleTreeReference Result; + Result.Root = Root; Result.Path = Path; return Result; } - static TupleTreeReference fromString(llvm::StringRef Path) { - return fromPath(*stringAsPath(Path)); + static TupleTreeReference fromString(RootT *Root, llvm::StringRef Path) { + return fromPath(Root, *stringAsPath(Path)); } bool operator==(const TupleTreeReference &Other) const { @@ -1041,10 +1042,27 @@ public: const TupleTreePath &path() const { return Path; } - T *get() const { + T *get() { revng_check(Root != nullptr); + + if (Path.size() == 0) + return nullptr; + return getByPath(Path, *Root); } + + const T *get() const { + revng_check(Root != nullptr); + + if (Path.size() == 0) + return nullptr; + + return getByPath(Path, *Root); + } + + bool isValid() const { + return (*this != TupleTreeReference() and get() != nullptr); + } }; template @@ -1058,7 +1076,9 @@ struct llvm::yaml::ScalarTraits { } static llvm::StringRef input(llvm::StringRef Path, void *, T &Obj) { - Obj = T::fromString(Path); + // We temporarily initialize Root to nullptr, a post-processing phase will + // take care of fixup these + Obj = T::fromString(nullptr, Path); return {}; } diff --git a/include/revng/Model/Type.h b/include/revng/Model/Type.h new file mode 100644 index 000000000..e79311b05 --- /dev/null +++ b/include/revng/Model/Type.h @@ -0,0 +1,938 @@ +#pragma once + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include +#include +#include +#include + +#include "revng/ADT/KeyedObjectTraits.h" +#include "revng/ADT/RecursiveCoroutine.h" +#include "revng/ADT/SortedVector.h" +#include "revng/ADT/UpcastablePointer.h" +#include "revng/ADT/UpcastablePointer/YAMLTraits.h" +#include "revng/Model/Register.h" +#include "revng/Model/TupleTree.h" +#include "revng/Support/Assert.h" +#include "revng/Support/Debug.h" +#include "revng/Support/YAMLTraits.h" + +namespace model { +class VerifyHelper; +class Binary; +class Type; +class PrimitiveType; +class EnumType; +class TypedefType; +class StructType; +class UnionType; +class CABIFunctionType; +class RawFunctionType; +class TypedRegister; +class NamedTypedRegister; + +class Qualifier; +class QualifiedType; + +class EnumEntry; +class AggregateField; +class StructField; +class UnionField; +class Argument; +class ABILocation; + +} // end namespace model + +template +using Fields = typename TupleLikeTraits::Fields; + +namespace model { + +/// \note Zero-sized identifiers are valid +class Identifier : public llvm::SmallString<16> { +public: + using llvm::SmallString<16>::SmallString; + using llvm::SmallString<16>::operator=; + +public: + static const Identifier Empty; + +public: + static Identifier fromString(llvm::StringRef Name) { + revng_assert(Name.size() != 0); + Identifier Result(Name); + + if (std::isdigit(Result[0])) + Result.insert(Result.begin(), '_'); + + for (char &C : Result) + if (not std::isalnum(C)) + C = '_'; + + return Result; + } + +public: + bool verify() const debug_function; + bool verify(bool Assert) const debug_function; + bool verify(VerifyHelper &VH) const; +}; + +} // namespace model + +/// \brief KeyedObjectTraits for std::string based on its value +template<> +struct KeyedObjectTraits + : public IdentityKeyedObjectTraits {}; + +template<> +struct llvm::yaml::ScalarTraits { + static void + output(const model::Identifier &Value, void *, llvm::raw_ostream &Output) { + Output << Value; + } + + static StringRef + input(llvm::StringRef Scalar, void *, model::Identifier &Value) { + Value = model::Identifier(Scalar); + return StringRef(); + } + + static QuotingType mustQuote(StringRef) { return QuotingType::Double; } +}; + +namespace model::TypeKind { + +/// \brief Enum for identifying different kind of model types. +enum Values { + Invalid, + Primitive, + Enum, + Typedef, + Struct, + Union, + CABIFunctionType, + RawFunctionType, +}; + +} // end namespace model::TypeKind + +// Make model::TypeKind yaml-serializable, required for making model::Type +// yaml-serializable as well +template<> +struct llvm::yaml::ScalarEnumerationTraits { + template + static void enumeration(IOType &IO, model::TypeKind::Values &Val) { + IO.enumCase(Val, "Invalid", model::TypeKind::Invalid); + IO.enumCase(Val, "Primitive", model::TypeKind::Primitive); + IO.enumCase(Val, "Enum", model::TypeKind::Enum); + IO.enumCase(Val, "Typedef", model::TypeKind::Typedef); + IO.enumCase(Val, "Struct", model::TypeKind::Struct); + IO.enumCase(Val, "Union", model::TypeKind::Union); + IO.enumCase(Val, "CABIFunctionType", model::TypeKind::CABIFunctionType); + IO.enumCase(Val, "RawFunctionType", model::TypeKind::RawFunctionType); + } +}; + +namespace model::TypeKind { + +inline llvm::StringRef getName(model::TypeKind::Values V) { + return getNameFromYAMLEnumScalar(V); +} + +inline model::TypeKind::Values fromName(llvm::StringRef Name) { + return getValueFromYAMLScalar(Name); +} + +} // end namespace model::TypeKind + +namespace model::QualifierKind { + +/// \brief Enum for identifying different kinds of qualifiers. +// +// Notice that we are choosing to represent pointers and arrays as qualifiers. +// The idea is that a qualifier is something that you can add to a type T to +// obtain another type R, in such a way that if T is fully known also R is fully +// known. In this sense Pointer and Array types are qualified types. +enum Values { Invalid, Pointer, Array, Const }; + +} // end namespace model::QualifierKind + +// Make model::QualifierKind::Values yaml-serializable, required for making +// model::Qualifier yaml-serializable as well +template<> +struct llvm::yaml::ScalarEnumerationTraits { + template + static void enumeration(IOType &IO, model::QualifierKind::Values &Val) { + IO.enumCase(Val, "Invalid", model::QualifierKind::Invalid); + IO.enumCase(Val, "Pointer", model::QualifierKind::Pointer); + IO.enumCase(Val, "Array", model::QualifierKind::Array); + IO.enumCase(Val, "Const", model::QualifierKind::Const); + } +}; + +// Make model::Type derived types usable with UpcastablePointer +template<> +struct concrete_types_traits { + using type = std::tuple; +}; + +template<> +struct concrete_types_traits { + using type = std::tuple; +}; + +/// \brief Concept to identify all types that are derived from model::Type +template +concept IsModelType = std::is_base_of_v; + +/// \brief Base class of model types used for LLVM-style RTTI +class model::Type { +public: + using Key = std::pair; + +public: + TypeKind::Values Kind = TypeKind::Invalid; + uint64_t ID = 0; + +public: + static bool classof(const Type *T) { return false; } + + Key key() const { return Key{ Kind, ID }; } + + Identifier name() const; + +protected: + Type(TypeKind::Values Kind, uint64_t ID) : Kind(Kind), ID(ID) {} + Type(TypeKind::Values Kind); + +public: + bool operator==(const Type &Other) const { return key() == Other.key(); } + + bool operator<(const Type &Other) const { return key() < Other.key(); } + +public: + std::optional size() const debug_function; + RecursiveCoroutine> size(VerifyHelper &VH) const; + +public: + bool verify() const debug_function; + bool verify(bool Assert) const debug_function; + RecursiveCoroutine verify(VerifyHelper &VH) const; + +protected: + bool verifyBase(VerifyHelper &VH) const; +}; + +INTROSPECTION_NS(model, Type, Kind, ID); + +template<> +struct llvm::yaml::MappingTraits + : public TupleLikeMappingTraits {}; + +static_assert(Yamlizable); + +namespace model { + +using UpcastableType = UpcastablePointer; + +template +inline model::UpcastableType +makeTypeWithID(model::TypeKind::Values Kind, uint64_t ID); + +} // end namespace model + +template<> +struct llvm::yaml::ScalarTraits + : CompositeScalar {}; + +// Make UpcastablePointers to model types usable in KeyedObject containers +template<> +struct KeyedObjectTraits { + static model::Type::Key key(const model::UpcastableType &Val) { + return Val->key(); + } + static model::UpcastableType fromKey(const model::Type::Key &K) { + return model::makeTypeWithID(K.first, K.second); + } +}; + +/// \brief Make UpcastableType yaml-serializable polymorphically +template<> +struct llvm::yaml::MappingTraits + : public PolymorphicMappingTraits {}; + +/// \brief A qualifier for a model::Type +class model::Qualifier { +public: + QualifierKind::Values Kind = QualifierKind::Invalid; + /// Size: size in bytes for Pointer, number of elements for Array, 0 otherwise + uint64_t Size = 0; + +public: + // Kind is not Invalid, Pointer and Const have no Size, Array has Size. + bool verify() const debug_function; + bool verify(bool Assert) const debug_function; + bool verify(VerifyHelper &VH) const; + +public: + static Qualifier createConst() { return { QualifierKind::Const, 0 }; } + + static Qualifier createPointer(uint64_t Size) { + return { QualifierKind::Pointer, Size }; + } + + static Qualifier createArray(uint64_t S) { + return { QualifierKind::Array, S }; + } + +public: + bool isConstQualifier() const { + revng_assert(verify(true)); + return Kind == QualifierKind::Const; + } + + bool isArrayQualifier() const { + revng_assert(verify(true)); + return Kind == QualifierKind::Array; + } + + bool isPointerQualifier() const { + revng_assert(verify(true)); + return Kind == QualifierKind::Pointer; + } + + bool operator==(const Qualifier &) const = default; +}; +INTROSPECTION_NS(model, Qualifier, Kind, Size); + +/// \brief Make Qualifier yaml-serializable +template<> +struct llvm::yaml::MappingTraits + : public TupleLikeMappingTraits::Size> {}; + +// Make std::vector yaml-serializable as a sequence +LLVM_YAML_IS_SEQUENCE_VECTOR(model::Qualifier) + +namespace model { + +using TypePath = TupleTreeReference; + +} // end namespace model + +/// \brief A qualified version of a model::Type. Can have many nested qualifiers +class model::QualifiedType { +public: + TypePath UnqualifiedType; + std::vector Qualifiers = {}; + +public: + bool operator==(const model::QualifiedType &Other) const = default; + +public: + std::optional size() const debug_function; + RecursiveCoroutine> size(VerifyHelper &VH) const; + +public: + bool verify() const debug_function; + bool verify(bool Assert) const debug_function; + RecursiveCoroutine verify(VerifyHelper &VH) const; +}; +INTROSPECTION_NS(model, QualifiedType, UnqualifiedType, Qualifiers); + +/// \brief Make QualifiedType yaml-serializable +template<> +struct llvm::yaml::MappingTraits + : public TupleLikeMappingTraits::Qualifiers> {}; + +namespace model::PrimitiveTypeKind { + +// WARNING: these end up in type IDs, changing these means breaks the file +// format +enum Values { + Invalid, + Void, + Generic, + PointerOrNumber, + Number, + Unsigned, + Signed, + Float +}; + +} // end namespace model::PrimitiveTypeKind + +// Make model::PrimitiveTypeKind::Values yaml-serializable +template<> +struct llvm::yaml::ScalarEnumerationTraits { + template + static void enumeration(IOType &IO, model::PrimitiveTypeKind::Values &Val) { + IO.enumCase(Val, "Invalid", model::PrimitiveTypeKind::Invalid); + IO.enumCase(Val, "Void", model::PrimitiveTypeKind::Void); + IO.enumCase(Val, "Generic", model::PrimitiveTypeKind::Generic); + IO.enumCase(Val, + "PointerOrNumber", + model::PrimitiveTypeKind::PointerOrNumber); + IO.enumCase(Val, "Number", model::PrimitiveTypeKind::Number); + IO.enumCase(Val, "Unsigned", model::PrimitiveTypeKind::Unsigned); + IO.enumCase(Val, "Signed", model::PrimitiveTypeKind::Signed); + IO.enumCase(Val, "Float", model::PrimitiveTypeKind::Float); + } +}; + +namespace model::PrimitiveTypeKind { + +inline llvm::StringRef getName(model::PrimitiveTypeKind::Values V) { + return getNameFromYAMLEnumScalar(V); +} + +inline model::PrimitiveTypeKind::Values fromName(const llvm::Twine &Name) { + return getValueFromYAMLScalar(Name.str()); +} + +} // end namespace model::PrimitiveTypeKind + +/// \brief A primitive type in model: sized integers, booleans, floats and void. +class model::PrimitiveType : public model::Type { +public: + static constexpr const char *Tag = "!Primitive"; + static constexpr const TypeKind::Values AssociatedKind = TypeKind::Primitive; + +public: + PrimitiveTypeKind::Values PrimitiveKind = model::PrimitiveTypeKind::Invalid; + /// Size in bytes + uint8_t Size = 0; + +public: + PrimitiveType(PrimitiveTypeKind::Values PrimitiveKind, uint8_t ByteSize); + PrimitiveType(uint64_t ID); + PrimitiveType() : PrimitiveType(model::PrimitiveTypeKind::Void) {} + +public: + Identifier name() const; + +public: + static bool classof(const Type *T) { return T->Kind == TypeKind::Primitive; } + bool operator==(const PrimitiveType &Other) const = default; +}; +INTROSPECTION_NS(model, PrimitiveType, Kind, ID, PrimitiveKind, Size); + +template<> +struct llvm::yaml::MappingTraits + : public TupleLikeMappingTraits {}; + +/// \brief An entry in a model enum, with a name and a value. +class model::EnumEntry { +public: + uint64_t Value; + Identifier CustomName; + SortedVector Aliases; + +public: + EnumEntry(uint64_t Value) : Value(Value) {} + EnumEntry() : EnumEntry(0) {} + +public: + bool operator==(const EnumEntry &) const = default; + + // The entry should have a non-empty name, the name should not be a valid + // alias, and there should not be empty aliases. + bool verify() const debug_function; + bool verify(bool Assert) const debug_function; + bool verify(VerifyHelper &VH) const; +}; +INTROSPECTION_NS(model, EnumEntry, Value, CustomName, Aliases); + +/// \brief KeyedObjectTraits for model::EnumEntry based on its value +template<> +struct KeyedObjectTraits { + + static uint64_t key(const model::EnumEntry &E) { return E.Value; } + + static model::EnumEntry fromKey(uint64_t V) { return model::EnumEntry{ V }; } +}; + +/// \brief Make EnumEntry yaml-serializable +template<> +struct llvm::yaml::MappingTraits + : public TupleLikeMappingTraits::CustomName> {}; + +/// \brief An enum type in model. Enums are actually typedefs of unnamed +/// enums. +class model::EnumType : public model::Type { +public: + static constexpr const char *Tag = "!Enum"; + static constexpr const char *AutomaticNamePrefix = "enum_"; + static constexpr const TypeKind::Values AssociatedKind = TypeKind::Enum; + +public: + Identifier CustomName; + // TODO: once we decide to embed path prefixes into TupleTreeReference, it'll + // be easier to restrict this TupleTreeReference to point to a + // PrimitiveType. For now, it's not really worth the extra complexity. + TypePath UnderlyingType; + SortedVector Entries; + +public: + /// \note Not to be used directly, only KeyedObjectTraits should use this + EnumType(uint64_t ID) : Type(AssociatedKind, ID) {} + EnumType() : Type(AssociatedKind) {} + +public: + Identifier name() const; + static bool classof(const Type *T) { return T->Kind == TypeKind::Enum; } + bool operator==(const EnumType &Other) const = default; +}; +INTROSPECTION_NS(model, + EnumType, + Kind, + ID, + CustomName, + UnderlyingType, + Entries); + +template<> +struct llvm::yaml::MappingTraits + : public TupleLikeMappingTraits::CustomName> {}; + +/// \brief A typedef type in model. +class model::TypedefType : public model::Type { +public: + static constexpr const char *Tag = "!Typedef"; + static constexpr const char *AutomaticNamePrefix = "typedef_"; + static constexpr const TypeKind::Values AssociatedKind = TypeKind::Typedef; + +public: + Identifier CustomName; + QualifiedType UnderlyingType; + +public: + /// \note Not to be used directly, only KeyedObjectTraits should use this + TypedefType(uint64_t ID) : Type(AssociatedKind, ID) {} + TypedefType() : Type(AssociatedKind) {} + +public: + Identifier name() const; + static bool classof(const Type *T) { return T->Kind == TypeKind::Typedef; } +}; +INTROSPECTION_NS(model, TypedefType, Kind, ID, CustomName, UnderlyingType); + +template<> +struct llvm::yaml::MappingTraits + : public TupleLikeMappingTraits::CustomName> {}; + +/// \brief A field of an aggregate type in model, with qualified type and name +class model::AggregateField { +public: + Identifier CustomName; + QualifiedType Type; + +public: + AggregateField() {} + + bool operator==(const AggregateField &) const = default; + +public: + bool verify() const debug_function; + bool verify(bool Assert) const debug_function; + RecursiveCoroutine verify(VerifyHelper &VH) const; +}; + +/// \brief A field of a struct type in model, with offset, qualified type, and +/// name +class model::StructField : public model::AggregateField { +public: + uint64_t Offset = 0; + +public: + StructField(uint64_t Offset) : Offset(Offset) {} + StructField() : StructField(0) {} + + bool operator==(const StructField &) const = default; +}; +INTROSPECTION_NS(model, StructField, CustomName, Type, Offset); + +/// \brief KeyedObjectTraits for model::StructType based on its byte-offset in +/// struct +template<> +struct KeyedObjectTraits { + + static uint64_t key(const model::StructField &Val) { return Val.Offset; } + + static model::StructField fromKey(const uint64_t &Offset) { + return model::StructField(Offset); + } +}; + +/// \brief Make StructField yaml-serializable +template<> +struct llvm::yaml::MappingTraits + : public TupleLikeMappingTraits::CustomName> {}; + +/// \brief A struct type in model. Structs are actually typedefs of unnamed +/// structs in C. +class model::StructType : public model::Type { +public: + static constexpr const char *Tag = "!Struct"; + static constexpr const char *AutomaticNamePrefix = "struct_"; + static constexpr const TypeKind::Values AssociatedKind = TypeKind::Struct; + +public: + Identifier CustomName; + SortedVector Fields; + /// Size in bytes + uint64_t Size = 0; + +public: + /// \note Not to be used directly, only KeyedObjectTraits should use this + StructType(uint64_t ID) : Type(AssociatedKind, ID) {} + StructType() : Type(AssociatedKind) {} + +public: + Identifier name() const; + static bool classof(const Type *T) { return T->Kind == TypeKind::Struct; } +}; +INTROSPECTION_NS(model, StructType, Kind, ID, CustomName, Fields, Size); + +template<> +struct llvm::yaml::MappingTraits + : public TupleLikeMappingTraits::CustomName> {}; + +/// \brief A field of a union type in model, with position, qualified type, and +/// name +class model::UnionField : public model::AggregateField { +public: + uint64_t Index; + +public: + UnionField(uint64_t Index) : AggregateField(), Index(Index) {} + UnionField() : UnionField(0) {} + +public: + bool operator==(const UnionField &Other) const = default; +}; +INTROSPECTION_NS(model, UnionField, CustomName, Type, Index); + +/// \brief KeyedObjectTraits for model::UnionField based on its position in the +/// union +template<> +struct KeyedObjectTraits { + + static uint64_t key(const model::UnionField &Val) { return Val.Index; } + + static model::UnionField fromKey(const uint64_t &Index) { + return model::UnionField(Index); + } +}; + +/// \brief Make UnionField yaml-serializable +template<> +struct llvm::yaml::MappingTraits + : public TupleLikeMappingTraits::CustomName> {}; + +/// \brief A union type in model. Unions are actually typedefs of unnamed +/// unions in C. +class model::UnionType : public model::Type { +public: + static constexpr const char *Tag = "!Union"; + static constexpr const char *AutomaticNamePrefix = "union_"; + static constexpr const TypeKind::Values AssociatedKind = TypeKind::Union; + +public: + Identifier CustomName; + SortedVector Fields; + +public: + /// \note Not to be used directly, only KeyedObjectTraits should use this + UnionType(uint64_t ID) : Type(AssociatedKind, ID) {} + UnionType() : Type(AssociatedKind) {} + +public: + Identifier name() const; + static bool classof(const Type *T) { return T->Kind == TypeKind::Union; } +}; +INTROSPECTION_NS(model, UnionType, Kind, ID, CustomName, Fields); + +template<> +struct llvm::yaml::MappingTraits + : public TupleLikeMappingTraits::CustomName> {}; + +/// \brief +class model::TypedRegister { +public: + Register::Values Location; + QualifiedType Type; + +public: + TypedRegister(Register::Values Location) : Location(Location) {} + TypedRegister() : TypedRegister(Register::Invalid) {} + +public: + bool operator==(const TypedRegister &) const = default; + +public: + bool verify() const debug_function; + bool verify(bool Assert) const debug_function; + RecursiveCoroutine verify(VerifyHelper &VH) const; +}; +INTROSPECTION_NS(model, TypedRegister, Location, Type); + +template<> +struct KeyedObjectTraits { + static model::Register::Values key(const model::TypedRegister &Obj) { + return Obj.Location; + } + + static model::TypedRegister fromKey(const model::Register::Values &Register) { + return model::TypedRegister(Register); + } +}; + +template<> +struct llvm::yaml::MappingTraits + : public TupleLikeMappingTraits {}; + +/// \brief +class model::NamedTypedRegister : public TypedRegister { +public: + Identifier CustomName; + +public: + using TypedRegister::TypedRegister; + using TypedRegister::operator==; + +public: + Identifier name() const; + +public: + bool verify() const debug_function; + bool verify(bool Assert) const debug_function; + RecursiveCoroutine verify(VerifyHelper &VH) const; +}; +INTROSPECTION_NS(model, NamedTypedRegister, Location, Type, CustomName); + +template<> +struct KeyedObjectTraits { + static model::Register::Values key(const model::NamedTypedRegister &Obj) { + return Obj.Location; + } + + static model::NamedTypedRegister + fromKey(const model::Register::Values &Register) { + return model::NamedTypedRegister(Register); + } +}; + +constexpr auto NTRCustomName = Fields::CustomName; + +template<> +struct llvm::yaml::MappingTraits + : public TupleLikeMappingTraits {}; + +class model::RawFunctionType : public model::Type { +public: + static constexpr const char *Tag = "!RawFunctionType"; + static constexpr const char *AutomaticNamePrefix = "rawfunction_"; + static constexpr const auto AssociatedKind = TypeKind::RawFunctionType; + +public: + Identifier CustomName; + SortedVector Arguments; + SortedVector ReturnValues; + SortedVector PreservedRegisters; + uint64_t FinalStackOffset = 0; + +public: + /// \note Not to be used directly, only KeyedObjectTraits should use this + RawFunctionType(uint64_t ID) : Type(AssociatedKind, ID) {} + RawFunctionType() : Type(AssociatedKind) {} + +public: + Identifier name() const; + static bool classof(const Type *T) { + return T->Kind == TypeKind::RawFunctionType; + } +}; +INTROSPECTION_NS(model, + RawFunctionType, + Kind, + ID, + CustomName, + Arguments, + ReturnValues, + PreservedRegisters, + FinalStackOffset); + +template<> +struct llvm::yaml::MappingTraits + : public TupleLikeMappingTraits::CustomName> { +}; + +template +V getOrDefault(const std::map &Map, const K &Key, const V &Default) { + auto It = Map.find(Key); + if (It == Map.end()) + return Default; + else + return It->second; +} + +namespace model::abi { +enum Values { Invalid, SystemV_x86_64 }; +} // namespace model::abi + +namespace llvm::yaml { +template<> +struct ScalarEnumerationTraits { + template + static void enumeration(T &IO, model::abi::Values &V) { + using namespace model::abi; + IO.enumCase(V, "Invalid", Invalid); + IO.enumCase(V, "SystemV_x86_64", SystemV_x86_64); + } +}; +} // namespace llvm::yaml + +/// \brief The argument of a function type +/// +/// It features an argument index (the key), a type and an optional name +class model::Argument { +public: + uint64_t Index; + QualifiedType Type; + Identifier CustomName; + +public: + Argument(uint64_t Index) : Index(Index) {} + Argument() : Argument(0) {} + +public: + bool operator==(const Argument &) const = default; + +public: + bool verify() const debug_function; + bool verify(bool Assert) const debug_function; + RecursiveCoroutine verify(VerifyHelper &VH) const; +}; +INTROSPECTION_NS(model, Argument, Index, Type, CustomName); + +/// \brief KeyedObjectTraits for model::Argument based on its position +template<> +struct KeyedObjectTraits { + + static uint64_t key(const model::Argument &Val) { return Val.Index; } + + static model::Argument fromKey(const uint64_t &Index) { + return model::Argument(Index); + } +}; + +/// \brief Make Argument yaml-serializable +template<> +struct llvm::yaml::MappingTraits + : public TupleLikeMappingTraits::CustomName> {}; + +/// \brief The function type described through a C-like prototype plus an ABI +/// +/// This is an "high level" representation of the prototype of a function. It is +/// expressed as list of arguments composed by an index and a type. No +/// information about the register is embedded. That information is implicit in +/// the ABI this type is associated to. +/// +/// \see RawFunctionType +class model::CABIFunctionType : public model::Type { +public: + static constexpr const char *Tag = "!CABIFunctionType"; + static constexpr const char *AutomaticNamePrefix = "cabifunction_"; + static constexpr const auto AssociatedKind = TypeKind::CABIFunctionType; + + Identifier CustomName; + abi::Values ABI = abi::Invalid; + QualifiedType ReturnType; + SortedVector Arguments; + // TODO: handle variadic functions + +public: + /// \note Not to be used directly, only KeyedObjectTraits should use this + CABIFunctionType(uint64_t ID) : Type(AssociatedKind, ID) {} + CABIFunctionType() : Type(AssociatedKind) {} + +public: + Identifier name() const; + static bool classof(const Type *T) { + return T->Kind == TypeKind::CABIFunctionType; + } +}; +INTROSPECTION_NS(model, + CABIFunctionType, + Kind, + ID, + CustomName, + ABI, + ReturnType, + Arguments); + +template<> +struct llvm::yaml::MappingTraits + : public TupleLikeMappingTraits::CustomName> { +}; + +namespace model { + +template +inline model::UpcastableType +makeTypeWithID(model::TypeKind::Values K, uint64_t ID) { + using concrete_types = concrete_types_traits_t; + if constexpr (I < std::tuple_size_v) { + using type = std::tuple_element_t; + if (type::AssociatedKind == K) + return model::UpcastableType(new type(ID)); + else + return model::makeTypeWithID(K, ID); + } else { + return model::UpcastableType(nullptr); + } +} + +using TypesSet = SortedVector; + +} // end namespace model + +static_assert(validateTupleTree(IsYamlizable), + "All elements of the model type system must be YAMLizable"); + +namespace model { + +template +inline UpcastableType makeType(Args &&...A) { + return UpcastableType::make(std::forward(A)...); +} + +} // end namespace model diff --git a/include/revng/Model/VerifyHelper.h b/include/revng/Model/VerifyHelper.h new file mode 100644 index 000000000..14d222bd6 --- /dev/null +++ b/include/revng/Model/VerifyHelper.h @@ -0,0 +1,82 @@ +#pragma once + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include +#include + +#include "revng/Support/Assert.h" + +namespace model { +class Type; +} // namespace model +namespace model { + +class VerifyHelper { +private: + std::set VerifiedCache; + std::map SizeCache; + std::set InProgress; + bool AssertOnFail = false; + +public: + VerifyHelper() = default; + VerifyHelper(bool AssertOnFail) : AssertOnFail(AssertOnFail) {} + + ~VerifyHelper() { revng_assert(InProgress.size() == 0); } + +public: + void setVerified(const model::Type *T) { + revng_assert(not isVerified(T)); + VerifiedCache.insert(T); + } + + bool isVerified(const model::Type *T) const { + return VerifiedCache.count(T) != 0; + } + +public: + bool isVerificationInProgess(const model::Type *T) const { + return InProgress.count(T) != 0; + } + + void verificationInProgess(const model::Type *T) { + revng_assert(not isVerificationInProgess(T)); + revng_assert(not isVerified(T)); + InProgress.insert(T); + } + + void verificationCompleted(const model::Type *T) { + revng_assert(isVerificationInProgess(T)); + InProgress.erase(T); + } + +public: + void setSize(const model::Type *T, uint64_t Size) { + revng_assert(not size(T)); + SizeCache[T] = Size; + } + + std::optional size(const model::Type *T) { + auto It = SizeCache.find(T); + if (It != SizeCache.end()) + return It->second; + else + return {}; + } + +public: + bool maybeFail(bool Result) const { + if (AssertOnFail and not Result) { + revng_abort(); + } else { + return Result; + } + } + + bool fail() const { return maybeFail(false); } +}; + +} // namespace model diff --git a/include/revng/Support/MetaAddress.h b/include/revng/Support/MetaAddress.h index 6bb1464ef..917de756d 100644 --- a/include/revng/Support/MetaAddress.h +++ b/include/revng/Support/MetaAddress.h @@ -841,7 +841,10 @@ public: static MetaAddress fromString(llvm::StringRef Text); private: - using Tied = std::tuple; + using Tied = std::tuple; Tied tie() const { return std::tie(Epoch, AddressSpace, Type, Address); } }; diff --git a/lib/FunctionIsolation/EnforceABI.cpp b/lib/FunctionIsolation/EnforceABI.cpp index 7cfd0c72a..a32ee65f3 100644 --- a/lib/FunctionIsolation/EnforceABI.cpp +++ b/lib/FunctionIsolation/EnforceABI.cpp @@ -49,53 +49,6 @@ static cl::opt DisableSafetyChecks("disable-enforce-abi-safety-checks", cl::cat(MainCategory), cl::init(false)); -static bool areCompatible(model::RegisterState::Values LHS, - model::RegisterState::Values RHS) { - using namespace model::RegisterState; - - if (LHS == RHS or LHS == Maybe or RHS == Maybe) - return true; - - switch (LHS) { - case NoOrDead: - return RHS == No or RHS == Dead; - case YesOrDead: - return RHS == Yes or RHS == Dead; - case No: - return RHS == NoOrDead; - case Yes: - return RHS == YesOrDead; - case Dead: - return RHS == NoOrDead or RHS == YesOrDead; - case Contradiction: - return false; - case Invalid: - default: - revng_abort(); - } - - revng_abort(); -} - -static bool areCompatible(const model::FunctionABIRegister &LHS, - const model::FunctionABIRegister &RHS) { - return areCompatible(LHS.Argument, RHS.Argument) - and areCompatible(LHS.ReturnValue, RHS.ReturnValue); -} - -static StringRef -areCompatible(const model::Function &Callee, const model::CallEdge &Edge) { - - for (const model::FunctionABIRegister &Register : Callee.Registers) { - auto It = Edge.Registers.find(Register.Register); - if (It != Edge.Registers.end() and not areCompatible(Register, *It)) { - return model::Register::getName(Register.Register); - } - } - - return StringRef(); -} - class EnforceABIImpl { public: EnforceABIImpl(Module &M, @@ -161,8 +114,8 @@ void EnforceABIImpl::run() { if (FunctionModel.Type == model::FunctionType::Fake) continue; - revng_assert(FunctionModel.Name.size() != 0); - Function *OldFunction = M.getFunction(FunctionModel.Name); + revng_assert(not FunctionModel.name().empty()); + Function *OldFunction = M.getFunction(FunctionModel.name()); revng_assert(OldFunction != nullptr); OldFunctions.push_back(OldFunction); Function *NewFunction = handleFunction(*OldFunction, FunctionModel); @@ -209,28 +162,27 @@ void EnforceABIImpl::run() { } } -Function *EnforceABIImpl::handleFunction(Function &OldFunction, - const model::Function &FunctionModel) { - SmallVector ArgumentsTypes; - SmallVector ArgumentCSVs; - SmallVector ReturnTypes; - SmallVector ReturnCSVs; +static FunctionType * +toLLVMType(llvm::Module *M, const model::RawFunctionType &Prototype) { + using model::NamedTypedRegister; + using model::RawFunctionType; + using model::TypedRegister; - for (const model::FunctionABIRegister &Register : FunctionModel.Registers) { - auto Name = ABIRegister::toCSVName(Register.Register); - auto *CSV = cast(M.getGlobalVariable(Name, true)); + LLVMContext &Context = M->getContext(); - // Collect arguments - if (shouldEmit(Register.Argument)) { - ArgumentsTypes.push_back(CSV->getType()->getPointerElementType()); - ArgumentCSVs.push_back(CSV); - } + SmallVector ArgumentsTypes; + SmallVector ReturnTypes; - // Collect return values - if (shouldEmit(Register.ReturnValue)) { - ReturnTypes.push_back(CSV->getType()->getPointerElementType()); - ReturnCSVs.push_back(CSV); - } + for (const NamedTypedRegister &TR : Prototype.Arguments) { + auto Name = ABIRegister::toCSVName(TR.Location); + auto *CSV = cast(M->getGlobalVariable(Name, true)); + ArgumentsTypes.push_back(CSV->getType()->getPointerElementType()); + } + + for (const TypedRegister &TR : Prototype.ReturnValues) { + auto Name = ABIRegister::toCSVName(TR.Location); + auto *CSV = cast(M->getGlobalVariable(Name, true)); + ReturnTypes.push_back(CSV->getType()->getPointerElementType()); } // Create the return type @@ -243,7 +195,34 @@ Function *EnforceABIImpl::handleFunction(Function &OldFunction, ReturnType = StructType::create(ReturnTypes); // Create new function - auto *NewType = FunctionType::get(ReturnType, ArgumentsTypes, false); + return FunctionType::get(ReturnType, ArgumentsTypes, false); +} + +Function *EnforceABIImpl::handleFunction(Function &OldFunction, + const model::Function &FunctionModel) { + using model::NamedTypedRegister; + using model::RawFunctionType; + using model::TypedRegister; + + SmallVector ArgumentCSVs; + SmallVector ReturnCSVs; + + const auto &Prototype = *cast(FunctionModel.Prototype.get()); + // We sort arguments by their CSV name + for (const NamedTypedRegister &TR : Prototype.Arguments) { + auto Name = ABIRegister::toCSVName(TR.Location); + auto *CSV = cast(M.getGlobalVariable(Name, true)); + ArgumentCSVs.push_back(CSV); + } + + for (const TypedRegister &TR : Prototype.ReturnValues) { + auto Name = ABIRegister::toCSVName(TR.Location); + auto *CSV = cast(M.getGlobalVariable(Name, true)); + ReturnCSVs.push_back(CSV); + } + + // Create new function + auto *NewType = toLLVMType(&M, Prototype); auto *NewFunction = Function::Create(NewType, GlobalValue::ExternalLinkage, "", @@ -253,9 +232,9 @@ Function *EnforceABIImpl::handleFunction(Function &OldFunction, FunctionTags::Lifted.addTo(NewFunction); // Set argument names - unsigned I = 0; - for (Argument &Argument : NewFunction->args()) - Argument.setName(ArgumentCSVs[I++]->getName()); + for (const auto &[LLVMArgument, ModelArgument] : + zip(NewFunction->args(), Prototype.Arguments)) + LLVMArgument.setName(ModelArgument.name()); // Steal body from the old function std::vector Body; @@ -284,7 +263,7 @@ Function *EnforceABIImpl::handleFunction(Function &OldFunction, for (GlobalVariable *ReturnCSV : ReturnCSVs) ReturnValues.push_back(Builder.CreateLoad(ReturnCSV)); - if (ReturnTypes.size() == 1) + if (ReturnValues.size() == 1) Builder.CreateRet(ReturnValues[0]); else Initializers.createReturn(Builder, ReturnValues); @@ -301,7 +280,8 @@ void EnforceABIImpl::handleRegularFunctionCall(CallInst *Call) { Function *Caller = Call->getParent()->getParent(); const model::Function &FunctionModel = *FunctionsMap.at(Caller); - revng_assert(Call->getParent()->getParent()->getName() == FunctionModel.Name); + Function *CallerFunction = Call->getParent()->getParent(); + revng_assert(CallerFunction->getName() == FunctionModel.name()); Function *Callee = cast(skipCasts(Call->getCalledOperand())); bool IsDirect = (Callee != FunctionDispatcher); @@ -319,69 +299,34 @@ void EnforceABIImpl::handleRegularFunctionCall(CallInst *Call) { break; } - if (DisableSafetyChecks or IsDirect) { - // The callee is a well-known callee, generate a direct call - IRBuilder<> Builder(Call); - generateCall(Builder, Callee, *CallSite); + // Note that currently, in case of indirect call, we emit a call to a + // placeholder function that will throw an exception. If exceptions are + // correctly supported post enforce-abi, and the ABI data is correct, this + // should work. However this is not very efficient. + // + // Alternatives: + // + // 1. Emit an inline dispatcher that calls all the compatible functions (i.e., + // they take a subset of the call site's arguments and return a superset of + // the call site's return values). + // 2. We have a dedicated outlined dispatcher that takes all the arguments of + // the call site, plus all the registers of the return values. Under the + // assumption that each return value of the call site is either a return + // value of the callee or is preserved by the callee, we can fill each + // return value using the callee's return value or the argument + // representing the value of that register before the call. + // In case the call site expects a return value that is neither a return + // value nor a preserved register or the callee, we exclude it from the + /// switch. - // Create an additional store to the local %pc, so that the optimizer cannot - // do stuff with llvm.assume. - revng_assert(OpaquePC != nullptr); - Builder.CreateStore(Builder.CreateCall(OpaquePC), GCBI.pcReg()); + // Generate the call + IRBuilder<> Builder(Call); + generateCall(Builder, Callee, *CallSite); - } else { - // If it's an indirect call, enumerate all the compatible callees and - // generate a call for each of them - - EnforceABILog << getName(Call) << " is an indirect call compatible with:\n"; - - BasicBlock *BeforeSplit = Call->getParent(); - BasicBlock *AfterSplit = BeforeSplit->splitBasicBlock(Call); - BeforeSplit->getTerminator()->eraseFromParent(); - - IRBuilder<> Builder(BeforeSplit); - BasicBlock *UnexpectedPC = findByBlockType(AfterSplit->getParent(), - BlockType::UnexpectedPCBlock); - - ProgramCounterHandler::DispatcherTargets Targets; - - unsigned Count = 0; - for (auto &[F, FunctionModel] : FunctionsMap) { - EnforceABILog << " " << F->getName().data() << " "; - - // Check compatibility - StringRef IncompatibleCSV = areCompatible(*FunctionModel, *CallSite); - bool Incompatible = not IncompatibleCSV.empty(); - if (Incompatible) { - EnforceABILog << "[No: " << IncompatibleCSV.data() << "]"; - } else { - EnforceABILog << "[Yes]"; - Count++; - - // Create the basic block containing the call - auto *Case = BasicBlock::Create(Context, - "", - BeforeSplit->getParent(), - AfterSplit); - Builder.SetInsertPoint(Case); - generateCall(Builder, F, *CallSite); - Builder.CreateBr(AfterSplit); - - // Record for inline dispatcher - Targets.push_back({ FunctionModel->Entry, Case }); - } - EnforceABILog << DoLog; - } - - // Actually create the inline dispatcher - Builder.SetInsertPoint(BeforeSplit); - GCBI.programCounterHandler()->buildDispatcher(Targets, - Builder, - UnexpectedPC, - {}); - - EnforceABILog << Count << " functions" << DoLog; - } + // Create an additional store to the local %pc, so that the optimizer cannot + // do stuff with llvm.assume. + revng_assert(OpaquePC != nullptr); + Builder.CreateStore(Builder.CreateCall(OpaquePC), GCBI.pcReg()); // Drop the original call Call->eraseFromParent(); @@ -390,85 +335,50 @@ void EnforceABIImpl::handleRegularFunctionCall(CallInst *Call) { void EnforceABIImpl::generateCall(IRBuilder<> &Builder, Function *Callee, const model::CallEdge &CallSite) { + using model::NamedTypedRegister; + using model::RawFunctionType; + using model::TypedRegister; + revng_assert(Callee != nullptr); - llvm::SmallVector ArgumentsTypes; llvm::SmallVector Arguments; - llvm::SmallVector ReturnTypes; llvm::SmallVector ReturnCSVs; - bool IsDirect = (Callee != FunctionDispatcher); - if (not IsDirect) { - revng_assert(DisableSafetyChecks); - - // Collect arguments, returns and their type. - for (const model::FunctionABIRegister &Register : CallSite.Registers) { - auto Name = ABIRegister::toCSVName(Register.Register); - GlobalVariable *CSV = M.getGlobalVariable(Name, true); - if (shouldEmit(Register.Argument)) { - ArgumentsTypes.push_back(CSV->getType()->getPointerElementType()); - Arguments.push_back(Builder.CreateLoad(CSV)); - } - - if (shouldEmit(Register.ReturnValue)) { - ReturnTypes.push_back(CSV->getType()->getPointerElementType()); - ReturnCSVs.push_back(CSV); - } - } - - // Create here on the fly the indirect function that we want to call. - // Create the return type - Type *ReturnType = Type::getVoidTy(Context); - if (ReturnTypes.size() == 0) - ReturnType = Type::getVoidTy(Context); - else if (ReturnTypes.size() == 1) - ReturnType = ReturnTypes[0]; - else - ReturnType = StructType::create(ReturnTypes); + const auto &Prototype = *cast(CallSite.Prototype.get()); + bool IsIndirect = (Callee != FunctionDispatcher); + if (IsIndirect) { // Create a new `indirect_placeholder` function with the specific function // type we need - auto *NewType = FunctionType::get(ReturnType, ArgumentsTypes, false); + auto *NewType = toLLVMType(&M, Prototype); Callee = IndirectPlaceholderPool.get(NewType, NewType, "indirect_placeholder"); } else { - - // Additional debug checks if we are not emitting an indirect call. BasicBlock *InsertBlock = Builder.GetInsertPoint()->getParent(); revng_log(EnforceABILog, "Emitting call to " << getName(Callee) << " from " << getName(InsertBlock)); - - const model::Function *FunctionModel = FunctionsMap.at(Callee); - revng_assert(FunctionTags::Lifted.isTagOf(Callee)); - StringRef IncompatibleCSV = areCompatible(*FunctionModel, CallSite); - bool Incompatible = not IncompatibleCSV.empty(); - if (Incompatible) { - dbg << getName(InsertBlock) << " -> " - << (Callee == nullptr ? "nullptr" : Callee->getName().data()) << ": " - << IncompatibleCSV.data() << "\n"; - revng_abort(); - } - - // Collect arguments, returns and their type. - for (const model::FunctionABIRegister &Register : - FunctionModel->Registers) { - auto Name = ABIRegister::toCSVName(Register.Register); - GlobalVariable *CSV = M.getGlobalVariable(Name, true); - - if (shouldEmit(Register.Argument)) { - ArgumentsTypes.push_back(CSV->getType()->getPointerElementType()); - Arguments.push_back(Builder.CreateLoad(CSV)); - } - - if (shouldEmit(Register.ReturnValue)) { - ReturnTypes.push_back(CSV->getType()->getPointerElementType()); - ReturnCSVs.push_back(CSV); - } - } } + // + // Collect arguments and returns + // + for (const NamedTypedRegister &TR : Prototype.Arguments) { + auto Name = ABIRegister::toCSVName(TR.Location); + GlobalVariable *CSV = M.getGlobalVariable(Name, true); + Arguments.push_back(Builder.CreateLoad(CSV)); + } + + for (const TypedRegister &TR : Prototype.ReturnValues) { + auto Name = ABIRegister::toCSVName(TR.Location); + GlobalVariable *CSV = M.getGlobalVariable(Name, true); + ReturnCSVs.push_back(CSV); + } + + // + // Produce the call + // auto *Result = Builder.CreateCall(Callee, Arguments); if (ReturnCSVs.size() != 1) { unsigned I = 0; diff --git a/lib/FunctionIsolation/InvokeIsolatedFunctions.cpp b/lib/FunctionIsolation/InvokeIsolatedFunctions.cpp index 47121bf7a..8fd09ef29 100644 --- a/lib/FunctionIsolation/InvokeIsolatedFunctions.cpp +++ b/lib/FunctionIsolation/InvokeIsolatedFunctions.cpp @@ -48,7 +48,7 @@ public: // TODO: this temporary Map[Function.Entry] = { &Function, nullptr, - M->getFunction(Function.Name) }; + M->getFunction(Function.name()) }; } for (BasicBlock &BB : *RootFunction) { @@ -147,14 +147,14 @@ public: // In case the isolated functions has arguments, provide them SmallVector Arguments; if (F->getFunctionType()->getNumParams() > 0) { - for (const model::FunctionABIRegister &Register : - ModelFunction->Registers) { - if (shouldEmit(Register.Argument)) { - auto Name = ABIRegister::toCSVName(Register.Register); - GlobalVariable *CSV = M->getGlobalVariable(Name, true); - revng_assert(CSV != nullptr); - Arguments.push_back(Builder.CreateLoad(CSV)); - } + using model::RawFunctionType; + auto PrototypePath = ModelFunction->Prototype; + const auto &Prototype = *cast(PrototypePath.get()); + for (const model::NamedTypedRegister &TR : Prototype.Arguments) { + auto Name = ABIRegister::toCSVName(TR.Location); + GlobalVariable *CSV = M->getGlobalVariable(Name, true); + revng_assert(CSV != nullptr); + Arguments.push_back(Builder.CreateLoad(CSV)); } } diff --git a/lib/FunctionIsolation/IsolateFunctions.cpp b/lib/FunctionIsolation/IsolateFunctions.cpp index 9e4271456..726e266cf 100644 --- a/lib/FunctionIsolation/IsolateFunctions.cpp +++ b/lib/FunctionIsolation/IsolateFunctions.cpp @@ -794,7 +794,7 @@ IFI::isolate(const model::Function &Function) { FunctionTags::Lifted.addTo(NewFunction); revng_assert(NewFunction != nullptr); - NewFunction->setName(OriginalEntry->getName()); + NewFunction->setName(Function.name()); FunctionType *FT = NewFunction->getFunctionType(); revng_assert(FT->getReturnType()->isVoidTy()); diff --git a/lib/Model/Binary.cpp b/lib/Model/Binary.cpp index f39dbcf48..be2d449e2 100644 --- a/lib/Model/Binary.cpp +++ b/lib/Model/Binary.cpp @@ -12,6 +12,7 @@ #include "revng/ADT/GenericGraph.h" #include "revng/Model/Binary.h" +#include "revng/Model/VerifyHelper.h" using namespace llvm; @@ -70,25 +71,93 @@ public: } }; +model::TypePath +Binary::getPrimitiveType(PrimitiveTypeKind::Values V, uint8_t ByteSize) { + PrimitiveType Temporary(V, ByteSize); + Type::Key PrimitiveKey{ TypeKind::Primitive, Temporary.ID }; + auto It = Types.find(PrimitiveKey); + + // If we couldn't find it, create it + if (It == Types.end()) { + auto *NewPrimitiveType = new PrimitiveType(V, ByteSize); + It = Types.insert(UpcastableType(NewPrimitiveType)).first; + } + + return getTypePath(It->get()); +} + +TypePath Binary::recordNewType(UpcastablePointer &&T) { + auto It = Types.insert(T).first; + return getTypePath(It->get()); +} + +bool Binary::verifyTypes() const { + return verifyTypes(false); +} + +bool Binary::verifyTypes(bool Assert) const { + VerifyHelper VH(Assert); + return verifyTypes(VH); +} + +bool Binary::verifyTypes(VerifyHelper &VH) const { + // All types on their own should verify + std::set Names; + for (auto &Type : Types) { + // Verify the type + if (not Type.get()->verify(VH)) + return VH.fail(); + + // Ensure the names are unique + if (not Names.insert(Type->name()).second) + return VH.fail(); + } + + return true; +} + bool Binary::verify() const { + return verify(false); +} + +bool Binary::verify(bool Assert) const { + VerifyHelper VH(Assert); + return verify(VH); +} + +bool Binary::verify(VerifyHelper &VH) const { for (const Function &F : Functions) { // Verify individual functions - if (not F.verify()) - return false; + if (not F.verify(VH)) + return VH.fail(); - // Ensure all the direct function calls target an existing function + // Check function calls for (const BasicBlock &Block : F.CFG) { for (const auto &Edge : Block.Successors) { - if (Edge->Type == FunctionEdgeType::FunctionCall - and Functions.count(Edge->Destination) == 0) { - return false; + + if (Edge->Type == model::FunctionEdgeType::FunctionCall) { + // We're in a direct call, get the callee + const auto *Call = dyn_cast(Edge.get()); + auto It = Functions.find(Call->Destination); + + // If missing, fail + if (It == Functions.end()) + return VH.fail(); + + // If call and callee prototypes differ, fail + const Function &Callee = *It; + if (Call->Prototype != Callee.Prototype) + return VH.fail(); } } } } - return true; + // + // Verify the type system + // + return verifyTypes(VH); } static FunctionCFG getGraph(const Function &F) { @@ -131,6 +200,16 @@ static FunctionCFG getGraph(const Function &F) { return Graph; } +Identifier Function::name() const { + using llvm::Twine; + if (not CustomName.empty()) { + return CustomName; + } else { + auto AutomaticName = (Twine("function_") + Entry.toString()).str(); + return Identifier::fromString(AutomaticName); + } +} + void Function::dumpCFG() const { FunctionCFG CFG = getGraph(*this); raw_os_ostream Stream(dbg); @@ -138,8 +217,17 @@ void Function::dumpCFG() const { } bool Function::verify() const { + return verify(false); +} + +bool Function::verify(bool Assert) const { + VerifyHelper VH(Assert); + return verify(VH); +} + +bool Function::verify(VerifyHelper &VH) const { if (Type == FunctionType::Fake) - return CFG.size() == 0; + return VH.maybeFail(CFG.size() == 0); // Verify blocks bool HasEntry = false; @@ -147,35 +235,107 @@ bool Function::verify() const { if (Block.Start == Entry) { if (HasEntry) - return false; + return VH.fail(); HasEntry = true; } for (const auto &Edge : Block.Successors) - if (not Edge->verify()) - return false; + if (not Edge->verify(VH)) + return VH.fail(); } if (not HasEntry) - return false; + return VH.fail(); // Populate graph FunctionCFG Graph = getGraph(*this); // Ensure all the nodes are reachable from the entry node if (not Graph.allNodesAreReachable()) - return false; + return VH.fail(); // Ensure the only node with no successors is invalid if (not Graph.hasOnlyInvalidExits()) - return false; + return VH.fail(); + + // Prototype is present + if (not Prototype.isValid()) + return VH.fail(); + + // Prototype is valid + if (not Prototype.get()->verify(VH)) + return VH.fail(); + + const model::Type *FunctionType = Prototype.get(); + if (not(isa(FunctionType) + or isa(FunctionType))) + return VH.fail(); return true; } bool FunctionEdge::verify() const { + return verify(false); +} + +bool FunctionEdge::verify(bool Assert) const { + VerifyHelper VH(Assert); + return verify(VH); +} + +static bool verifyFunctionEdge(VerifyHelper &VH, const FunctionEdge &E) { using namespace model::FunctionEdgeType; - return Destination.isValid() == hasDestination(Type); + return VH.maybeFail(E.Type != FunctionEdgeType::Invalid + and E.Destination.isValid() == hasDestination(E.Type)); +} + +bool FunctionEdge::verify(VerifyHelper &VH) const { + if (auto *Call = dyn_cast(this)) + return VH.maybeFail(Call->verify(VH)); + else + return verifyFunctionEdge(VH, *this); +} + +bool CallEdge::verify() const { + return verify(false); +} + +bool CallEdge::verify(bool Assert) const { + VerifyHelper VH(Assert); + return verify(VH); +} + +bool CallEdge::verify(VerifyHelper &VH) const { + return VH.maybeFail(verifyFunctionEdge(VH, *this) and Prototype.isValid() + and Prototype.get()->verify(VH)); +} + +Identifier BasicBlock::name() const { + using llvm::Twine; + if (not CustomName.empty()) + return CustomName; + else + return Identifier(std::string("bb_") + Start.toString()); +} + +bool BasicBlock::verify() const { + return verify(false); +} + +bool BasicBlock::verify(bool Assert) const { + VerifyHelper VH(Assert); + return verify(VH); +} + +bool BasicBlock::verify(VerifyHelper &VH) const { + if (Start.isInvalid() or End.isInvalid() or not CustomName.verify(VH)) + return VH.fail(); + + for (auto &Edge : Successors) + if (not Edge->verify(VH)) + return VH.fail(); + + return true; } } // namespace model diff --git a/lib/Model/CMakeLists.txt b/lib/Model/CMakeLists.txt index d9de6ed0f..0f9eae67e 100644 --- a/lib/Model/CMakeLists.txt +++ b/lib/Model/CMakeLists.txt @@ -5,7 +5,8 @@ revng_add_analyses_library_internal(revngModel Binary.cpp LoadModelPass.cpp - SerializeModelPass.cpp) + SerializeModelPass.cpp + Type.cpp) target_link_libraries(revngModel revngSupport) diff --git a/lib/Model/Type.cpp b/lib/Model/Type.cpp new file mode 100644 index 000000000..e76f897f4 --- /dev/null +++ b/lib/Model/Type.cpp @@ -0,0 +1,928 @@ +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include +#include +#include +#include +#include + +#include "llvm/ADT/SmallSet.h" +#include "llvm/Support/MathExtras.h" + +#include "revng/Model/Binary.h" +#include "revng/Model/Type.h" +#include "revng/Model/VerifyHelper.h" + +using llvm::cast; +using llvm::dyn_cast; + +namespace model { + +const Identifier Identifier::Empty = Identifier(""); + +static std::set CReservedKeywords = { + // C reserved keywords + "auto", + "break", + "case", + "char", + "const", + "continue", + "default", + "do", + "double", + "else", + "enum", + "extern", + "float", + "for", + "goto", + "if", + "inline", // Since C99 + "int", + "long", + "register", + "restrict", // Since C99 + "return", + "short", + "signed", + "sizeof", + "static", + "struct", + "switch", + "typedef", + "union", + "unsigned", + "volatile", + "while", + "_Alignas", // Since C11 + "_Alignof", // Since C11 + "_Atomic", // Since C11 + "_Bool", // Since C99 + "_Complex", // Since C99 + "_Decimal128", // Since C23 + "_Decimal32", // Since C23 + "_Decimal64", // Since C23 + "_Generic", // Since C11 + "_Imaginary", // Since C99 + "_Noreturn", // Since C11 + "_Static_assert", // Since C11 + "_Thread_local", // Since C11 + // Convenience macros + "alignas", + "alignof", + "bool", + "complex", + "imaginary", + "noreturn", + "static_assert", + "thread_local", + // Convenience macros for atomic types + "atomic_bool", + "atomic_char", + "atomic_schar", + "atomic_uchar", + "atomic_short", + "atomic_ushort", + "atomic_int", + "atomic_uint", + "atomic_long", + "atomic_ulong", + "atomic_llong", + "atomic_ullong", + "atomic_char16_t", + "atomic_char32_t", + "atomic_wchar_t", + "atomic_int_least8_t", + "atomic_uint_least8_t", + "atomic_int_least16_t", + "atomic_uint_least16_t", + "atomic_int_least32_t", + "atomic_uint_least32_t", + "atomic_int_least64_t", + "atomic_uint_least64_t", + "atomic_int_fast8_t", + "atomic_uint_fast8_t", + "atomic_int_fast16_t", + "atomic_uint_fast16_t", + "atomic_int_fast32_t", + "atomic_uint_fast32_t", + "atomic_int_fast64_t", + "atomic_uint_fast64_t", + "atomic_intptr_t", + "atomic_uintptr_t", + "atomic_size_t", + "atomic_ptrdiff_t", + "atomic_intmax_t", + "atomic_uintmax_t", + // C Extensions + "_Pragma", + "asm", +}; + +static llvm::cl::opt ModelTypeIDSeed("model-type-id-seed", + llvm::cl::desc("Set the seed " + "for the " + "generation of " + "ID of model " + "Types"), + llvm::cl::cat(MainCategory), + llvm::cl::init(false)); + +class RNG { + std::mt19937_64 Generator; + std::uniform_int_distribution Distribution; + +public: + RNG() : + Generator(ModelTypeIDSeed.getNumOccurrences() ? ModelTypeIDSeed.getValue() : + std::random_device()()), + Distribution(std::numeric_limits::min(), + std::numeric_limits::max()) {} + + uint64_t get() { return Distribution(Generator); } +}; + +llvm::ManagedStatic IDGenerator; + +model::Type::Type(TypeKind::Values TK) : + model::Type::Type(TK, IDGenerator->get()) { +} + +Identifier model::Type::name() const { + auto *This = this; + auto GetName = [](auto &Upcasted) -> Identifier { return Upcasted.name(); }; + return upcast(This, GetName, Identifier("")); +} + +bool Qualifier::verify() const { + return verify(false); +} + +bool Qualifier::verify(bool Assert) const { + VerifyHelper VH(Assert); + return verify(VH); +} + +bool Qualifier::verify(VerifyHelper &VH) const { + switch (Kind) { + case QualifierKind::Invalid: + return VH.fail(); + case QualifierKind::Pointer: + return VH.maybeFail(Size > 0 and llvm::isPowerOf2_64(Size)); + case QualifierKind::Const: + return VH.maybeFail(Size == 0); + case QualifierKind::Array: + return VH.maybeFail(Size > 0); + } + + return VH.fail(); +} + +static constexpr bool +isValidPrimitiveSize(PrimitiveTypeKind::Values PrimKind, uint8_t BS) { + switch (PrimKind) { + case PrimitiveTypeKind::Invalid: + return false; + + case PrimitiveTypeKind::Void: + return BS == 0; + + case PrimitiveTypeKind::Generic: + case PrimitiveTypeKind::PointerOrNumber: + case PrimitiveTypeKind::Number: + case PrimitiveTypeKind::Unsigned: + case PrimitiveTypeKind::Signed: + return BS == 1 or BS == 2 or BS == 4 or BS == 8 or BS == 16; + + case PrimitiveTypeKind::Float: + return BS == 2 or BS == 4 or BS == 8 or BS == 16; + } + + revng_abort(); +} + +Identifier model::PrimitiveType::name() const { + using llvm::Twine; + revng_assert(isValidPrimitiveSize(PrimitiveKind, Size)); + Identifier Result; + + switch (PrimitiveKind) { + case PrimitiveTypeKind::Void: + Result = "void"; + break; + + case PrimitiveTypeKind::Unsigned: + (Twine("uint") + Twine(Size * 8) + Twine("_t")).toVector(Result); + break; + + case PrimitiveTypeKind::Number: + (Twine("number") + Twine(Size * 8) + Twine("_t")).toVector(Result); + break; + + case PrimitiveTypeKind::PointerOrNumber: + ("pointer_or_number" + Twine(Size * 8) + "_t").toVector(Result); + break; + + case PrimitiveTypeKind::Generic: + (Twine("generic") + Twine(Size * 8) + Twine("_t")).toVector(Result); + break; + + case PrimitiveTypeKind::Signed: + (Twine("int") + Twine(Size * 8) + Twine("_t")).toVector(Result); + break; + + case PrimitiveTypeKind::Float: + (Twine("float") + Twine(Size * 8) + Twine("_t")).toVector(Result); + break; + + default: + revng_abort(); + } + + return Result; +} + +template +Identifier customNameOrAutomatic(T *This) { + using llvm::Twine; + if (not This->CustomName.empty()) + return This->CustomName; + else + return Identifier((Twine(T::AutomaticNamePrefix) + Twine(This->ID)).str()); +} + +Identifier model::StructType::name() const { + return customNameOrAutomatic(this); +} + +Identifier model::TypedefType::name() const { + return customNameOrAutomatic(this); +} + +Identifier model::EnumType::name() const { + return customNameOrAutomatic(this); +} + +Identifier model::UnionType::name() const { + return customNameOrAutomatic(this); +} + +Identifier model::NamedTypedRegister::name() const { + using llvm::Twine; + if (not CustomName.empty()) + return CustomName; + else + return Identifier(model::Register::getRegisterName(Location)); +} + +Identifier model::RawFunctionType::name() const { + return customNameOrAutomatic(this); +} + +Identifier model::CABIFunctionType::name() const { + return customNameOrAutomatic(this); +} + +static uint64_t +makePrimitiveID(PrimitiveTypeKind::Values PrimitiveKind, uint8_t Size) { + return (static_cast(PrimitiveKind) << 8) | Size; +} + +static PrimitiveTypeKind::Values getPrimitiveKind(uint64_t ID) { + return static_cast(ID >> 8); +} + +static uint8_t getPrimitiveSize(uint64_t ID) { + return ID & ((1 << 8) - 1); +} + +PrimitiveType::PrimitiveType(PrimitiveTypeKind::Values PrimitiveKind, + uint8_t Size) : + Type(AssociatedKind, makePrimitiveID(PrimitiveKind, Size)), + PrimitiveKind(PrimitiveKind), + Size(Size) { +} + +PrimitiveType::PrimitiveType(uint64_t ID) : + Type(AssociatedKind, ID), + PrimitiveKind(getPrimitiveKind(ID)), + Size(getPrimitiveSize(ID)) { +} + +bool EnumEntry::verify() const { + return verify(false); +} + +bool EnumEntry::verify(bool Assert) const { + VerifyHelper VH(Assert); + return verify(VH); +} + +bool EnumEntry::verify(VerifyHelper &VH) const { + for (const Identifier &Alias : Aliases) + if (not Alias.verify(VH)) + return VH.fail(); + + return VH.maybeFail(CustomName.verify(VH) and not Aliases.count(CustomName) + and not Aliases.count(Identifier::Empty)); +} + +static bool isOnlyConstQualified(const QualifiedType &QT) { + if (QT.Qualifiers.empty() or QT.Qualifiers.size() > 1) + return false; + + return QT.Qualifiers[0].isConstQualifier(); +} + +struct VoidConstResult { + bool IsVoid; + bool IsConst; +}; + +static VoidConstResult isVoidConst(const QualifiedType *QualType) { + VoidConstResult Result{ /* IsVoid */ false, /* IsConst */ false }; + + bool Done = false; + while (not Done) { + + // If the argument type is qualified try to get the unqualified version. + // Warning: we only skip const-qualifiers here, cause the other qualifiers + // actually produce a different type. + const Type *UnqualType = nullptr; + if (not QualType->Qualifiers.empty()) { + + // If it has a non-const qualifier, it can never be void because it's a + // pointer or array, so we can break out. + if (not isOnlyConstQualified(*QualType)) { + Done = true; + continue; + } + + // We know that it's const-qualified here, and it only has one + // qualifier, hence we can skip the const-qualifier. + Result.IsConst = true; + if (not QualType->UnqualifiedType.Root) + return Result; + } + + UnqualType = QualType->UnqualifiedType.get(); + + switch (UnqualType->Kind) { + + // If we still have a typedef in our way, unwrap it and keep looking. + case TypeKind::Typedef: { + QualType = &cast(UnqualType)->UnderlyingType; + } break; + + // If we have a primitive type, check the name, and we're done. + case TypeKind::Primitive: { + auto *P = cast(UnqualType); + Result.IsVoid = P->PrimitiveKind == PrimitiveTypeKind::Void; + Done = true; + } break; + + // In all the other cases it's not void, break from the while. + default: { + Done = true; + } break; + } + } + return Result; +} + +std::optional QualifiedType::size() const { + VerifyHelper VH; + return size(VH); +} + +RecursiveCoroutine> +QualifiedType::size(VerifyHelper &VH) const { + // This code assumes that the QualifiedType QT is well formed. + auto QIt = Qualifiers.begin(); + auto QEnd = Qualifiers.end(); + + for (; QIt != QEnd; ++QIt) { + + auto &Q = *QIt; + switch (Q.Kind) { + + case QualifierKind::Invalid: + revng_abort(); + + case QualifierKind::Pointer: + // If we find a pointer, we're done + rc_return Q.Size; + + case QualifierKind::Array: { + // The size is equal to (number of elements of the array) * (size of a + // single element). + const QualifiedType ArrayElem{ UnqualifiedType, + { std::next(QIt), QEnd } }; + auto MaybeSize = rc_recur ArrayElem.size(VH); + revng_assert(MaybeSize); + rc_return *MaybeSize *Q.Size; + } + + case QualifierKind::Const: + // Do nothing, just skip over it + break; + } + } + + rc_return rc_recur UnqualifiedType.get()->size(VH); +} + +std::optional Type::size() const { + VerifyHelper VH; + return size(VH); +} + +RecursiveCoroutine> Type::size(VerifyHelper &VH) const { + using ResultType = std::optional; + auto MaybeSize = VH.size(this); + if (MaybeSize) + rc_return{ *MaybeSize == 0 ? ResultType{} : *MaybeSize }; + + // This code assumes that the type T is well formed. + ResultType Size; + + switch (Kind) { + case TypeKind::Invalid: + revng_abort(); + + case TypeKind::RawFunctionType: + case TypeKind::CABIFunctionType: + // Function prototypes have no size + Size = {}; + break; + + case TypeKind::Primitive: { + auto *P = cast(this); + + if (P->PrimitiveKind == model::PrimitiveTypeKind::Void) { + // Void types have no size + revng_assert(P->Size == 0); + Size = {}; + } else { + Size = P->Size; + } + } break; + + case TypeKind::Enum: { + auto *U = llvm::cast(this)->UnderlyingType.get(); + Size = rc_recur U->size(VH); + } break; + + case TypeKind::Typedef: { + auto *Typedef = llvm::cast(this); + Size = rc_recur Typedef->UnderlyingType.size(VH); + } break; + + case TypeKind::Struct: { + Size = llvm::cast(this)->Size; + } break; + + case TypeKind::Union: { + auto *U = llvm::cast(this); + uint64_t Max = 0ULL; + for (const auto &Field : U->Fields) { + auto FieldSize = rc_recur Field.Type.size(VH); + Max = std::max(Max, FieldSize ? *FieldSize : 0); + } + Size = { Max == 0 ? ResultType{} : Max }; + } break; + } + + VH.setSize(this, Size ? *Size : 0); + + rc_return Size; +}; + +static RecursiveCoroutine +verifyImpl(VerifyHelper &VH, const PrimitiveType *T) { + rc_return VH.maybeFail(T->Kind == TypeKind::Primitive + and makePrimitiveID(T->PrimitiveKind, T->Size) == T->ID + and isValidPrimitiveSize(T->PrimitiveKind, T->Size)); +} + +bool Identifier::verify() const { + return verify(false); +} + +bool Identifier::verify(bool Assert) const { + VerifyHelper VH(Assert); + return verify(VH); +} + +bool Identifier::verify(VerifyHelper &VH) const { + return VH.maybeFail(not(not empty() and std::isdigit((*this)[0])) + and not count(' ') + and not CReservedKeywords.count(llvm::StringRef(*this))); +} + +static RecursiveCoroutine +verifyImpl(VerifyHelper &VH, const EnumType *T) { + if (T->Kind != TypeKind::Enum or T->Entries.empty() + or not T->CustomName.verify(VH)) + rc_return VH.fail(); + + // The underlying type has to be a primitive type + if (not T->UnderlyingType.isValid()) + rc_return VH.fail(); + + auto *Underlying = dyn_cast(T->UnderlyingType.get()); + if (Underlying == nullptr) + rc_return VH.fail(); + + if (not rc_recur Underlying->verify(VH)) + rc_return VH.fail(); + + // We only allow signed/unsigned as underlying type + if (Underlying->PrimitiveKind != PrimitiveTypeKind::Signed + and Underlying->PrimitiveKind != PrimitiveTypeKind::Unsigned) + rc_return VH.fail(); + + llvm::SmallSet Names; + for (auto &Entry : T->Entries) { + + if (not Entry.verify(VH)) + rc_return VH.fail(); + + // TODO: verify Entry.Value is within boundaries + + if (not Entry.CustomName.empty()) { + if (not Names.insert(Entry.CustomName).second) + rc_return VH.fail(); + } + } + + rc_return true; +} + +static RecursiveCoroutine +verifyImpl(VerifyHelper &VH, const TypedefType *T) { + rc_return VH.maybeFail(T->CustomName.verify(VH) + and T->Kind == TypeKind::Typedef + and rc_recur T->UnderlyingType.verify(VH)); +} + +inline RecursiveCoroutine isScalar(const QualifiedType &QT) { + for (const Qualifier &Q : QT.Qualifiers) { + switch (Q.Kind) { + case QualifierKind::Invalid: + revng_abort(); + case QualifierKind::Pointer: + rc_return true; + case QualifierKind::Array: + rc_return false; + case QualifierKind::Const: + break; + } + } + + const Type *Unqualified = QT.UnqualifiedType.get(); + revng_assert(Unqualified != nullptr); + if (llvm::isa(Unqualified)) { + rc_return true; + } else if (llvm::isa(Unqualified)) { + QualifiedType Inner = QT; + Inner.Qualifiers.clear(); + rc_return rc_recur isScalar(Inner); + } + + rc_return false; +} + +static RecursiveCoroutine +verifyImpl(VerifyHelper &VH, const StructType *T) { + if (not T->CustomName.verify(VH) or T->Kind != TypeKind::Struct or not T->Size + or T->Fields.empty()) + rc_return VH.fail(); + + llvm::SmallSet Names; + auto FieldIt = T->Fields.begin(); + auto FieldEnd = T->Fields.end(); + for (; FieldIt != FieldEnd; ++FieldIt) { + auto &Field = *FieldIt; + + if (not rc_recur Field.verify(VH)) + rc_return VH.fail(); + + if (Field.Offset >= T->Size) + rc_return VH.fail(); + + auto MaybeSize = rc_recur Field.Type.size(VH); + + // Structs cannot have zero-sized fields + if (not MaybeSize) + rc_return VH.fail(); + + auto FieldEndOffset = Field.Offset + *MaybeSize; + auto NextFieldIt = std::next(FieldIt); + if (NextFieldIt != FieldEnd) { + // If this field is not the last, check that it does not overlap with the + // following field. + if (FieldEndOffset > NextFieldIt->Offset) + rc_return VH.fail(); + } else if (FieldEndOffset > T->Size) { + // Otherwise, if this field is the last, check that it's not larger than + // size. + rc_return VH.fail(); + } + + if (isVoidConst(&Field.Type).IsVoid) + rc_return VH.fail(); + + bool New = Field.CustomName.empty() ? true : + Names.insert(Field.CustomName).second; + if (not New) + rc_return VH.fail(); + } + rc_return true; +} + +static RecursiveCoroutine +verifyImpl(VerifyHelper &VH, const UnionType *T) { + if (not T->CustomName.verify(VH) or T->Kind != TypeKind::Union + or T->Fields.empty()) + rc_return false; + + llvm::SmallSet Names; + for (auto &Group : llvm::enumerate(T->Fields)) { + auto &Field = Group.value(); + uint64_t ExpectedIndex = Group.index(); + + if (Field.Index != ExpectedIndex) + rc_return VH.fail(); + + if (not rc_recur Field.verify(VH)) + rc_return VH.fail(); + + if (Field.CustomName.size() > 0) { + if (not Names.insert(Field.CustomName).second) + rc_return VH.fail(); + } + + if (isVoidConst(&Field.Type).IsVoid) + rc_return VH.fail(); + } + + rc_return true; +} + +static RecursiveCoroutine +verifyImpl(VerifyHelper &VH, const CABIFunctionType *T) { + if (not T->CustomName.verify(VH) or T->Kind != TypeKind::CABIFunctionType + or not rc_recur T->ReturnType.verify(VH)) + rc_return VH.fail(); + + for (auto &Group : llvm::enumerate(T->Arguments)) { + auto &Argument = Group.value(); + uint64_t ArgPos = Group.index(); + + if (not Argument.CustomName.verify(VH)) + rc_return VH.fail(); + + if (Argument.Index != ArgPos) + rc_return VH.fail(); + + if (not rc_recur Argument.Type.verify(VH)) + rc_return VH.fail(); + + VoidConstResult VoidConst = isVoidConst(&Argument.Type); + if (VoidConst.IsVoid) { + // If we have a void argument it must be the only one, and the function + // cannot be vararg. + if (T->Arguments.size() > 1) + rc_return VH.fail(); + + // Cannot have const-qualified void as argument. + if (VoidConst.IsConst) + rc_return VH.fail(); + } + } + + rc_return true; +} + +static RecursiveCoroutine +verifyImpl(VerifyHelper &VH, const RawFunctionType *T) { + + for (const NamedTypedRegister &Argument : T->Arguments) + if (not rc_recur Argument.verify(VH)) + rc_return VH.fail(); + + for (const TypedRegister &Return : T->ReturnValues) + if (not rc_recur Return.verify(VH)) + rc_return VH.fail(); + + for (const Register::Values &Preserved : T->PreservedRegisters) + if (Preserved == Register::Invalid) + rc_return VH.fail(); + + rc_return VH.maybeFail(T->CustomName.verify(VH)); +} + +bool Type::verify() const { + return verify(false); +} + +bool Type::verify(bool Assert) const { + VerifyHelper VH(Assert); + return verify(VH); +} + +RecursiveCoroutine Type::verify(VerifyHelper &VH) const { + if (VH.isVerified(this)) + rc_return true; + + // Ensure we have not infinite recursion + if (VH.isVerificationInProgess(this)) + rc_return VH.fail(); + + VH.verificationInProgess(this); + + if (ID == 0) + rc_return VH.fail(); + + bool Result = false; + + // We could use upcast() but we'd need to workaround coroutines. + switch (Kind) { + case TypeKind::Primitive: + Result = rc_recur verifyImpl(VH, cast(this)); + break; + + case TypeKind::Enum: + Result = rc_recur verifyImpl(VH, cast(this)); + break; + + case TypeKind::Typedef: + Result = rc_recur verifyImpl(VH, cast(this)); + break; + + case TypeKind::Struct: + Result = rc_recur verifyImpl(VH, cast(this)); + break; + + case TypeKind::Union: + Result = rc_recur verifyImpl(VH, cast(this)); + break; + + case TypeKind::CABIFunctionType: + Result = rc_recur verifyImpl(VH, cast(this)); + break; + + case TypeKind::RawFunctionType: + Result = rc_recur verifyImpl(VH, cast(this)); + break; + + default: // Do nothing; + ; + } + + if (Result) + VH.setVerified(this); + + VH.verificationCompleted(this); + + rc_return VH.maybeFail(Result); +} + +bool QualifiedType::verify() const { + return verify(false); +} + +bool QualifiedType::verify(bool Assert) const { + VerifyHelper VH(Assert); + return verify(VH); +} + +RecursiveCoroutine QualifiedType::verify(VerifyHelper &VH) const { + if (not UnqualifiedType.isValid()) + rc_return VH.fail(); + + // Verify the qualifiers are valid + for (const auto &Q : Qualifiers) + if (not Q.verify(VH)) + rc_return VH.fail(); + + auto QIt = Qualifiers.begin(); + auto QEnd = Qualifiers.end(); + for (; QIt != QEnd; ++QIt) { + const auto &Q = *QIt; + auto NextQIt = std::next(QIt); + bool HasNext = NextQIt != QEnd; + + // Check that we have not two consecutive const qualifiers + if (HasNext and Q.isConstQualifier() and NextQIt->isConstQualifier()) + rc_return VH.fail(); + + if (Q.isPointerQualifier()) { + // Don't proceed the verification, just make sure the pointer is either + // 32- or 64-bits + rc_return VH.maybeFail(Q.Size == 4 or Q.Size == 8); + + } else if (Q.isArrayQualifier()) { + // Ensure there's at least one element + if (Q.Size <= 1) + rc_return VH.fail(); + + // Verify element type + QualifiedType ElementType{ UnqualifiedType, { NextQIt, QEnd } }; + if (not rc_recur ElementType.verify(VH)) + rc_return VH.fail(); + + // Ensure the element type has a size and stop + auto MaybeSize = rc_recur ElementType.size(VH); + rc_return VH.maybeFail(MaybeSize.has_value()); + } else if (Q.isConstQualifier()) { + // const qualifiers must have zero size + if (Q.Size != 0) + rc_return VH.fail(); + + } else { + revng_abort(); + } + } + + // If we get here, we either have no qualifiers or just const qualifiers: + // recur on the underlying type + rc_return VH.maybeFail(rc_recur UnqualifiedType.get()->verify(VH)); +} + +bool TypedRegister::verify() const { + return verify(false); +} + +bool TypedRegister::verify(bool Assert) const { + VerifyHelper VH(Assert); + return verify(VH); +} + +RecursiveCoroutine TypedRegister::verify(VerifyHelper &VH) const { + // Ensure the type we're pointing to is scalar + if (not isScalar(Type)) + rc_return VH.fail(); + + if (Location == Register::Invalid) + rc_return VH.fail(); + + // Ensure if fits in the corresponding register + auto MaybeTypeSize = rc_recur Type.size(VH); + + // Zero-sized types are not allowed + if (not MaybeTypeSize) + rc_return VH.fail(); + + size_t RegisterSize = model::Register::getSize(Location); + if (*MaybeTypeSize > RegisterSize) + rc_return VH.fail(); + + rc_return VH.maybeFail(rc_recur Type.verify(VH)); +} + +bool NamedTypedRegister::verify() const { + return verify(false); +} + +bool NamedTypedRegister::verify(bool Assert) const { + VerifyHelper VH(Assert); + return verify(VH); +} + +RecursiveCoroutine NamedTypedRegister::verify(VerifyHelper &VH) const { + const TypedRegister &TR = *this; + rc_return VH.maybeFail(CustomName.verify(VH) and rc_recur TR.verify(VH)); +} + +bool AggregateField::verify() const { + return verify(false); +} + +bool AggregateField::verify(bool Assert) const { + VerifyHelper VH(Assert); + return verify(VH); +} + +RecursiveCoroutine AggregateField::verify(VerifyHelper &VH) const { + rc_return VH.maybeFail(CustomName.verify(VH) and rc_recur Type.verify(VH)); +} + +bool Argument::verify() const { + return verify(false); +} + +bool Argument::verify(bool Assert) const { + VerifyHelper VH(Assert); + return verify(VH); +} + +RecursiveCoroutine Argument::verify(VerifyHelper &VH) const { + rc_return VH.maybeFail(CustomName.verify(VH) and rc_recur Type.verify(VH)); +} + +} // namespace model diff --git a/lib/StackAnalysis/StackAnalysis.cpp b/lib/StackAnalysis/StackAnalysis.cpp index 40d99751f..599ce785f 100644 --- a/lib/StackAnalysis/StackAnalysis.cpp +++ b/lib/StackAnalysis/StackAnalysis.cpp @@ -124,14 +124,13 @@ void commitToModel(GeneratedCodeBasicInfo &GCBI, model::Binary &TheBinary) { using namespace model; + // + // Create all the model::Function + // for (const auto &[Entry, FunctionSummary] : Summary.Functions) { if (Entry == nullptr) continue; - // - // Initialize model::Function - // - // Get the entry point address MetaAddress EntryPC = getBasicBlockPC(Entry); revng_assert(EntryPC.isValid()); @@ -141,8 +140,6 @@ void commitToModel(GeneratedCodeBasicInfo &GCBI, model::Function &Function = TheBinary.Functions[EntryPC]; // Assign a name - Function.Name = Entry->getName(); - revng_assert(Function.Name.size() != 0); using FT = model::FunctionType::Values; Function.Type = static_cast(FunctionSummary.Type); @@ -150,20 +147,54 @@ void commitToModel(GeneratedCodeBasicInfo &GCBI, if (Function.Type == model::FunctionType::Fake) continue; - // Populate arguments and return values + // Build the function prototype + auto NewType = makeType(); + auto &FunctionType = *llvm::cast(NewType.get()); { - auto Inserter = Function.Registers.batch_insert(); + auto ArgumentsInserter = FunctionType.Arguments.batch_insert(); + auto ReturnValuesInserter = FunctionType.ReturnValues.batch_insert(); for (auto &[CSV, FRD] : FunctionSummary.RegisterSlots) { - auto ID = ABIRegister::fromCSVName(CSV->getName(), GCBI.arch()); - if (ID == model::Register::Invalid) + auto RegisterID = ABIRegister::fromCSVName(CSV->getName(), GCBI.arch()); + if (RegisterID == Register::Invalid or CSV == GCBI.spReg()) continue; - FunctionABIRegister TheRegister(ID); - TheRegister.Argument = toRegisterState(FRD.Argument); - TheRegister.ReturnValue = toRegisterState(FRD.ReturnValue); - Inserter.insert(TheRegister); + + llvm::Type *CSVType = CSV->getType()->getPointerElementType(); + auto CSVSize = CSVType->getIntegerBitWidth() / 8; + NamedTypedRegister TR(RegisterID); + TR.Type = { + TheBinary.getPrimitiveType(PrimitiveTypeKind::Generic, CSVSize), {} + }; + + if (model::RegisterState::shouldEmit(toRegisterState(FRD.Argument))) + ArgumentsInserter.insert(TR); + + if (model::RegisterState::shouldEmit(toRegisterState(FRD.ReturnValue))) + ReturnValuesInserter.insert(TR); + + // TODO: populate preserved registers } } + Function.Prototype = TheBinary.recordNewType(std::move(NewType)); + } + + // + // Populate the CFG + // + for (const auto &[Entry, FunctionSummary] : Summary.Functions) { + if (Entry == nullptr) + continue; + MetaAddress EntryPC = getBasicBlockPC(Entry); + + auto It = TheBinary.Functions.find(EntryPC); + if (It == TheBinary.Functions.end()) + continue; + + model::Function &Function = *It; + + if (Function.Type == model::FunctionType::Fake) + continue; + auto MakeEdge = [](MetaAddress Destination, FunctionEdgeType::Values Type) { FunctionEdge *Result = nullptr; if (FunctionEdgeType::isCall(Type)) @@ -247,8 +278,6 @@ void commitToModel(GeneratedCodeBasicInfo &GCBI, if (EdgeType == FET::Invalid) continue; - bool IsCall = FunctionEdgeType::isCall(EdgeType); - // Identify Source address auto [Source, Size] = getPC(BB->getTerminator()); Source += Size; @@ -259,7 +288,6 @@ void commitToModel(GeneratedCodeBasicInfo &GCBI, MetaAddress JumpTargetAddress = GCBI.getPCFromNewPC(JumpTargetBB); model::BasicBlock &CurrentBlock = Function.CFG[JumpTargetAddress]; CurrentBlock.End = Source; - CurrentBlock.Name = JumpTargetBB->getName(); auto SuccessorsInserter = CurrentBlock.Successors.batch_insert(); if (EdgeType == FET::DirectBranch) { @@ -275,7 +303,7 @@ void commitToModel(GeneratedCodeBasicInfo &GCBI, for (const auto &[_, Destination] : make_range(First, Last)) SuccessorsInserter.insert(MakeEdge(Destination, EdgeType)); - } else if (IsCall) { + } else if (FunctionEdgeType::isCall(EdgeType)) { // Handle call llvm::BasicBlock *Successor = BB->getSingleSuccessor(); MetaAddress Destination = MetaAddress::invalid(); @@ -287,26 +315,57 @@ void commitToModel(GeneratedCodeBasicInfo &GCBI, const auto &Result = SuccessorsInserter.insert(TempEdge); auto *Edge = llvm::cast(Result.get()); - bool Found = false; - for (const FunctionsSummary::CallSiteDescription &CSD : - FunctionSummary.CallSites) { - if (not CSD.Call->isTerminator() or CSD.Call->getParent() != BB) - continue; + if (Destination.isValid()) { + // If it's a direct call, inherit the prototype from the callee + model::Function &Callee = TheBinary.Functions.at(Destination); + Edge->Prototype = Callee.Prototype; + } else { + // It's an indirect call: forge a new prototype + auto NewType = makeType(); + auto &CallType = *llvm::cast(NewType.get()); + { + auto ArgumentsInserter = CallType.Arguments.batch_insert(); + auto ReturnValuesInserter = CallType.ReturnValues.batch_insert(); + bool Found = false; + for (const FunctionsSummary::CallSiteDescription &CSD : + FunctionSummary.CallSites) { + if (not CSD.Call->isTerminator() or CSD.Call->getParent() != BB) + continue; - revng_assert(not Found); - Found = true; - auto Inserter = Edge->Registers.batch_insert(); - for (auto &[CSV, FCRD] : CSD.RegisterSlots) { - auto ID = ABIRegister::fromCSVName(CSV->getName(), GCBI.arch()); - if (ID == model::Register::Invalid) - continue; - FunctionABIRegister TheRegister(ID); - TheRegister.Argument = toRegisterState(FCRD.Argument); - TheRegister.ReturnValue = toRegisterState(FCRD.ReturnValue); - Inserter.insert(TheRegister); + revng_assert(not Found); + Found = true; + for (auto &[CSV, FCRD] : CSD.RegisterSlots) { + auto RegisterID = ABIRegister::fromCSVName(CSV->getName(), + GCBI.arch()); + if (RegisterID == model::Register::Invalid + or CSV == GCBI.spReg()) + continue; + + llvm::Type *CSVType = CSV->getType()->getPointerElementType(); + auto CSVSize = CSVType->getIntegerBitWidth() / 8; + NamedTypedRegister TR(RegisterID); + TR.Type = { + TheBinary.getPrimitiveType(model::PrimitiveTypeKind::Generic, + CSVSize), + {} + }; + + auto ArgumentState = toRegisterState(FCRD.Argument); + if (model::RegisterState::shouldEmit(ArgumentState)) + ArgumentsInserter.insert(TR); + + auto ReturnValueState = toRegisterState(FCRD.ReturnValue); + if (model::RegisterState::shouldEmit(ReturnValueState)) + ReturnValuesInserter.insert(TR); + + // TODO: populate preserved registers and FinalStackOffset + } + } + revng_assert(Found); } + + Edge->Prototype = TheBinary.recordNewType(std::move(NewType)); } - revng_assert(Found); } else { // Handle other successors @@ -321,7 +380,7 @@ void commitToModel(GeneratedCodeBasicInfo &GCBI, } } - revng_check(TheBinary.verify()); + revng_check(TheBinary.verify(true)); } bool StackAnalysis::runOnModule(Module &M) { diff --git a/tests/unit/Model.cpp b/tests/unit/Model.cpp index 2d7dd572a..89294cff7 100644 --- a/tests/unit/Model.cpp +++ b/tests/unit/Model.cpp @@ -24,7 +24,7 @@ BOOST_AUTO_TEST_CASE(TestIntrospection) { Function TheFunction(MetaAddress::invalid()); // Use get - TheFunction.Name = "FunctionName"; + TheFunction.CustomName = "FunctionName"; revng_check(get<1>(TheFunction) == "FunctionName"); // Test std::tuple_size @@ -33,9 +33,9 @@ BOOST_AUTO_TEST_CASE(TestIntrospection) { // Test TupleLikeTraits using TLT = TupleLikeTraits; static_assert(std::is_same_v, - decltype(TheFunction.Name)>); + decltype(TheFunction.CustomName)>); revng_check(StringRef(TLT::Name) == "model::Function"); - revng_check(StringRef(TLT::FieldsName[1]) == "Name"); + revng_check(StringRef(TLT::FieldsName[1]) == "CustomName"); } BOOST_AUTO_TEST_CASE(TestPathAccess) { @@ -88,7 +88,7 @@ BOOST_AUTO_TEST_CASE(TestStringPathConversion) { TupleTreePath InvalidFunctionNamePath = InvalidFunctionPath; InvalidFunctionNamePath.push_back(size_t(1)); - auto MaybePath = stringAsPath("/Functions/:Invalid/Name"); + auto MaybePath = stringAsPath("/Functions/:Invalid/CustomName"); revng_check(MaybePath.value() == InvalidFunctionNamePath); auto CheckRoundTrip = [](const char *String) { @@ -132,8 +132,8 @@ BOOST_AUTO_TEST_CASE(TestPathMatcher) { auto ARM1000EntryPath = Matcher.apply(ARM1000, ARM2000); auto ARM1000EntryPathAsString = pathAsString(ARM1000EntryPath); - auto ExpectedName = ("/Functions/0x1000:Code_arm/CFG/" - "0x2000:Code_arm/Start"); + const auto *ExpectedName = ("/Functions/0x1000:Code_arm/CFG/" + "0x2000:Code_arm/Start"); revng_check(ARM1000EntryPathAsString == ExpectedName); auto Match = Matcher.match(ARM1000EntryPath); @@ -189,7 +189,7 @@ BOOST_AUTO_TEST_CASE(TestTupleTreeReference) { TupleTree TheRoot; Element &AnElement = TheRoot->Elements[3]; - AnElement.Self = Reference::fromString("/Elements/3"); + AnElement.Self = Reference::fromString(TheRoot.get(), "/Elements/3"); TheRoot.initializeReferences(); diff --git a/tests/unit/ModelType.cpp b/tests/unit/ModelType.cpp new file mode 100644 index 000000000..c502f5432 --- /dev/null +++ b/tests/unit/ModelType.cpp @@ -0,0 +1,610 @@ +// +// 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/Binary.h" +#include "revng/Model/Type.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::outs() << "Serialized\n" << Buffer; + + auto Deserialized = TupleTree::deserialize(Buffer); + + std::string OtherBuffer; + Deserialized.serialize(OtherBuffer); + llvm::outs() << "Deserialized\n" << OtherBuffer; + + return Deserialized; +} + +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, 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 == 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 + Enum->UnderlyingType = Int32; + 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)); + + // Inserting an alias is ok + revng_check(Enum->Entries.at(0).Aliases.insert({ "value_0_alias" }).second); + revng_check(Enum->verify(true)); + revng_check(T->verify(true)); + revng_check(checkSerialization(T)); + + // Inserting an alias with the same name of the Name succeeds but is bad + revng_check(Enum->Entries.at(0).Aliases.insert(Identifier("value0")).second); + revng_check(not Enum->verify(false)); + revng_check(not T->verify(false)); + + // But if we remove it we're good again. + revng_check(Enum->Entries.at(0).Aliases.erase(Identifier("value0"))); + revng_check(Enum->verify(true)); + revng_check(T->verify(true)); + revng_check(checkSerialization(T)); + + // Inserting an empty-name alias succeeds but is bad + revng_check(Enum->Entries.at(0).Aliases.insert(Identifier("")).second); + revng_check(not Enum->verify(false)); + revng_check(not T->verify(false)); + + // But if we remove it we're good again. + revng_check(Enum->Entries.at(0).Aliases.erase(Identifier(""))); + 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 + Enum->UnderlyingType = TypePath::fromString(T.get(), "/Types/Typedef-42"); + 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 + Enum->UnderlyingType = T->getTypePath(Enum); + revng_check(not Enum->verify(false)); + revng_check(not T->verify(false)); + + // If we put back the proper underlying type it verifies. + Enum->UnderlyingType = Int32; + 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 invalid + Struct->Fields.clear(); + revng_check(not Struct->verify(false)); + revng_check(not 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()); + 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, { { model::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)); +} diff --git a/tests/unit/UnitTests.cmake b/tests/unit/UnitTests.cmake index 01fc8fe5f..bacb2b967 100644 --- a/tests/unit/UnitTests.cmake +++ b/tests/unit/UnitTests.cmake @@ -244,6 +244,7 @@ target_include_directories(test_model target_link_libraries(test_model revngSupport revngUnitTestHelpers + revngModel Boost::unit_test_framework ${LLVM_LIBRARIES}) add_test(NAME test_model COMMAND ./bin/test_model) @@ -309,3 +310,21 @@ target_compile_definitions(test_recursive_coroutines_fallback PRIVATE DISABLE_RE add_recursive_coroutine_test(test_recursive_coroutines_iterative) target_compile_definitions(test_recursive_coroutines_iterative PRIVATE ITERATIVE) + +# +# test_model_type +# + +revng_add_private_executable(test_model_type "${SRC}/ModelType.cpp") +target_compile_definitions(test_model_type + PRIVATE "BOOST_TEST_DYN_LINK=1") +target_include_directories(test_model_type + PRIVATE "${CMAKE_SOURCE_DIR}") +target_link_libraries(test_model_type + revngSupport + revngUnitTestHelpers + revngModel + Boost::unit_test_framework + ${LLVM_LIBRARIES}) +add_test(NAME test_model_type COMMAND ./bin/test_model_type) +set_tests_properties(test_model_type PROPERTIES LABELS "unit")