2 // mono-shlib-cop.cs: Check unmanaged dependencies
5 // mcs mono-shlib-cop.cs -r:Mono.Posix -r:Mono.GetOptions
8 // Jonathan Pryor (jonpryor@vt.edu)
10 // (C) 2005 Jonathan Pryor
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:
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
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.
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
41 // - DllImporting a symbol which doesn't exist in the target library
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.
51 // - Create AppDomain with ApplicationBase path set to directory assembly
53 // - Create an AssemblyChecker instance within the AppDomain
54 // - Check an assembly with AssemblyChecker; store results in Report.
59 // - specify $prefix to use for finding $prefix/etc/mono/config
60 // - dllmap caching? (Is it possible to avoid reading the .config file
61 // into each AppDomain at least once? OS file caching may keep perf from
62 // dieing with all the potential I/O.)
63 // - Make -r work correctly (-r:Mono.Posix should read Mono.Posix from the
64 // GAC and inspect it.)
69 using System.Collections;
70 using System.Diagnostics;
72 using System.Reflection;
73 using System.Runtime.InteropServices;
76 using Mono.GetOptions;
79 [assembly: AssemblyTitle ("mono-shlib-cop")]
80 [assembly: AssemblyCopyright ("(C) 2005 Jonathan Pryor")]
81 [assembly: AssemblyDescription ("Looks up shared library dependencies of managed code")]
82 [assembly: Mono.Author ("Jonathan Pryor")]
83 [assembly: Mono.UsageComplement ("[ASSEMBLY]+ [-r:ASSEMBLY_REF]+")]
84 [assembly: Mono.ReportBugsTo ("jonpryor@vt.edu")]
86 namespace Mono.Unmanaged.Check {
88 sealed class MessageInfo {
91 public string Message;
93 public MessageInfo (string type, string member, string message)
100 public override bool Equals (object value)
102 MessageInfo other = value as MessageInfo;
106 return Type == other.Type && Member == other.Member &&
107 Message == other.Message;
110 public override int GetHashCode ()
112 return Type.GetHashCode () ^ Member.GetHashCode () ^
113 Message.GetHashCode ();
117 sealed class MessageCollection : MarshalByRefObject {
118 private ArrayList InnerList = new ArrayList ();
120 public MessageCollection ()
124 public int Add (MessageInfo value)
126 if (!InnerList.Contains (value))
127 return InnerList.Add (value);
128 return InnerList.IndexOf (value);
131 public void AddRange (MessageInfo[] value)
133 foreach (MessageInfo v in value)
137 public void AddRange (MessageCollection value)
139 foreach (MessageInfo v in value)
143 public bool Contains (MessageInfo value)
145 return InnerList.Contains (value);
148 public void CopyTo (MessageInfo[] array, int index)
150 InnerList.CopyTo (array, index);
153 public int IndexOf (MessageInfo value)
155 return InnerList.IndexOf (value);
158 public void Insert (int index, MessageInfo value)
160 InnerList.Insert (index, value);
163 public void Remove (MessageInfo value)
165 InnerList.Remove (value);
168 public IEnumerator GetEnumerator ()
170 return InnerList.GetEnumerator ();
174 sealed class Report : MarshalByRefObject {
175 private MessageCollection errors = new MessageCollection ();
176 private MessageCollection warnings = new MessageCollection ();
178 public MessageCollection Errors {
182 public MessageCollection Warnings {
183 get {return warnings;}
187 sealed class AssemblyChecker : MarshalByRefObject {
189 public void CheckFile (string file, Report report)
192 Check (Assembly.LoadFile (file), report);
194 catch (FileNotFoundException e) {
195 report.Errors.Add (new MessageInfo (null, null,
196 "Could not load `" + file + "': " + e.Message));
200 public void CheckWithPartialName (string partial, Report report)
202 AssemblyName an = new AssemblyName ();
205 Assembly a = Assembly.Load (an);
208 catch (FileNotFoundException e) {
209 report.Errors.Add (new MessageInfo (null, null,
210 "Could not load assembly reference `" + partial + "': " + e.Message));
214 private void Check (Assembly a, Report report)
216 foreach (Type t in a.GetTypes ()) {
221 private void Check (Type type, Report report)
223 BindingFlags bf = BindingFlags.Instance | BindingFlags.Static |
224 BindingFlags.Public | BindingFlags.NonPublic;
226 foreach (MemberInfo mi in type.GetMembers (bf)) {
227 CheckMember (type, mi, report);
231 private void CheckMember (Type type, MemberInfo mi, Report report)
233 DllImportAttribute[] attributes = null;
234 MethodBase[] methods = null;
235 switch (mi.MemberType) {
236 case MemberTypes.Constructor: case MemberTypes.Method: {
237 MethodBase mb = (MethodBase) mi;
238 attributes = new DllImportAttribute[]{GetDllImportInfo (mb)};
239 methods = new MethodBase[]{mb};
242 case MemberTypes.Event: {
243 EventInfo ei = (EventInfo) mi;
244 MethodBase add = ei.GetAddMethod (true);
245 MethodBase remove = ei.GetRemoveMethod (true);
246 attributes = new DllImportAttribute[]{
247 GetDllImportInfo (add), GetDllImportInfo (remove)};
248 methods = new MethodBase[]{add, remove};
251 case MemberTypes.Property: {
252 PropertyInfo pi = (PropertyInfo) mi;
253 MethodInfo[] accessors = pi.GetAccessors (true);
254 if (accessors == null)
256 attributes = new DllImportAttribute[accessors.Length];
257 methods = new MethodBase [accessors.Length];
258 for (int i = 0; i < accessors.Length; ++i) {
259 attributes [i] = GetDllImportInfo (accessors [i]);
260 methods [i] = accessors [i];
265 if (attributes == null || methods == null)
268 for (int i = 0; i < attributes.Length; ++i) {
269 if (attributes [i] == null)
271 CheckLibrary (methods [i], attributes [i], report);
275 private static DllImportAttribute GetDllImportInfo (MethodBase method)
280 if ((method.Attributes & MethodAttributes.PinvokeImpl) == 0)
283 // .NET 2.0 synthesizes pseudo-attributes such as DllImport
284 DllImportAttribute dia = (DllImportAttribute) Attribute.GetCustomAttribute (method,
285 typeof(DllImportAttribute), false);
289 // We're not on .NET 2.0; assume we're on Mono and use some internal
291 Type MonoMethod = Type.GetType ("System.Reflection.MonoMethod", false);
292 if (MonoMethod == null) {
295 MethodInfo GetDllImportAttribute =
296 MonoMethod.GetMethod ("GetDllImportAttribute",
297 BindingFlags.Static | BindingFlags.NonPublic);
298 if (GetDllImportAttribute == null) {
301 IntPtr mhandle = method.MethodHandle.Value;
302 return (DllImportAttribute) GetDllImportAttribute.Invoke (null,
303 new object[]{mhandle});
306 private void CheckLibrary (MethodBase method, DllImportAttribute attribute,
309 string library = attribute.Value;
310 string entrypoint = attribute.EntryPoint;
311 string type = method.DeclaringType.FullName;
312 string mname = method.Name;
317 Trace.WriteLine ("Trying to load base library: " + library);
319 foreach (string name in GetLibraryNames (method.DeclaringType, library)) {
320 if (LoadLibrary (type, mname, name, entrypoint, report, out error)) {
327 report.Errors.Add (new MessageInfo (
329 "Could not load library `" + library + "': " + error));
333 // UnixFileInfo f = new UnixFileInfo (soname);
334 if (found.EndsWith (".so")) {
335 report.Warnings.Add (new MessageInfo (type, mname,
336 string.Format ("Library `{0}' might be a development library",
341 [DllImport ("libgmodule-2.0.so")]
342 private static extern IntPtr g_module_open (string filename, int flags);
343 private static int G_MODULE_BIND_LAZY = 1 << 0;
344 private static int G_MODULE_BIND_LOCAL = 1 << 1;
345 // private static int G_MODULE_BIND_MASK = 0x03;
347 [DllImport ("libgmodule-2.0.so")]
348 private static extern int g_module_close (IntPtr handle);
350 [DllImport ("libgmodule-2.0.so")]
351 private static extern IntPtr g_module_error ();
353 [DllImport ("libgmodule-2.0.so")]
354 private static extern IntPtr g_module_name (IntPtr h);
356 [DllImport ("libgmodule-2.0.so")]
357 private static extern IntPtr g_module_build_path (
358 string directory, string module_name);
360 [DllImport ("libgmodule-2.0.so")]
361 private static extern int g_module_symbol (IntPtr module,
362 string symbol_name, out IntPtr symbol);
364 [DllImport ("libglib-2.0.so")]
365 private static extern void g_free (IntPtr mem);
367 private static string[] GetLibraryNames (Type type, string library)
369 // TODO: keep in sync with
370 // mono/metadata/loader.c:mono_lookup_pinvoke_call
371 ArrayList names = new ArrayList ();
373 string dll_map = GetDllMapEntry (type, library);
378 int _dll_index = library.LastIndexOf (".dll");
380 names.Add (library.Substring (0, _dll_index));
382 if (!library.StartsWith ("lib"))
383 names.Add ("lib" + library);
385 IntPtr s = g_module_build_path (null, library);
386 if (s != IntPtr.Zero) {
388 names.Add (Marshal.PtrToStringAnsi (s));
395 s = g_module_build_path (".", library);
396 if (s != IntPtr.Zero) {
398 names.Add (Marshal.PtrToStringAnsi (s));
405 return (string[]) names.ToArray (typeof(string));
408 private static bool LoadLibrary (string type, string member,
409 string library, string symbol, Report report, out string error)
412 IntPtr h = g_module_open (library,
413 G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
415 Trace.WriteLine (" Trying library name: " + library);
416 if (h != IntPtr.Zero) {
417 string soname = Marshal.PtrToStringAnsi (g_module_name (h));
418 Trace.WriteLine ("Able to load library " + library +
419 "; soname=" + soname);
421 if (g_module_symbol (h, symbol, out ignore) == 0)
422 report.Errors.Add (new MessageInfo (
424 string.Format ("library `{0}' is missing symbol `{1}'",
428 error = Marshal.PtrToStringAnsi (g_module_error ());
429 Trace.WriteLine ("\tError loading library `" + library + "': " + error);
432 if (h != IntPtr.Zero)
438 private static XmlDocument etc_mono_config;
440 static AssemblyChecker ()
442 etc_mono_config = new XmlDocument ();
443 etc_mono_config.Load ("/etc/mono/config");
446 private static string GetDllMapEntry (Type type, string library)
448 string xpath = "//dllmap[@dll=\"" + library + "\"]";
449 // string xpath = "//dllmap";
450 string _config = type.Assembly.Location + ".config";
451 if (File.Exists (_config)) {
452 Trace.WriteLine ("Reading .config file: " + _config);
453 XmlDocument config = new XmlDocument ();
454 config.Load (_config);
455 XmlNodeList entry = config.SelectNodes (xpath);
457 return entry[0].Attributes ["target"].Value;
459 Trace.WriteLine (".config not found; using /etc/mono/config");
460 XmlNodeList maps = etc_mono_config.SelectNodes (xpath);
461 Trace.WriteLine (string.Format ("{0} <dllmap/> entries found!", maps.Count));
463 return maps[0].Attributes ["target"].Value;
468 class MyOptions : Options {
469 [Option (int.MaxValue, "Assemblies to load by partial names (e.g. from the GAC)", 'r')]
470 public string[] references = new string[]{};
475 [DllImport ("does-not-exist")]
476 private static extern void Foo ();
478 [DllImport ("another-native-lib")]
479 private static extern void Bar ();
481 public static void Main (string[] args)
483 MyOptions o = new MyOptions ();
484 o.ProcessArgs (args);
486 AssemblyChecker checker = new AssemblyChecker ();
487 Report report = new Report ();
488 foreach (string assembly in o.RemainingArguments) {
489 checker.CheckFile (assembly, report);
492 foreach (string assembly in o.references) {
493 checker.CheckWithPartialName (assembly, report);
496 foreach (MessageInfo m in report.Errors) {
497 PrintMessage ("error", m);
500 foreach (MessageInfo m in report.Warnings) {
501 PrintMessage ("warning", m);
505 private static void PrintMessage (string type, MessageInfo m)
507 Console.Write ("{0}: ", type);
509 Console.Write ("in {0}", m.Type);
510 if (m.Member != null)
511 Console.Write (".{0}: ", m.Member);
512 Console.WriteLine (m.Message);