Files
revng-revng/tests/unit/ModelType.cpp
T
Pietro Fezzardi df30792435 PrimitiveType: support for 80 and 96 bits types
This commit completes the support of various rare PrimitiveTypes, that
has been added opportunistically over time and has remained inconsistent
across the codebase:

- float80_t
- float96_t
- generic80_t
- generic96_t

The first two are necessary because on some platforms long double is
either 10 or 12 bytes wide.

The second two are necessary because the Generic PrimitiveType should
allow all non-zero byte sizes allowed by any other PrimitiveType.
2022-11-30 15:06:54 +01:00

632 lines
22 KiB
C++

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