preface
As you can see from the previous article, Struct type is essentially StructMetadata through Mirror parsing.
Complete simulation with SWIFT codeStructMetadata
We come up first hi, to raise the high interest, read the source code behind a bit boring.
I have uploaded the translated source code to GitHub, and you can see the results directly below
Inside the Teacher class can be replaced by their own class, can also increase the replacement attribute, inside the comments have written in detail.
Description
There is something about Kind, which was described in detail in the last article and won’t be repeated here, but let’s go back to defining Description earlier.
/// An out-of-line description of the type.
TargetSignedPointer<Runtime, const TargetValueTypeDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;
template <typename Runtime, typename T>
using TargetSignedPointer = typename Runtime::template SignedPointer<T>;
Copy the code
We see TargetValueTypeDescriptor to modify the Description, but it opens at TargetSignedPointer definition found is a SignedPointer < T > pointer, so specific content or T depends on the pattern, T here is incoming TargetValueTypeDescriptor. Note that Description is defined in the TargetStructMetadata subclass TargetValueMetadata, so let’s go back to the declaration of the parent TargetStructMetadata class:
const TargetStructDescriptor<Runtime> *getDescription(a) const {
return llvm::cast<TargetStructDescriptor<Runtime>>(this->Description);
}
Copy the code
So in the getDescription() method that’s called externally, it converts the Description T to TargetStructDescriptor, so the real T is TargetStructDescriptor, So let’s hit the definition of TargetStructDescriptor:
template <typename Runtime>
class TargetStructDescriptor final
: public TargetValueTypeDescriptor<Runtime>,
public TrailingGenericContextObjects<TargetStructDescriptor<Runtime>,
TargetTypeGenericContextDescriptorHeader,
/*additional trailing objects*/
TargetForeignMetadataInitialization<Runtime>,
TargetSingletonMetadataInitialization<Runtime>> {
...
uint32_t NumFields;
uint32_tFieldOffsetVectorOffset; . }Copy the code
Two attributes appear here:
NumFields
: Number of attributes in a structureFieldOffsetVectorOffset
If we print the memory of the struct instance, we will find that the values of the properties are stored in the memory in their own way, so if the program needs to value, how to locate the value quickly? There is actually a memory that records the starting position of each attribute, and it stores the starting position of each attribute in a 4-byte array, so where is that memory? This memory is actually right next to each otherStructMetadata
And then, of course, this is just what I measured. The right thing to do is to takeFieldOffsetVectorOffset
, this value is the value that records the existence of the segmentStructMetadata
The offset back of the first address. I printed it as 2, which is the length of 2 Pointers, which is right next to each otherStructMetadata
Behind.
The Description is not yet complete, did we have to see the fruits of the parent class attribute, here find multiple inheritance, us there is no attribute points to open TrailingGenericContextObjects (behind the class a bit deep, as I conversion source), So we focus on TargetValueTypeDescriptor (around a circle back again).
TargetValueTypeDescriptor
We see TargetValueTypeDescriptor code:
template <typename Runtime>
class TargetValueTypeDescriptor
: public TargetTypeContextDescriptor<Runtime> {
public:
static bool classof(const TargetContextDescriptor<Runtime> *cd) {
return cd->getKind() == ContextDescriptorKind::Struct ||
cd->getKind() == ContextDescriptorKind::Enum; }};Copy the code
I can’t see anything valuable, to continue to see a parent class: TargetTypeContextDescriptor
template <typename Runtime>
class TargetTypeContextDescriptor
: public TargetContextDescriptor<Runtime> {
public:
TargetRelativeDirectPointer<Runtime, const char./*nullable*/ false> Name;
TargetRelativeDirectPointer<Runtime, MetadataResponse(...). ./*Nullable*/ true> AccessFunctionPtr;
TargetRelativeDirectPointer<Runtime, const reflection::FieldDescriptor,
/*nullable*/ true> Fields; . };Copy the code
Find 3 properties: Name, AccessFunctionPtr, Fields:
Name
: Structure nameAccessFunctionPtr
: The function type here is a proxy. You need to call getAccessFunction() to get the real function pointer. You get a wrapper class for the MetadataAccessFunction MetadataAccessFunction pointer, which provides an overload of operator() to call it using the correct calling conventionFields
: a pointer to the field descriptor of the type, if any. A description of a type field from which you can obtain the properties of a structure.
Behind these properties are TargetRelativeDirectPointer modification, this alone speak this class, we did see a parent class attribute:
struct TargetContextDescriptor {
/// Flags describing the context, including its kind and format version.
ContextDescriptorFlags Flags;
/// The parent context, or null if this is a top-level context.TargetRelativeContextPointer<Runtime> Parent; . };Copy the code
There are two attributes found here:
Flags
: The first common tag stored in any context descriptor. It opens atContextDescriptorFlags
And discovered that he was essentially auint32_t
Value, which then provides methods to use bitwise operations to return many tokens, such as type, version number, and so on.Parent
: Parent context, or null if it is a top-level context. The object is of type InProcess and should contain a pointer. When measured, the object is null.TargetRelativeContextPointer
In principle, andTargetRelativeDirectPointer
Almost. LookTargetRelativeDirectPointer
Will do.
At this point, there are no more superclasses, so we can calculate a total of seven attributes that can be converted to Swift code like this:
struct TargetStructDescriptor {
// The first common tag stored in any context descriptor
var Flags: ContextDescriptorFlags
// Reuse the RelativeDirectPointer type. It's not, but it looks the same
// Parent context, or null if it is a top-level context. The object is of type InProcess and should contain a pointer. When measured, the object is null
var Parent: RelativeDirectPointer<InProcess>
// Get the Struct name
var Name: RelativeDirectPointer<CChar>
Call getAccessFunction() to get the real function pointer (there is no wrapper). You get a wrapper class for the MetadataAccessFunction pointer. This function provides an overloading of operator() to call it with the correct calling convention (variable length arguments), and accidentally named renormalization calls this method (I don't know much about this yet).
var AccessFunctionPtr: RelativeDirectPointer<UnsafeRawPointer>
// A pointer to the field descriptor of the type (if any). A description of a type field from which you can obtain the properties of a structure.
var Fields: RelativeDirectPointer<FieldDescriptor>
// The number of struct attributes
var NumFields: Int32
// The offset of the field offset vector that stores the structure. If the offset is 0, you have no attributes
var FieldOffsetVectorOffset: Int32
}
Copy the code
TargetRelativeDirectPointer
The above TargetRelativeDirectPointer appeared many times, what is this? Let’s look at the definition:
using TargetRelativeDirectPointer
= typename Runtime::template RelativeDirectPointer<Pointee, Nullable>;
template <typename T, bool Nullable = true.typename Offset = int32_t.typename = void>
class RelativeDirectPointer;
/// A direct relative reference to an object that is not a function pointer.
template <typename T, bool Nullable, typename Offset>
class RelativeDirectPointer<T, Nullable, Offset,
typenamestd::enable_if<! std::is_function<T>::value>::type> :private RelativeDirectPointerImpl<T, Nullable, Offset>
{
using super = RelativeDirectPointerImpl<T, Nullable, Offset>;
public:
using super::get;
using super::super;
RelativeDirectPointer &operator=(T *absolute) & {
super::operator=(absolute);
return *this;
}
operator typename super::PointerTy(a) const & {
return this->get(a); }const typename super::ValueTy *operator- > ()const & {
return this->get(a); }using super::isNull;
};
Copy the code
An alias, and saw nothing particularly valuable in RelativeDirectPointer, there are several operation call the get () method, so we have to see whether the parent class RelativeDirectPointerImpl:
template<typename T, bool Nullable, typename Offset>
class RelativeDirectPointerImpl {
private: Offset RelativeOffset; .public:...using ValueTy = T;
usingPointerTy = T*; .PointerTy get(a) const & {
// Check for null.
if (Nullable && RelativeOffset == 0)
return nullptr;
// The value is addressed relative to `this`.
uintptr_t absolute = detail::applyRelativeOffset(this, RelativeOffset);
return reinterpret_cast<PointerTy>(absolute); }... };Copy the code
I put the key code copy come over, the whole RelativeDirectPointerImpl is an attribute RelativeOffset, so what is the Offset, we see the Offset is a template properties, from a subclass is int32_t.
So ValueTy refers to the value of the stereotype itself, and PointerTy is the address of the pointer to the value of the stereotype.
Get () returns a PointerTy pointer. If you get the PointerTy pointer address, you can easily get the ValueTy template value. So we are clear, the function of the class is to get a pass into the value of the paradigm, for example, such as the above TargetRelativeDirectPointer < Runtime, const char, nullable / * * / false > Name; We can get a pointer to char * by calling name.get ().
So how do we get the address of the pointer? Key to call for the detail: : applyRelativeOffset (this, RelativeOffset); Method, let’s click on it and see how he does it:
template<typename BasePtrTy, typename Offset>
static inline uintptr_t applyRelativeOffset(BasePtrTy *basePtr, Offset offset) {
static_assert(std::is_integral<Offset>::value &&
std::is_signed<Offset>::value,
"offset type should be signed integer");
auto base = reinterpret_cast<uintptr_t>(basePtr);
auto extendOffset = (uintptr_t) (intptr_t)offset;
return base + extendOffset;
}
Copy the code
Obviously, by adding up the values we just passed in, we get the address of the last paradigm. Just incoming is this itself and RelativeOffset value, so clearly, plus RelativeOffset RelativeDirectPointerImpl place address offsets, can get paradigm’s address. A bit like a file directory, using the relative path of the current path to get the absolute path.
Finally, let’s look at the Swift wrapper with a RelativeDirectPointer:
// This type is used to get the real address from the offset value of the current address, sort of like a file directory, using the relative path of the current path to get the absolute path.
struct RelativeDirectPointer<T> {
var offset: Int32 // Store the offset from the current address
// Get the real address from the relative offset of the address
mutating func get(a) -> UnsafeMutablePointer<T> {
let offset = self.offset
return withUnsafeMutablePointer(to: &self) {
return UnsafeMutableRawPointer($0).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self)}}}Copy the code
By this point, you can already get the structure’s name if you wrap it yourself. But there’s one more generic FieldDescriptor that we didn’t talk about, that’s the one that decorates Fields, that’s the one that describes properties, and once we’ve done that, we’re done parsing structure metadata.
FieldDescriptor
So let’s click on the definition of FieldDescriptor:
class FieldDescriptor {
const FieldRecord *getFieldRecordBuffer(a) const {
return reinterpret_cast<const FieldRecord *>(this + 1);
}
public:
const RelativeDirectPointer<const char> MangledTypeName;
const RelativeDirectPointer<const char> Superclass;
const FieldDescriptorKind Kind;
const uint16_t FieldRecordSize;
const uint32_tNumFields; .llvm::ArrayRef<FieldRecord> getFields(a) const {
return {getFieldRecordBuffer(), NumFields}; }... }Copy the code
The FieldDescriptor doesn’t have a parent class, so that’s the five properties we see:
MangledTypeName
: Type naming renormalization, with this thing, can support usSwift
Method overloadingSuperclass
: see if the name is the parent, no test = =Kind
: an enumeration,FieldDescriptorKind
Size of theuint16_t
, take a look at the source definition:
enum class FieldDescriptorKind : uint16_t {
Struct,
Class,
Enum,
MultiPayloadEnum,
Protocol,
ClassProtocol,
ObjCProtocol,
ObjCClass
};
Copy the code
FieldRecordSize
Multiply NumFields by RecordSize to get RecordSize.NumFields
: Number of attributes, this and the one aboveNumFields
The same.
Looking at the properties above, I don’t see any properties that support getting the name of the property. But we see that there is a getFields() method to get the name of the property (how do I know that? After calling this method, you get an array of FieldRecord objects. FieldRecord is an array of FieldRecord objects that you can use to find FieldRecord objects.
We can see that the getFieldRecordBuffer() method is called in getFields(), which returns a pointer to FieldRecord *, so the starting position of the FieldRecord object array is the FieldRecord * pointer. How do I get the pointer to FieldRecord *? We can see that in the getFieldRecordBuffer() method we just force (this +1), which is to take the size of the FieldDescriptor as step size +1, in other words, the pointer address is next to the content of the FieldDescriptor.
That’s it, let’s see how do we translate it into Swift code
struct FieldDescriptor {
enum FieldDescriptorKind: UInt16 {
case Struct
case Class
case Enum
// Fixed-size multi-payload enums have a special descriptor format that encodes spare bits.
case MultiPayloadEnum
// A Swift opaque protocol. There are no fields, just a record for the type itself.
case kProtocol
// A Swift class-bound protocol.
case ClassProtocol
// An Objective-C protocol, which may be imported or defined in Swift.
case ObjCProtocol
// An Objective-C class, which may be imported or defined in Swift.
// In the former case, field type metadata is not emitted, and must be obtained from the Objective-C runtime.
case ObjCClass
}
var MangledTypeName: RelativeDirectPointer<CChar>// Type naming renormalization
var Superclass: RelativeDirectPointer<CChar>/ / parent class name
var Kind: FieldDescriptorKind// Type, look at enumeration
var FieldRecordSize: Int16 // Multiply this value by NumFields to get RecordSize
var NumFields: Int32// The number of attributes
// Get each attribute to get FieldRecord
mutating func getField(index: Int) -> UnsafeMutablePointer<FieldRecord> {
return withUnsafeMutablePointer(to: &self) {
let arrayPtr = UnsafeMutableRawPointer($0.advanced(by: 1)).assumingMemoryBound(to: FieldRecord.self)
return arrayPtr.advanced(by: index)
}
}
}
struct FieldRecord {
struct FieldRecordFlags {
var Data: UInt32
/// Is this an indirect enum case?
func isIndirectCase(a) -> Bool {
return (Data & 0x1) = = 0x1;
}
/// Is this a mutable `var` property?
func isVar(a) -> Bool {
return (Data & 0x2) = = 0x2; }}var Flags: FieldRecordFlags / / tag
var MangledTypeName: RelativeDirectPointer<CChar>// Type naming renormalization
var FieldName: RelativeDirectPointer<CChar>/ / the property name
}
Copy the code
conclusion
To this point, we StructMetadata content basically finished analysis, if you want to complete the translation code, see the beginning of the article is. Of course, this is only StructMetadata attributes of the face, there are a lot of details, that is, details, but the point open, found endless…
I also translated some about StructMetadata source code, but because do not know what is going to do, so analysis is very tired, if a little feel friends may have a look, the content is for reference only, because I have a little knowledge of (a little feeling, but also almost, may see other modules, later can’t say for certain inspiration triggered)
The content is placed in this file in StructMetadataExtension, which is included in the GitHub project above