Compiler: preload the reference assemblies, add an assembly resolver

This allows the compiler to strictly follow the user-provided reference
assembly list. It is very useful for cases when the reference list is
huge and not everything in it is in clear state; for example, when a
reference assembly list is provided by NuGet transitive closure.
This commit is contained in:
Friedrich von Never 2022-09-21 21:43:27 +02:00
parent d7d6d24d57
commit 2810132be7
4 changed files with 100 additions and 22 deletions

View file

@ -2637,7 +2637,7 @@ namespace PascalABCCompiler
}
}
private string GetReferenceFileName(string FileName, SyntaxTree.SourceContext sc, string curr_path)
private string GetReferenceFileName(string FileName, SyntaxTree.SourceContext sc, string curr_path, bool overwrite)
{
if (standart_assembly_dict.ContainsKey(FileName))
return standart_assembly_dict[FileName];
@ -2658,7 +2658,14 @@ namespace PascalABCCompiler
if (System.IO.File.Exists(FullFileName))
{
var NewFileName = Path.Combine(compilerOptions.OutputDirectory, Path.GetFileName(FullFileName));
if (FullFileName != NewFileName) File.Copy(FullFileName, NewFileName, true);
if (FullFileName != NewFileName)
{
if (overwrite)
File.Copy(FullFileName, NewFileName, true);
else if (!File.Exists(NewFileName))
File.Copy(FullFileName, NewFileName, false);
}
return NewFileName;
}
else
@ -2811,16 +2818,20 @@ namespace PascalABCCompiler
}
}
private Assembly PreloadReference(TreeRealization.compiler_directive reference)
{
var sc = GetSourceContext(reference);
var fileName = GetReferenceFileName(reference.directive, sc, Path.GetDirectoryName(reference.source_file), true);
return NetHelper.NetHelper.PreloadAssembly(fileName);
}
private CompilationUnit CompileReference(PascalABCCompiler.TreeRealization.unit_node_list Units, TreeRealization.compiler_directive cd)
{
TreeRealization.location loc = cd.location;
SyntaxTree.SourceContext sc = null;
if (loc != null)
sc = new SyntaxTree.SourceContext(loc.begin_line_num, loc.begin_column_num, loc.end_line_num, loc.end_column_num, 0, 0);
var sc = GetSourceContext(cd);
string UnitName = null;
try
{
UnitName = GetReferenceFileName(cd.directive, sc, Path.GetDirectoryName(cd.source_file));
UnitName = GetReferenceFileName(cd.directive, sc, Path.GetDirectoryName(cd.source_file), false);
}
catch (AssemblyNotFound ex)
{
@ -2843,6 +2854,14 @@ namespace PascalABCCompiler
//throw new DLLReadingError(UnitName);
throw new AssemblyReadingError(CurrentCompilationUnit.SyntaxTree.file_name, UnitName, sc);
}
private SyntaxTree.SourceContext GetSourceContext(TreeRealization.compiler_directive cd)
{
var loc = cd.location;
if (loc == null) return null;
return new SyntaxTree.SourceContext(loc.begin_line_num, loc.begin_column_num, loc.end_line_num,
loc.end_column_num, 0, 0);
}
private bool HasIncludeNamespacesDirective(CompilationUnit Unit)
{
@ -3030,6 +3049,8 @@ namespace PascalABCCompiler
}
}
var referenceDirectives = new List<TreeRealization.compiler_directive>();
foreach (TreeRealization.compiler_directive cd in directives)
{
if (cd.name.ToLower() == TreeConverter.compiler_string_consts.compiler_directive_reference)
@ -3037,16 +3058,33 @@ namespace PascalABCCompiler
if (string.IsNullOrEmpty(cd.directive))
throw new TreeConverter.SimpleSemanticError(cd.location, "EXPECTED_ASSEMBLY_NAME");
else
CompileReference(res, cd);
referenceDirectives.Add(cd);
}
}
if (CompilerOptions.ProjectCompiled)
{
foreach (ReferenceInfo ri in project.references)
{
CompileReference(res, new TreeRealization.compiler_directive("reference", ri.full_assembly_name, null, project.MainFile));
referenceDirectives.Add(new TreeRealization.compiler_directive("reference", ri.full_assembly_name, null, project.MainFile));
}
}
// It's important to preload all the referenced assemblies before starting the compilation. During the
// compilation, we need to access types from every referenced assembly. An attempt to access them could fail
// if a transitively dependent assembly is not loaded, yet.
//
// It's not always possible to solve by re-ordering the references, since there are cases of
// mutually-dependent assemblies (i.e. dependency loops) in the wild.
var preloadedAssemblies = new List<Assembly>();
foreach (var reference in referenceDirectives)
preloadedAssemblies.Add(PreloadReference(reference));
using (new NetHelper.AssemblyResolveScope(AppDomain.CurrentDomain, preloadedAssemblies))
{
foreach (var reference in referenceDirectives)
CompileReference(res, reference);
}
return res;
}

