using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
using Jint.Native;
using Jint.Native.Object;
using Jint.Runtime.Descriptors;
#pragma warning disable IL3050
namespace Jint.Runtime.Interop;
///
/// Any instance on this class represents a reference to a CLR namespace.
/// Accessing its properties will look for a class of the full name, or instantiate
/// a new as it assumes that the property is a deeper
/// level of the current namespace
///
[RequiresUnreferencedCode("Dynamic loading")]
public class NamespaceReference : ObjectInstance, ICallable
{
private readonly string? _path;
public NamespaceReference(Engine engine, string? path) : base(engine)
{
_path = path;
}
public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc)
{
return false;
}
public override bool Delete(JsValue property)
{
return false;
}
JsValue ICallable.Call(JsValue thisObject, params JsCallArguments arguments)
{
// direct calls on a NamespaceReference constructor object is creating a generic type
var genericTypes = new Type[arguments.Length];
for (int i = 0; i < arguments.Length; i++)
{
var genericTypeReference = arguments[i];
if (genericTypeReference.IsUndefined()
|| !genericTypeReference.IsObject()
|| genericTypeReference.AsObject() is not TypeReference tr)
{
Throw.TypeError(_engine.Realm, "Invalid generic type parameter on " + _path + ", if this is not a generic type / method, are you missing a lookup assembly?");
return default;
}
genericTypes[i] = tr.ReferenceType;
}
var typeReference = GetPath(_path + "`" + arguments.Length.ToString(CultureInfo.InvariantCulture)).As();
if (typeReference is null)
{
return Undefined;
}
try
{
var genericType = typeReference.ReferenceType.MakeGenericType(genericTypes);
return TypeReference.CreateTypeReference(Engine, genericType);
}
catch (Exception e)
{
Throw.InvalidOperationException($"Invalid generic type parameter on {_path}, if this is not a generic type / method, are you missing a lookup assembly?", e);
return null;
}
}
public override JsValue Get(JsValue property, JsValue receiver)
{
var newPath = string.IsNullOrEmpty(_path)
? property.ToString()
: $"{_path}.{property}";
return GetPath(newPath);
}
[RequiresUnreferencedCode("Dynamic loading")]
public JsValue GetPath(string path)
{
if (_engine.TypeCache.TryGetValue(path, out var type))
{
if (type == null)
{
return new NamespaceReference(_engine, path);
}
return TypeReference.CreateTypeReference(_engine, type);
}
// in CoreCLR, for example, classes that used to be in
// mscorlib were moved away, and only stubs remained, because
// of that, we do the search on the lookup assemblies first,
// and only then in mscorlib. Probelm usage: System.IO.File.CreateText
// search in loaded assemblies
var lookupAssemblies = new[] { Assembly.GetCallingAssembly(), Assembly.GetExecutingAssembly() };
foreach (var assembly in lookupAssemblies)
{
type = assembly.GetType(path);
if (type != null)
{
_engine.TypeCache.Add(path, type);
return TypeReference.CreateTypeReference(_engine, type);
}
}
// search in lookup assemblies
var comparedPath = path.Replace('+', '.');
foreach (var assembly in _engine.Options.Interop.AllowedAssemblies)
{
type = assembly.GetType(path);
if (type != null)
{
_engine.TypeCache.Add(path, type);
return TypeReference.CreateTypeReference(_engine, type);
}
var lastPeriodPos = path.LastIndexOf('.');
if (lastPeriodPos != -1)
{
var trimPath = path.Substring(0, lastPeriodPos);
type = GetType(assembly, trimPath);
}
if (type != null)
{
foreach (Type nType in GetAllNestedTypes(type))
{
if (nType.FullName != null && nType.FullName.Replace('+', '.').Equals(comparedPath, StringComparison.Ordinal))
{
_engine.TypeCache.Add(comparedPath, nType);
return TypeReference.CreateTypeReference(_engine, nType);
}
}
}
}
// search for type in mscorlib
type = System.Type.GetType(path);
if (type != null)
{
_engine.TypeCache.Add(path, type);
return TypeReference.CreateTypeReference(_engine, type);
}
// the new path doesn't represent a known class, thus return a new namespace instance
_engine.TypeCache.Add(path, null);
return new NamespaceReference(_engine, path);
}
/// Gets a type.
///Nested type separators are converted to '.' instead of '+'
/// The assembly.
/// Name of the type.
///
/// The type.
[RequiresUnreferencedCode("Assembly type loading")]
private static Type? GetType(Assembly assembly, string typeName)
{
var compared = typeName.Replace('+', '.');
foreach (Type t in assembly.GetTypes())
{
if (string.Equals(t.FullName?.Replace('+', '.'), compared, StringComparison.Ordinal))
{
return t;
}
}
return null;
}
private static Type[] GetAllNestedTypes(Type type)
{
var types = new List();
AddNestedTypesRecursively(types, type);
return types.ToArray();
}
private static void AddNestedTypesRecursively(
List types,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicNestedTypes)] Type type)
{
foreach (var nestedType in type.GetNestedTypes(BindingFlags.Public))
{
types.Add(nestedType);
AddNestedTypesRecursively(types, nestedType);
}
}
public override PropertyDescriptor GetOwnProperty(JsValue property)
{
return PropertyDescriptor.Undefined;
}
public override string ToString()
{
return "[CLR namespace: " + _path + "]";
}
}