2 // mono-shlib-cop.cs: Check unmanaged dependencies
5 // mcs mono-shlib-cop.cs ../../class/Mono.Options/Mono.Options/Options.cs -r:Mono.Posix
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:
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
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.
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
43 // - DllImporting a symbol which doesn't exist in the target library
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.
53 // - Create AppDomain with ApplicationBase path set to directory assembly
55 // - Create an AssemblyChecker instance within the AppDomain
56 // - Check an assembly with AssemblyChecker; store results in AssemblyCheckInfo.
61 // - Make -r work correctly (-r:Mono.Posix should read Mono.Posix from the
62 // GAC and inspect it.)
67 using System
.Collections
;
68 using System
.Collections
.Generic
;
69 using System
.Diagnostics
;
71 using System
.Reflection
;
72 using System
.Runtime
.InteropServices
;
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
{
87 sealed class MessageInfo
{
90 public string Message
;
92 public MessageInfo (string type
, string member
, string message
)
99 public override bool Equals (object value)
101 MessageInfo other
= value as MessageInfo
;
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)
136 public void AddRange (MessageCollection
value)
138 foreach (MessageInfo v
in value)
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
{
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
);
203 XmlNode map
= d
.SelectSingleNode (xpath
);
205 return map
.Attributes
["target"].Value
;
207 foreach (XmlDocument config
in mono_configs
) {
208 XmlNode map
= config
.SelectSingleNode (xpath
);
210 return map
.Attributes
["target"].Value
;
215 private XmlDocument
GetAssemblyConfig (string assemblyPath
)
217 XmlDocument d
= null;
218 if (assembly_configs
.Contains (assemblyPath
)) {
219 d
= (XmlDocument
) assembly_configs
[assemblyPath
];
222 string _config
= assemblyPath
+ ".config";
223 if (File
.Exists (_config
)) {
224 d
= new XmlDocument ();
227 assembly_configs
.Add (assemblyPath
, d
);
233 sealed class AssemblyChecker
: MarshalByRefObject
{
235 public void CheckFile (string file
, AssemblyCheckInfo report
)
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
)
253 a
= Assembly
.LoadWithPartialName (p
);
254 retry
= p
.EndsWith (".dll");
256 p
= p
.Substring (0, p
.Length
-4);
258 } while (a
== null && retry
);
261 report
.Errors
.Add (new MessageInfo (null, null,
262 "Could not load assembly reference `" + partial + "'."));
269 private void Check (Assembly a
, AssemblyCheckInfo report
)
271 foreach (Type t
in a
.GetTypes ()) {
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}
;
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}
;
306 case MemberTypes
.Property
: {
307 PropertyInfo pi
= (PropertyInfo
) mi
;
308 MethodInfo
[] accessors
= pi
.GetAccessors (true);
309 if (accessors
== null)
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
];
320 if (attributes
== null || methods
== null)
323 for (int i
= 0; i
< attributes
.Length
; ++i
) {
324 if (attributes
[i
] == null)
326 CheckLibrary (methods
[i
], attributes
[i
], report
);
330 private static DllImportAttribute
GetDllImportInfo (MethodBase method
)
335 if ((method
.Attributes
& MethodAttributes
.PinvokeImpl
) == 0)
338 // .NET 2.0 synthesizes pseudo-attributes such as DllImport
339 DllImportAttribute dia
= (DllImportAttribute
) Attribute
.GetCustomAttribute (method
,
340 typeof(DllImportAttribute
), false);
344 // We're not on .NET 2.0; assume we're on Mono and use some internal
346 Type MonoMethod
= Type
.GetType ("System.Reflection.MonoMethod", false);
347 if (MonoMethod
== null) {
350 MethodInfo GetDllImportAttribute
=
351 MonoMethod
.GetMethod ("GetDllImportAttribute",
352 BindingFlags
.Static
| BindingFlags
.NonPublic
);
353 if (GetDllImportAttribute
== 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
;
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
)) {
382 report
.Errors
.Add (new MessageInfo (
384 "Could not load library `" + library
+ "': " + error
));
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",
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
);
433 int _dll_index
= library
.LastIndexOf (".dll");
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
) {
443 names
.Add (Marshal
.PtrToStringAnsi (s
));
450 s
= g_module_build_path (".", library
);
451 if (s
!= IntPtr
.Zero
) {
453 names
.Add (Marshal
.PtrToStringAnsi (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
)
467 IntPtr h
= g_module_open (library
,
468 G_MODULE_BIND_LAZY
| G_MODULE_BIND_LOCAL
);
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
);
476 if (g_module_symbol (h
, symbol
, out ignore
) == 0)
477 report
.Errors
.Add (new MessageInfo (
479 string.Format ("library `{0}' is missing symbol `{1}'",
483 error
= Marshal
.PtrToStringAnsi (g_module_error ());
484 Trace
.WriteLine ("\tError loading library `" + library
+ "': " + error
);
487 if (h
!= IntPtr
.Zero
)
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
) },
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
);
541 Console
.Write ("in {0}", m
.Type
);
542 if (m
.Member
!= null)
543 Console
.Write (".{0}: ", m
.Member
);
544 Console
.WriteLine (m
.Message
);