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