Skip to content

Commit e18b10a

Browse files
committed
Avoid reading and writing global state when loading native library
1 parent 47287bc commit e18b10a

File tree

3 files changed

+70
-30
lines changed

3 files changed

+70
-30
lines changed

LibGit2Sharp/Core/NativeMethods.cs

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
using System;
2-
using System.Globalization;
32
using System.IO;
43
using System.Runtime.CompilerServices;
4+
using System.Runtime.ConstrainedExecution;
55
using System.Runtime.InteropServices;
66
using LibGit2Sharp.Core.Handles;
77

8+
// Restrict the set of directories where the native library is loaded from to safe directories.
9+
[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.ApplicationDirectory | DllImportSearchPath.SafeDirectories)]
10+
11+
#pragma warning disable IDE1006 // Naming Styles
12+
813
// ReSharper disable InconsistentNaming
914
namespace LibGit2Sharp.Core
1015
{
@@ -17,41 +22,52 @@ internal static class NativeMethods
1722
// This will handle initialization and shutdown of the underlying
1823
// native library.
1924
#pragma warning disable 0414
20-
private static readonly NativeShutdownObject shutdownObject;
21-
#pragma warning restore 0414
25+
private static NativeShutdownObject shutdownObject;
26+
#pragma warning restore 0414
2227

2328
static NativeMethods()
2429
{
25-
if (Platform.OperatingSystem == OperatingSystemType.Windows)
30+
if (Platform.IsRunningOnNetFramework())
2631
{
27-
string nativeLibraryPath = GlobalSettings.GetAndLockNativeLibraryPath();
28-
29-
string path = Path.Combine(nativeLibraryPath, Platform.ProcessorArchitecture);
32+
string dllPath = Path.Combine(GlobalSettings.GetAndLockNativeLibraryPath(), libgit2 + ".dll");
3033

31-
const string pathEnvVariable = "PATH";
32-
Environment.SetEnvironmentVariable(pathEnvVariable,
33-
String.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", path, Path.PathSeparator, Environment.GetEnvironmentVariable(pathEnvVariable)));
34+
// Try to load the .dll from the path explicitly.
35+
// If this call succeeds further DllImports will find the library loaded and not attempt to load it again.
36+
// If it fails the next DllImport will load the library from safe directories.
37+
LoadWindowsLibrary(dllPath);
3438
}
3539

36-
LoadNativeLibrary();
37-
shutdownObject = new NativeShutdownObject();
40+
InitializeNativeLibrary();
3841
}
3942

43+
[DllImport("kernel32", EntryPoint = "LoadLibrary")]
44+
private static extern IntPtr LoadWindowsLibrary(string path);
45+
4046
// Avoid inlining this method because otherwise mono's JITter may try
4147
// to load the library _before_ we've configured the path.
4248
[MethodImpl(MethodImplOptions.NoInlining)]
43-
private static void LoadNativeLibrary()
49+
private static void InitializeNativeLibrary()
4450
{
45-
// Configure the OpenSSL locking on the true initialization
46-
// of the library.
47-
if (git_libgit2_init() == 1)
51+
int initCounter;
52+
try
53+
{
54+
}
55+
finally // avoid thread aborts
56+
{
57+
// Initialization can be called multiple times as long as there is a corresponding shutdown to each initialization.
58+
initCounter = git_libgit2_init();
59+
shutdownObject = new NativeShutdownObject();
60+
}
61+
62+
// Configure the OpenSSL locking on the first initialization of the library in the current process.
63+
if (initCounter == 1)
4864
{
4965
git_openssl_set_locking();
5066
}
5167
}
5268

5369
// Shutdown the native library in a finalizer.
54-
private sealed class NativeShutdownObject
70+
private sealed class NativeShutdownObject : CriticalFinalizerObject
5571
{
5672
~NativeShutdownObject()
5773
{

LibGit2Sharp/Core/Platform.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,17 @@ public static OperatingSystemType OperatingSystem
3636
throw new InvalidOperationException();
3737
}
3838
}
39+
40+
/// <summary>
41+
/// Returns true if the runtime is Mono.
42+
/// </summary>
43+
public static bool IsRunningOnMono()
44+
=> Type.GetType("Mono.Runtime") != null;
45+
46+
/// <summary>
47+
/// Returns true if the runtime is .NET Framework.
48+
/// </summary>
49+
public static bool IsRunningOnNetFramework()
50+
=> typeof(object).Assembly.GetName().Name == "mscorlib" && !IsRunningOnMono();
3951
}
4052
}

