Skip to content

Commit 6828bdb

Browse files
committed
[Java.Interop.Tools.Cecil] Fix DirectoryAssemblyResolver.Dispose()
Context: #87 (comment) Context: https://bugzilla.xamarin.com/show_bug.cgi?id=44529 (Private) Bug #44529 is an `IOException` on `xamarin-android/master` due to file sharing: Error executing task StripEmbeddedLibraries: System.IO.IOException: Sharing violation on path .../obj/Release/linksrc/Xamarin.Android.NUnitLite.dll at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) [0x0025f] in <253a3790b2c44512bbca8669ecfc1822>:0 at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) [0x00000] in <253a3790b2c44512bbca8669ecfc1822>:0 at (wrapper remoting-invoke-with-check) System.IO.FileStream:.ctor (string,System.IO.FileMode,System.IO.FileAccess,System.IO.FileShare) at Mono.Cecil.ModuleDefinition.GetFileStream (System.String fileName, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) [0x00007] in :0 at Mono.Cecil.ModuleDefinition.Write (System.String fileName, Mono.Cecil.WriterParameters parameters) [0x00007] in :0 at Mono.Cecil.AssemblyDefinition.Write (System.String fileName, Mono.Cecil.WriterParameters parameters) [0x00001] in :0 at Xamarin.Android.Tasks.StripEmbeddedLibraries.Execute () [0x0034a] in <3d5202a5d4874a76a99388021bf1ab1a>:0 The underlying cause of this change is the migration to Cecil 0.10.0-beta1-v2 (dfed286), which -- along with API changes -- has some *semantic* changes [^0]. In particular, within Cecil <= 0.9.x, `AssemblyDefinition` was entirely in-memory. Starting with Cecil 0.10.x, `AssemblyDefinition` isn't; it is backed by a `System.IO.Stream`, which can be in-memory (if `ReaderParameters.InMemory` is `true`), or a `FileStream` (the default). This normally might not be bad, except we also have `Java.Interop.Tools.Cecil.DirectoryAssemblyResolver`, which *caches* all created `AssemblyDefinition` instances. Thus, "normal" *correct* Cecil use would be fine, so long as you know all assemblies which have been loaded, load them with the correct `ReaderParameters`, and promptly `Dispose()` of the `AssemblyDefinition` when done. `DirectoryAssemblyResolver` throws a wrench in that, because (1) commit dfed286 incorrectly implemented `DirectoryAssemblyResolver.Dispose()`, leaving all of the cached `AssemblyDefinition` instances still "live", which means (2) The lifetime of the `Stream` underlying the `AssemblyDefinition` is controlled by the GC, which can mean nearly anything. This is all a *huge* recipe for confusion. Fix `DirectoryAssemblyResolver.Dispose()` so that the cached `AssemblyDefinition` instances are `Dispose()`d of, and review all use of `DirectoryAssemblyResolver` within Java.Interop to ensure that any created instances are appropriate `Dispose()`d of. Additionally, add a new `DirectoryAssemblyResolver` constructor to allow controlling the `ReaderParameters` that `DirectoryAssemblyResolver.Load()` uses when loading an assembly: partial class DirectoryAssemblyResolver { public DirectoryAssemblyResolver ( Action<string, object[]> logWarnings, bool loadDebugSymbols, ReaderParameters loadReaderParameters = null); } The new `loadReaderParameters` allows specifying the default `ReaderParameters` values to use when `DirectoryAssemblyResolver.Load()` is invoked. This ensures that all assemblies loaded by `DirectoryAssemblyResolver` are loaded in a consistent fashion (e.g. readonly, read+write, in-memory), which will hopefully allow code to be sanely reasoned about. [^0]: The best kind of changes!
1 parent aa21ec9 commit 6828bdb

File tree

4 files changed

+45
-13
lines changed

4 files changed

+45
-13
lines changed

