You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

260 lines
12 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. #nullable enable
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Reflection;
  6. using IPA.Logging;
  7. using IPA.Utilities;
  8. using IPA.AntiMalware;
  9. #if NET4
  10. using Expression = System.Linq.Expressions.Expression;
  11. using ExpressionEx = System.Linq.Expressions.Expression;
  12. #endif
  13. #if NET3
  14. using Net3_Proxy;
  15. #endif
  16. namespace IPA.Loader
  17. {
  18. /// <summary>
  19. /// The type that handles value injecting into a plugin's initialization methods.
  20. /// </summary>
  21. /// <remarks>
  22. /// The default injectors and what they provide are shown in this table.
  23. /// <list type="table">
  24. /// <listheader>
  25. /// <term>Parameter Type</term>
  26. /// <description>Injected Value</description>
  27. /// </listheader>
  28. /// <item>
  29. /// <term><see cref="Logger"/></term>
  30. /// <description>A <see cref="StandardLogger"/> specialized for the plugin being injected</description>
  31. /// </item>
  32. /// <item>
  33. /// <term><see cref="PluginMetadata"/></term>
  34. /// <description>The <see cref="PluginMetadata"/> of the plugin being injected</description>
  35. /// </item>
  36. /// <item>
  37. /// <term><see cref="Config.Config"/></term>
  38. /// <description>A <see cref="Config.Config"/> object for the plugin being injected.
  39. /// <para>
  40. /// These parameters may have <see cref="Config.Config.NameAttribute"/> and <see cref="Config.Config.PreferAttribute"/> to control
  41. /// how it is constructed.
  42. /// </para>
  43. /// </description>
  44. /// </item>
  45. /// <item>
  46. /// <term><see cref="IAntiMalware"/></term>
  47. /// <description>The <see cref="IAntiMalware"/> instance which should be used for any potentially dangerous files.</description>
  48. /// </item>
  49. /// </list>
  50. /// For all of the default injectors, only one of each will be generated, and any later parameters will recieve the same value as the first one.
  51. /// </remarks>
  52. public static class PluginInitInjector
  53. {
  54. /// <summary>
  55. /// A typed injector for a plugin's Init method. When registered, called for all associated types. If it returns null, the default for the type will be used.
  56. /// </summary>
  57. /// <param name="previous">the previous return value of the function, or <see langword="null"/> if never called for plugin.</param>
  58. /// <param name="param">the <see cref="ParameterInfo"/> of the parameter being injected.</param>
  59. /// <param name="meta">the <see cref="PluginMetadata"/> for the plugin being loaded.</param>
  60. /// <returns>the value to inject into that parameter.</returns>
  61. public delegate object? InjectParameter(object? previous, ParameterInfo param, PluginMetadata meta);
  62. /// <summary>
  63. /// A provider for parameter injectors to request injected values themselves.
  64. /// </summary>
  65. /// <remarks>
  66. /// Some injectors may look at attributes on the parameter to gain additional information about what it should provide.
  67. /// If an injector wants to allow end users to affect the things it requests, it may pass the parameter it is currently
  68. /// injecting for to this delegate along with a type override to select some other type.
  69. /// </remarks>
  70. /// <param name="forParam">the parameter that this is providing for.</param>
  71. /// <param name="typeOverride">an optional override for the parameter type.</param>
  72. /// <returns>the value that would otherwise be injected.</returns>
  73. public delegate object? InjectedValueProvider(ParameterInfo forParam, Type? typeOverride = null);
  74. /// <summary>
  75. /// A typed injector for a plugin's Init method. When registered, called for all associated types. If it returns null, the default for the type will be used.
  76. /// </summary>
  77. /// <param name="previous">the previous return value of the function, or <see langword="null"/> if never called for plugin.</param>
  78. /// <param name="param">the <see cref="ParameterInfo"/> of the parameter being injected.</param>
  79. /// <param name="meta">the <see cref="PluginMetadata"/> for the plugin being loaded.</param>
  80. /// <param name="provider">an <see cref="InjectedValueProvider"/> to allow the injector to request injected values.</param>
  81. /// <returns>the value to inject into that parameter.</returns>
  82. public delegate object? InjectParameterNested(object? previous, ParameterInfo param, PluginMetadata meta, InjectedValueProvider provider);
  83. /// <summary>
  84. /// Invokes the provider with <paramref name="param"/> and <typeparamref name="T"/> and casts the result to <typeparamref name="T"/>.
  85. /// </summary>
  86. /// <typeparam name="T">the type of object to be injected</typeparam>
  87. /// <param name="provider">the provider to invoke.</param>
  88. /// <param name="param">the parameter to provide for</param>
  89. /// <returns>the value requested, or <see langword="null"/>.</returns>
  90. public static T? Inject<T>(this InjectedValueProvider provider, ParameterInfo param)
  91. => (T?)provider?.Invoke(param, typeof(T));
  92. /// <summary>
  93. /// Adds an injector to be used when calling future plugins' Init methods.
  94. /// </summary>
  95. /// <param name="type">the type of the parameter.</param>
  96. /// <param name="injector">the function to call for injection.</param>
  97. public static void AddInjector(Type type, InjectParameter injector)
  98. => AddInjector(type, (pre, par, met, pro) => injector(pre, par, met));
  99. /// <summary>
  100. /// Adds an injector to be used when calling future plugins' Init methods.
  101. /// </summary>
  102. /// <param name="type">the type of the parameter.</param>
  103. /// <param name="injector">the function to call for injection.</param>
  104. public static void AddInjector(Type type, InjectParameterNested injector)
  105. {
  106. injectors.Add(new TypedInjector(type, injector));
  107. }
  108. private struct TypedInjector : IEquatable<TypedInjector>
  109. {
  110. public Type Type;
  111. public InjectParameterNested Injector;
  112. public TypedInjector(Type t, InjectParameterNested i)
  113. { Type = t; Injector = i; }
  114. public object? Inject(object? prev, ParameterInfo info, PluginMetadata meta, InjectedValueProvider provider)
  115. => Injector(prev, info, meta, provider);
  116. public bool Equals(TypedInjector other)
  117. => Type == other.Type && Injector == other.Injector;
  118. public override bool Equals(object obj)
  119. => obj is TypedInjector i && Equals(i);
  120. public override int GetHashCode()
  121. => Type.GetHashCode() ^ Injector.GetHashCode();
  122. public static bool operator ==(TypedInjector a, TypedInjector b) => a.Equals(b);
  123. public static bool operator !=(TypedInjector a, TypedInjector b) => !a.Equals(b);
  124. }
  125. private static readonly List<TypedInjector> injectors = new()
  126. {
  127. new TypedInjector(typeof(Logger), (prev, param, meta, _) => prev ?? new StandardLogger(meta.Name)),
  128. new TypedInjector(typeof(PluginMetadata), (prev, param, meta, _) => prev ?? meta),
  129. new TypedInjector(typeof(Config.Config), (prev, param, meta, _) => prev ?? Config.Config.GetConfigFor(meta.Name, param)),
  130. new TypedInjector(typeof(IAntiMalware), (prev, param, meta, _) => prev ?? AntiMalwareEngine.Engine)
  131. };
  132. private static int? MatchPriority(Type target, Type source)
  133. {
  134. if (target == source) return int.MaxValue;
  135. if (!target.IsAssignableFrom(source)) return null;
  136. if (!target.IsInterface && !source.IsSubclassOf(target)) return int.MinValue;
  137. int value = int.MaxValue - 1;
  138. while (true)
  139. {
  140. if (source is null) return value;
  141. if (target.IsInterface && source.GetInterfaces().Contains(target))
  142. return value;
  143. else if (target == source)
  144. return value;
  145. else
  146. {
  147. value--; // lower priority
  148. source = source.BaseType;
  149. }
  150. }
  151. }
  152. private static readonly MethodInfo InjectMethod = typeof(PluginInitInjector).GetMethod(nameof(Inject), BindingFlags.NonPublic | BindingFlags.Static);
  153. internal static Expression InjectedCallExpr(ParameterInfo[] initParams, Expression meta, Expression persistVar, Func<IEnumerable<Expression>, Expression> exprGen)
  154. {
  155. var arr = ExpressionEx.Variable(typeof(object[]), "initArr");
  156. return ExpressionEx.Block(new[] { arr },
  157. ExpressionEx.Assign(arr, Expression.Call(InjectMethod, Expression.Constant(initParams), meta, persistVar)),
  158. exprGen(initParams
  159. .Select(p => p.ParameterType)
  160. .Select((t, i) => (Expression)Expression.Convert(
  161. Expression.ArrayIndex(arr, Expression.Constant(i)), t))));
  162. }
  163. private static object? InjectForParameter(
  164. Dictionary<TypedInjector, object?> previousValues,
  165. PluginMetadata meta,
  166. ParameterInfo param,
  167. Type paramType,
  168. InjectedValueProvider provider)
  169. {
  170. var value = paramType.GetDefault();
  171. var toUse = injectors
  172. .Select(i => (inject: i, priority: MatchPriority(paramType, i.Type))) // check match priority, combine it
  173. .NonNull(t => t.priority) // filter null priorities
  174. .Select(t => (t.inject, priority: t.priority!.Value)) // remove nullable
  175. .OrderByDescending(t => t.priority) // sort by value
  176. .Select(t => t.inject); // remove priority value
  177. // this tries injectors in order of closest match by type provided
  178. foreach (var pair in toUse)
  179. {
  180. object? prev = null;
  181. if (previousValues.ContainsKey(pair))
  182. prev = previousValues[pair];
  183. var val = pair.Inject(prev, param, meta, provider);
  184. previousValues[pair] = val;
  185. if (val == null) continue;
  186. value = val;
  187. break;
  188. }
  189. return value;
  190. }
  191. private class InjectedValueProviderWrapperImplementation
  192. {
  193. public Dictionary<TypedInjector, object?> PreviousValues { get; }
  194. public PluginMetadata Meta { get; }
  195. public InjectedValueProvider Provider { get; }
  196. public InjectedValueProviderWrapperImplementation(PluginMetadata meta)
  197. {
  198. Meta = meta;
  199. PreviousValues = new();
  200. Provider = Inject;
  201. }
  202. private object? Inject(ParameterInfo param, Type? typeOverride = null)
  203. => InjectForParameter(PreviousValues, Meta, param, typeOverride ?? param.ParameterType, Provider);
  204. }
  205. internal static object?[] Inject(ParameterInfo[] initParams, PluginMetadata meta, ref object? persist)
  206. {
  207. var initArgs = new List<object?>();
  208. var impl = persist as InjectedValueProviderWrapperImplementation;
  209. if (impl == null || impl.Meta != meta)
  210. {
  211. impl = new(meta);
  212. persist = impl;
  213. }
  214. foreach (var param in initParams)
  215. {
  216. var paramType = param.ParameterType;
  217. var value = InjectForParameter(impl.PreviousValues, meta, param, paramType, impl.Provider);
  218. initArgs.Add(value);
  219. }
  220. return initArgs.ToArray();
  221. }
  222. }
  223. }