using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using Python.Runtime.StateSerialization;
namespace Python.Runtime
{
///
/// The ClassManager is responsible for creating and managing instances
/// that implement the Python type objects that reflect managed classes.
/// Each managed type reflected to Python is represented by an instance
/// of a concrete subclass of ClassBase. Each instance is associated with
/// a generated Python type object, whose slots point to static methods
/// of the managed instance's class.
///
internal class ClassManager
{
// Binding flags to determine which members to expose in Python.
// This is complicated because inheritance in Python is name
// based. We can't just find DeclaredOnly members, because we
// could have a base class A that defines two overloads of a
// method and a class B that defines two more. The name-based
// descriptor Python will find needs to know about inherited
// overloads as well as those declared on the sub class.
internal static readonly BindingFlags BindingFlags = BindingFlags.Static |
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic;
internal static Dictionary cache = new(capacity: 128);
private static readonly Type dtype;
private ClassManager()
{
}
static ClassManager()
{
// SEE: https://msdn.microsoft.com/en-us/library/96b1ayy4(v=vs.100).aspx
// ""All delegates inherit from MulticastDelegate, which inherits from Delegate.""
// Was Delegate, which caused a null MethodInfo returned from GetMethode("Invoke")
// and crashed on Linux under Mono.
dtype = typeof(MulticastDelegate);
}
public static void Reset()
{
cache.Clear();
}
internal static void RemoveClasses()
{
foreach (var @class in cache.Values)
{
@class.Dispose();
}
cache.Clear();
}
internal static ClassManagerState SaveRuntimeData()
{
var contexts = new Dictionary>();
foreach (var cls in cache)
{
var cb = (ClassBase)ManagedType.GetManagedObject(cls.Value)!;
var context = cb.Save(cls.Value);
if (context is not null)
{
contexts[cls.Value] = context;
}
// Remove all members added in InitBaseClass.
// this is done so that if domain reloads and a member of a
// reflected dotnet class is removed, it is removed from the
// Python object's dictionary tool; thus raising an AttributeError
// instead of a TypeError.
// Classes are re-initialized on in RestoreRuntimeData.
using var dict = Runtime.PyObject_GenericGetDict(cls.Value);
foreach (var member in cb.dotNetMembers)
{
if ((Runtime.PyDict_DelItemString(dict.Borrow(), member) == -1) &&
(Exceptions.ExceptionMatches(Exceptions.KeyError)))
{
// Trying to remove a key that's not in the dictionary
// raises an error. We don't care about it.
Runtime.PyErr_Clear();
}
else if (Exceptions.ErrorOccurred())
{
throw PythonException.ThrowLastAsClrException();
}
}
// We modified the Type object, notify it we did.
Runtime.PyType_Modified(cls.Value);
}
return new()
{
Contexts = contexts,
Cache = cache,
};
}
internal static void RestoreRuntimeData(ClassManagerState storage)
{
cache = storage.Cache;
var invalidClasses = new List>();
var contexts = storage.Contexts;
foreach (var pair in cache)
{
var context = contexts[pair.Value];
if (pair.Key.Valid)
{
pair.Value.Restore(context);
}
else
{
invalidClasses.Add(pair);
var cb = new UnloadedClass(pair.Key.Name);
cb.Load(pair.Value, context);
pair.Value.Restore(cb);
}
}
}
///
/// Return the ClassBase-derived instance that implements a particular
/// reflected managed type, creating it if it doesn't yet exist.
///
internal static BorrowedReference GetClass(Type type) => ReflectedClrType.GetOrCreate(type);
internal static ClassBase GetClassImpl(Type type)
{
var pyType = GetClass(type);
var impl = (ClassBase)ManagedType.GetManagedObject(pyType)!;
Debug.Assert(impl is not null);
return impl!;
}
///
/// Create a new ClassBase-derived instance that implements a reflected
/// managed type. The new object will be associated with a generated
/// Python type object.
///
internal static ClassBase CreateClass(Type type)
{
// Next, select the appropriate managed implementation class.
// Different kinds of types, such as array types or interface
// types, want to vary certain implementation details to make
// sure that the type semantics are consistent in Python.
ClassBase impl;
// Check to see if the given type extends System.Exception. This
// lets us check once (vs. on every lookup) in case we need to
// wrap Exception-derived types in old-style classes
if (type.ContainsGenericParameters)
{
impl = new GenericType(type);
}
else if (type.IsSubclassOf(dtype))
{
impl = new DelegateObject(type);
}
else if (type.IsArray)
{
impl = new ArrayObject(type);
}
else if (type.IsInterface)
{
impl = new InterfaceObject(type);
}
else if (type == typeof(Exception) ||
type.IsSubclassOf(typeof(Exception)))
{
impl = new ExceptionClassObject(type);
}
#pragma warning disable CS0618 // Type or member is obsolete. OK for internal use.
else if (null != PythonDerivedType.GetPyObjField(type))
#pragma warning restore CS0618 // Type or member is obsolete
{
impl = new ClassDerivedObject(type);
}
else
{
impl = new ClassObject(type);
}
return impl;
}
internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType pyType)
{
// First, we introspect the managed type and build some class
// information, including generating the member descriptors
// that we'll be putting in the Python class __dict__.
ClassInfo info = GetClassInfo(type, impl);
impl.indexer = info.indexer;
impl.del = info.del;
impl.richcompare.Clear();
// Finally, initialize the class __dict__ and return the object.
using var newDict = Runtime.PyObject_GenericGetDict(pyType.Reference);
BorrowedReference dict = newDict.Borrow();
foreach (var iter in info.members)
{
var item = iter.Value;
var name = iter.Key;
impl.dotNetMembers.Add(name);
Runtime.PyDict_SetItemString(dict, name, item);
if (ClassBase.CilToPyOpMap.TryGetValue(name, out var pyOp)
// workaround for unintialized types crashing in GetManagedObject
&& item is not ReflectedClrType
&& ManagedType.GetManagedObject(item) is MethodObject method)
{
impl.richcompare.Add(pyOp, method);
}
}
// If class has constructors, generate an __doc__ attribute.
NewReference doc = default;
Type marker = typeof(DocStringAttribute);
var attrs = (Attribute[])type.GetCustomAttributes(marker, false);
if (attrs.Length != 0)
{
var attr = (DocStringAttribute)attrs[0];
string docStr = attr.DocString;
doc = Runtime.PyString_FromString(docStr);
Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, doc.Borrow());
}
// If this is a ClassObject AND it has constructors, generate a __doc__ attribute.
// required that the ClassObject.ctors be changed to internal
if (impl is ClassObject co)
{
if (co.NumCtors > 0 && !co.HasCustomNew())
{
// Implement Overloads on the class object
if (!CLRModule._SuppressOverloads)
{
// HACK: __init__ points to instance constructors.
// When unbound they fully instantiate object, so we get overloads for free from MethodBinding.
var init = info.members["__init__"];
// TODO: deprecate __overloads__ soon...
Runtime.PyDict_SetItem(dict, PyIdentifier.__overloads__, init);
Runtime.PyDict_SetItem(dict, PyIdentifier.Overloads, init);
}
// don't generate the docstring if one was already set from a DocStringAttribute.
if (!CLRModule._SuppressDocs && doc.IsNull())
{
doc = co.GetDocString();
Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, doc.Borrow());
}
}
if (Runtime.PySequence_Contains(dict, PyIdentifier.__doc__) != 1)
{
// Ensure that at least some doc string is set
using var fallbackDoc = Runtime.PyString_FromString(
$"Python wrapper for .NET type {type}"
);
Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, fallbackDoc.Borrow());
}
}
doc.Dispose();
// The type has been modified after PyType_Ready has been called
// Refresh the type
Runtime.PyType_Modified(pyType.Reference);
}
internal static bool ShouldBindMethod(MethodBase mb)
{
if (mb is null) throw new ArgumentNullException(nameof(mb));
return (mb.IsPublic || mb.IsFamily || mb.IsFamilyOrAssembly);
}
internal static bool ShouldBindField(FieldInfo fi)
{
if (fi is null) throw new ArgumentNullException(nameof(fi));
return (fi.IsPublic || fi.IsFamily || fi.IsFamilyOrAssembly);
}
internal static bool ShouldBindProperty(PropertyInfo pi)
{
MethodInfo? mm;
try
{
mm = pi.GetGetMethod(true);
if (mm == null)
{
mm = pi.GetSetMethod(true);
}
}
catch (SecurityException)
{
// GetGetMethod may try to get a method protected by
// StrongNameIdentityPermission - effectively private.
return false;
}
if (mm == null)
{
return false;
}
return ShouldBindMethod(mm);
}
internal static bool ShouldBindEvent(EventInfo ei)
{
return ei.GetAddMethod(true) is { } add && ShouldBindMethod(add);
}
private static ClassInfo GetClassInfo(Type type, ClassBase impl)
{
var ci = new ClassInfo();
var methods = new Dictionary>();
MethodInfo meth;
ExtensionType ob;
string name;
Type tp;
int i, n;
MemberInfo[] info = type.GetMembers(BindingFlags);
var local = new HashSet();
var items = new List();
MemberInfo m;
// Loop through once to find out which names are declared
for (i = 0; i < info.Length; i++)
{
m = info[i];
if (m.DeclaringType == type)
{
local.Add(m.Name);
}
}
if (type.IsEnum)
{
var opsImpl = typeof(EnumOps<>).MakeGenericType(type);
foreach (var op in opsImpl.GetMethods(OpsHelper.BindingFlags))
{
local.Add(op.Name);
}
info = info.Concat(opsImpl.GetMethods(OpsHelper.BindingFlags)).ToArray();
// only [Flags] enums support bitwise operations
if (type.IsFlagsEnum())
{
opsImpl = typeof(FlagEnumOps<>).MakeGenericType(type);
foreach (var op in opsImpl.GetMethods(OpsHelper.BindingFlags))
{
local.Add(op.Name);
}
info = info.Concat(opsImpl.GetMethods(OpsHelper.BindingFlags)).ToArray();
}
}
// Now again to filter w/o losing overloaded member info
for (i = 0; i < info.Length; i++)
{
m = info[i];
if (local.Contains(m.Name))
{
items.Add(m);
}
}
if (type.IsInterface)
{
// Interface inheritance seems to be a different animal:
// more contractual, less structural. Thus, a Type that
// represents an interface that inherits from another
// interface does not return the inherited interface's
// methods in GetMembers. For example ICollection inherits
// from IEnumerable, but ICollection's GetMemebers does not
// return GetEnumerator.
//
// Not sure if this is the correct way to fix this, but it
// seems to work. Thanks to Bruce Dodson for the fix.
Type[] inheritedInterfaces = type.GetInterfaces();
for (i = 0; i < inheritedInterfaces.Length; ++i)
{
Type inheritedType = inheritedInterfaces[i];
MemberInfo[] imembers = inheritedType.GetMembers(BindingFlags);
for (n = 0; n < imembers.Length; n++)
{
m = imembers[n];
if (!local.Contains(m.Name))
{
items.Add(m);
}
}
}
// All interface implementations inherit from Object,
// but GetMembers don't return them either.
var objFlags = BindingFlags.Public | BindingFlags.Instance;
foreach (var mi in typeof(object).GetMembers(objFlags))
{
if (!local.Contains(mi.Name) && mi is not ConstructorInfo)
{
items.Add(mi);
}
}
}
for (i = 0; i < items.Count; i++)
{
var mi = (MemberInfo)items[i];
switch (mi.MemberType)
{
case MemberTypes.Method:
meth = (MethodInfo)mi;
if (!ShouldBindMethod(meth))
{
continue;
}
name = meth.Name;
//TODO mangle?
if (name == "__init__" && !impl.HasCustomNew())
continue;
if (!methods.TryGetValue(name, out var methodList))
{
methodList = methods[name] = new List();
}
methodList.Add(meth);
continue;
case MemberTypes.Constructor when !impl.HasCustomNew():
var ctor = (ConstructorInfo)mi;
if (ctor.IsStatic)
{
continue;
}
name = "__init__";
if (!methods.TryGetValue(name, out methodList))
{
methodList = methods[name] = new List();
}
methodList.Add(ctor);
continue;
case MemberTypes.Property:
var pi = (PropertyInfo)mi;
if(!ShouldBindProperty(pi))
{
continue;
}
// Check for indexer
ParameterInfo[] args = pi.GetIndexParameters();
if (args.GetLength(0) > 0)
{
Indexer? idx = ci.indexer;
if (idx == null)
{
ci.indexer = new Indexer();
idx = ci.indexer;
}
idx.AddProperty(pi);
continue;
}
ob = new PropertyObject(pi);
ci.members[pi.Name] = ob.AllocObject();
continue;
case MemberTypes.Field:
var fi = (FieldInfo)mi;
if (!ShouldBindField(fi))
{
continue;
}
ob = new FieldObject(fi);
ci.members[mi.Name] = ob.AllocObject();
continue;
case MemberTypes.Event:
var ei = (EventInfo)mi;
if (!ShouldBindEvent(ei))
{
continue;
}
ob = ei.AddMethod.IsStatic
? new EventBinding(ei)
: new EventObject(ei);
ci.members[ei.Name] = ob.AllocObject();
continue;
case MemberTypes.NestedType:
tp = (Type)mi;
if (!(tp.IsNestedPublic || tp.IsNestedFamily ||
tp.IsNestedFamORAssem))
{
continue;
}
// Note the given instance might be uninitialized
var pyType = GetClass(tp);
// make a copy, that could be disposed later
ci.members[mi.Name] = new ReflectedClrType(pyType);
continue;
}
}
foreach (var iter in methods)
{
name = iter.Key;
var mlist = iter.Value.ToArray();
ob = new MethodObject(type, name, mlist);
ci.members[name] = ob.AllocObject();
if (name == nameof(IDictionary.Remove)
&& mlist.Any(m => m.DeclaringType?.GetInterfaces()
.Any(i => i.TryGetGenericDefinition() == typeof(IDictionary<,>)) is true))
{
ci.del = new();
ci.del.AddRange(mlist.Where(m => !m.IsStatic));
}
else if (name == nameof(IList.RemoveAt)
&& mlist.Any(m => m.DeclaringType?.GetInterfaces()
.Any(i => i.TryGetGenericDefinition() == typeof(IList<>)) is true))
{
ci.del = new();
ci.del.AddRange(mlist.Where(m => !m.IsStatic));
}
if (mlist.Any(OperatorMethod.IsOperatorMethod))
{
string pyName = OperatorMethod.GetPyMethodName(name);
string pyNameReverse = OperatorMethod.ReversePyMethodName(pyName);
OperatorMethod.FilterMethods(mlist, out var forwardMethods, out var reverseMethods);
// Only methods where the left operand is the declaring type.
if (forwardMethods.Length > 0)
ci.members[pyName] = new MethodObject(type, name, forwardMethods).AllocObject();
// Only methods where only the right operand is the declaring type.
if (reverseMethods.Length > 0)
ci.members[pyNameReverse] = new MethodObject(type, name, reverseMethods, argsReversed: true).AllocObject();
}
}
if (ci.indexer == null && type.IsClass)
{
// Indexer may be inherited.
var parent = type.BaseType;
while (parent != null && ci.indexer == null)
{
foreach (var prop in parent.GetProperties()) {
var args = prop.GetIndexParameters();
if (args.GetLength(0) > 0)
{
ci.indexer = new Indexer();
ci.indexer.AddProperty(prop);
break;
}
}
parent = parent.BaseType;
}
}
return ci;
}
///
/// This class owns references to PyObjects in the `members` member.
/// The caller has responsibility to DECREF them.
///
private class ClassInfo
{
public Indexer? indexer;
public MethodBinder? del;
public readonly Dictionary members = new();
internal ClassInfo()
{
indexer = null;
}
}
}
}