View file

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace PascalABCCompiler.NetHelper
{
/// <summary>
/// Will force the application domain to load passed assemblies when requested, while ignoring the assembly
/// versions. Handles <see cref="AppDomain.AssemblyResolve"/> until disposed.
/// </summary>
public class AssemblyResolveScope : IDisposable
{
private readonly AppDomain _appDomain;
private readonly Dictionary<string, Assembly> _assemblies = new Dictionary<string, Assembly>();
public AssemblyResolveScope(AppDomain appDomain, IEnumerable<Assembly> assemblies)
{
foreach (var assembly in assemblies)
{
// NOTE: assemblies with identical simple names will overwrite each other
_assemblies[assembly.GetName().Name] = assembly;
}
_appDomain = appDomain;
_appDomain.AssemblyResolve += OnAssemblyResolve;
}
public void Dispose()
{
_appDomain.AssemblyResolve -= OnAssemblyResolve;
}
private Assembly OnAssemblyResolve(object _, ResolveEventArgs args)
{
var requestedName = new AssemblyName(args.Name);
if (_assemblies.TryGetValue(requestedName.Name, out var assembly))
return assembly;
return null;
}
}
}

View file

@ -4,6 +4,7 @@ using System;
using PascalABCCompiler.SemanticTree;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading;
@ -359,6 +360,12 @@ namespace PascalABCCompiler.NetHelper
else
return false;
}
public static Assembly PreloadAssembly(string name)
{
var bytes = File.ReadAllBytes(name);
return Assembly.Load(bytes);
}
public static Assembly LoadAssembly(string name)
{
@ -408,20 +415,10 @@ namespace PascalABCCompiler.NetHelper
}
}
//if (!System.IO.Path.GetFileName(name).ToLower().Contains("microsoft.directx"))
try
try
{
System.IO.FileStream fs = System.IO.File.OpenRead(name);
byte[] buf = new byte[fs.Length];
fs.Read(buf, 0, (int)fs.Length);
fs.Close();
curr_inited_assm_path = name;
a = System.Reflection.Assembly.Load(buf);
a.GetTypes();
buf = null;
//Thread th = new Thread(new ThreadStart(collect_internal));
//th.Start();
}
a = PreloadAssembly(name);
}
catch (Exception ex)
{
a = System.Reflection.Assembly.LoadFrom(name);

View file

@ -110,6 +110,7 @@
<Compile Include="LambdaExpressions\LambdaProcessingState.cs" />
<Compile Include="LambdaExpressions\LambdaResultTypeInferrer.cs" />
<Compile Include="LambdaExpressions\LambdaSearcher.cs" />
<Compile Include="NetWrappers\AssemblyResolveScope.cs" />
<Compile Include="TreeConversion\HasCapturedLambdaParameterInInternalLambdaBody.cs" />
<Compile Include="TreeConversion\LambdaHelper.cs" />
<Compile Include="SymbolTable\DSST\AreaArrayTable.cs">