Files
go-winrt/internal/winmd/typedef.go
2025-08-22 17:42:23 -04:00

311 lines
9.5 KiB
Go

package winmd
import (
"fmt"
"strconv"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/tdakkota/win32metadata/md"
"github.com/tdakkota/win32metadata/types"
)
// TypeDef is a helper struct that wraps types.TypeDef and stores the original context
// of the typeDef.
type TypeDef struct {
types.TypeDef
HasContext
logger log.Logger
}
// QualifiedID holds the namespace and the name of a qualified element. This may be a type, a static function or a field
type QualifiedID struct {
Namespace string
Name string
}
// GetValueForEnumField returns the value of the requested enum field.
func (typeDef *TypeDef) GetValueForEnumField(fieldIndex uint32) (string, error) {
// For each Enum value definition, there is a corresponding row in the Constant table to store the integer value for the enum value.
tableConstants := typeDef.Ctx().Table(md.Constant)
for i := uint32(0); i < tableConstants.RowCount(); i++ {
var constant types.Constant
if err := constant.FromRow(tableConstants.Row(i)); err != nil {
return "", err
}
if t, _ := constant.Parent.Table(); t != md.Field {
continue
}
// does the blob belong to the field we're looking for?
// The parent is an index into the field table that holds the associated enum value record
if constant.Parent.TableIndex() != fieldIndex {
continue
}
// The value is a blob that we need to read as little endian
var blobIndex uint32
for i, b := range constant.Value {
blobIndex += uint32(b) << (i * 8)
}
return strconv.Itoa(int(blobIndex)), nil
}
return "", fmt.Errorf("no value found for field %d", fieldIndex)
}
// GetAttributeWithType returns the value of the given attribute type and fails if not found.
func (typeDef *TypeDef) GetAttributeWithType(lookupAttrTypeClass string) ([]byte, error) {
result := typeDef.GetTypeDefAttributesWithType(lookupAttrTypeClass)
if len(result) == 0 {
return nil, fmt.Errorf("type %s has no custom attribute %s", typeDef.TypeNamespace+"."+typeDef.TypeName, lookupAttrTypeClass)
} else if len(result) > 1 {
_ = level.Warn(typeDef.logger).Log(
"msg", "type has multiple custom attributes, returning the first one",
"type", typeDef.TypeNamespace+"."+typeDef.TypeName,
"attr", lookupAttrTypeClass,
)
}
return result[0], nil
}
// GetTypeDefAttributesWithType returns the values of all the attributes that match the given type.
func (typeDef *TypeDef) GetTypeDefAttributesWithType(lookupAttrTypeClass string) [][]byte {
result := make([][]byte, 0)
cAttrTable := typeDef.Ctx().Table(md.CustomAttribute)
for i := uint32(0); i < cAttrTable.RowCount(); i++ {
var cAttr types.CustomAttribute
if err := cAttr.FromRow(cAttrTable.Row(i)); err != nil {
continue
}
// - Parent: The owner of the Attribute must be the given typeDef
if cAttrParentTable, _ := cAttr.Parent.Table(); cAttrParentTable != md.TypeDef {
continue
}
var parentTypeDef TypeDef
row, ok := cAttr.Parent.Row(typeDef.Ctx())
if !ok {
continue
}
if err := parentTypeDef.FromRow(row); err != nil {
continue
}
// does the blob belong to the type we're looking for?
if parentTypeDef.TypeNamespace != typeDef.TypeNamespace || parentTypeDef.TypeName != typeDef.TypeName {
continue
}
// - Type: the attribute type must be the given type
// the cAttr.Type table can be either a MemberRef or a MethodRef.
// Since we are looking for a type, we will only consider the MemberRef.
if cAttrTypeTable, _ := cAttr.Type.Table(); cAttrTypeTable != md.MemberRef {
continue
}
var attrTypeMemberRef types.MemberRef
row, ok = cAttr.Type.Row(typeDef.Ctx())
if !ok {
continue
}
if err := attrTypeMemberRef.FromRow(row); err != nil {
continue
}
// we need to check the MemberRef Class
// the value can belong to several tables, but we are only going to check for TypeRef
if classTable, _ := attrTypeMemberRef.Class.Table(); classTable != md.TypeRef {
continue
}
var attrTypeRef types.TypeRef
row, ok = attrTypeMemberRef.Class.Row(typeDef.Ctx())
if !ok {
continue
}
if err := attrTypeRef.FromRow(row); err != nil {
continue
}
if attrTypeRef.TypeNamespace+"."+attrTypeRef.TypeName == lookupAttrTypeClass {
result = append(result, cAttr.Value)
}
}
return result
}
// GetImplementedInterfaces returns the interfaces implemented by the type.
func (typeDef *TypeDef) GetImplementedInterfaces() ([]QualifiedID, error) {
interfaces := make([]QualifiedID, 0)
tableInterfaceImpl := typeDef.Ctx().Table(md.InterfaceImpl)
for i := uint32(0); i < tableInterfaceImpl.RowCount(); i++ {
var interfaceImpl types.InterfaceImpl
if err := interfaceImpl.FromRow(tableInterfaceImpl.Row(i)); err != nil {
return nil, err
}
classTd, err := interfaceImpl.ResolveClass(typeDef.Ctx())
if err != nil {
return nil, err
}
if classTd.TypeNamespace+"."+classTd.TypeName != typeDef.TypeNamespace+"."+typeDef.TypeName {
// not the class we are looking for
continue
}
if t, ok := interfaceImpl.Interface.Table(); ok && t == md.TypeSpec {
// ignore type spec rows
continue
}
ifaceNS, ifaceName, err := typeDef.Ctx().ResolveTypeDefOrRefName(interfaceImpl.Interface)
if err != nil {
return nil, err
}
interfaces = append(interfaces, QualifiedID{Namespace: ifaceNS, Name: ifaceName})
}
return interfaces, nil
}
// Extends returns true if the type extends the given class
func (typeDef *TypeDef) Extends(class string) (bool, error) {
ns, name, err := typeDef.Ctx().ResolveTypeDefOrRefName(typeDef.TypeDef.Extends)
if err != nil {
return false, err
}
return ns+"."+name == class, nil
}
// GetGenericParams returns the generic parameters of the type.
func (typeDef *TypeDef) GetGenericParams() ([]*types.GenericParam, error) {
params := make([]*types.GenericParam, 0)
tableGenericParam := typeDef.Ctx().Table(md.GenericParam)
for i := uint32(0); i < tableGenericParam.RowCount(); i++ {
var genericParam types.GenericParam
if err := genericParam.FromRow(tableGenericParam.Row(i)); err != nil {
continue
}
// - Owner: The owner of the Attribute must be the given typeDef
if genericParamOwnerTable, _ := genericParam.Owner.Table(); genericParamOwnerTable != md.TypeDef {
continue
}
var ownerTypeDef types.TypeDef
row, ok := genericParam.Owner.Row(typeDef.Ctx())
if !ok {
continue
}
if err := ownerTypeDef.FromRow(row); err != nil {
continue
}
// does the blob belong to the type we're looking for?
if ownerTypeDef.TypeNamespace != typeDef.TypeNamespace || ownerTypeDef.TypeName != typeDef.TypeName {
continue
}
params = append(params, &genericParam)
}
if len(params) == 0 {
return nil, fmt.Errorf("could not find generic params for type %s.%s", typeDef.TypeNamespace, typeDef.TypeName)
}
return params, nil
}
// IsInterface returns true if the type is an interface
func (typeDef *TypeDef) IsInterface() bool {
return typeDef.Flags.Interface()
}
// IsEnum returns true if the type is an enum
func (typeDef *TypeDef) IsEnum() bool {
ok, err := typeDef.Extends("System.Enum")
if err != nil {
_ = level.Error(typeDef.logger).Log("msg", "error resolving type extends, all classes should extend at least System.Object", "err", err)
return false
}
return ok
}
// IsDelegate returns true if the type is a delegate
func (typeDef *TypeDef) IsDelegate() bool {
if !(typeDef.Flags.Public() && typeDef.Flags.Sealed()) {
return false
}
ok, err := typeDef.Extends("System.MulticastDelegate")
if err != nil {
_ = level.Error(typeDef.logger).Log("msg", "error resolving type extends, all classes should extend at least System.Object", "err", err)
return false
}
return ok
}
// IsStruct returns true if the type is a struct
func (typeDef *TypeDef) IsStruct() bool {
ok, err := typeDef.Extends("System.ValueType")
if err != nil {
_ = level.Error(typeDef.logger).Log("msg", "error resolving type extends, all classes should extend at least System.Object", "err", err)
return false
}
return ok
}
// IsRuntimeClass returns true if the type is a runtime class
func (typeDef *TypeDef) IsRuntimeClass() bool {
// Flags: all runtime classes must carry the public, auto layout, class, and tdWindowsRuntime flags.
return typeDef.Flags.Public() && typeDef.Flags.AutoLayout() && typeDef.Flags.Class() && typeDef.Flags&0x4000 != 0
}
// GUID returns the GUID of the type.
func (typeDef *TypeDef) GUID() (string, error) {
blob, err := typeDef.GetAttributeWithType(AttributeTypeGUID)
if err != nil {
return "", err
}
return guidBlobToString(blob)
}
// guidBlobToString converts an array into the textual representation of a GUID
func guidBlobToString(b types.Blob) (string, error) {
// the guid is a blob of 20 bytes
if len(b) != 20 {
return "", fmt.Errorf("invalid GUID blob length: %d", len(b))
}
// that starts with 0100
if b[0] != 0x01 || b[1] != 0x00 {
return "", fmt.Errorf("invalid GUID blob header, expected '0x01 0x00' but found '0x%02x 0x%02x'", b[0], b[1])
}
// and ends with 0000
if b[18] != 0x00 || b[19] != 0x00 {
return "", fmt.Errorf("invalid GUID blob footer, expected '0x00 0x00' but found '0x%02x 0x%02x'", b[18], b[19])
}
guid := b[2 : len(b)-2]
// the string version has 5 parts separated by '-'
return fmt.Sprintf("%08x-%04x-%04x-%04x-%04x%08x",
// The first 3 are encoded as little endian
uint32(guid[0])|uint32(guid[1])<<8|uint32(guid[2])<<16|uint32(guid[3])<<24,
uint16(guid[4])|uint16(guid[5])<<8,
uint16(guid[6])|uint16(guid[7])<<8,
//the rest is not
uint16(guid[8])<<8|uint16(guid[9]),
uint16(guid[10])<<8|uint16(guid[11]),
uint32(guid[12])<<24|uint32(guid[13])<<16|uint32(guid[14])<<8|uint32(guid[15])), nil
}