// // CheckVisibility.cs // // Author: // Jb Evain (jbevain@novell.com) // // (C) 2007 Novell, Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Text; using Mono.Linker; using Mono.Linker.Steps; using Mono.Cecil; using Mono.Cecil.Cil; namespace Mono.Tuner { public class CheckVisibility : BaseStep { bool throw_on_error; protected override void Process () { throw_on_error = GetThrowOnVisibilityErrorParameter (); } bool GetThrowOnVisibilityErrorParameter () { try { return bool.Parse (Context.GetParameter ("throw_on_visibility_error")); } catch { return false; } } protected override void ProcessAssembly (AssemblyDefinition assembly) { if (assembly.Name.Name == "mscorlib" || assembly.Name.Name == "smcs") return; if (Annotations.GetAction (assembly) != AssemblyAction.Link) return; Report ("in assembly {0}", assembly.Name); foreach (ModuleDefinition module in assembly.Modules) foreach (TypeDefinition type in module.Types) CheckType (type); } void CheckType (TypeDefinition type) { if (!IsVisibleFrom (type, type.BaseType)) { ReportError ("Base type `{0}` of type `{1}` is not visible", type.BaseType, type); } CheckInterfaces (type); CheckFields (type); CheckMethods (type); } void CheckInterfaces (TypeDefinition type) { foreach (var iface in type.Interfaces) { if (!IsVisibleFrom (type, iface.InterfaceType)) { ReportError ("Interface `{0}` implemented by `{1}` is not visible", iface, type); } } } static bool IsPublic (TypeDefinition type) { return (type.DeclaringType == null && type.IsPublic) || type.IsNestedPublic; } static bool AreInDifferentAssemblies (TypeDefinition type, TypeDefinition target) { if (type.Module.Assembly.Name.FullName == target.Module.Assembly.Name.FullName) return false; return !IsInternalVisibleTo (target.Module.Assembly, type.Module.Assembly); } static bool IsInternalVisibleTo (AssemblyDefinition assembly, AssemblyDefinition candidate) { foreach (CustomAttribute attribute in assembly.CustomAttributes) { if (!IsInternalsVisibleToAttribute (attribute)) continue; if (attribute.ConstructorArguments.Count == 0) continue; string signature = (string) attribute.ConstructorArguments [0].Value; if (InternalsVisibleToSignatureMatch (signature, candidate.Name)) return true; } return false; } static bool InternalsVisibleToSignatureMatch (string signature, AssemblyNameReference reference) { int pos = signature.IndexOf (","); if (pos == -1) return signature == reference.Name; string assembly_name = signature.Substring (0, pos); pos = signature.IndexOf ("="); if (pos == -1) throw new ArgumentException (); string public_key = signature.Substring (pos + 1).ToLower (); return assembly_name == reference.Name && public_key == ToPublicKeyString (reference.PublicKey); } static string ToPublicKeyString (byte [] public_key) { StringBuilder signature = new StringBuilder (public_key.Length); for (int i = 0; i < public_key.Length; i++) signature.Append (public_key [i].ToString ("x2")); return signature.ToString (); } static bool IsInternalsVisibleToAttribute (CustomAttribute attribute) { return attribute.Constructor.DeclaringType.FullName == "System.Runtime.CompilerServices.InternalsVisibleToAttribute"; } bool IsVisibleFrom (TypeDefinition type, TypeReference reference) { if (reference == null) return true; if (reference is GenericParameter || reference.GetElementType () is GenericParameter) return true; TypeDefinition other = reference.Resolve (); if (other == null) return true; if (!AreInDifferentAssemblies (type, other)) return true; if (IsPublic (other)) return true; return false; } bool IsVisibleFrom (TypeDefinition type, MethodReference reference) { if (reference == null) return true; MethodDefinition meth = reference.Resolve (); if (meth == null) return true; TypeDefinition dec = (TypeDefinition) meth.DeclaringType; if (!IsVisibleFrom (type, dec)) return false; if (meth.IsPublic) return true; if (type == dec || IsNestedIn (type, dec)) return true; if (meth.IsFamily && InHierarchy (type, dec)) return true; if (meth.IsFamilyOrAssembly && (!AreInDifferentAssemblies (type, dec) || InHierarchy (type, dec))) return true; if (meth.IsFamilyAndAssembly && (!AreInDifferentAssemblies (type, dec) && InHierarchy (type, dec))) return true; if (!AreInDifferentAssemblies (type, dec) && meth.IsAssembly) return true; return false; } bool IsVisibleFrom (TypeDefinition type, FieldReference reference) { if (reference == null) return true; FieldDefinition field = reference.Resolve (); if (field == null) return true; TypeDefinition dec = (TypeDefinition) field.DeclaringType; if (!IsVisibleFrom (type, dec)) return false; if (field.IsPublic) return true; if (type == dec || IsNestedIn (type, dec)) return true; if (field.IsFamily && InHierarchy (type, dec)) return true; if (field.IsFamilyOrAssembly && (!AreInDifferentAssemblies (type, dec) || InHierarchy (type, dec))) return true; if (field.IsFamilyAndAssembly && (!AreInDifferentAssemblies (type, dec) && InHierarchy (type, dec))) return true; if (!AreInDifferentAssemblies (type, dec) && field.IsAssembly) return true; return false; } static bool IsNestedIn (TypeDefinition type, TypeDefinition other) { TypeDefinition declaring = type.DeclaringType; if (declaring == null) return false; if (declaring == other) return true; if (declaring.DeclaringType == null) return false; return IsNestedIn (declaring, other); } static bool InHierarchy (TypeDefinition type, TypeDefinition other) { if (type.BaseType == null) return false; TypeDefinition baseType = type.BaseType.Resolve (); if (baseType == other) return true; return InHierarchy (baseType, other); } static void Report (string pattern, params object [] parameters) { Console.WriteLine ("[check] " + pattern, parameters); } void ReportError (string pattern, params object [] parameters) { Report (pattern, parameters); if (throw_on_error) throw new VisibilityErrorException (string.Format (pattern, parameters)); } void CheckFields (TypeDefinition type) { foreach (FieldDefinition field in type.Fields) { if (!IsVisibleFrom (type, field.FieldType)) { ReportError ("Field `{0}` of type `{1}` is not visible from `{2}`", field.Name, field.FieldType, type); } } } void CheckMethods (TypeDefinition type) { CheckMethods (type, type.Methods); } void CheckMethods (TypeDefinition type, ICollection methods) { foreach (MethodDefinition method in methods) { if (!IsVisibleFrom (type, method.ReturnType)) { ReportError ("Method return type `{0}` in method `{1}` is not visible", method.ReturnType, method); } foreach (ParameterDefinition parameter in method.Parameters) { if (!IsVisibleFrom (type, parameter.ParameterType)) { ReportError ("Parameter `{0}` of type `{1}` in method `{2}` is not visible.", parameter.Index, parameter.ParameterType, method); } } if (method.HasBody) CheckBody (method); } } void CheckBody (MethodDefinition method) { TypeDefinition type = (TypeDefinition) method.DeclaringType; foreach (VariableDefinition variable in method.Body.Variables) { if (!IsVisibleFrom ((TypeDefinition) method.DeclaringType, variable.VariableType)) { ReportError ("Variable `{0}` of type `{1}` from method `{2}` is not visible", variable.Index, variable.VariableType, method); } } foreach (Instruction instr in method.Body.Instructions) { switch (instr.OpCode.OperandType) { case OperandType.InlineType: case OperandType.InlineMethod: case OperandType.InlineField: case OperandType.InlineTok: bool error = false; TypeReference type_ref = instr.Operand as TypeReference; if (type_ref != null) error = !IsVisibleFrom (type, type_ref); MethodReference meth_ref = instr.Operand as MethodReference; if (meth_ref != null) error = !IsVisibleFrom (type, meth_ref); FieldReference field_ref = instr.Operand as FieldReference; if (field_ref != null) error = !IsVisibleFrom (type, field_ref); if (error) { ReportError ("Operand `{0}` of type {1} at offset 0x{2} in method `{3}` is not visible", instr.Operand, instr.OpCode.OperandType, instr.Offset.ToString ("x4"), method); } break; default: continue; } } } class VisibilityErrorException : Exception { public VisibilityErrorException (string message) : base (message) { } } } }