LibGit2Sharp/GlobalSettings.cs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Reflection;
5+
using System.Runtime.InteropServices;
56
using LibGit2Sharp.Core;
67

78
namespace LibGit2Sharp
@@ -13,6 +14,7 @@ public static class GlobalSettings
1314
{
1415
private static readonly Lazy<Version> version = new Lazy<Version>(Version.Build);
1516
private static readonly Dictionary<Filter, FilterRegistration> registeredFilters;
17+
private static readonly bool nativeLibraryPathAllowed;
1618

1719
private static LogConfiguration logConfiguration = LogConfiguration.None;
1820

@@ -21,7 +23,9 @@ public static class GlobalSettings
2123

2224
static GlobalSettings()
2325
{
24-
if (Platform.OperatingSystem == OperatingSystemType.Windows)
26+
nativeLibraryPathAllowed = Platform.IsRunningOnNetFramework();
27+
28+
if (nativeLibraryPathAllowed)
2529
{
2630
/* Assembly.CodeBase is not actually a correctly formatted
2731
* URI. It's merely prefixed with `file:///` and has its
@@ -148,23 +152,24 @@ public static LogConfiguration LogConfiguration
148152
}
149153

150154
/// <summary>
151-
/// Sets a hint path for searching for native binaries: when
152-
/// specified, native binaries will first be searched in a
153-
/// subdirectory of the given path corresponding to the operating
154-
/// system and architecture (eg, "x86" or "x64") before falling
155-
/// back to the default path ("lib\win32\x86" or "lib\win32\x64"
156-
/// next to the application).
155+
/// Sets a path for loading native binaries on .NET Framework.
156+
/// When specified, native .dll will first be searched in a
157+
/// subdirectory of the given path corresponding to the
158+
/// architecture ("x86" or "x64") before falling
159+
/// back to searching the default load directories
160+
/// (<see cref="DllImportSearchPath.AssemblyDirectory"/>,
161+
/// <see cref="DllImportSearchPath.ApplicationDirectory"/> and
162+
/// <see cref="DllImportSearchPath.SafeDirectories"/>).
157163
/// <para>
158164
/// This must be set before any other calls to the library,
159-
/// and is not available on Unix platforms: see your dynamic
160-
/// library loader's documentation for details.
165+
/// and is not available on other platforms than .NET Framework.
161166
/// </para>
162167
/// </summary>
163168
public static string NativeLibraryPath
164169
{
165170
get
166171
{
167-
if (Platform.OperatingSystem != OperatingSystemType.Windows)
172+
if (!nativeLibraryPathAllowed)
168173
{
169174
throw new LibGit2SharpException("Querying the native hint path is only supported on Windows platforms");
170175
}
@@ -174,7 +179,7 @@ public static string NativeLibraryPath
174179

175180
set
176181
{
177-
if (Platform.OperatingSystem != OperatingSystemType.Windows)
182+
if (!nativeLibraryPathAllowed)
178183
{
179184
throw new LibGit2SharpException("Setting the native hint path is only supported on Windows platforms");
180185
}
@@ -184,14 +189,21 @@ public static string NativeLibraryPath
184189
throw new LibGit2SharpException("You cannot set the native library path after it has been loaded");
185190
}
186191

187-
nativeLibraryPath = value;
192+
try
193+
{
194+
nativeLibraryPath = Path.GetFullPath(value);
195+
}
196+
catch (Exception e)
197+
{
198+
throw new LibGit2SharpException(e.Message);
199+
}
188200
}
189201
}
190202

191203
internal static string GetAndLockNativeLibraryPath()
192204
{
193205
nativeLibraryPathLocked = true;
194-
return nativeLibraryPath;
206+
return Path.Combine(nativeLibraryPath, Platform.ProcessorArchitecture);
195207
}
196208

197209
/// <summary>

0 commit comments

Comments
 (0)