summaryrefslogtreecommitdiff
path: root/src/System.Private.CoreLib/shared/System/Runtime/Loader/AssemblyLoadContext.cs
blob: 1c19e0971c51f224cae0bdf9ff2e6dfef898beb2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;

namespace System.Runtime.Loader
{
    public partial class AssemblyLoadContext
    {
        private enum InternalState
        {
            /// <summary>
            /// The ALC is alive (default)
            /// </summary>
            Alive,

            /// <summary>
            /// The unload process has started, the Unloading event will be called
            /// once the underlying LoaderAllocator has been finalized
            /// </summary>
            Unloading
        }

        private static readonly Dictionary<long, WeakReference<AssemblyLoadContext>> s_allContexts = new Dictionary<long, WeakReference<AssemblyLoadContext>>();
        private static long s_nextId;

#region private data members
        // If you modify any of these fields, you must also update the
        // AssemblyLoadContextBaseObject structure in object.h

        // synchronization primitive to protect against usage of this instance while unloading
        private readonly object _unloadLock;

        private event Func<Assembly, string, IntPtr> _resolvingUnmanagedDll = null!;

        private event Func<AssemblyLoadContext, AssemblyName, Assembly> _resolving = null!;

        private event Action<AssemblyLoadContext> _unloading = null!;

        private readonly string? _name;

        // Contains the reference to VM's representation of the AssemblyLoadContext
        private readonly IntPtr _nativeAssemblyLoadContext;

        // Id used by s_allContexts
        private readonly long _id;

        // Indicates the state of this ALC (Alive or in Unloading state)
        private InternalState _state;

        private readonly bool _isCollectible;
#endregion

        protected AssemblyLoadContext() : this(false, false, null)
        {
        }

        protected AssemblyLoadContext(bool isCollectible) : this(false, isCollectible, null)
        {
        }

        public AssemblyLoadContext(string? name, bool isCollectible = false) : this(false, isCollectible, name)
        {
        }

        private protected AssemblyLoadContext(bool representsTPALoadContext, bool isCollectible, string? name)
        {
            // Initialize the VM side of AssemblyLoadContext if not already done.
            _isCollectible = isCollectible;

            _name = name;

            // The _unloadLock needs to be assigned after the IsCollectible to ensure proper behavior of the finalizer
            // even in case the following allocation fails or the thread is aborted between these two lines.
            _unloadLock = new object();

            if (!isCollectible)
            {
                // For non collectible AssemblyLoadContext, the finalizer should never be called and thus the AssemblyLoadContext should not
                // be on the finalizer queue.
                GC.SuppressFinalize(this);
            }

            // If this is a collectible ALC, we are creating a weak handle tracking resurrection otherwise we use a strong handle
            var thisHandle = GCHandle.Alloc(this, IsCollectible ? GCHandleType.WeakTrackResurrection : GCHandleType.Normal);
            var thisHandlePtr = GCHandle.ToIntPtr(thisHandle);
            _nativeAssemblyLoadContext = InitializeAssemblyLoadContext(thisHandlePtr, representsTPALoadContext, isCollectible);

            // Add this instance to the list of alive ALC
            lock (s_allContexts)
            {
                _id = s_nextId++;
                s_allContexts.Add(_id, new WeakReference<AssemblyLoadContext>(this, true));
            }
        }

        ~AssemblyLoadContext()
        {
            // Use the _unloadLock as a guard to detect the corner case when the constructor of the AssemblyLoadContext was not executed
            // e.g. due to the JIT failing to JIT it.
            if (_unloadLock != null)
            {
                // Only valid for a Collectible ALC. Non-collectible ALCs have the finalizer suppressed.
                Debug.Assert(IsCollectible);
                // We get here only in case the explicit Unload was not initiated.
                Debug.Assert(_state != InternalState.Unloading);
                InitiateUnload();
            }
        }

        private void RaiseUnloadEvent()
        {
            // Ensure that we raise the Unload event only once
            Interlocked.Exchange(ref _unloading, null!)?.Invoke(this);
        }

