X Tutup
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Serialization; using Python.Runtime.StateSerialization; namespace Python.Runtime { /// /// The managed metatype. This object implements the type of all reflected /// types. It also provides support for single-inheritance from reflected /// managed types. /// internal sealed class MetaType : ManagedType { #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. // set in Initialize private static PyType PyCLRMetaType; private static SlotsHolder _metaSlotsHodler; private static int TypeDictOffset = -1; #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. internal static readonly string[] CustomMethods = new string[] { "__instancecheck__", "__subclasscheck__", }; /// /// Metatype initialization. This bootstraps the CLR metatype to life. /// public static PyType Initialize() { PyCLRMetaType = TypeManager.CreateMetaType(typeof(MetaType), out _metaSlotsHodler); // Retrieve the offset of the type's dictionary from PyType_Type for // use in the tp_setattro implementation. using (NewReference dictOffset = Runtime.PyObject_GetAttr(Runtime.PyTypeType, PyIdentifier.__dictoffset__)) { if (dictOffset.IsNull()) { throw new InvalidOperationException("Could not get __dictoffset__ from PyType_Type"); } nint dictOffsetVal = Runtime.PyLong_AsSignedSize_t(dictOffset.Borrow()); if (dictOffsetVal <= 0) { throw new InvalidOperationException("Could not get __dictoffset__ from PyType_Type"); } TypeDictOffset = checked((int)dictOffsetVal); } return PyCLRMetaType; } public static void Release() { if (Runtime.HostedInPython) { _metaSlotsHodler.ResetSlots(); } TypeDictOffset = -1; PyCLRMetaType.Dispose(); } internal static MetatypeState SaveRuntimeData() => new() { CLRMetaType = PyCLRMetaType }; internal static PyType RestoreRuntimeData(MetatypeState storage) { PyCLRMetaType = storage.CLRMetaType; _metaSlotsHodler = new SlotsHolder(PyCLRMetaType); TypeManager.InitializeSlots(PyCLRMetaType, typeof(MetaType), _metaSlotsHodler); IntPtr mdef = Util.ReadIntPtr(PyCLRMetaType, TypeOffset.tp_methods); foreach (var methodName in CustomMethods) { var mi = typeof(MetaType).GetMethod(methodName); ThunkInfo thunkInfo = Interop.GetThunk(mi); _metaSlotsHodler.KeeapAlive(thunkInfo); mdef = TypeManager.WriteMethodDef(mdef, methodName, thunkInfo.Address); } return PyCLRMetaType; } /// /// Metatype __new__ implementation. This is called to create a new /// class / type when a reflected class is subclassed. /// public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, BorrowedReference kw) { var len = Runtime.PyTuple_Size(args); if (len < 3) { return Exceptions.RaiseTypeError("invalid argument list"); } BorrowedReference name = Runtime.PyTuple_GetItem(args, 0); BorrowedReference bases = Runtime.PyTuple_GetItem(args, 1); BorrowedReference dict = Runtime.PyTuple_GetItem(args, 2); // Extract interface types and base class types. var interfaces = new List(); // More than one base type case be declared, but an exception will be thrown // if more than one is a class/not an interface. var baseTypes = new List(); var baseClassCount = Runtime.PyTuple_Size(bases); if (baseClassCount == 0) { return Exceptions.RaiseTypeError("zero base classes "); } for (nint i = 0; i < baseClassCount; i++) { var baseTypeIt = Runtime.PyTuple_GetItem(bases, (int)i); if (GetManagedObject(baseTypeIt) is ClassBase classBaseIt) { if (!classBaseIt.type.Valid) { return Exceptions.RaiseTypeError("Invalid type used as a super type."); } if (classBaseIt.type.Value.IsInterface) { interfaces.Add(classBaseIt.type.Value); } else { baseTypes.Add(classBaseIt); } } else { return Exceptions.RaiseTypeError("Non .NET type used as super class for meta type. This is not supported."); } } // if the base type count is 0, there might still be interfaces to implement. if (baseTypes.Count == 0) { baseTypes.Add(new ClassBase(typeof(object))); } // Multiple inheritance is not supported, unless the other types are interfaces if (baseTypes.Count > 1) { var types = string.Join(", ", baseTypes.Select(baseType => baseType.type.Value)); return Exceptions.RaiseTypeError($"Multiple inheritance with managed classes cannot be used. Types: {types} "); } // check if the list of interfaces contains no duplicates. if (interfaces.Distinct().Count() != interfaces.Count) { // generate a string containing the problematic types. var duplicateTypes = interfaces.GroupBy(type => type) .Where(typeGroup => typeGroup.Count() > 1) .Select(typeGroup => typeGroup.Key); var duplicateTypesString = string.Join(", ", duplicateTypes); return Exceptions.RaiseTypeError($"An interface can only be implemented once. Duplicate types: {duplicateTypesString}"); } var cb = baseTypes[0]; try { if (!cb.CanSubclass()) { return Exceptions.RaiseTypeError("delegates, enums and array types cannot be subclassed"); } } catch (SerializationException) { return Exceptions.RaiseTypeError($"Underlying C# Base class {cb.type} has been deleted"); } BorrowedReference slots = Runtime.PyDict_GetItem(dict, PyIdentifier.__slots__); if (slots != null) { return Exceptions.RaiseTypeError("subclasses of managed classes do not support __slots__"); } // If __assembly__ or __namespace__ are in the class dictionary then create // a managed sub type. // This creates a new managed type that can be used from .net to call back // into python. if (null != dict) { using var clsDict = new PyDict(dict); if (clsDict.HasKey("__assembly__") || clsDict.HasKey("__namespace__")) { return TypeManager.CreateSubType(name, baseTypes[0], interfaces, clsDict); } } var base_type = Runtime.PyTuple_GetItem(bases, 0); // otherwise just create a basic type without reflecting back into the managed side. IntPtr func = Util.ReadIntPtr(Runtime.PyTypeType, TypeOffset.tp_new); NewReference type = NativeCall.Call_3(func, tp, args, kw); if (type.IsNull()) { return default; } var flags = PyType.GetFlags(type.Borrow()); if (!flags.HasFlag(TypeFlags.Ready)) throw new NotSupportedException("PyType.tp_new returned an incomplete type"); flags |= TypeFlags.HasClrInstance; flags |= TypeFlags.HeapType; flags |= TypeFlags.BaseType; flags |= TypeFlags.Subclass; flags |= TypeFlags.HaveGC; PyType.SetFlags(type.Borrow(), flags); TypeManager.CopySlot(base_type, type.Borrow(), TypeOffset.tp_dealloc); // Hmm - the standard subtype_traverse, clear look at ob_size to // do things, so to allow gc to work correctly we need to move // our hidden handle out of ob_size. Then, in theory we can // comment this out and still not crash. TypeManager.CopySlot(base_type, type.Borrow(), TypeOffset.tp_traverse); TypeManager.CopySlot(base_type, type.Borrow(), TypeOffset.tp_clear); // derived types must have their GCHandle at the same offset as the base types int clrInstOffset = Util.ReadInt32(base_type, Offsets.tp_clr_inst_offset); Debug.Assert(clrInstOffset > 0 && clrInstOffset < Util.ReadInt32(type.Borrow(), TypeOffset.tp_basicsize)); Util.WriteInt32(type.Borrow(), Offsets.tp_clr_inst_offset, clrInstOffset); // for now, move up hidden handle... var gc = (GCHandle)Util.ReadIntPtr(base_type, Offsets.tp_clr_inst); Util.WriteIntPtr(type.Borrow(), Offsets.tp_clr_inst, (IntPtr)GCHandle.Alloc(gc.Target)); Runtime.PyType_Modified(type.Borrow()); return type; } public static NewReference tp_alloc(BorrowedReference mt, nint n) => Runtime.PyType_GenericAlloc(mt, n); public static void tp_free(NewReference tp) { Runtime.PyObject_GC_Del(tp.Steal()); } /// /// Metatype __call__ implementation. This is needed to ensure correct /// initialization (__init__ support), because the tp_call we inherit /// from PyType_Type won't call __init__ for metatypes it doesn't know. /// public static NewReference tp_call(BorrowedReference tp, BorrowedReference args, BorrowedReference kw) { IntPtr tp_new = Util.ReadIntPtr(tp, TypeOffset.tp_new); if (tp_new == IntPtr.Zero) { return Exceptions.RaiseTypeError("invalid object"); } using NewReference obj = NativeCall.Call_3(tp_new, tp, args, kw); if (obj.IsNull()) { return default; } var type = GetManagedObject(tp)!; return type.Init(obj.Borrow(), args, kw) ? obj.Move() : default; } /// /// Type __setattr__ implementation for reflected types. Note that this /// is slightly different than the standard setattr implementation for /// the normal Python metatype (PyTypeType). We need to look first in /// the type object of a reflected type for a descriptor in order to /// support the right setattr behavior for static fields and properties. /// public static int tp_setattro(BorrowedReference tp, BorrowedReference name, BorrowedReference value) { BorrowedReference descr = Runtime._PyType_Lookup(tp, name); if (descr != null) { BorrowedReference dt = Runtime.PyObject_TYPE(descr); if (dt == Runtime.PyWrapperDescriptorType || dt == Runtime.PyMethodType || typeof(ExtensionType).IsInstanceOfType(GetManagedObject(descr)) ) { IntPtr fp = Util.ReadIntPtr(dt, TypeOffset.tp_descr_set); if (fp != IntPtr.Zero) { return NativeCall.Int_Call_3(fp, descr, name, value); } Exceptions.SetError(Exceptions.AttributeError, "attribute is read-only"); return -1; } } // Access the type's dictionary directly // // We can not use the PyObject_GenericSetAttr because since Python // 3.14 as https://github.com/python/cpython/pull/118454 intrdoduced // an assertion to prevent it from being called from metatypes. // // The direct dictionary access is equivalent to what Cython does // to work around the same issue: https://github.com/cython/cython/pull/6325 BorrowedReference typeDict = new(Util.ReadIntPtr(tp, TypeDictOffset)); int res; if (value.IsNull) { res = Runtime.PyDict_DelItem(typeDict, name); if (res != 0) { Exceptions.SetError(Exceptions.AttributeError, "attribute not found"); } } else { res = Runtime.PyDict_SetItem(typeDict, name, value); } Runtime.PyType_Modified(tp); return res; } /// /// The metatype has to implement [] semantics for generic types, so /// here we just delegate to the generic type def implementation. Its /// own mp_subscript /// public static NewReference mp_subscript(BorrowedReference tp, BorrowedReference idx) { if (GetManagedObject(tp) is ClassBase cb) { return cb.type_subscript(idx); } return Exceptions.RaiseTypeError("unsubscriptable object"); } /// /// Dealloc implementation. This is called when a Python type generated /// by this metatype is no longer referenced from the Python runtime. /// public static void tp_dealloc(NewReference lastRef) { var weakrefs = Runtime.PyObject_GetWeakRefList(lastRef.Borrow()); if (weakrefs != null) { Runtime.PyObject_ClearWeakRefs(lastRef.Borrow()); } // Fix this when we dont cheat on the handle for subclasses! var flags = PyType.GetFlags(lastRef.Borrow()); if ((flags & TypeFlags.Subclass) == 0) { TryGetGCHandle(lastRef.Borrow())?.Free(); #if DEBUG // prevent ExecutionEngineException in debug builds in case we have a bug // this would allow using managed debugger to investigate the issue SetGCHandle(lastRef.Borrow(), default); #endif } var op = Runtime.PyObject_TYPE(lastRef.Borrow()); Debug.Assert(Runtime.PyCLRMetaType is null || Runtime.PyCLRMetaType == op); var builtinType = Runtime.PyObject_TYPE(Runtime.PyObject_TYPE(op)); // Delegate the rest of finalization the Python metatype. Note // that the PyType_Type implementation of tp_dealloc will call // tp_free on the type of the type being deallocated - in this // case our CLR metatype. That is why we implement tp_free. IntPtr tp_dealloc = Util.ReadIntPtr(builtinType, TypeOffset.tp_dealloc); NativeCall.CallDealloc(tp_dealloc, lastRef.Steal()); // We must decref our type. // type_dealloc from PyType will use it to get tp_free so we must keep the value Runtime.XDecref(StolenReference.DangerousFromPointer(op.DangerousGetAddress())); } private static NewReference DoInstanceCheck(BorrowedReference tp, BorrowedReference args, bool checkType) { if (GetManagedObject(tp) is not ClassBase cb || !cb.type.Valid) { return new NewReference(Runtime.PyFalse); } using var argsObj = new PyList(args); if (argsObj.Length() != 1) { return Exceptions.RaiseTypeError("Invalid parameter count"); } PyObject arg = argsObj[0]; var otherType = checkType ? arg : arg.GetPythonType(); if (Runtime.PyObject_TYPE(otherType) != PyCLRMetaType) { return new NewReference(Runtime.PyFalse); } if (GetManagedObject(otherType) is ClassBase otherCb && otherCb.type.Valid) { return Converter.ToPython(cb.type.Value.IsAssignableFrom(otherCb.type.Value)); } return new NewReference(Runtime.PyFalse); } public static NewReference __instancecheck__(BorrowedReference tp, BorrowedReference args) { return DoInstanceCheck(tp, args, false); } public static NewReference __subclasscheck__(BorrowedReference tp, BorrowedReference args) { return DoInstanceCheck(tp, args, true); } } }
X Tutup