src/Java.Interop.Tools.Cecil/Java.Interop.Tools.Cecil/DirectoryAssemblyResolver.cs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,20 @@ public class DirectoryAssemblyResolver : IAssemblyResolver {
6464
bool loadDebugSymbols;
6565
Action<string, object[]> logWarnings;
6666

67-
public DirectoryAssemblyResolver (Action<string, object[]> logWarnings, bool loadDebugSymbols)
67+
ReaderParameters loadReaderParameters;
68+
69+
static readonly ReaderParameters DefaultLoadReaderParameters = new ReaderParameters {
70+
};
71+
72+
public DirectoryAssemblyResolver (Action<string, object[]> logWarnings, bool loadDebugSymbols, ReaderParameters loadReaderParameters = null)
6873
{
6974
if (logWarnings == null)
7075
throw new ArgumentNullException (nameof (logWarnings));
7176
cache = new Dictionary<string, AssemblyDefinition> ();
7277
this.loadDebugSymbols = loadDebugSymbols;
7378
this.logWarnings = logWarnings;
7479
SearchDirectories = new List<string> ();
80+
this.loadReaderParameters = loadReaderParameters ?? DefaultLoadReaderParameters;
7581
}
7682

7783
public void Dispose ()
@@ -82,8 +88,15 @@ public void Dispose ()
8288

8389
protected virtual void Dispose (bool disposing)
8490
{
91+
if (!disposing || cache == null)
92+
return;
93+
foreach (var e in cache) {
94+
e.Value.Dispose ();
95+
}
96+
cache = null;
8597
}
8698

99+
[Obsolete ("Should not be used; was required with previous Cecil versions.")]
87100
public IDictionary ToResolverCache ()
88101
{
89102
var resolver_cache = new Hashtable ();
@@ -114,9 +127,21 @@ public virtual AssemblyDefinition Load (string fileName)
114127

115128
protected virtual AssemblyDefinition ReadAssembly (string file)
116129
{
130+
bool haveDebugSymbols = loadDebugSymbols &&
131+
(File.Exists (file + ".mdb") ||
132+
File.Exists (Path.ChangeExtension (file, ".pdb")));
117133
var reader_parameters = new ReaderParameters () {
118-
AssemblyResolver = this,
119-
ReadSymbols = loadDebugSymbols && (File.Exists(file + ".mdb") || File.Exists(Path.ChangeExtension(file, ".pdb"))),
134+
ApplyWindowsRuntimeProjections = loadReaderParameters.ApplyWindowsRuntimeProjections,
135+
AssemblyResolver = this,
136+
MetadataImporterProvider = loadReaderParameters.MetadataImporterProvider,
137+
InMemory = loadReaderParameters.InMemory,
138+
MetadataResolver = loadReaderParameters.MetadataResolver,
139+
ReadingMode = loadReaderParameters.ReadingMode,
140+
ReadSymbols = haveDebugSymbols,
141+
ReadWrite = loadReaderParameters.ReadWrite,
142+
ReflectionImporterProvider = loadReaderParameters.ReflectionImporterProvider,
143+
SymbolReaderProvider = loadReaderParameters.SymbolReaderProvider,
144+
SymbolStream = loadReaderParameters.SymbolStream,
120145
};
121146
try {
122147
return AssemblyDefinition.ReadAssembly (file, reader_parameters);

src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGenerator.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,15 @@ public TypeNameMapGenerator (IEnumerable<string> assemblies, Action<string, obje
6868

6969
Log = logMessage;
7070
var Assemblies = assemblies.ToList ();
71-
var resolver = new DirectoryAssemblyResolver (Log, loadDebugSymbols: true);
71+
var rp = new ReaderParameters (ReadingMode.Immediate);
7272

73-
foreach (var assembly in Assemblies) {
74-
resolver.Load (Path.GetFullPath (assembly));
75-
}
73+
using (var resolver = new DirectoryAssemblyResolver (Log, loadDebugSymbols: true, loadReaderParameters: rp)) {
74+
foreach (var assembly in Assemblies) {
75+
resolver.Load (Path.GetFullPath (assembly));
76+
}
7677

77-
Types = JavaTypeScanner.GetJavaTypes (Assemblies, resolver, logMessage);
78+
Types = JavaTypeScanner.GetJavaTypes (Assemblies, resolver, logMessage);
79+
}
7880
}
7981

8082
public TypeNameMapGenerator (IEnumerable<TypeDefinition> types, Action<string, object[]> logMessage)

tools/generator/CodeGenerator.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,10 @@ public static void Run (CodeGeneratorOptions options)
278278
apiXmlFile = api_xml_adjuster_output ?? Path.Combine (Path.GetDirectoryName (filename), Path.GetFileName (filename) + ".adjusted");
279279
new Adjuster ().Process (filename, SymbolTable.AllRegisteredSymbols ().OfType<GenBase> ().ToArray (), apiXmlFile);
280280
}
281-
if (only_xml_adjuster)
281+
if (only_xml_adjuster) {
282+
resolver.Dispose ();
282283
return;
284+
}
283285

284286
// load XML API definition with fixups.
285287

@@ -296,6 +298,7 @@ public static void Run (CodeGeneratorOptions options)
296298
Parser p = new Parser ();
297299
List<GenBase> gens = p.Parse (apiXmlFile, fixups, api_level, product_version);
298300
if (gens == null) {
301+
resolver.Dispose ();
299302
return;
300303
}
301304
apiSource = p.ApiSource;
@@ -347,6 +350,8 @@ public static void Run (CodeGeneratorOptions options)
347350
: enummap.WriteEnumerations (enumdir, enums, FlattenNestedTypes (gens).ToArray (), opt.UseShortFileNames);
348351

349352
gen_info.GenerateLibraryProjectFile (options, enumFiles);
353+
354+
resolver.Dispose ();
350355
}
351356

352357
static IEnumerable<GenBase> FlattenNestedTypes (IEnumerable<GenBase> gens)

tools/jcw-gen/App.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,7 @@ public static int Main (string [] args)
4848
if (assemblies.Count == 0) {
4949
Console.Error.WriteLine ("jcw-gen: No assemblies specified.");
5050
r = 1;
51-
}
52-
else if (outputPath == null) {
51+
} else if (outputPath == null) {
5352
Console.Error.WriteLine ("jcw-gen: No output directory specified. Use `jcw-gen -o PATH`.");
5453
r = 1;
5554
}
@@ -66,10 +65,11 @@ public static int Main (string [] args)
6665
GenerateJavaCallableWrapper (type, outputPath);
6766
}
6867
return 0;
69-
}
70-
catch (Exception e) {
68+
} catch (Exception e) {
7169
Console.Error.Write ("jcw-gen: {0}", verbosity > 0 ? e.ToString () : e.Message);
7270
return 1;
71+
} finally {
72+
resolver.Dispose ();
7373
}
7474
}
7575

0 commit comments

Comments
 (0)