        private void InitiateUnload()
        {
            RaiseUnloadEvent();

            // When in Unloading state, we are not supposed to be called on the finalizer
            // as the native side is holding a strong reference after calling Unload
            lock (_unloadLock)
            {
                Debug.Assert(_state == InternalState.Alive);

                var thisStrongHandle = GCHandle.Alloc(this, GCHandleType.Normal);
                var thisStrongHandlePtr = GCHandle.ToIntPtr(thisStrongHandle);
                // The underlying code will transform the original weak handle
                // created by InitializeLoadContext to a strong handle
                PrepareForAssemblyLoadContextRelease(_nativeAssemblyLoadContext, thisStrongHandlePtr);

                _state = InternalState.Unloading;
            }

            lock (s_allContexts)
            {
                s_allContexts.Remove(_id);
            }
        }

        public IEnumerable<Assembly> Assemblies
        {
            get
            {
                foreach (Assembly a in GetLoadedAssemblies())
                {
                    AssemblyLoadContext? alc = GetLoadContext(a);

                    if (alc == this)
                    {
                        yield return a;
                    }
                }
            }
        }

        // Event handler for resolving native libraries.
        // This event is raised if the native library could not be resolved via
        // the default resolution logic [including AssemblyLoadContext.LoadUnmanagedDll()]
        //
        // Inputs: Invoking assembly, and library name to resolve
        // Returns: A handle to the loaded native library
        public event Func<Assembly, string, IntPtr> ResolvingUnmanagedDll // TODO-NULLABLE: Should all events use nullable delegate types?
        {
            add
            {
                _resolvingUnmanagedDll += value;
            }
            remove
            {
                _resolvingUnmanagedDll -= value;
            }
        }

        // Event handler for resolving managed assemblies.
        // This event is raised if the managed assembly could not be resolved via
        // the default resolution logic [including AssemblyLoadContext.Load()]
        //
        // Inputs: The AssemblyLoadContext and AssemblyName to be loaded
        // Returns: The Loaded assembly object.
        public event Func<AssemblyLoadContext, AssemblyName, Assembly?> Resolving // TODO-NULLABLE: Should all events use nullable delegate types?
        {
            add
            {
                _resolving += value;
            }
            remove
            {
                _resolving -= value;
            }
        }

        public event Action<AssemblyLoadContext> Unloading // TODO-NULLABLE: Should all events use nullable delegate types?
        {
            add
            {
                _unloading += value;
            }
            remove
            {
                _unloading -= value;
            }
        }

#region AppDomainEvents
        // Occurs when an Assembly is loaded
        internal static event AssemblyLoadEventHandler AssemblyLoad; // TODO-NULLABLE: Should all events use nullable delegate types?

        // Occurs when resolution of type fails
        internal static event ResolveEventHandler TypeResolve; // TODO-NULLABLE: Should all events use nullable delegate types?

        // Occurs when resolution of resource fails
        internal static event ResolveEventHandler ResourceResolve; // TODO-NULLABLE: Should all events use nullable delegate types?

        // Occurs when resolution of assembly fails
        // This event is fired after resolve events of AssemblyLoadContext fails
        internal static event ResolveEventHandler AssemblyResolve; // TODO-NULLABLE: Should all events use nullable delegate types?
#endregion

        public static AssemblyLoadContext Default => DefaultAssemblyLoadContext.s_loadContext;

        public bool IsCollectible { get { return _isCollectible;} }

        public string? Name { get { return _name;} }

        public override string ToString() => "\"" + Name + "\" " + GetType().ToString() + " #" + _id;

        public static IEnumerable<AssemblyLoadContext> All
        {
            get
            {
                _ = AssemblyLoadContext.Default; // Ensure default is initialized

                List<WeakReference<AssemblyLoadContext>>? alcList = null;
                lock (s_allContexts)
                {
                    // To make this thread safe we need a quick snapshot while locked
                    alcList = new List<WeakReference<AssemblyLoadContext>>(s_allContexts.Values);
                }

                foreach (WeakReference<AssemblyLoadContext> weakAlc in alcList)
                {
                    if (weakAlc.TryGetTarget(out AssemblyLoadContext? alc))
                    {
                        yield return alc;
                    }
                }
            }
        }

        // Helper to return AssemblyName corresponding to the path of an IL assembly
        public static AssemblyName GetAssemblyName(string assemblyPath)
        {
            if (assemblyPath == null)
            {
                throw new ArgumentNullException(nameof(assemblyPath));
            }

            return AssemblyName.GetAssemblyName(assemblyPath);
        }

