IKVM11  11
Java SE 11 Virtual Machine for .NET
Loading...
Searching...
No Matches
ImportContext.cs
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2014 Jeroen Frijters
3
4 This software is provided 'as-is', without any express or implied
5 warranty. In no event will the authors be held liable for any damages
6 arising from the use of this software.
7
8 Permission is granted to anyone to use this software for any purpose,
9 including commercial applications, and to alter it and redistribute it
10 freely, subject to the following restrictions:
11
12 1. The origin of this software must not be misrepresented; you must not
13 claim that you wrote the original software. If you use this software
14 in a product, an acknowledgment in the product documentation would be
15 appreciated but is not required.
16 2. Altered source versions must be plainly marked as such, and must not be
17 misrepresented as being the original software.
18 3. This notice may not be removed or altered from any source distribution.
19
20 Jeroen Frijters
21 jeroen@frijters.net
22
23*/
24using System;
25using System.Collections.Generic;
26using System.IO;
27using System.IO.Compression;
28using System.Linq;
29using System.Text.RegularExpressions;
30using System.Threading;
31
32using IKVM.ByteCode;
35using IKVM.Reflection;
37using IKVM.Runtime;
39
40using Microsoft.Extensions.DependencyInjection;
41
42namespace IKVM.Tools.Importer
43{
44
49 {
50
54 class CompilerOptionsDiagnosticHandler : FormattedDiagnosticHandler
55 {
56
57 readonly ImportState _options;
58
65 public CompilerOptionsDiagnosticHandler(ImportState options, string spec, DiagnosticFormatterProvider formatters) :
66 base(spec, formatters)
67 {
68 _options = options ?? throw new ArgumentNullException(nameof(options));
69 }
70
72 public override bool IsEnabled(Diagnostic diagnostic)
73 {
74 return diagnostic.Level is not DiagnosticLevel.Trace and not DiagnosticLevel.Info;
75 }
76
78 public override void Report(in DiagnosticEvent @event)
79 {
80 if (IsEnabled(@event.Diagnostic) == false)
81 return;
82
83 var key = @event.Diagnostic.Id.ToString();
84 for (int i = 0; ; i++)
85 {
86 if (_options.suppressWarnings.Contains(key))
87 return;
88
89 if (i == @event.Args.Length)
90 break;
91
92 key += ":" + @event.Args[i];
93 }
94
95 _options.suppressWarnings.Add(key);
96
97 base.Report(@event);
98 }
99
100 }
101
102 readonly ImportOptions _options;
103 string manifestMainClass;
104 string defaultAssemblyName;
105 static bool time;
106 static string runtimeAssembly;
107 static bool nostdlib;
108 static bool nonDeterministicOutput;
109 static DebugMode debugMode;
110 static readonly List<string> libpaths = new List<string>();
111 internal static readonly AssemblyResolver resolver = new AssemblyResolver();
112
113 public static int Execute(ImportOptions options)
114 {
115 DateTime start = DateTime.Now;
116 Thread.CurrentThread.Name = "compiler";
117
118 try
119 {
120 try
121 {
122 return Compile(options);
123 }
124 catch (TypeInitializationException x)
125 {
126 if (x.InnerException is FatalCompilerErrorException)
127 throw x.InnerException;
128
129 throw;
130 }
131 }
133 {
134 Console.Error.WriteLine(x.Message);
135 return 1;
136 }
137 catch (Exception x)
138 {
139 Console.Error.WriteLine();
140 Console.Error.WriteLine("*** COMPILER ERROR ***");
141 Console.Error.WriteLine();
142 Console.Error.WriteLine(System.Reflection.Assembly.GetExecutingAssembly().FullName);
143 Console.Error.WriteLine(System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory());
144 Console.Error.WriteLine("{0} {1}-bit", Environment.Version, IntPtr.Size * 8);
145 Console.Error.WriteLine();
146 Console.Error.WriteLine(x);
147 return 2;
148 }
149 finally
150 {
151 if (time)
152 {
153 Console.WriteLine("Total cpu time: {0}", System.Diagnostics.Process.GetCurrentProcess().TotalProcessorTime);
154 Console.WriteLine("User cpu time: {0}", System.Diagnostics.Process.GetCurrentProcess().UserProcessorTime);
155 Console.WriteLine("Total wall clock time: {0}", DateTime.Now - start);
156 Console.WriteLine("Peak virtual memory: {0}", System.Diagnostics.Process.GetCurrentProcess().PeakVirtualMemorySize64);
157 for (int i = 0; i <= GC.MaxGeneration; i++)
158 {
159 Console.WriteLine("GC({0}) count: {1}", i, GC.CollectionCount(i));
160 }
161 }
162 }
163 }
164
172 static IDiagnosticHandler GetDiagnostics(IServiceProvider services, ImportState options, string spec)
173 {
174 if (services is null)
175 throw new ArgumentNullException(nameof(services));
176 if (string.IsNullOrWhiteSpace(spec))
177 throw new ArgumentException($"'{nameof(spec)}' cannot be null or whitespace.", nameof(spec));
178
179 return ActivatorUtilities.CreateInstance<CompilerOptionsDiagnosticHandler>(services, options, spec);
180 }
181
182 static int Compile(ImportOptions options)
183 {
184 var rootTarget = new ImportState();
185 var services = new ServiceCollection();
186 services.AddToolsDiagnostics();
187 services.AddSingleton(p => GetDiagnostics(p, rootTarget, options.Log));
188 services.AddSingleton<ISymbolResolver, ManagedResolver>();
189 services.AddSingleton<StaticCompiler>();
190 using var provider = services.BuildServiceProvider();
191
192 var diagnostics = provider.GetRequiredService<IDiagnosticHandler>();
193 var compiler = provider.GetRequiredService<StaticCompiler>();
194 var targets = new List<ImportState>();
195 var context = new RuntimeContext(new RuntimeContextOptions(), diagnostics, provider.GetRequiredService<ISymbolResolver>(), options.Bootstrap, compiler);
196
197 compiler.rootTarget = rootTarget;
198 var importer = new ImportContext();
199 importer.ParseCommandLine(context, compiler, diagnostics, options, targets, rootTarget);
200 compiler.Init(nonDeterministicOutput, rootTarget.debugMode, libpaths);
201 resolver.Warning += (warning, message, parameters) => loader_Warning(compiler, diagnostics, warning, message, parameters);
202 resolver.Init(compiler.Universe, nostdlib, rootTarget.unresolvedReferences, libpaths);
203 ResolveReferences(compiler, diagnostics, targets);
204 ResolveStrongNameKeys(targets);
205
206 if (targets.Count == 0)
207 throw new FatalCompilerErrorException(DiagnosticEvent.NoTargetsFound());
208
209 if (compiler.errorCount != 0)
210 return 1;
211
212 try
213 {
214 return ImportClassLoader.Compile(importer, context, compiler, diagnostics, runtimeAssembly, targets);
215 }
216 catch (FileFormatLimitationExceededException x)
217 {
218 throw new FatalCompilerErrorException(DiagnosticEvent.FileFormatLimitationExceeded(x.Message));
219 }
220 }
221
222 static void loader_Warning(StaticCompiler compiler, IDiagnosticHandler diagnostics, AssemblyResolver.WarningId warning, string message, string[] parameters)
223 {
224 switch (warning)
225 {
226 case AssemblyResolver.WarningId.HigherVersion:
227 diagnostics.AssumeAssemblyVersionMatch(parameters[0], parameters[1]);
228 break;
229 case AssemblyResolver.WarningId.InvalidLibDirectoryOption:
230 diagnostics.InvalidDirectoryInLibOptionPath(parameters[0]);
231 break;
232 case AssemblyResolver.WarningId.InvalidLibDirectoryEnvironment:
233 diagnostics.InvalidDirectoryInLibEnvironmentPath(parameters[0]);
234 break;
235 case AssemblyResolver.WarningId.LegacySearchRule:
236 diagnostics.LegacySearchRule(parameters[0]);
237 break;
238 case AssemblyResolver.WarningId.LocationIgnored:
239 diagnostics.AssemblyLocationIgnored(parameters[0], parameters[1], parameters[2]);
240 break;
241 default:
242 diagnostics.UnknownWarning(string.Format(message, parameters));
243 break;
244 }
245 }
246
247 static void ResolveStrongNameKeys(List<ImportState> targets)
248 {
249 foreach (var options in targets)
250 {
251 if (options.keyfile != null && options.keycontainer != null)
252 throw new FatalCompilerErrorException(DiagnosticEvent.CannotSpecifyBothKeyFileAndContainer());
253
254 if (options.keyfile == null && options.keycontainer == null && options.delaysign)
255 throw new FatalCompilerErrorException(DiagnosticEvent.DelaySignRequiresKey());
256
257 if (options.keyfile != null)
258 {
259 if (options.delaysign)
260 {
261 var buf = ReadAllBytes(options.keyfile);
262 try
263 {
264 // maybe it is a key pair, if so we need to extract just the public key
265 buf = new StrongNameKeyPair(buf).PublicKey;
266 }
267 catch
268 {
269
270 }
271
272 options.publicKey = buf;
273 }
274 else
275 {
276 SetStrongNameKeyPair(ref options.keyPair, options.keyfile, null);
277 }
278 }
279 else if (options.keycontainer != null)
280 {
281 StrongNameKeyPair keyPair = null;
282 SetStrongNameKeyPair(ref keyPair, null, options.keycontainer);
283 if (options.delaysign)
284 options.publicKey = keyPair.PublicKey;
285 else
286 options.keyPair = keyPair;
287 }
288 }
289 }
290
291 internal static byte[] ReadAllBytes(FileInfo path)
292 {
293 for (var attempt = 0; ; attempt++)
294 {
295 try
296 {
297 return File.ReadAllBytes(path.FullName);
298 }
299 catch (IOException) when (attempt < 5)
300 {
301 // javac can briefly retain a handle after it reports success.
302 Thread.Sleep(200 * (attempt + 1));
303 }
304 catch (Exception x)
305 {
306 throw new FatalCompilerErrorException(DiagnosticEvent.ErrorReadingFile(path.ToString(), x.Message));
307 }
308 }
309 }
310
311 void ParseCommandLine(RuntimeContext context, StaticCompiler compiler, IDiagnosticHandler diagnostics, ImportOptions options, List<ImportState> targets, ImportState compilerOptions)
312 {
313 compilerOptions.target = PEFileKinds.ConsoleApplication;
314 compilerOptions.guessFileKind = true;
315 compilerOptions.version = new Version(0, 0, 0, 0);
316 compilerOptions.apartment = ApartmentState.STA;
317 compilerOptions.props = new Dictionary<string, string>();
318 ContinueParseCommandLine(context, compiler, diagnostics, options, targets, compilerOptions);
319 }
320
321 void ContinueParseCommandLine(RuntimeContext context, StaticCompiler compiler, IDiagnosticHandler diagnostics, ImportOptions options, List<ImportState> targets, ImportState compilerOptions)
322 {
323 if (options.Output != null)
324 compilerOptions.path = options.Output;
325
326 if (options.AssemblyName != null)
327 compilerOptions.assembly = options.AssemblyName;
328
329 switch (options.Target)
330 {
331 case ImportTarget.Exe:
332 compilerOptions.target = PEFileKinds.ConsoleApplication;
333 compilerOptions.guessFileKind = false;
334 break;
335 case ImportTarget.WinExe:
336 compilerOptions.target = PEFileKinds.WindowApplication;
337 compilerOptions.guessFileKind = false;
338 break;
339 case ImportTarget.Module:
340 compilerOptions.targetIsModule = true;
341 compilerOptions.target = PEFileKinds.Dll;
342 compilerOptions.guessFileKind = false;
343 nonDeterministicOutput = true;
344 break;
345 case ImportTarget.Library:
346 compilerOptions.target = PEFileKinds.Dll;
347 compilerOptions.guessFileKind = false;
348 break;
349 default:
350 throw new FatalCompilerErrorException(DiagnosticEvent.UnrecognizedTargetType(options.Target.ToString()));
351 }
352
353 switch (options.Platform)
354 {
355 case ImportPlatform.X86:
356 compilerOptions.pekind = PortableExecutableKinds.ILOnly | PortableExecutableKinds.Required32Bit;
357 compilerOptions.imageFileMachine = ImageFileMachine.I386;
358 break;
359 case ImportPlatform.X64:
360 compilerOptions.pekind = PortableExecutableKinds.ILOnly | PortableExecutableKinds.PE32Plus;
361 compilerOptions.imageFileMachine = ImageFileMachine.AMD64;
362 break;
363 case ImportPlatform.ARM:
364 compilerOptions.pekind = PortableExecutableKinds.ILOnly;
365 compilerOptions.imageFileMachine = ImageFileMachine.ARM;
366 break;
367 case ImportPlatform.ARM64:
368 compilerOptions.pekind = PortableExecutableKinds.ILOnly;
369 compilerOptions.imageFileMachine = ImageFileMachine.ARM64;
370 break;
371 case ImportPlatform.AnyCpu32BitPreferred:
372 compilerOptions.pekind = PortableExecutableKinds.ILOnly | PortableExecutableKinds.Preferred32Bit;
373 compilerOptions.imageFileMachine = ImageFileMachine.UNKNOWN;
374 break;
375 case ImportPlatform.AnyCpu:
376 compilerOptions.pekind = PortableExecutableKinds.ILOnly;
377 compilerOptions.imageFileMachine = ImageFileMachine.UNKNOWN;
378 break;
379 default:
380 throw new FatalCompilerErrorException(DiagnosticEvent.UnrecognizedPlatform(options.Platform.ToString()));
381 }
382
383 switch (options.Apartment)
384 {
385 case ImportApartment.STA:
386 compilerOptions.apartment = ApartmentState.STA;
387 break;
388 case ImportApartment.MTA:
389 compilerOptions.apartment = ApartmentState.MTA;
390 break;
391 case ImportApartment.None:
392 compilerOptions.apartment = ApartmentState.Unknown;
393 break;
394 default:
395 throw new FatalCompilerErrorException(DiagnosticEvent.UnrecognizedApartment(options.Apartment.ToString()));
396 }
397
398 if (options.NoGlobbing)
399 compilerOptions.noglobbing = true;
400
401 if (options.Properties.Count > 0)
402 foreach (var kvp in options.Properties)
403 compilerOptions.props[kvp.Key] = kvp.Value;
404
405 if (options.EnableAssertions != null)
406 {
407 if (options.EnableAssertions.Length == 0)
408 compilerOptions.props["ikvm.assert.default"] = "true";
409 else
410 compilerOptions.props["ikvm.assert.enable"] = string.Join(";", options.EnableAssertions);
411 }
412
413 if (options.DisableAssertions != null)
414 {
415 if (options.DisableAssertions.Length == 0)
416 compilerOptions.props["ikvm.assert.default"] = "false";
417 else
418 compilerOptions.props["ikvm.assert.disable"] = string.Join(";", options.DisableAssertions);
419 }
420
421 if (options.RemoveAssertions)
422 compilerOptions.codegenoptions |= CodeGenOptions.RemoveAsserts;
423
424 if (options.Main != null)
425 compilerOptions.mainClass = options.Main;
426
427 foreach (var reference in options.References)
428 ArrayAppend(ref compilerOptions.unresolvedReferences, reference);
429
430 foreach (var spec in options.Recurse)
431 {
432 var exists = false;
433
434 // MONOBUG On Mono 1.0.2, Directory.Exists throws an exception if we pass an invalid directory name
435 try
436 {
437 exists = Directory.Exists((string)spec);
438 }
439 catch (IOException)
440 {
441
442 }
443
444 var found = false;
445 if (exists)
446 {
447 var dir = new DirectoryInfo(spec);
448 found = Recurse(context, compiler, compilerOptions, diagnostics, dir, dir, "*");
449 }
450 else
451 {
452 try
453 {
454 var dir = new DirectoryInfo(Path.GetDirectoryName(spec));
455 if (dir.Exists)
456 {
457 found = Recurse(context, compiler, compilerOptions, diagnostics, dir, dir, Path.GetFileName(spec));
458 }
459 else
460 {
461 found = RecurseJar(context, compiler, compilerOptions, diagnostics, spec);
462 }
463 }
464 catch (PathTooLongException)
465 {
466 throw new FatalCompilerErrorException(DiagnosticEvent.PathTooLong(spec));
467 }
468 catch (DirectoryNotFoundException)
469 {
470 throw new FatalCompilerErrorException(DiagnosticEvent.PathNotFound(spec));
471 }
472 catch (ArgumentException)
473 {
474 throw new FatalCompilerErrorException(DiagnosticEvent.InvalidPath(spec));
475 }
476 }
477
478 if (!found)
479 throw new FatalCompilerErrorException(DiagnosticEvent.FileNotFound(spec));
480 }
481
482 foreach (var kvp in options.Resources)
483 {
484 var fileInfo = GetFileInfo(kvp.Value.FullName);
485 var fileName = kvp.Key.TrimStart('/').TrimEnd('/');
486 compilerOptions.GetResourcesJar().Add(fileName, ReadAllBytes(fileInfo), fileInfo);
487 }
488
489 foreach (var kvp in options.ExternalResources)
490 {
491 if (!File.Exists(kvp.Value.FullName))
492 throw new FatalCompilerErrorException(DiagnosticEvent.ExternalResourceNotFound(kvp.Value.FullName));
493 if (Path.GetFileName(kvp.Value.FullName) != kvp.Value.FullName)
494 throw new FatalCompilerErrorException(DiagnosticEvent.ExternalResourceNameInvalid(kvp.Value.FullName));
495
496 // TODO resource name clashes should be tested
497 compilerOptions.externalResources ??= new Dictionary<string, string>();
498 compilerOptions.externalResources.Add(kvp.Key, kvp.Value.FullName);
499 }
500
501 if (options.NoJNI)
502 compilerOptions.codegenoptions |= CodeGenOptions.NoJNI;
503
504 if (options.Exclude != null)
505 ProcessExclusionFile(ref compilerOptions.classesToExclude, options.Exclude.FullName);
506
507 if (options.Version != null)
508 compilerOptions.version = options.Version;
509
510 if (options.FileVersion != null)
511 compilerOptions.fileversion = options.FileVersion.ToString();
512
513 if (options.Win32Icon != null)
514 {
515 compilerOptions.iconfile = GetFileInfo(options.Win32Icon.FullName);
516 }
517
518 if (options.Win32Manifest != null)
519 compilerOptions.manifestFile = GetFileInfo(options.Win32Manifest.FullName);
520
521 if (options.KeyFile != null)
522 compilerOptions.keyfile = GetFileInfo(options.KeyFile.FullName);
523
524 if (options.Key != null)
525 compilerOptions.keycontainer = options.Key;
526
527 if (options.DelaySign)
528 compilerOptions.delaysign = true;
529
530 switch (options.Debug)
531 {
532 case ImportDebug.None:
533 compilerOptions.debugMode = DebugMode.None;
534 break;
535 case ImportDebug.Portable:
536 compilerOptions.codegenoptions |= CodeGenOptions.EmitSymbols;
537 compilerOptions.debugMode = DebugMode.Portable;
538 break;
539 case ImportDebug.Embedded:
540 compilerOptions.codegenoptions |= CodeGenOptions.EmitSymbols;
541 compilerOptions.debugMode = DebugMode.Embedded;
542 break;
543 }
544
545 if (options.Deterministic == false)
546 nonDeterministicOutput = true;
547
548 if (options.Optimize == false)
549 compilerOptions.codegenoptions |= CodeGenOptions.DisableOptimizations;
550
551 if (options.SourcePath != null)
552 compilerOptions.sourcepath = options.SourcePath.FullName;
553
554 if (options.Remap != null)
555 compilerOptions.remapfile = GetFileInfo(options.Remap.FullName);
556
557 if (options.NoStackTraceInfo)
558 compilerOptions.codegenoptions |= CodeGenOptions.NoStackTraceInfo;
559
560 if (options.RemoveUnusedPrivateFields)
561 compilerOptions.codegenoptions |= CodeGenOptions.RemoveUnusedFields;
562
563 if (options.CompressResources)
564 compilerOptions.compressedResources = true;
565
566 if (options.StrictFinalFieldSemantics)
567 compilerOptions.codegenoptions |= CodeGenOptions.StrictFinalFieldSemantics;
568
569 if (options.PrivatePackages != null)
570 foreach (var prefix in options.PrivatePackages)
571 ArrayAppend(ref compilerOptions.privatePackages, prefix);
572
573 if (options.PublicPackages != null)
574 foreach (var prefix in options.PublicPackages)
575 ArrayAppend(ref compilerOptions.publicPackages, prefix);
576
577 if (options.NoWarn != null)
578 foreach (var diagnostic in options.NoWarn)
579 compilerOptions.suppressWarnings.Add(diagnostic.Id.ToString());
580
581 // TODO handle specific diagnostic IDs
582 if (options.WarnAsError != null)
583 {
584 if (options.WarnAsError.Length == 0)
585 compilerOptions.warnaserror = true;
586 else
587 foreach (var i in options.WarnAsError)
588 compilerOptions.errorWarnings.Add(i.Id.ToString());
589 }
590
591 if (options.Runtime != null)
592 runtimeAssembly = options.Runtime.FullName;
593
594 if (options.Time)
595 time = true;
596
597 if (options.ClassLoader != null)
598 compilerOptions.classLoader = options.ClassLoader;
599
600 if (options.SharedClassLoader)
601 compilerOptions.sharedclassloader ??= new List<ImportClassLoader>();
602
603 if (options.BaseAddress != null)
604 {
605 var baseAddress = options.BaseAddress;
606 ulong baseAddressParsed;
607 if (baseAddress.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
608 baseAddressParsed = ulong.Parse(baseAddress.Substring(2), System.Globalization.NumberStyles.AllowHexSpecifier);
609 else
610 baseAddressParsed = ulong.Parse(baseAddress); // note that unlike CSC we don't support octal
611
612 compilerOptions.baseAddress = (baseAddressParsed & 0xFFFFFFFFFFFF0000UL);
613 }
614
615 if (options.FileAlign != null)
616 {
617 if (!uint.TryParse(options.FileAlign, out var filealign) || filealign < 512 || filealign > 8192 || (filealign & (filealign - 1)) != 0)
618 throw new FatalCompilerErrorException(DiagnosticEvent.InvalidFileAlignment(options.FileAlign));
619
620 compilerOptions.fileAlignment = filealign;
621 }
622
623 if (options.NoPeerCrossReference)
624 compilerOptions.crossReferenceAllPeers = false;
625
626 if (options.NoStdLib)
627 nostdlib = true;
628
629 if (options.Libraries != null)
630 foreach (var lib in options.Libraries)
631 libpaths.Add(lib.FullName);
632
633 if (options.NoAutoSerialization)
634 compilerOptions.codegenoptions |= CodeGenOptions.NoAutomagicSerialization;
635
636 if (options.HighEntropyVA)
637 {
638 compilerOptions.highentropyva = true;
639 }
640
641 if (options.Proxies != null)
642 {
643 foreach (var proxy in options.Proxies)
644 {
645 if (compilerOptions.proxies.Contains(proxy))
646 diagnostics.DuplicateProxy(proxy);
647
648 compilerOptions.proxies.Add(proxy);
649 }
650 }
651
652 if (options.AllowNonVirtualCalls)
653 JVM.AllowNonVirtualCalls = true;
654
655 if (options.Static)
656 {
657 // we abuse -static to also enable support for NoRefEmit scenarios
658 compilerOptions.codegenoptions |= CodeGenOptions.DisableDynamicBinding | CodeGenOptions.NoRefEmitHelpers;
659 }
660
661 if (options.NoJarStubs) // undocumented temporary option to mitigate risk
662 {
663 compilerOptions.nojarstubs = true;
664 }
665
666 if (options.AssemblyAttributes != null)
667 foreach (var i in options.AssemblyAttributes)
668 ProcessAttributeAnnotationsClass(context, diagnostics, ref compilerOptions.assemblyAttributeAnnotations, i.FullName);
669
670 if (options.WarningLevel4Option) // undocumented option to always warn if a class isn't found
671 compilerOptions.warningLevelHigh = true;
672
673 if (options.NoParameterReflection) // undocumented option to compile core class libraries with, to disable MethodParameter attribute
674 compilerOptions.noParameterReflection = true;
675
676 if (options.Bootstrap)
677 compilerOptions.bootstrap = true;
678
679 if (compilerOptions.targetIsModule && compilerOptions.sharedclassloader != null)
680 throw new FatalCompilerErrorException(DiagnosticEvent.SharedClassLoaderCannotBeUsedOnModuleTarget());
681
682 ReadFiles(context, compiler, compilerOptions, diagnostics, options.Inputs.Select(i => i.FullName).ToList());
683
684 foreach (var nested in options.Nested)
685 {
686 var nestedLevel = new ImportContext();
687 nestedLevel.manifestMainClass = manifestMainClass;
688 nestedLevel.defaultAssemblyName = defaultAssemblyName;
689 nestedLevel.ContinueParseCommandLine(context, compiler, diagnostics, nested, targets, compilerOptions.Copy());
690 }
691
692 if (compilerOptions.assembly == null)
693 {
694 var basename = compilerOptions.path == null ? defaultAssemblyName : compilerOptions.path.Name;
695 if (basename == null)
696 throw new FatalCompilerErrorException(DiagnosticEvent.NoOutputFileSpecified());
697
698 int idx = basename.LastIndexOf('.');
699 if (idx > 0)
700 compilerOptions.assembly = basename.Substring(0, idx);
701 else
702 compilerOptions.assembly = basename;
703 }
704
705 if (compilerOptions.path != null && compilerOptions.guessFileKind)
706 {
707 if (compilerOptions.path.Extension.Equals(".dll", StringComparison.OrdinalIgnoreCase))
708 compilerOptions.target = PEFileKinds.Dll;
709
710 compilerOptions.guessFileKind = false;
711 }
712
713 if (compilerOptions.mainClass == null && manifestMainClass != null && (compilerOptions.guessFileKind || compilerOptions.target != PEFileKinds.Dll))
714 {
715 diagnostics.MainMethodFromManifest(manifestMainClass);
716 compilerOptions.mainClass = manifestMainClass;
717 }
718
719 // schedule run if leaf-node
720 if (options.Nested == null || options.Nested.Length == 0)
721 targets.Add(compilerOptions);
722 }
723
724 internal static FileInfo GetFileInfo(string path)
725 {
726 try
727 {
728 FileInfo fileInfo = new FileInfo(path);
729 if (fileInfo.Directory == null)
730 {
731 // this happens with an incorrect unc path (e.g. "\\foo\bar")
732 throw new FatalCompilerErrorException(DiagnosticEvent.InvalidPath(path));
733 }
734 return fileInfo;
735 }
736 catch (ArgumentException)
737 {
738 throw new FatalCompilerErrorException(DiagnosticEvent.InvalidPath(path));
739 }
740 catch (NotSupportedException)
741 {
742 throw new FatalCompilerErrorException(DiagnosticEvent.InvalidPath(path));
743 }
744 catch (PathTooLongException)
745 {
746 throw new FatalCompilerErrorException(DiagnosticEvent.PathTooLong(path));
747 }
748 catch (UnauthorizedAccessException)
749 {
750 // this exception does not appear to be possible
751 throw new FatalCompilerErrorException(DiagnosticEvent.InvalidPath(path));
752 }
753 }
754
755 void ReadFiles(RuntimeContext context, StaticCompiler compiler, ImportState options, IDiagnosticHandler diagnostics, List<string> fileNames)
756 {
757 foreach (var fileName in fileNames)
758 {
759 if (defaultAssemblyName == null)
760 {
761 try
762 {
763 defaultAssemblyName = new FileInfo(Path.GetFileName(fileName)).Name;
764 }
765 catch (ArgumentException)
766 {
767 // if the filename contains a wildcard (or any other invalid character), we ignore
768 // it as a potential default assembly name
769 }
770 catch (NotSupportedException)
771 {
772
773 }
774 catch (PathTooLongException)
775 {
776
777 }
778 }
779
780 if (HasWildcardPattern(fileName) == false)
781 {
782 if (File.Exists(fileName))
783 {
784 ProcessFile(context, compiler, options, diagnostics, null, fileName);
785 continue;
786 }
787
788 diagnostics.InputFileNotFound(fileName);
789 continue;
790 }
791
792 string[] files = null;
793 try
794 {
795 var path = Path.GetDirectoryName(fileName);
796 files = Directory.GetFiles(path == "" ? "." : path, Path.GetFileName(fileName));
797 }
798 catch
799 {
800
801 }
802
803 if (files == null || files.Length == 0)
804 {
805 diagnostics.InputFileNotFound(fileName);
806 }
807 else
808 {
809 foreach (var f in files)
810 {
811 ProcessFile(context, compiler, options, diagnostics, null, f);
812 }
813 }
814 }
815 }
816
817 static bool HasWildcardPattern(string path)
818 {
819 return path.IndexOf('*') >= 0 || path.IndexOf('?') >= 0;
820 }
821
822 internal static bool TryParseVersion(string str, out Version version)
823 {
824 if (str.EndsWith(".*"))
825 {
826 str = str.Substring(0, str.Length - 1);
827 int count = str.Split('.').Length;
828 // NOTE this is the published algorithm for generating automatic build and revision numbers
829 // (see AssemblyVersionAttribute constructor docs), but it turns out that the revision
830 // number is off an hour (on my system)...
831 DateTime now = DateTime.Now;
832 int seconds = (int)(now.TimeOfDay.TotalSeconds / 2);
833 int days = (int)(now - new DateTime(2000, 1, 1)).TotalDays;
834 if (count == 3)
835 {
836 str += days + "." + seconds;
837 }
838 else if (count == 4)
839 {
840 str += seconds;
841 }
842 else
843 {
844 version = null;
845 return false;
846 }
847 }
848 try
849 {
850 version = new Version(str);
851 return version.Major <= 65535 && version.Minor <= 65535 && version.Build <= 65535 && version.Revision <= 65535;
852 }
853 catch (ArgumentException) { }
854 catch (FormatException) { }
855 catch (OverflowException) { }
856 version = null;
857 return false;
858 }
859
860 static void SetStrongNameKeyPair(ref StrongNameKeyPair strongNameKeyPair, FileInfo keyFile, string keyContainer)
861 {
862 try
863 {
864 if (keyFile != null)
865 strongNameKeyPair = new StrongNameKeyPair(ReadAllBytes(keyFile));
866 else
867 strongNameKeyPair = new StrongNameKeyPair(keyContainer);
868
869 // FXBUG we explicitly try to access the public key force a check (the StrongNameKeyPair constructor doesn't validate the key)
870 if (strongNameKeyPair.PublicKey != null)
871 {
872
873 }
874 }
875 catch (Exception x)
876 {
877 throw new FatalCompilerErrorException(DiagnosticEvent.InvalidStrongNameKeyPair(keyFile != null ? "file" : "container", x.Message));
878 }
879 }
880
881 static void ResolveReferences(StaticCompiler compiler, IDiagnosticHandler diagnostics, List<ImportState> targets)
882 {
883 var cache = new Dictionary<string, IKVM.Reflection.Assembly>();
884
885 foreach (var target in targets)
886 {
887 if (target.unresolvedReferences != null)
888 {
889 foreach (string reference in target.unresolvedReferences)
890 {
891 foreach (var peer in targets)
892 {
893 if (peer.assembly.Equals(reference, StringComparison.OrdinalIgnoreCase))
894 {
895 ArrayAppend(ref target.peerReferences, peer.assembly);
896 goto next_reference;
897 }
898 }
899 if (!resolver.ResolveReference(cache, ref target.references, reference))
900 {
901 throw new FatalCompilerErrorException(DiagnosticEvent.ReferenceNotFound(reference));
902 }
903 next_reference:;
904 }
905 }
906 }
907
908 // verify that we didn't reference any secondary assemblies of a shared class loader group
909 foreach (var target in targets)
910 {
911 if (target.references != null)
912 {
913 foreach (var asm in target.references)
914 {
915 var forwarder = asm.GetType("__<MainAssembly>");
916 if (forwarder != null && forwarder.Assembly != asm)
917 diagnostics.NonPrimaryAssemblyReference(asm.Location, forwarder.Assembly.GetName().Name);
918 }
919 }
920 }
921
922 // add legacy references (from stub files)
923 foreach (var target in targets)
924 foreach (var assemblyName in target.legacyStubReferences.Keys)
925 ArrayAppend(ref target.references, resolver.LegacyLoad(new AssemblyName(assemblyName), null));
926
927 // now pre-load the secondary assemblies of any shared class loader groups
928 foreach (var target in targets)
929 if (target.references != null)
930 foreach (var asm in target.references)
931 RuntimeAssemblyClassLoader.PreloadExportedAssemblies(compiler, asm);
932 }
933
934 private static void ArrayAppend<T>(ref T[] array, T element)
935 {
936 if (array == null)
937 array = [element];
938 else
939 array = ArrayUtil.Concat(array, element);
940 }
941
942 private static void ArrayAppend<T>(ref T[] array, T[] append)
943 {
944 if (array == null)
945 {
946 array = append;
947 }
948 else if (append != null)
949 {
950 T[] tmp = new T[array.Length + append.Length];
951 Array.Copy(array, tmp, array.Length);
952 Array.Copy(append, 0, tmp, array.Length, append.Length);
953 array = tmp;
954 }
955 }
956
957 static byte[] ReadFromZip(ZipArchiveEntry ze)
958 {
959 using MemoryStream ms = new MemoryStream();
960 using Stream s = ze.Open();
961 s.CopyTo(ms);
962 return ms.ToArray();
963 }
964
965 static bool EmitStubWarning(RuntimeContext context, StaticCompiler compiler, ImportState options, IDiagnosticHandler diagnostics, byte[] buf)
966 {
967 IKVM.Runtime.ClassFile cf = null;
968
969 try
970 {
971 try
972 {
973 cf = new IKVM.Runtime.ClassFile(context, diagnostics, IKVM.ByteCode.Decoding.ClassFile.Read(buf), "<unknown>", ClassFileParseOptions.StaticImport, null);
974 }
975 catch (ClassFormatError)
976 {
977 return false;
978 }
979 catch (ByteCodeException)
980 {
981 return false;
982 }
983
984 if (cf.IKVMAssemblyAttribute == null)
985 {
986 return false;
987 }
988
989 if (cf.IKVMAssemblyAttribute.StartsWith("[["))
990 {
991 var r = new Regex(@"\[([^\[\]]+)\]");
992 var mc = r.Matches(cf.IKVMAssemblyAttribute);
993 foreach (Match m in mc)
994 {
995 options.legacyStubReferences[m.Groups[1].Value] = null;
996 diagnostics.StubsAreDeprecated(m.Groups[1].Value);
997 }
998 }
999 else
1000 {
1001 options.legacyStubReferences[cf.IKVMAssemblyAttribute] = null;
1002 diagnostics.StubsAreDeprecated(cf.IKVMAssemblyAttribute);
1003 }
1004
1005 return true;
1006 }
1007 finally
1008 {
1009 cf?.Dispose();
1010 }
1011 }
1012
1013 static bool IsExcludedOrStubLegacy(RuntimeContext context, StaticCompiler compiler, ImportState options, IDiagnosticHandler diagnostics, ZipArchiveEntry ze, byte[] data)
1014 {
1015 if (ze.Name.EndsWith(".class", StringComparison.OrdinalIgnoreCase))
1016 {
1017 try
1018 {
1019 var name = IKVM.Runtime.ClassFile.GetClassName(data, 0, data.Length, out var stub);
1020 if (options.IsExcludedClass(name) || (stub && EmitStubWarning(context, compiler, options, diagnostics, data)))
1021 {
1022 // we use stubs to add references, but otherwise ignore them
1023 return true;
1024 }
1025 }
1026 catch (ClassFormatError)
1027 {
1028
1029 }
1030 }
1031
1032 return false;
1033 }
1034
1035 void ProcessManifest(StaticCompiler compiler, ImportState options, ZipArchiveEntry ze)
1036 {
1037 if (manifestMainClass == null)
1038 {
1039 // read main class from manifest
1040 // TODO find out if we can use other information from manifest
1041 using Stream stream = ze.Open();
1042 using StreamReader rdr = new StreamReader(stream);
1043 string line;
1044 while ((line = rdr.ReadLine()) != null)
1045 {
1046 if (line.StartsWith("Main-Class: "))
1047 {
1048 line = line.Substring(12);
1049 string continuation;
1050 while ((continuation = rdr.ReadLine()) != null
1051 && continuation.StartsWith(" ", StringComparison.Ordinal))
1052 {
1053 line += continuation.Substring(1);
1054 }
1055 manifestMainClass = line.Replace('/', '.');
1056 break;
1057 }
1058 }
1059 }
1060 }
1061
1062 bool ProcessZipFile(RuntimeContext context, StaticCompiler compiler, ImportState options, IDiagnosticHandler diagnostics, string file, Predicate<ZipArchiveEntry> filter)
1063 {
1064 try
1065 {
1066 using var zf = ZipFile.OpenRead(file);
1067
1068 bool found = false;
1069 Jar jar = null;
1070 foreach (var ze in zf.Entries)
1071 {
1072 if (filter != null && !filter(ze))
1073 {
1074 // skip
1075 }
1076 else
1077 {
1078 found = true;
1079 var data = ReadFromZip(ze);
1080 if (IsExcludedOrStubLegacy(context, compiler, options, diagnostics, ze, data))
1081 {
1082 continue;
1083 }
1084 if (jar == null)
1085 {
1086 jar = options.GetJar(file);
1087 }
1088 jar.Add(ze.FullName, data);
1089 if (string.Equals(ze.FullName, "META-INF/MANIFEST.MF", StringComparison.OrdinalIgnoreCase))
1090 {
1091 ProcessManifest(compiler, options, ze);
1092 }
1093 }
1094 }
1095
1096 // include empty zip file
1097 if (!found)
1098 {
1099 options.GetJar(file);
1100 }
1101
1102 return found;
1103 }
1104 catch (InvalidDataException x)
1105 {
1106 throw new FatalCompilerErrorException(DiagnosticEvent.ErrorReadingFile(file, x.Message));
1107 }
1108 }
1109
1110 void ProcessFile(RuntimeContext context, StaticCompiler compiler, ImportState options, IDiagnosticHandler diagnostics, DirectoryInfo baseDir, string file)
1111 {
1112 var fileInfo = GetFileInfo(file);
1113 if (fileInfo.Extension.Equals(".jar", StringComparison.OrdinalIgnoreCase) || fileInfo.Extension.Equals(".zip", StringComparison.OrdinalIgnoreCase))
1114 {
1115 ProcessZipFile(context, compiler, options, diagnostics, file, null);
1116 }
1117 else
1118 {
1119 if (fileInfo.Extension.Equals(".class", StringComparison.OrdinalIgnoreCase))
1120 {
1121 byte[] data = ReadAllBytes(fileInfo);
1122 try
1123 {
1124 var name = IKVM.Runtime.ClassFile.GetClassName(data, 0, data.Length, out var stub);
1125 if (options.IsExcludedClass(name))
1126 return;
1127
1128 // we use stubs to add references, but otherwise ignore them
1129 if (stub && EmitStubWarning(context, compiler, options, diagnostics, data))
1130 return;
1131
1132 options.GetClassesJar().Add(name.Replace('.', '/') + ".class", data, fileInfo);
1133 return;
1134 }
1135 catch (ClassFormatError x)
1136 {
1137 diagnostics.ClassFormatError(file, x.Message);
1138 }
1139 }
1140
1141 if (baseDir == null)
1142 {
1143 diagnostics.UnknownFileType(file);
1144 }
1145 else
1146 {
1147 // include as resource
1148 // extract the resource name by chopping off the base directory
1149 var name = file.Substring(baseDir.FullName.Length);
1150 name = name.TrimStart(Path.DirectorySeparatorChar).Replace('\\', '/');
1151 options.GetResourcesJar().Add(name, ReadAllBytes(fileInfo), fileInfo);
1152 }
1153 }
1154 }
1155
1156 bool Recurse(RuntimeContext context, StaticCompiler compiler, ImportState options, IDiagnosticHandler diagnostics, DirectoryInfo baseDir, DirectoryInfo dir, string spec)
1157 {
1158 bool found = false;
1159
1160 foreach (var file in dir.GetFiles(spec))
1161 {
1162 found = true;
1163 ProcessFile(context, compiler, options, diagnostics, baseDir, file.FullName);
1164 }
1165
1166 foreach (var sub in dir.GetDirectories())
1167 {
1168 found |= Recurse(context, compiler, options, diagnostics, baseDir, sub, spec);
1169 }
1170
1171 return found;
1172 }
1173
1174 bool RecurseJar(RuntimeContext context, StaticCompiler compiler, ImportState options, IDiagnosticHandler diagnostics, string path)
1175 {
1176 var file = "";
1177 for (; ; )
1178 {
1179 file = Path.Combine(Path.GetFileName(path), file);
1180 path = Path.GetDirectoryName(path);
1181 if (Directory.Exists(path))
1182 {
1183 throw new DirectoryNotFoundException();
1184 }
1185 else if (File.Exists(path))
1186 {
1187 var pathFilter = Path.GetDirectoryName(file) + Path.DirectorySeparatorChar;
1188 var fileFilter = "^" + Regex.Escape(Path.GetFileName(file)).Replace("\\*", ".*").Replace("\\?", ".") + "$";
1189
1190 return ProcessZipFile(context, compiler, options, diagnostics, path, delegate (ZipArchiveEntry ze)
1191 {
1192 // MONOBUG Path.GetDirectoryName() doesn't normalize / to \ on Windows
1193 var name = ze.FullName.Replace('/', Path.DirectorySeparatorChar);
1194 return (Path.GetDirectoryName(name) + Path.DirectorySeparatorChar).StartsWith(pathFilter) && Regex.IsMatch(Path.GetFileName(ze.FullName), fileFilter);
1195 });
1196 }
1197 }
1198 }
1199
1200 //This processes an exclusion file with a single regular expression per line
1201 private static void ProcessExclusionFile(ref string[] classesToExclude, string filename)
1202 {
1203 try
1204 {
1205 var list = classesToExclude == null ? new List<string>() : new List<string>(classesToExclude);
1206 using (var file = new StreamReader(filename))
1207 {
1208 string line;
1209 while ((line = file.ReadLine()) != null)
1210 {
1211 line = line.Trim();
1212 if (!line.StartsWith("//") && line.Length != 0)
1213 {
1214 list.Add(line);
1215 }
1216 }
1217 }
1218
1219 classesToExclude = list.ToArray();
1220 }
1221 catch (Exception x)
1222 {
1223 throw new FatalCompilerErrorException(DiagnosticEvent.ErrorReadingFile(filename, x.Message));
1224 }
1225 }
1226
1227 static void ProcessAttributeAnnotationsClass(RuntimeContext context, IDiagnosticHandler diagnostics, ref object[] annotations, string filename)
1228 {
1229 try
1230 {
1231 using var file = File.OpenRead(filename);
1232 var cf = new IKVM.Runtime.ClassFile(context, diagnostics, IKVM.ByteCode.Decoding.ClassFile.Read(file), null, ClassFileParseOptions.StaticImport, null);
1233 ArrayAppend(ref annotations, cf.Annotations);
1234 }
1235 catch (Exception x)
1236 {
1237 throw new FatalCompilerErrorException(DiagnosticEvent.ErrorReadingFile(filename, x.Message));
1238 }
1239 }
1240
1241 internal static void HandleWarnArg(ICollection<string> target, string arg)
1242 {
1243 foreach (var w in arg.Split(','))
1244 {
1245 // Strip IKVM prefix
1246 int prefixStart = w.StartsWith("IKVM", StringComparison.OrdinalIgnoreCase) ? 4 : 0;
1247 int contextIndex = w.IndexOf(':', prefixStart);
1248 string context = string.Empty;
1249 string parse;
1250 if (contextIndex != -1)
1251 {
1252 // context includes ':' separator
1253 context = w.Substring(contextIndex);
1254 parse = w.Substring(prefixStart, contextIndex - prefixStart);
1255 }
1256 else
1257 {
1258 parse = w.Substring(prefixStart);
1259 }
1260
1261 if (!int.TryParse(parse, out var intResult))
1262 {
1263 // NamedResults aren't supported
1264 continue; // silently continue
1265 }
1266
1267 target.Add($"{intResult}{context}");
1268 }
1269 }
1270
1271 }
1272
1273}
Maintains services relevant to an instane of the IKVM runtime.
Provides the capability to look up diagnostic channels.
IDiagnosticHandler implementation that outputs based on a format specification.
Represents the context of an import operation outputing a single assembly.
static int Execute(ImportOptions options)
Holds some state for an instance of ImportContext.
Exposes methods to accept diagnostic invocations.
void NonPrimaryAssemblyReference(string arg0, string arg1)
The 'NonPrimaryAssemblyReference' diagnostic.
void InputFileNotFound(string arg0)
The 'InputFileNotFound' diagnostic.
void AssemblyLocationIgnored(string arg0, string arg1, string arg2)
The 'AssemblyLocationIgnored' diagnostic.
void UnknownFileType(string arg0)
The 'UnknownFileType' diagnostic.
void MainMethodFromManifest(string arg0)
The 'MainMethodFromManifest' diagnostic.
void InvalidDirectoryInLibOptionPath(string arg0)
The 'InvalidDirectoryInLibOptionPath' diagnostic.
void StubsAreDeprecated(string arg0)
The 'StubsAreDeprecated' diagnostic.
void InvalidDirectoryInLibEnvironmentPath(string arg0)
The 'InvalidDirectoryInLibEnvironmentPath' diagnostic.
void AssumeAssemblyVersionMatch(string arg0, string arg1)
The 'AssumeAssemblyVersionMatch' diagnostic.
void UnknownWarning(string arg0)
The 'UnknownWarning' diagnostic.
void ClassFormatError(string arg0, string arg1)
The 'ClassFormatError' diagnostic.
void LegacySearchRule(string arg0)
The 'LegacySearchRule' diagnostic.
void DuplicateProxy(string arg0)
The 'DuplicateProxy' diagnostic.
Provides an interface to resolve a maaged type symbols.
DiagnosticLevel
Represents the level of security of a diagnostic event.
static DiagnosticEvent PathNotFound(string arg0, Exception? exception=null, DiagnosticLocation location=default)
The 'PathNotFound' diagnostic.
static DiagnosticEvent FileFormatLimitationExceeded(string arg0, Exception? exception=null, DiagnosticLocation location=default)
The 'FileFormatLimitationExceeded' diagnostic.
static DiagnosticEvent CannotSpecifyBothKeyFileAndContainer(Exception? exception=null, DiagnosticLocation location=default)
The 'CannotSpecifyBothKeyFileAndContainer' diagnostic.
static DiagnosticEvent UnrecognizedTargetType(string arg0, Exception? exception=null, DiagnosticLocation location=default)
The 'UnrecognizedTargetType' diagnostic.
static DiagnosticEvent ErrorReadingFile(string arg0, string arg1, Exception? exception=null, DiagnosticLocation location=default)
The 'ErrorReadingFile' diagnostic.
static DiagnosticEvent SharedClassLoaderCannotBeUsedOnModuleTarget(Exception? exception=null, DiagnosticLocation location=default)
The 'SharedClassLoaderCannotBeUsedOnModuleTarget' diagnostic.
static DiagnosticEvent DelaySignRequiresKey(Exception? exception=null, DiagnosticLocation location=default)
The 'DelaySignRequiresKey' diagnostic.
static DiagnosticEvent ExternalResourceNotFound(string arg0, Exception? exception=null, DiagnosticLocation location=default)
The 'ExternalResourceNotFound' diagnostic.
static DiagnosticEvent FileNotFound(string arg0, Exception? exception=null, DiagnosticLocation location=default)
The 'FileNotFound' diagnostic.
static DiagnosticEvent InvalidStrongNameKeyPair(string arg0, string arg1, Exception? exception=null, DiagnosticLocation location=default)
The 'InvalidStrongNameKeyPair' diagnostic.
static DiagnosticEvent NoTargetsFound(Exception? exception=null, DiagnosticLocation location=default)
The 'NoTargetsFound' diagnostic.
static DiagnosticEvent PathTooLong(string arg0, Exception? exception=null, DiagnosticLocation location=default)
The 'PathTooLong' diagnostic.
static DiagnosticEvent ExternalResourceNameInvalid(string arg0, Exception? exception=null, DiagnosticLocation location=default)
The 'ExternalResourceNameInvalid' diagnostic.
static DiagnosticEvent UnrecognizedPlatform(string arg0, Exception? exception=null, DiagnosticLocation location=default)
The 'UnrecognizedPlatform' diagnostic.
static DiagnosticEvent UnrecognizedApartment(string arg0, Exception? exception=null, DiagnosticLocation location=default)
The 'UnrecognizedApartment' diagnostic.
static DiagnosticEvent InvalidPath(string arg0, Exception? exception=null, DiagnosticLocation location=default)
The 'InvalidPath' diagnostic.
static DiagnosticEvent NoOutputFileSpecified(Exception? exception=null, DiagnosticLocation location=default)
The 'NoOutputFileSpecified' diagnostic.
static DiagnosticEvent InvalidFileAlignment(string arg0, Exception? exception=null, DiagnosticLocation location=default)
The 'InvalidFileAlignment' diagnostic.
static DiagnosticEvent ReferenceNotFound(string arg0, Exception? exception=null, DiagnosticLocation location=default)
The 'ReferenceNotFound' diagnostic.