[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/pythonnet/pythonnet/v3.2.0-rc2/src/runtime/TypeManager.cs [Back]  [Original]

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Diagnostics;

using Python.Runtime.Native;
using Python.Runtime.StateSerialization;


namespace Python.Runtime
{

    /// 
    /// The TypeManager class is responsible for building binary-compatible
    /// Python type objects that are implemented in managed code.
    /// 
    internal class TypeManager
    {
        internal static IntPtr subtype_traverse;
        internal static IntPtr subtype_clear;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
        /// initialized in  rather than in constructor
        internal static IPythonBaseTypeProvider pythonBaseTypeProvider;
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.


        private const BindingFlags tbFlags = BindingFlags.Public | BindingFlags.Static;
        // Multi-step creation in GetType is serialised via _cacheCreateLock.
        internal static readonly ConcurrentDictionary cache = new();
        internal static readonly object _cacheCreateLock = new();

        // Concurrent: documents the multi-writer contract previously enforced
        // by callers happening to hold _cacheCreateLock.
        static readonly ConcurrentDictionary _slotsHolders = new(PythonReferenceComparer.Instance);

        // Slots which must be set
        private static readonly string[] _requiredSlots = new string[]
        {
            "tp_traverse",
            "tp_clear",
        };

        static readonly DynamicObjectMemberAccessor dynamicMemberAccessor = new();

        // tp_getattro_dlr_proxy / tp_setattro_dlr_proxy hit HasClrMember on every
        // attribute access; cache the reflection result per (Type, name).
        static readonly ConcurrentDictionary _hasClrMemberCache = new();

        static bool HasClrMember(object instance, string memberName) =>
            _hasClrMemberCache.GetOrAdd(
                (instance.GetType(), memberName),
                k => k.Item1.GetMember(k.Item2, BindingFlags.Public | BindingFlags.Instance).Length > 0);

        static bool IsPythonSpecialAttributeName(string memberName) =>
            memberName.Length > 4 && memberName.StartsWith("__") && memberName.EndsWith("__");

        static bool TryGetDynamicInstance(BorrowedReference ob, out object instance, out IDynamicMetaObjectProvider dynamicObject)
        {
            if (ManagedType.GetManagedObject(ob) is CLRObject co && co.inst is IDynamicMetaObjectProvider coDynamic)
            {
                instance = co.inst;
                dynamicObject = coDynamic;
                return true;
            }

            if (Converter.ToManaged(ob, typeof(IDynamicMetaObjectProvider), out object? managedDynamic, false)
                && managedDynamic is IDynamicMetaObjectProvider convertedDynamic)
            {
                instance = managedDynamic;
                dynamicObject = convertedDynamic;
                return true;
            }

            if (Converter.ToManaged(ob, typeof(object), out object? managedInstance, false)
                && managedInstance is IDynamicMetaObjectProvider boxedDynamic)
            {
                instance = managedInstance;
                dynamicObject = boxedDynamic;
                return true;
            }

            instance = null!;
            dynamicObject = null!;
            return false;
        }

        public static NewReference tp_getattro_dlr_proxy(BorrowedReference ob, BorrowedReference key)
        {
            var isDynamic = TryGetDynamicInstance(ob, out object instance, out IDynamicMetaObjectProvider dynamicObject);

            // The whole DLR machinery only makes sense with string keys and dynamic objects
            if (!isDynamic || !Runtime.PyString_Check(key))
            {
                return Runtime.PyObject_GenericGetAttr(ob, key);
            }

            string memberName = Runtime.GetManagedString(key)!;

            // Forward requests to GetDynamicMemberNames to the mixin implementation
            if (memberName == nameof(DynamicObjectMemberAccessor.GetDynamicMemberNames)
                && !HasClrMember(instance, memberName))
            {
                using var pyMemberNames = new Func(
                    () => dynamicMemberAccessor.GetDynamicMemberNames(dynamicObject)
                ).ToPython();
                return pyMemberNames.NewReferenceOrNull();
            }

            // Now, first try to access the Python attribute
            var attr = Runtime.PyObject_GenericGetAttr(ob, key);
            if (!attr.IsNull())
                return attr;

            // attr is null, so an exception must be set. If that exception is not an AttributeError,
            // we return from this function immediately without clearing. All later returns until the
            // very end will lead to the AttributeError getting raised.
            if (Runtime.PyErr_ExceptionMatches(Exceptions.AttributeError) == 0)
            {
                return default;
            }

            if (HasClrMember(instance, memberName) || IsPythonSpecialAttributeName(memberName))
            {
                return default;
            }

            bool resolved = false;
            object? value = null;
            try
            {
                resolved = dynamicMemberAccessor.TryGetMember(dynamicObject, memberName, out value);
            }
            catch (Exception e)
            {
                // Avoid wrapping the CLR exception via Converter.ToPython here: that would trigger
                // CLR type initialisation which can re-enter this slot on the same live object,
                // causing infinite recursion. A plain RuntimeError with the message is safe.
                Runtime.PyErr_Clear();
                Exceptions.SetError(Exceptions.RuntimeError, e.Message);
                return default;
            }

            if (!resolved)
            {
                return default;
            }

            Runtime.PyErr_Clear();

            using var pyValue = value.ToPython();
            return pyValue.NewReferenceOrNull();
        }

        public static int tp_setattro_dlr_proxy(BorrowedReference ob, BorrowedReference key, BorrowedReference val)
        {
            var isDynamic = TryGetDynamicInstance(ob, out object instance, out IDynamicMetaObjectProvider dynamicObject);

            // The whole DLR machinery only makes sense with string keys and dynamic objects
            if (!isDynamic || !Runtime.PyString_Check(key))
            {
                return Runtime.PyObject_GenericSetAttr(ob, key, val);
            }

            string memberName = Runtime.GetManagedString(key)!;

            // For Python-derived types (IPythonDerivedType), the Python descriptor protocol
            // (e.g. @property setters) takes priority over DLR member storage.
            if (instance is IPythonDerivedType)
            {
                int pyResult = Runtime.PyObject_GenericSetAttr(ob, key, val);
                if (pyResult == 0)
                    return 0;

                if (Runtime.PyErr_ExceptionMatches(Exceptions.AttributeError) == 0)
                    return pyResult;

                Runtime.PyErr_Clear();
                // Fall through to DLR fallback below
            }

            if (!HasClrMember(instance, memberName) && !IsPythonSpecialAttributeName(memberName))
            {
                // Try DLR member storage first
                bool handled;

                try
                {
                    if (val.IsNull)
                    {
                        handled = dynamicMemberAccessor.TryDeleteMember(dynamicObject, memberName);
                    }
                    else
                    {
                        object? managedValue = null;
                        if (val != Runtime.PyNone && !Converter.ToManaged(val, typeof(object), out managedValue, true))
                            return -1;

                        handled = dynamicMemberAccessor.TrySetMember(dynamicObject, memberName, managedValue);
                        if (!handled)
                        {
                            Exceptions.SetError(Exceptions.AttributeError, $"'{instance.GetType().Name}' object has no attribute '{memberName}'");
                            return -1;
                        }
                    }
                }
                catch (Exception e)
                {
                    // Same reasoning as the getter: avoid Converter.ToPython(e) to keep this
                    // slot re-entry-safe on live dynamic objects.
                    Exceptions.SetError(Exceptions.RuntimeError, e.Message);
                    return -1;
                }

                if (handled)
                    return 0;
            }

            // Fall back to Python attribute setting
            return Runtime.PyObject_GenericSetAttr(ob, key, val);
        }

        internal static void Initialize()
        {
            Debug.Assert(cache.Count == 0, "Cache should be empty",
                "Some errors may occurred on last shutdown");
            dynamicMemberAccessor.Clear();
            using (var plainType = SlotHelper.CreateObjectType())
            {
                subtype_traverse = Util.ReadIntPtr(plainType.Borrow(), TypeOffset.tp_traverse);
                subtype_clear = Util.ReadIntPtr(plainType.Borrow(), TypeOffset.tp_clear);
            }
            pythonBaseTypeProvider = PythonEngine.InteropConfiguration.pythonBaseTypeProviders;
        }

        internal static void RemoveTypes()
        {
            if (Runtime.HostedInPython)
            {
                foreach (var holder in _slotsHolders)
                {
                    // If refcount > 1, it needs to reset the managed slot,
                    // otherwise it can dealloc without any trick.
                    if (holder.Key.Refcount > 1)
                    {
                        holder.Value.ResetSlots();
                    }
                }
            }

            dynamicMemberAccessor.Clear();

            foreach (var type in cache.Values)
            {
                type.Dispose();
            }
            cache.Clear();
            _slotsHolders.Clear();
        }

        internal static TypeManagerState SaveRuntimeData()
            => new()
            {
                Cache = new Dictionary(cache),
            };

        internal static void RestoreRuntimeData(TypeManagerState storage)
        {
            Debug.Assert(cache == null || cache.Count == 0);
            var typeCache = storage.Cache;
            foreach (var entry in typeCache)
            {
                var type = entry.Key.Value;
                cache![type] = entry.Value;
                SlotsHolder holder = CreateSlotsHolder(entry.Value);
                InitializeSlots(entry.Value, type, holder);
                Runtime.PyType_Modified(entry.Value);
            }
        }

        internal static PyType GetType(Type type)
        {
            // Cached with refcount 1; effectively lives until the CPython runtime is finalised.
            if (cache.TryGetValue(type, out var pyType)) return pyType;
            // CreateType + cache write must be atomic so two threads do not both allocate.
            lock (_cacheCreateLock)
            {
                if (cache.TryGetValue(type, out pyType)) return pyType;
                pyType = CreateType(type);
                cache[type] = pyType;
                return pyType;
            }
        }
        /// 
        /// Given a managed Type derived from ExtensionType, get the handle to
        /// a Python type object that delegates its implementation to the Type
        /// object. These Python type instances are used to implement internal
        /// descriptor and utility types like ModuleObject, PropertyObject, etc.
        /// 
        internal static BorrowedReference GetTypeReference(Type type) => GetType(type).Reference;

        /// 
        /// The following CreateType implementations do the necessary work to
        /// create Python types to represent managed extension types, reflected
        /// types, subclasses of reflected types and the managed metatype. The
        /// dance is slightly different for each kind of type due to different
        /// behavior needed and the desire to have the existing Python runtime
        /// do as much of the allocation and initialization work as possible.
        /// 
        internal static unsafe PyType CreateType(Type impl)
        {
            // TODO: use PyType(TypeSpec) constructor
            PyType type = AllocateTypeObject(impl.Name, metatype: Runtime.PyCLRMetaType);

            BorrowedReference base_ = impl == typeof(CLRModule)
                ? Runtime.PyModuleType
                : Runtime.PyBaseObjectType;

            type.BaseReference = base_;

            int newFieldOffset = InheritOrAllocateStandardFields(type, base_);

            int tp_clr_inst_offset = newFieldOffset;
            newFieldOffset += IntPtr.Size;

            int ob_size = newFieldOffset;
            // Set tp_basicsize to the size of our managed instance objects.
            Util.WriteIntPtr(type, TypeOffset.tp_basicsize, (IntPtr)ob_size);
            Util.WriteInt32(type, ManagedType.Offsets.tp_clr_inst_offset, tp_clr_inst_offset);
            Util.WriteIntPtr(type, TypeOffset.tp_new, (IntPtr)Runtime.Delegates.PyType_GenericNew);

            SlotsHolder slotsHolder = CreateSlotsHolder(type);
            InitializeSlots(type, impl, slotsHolder);

            type.Flags = TypeFlags.Default | TypeFlags.HasClrInstance |
                           TypeFlags.HeapType | TypeFlags.HaveGC;

            if (Runtime.PyType_Ready(type) != 0)
            {
                throw PythonException.ThrowLastAsClrException();
            }


            using (var dict = Runtime.PyObject_GenericGetDict(type.Reference))
            using (var mod = Runtime.PyString_FromString("CLR"))
            {
                Runtime.PyDict_SetItem(dict.Borrow(), PyIdentifier.__module__, mod.Borrow());
            }

            // The type has been modified after PyType_Ready has been called
            // Refresh the type
            Runtime.PyType_Modified(type.Reference);
            return type;
        }


        internal static void InitializeClassCore(Type clrType, PyType pyType, ClassBase impl)
        {
            if (pyType.BaseReference != null)
            {
                return;
            }

            // Hide the gchandle of the implementation in a magic type slot.
            GCHandle gc = GCHandle.Alloc(impl);
            ManagedType.InitGCHandle(pyType, Runtime.CLRMetaType, gc);

            using var baseTuple = GetBaseTypeTuple(clrType);

            InitializeBases(pyType, baseTuple);
            // core fields must be initialized in partially constructed classes,
            // otherwise it would be impossible to manipulate GCHandle and check type size
            InitializeCoreFields(pyType);
        }

        internal static string GetPythonTypeName(Type clrType)
        {
            var result = new System.Text.StringBuilder();
            GetPythonTypeName(clrType, target: result);
            return result.ToString();
        }

        static void GetPythonTypeName(Type clrType, System.Text.StringBuilder target)
        {
            if (clrType.IsGenericType)
            {
                string fullName = clrType.GetGenericTypeDefinition().FullName;
                int argCountIndex = fullName.LastIndexOf('`');
                if (argCountIndex >= 0)
                {
                    string nonGenericFullName = fullName.Substring(0, argCountIndex);
                    string nonGenericName = CleanupFullName(nonGenericFullName);
                    target.Append(nonGenericName);

                    var arguments = clrType.GetGenericArguments();
                    target.Append('[');
                    for (int argIndex = 0; argIndex < arguments.Length; argIndex++)
                    {
                        if (argIndex != 0)
                        {
                            target.Append(',');
                        }

                        GetPythonTypeName(arguments[argIndex], target);
                    }

                    target.Append(']');

                    int nestedStart = fullName.IndexOf('+');
                    while (nestedStart >= 0)
                    {
                        target.Append('.');
                        int nextNested = fullName.IndexOf('+', nestedStart + 1);
                        if (nextNested < 0)
                        {
                            target.Append(fullName.Substring(nestedStart + 1));
                        }
                        else
                        {
                            target.Append(fullName.Substring(nestedStart + 1, length: nextNested - nestedStart - 1));
                        }
                        nestedStart = nextNested;
                    }

                    return;
                }
            }

            string name = CleanupFullName(clrType.FullName);
            target.Append(name);
        }

        static string CleanupFullName(string fullTypeName)
        {
            // Cleanup the type name to get rid of funny nested type names.
            string name = "clr." + fullTypeName;
            int i = name.LastIndexOf('+');
            if (i > -1)
            {
                name = name.Substring(i + 1);
            }

            i = name.LastIndexOf('.');
            if (i > -1)
            {
                name = name.Substring(i + 1);
            }

            return name;
        }

        static BorrowedReference InitializeBases(PyType pyType, PyTuple baseTuple)
        {
            Debug.Assert(baseTuple.Length() > 0);
            var primaryBase = baseTuple[0].Reference;
            pyType.BaseReference = primaryBase;

            if (baseTuple.Length() > 1)
            {
                Util.WriteIntPtr(pyType, TypeOffset.tp_bases, baseTuple.NewReferenceOrNull().DangerousMoveToPointer());
            }
            return primaryBase;
        }

        static void InitializeCoreFields(PyType type)
        {
            int newFieldOffset = InheritOrAllocateStandardFields(type);

            if (ManagedType.IsManagedType(type.BaseReference))
            {
                int baseClrInstOffset = Util.ReadInt32(type.BaseReference, ManagedType.Offsets.tp_clr_inst_offset);
                Util.WriteInt32(type, ManagedType.Offsets.tp_clr_inst_offset, baseClrInstOffset);
            }
            else
            {
                Util.WriteInt32(type, ManagedType.Offsets.tp_clr_inst_offset, newFieldOffset);
                newFieldOffset += IntPtr.Size;
            }

            int ob_size = newFieldOffset;

            Util.WriteIntPtr(type, TypeOffset.tp_basicsize, (IntPtr)ob_size);
            Util.WriteIntPtr(type, TypeOffset.tp_itemsize, IntPtr.Zero);
        }

        internal static void InitializeClass(PyType type, ClassBase impl, Type clrType)
        {
            // we want to do this after the slot stuff above in case the class itself implements a slot method
            SlotsHolder slotsHolder = CreateSlotsHolder(type);
            InitializeSlots(type, impl.GetType(), slotsHolder);

            impl.InitializeSlots(type, slotsHolder);

            OperatorMethod.FixupSlots(type, clrType);
            // Leverage followup initialization from the Python runtime. Note
            // that the type of the new type must PyType_Type at the time we
            // call this, else PyType_Ready will skip some slot initialization.

            if (!type.IsReady && Runtime.PyType_Ready(type) != 0)
            {
                throw PythonException.ThrowLastAsClrException();
            }

            if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(clrType))
            {
                InitializeSlot(type, TypeOffset.tp_getattro, new Interop.BB_N(tp_getattro_dlr_proxy), slotsHolder);
                InitializeSlot(type, TypeOffset.tp_setattro, new Interop.BBB_I32(tp_setattro_dlr_proxy), slotsHolder);
                Runtime.PyType_Modified(type.Reference);
            }

            var dict = Util.ReadRef(type, TypeOffset.tp_dict);
            string mn = clrType.Namespace ?? "";
            using (var mod = Runtime.PyString_FromString(mn))
                Runtime.PyDict_SetItem(dict, PyIdentifier.__module__, mod.Borrow());

            Runtime.PyType_Modified(type.Reference);

            //DebugUtil.DumpType(type);
        }

        static int InheritOrAllocateStandardFields(BorrowedReference type)
        {
            var @base = Util.ReadRef(type, TypeOffset.tp_base);
            return InheritOrAllocateStandardFields(type, @base);
        }
        static int InheritOrAllocateStandardFields(BorrowedReference typeRef, BorrowedReference @base)
        {
            IntPtr baseAddress = @base.DangerousGetAddress();
            IntPtr type = typeRef.DangerousGetAddress();
            int baseSize = Util.ReadInt32(@base, TypeOffset.tp_basicsize);
            int newFieldOffset = baseSize;

            void InheritOrAllocate(int typeField)
            {
                int value = Marshal.ReadInt32(baseAddress, typeField);
                if (value == 0)
                {
                    Marshal.WriteIntPtr(type, typeField, new IntPtr(newFieldOffset));
                    newFieldOffset += IntPtr.Size;
                }
                else
                {
                    Marshal.WriteIntPtr(type, typeField, new IntPtr(value));
                }
            }

            InheritOrAllocate(TypeOffset.tp_dictoffset);
            InheritOrAllocate(TypeOffset.tp_weaklistoffset);

            return newFieldOffset;
        }

        static PyTuple GetBaseTypeTuple(Type clrType)
        {
            var bases = pythonBaseTypeProvider
                .GetBaseTypes(clrType, new PyType[0])
                ?.ToArray();
            if (bases is null || bases.Length == 0)
            {
                throw new InvalidOperationException("At least one base type must be specified");
            }
            var nonBases = bases.Where(@base => !@base.Flags.HasFlag(TypeFlags.BaseType)).ToList();
            if (nonBases.Count > 0)
            {
                throw new InvalidProgramException("The specified Python type(s) can not be inherited from: "
                                                  + string.Join(", ", nonBases));
            }

            return new PyTuple(bases);
        }

        internal static NewReference CreateSubType(BorrowedReference py_name, ClassBase py_base_type, IList interfaces, BorrowedReference dictRef)
        {
            // Utility to create a subtype of a managed type with the ability for the
            // a python subtype able to override the managed implementation
            string? name = Runtime.GetManagedString(py_name);
            if (name is null)
            {
                Exceptions.SetError(Exceptions.ValueError, "Class name must not be None");
                return default;
            }

            // the derived class can have class attributes __assembly__ and __module__ which
            // control the name of the assembly and module the new type is created in.
            object? assembly = null;
            object? namespaceStr = null;

            using (var assemblyKey = new PyString("__assembly__"))
            {
                var assemblyPtr = Runtime.PyDict_GetItemWithError(dictRef, assemblyKey.Reference);
                if (assemblyPtr.IsNull)
                {
                    if (Exceptions.ErrorOccurred()) return default;
                }
                else if (!Converter.ToManagedValue(assemblyPtr, typeof(string), out assembly, true))
                {
                    return Exceptions.RaiseTypeError("Couldn't convert __assembly__ value to string");
                }

                using var namespaceKey = new PyString("__namespace__");
                var pyNamespace = Runtime.PyDict_GetItemWithError(dictRef, namespaceKey.Reference);
                if (pyNamespace.IsNull)
                {
                    if (Exceptions.ErrorOccurred()) return default;
                }
                else if (!Converter.ToManagedValue(pyNamespace, typeof(string), out namespaceStr, true))
                {
                    return Exceptions.RaiseTypeError("Couldn't convert __namespace__ value to string");
                }
            }

            // create the new managed type subclassing the base managed type
            return ReflectedClrType.CreateSubclass(py_base_type, interfaces, name,
                ns: (string?)namespaceStr,
                assembly: (string?)assembly,
                dict: dictRef);
        }

        internal static IntPtr WriteMethodDef(IntPtr mdef, IntPtr name, IntPtr func, PyMethodFlags flags, IntPtr doc)
        {
            Marshal.WriteIntPtr(mdef, name);
            Marshal.WriteIntPtr(mdef, 1 * IntPtr.Size, func);
            Marshal.WriteInt32(mdef, 2 * IntPtr.Size, (int)flags);
            Marshal.WriteIntPtr(mdef, 3 * IntPtr.Size, doc);
            return mdef + 4 * IntPtr.Size;
        }

        internal static IntPtr WriteMethodDef(IntPtr mdef, string name, IntPtr func, PyMethodFlags flags = PyMethodFlags.VarArgs,
            string? doc = null)
        {
            IntPtr namePtr = Marshal.StringToHGlobalAnsi(name);
            IntPtr docPtr = doc != null ? Marshal.StringToHGlobalAnsi(doc) : IntPtr.Zero;

            return WriteMethodDef(mdef, namePtr, func, flags, docPtr);
        }

        internal static IntPtr WriteMethodDefSentinel(IntPtr mdef)
        {
            return WriteMethodDef(mdef, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero);
        }

        internal static void FreeMethodDef(IntPtr mdef)
        {
            unsafe
            {
                var def = (PyMethodDef*)mdef;
                if (def->ml_name != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(def->ml_name);
                    def->ml_name = IntPtr.Zero;
                }
                if (def->ml_doc != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(def->ml_doc);
                    def->ml_doc = IntPtr.Zero;
                }
            }
        }

        internal static PyType CreateMetatypeWithGCHandleOffset()
        {
            var py_type = new PyType(Runtime.PyTypeType, prevalidated: true);
            int size = Util.ReadInt32(Runtime.PyTypeType, TypeOffset.tp_basicsize)
                       + IntPtr.Size // tp_clr_inst_offset
            ;

            var slots = new[] {
                new TypeSpec.Slot(TypeSlotID.tp_traverse, subtype_traverse),
                new TypeSpec.Slot(TypeSlotID.tp_clear, subtype_clear)
            };
            var result = new PyType(
                new TypeSpec(
                    "clr._internal.GCOffsetBase",
                    basicSize: size,
                    slots: slots,
                    TypeFlags.Default | TypeFlags.HeapType | TypeFlags.HaveGC
                ),
                bases: new PyTuple(new[] { py_type })
            );

            return result;
        }

        internal static PyType CreateMetaType(Type impl, out SlotsHolder slotsHolder)
        {
            // The managed metatype is functionally little different than the
            // standard Python metatype (PyType_Type). It overrides certain of
            // the standard type slots, and has to subclass PyType_Type for
            // certain functions in the C runtime to work correctly with it.

            PyType gcOffsetBase = CreateMetatypeWithGCHandleOffset();

            PyType type = AllocateTypeObject("CLRMetatype", metatype: gcOffsetBase);

            Util.WriteRef(type, TypeOffset.tp_base, new NewReference(gcOffsetBase).Steal());

            nint size = Util.ReadInt32(gcOffsetBase, TypeOffset.tp_basicsize)
                       + IntPtr.Size // tp_clr_inst
            ;
            Util.WriteIntPtr(type, TypeOffset.tp_basicsize, size);
            Util.WriteInt32(type, ManagedType.Offsets.tp_clr_inst_offset, ManagedType.Offsets.tp_clr_inst);

            const TypeFlags flags = TypeFlags.Default
                            | TypeFlags.HeapType
                            | TypeFlags.HaveGC
                            | TypeFlags.HasClrInstance;
            Util.WriteCLong(type, TypeOffset.tp_flags, (int)flags);

            // Slots will inherit from TypeType, it's not neccesary for setting them.
            // Inheried slots:
            // tp_basicsize, tp_itemsize,
            // tp_dictoffset, tp_weaklistoffset,
            // tp_traverse, tp_clear, tp_is_gc, etc.
            slotsHolder = SetupMetaSlots(impl, type);

            if (Runtime.PyType_Ready(type) != 0)
            {
                throw PythonException.ThrowLastAsClrException();
            }

            BorrowedReference dict = Util.ReadRef(type, TypeOffset.tp_dict);
            using (var mod = Runtime.PyString_FromString("clr._internal"))
                Runtime.PyDict_SetItemString(dict, "__module__", mod.Borrow());

            // The type has been modified after PyType_Ready has been called
            // Refresh the type
            Runtime.PyType_Modified(type);
            //DebugUtil.DumpType(type);

            return type;
        }

        internal static SlotsHolder SetupMetaSlots(Type impl, PyType type)
        {
            // Override type slots with those of the managed implementation.
            var slotsHolder = new SlotsHolder(type);
            InitializeSlots(type, impl, slotsHolder);

            // We need space for 3 PyMethodDef structs.
            int mdefSize = (MetaType.CustomMethods.Length + 1) * Marshal.SizeOf(typeof(PyMethodDef));
            IntPtr mdef = Runtime.PyMem_Malloc(mdefSize);
            IntPtr mdefStart = mdef;
            foreach (var methodName in MetaType.CustomMethods)
            {
                mdef = AddCustomMetaMethod(methodName, type, mdef, slotsHolder);
            }
            mdef = WriteMethodDefSentinel(mdef);
            Debug.Assert((long)(mdefStart + mdefSize) 
                {
                    var p = Util.ReadIntPtr(t, offset);
                    Runtime.PyMem_Free(p);
                    Util.WriteIntPtr(t, offset, IntPtr.Zero);
                });
            }
            return slotsHolder;
        }

        private static IntPtr AddCustomMetaMethod(string name, PyType type, IntPtr mdef, SlotsHolder slotsHolder)
        {
            MethodInfo mi = typeof(MetaType).GetMethod(name);
            ThunkInfo thunkInfo = Interop.GetThunk(mi);
            slotsHolder.KeeapAlive(thunkInfo);

            // XXX: Hard code with mode check.
            if (Runtime.HostedInPython)
            {
                IntPtr mdefAddr = mdef;
                slotsHolder.AddDealloctor(() =>
                {
                    var tp_dict = Util.ReadRef(type, TypeOffset.tp_dict);
                    if (Runtime.PyDict_DelItemString(tp_dict, name) != 0)
                    {
                        Runtime.PyErr_Print();
                        Debug.Fail($"Cannot remove {name} from metatype");
                    }
                    FreeMethodDef(mdefAddr);
                });
            }
            mdef = WriteMethodDef(mdef, name, thunkInfo.Address);
            return mdef;
        }

        /// 
        /// Utility method to allocate a type object & do basic initialization.
        /// 
        internal static PyType AllocateTypeObject(string name, PyType metatype)
        {
            var newType = Runtime.PyType_GenericAlloc(metatype, 0);
            var type = new PyType(newType.StealOrThrow());
            // Clr type would not use __slots__,
            // and the PyMemberDef after PyHeapTypeObject will have other uses(e.g. type handle),
            // thus set the ob_size to 0 for avoiding slots iterations.
            Util.WriteIntPtr(type, TypeOffset.ob_size, IntPtr.Zero);

            // Cheat a little: we'll set tp_name to the internal char * of
            // the Python version of the type name - otherwise we'd have to
            // allocate the tp_name and would have no way to free it.
            using var temp = Runtime.PyString_FromString(name);
            IntPtr raw = Runtime.PyUnicode_AsUTF8(temp.BorrowOrThrow());
            Util.WriteIntPtr(type, TypeOffset.tp_name, raw);
            Util.WriteRef(type, TypeOffset.name, new NewReference(temp).Steal());
            Util.WriteRef(type, TypeOffset.qualname, temp.Steal());

            // Ensure that tp_traverse and tp_clear are always set, since their
            // existence is enforced in newer Python versions in PyType_Ready
            Util.WriteIntPtr(type, TypeOffset.tp_traverse, subtype_traverse);
            Util.WriteIntPtr(type, TypeOffset.tp_clear, subtype_clear);

            // This is a new mechanism in Python 3.14. We should eventually use it to implement
            // a nicer type check, but for now we just need to ensure that it is set to NULL.
            if (TypeOffset.ht_token != -1)
                Util.WriteIntPtr(type, TypeOffset.ht_token, IntPtr.Zero);

            InheritSubstructs(type.Reference.DangerousGetAddress());

            return type;
        }

        /// 
        /// Inherit substructs, that are not inherited by default:
        /// https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_as_number
        /// 
        static void InheritSubstructs(IntPtr type)
        {
            IntPtr substructAddress = type + TypeOffset.nb_add;
            Marshal.WriteIntPtr(type, TypeOffset.tp_as_number, substructAddress);

            substructAddress = type + TypeOffset.sq_length;
            Marshal.WriteIntPtr(type, TypeOffset.tp_as_sequence, substructAddress);

            substructAddress = type + TypeOffset.mp_length;
            Marshal.WriteIntPtr(type, TypeOffset.tp_as_mapping, substructAddress);

            substructAddress = type + TypeOffset.bf_getbuffer;
            Marshal.WriteIntPtr(type, TypeOffset.tp_as_buffer, substructAddress);
        }

        /// 
        /// Given a newly allocated Python type object and a managed Type that
        /// provides the implementation for the type, connect the type slots of
        /// the Python object to the managed methods of the implementing Type.
        /// 
        internal static void InitializeSlots(PyType type, Type impl, SlotsHolder? slotsHolder = null)
        {
            // We work from the most-derived class up; make sure to get
            // the most-derived slot and not to override it with a base
            // class's slot.
            var seen = new HashSet();

            while (impl != null)
            {
                MethodInfo[] methods = impl.GetMethods(tbFlags);
                foreach (MethodInfo method in methods)
                {
                    string name = method.Name;
                    if (!name.StartsWith("tp_") && !TypeOffset.IsSupportedSlotName(name))
                    {
                        Debug.Assert(!name.Contains("_") || name.StartsWith("_") || method.IsSpecialName);
                        continue;
                    }

                    if (seen.Contains(name))
                    {
                        continue;
                    }

                    InitializeSlot(type, Interop.GetThunk(method), name, slotsHolder);

                    seen.Add(name);
                }

                var initSlot = impl.GetMethod("InitializeSlots", BindingFlags.Static | BindingFlags.Public);
                initSlot?.Invoke(null, parameters: new object?[] { type, seen, slotsHolder });

                impl = impl.BaseType;
            }

            SetRequiredSlots(type, seen);
        }

        private static void SetRequiredSlots(PyType type, HashSet seen)
        {
            foreach (string slot in _requiredSlots)
            {
                if (seen.Contains(slot))
                {
                    continue;
                }
                var offset = TypeOffset.GetSlotOffset(slot);
                Util.WriteIntPtr(type, offset, SlotsHolder.GetDefaultSlot(offset));
            }
        }

        static void InitializeSlot(BorrowedReference type, ThunkInfo thunk, string name, SlotsHolder? slotsHolder)
        {
            if (!Enum.TryParse(name, out _))
            {
                throw new NotSupportedException("Bad slot name " + name);
            }
            int offset = TypeOffset.GetSlotOffset(name);
            InitializeSlot(type, offset, thunk, slotsHolder);
        }

        static void InitializeSlot(BorrowedReference type, int slotOffset, MethodInfo method, SlotsHolder slotsHolder)
        {
            var thunk = Interop.GetThunk(method);
            InitializeSlot(type, slotOffset, thunk, slotsHolder);
        }

        internal static void InitializeSlot(BorrowedReference type, int slotOffset, Delegate impl, SlotsHolder slotsHolder)
        {
            var thunk = Interop.GetThunk(impl);
            InitializeSlot(type, slotOffset, thunk, slotsHolder);
        }

        internal static void InitializeSlotIfEmpty(BorrowedReference type, int slotOffset, Delegate impl, SlotsHolder slotsHolder)
        {
            if (slotsHolder.IsHolding(slotOffset)) return;
            InitializeSlot(type, slotOffset, impl, slotsHolder);
        }

        static void InitializeSlot(BorrowedReference type, int slotOffset, ThunkInfo thunk, SlotsHolder? slotsHolder)
        {
            Util.WriteIntPtr(type, slotOffset, thunk.Address);
            if (slotsHolder != null)
            {
                slotsHolder.Set(slotOffset, thunk);
            }
        }

        /// 
        /// Utility method to copy slots from a given type to another type.
        /// 
        internal static void CopySlot(BorrowedReference from, BorrowedReference to, int offset)
        {
            IntPtr fp = Util.ReadIntPtr(from, offset);
            Util.WriteIntPtr(to, offset, fp);
        }

        internal static SlotsHolder CreateSlotsHolder(PyType type)
        {
            type = new PyType(type);
            var holder = new SlotsHolder(type);
            _slotsHolders[type] = holder;
            return holder;
        }
    }


    class SlotsHolder
    {
        public delegate void Resetor(PyType type, int offset);

        private readonly Dictionary _slots = new();
        private readonly List _keepalive = new();
        private readonly Dictionary _customResetors = new();
        private readonly List _deallocators = new();
        private bool _alreadyReset = false;

        private readonly PyType Type;

        public string?[] Holds => _slots.Keys.Select(TypeOffset.GetSlotName).ToArray();

        /// 
        /// Create slots holder for holding the delegate of slots and be able  to reset them.
        /// 
        /// Steals a reference to target type
        public SlotsHolder(PyType type)
        {
            this.Type = type;
        }

        public bool IsHolding(int offset) => _slots.ContainsKey(offset);

        public ICollection Slots => _slots.Keys;

        public void Set(int offset, ThunkInfo thunk)
        {
            _slots[offset] = thunk;
        }

        public void Set(int offset, Resetor resetor)
        {
            _customResetors[offset] = resetor;
        }

        public void AddDealloctor(Action deallocate)
        {
            _deallocators.Add(deallocate);
        }

        public void KeeapAlive(ThunkInfo thunk)
        {
            _keepalive.Add(thunk);
        }

        public static void ResetSlots(BorrowedReference type, IEnumerable slots)
        {
            foreach (int offset in slots)
            {
                IntPtr ptr = GetDefaultSlot(offset);
#if DEBUG
                //DebugUtil.Print($"Set slot to 0x{ptr.ToString("X")} at {typeName}");
#endif
                Util.WriteIntPtr(type, offset, ptr);
            }
        }

        public void ResetSlots()
        {
            if (_alreadyReset)
            {
                return;
            }
            _alreadyReset = true;
#if DEBUG
            IntPtr tp_name = Util.ReadIntPtr(Type, TypeOffset.tp_name);
            string typeName = Marshal.PtrToStringAnsi(tp_name);
#endif
            ResetSlots(Type, _slots.Keys);

            foreach (var action in _deallocators)
            {
                action();
            }

            foreach (var pair in _customResetors)
            {
                int offset = pair.Key;
                var resetor = pair.Value;
                resetor?.Invoke(Type, offset);
            }

            _customResetors.Clear();
            _slots.Clear();
            _keepalive.Clear();
            _deallocators.Clear();

            // Custom reset
            if (Type != Runtime.CLRMetaType)
            {
                var metatype = Runtime.PyObject_TYPE(Type);
                ManagedType.TryFreeGCHandle(Type, metatype);
            }
            Runtime.PyType_Modified(Type);
        }

        public static IntPtr GetDefaultSlot(int offset)
        {
            if (offset == TypeOffset.tp_clear)
            {
                return TypeManager.subtype_clear;
            }
            else if (offset == TypeOffset.tp_traverse)
            {
                return TypeManager.subtype_traverse;
            }
            else if (offset == TypeOffset.tp_dealloc)
            {
                // tp_free of PyTypeType is point to PyObejct_GC_Del.
                return Util.ReadIntPtr(Runtime.PyTypeType, TypeOffset.tp_free);
            }
            else if (offset == TypeOffset.tp_free)
            {
                // PyObject_GC_Del
                return Util.ReadIntPtr(Runtime.PyTypeType, TypeOffset.tp_free);
            }
            else if (offset == TypeOffset.tp_call)
            {
                return IntPtr.Zero;
            }
            else if (offset == TypeOffset.tp_new)
            {
                // PyType_GenericNew
                return Util.ReadIntPtr(Runtime.PySuper_Type, TypeOffset.tp_new);
            }
            else if (offset == TypeOffset.tp_getattro)
            {
                // PyObject_GenericGetAttr
                return Util.ReadIntPtr(Runtime.PyBaseObjectType, TypeOffset.tp_getattro);
            }
            else if (offset == TypeOffset.tp_setattro)
            {
                // PyObject_GenericSetAttr
                return Util.ReadIntPtr(Runtime.PyBaseObjectType, TypeOffset.tp_setattro);
            }

            return Util.ReadIntPtr(Runtime.PyTypeType, offset);
        }
    }


    static class SlotHelper
    {
        public static NewReference CreateObjectType()
        {
            using var globals = Runtime.PyDict_New();
            if (Runtime.PyDict_SetItemString(globals.Borrow(), "__builtins__", Runtime.PyEval_GetBuiltins()) != 0)
            {
                globals.Dispose();
                throw PythonException.ThrowLastAsClrException();
            }
            const string code = "class A(object): pass";
            using var resRef = Runtime.PyRun_String(code, RunFlagType.File, globals.Borrow(), globals.Borrow());
            if (resRef.IsNull())
            {
                globals.Dispose();
                throw PythonException.ThrowLastAsClrException();
            }
            resRef.Dispose();
            BorrowedReference A = Runtime.PyDict_GetItemString(globals.Borrow(), "A");
            return new NewReference(A);
        }
    }
}

Web Proxy Viewer  |  New URL  |  Original Page