        // Custom AssemblyLoadContext implementations can override this
        // method to perform custom processing and use one of the protected
        // helpers above to load the assembly.
        protected virtual Assembly? Load(AssemblyName assemblyName)
        {
            return null;
        }

#if !CORERT
        [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
        public Assembly LoadFromAssemblyName(AssemblyName assemblyName)
        {
            if (assemblyName == null)
                throw new ArgumentNullException(nameof(assemblyName));

            // Attempt to load the assembly, using the same ordering as static load, in the current load context.
            StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
            return Assembly.Load(assemblyName, ref stackMark, this);
        }
#endif

        // These methods load assemblies into the current AssemblyLoadContext
        // They may be used in the implementation of an AssemblyLoadContext derivation
        public Assembly LoadFromAssemblyPath(string assemblyPath)
        {
            if (assemblyPath == null)
            {
                throw new ArgumentNullException(nameof(assemblyPath));
            }

            if (PathInternal.IsPartiallyQualified(assemblyPath))
            {
                throw new ArgumentException(SR.Argument_AbsolutePathRequired, nameof(assemblyPath));
            }

            lock (_unloadLock)
            {
                VerifyIsAlive();

                return InternalLoadFromPath(assemblyPath, null);
            }
        }

        public Assembly LoadFromNativeImagePath(string nativeImagePath, string? assemblyPath)
        {
            if (nativeImagePath == null)
            {
                throw new ArgumentNullException(nameof(nativeImagePath));
            }

            if (PathInternal.IsPartiallyQualified(nativeImagePath))
            {
                throw new ArgumentException(SR.Argument_AbsolutePathRequired, nameof(nativeImagePath));
            }

            if (assemblyPath != null && PathInternal.IsPartiallyQualified(assemblyPath))
            {
                throw new ArgumentException(SR.Argument_AbsolutePathRequired, nameof(assemblyPath));
            }

            lock (_unloadLock)
            {
                VerifyIsAlive();

                return InternalLoadFromPath(assemblyPath, nativeImagePath);
            }
        }

        public Assembly LoadFromStream(Stream assembly)
        {
            return LoadFromStream(assembly, null);
        }

        public Assembly LoadFromStream(Stream assembly, Stream? assemblySymbols)
        {
            if (assembly == null)
            {
                throw new ArgumentNullException(nameof(assembly));
            }

            int iAssemblyStreamLength = (int)assembly.Length;

            if (iAssemblyStreamLength <= 0)
            {
                throw new BadImageFormatException(SR.BadImageFormat_BadILFormat);
            }

            // Allocate the byte[] to hold the assembly
            byte[] arrAssembly = new byte[iAssemblyStreamLength];

            // Copy the assembly to the byte array
            assembly.Read(arrAssembly, 0, iAssemblyStreamLength);

            // Get the symbol stream in byte[] if provided
            byte[]? arrSymbols = null;
            if (assemblySymbols != null)
            {
                var iSymbolLength = (int)assemblySymbols.Length;
                arrSymbols = new byte[iSymbolLength];

                assemblySymbols.Read(arrSymbols, 0, iSymbolLength);
            }

            lock (_unloadLock)
            {
                VerifyIsAlive();

                return InternalLoad(arrAssembly, arrSymbols);
            }
        }

        // This method provides a way for overriders of LoadUnmanagedDll() to load an unmanaged DLL from a specific path in a
        // platform-independent way. The DLL is loaded with default load flags.
        protected IntPtr LoadUnmanagedDllFromPath(string unmanagedDllPath)
        {
            if (unmanagedDllPath == null)
            {
                throw new ArgumentNullException(nameof(unmanagedDllPath));
            }

            if (unmanagedDllPath.Length == 0)
            {
                throw new ArgumentException(SR.Argument_EmptyPath, nameof(unmanagedDllPath));
            }

            if (PathInternal.IsPartiallyQualified(unmanagedDllPath))
            {
                throw new ArgumentException(SR.Argument_AbsolutePathRequired, nameof(unmanagedDllPath));
            }

            return InternalLoadUnmanagedDllFromPath(unmanagedDllPath);
        }

        // Custom AssemblyLoadContext implementations can override this
        // method to perform the load of unmanaged native dll
        // This function needs to return the HMODULE of the dll it loads
        protected virtual IntPtr LoadUnmanagedDll(string unmanagedDllName)
        {
            //defer to default coreclr policy of loading unmanaged dll
            return IntPtr.Zero;
        }

        public void Unload()
        {
            if (!IsCollectible)
            {
                throw new InvalidOperationException(SR.AssemblyLoadContext_Unload_CannotUnloadIfNotCollectible);
            }

            GC.SuppressFinalize(this);
            InitiateUnload();
        }

        internal static void OnProcessExit()
        {
            lock (s_allContexts)
            {
                foreach (var alcAlive in s_allContexts)
                {
                    if (alcAlive.Value.TryGetTarget(out AssemblyLoadContext? alc))
                    {
                        alc.RaiseUnloadEvent();
                    }
                }
            }
        }

        private void VerifyIsAlive()
        {
            if (_state != InternalState.Alive)
            {
                throw new InvalidOperationException(SR.AssemblyLoadContext_Verify_NotUnloading);
            }
        }

        private static AsyncLocal<AssemblyLoadContext?>? s_asyncLocalCurrent;

        /// <summary>Nullable current AssemblyLoadContext used for context sensitive reflection APIs</summary>
        /// <remarks>
        /// This is an advanced setting used in reflection assembly loading scenarios.
        ///
        /// There are a set of contextual reflection APIs which load managed assemblies through an inferred AssemblyLoadContext.
        /// * <see cref="System.Activator.CreateInstance" />
        /// * <see cref="System.Reflection.Assembly.Load" />
        /// * <see cref="System.Reflection.Assembly.GetType" />
        /// * <see cref="System.Type.GetType" />
        ///
        /// When CurrentContextualReflectionContext is null, the AssemblyLoadContext is inferred.
        /// The inference logic is simple.
        /// * For static methods, it is the AssemblyLoadContext which loaded the method caller's assembly.
        /// * For instance methods, it is the AssemblyLoadContext which loaded the instance's assembly.
        ///
        /// When this property is set, the CurrentContextualReflectionContext value is used by these contextual reflection APIs for loading.
        ///
        /// This property is typically set in a using block by
        /// <see cref="System.Runtime.Loader.AssemblyLoadContext.EnterContextualReflection"/>.
        ///
        /// The property is stored in an AsyncLocal&lt;AssemblyLoadContext&gt;. This means the setting can be unique for every async or thread in the process.
        ///
        /// For more details see https://github.com/dotnet/coreclr/blob/master/Documentation/design-docs/AssemblyLoadContext.ContextualReflection.md
        /// </remarks>
        public static AssemblyLoadContext? CurrentContextualReflectionContext
        {
            get { return s_asyncLocalCurrent?.Value; }
        }

        private static void SetCurrentContextualReflectionContext(AssemblyLoadContext? value)
        {
            if (s_asyncLocalCurrent == null)
            {
                Interlocked.CompareExchange<AsyncLocal<AssemblyLoadContext?>?>(ref s_asyncLocalCurrent, new AsyncLocal<AssemblyLoadContext?>(), null);
            }
            s_asyncLocalCurrent!.Value = value; // Remove ! when compiler specially-recognizes CompareExchange for nullability
        }

        /// <summary>Enter scope using this AssemblyLoadContext for ContextualReflection</summary>
        /// <returns>A disposable ContextualReflectionScope for use in a using block</returns>
        /// <remarks>
        /// Sets CurrentContextualReflectionContext to this instance.
        /// <see cref="System.Runtime.Loader.AssemblyLoadContext.CurrentContextualReflectionContext"/>
        ///
        /// Returns a disposable ContextualReflectionScope for use in a using block. When the using calls the
        /// Dispose() method, it restores the ContextualReflectionScope to its previous value.
        /// </remarks>
        public ContextualReflectionScope EnterContextualReflection()
        {
            return new ContextualReflectionScope(this);
        }

        /// <summary>Enter scope using this AssemblyLoadContext for ContextualReflection</summary>
        /// <param name="activating">Set CurrentContextualReflectionContext to the AssemblyLoadContext which loaded activating.</param>
        /// <returns>A disposable ContextualReflectionScope for use in a using block</returns>
        /// <remarks>
        /// Sets CurrentContextualReflectionContext to to the AssemblyLoadContext which loaded activating.
        /// <see cref="System.Runtime.Loader.AssemblyLoadContext.CurrentContextualReflectionContext"/>
        ///
        /// Returns a disposable ContextualReflectionScope for use in a using block. When the using calls the
        /// Dispose() method, it restores the ContextualReflectionScope to its previous value.
        /// </remarks>
        public static ContextualReflectionScope EnterContextualReflection(Assembly? activating)
        {
            if (activating == null)
                return new ContextualReflectionScope(null);

            AssemblyLoadContext? assemblyLoadContext = GetLoadContext(activating);

            if (assemblyLoadContext == null)
            {
                // All RuntimeAssemblies & Only RuntimeAssemblies have an AssemblyLoadContext
                throw new ArgumentException(SR.Arg_MustBeRuntimeAssembly, nameof(activating));
            }

            return assemblyLoadContext.EnterContextualReflection();
        }

        /// <summary>Opaque disposable struct used to restore CurrentContextualReflectionContext</summary>
        /// <remarks>
        /// This is an implmentation detail of the AssemblyLoadContext.EnterContextualReflection APIs.
        /// It is a struct, to avoid heap allocation.
        /// It is required to be public to avoid boxing.
        /// <see cref="System.Runtime.Loader.AssemblyLoadContext.EnterContextualReflection"/>
        /// </remarks>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public struct ContextualReflectionScope : IDisposable
        {
            private readonly AssemblyLoadContext? _activated;
            private readonly AssemblyLoadContext? _predecessor;
            private readonly bool _initialized;

            internal ContextualReflectionScope(AssemblyLoadContext? activating)
            {
                _predecessor = AssemblyLoadContext.CurrentContextualReflectionContext;
                AssemblyLoadContext.SetCurrentContextualReflectionContext(activating);
                _activated = activating;
                _initialized = true;
            }

            public void Dispose()
            {
                if (_initialized)
                {
                    // Do not clear initialized. Always restore the _predecessor in Dispose()
                    // _initialized = false;
                    AssemblyLoadContext.SetCurrentContextualReflectionContext(_predecessor);
                }
            }
        }

        private Assembly? ResolveSatelliteAssembly(AssemblyName assemblyName)
        {
            // Called by native runtime when CultureName is not empty
            Debug.Assert(assemblyName.CultureName?.Length > 0);

            string satelliteSuffix = ".resources";

            if (assemblyName.Name == null || !assemblyName.Name.EndsWith(satelliteSuffix, StringComparison.Ordinal))
                return null;

            string parentAssemblyName = assemblyName.Name.Substring(0, assemblyName.Name.Length - satelliteSuffix.Length);

            Assembly parentAssembly = LoadFromAssemblyName(new AssemblyName(parentAssemblyName));

            AssemblyLoadContext parentALC = GetLoadContext(parentAssembly)!;

            string parentDirectory = Path.GetDirectoryName(parentAssembly.Location)!;

            string assemblyPath = Path.Combine(parentDirectory, assemblyName.CultureName!, $"{assemblyName.Name}.dll");

            if (Internal.IO.File.InternalExists(assemblyPath))
            {
                return parentALC.LoadFromAssemblyPath(assemblyPath);
            }
            else if (Path.IsCaseSensitive)
            {
                assemblyPath = Path.Combine(parentDirectory, assemblyName.CultureName!.ToLowerInvariant(), $"{assemblyName.Name}.dll");

                if (Internal.IO.File.InternalExists(assemblyPath))
                {
                    return parentALC.LoadFromAssemblyPath(assemblyPath);
                }
            }

            return null;
        }
    }

    internal sealed class DefaultAssemblyLoadContext : AssemblyLoadContext
    {
        internal static readonly AssemblyLoadContext s_loadContext = new DefaultAssemblyLoadContext();

        internal DefaultAssemblyLoadContext() : base(true, false, "Default")
        {
        }
    }

    internal sealed class IndividualAssemblyLoadContext : AssemblyLoadContext
    {
        internal IndividualAssemblyLoadContext(string name) : base(false, false, name)
        {
        }
    }
}