2010-02-13 Jb Evain <jbevain@novell.com>
[mcs.git] / tools / mono-shlib-cop / mono-shlib-cop.cs
blob8b00fc458071dac03c1e997441abab309cf4b074
1 //
2 // mono-shlib-cop.cs: Check unmanaged dependencies
3 //
4 // Compile as:
5 // mcs mono-shlib-cop.cs ../../class/Mono.Options/Mono.Options/Options.cs -r:Mono.Posix
6 //
7 // Authors:
8 // Jonathan Pryor (jonpryor@vt.edu)
9 // Jonathan Pryor (jpryor@novell.com)
11 // (C) 2005 Jonathan Pryor
12 // (C) 2008 Novell, Inc.
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
23 //
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
26 //
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37 // About:
38 // mono-shlib-cop is designed to inspect an assembly and report about
39 // potentially erroneous practices. In particular, this includes:
40 // - DllImporting a .so which may be a symlink (which typically requires the
41 // -devel packages on Linux distros, thus bloating installation and
42 // angering users)
43 // - DllImporting a symbol which doesn't exist in the target library
44 // - etc.
46 // Implementation:
47 // - Each assembly needs to be loaded into an AppDomain so that we can
48 // adjust the ApplicationBase path (which will allow us to more reliably
49 // load assemblies which depend upon assemblies in the same directory).
50 // We can share AppDomains (1/directory), but we (alas) can't use a
51 // single AppDomain for the entire app.
52 // - Thus, algorithm:
53 // - Create AppDomain with ApplicationBase path set to directory assembly
54 // resides in
55 // - Create an AssemblyChecker instance within the AppDomain
56 // - Check an assembly with AssemblyChecker; store results in AssemblyCheckInfo.
57 // - Print results.
59 // TODO:
60 // - AppDomain use
61 // - Make -r work correctly (-r:Mono.Posix should read Mono.Posix from the
62 // GAC and inspect it.)
64 #define TRACE
66 using System;
67 using System.Collections;
68 using System.Collections.Generic;
69 using System.Diagnostics;
70 using System.IO;
71 using System.Reflection;
72 using System.Runtime.InteropServices;
73 using System.Xml;
75 using Mono.Options;
76 using Mono.Unix;
78 [assembly: AssemblyTitle ("mono-shlib-cop")]
79 [assembly: AssemblyCopyright ("(C) 2005 Jonathan Pryor")]
80 [assembly: AssemblyDescription ("Looks up shared library dependencies of managed code")]
81 [assembly: Mono.Author ("Jonathan Pryor")]
82 [assembly: Mono.UsageComplement ("[ASSEMBLY]+ [-r:ASSEMBLY_REF]+")]
83 [assembly: Mono.ReportBugsTo ("jonpryor@vt.edu")]
85 namespace Mono.Unmanaged.Check {
86 [Serializable]
87 sealed class MessageInfo {
88 public string Type;
89 public string Member;
90 public string Message;
92 public MessageInfo (string type, string member, string message)
94 Type = type;
95 Member = member;
96 Message = message;
99 public override bool Equals (object value)
101 MessageInfo other = value as MessageInfo;
102 if (other == null)
103 return false;
105 return Type == other.Type && Member == other.Member &&
106 Message == other.Message;
109 public override int GetHashCode ()
111 return Type.GetHashCode () ^ Member.GetHashCode () ^
112 Message.GetHashCode ();
116 sealed class MessageCollection : MarshalByRefObject {
117 private ArrayList InnerList = new ArrayList ();
119 public MessageCollection ()
123 public int Add (MessageInfo value)
125 if (!InnerList.Contains (value))
126 return InnerList.Add (value);
127 return InnerList.IndexOf (value);
130 public void AddRange (MessageInfo[] value)
132 foreach (MessageInfo v in value)
133 Add (v);
136 public void AddRange (MessageCollection value)
138 foreach (MessageInfo v in value)
139 Add (v);
142 public bool Contains (MessageInfo value)
144 return InnerList.Contains (value);
147 public void CopyTo (MessageInfo[] array, int index)
149 InnerList.CopyTo (array, index);
152 public int IndexOf (MessageInfo value)
154 return InnerList.IndexOf (value);
157 public void Insert (int index, MessageInfo value)
159 InnerList.Insert (index, value);
162 public void Remove (MessageInfo value)
164 InnerList.Remove (value);
167 public IEnumerator GetEnumerator ()
169 return InnerList.GetEnumerator ();
173 sealed class AssemblyCheckInfo : MarshalByRefObject {
174 private MessageCollection errors = new MessageCollection ();
175 private MessageCollection warnings = new MessageCollection ();
177 public MessageCollection Errors {
178 get {return errors;}
181 public MessageCollection Warnings {
182 get {return warnings;}
185 private XmlDocument[] mono_configs = new XmlDocument [0];
186 private IDictionary assembly_configs = new Hashtable ();
188 public void SetInstallationPrefixes (IList<string> prefixes)
190 mono_configs = new XmlDocument [prefixes.Count];
191 for (int i = 0; i < mono_configs.Length; ++i) {
192 mono_configs [i] = new XmlDocument ();
193 mono_configs [i].Load (Path.Combine (prefixes [i], "etc/mono/config"));
197 public string GetDllmapEntry (string assemblyPath, string library)
199 string xpath = "/configuration/dllmap[@dll=\"" + library + "\"]";
201 XmlDocument d = GetAssemblyConfig (assemblyPath);
202 if (d != null) {
203 XmlNode map = d.SelectSingleNode (xpath);
204 if (map != null)
205 return map.Attributes ["target"].Value;
207 foreach (XmlDocument config in mono_configs) {
208 XmlNode map = config.SelectSingleNode (xpath);
209 if (map != null)
210 return map.Attributes ["target"].Value;
212 return null;
215 private XmlDocument GetAssemblyConfig (string assemblyPath)
217 XmlDocument d = null;
218 if (assembly_configs.Contains (assemblyPath)) {
219 d = (XmlDocument) assembly_configs [assemblyPath];
221 else {
222 string _config = assemblyPath + ".config";
223 if (File.Exists (_config)) {
224 d = new XmlDocument ();
225 d.Load (_config);
227 assembly_configs.Add (assemblyPath, d);
229 return d;
233 sealed class AssemblyChecker : MarshalByRefObject {
235 public void CheckFile (string file, AssemblyCheckInfo report)
237 try {
238 Check (Assembly.LoadFile (file), report);
240 catch (FileNotFoundException e) {
241 report.Errors.Add (new MessageInfo (null, null,
242 "Could not load `" + file + "': " + e.Message));
246 public void CheckWithPartialName (string partial, AssemblyCheckInfo report)
248 string p = partial;
249 Assembly a;
250 bool retry;
252 do {
253 a = Assembly.LoadWithPartialName (p);
254 retry = p.EndsWith (".dll");
255 if (retry) {
256 p = p.Substring (0, p.Length-4);
258 } while (a == null && retry);
260 if (a == null) {
261 report.Errors.Add (new MessageInfo (null, null,
262 "Could not load assembly reference `" + partial + "'."));
263 return;
266 Check (a, report);
269 private void Check (Assembly a, AssemblyCheckInfo report)
271 foreach (Type t in a.GetTypes ()) {
272 Check (t, report);
276 private void Check (Type type, AssemblyCheckInfo report)
278 BindingFlags bf = BindingFlags.Instance | BindingFlags.Static |
279 BindingFlags.Public | BindingFlags.NonPublic;
281 foreach (MemberInfo mi in type.GetMembers (bf)) {
282 CheckMember (type, mi, report);
286 private void CheckMember (Type type, MemberInfo mi, AssemblyCheckInfo report)
288 DllImportAttribute[] attributes = null;
289 MethodBase[] methods = null;
290 switch (mi.MemberType) {
291 case MemberTypes.Constructor: case MemberTypes.Method: {
292 MethodBase mb = (MethodBase) mi;
293 attributes = new DllImportAttribute[]{GetDllImportInfo (mb)};
294 methods = new MethodBase[]{mb};
295 break;
297 case MemberTypes.Event: {
298 EventInfo ei = (EventInfo) mi;
299 MethodBase add = ei.GetAddMethod (true);
300 MethodBase remove = ei.GetRemoveMethod (true);
301 attributes = new DllImportAttribute[]{
302 GetDllImportInfo (add), GetDllImportInfo (remove)};
303 methods = new MethodBase[]{add, remove};
304 break;
306 case MemberTypes.Property: {
307 PropertyInfo pi = (PropertyInfo) mi;
308 MethodInfo[] accessors = pi.GetAccessors (true);
309 if (accessors == null)
310 break;
311 attributes = new DllImportAttribute[accessors.Length];
312 methods = new MethodBase [accessors.Length];
313 for (int i = 0; i < accessors.Length; ++i) {
314 attributes [i] = GetDllImportInfo (accessors [i]);
315 methods [i] = accessors [i];
317 break;
320 if (attributes == null || methods == null)
321 return;
323 for (int i = 0; i < attributes.Length; ++i) {
324 if (attributes [i] == null)
325 continue;
326 CheckLibrary (methods [i], attributes [i], report);
330 private static DllImportAttribute GetDllImportInfo (MethodBase method)
332 if (method == null)
333 return null;
335 if ((method.Attributes & MethodAttributes.PinvokeImpl) == 0)
336 return null;
338 // .NET 2.0 synthesizes pseudo-attributes such as DllImport
339 DllImportAttribute dia = (DllImportAttribute) Attribute.GetCustomAttribute (method,
340 typeof(DllImportAttribute), false);
341 if (dia != null)
342 return dia;
344 // We're not on .NET 2.0; assume we're on Mono and use some internal
345 // methods...
346 Type MonoMethod = Type.GetType ("System.Reflection.MonoMethod", false);
347 if (MonoMethod == null) {
348 return null;
350 MethodInfo GetDllImportAttribute =
351 MonoMethod.GetMethod ("GetDllImportAttribute",
352 BindingFlags.Static | BindingFlags.NonPublic);
353 if (GetDllImportAttribute == null) {
354 return null;
356 IntPtr mhandle = method.MethodHandle.Value;
357 return (DllImportAttribute) GetDllImportAttribute.Invoke (null,
358 new object[]{mhandle});
361 private void CheckLibrary (MethodBase method, DllImportAttribute attribute,
362 AssemblyCheckInfo report)
364 string library = attribute.Value;
365 string entrypoint = attribute.EntryPoint;
366 string type = method.DeclaringType.FullName;
367 string mname = method.Name;
369 string found = null;
370 string error = null;
372 Trace.WriteLine ("Trying to load base library: " + library);
374 foreach (string name in GetLibraryNames (method.DeclaringType, library, report)) {
375 if (LoadLibrary (type, mname, name, entrypoint, report, out error)) {
376 found = name;
377 break;
381 if (found == null) {
382 report.Errors.Add (new MessageInfo (
383 type, mname,
384 "Could not load library `" + library + "': " + error));
385 return;
388 // UnixFileInfo f = new UnixFileInfo (soname);
389 if (found.EndsWith (".so")) {
390 report.Warnings.Add (new MessageInfo (type, mname,
391 string.Format ("Library `{0}' might be a development library",
392 found)));
396 [DllImport ("libgmodule-2.0.so")]
397 private static extern IntPtr g_module_open (string filename, int flags);
398 private static int G_MODULE_BIND_LAZY = 1 << 0;
399 private static int G_MODULE_BIND_LOCAL = 1 << 1;
400 // private static int G_MODULE_BIND_MASK = 0x03;
402 [DllImport ("libgmodule-2.0.so")]
403 private static extern int g_module_close (IntPtr handle);
405 [DllImport ("libgmodule-2.0.so")]
406 private static extern IntPtr g_module_error ();
408 [DllImport ("libgmodule-2.0.so")]
409 private static extern IntPtr g_module_name (IntPtr h);
411 [DllImport ("libgmodule-2.0.so")]
412 private static extern IntPtr g_module_build_path (
413 string directory, string module_name);
415 [DllImport ("libgmodule-2.0.so")]
416 private static extern int g_module_symbol (IntPtr module,
417 string symbol_name, out IntPtr symbol);
419 [DllImport ("libglib-2.0.so")]
420 private static extern void g_free (IntPtr mem);
422 private static string[] GetLibraryNames (Type type, string library, AssemblyCheckInfo report)
424 // TODO: keep in sync with
425 // mono/metadata/loader.c:mono_lookup_pinvoke_call
426 ArrayList names = new ArrayList ();
428 string dll_map = report.GetDllmapEntry (type.Assembly.Location, library);
429 if (dll_map != null)
430 names.Add (dll_map);
432 names.Add (library);
433 int _dll_index = library.LastIndexOf (".dll");
434 if (_dll_index >= 0)
435 names.Add (library.Substring (0, _dll_index));
437 if (!library.StartsWith ("lib"))
438 names.Add ("lib" + library);
440 IntPtr s = g_module_build_path (null, library);
441 if (s != IntPtr.Zero) {
442 try {
443 names.Add (Marshal.PtrToStringAnsi (s));
445 finally {
446 g_free (s);
450 s = g_module_build_path (".", library);
451 if (s != IntPtr.Zero) {
452 try {
453 names.Add (Marshal.PtrToStringAnsi (s));
455 finally {
456 g_free (s);
460 return (string[]) names.ToArray (typeof(string));
463 private static bool LoadLibrary (string type, string member,
464 string library, string symbol, AssemblyCheckInfo report, out string error)
466 error = null;
467 IntPtr h = g_module_open (library,
468 G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
469 try {
470 Trace.WriteLine (" Trying library name: " + library);
471 if (h != IntPtr.Zero) {
472 string soname = Marshal.PtrToStringAnsi (g_module_name (h));
473 Trace.WriteLine ("Able to load library " + library +
474 "; soname=" + soname);
475 IntPtr ignore;
476 if (g_module_symbol (h, symbol, out ignore) == 0)
477 report.Errors.Add (new MessageInfo (
478 type, member,
479 string.Format ("library `{0}' is missing symbol `{1}'",
480 library, symbol)));
481 return true;
483 error = Marshal.PtrToStringAnsi (g_module_error ());
484 Trace.WriteLine ("\tError loading library `" + library + "': " + error);
486 finally {
487 if (h != IntPtr.Zero)
488 g_module_close (h);
490 return false;
494 class Runner {
496 public static void Main (string[] args)
498 var references = new List<string> ();
499 var prefixes = new List<string> ();
501 List<string> files = new OptionSet {
502 { "p|prefix|prefixes=",
503 "Mono installation prefixes (for $prefix/etc/mono/config)",
504 v => prefixes.Add (v) },
505 { "r|reference|references=",
506 "Assemblies to load by partial names (e.g. from the GAC)",
507 v => references.Add (v) },
508 }.Parse (args);
510 AssemblyChecker checker = new AssemblyChecker ();
511 AssemblyCheckInfo report = new AssemblyCheckInfo ();
512 if (prefixes.Count == 0) {
513 // SystemConfigurationFile is $sysconfdir/mono/VERSION/machine.config
514 // We want $sysconfdir
515 DirectoryInfo configDir =
516 new FileInfo (RuntimeEnvironment.SystemConfigurationFile).Directory.Parent.Parent.Parent;
517 prefixes.Add (configDir.ToString ());
519 report.SetInstallationPrefixes (prefixes);
520 foreach (string assembly in files) {
521 checker.CheckFile (assembly, report);
524 foreach (string assembly in references) {
525 checker.CheckWithPartialName (assembly, report);
528 foreach (MessageInfo m in report.Errors) {
529 PrintMessage ("error", m);
532 foreach (MessageInfo m in report.Warnings) {
533 PrintMessage ("warning", m);
537 private static void PrintMessage (string type, MessageInfo m)
539 Console.Write ("{0}: ", type);
540 if (m.Type != null)
541 Console.Write ("in {0}", m.Type);
542 if (m.Member != null)
543 Console.Write (".{0}: ", m.Member);
544 Console.WriteLine (m.Message);