// Parse a string specifying a list of assemblies and types // containing a startup hook, and call each hook in turn. private static void ProcessStartupHooks() { if (!IsSupported) { return; } // Initialize tracing before any user code can be called if EventSource is enabled. if (EventSource.IsSupported) { System.Diagnostics.Tracing.RuntimeEventSource.Initialize(); } string?startupHooksVariable = AppContext.GetData("STARTUP_HOOKS") as string; if (startupHooksVariable == null) { return; } ReadOnlySpan <char> disallowedSimpleAssemblyNameChars = stackalloc char[4] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar, ' ', ',' }; // Parse startup hooks variable string[] startupHookParts = startupHooksVariable.Split(Path.PathSeparator); StartupHookNameOrPath[] startupHooks = new StartupHookNameOrPath[startupHookParts.Length]; for (int i = 0; i < startupHookParts.Length; i++) { string startupHookPart = startupHookParts[i]; if (string.IsNullOrEmpty(startupHookPart)) { // Leave the slot in startupHooks empty (nulls for everything). This is simpler than shifting and resizing the array. continue; } if (Path.IsPathFullyQualified(startupHookPart)) { startupHooks[i].Path = startupHookPart; } else { // The intent here is to only support simple assembly names, but AssemblyName .ctor accepts // lot of other forms (fully qualified assembly name, strings which look like relative paths and so on). // So add a check on top which will disallow any directory separator, space or comma in the assembly name. for (int j = 0; j < disallowedSimpleAssemblyNameChars.Length; j++) { if (startupHookPart.Contains(disallowedSimpleAssemblyNameChars[j])) { throw new ArgumentException(SR.Format(SR.Argument_InvalidStartupHookSimpleAssemblyName, startupHookPart)); } } if (startupHookPart.EndsWith(DisallowedSimpleAssemblyNameSuffix, StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException(SR.Format(SR.Argument_InvalidStartupHookSimpleAssemblyName, startupHookPart)); } try { // This will throw if the string is not a valid assembly name. startupHooks[i].AssemblyName = new AssemblyName(startupHookPart); } catch (Exception assemblyNameException) { throw new ArgumentException(SR.Format(SR.Argument_InvalidStartupHookSimpleAssemblyName, startupHookPart), assemblyNameException); } } } // Call each hook in turn foreach (StartupHookNameOrPath startupHook in startupHooks) { #pragma warning disable IL2026 // suppressed in ILLink.Suppressions.LibraryBuild.xml CallStartupHook(startupHook); #pragma warning restore IL2026 } }
private static void CallStartupHook(StartupHookNameOrPath startupHook) { Assembly assembly; try { if (startupHook.Path != null) { Debug.Assert(Path.IsPathFullyQualified(startupHook.Path)); assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(startupHook.Path); } else if (startupHook.AssemblyName != null) { Debug.Assert(startupHook.AssemblyName != null); assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(startupHook.AssemblyName); } else { // Empty slot - skip it return; } } catch (Exception assemblyLoadException) { throw new ArgumentException( SR.Format(SR.Argument_StartupHookAssemblyLoadFailed, startupHook.Path ?? startupHook.AssemblyName !.ToString()), assemblyLoadException); } Debug.Assert(assembly != null); Type type = assembly.GetType(StartupHookTypeName, throwOnError: true) !; // Look for a static method without any parameters MethodInfo?initializeMethod = type.GetMethod(InitializeMethodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, null, // use default binder Type.EmptyTypes, // parameters null); // no parameter modifiers bool wrongSignature = false; if (initializeMethod == null) { // There weren't any static methods without // parameters. Look for any methods with the correct // name, to provide precise error handling. try { // This could find zero, one, or multiple methods // with the correct name. initializeMethod = type.GetMethod(InitializeMethodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance); } catch (AmbiguousMatchException) { // Found multiple. Will throw below due to initializeMethod being null. Debug.Assert(initializeMethod == null); } if (initializeMethod != null) { // Found one wrongSignature = true; } else { // Didn't find any throw new MissingMethodException(StartupHookTypeName, InitializeMethodName); } } else if (initializeMethod.ReturnType != typeof(void)) { wrongSignature = true; } if (wrongSignature) { throw new ArgumentException(SR.Format(SR.Argument_InvalidStartupHookSignature, StartupHookTypeName + Type.Delimiter + InitializeMethodName, startupHook.Path ?? startupHook.AssemblyName.ToString())); } Debug.Assert(initializeMethod != null && initializeMethod.IsStatic && initializeMethod.ReturnType == typeof(void) && initializeMethod.GetParameters().Length == 0); initializeMethod.Invoke(null, null); }