/* Copyright (C) 2011-2015 de4dot@gmail.com This file is part of de4dot. de4dot is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. de4dot is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with de4dot. If not, see . */ using System; using System.Collections.Generic; using dnlib.DotNet; namespace de4dot.blocks.cflow { /// /// Checks whether a type has access to some other target type, method or field /// according to the target's visibility. /// public struct AccessChecker { TypeDef userType; List userTypeEnclosingTypes; bool enclosingTypesInitialized; Dictionary baseTypes; bool baseTypesInitialized; [Flags] enum CheckTypeAccess { /// /// Can't access the type /// None = 0, /// /// Normal access to the type and its members. Type + member must be public, internal /// or protected (for sub classes) to access the member. /// Normal = 1, /// /// Full access to the type, even if the type is private. If clear, the type /// must be public, internal or protected (for sub classes). /// FullTypeAccess = 2, /// /// Full access to the type's members (types, fields, methods), even if the /// members are private. If clear, the members must be public, internal /// or protected (for sub classes) /// FullMemberAccess = 4, /// /// Full access to the type and its members /// Full = Normal | FullTypeAccess | FullMemberAccess, } /// /// Gets/sets the user type which is accessing the target type, field or method /// public TypeDef UserType { get { return userType; } set { if (userType == value) return; userType = value; enclosingTypesInitialized = false; baseTypesInitialized = false; if (userTypeEnclosingTypes != null) userTypeEnclosingTypes.Clear(); if (baseTypes != null) baseTypes.Clear(); } } /// /// Constructor /// /// The type accessing the target type, field or method public AccessChecker(TypeDef userType) { this.userType = userType; this.userTypeEnclosingTypes = null; this.baseTypes = null; this.enclosingTypesInitialized = false; this.baseTypesInitialized = false; } /// /// Checks whether it can access a method or a field /// /// Operand /// true if it has access to it, false if not, and null /// if we can't determine it (eg. we couldn't resolve a type or input was null) public bool? CanAccess(object op) { var md = op as MethodDef; if (md != null) return CanAccess(md); var mr = op as MemberRef; if (mr != null) return CanAccess(mr); var fd = op as FieldDef; if (fd != null) return CanAccess(fd); var ms = op as MethodSpec; if (ms != null) return CanAccess(ms); var tr = op as TypeRef; if (tr != null) return CanAccess(tr.Resolve()); var td = op as TypeDef; if (td != null) return CanAccess(td); var ts = op as TypeSpec; if (ts != null) return CanAccess(ts); return null; } /// /// Checks whether it can access a /// /// The type /// true if it has access to it, false if not, and null /// if we can't determine it (eg. we couldn't resolve a type or input was null) public bool? CanAccess(TypeRef tr) { if (tr == null) return null; return CanAccess(tr.Resolve()); } /// /// Checks whether it can access a /// /// The type /// true if it has access to it, false if not, and null /// if we can't determine it (eg. we couldn't resolve a type or input was null) public bool? CanAccess(TypeDef td) { var access = GetTypeAccess(td, null); if (access == null) return null; return (access.Value & CheckTypeAccess.Normal) != 0; } /// /// Returns the access we have to . If is /// enclosing this type, we have private access to it and all its members. If its /// declaring type encloses us, we have private access to it, but only normal access /// to its members. Else, we only have normal access to it and its members. If we inherit /// it, we have protected access to it and its members. /// /// The type /// Generic instance of or null if none CheckTypeAccess? GetTypeAccess(TypeDef td, GenericInstSig git) { if (td == null) return null; if (userType == td) return CheckTypeAccess.Full; // If this is our nested type, we have private access to it itself, but normal // access to its members. if (td.DeclaringType == userType) return CheckTypeAccess.Normal | CheckTypeAccess.FullTypeAccess; // If we're not a nested type, td can't be our enclosing type if (userType.DeclaringType == null) return GetTypeAccess2(td, git); // Can't be an enclosing type if they're not in the same module if (userType.Module != td.Module) return GetTypeAccess2(td, git); var tdEncTypes = GetEnclosingTypes(td, true); var ourEncTypes = InitializeOurEnclosingTypes(); int maxChecks = Math.Min(tdEncTypes.Count, ourEncTypes.Count); int commonIndex; for (commonIndex = 0; commonIndex < maxChecks; commonIndex++) { if (tdEncTypes[commonIndex] != ourEncTypes[commonIndex]) break; } // If td encloses us, then we have access to td and all its members even if // they're private. if (commonIndex == tdEncTypes.Count) return CheckTypeAccess.Full; // If there are no common enclosing types, only check the visibility. if (commonIndex == 0) return GetTypeAccess2(td, git); // If td's declaring type encloses this, then we have full access to td even if // it's private, but only normal access to its members. if (commonIndex + 1 == tdEncTypes.Count) return CheckTypeAccess.Normal | CheckTypeAccess.FullTypeAccess; // Normal visibility checks starting from type after common enclosing type. // Note that we have full access to it so we don't need to check its access, // so start from the next one. for (int i = commonIndex + 1; i < tdEncTypes.Count; i++) { if (!IsVisible(tdEncTypes[i], null)) return CheckTypeAccess.None; } return CheckTypeAccess.Normal; } CheckTypeAccess GetTypeAccess2(TypeDef td, GenericInstSig git) { while (td != null) { var declType = td.DeclaringType; if (userType != declType && !IsVisible(td, git)) return CheckTypeAccess.None; td = declType; git = null; } return CheckTypeAccess.Normal; } /// /// Checks whether is visible to us without checking whether they /// have any common enclosing types. /// /// Type /// Generic instance of or null if none bool IsVisible(TypeDef td, GenericInstSig git) { if (td == null) return false; if (td == userType) return true; switch (td.Visibility) { case TypeAttributes.NotPublic: return IsSameAssemblyOrFriendAssembly(td.Module); case TypeAttributes.Public: return true; case TypeAttributes.NestedPublic: return true; case TypeAttributes.NestedPrivate: return false; case TypeAttributes.NestedFamily: return CheckFamily(td, git); case TypeAttributes.NestedAssembly: return IsSameAssemblyOrFriendAssembly(td.Module); case TypeAttributes.NestedFamANDAssem: return IsSameAssemblyOrFriendAssembly(td.Module) && CheckFamily(td, git); case TypeAttributes.NestedFamORAssem: return IsSameAssemblyOrFriendAssembly(td.Module) || CheckFamily(td, git); default: return false; } } bool IsSameAssemblyOrFriendAssembly(ModuleDef module) { if (module == null) return false; var userModule = userType.Module; if (userModule == null) return false; if (userModule == module) return true; var userAsm = userModule.Assembly; var modAsm = module.Assembly; if (IsSameAssembly(userAsm, modAsm)) return true; if (userAsm != null && userAsm.IsFriendAssemblyOf(modAsm)) return true; return false; } static bool IsSameAssembly(IAssembly asm1, IAssembly asm2) { if (asm1 == null || asm2 == null) return false; if (asm1 == asm2) return true; return new AssemblyNameComparer(AssemblyNameComparerFlags.All).Equals(asm1, asm2); } /// /// Checks whether has access to . /// is Family, FamANDAssem, or FamORAssem. /// /// Type /// Generic instance of or null if none bool CheckFamily(TypeDef td, GenericInstSig git) { if (td == null) return false; InitializeBaseTypes(); if (baseTypes.ContainsKey(git == null ? (IType)td : git)) return true; // td is Family, FamANDAssem, or FamORAssem. If we derive from its enclosing type, // we have access to it. var td2 = td.DeclaringType; if (td2 != null && baseTypes.ContainsKey(td2)) return true; // If one of our enclosing types derive from it, we also have access to it var userDeclType = userType.DeclaringType; if (userDeclType != null) return new AccessChecker(userDeclType).CheckFamily(td, git); return false; } void InitializeBaseTypes() { if (baseTypesInitialized) return; if (baseTypes == null) baseTypes = new Dictionary(TypeEqualityComparer.Instance); baseTypesInitialized = true; ITypeDefOrRef baseType = userType; while (baseType != null) { baseTypes[baseType] = true; baseType = baseType.GetBaseType(); } } List InitializeOurEnclosingTypes() { if (!enclosingTypesInitialized) { userTypeEnclosingTypes = GetEnclosingTypes(userType, true); enclosingTypesInitialized = true; } return userTypeEnclosingTypes; } /// /// Returns a list of all enclosing types, in order of non-enclosed to most enclosed type /// /// Type /// true if should be included /// A list of all enclosing types static List GetEnclosingTypes(TypeDef td, bool includeInput) { var list = new List(); if (includeInput && td != null) list.Add(td); while (td != null) { var dt = td.DeclaringType; if (dt == null) break; if (list.Contains(dt)) break; list.Add(dt); td = dt; } list.Reverse(); return list; } /// /// Checks whether it can access a /// /// The field /// true if it has access to it, false if not, and null /// if we can't determine it (eg. we couldn't resolve a type or input was null) public bool? CanAccess(FieldDef fd) { return CanAccess(fd, null); } bool? CanAccess(FieldDef fd, GenericInstSig git) { if (fd == null) return null; var access = GetTypeAccess(fd.DeclaringType, git); if (access == null) return null; var acc = access.Value; if ((acc & CheckTypeAccess.Normal) == 0) return false; if ((acc & CheckTypeAccess.FullMemberAccess) != 0) return true; return IsVisible(fd, git); } bool IsVisible(FieldDef fd, GenericInstSig git) { if (fd == null) return false; var fdDeclaringType = fd.DeclaringType; if (fdDeclaringType == null) return false; if (userType == fdDeclaringType) return true; switch (fd.Access) { case FieldAttributes.PrivateScope: // Private scope aka compiler controlled fields/methods can only be accessed // by a Field/Method token. This means they must be in the same module. return userType.Module == fdDeclaringType.Module; case FieldAttributes.Private: return false; case FieldAttributes.FamANDAssem: return IsSameAssemblyOrFriendAssembly(fdDeclaringType.Module) && CheckFamily(fdDeclaringType, git); case FieldAttributes.Assembly: return IsSameAssemblyOrFriendAssembly(fdDeclaringType.Module); case FieldAttributes.Family: return CheckFamily(fdDeclaringType, git); case FieldAttributes.FamORAssem: return IsSameAssemblyOrFriendAssembly(fdDeclaringType.Module) || CheckFamily(fdDeclaringType, git); case FieldAttributes.Public: return true; default: return false; } } /// /// Checks whether it can access a /// /// The method /// true if it has access to it, false if not, and null /// if we can't determine it (eg. we couldn't resolve a type or input was null) public bool? CanAccess(MethodDef md) { return CanAccess(md, (GenericInstSig)null); } bool? CanAccess(MethodDef md, GenericInstSig git) { if (md == null) return null; var access = GetTypeAccess(md.DeclaringType, git); if (access == null) return null; var acc = access.Value; if ((acc & CheckTypeAccess.Normal) == 0) return false; if ((acc & CheckTypeAccess.FullMemberAccess) != 0) return true; return IsVisible(md, git); } bool IsVisible(MethodDef md, GenericInstSig git) { if (md == null) return false; var mdDeclaringType = md.DeclaringType; if (mdDeclaringType == null) return false; if (userType == mdDeclaringType) return true; switch (md.Access) { case MethodAttributes.PrivateScope: // Private scope aka compiler controlled fields/methods can only be accessed // by a Field/Method token. This means they must be in the same module. return userType.Module == mdDeclaringType.Module; case MethodAttributes.Private: return false; case MethodAttributes.FamANDAssem: return IsSameAssemblyOrFriendAssembly(mdDeclaringType.Module) && CheckFamily(mdDeclaringType, git); case MethodAttributes.Assembly: return IsSameAssemblyOrFriendAssembly(mdDeclaringType.Module); case MethodAttributes.Family: return CheckFamily(mdDeclaringType, git); case MethodAttributes.FamORAssem: return IsSameAssemblyOrFriendAssembly(mdDeclaringType.Module) || CheckFamily(mdDeclaringType, git); case MethodAttributes.Public: return true; default: return false; } } /// /// Checks whether it can access a /// /// The member reference /// true if it has access to it, false if not, and null /// if we can't determine it (eg. we couldn't resolve a type or input was null) public bool? CanAccess(MemberRef mr) { if (mr == null) return null; var parent = mr.Class; var td = parent as TypeDef; if (td != null) return CanAccess(td, mr); var tr = parent as TypeRef; if (tr != null) return CanAccess(tr.Resolve(), mr); var ts = parent as TypeSpec; if (ts != null) return CanAccess(ts.ResolveTypeDef(), ts.TryGetGenericInstSig(), mr); var md = parent as MethodDef; if (md != null) return CanAccess(md, mr); var mod = parent as ModuleRef; if (mod != null) return CanAccess(mod, mr); return null; } bool? CanAccess(TypeDef td, MemberRef mr) { return CanAccess(td, null, mr); } bool? CanAccess(TypeDef td, GenericInstSig git, MemberRef mr) { if (mr == null || td == null) return null; if (mr.MethodSig != null) { var md = td.FindMethodCheckBaseType(mr.Name, mr.MethodSig); if (md == null) { // Assume that it's an array type if it's one of these methods if (mr.Name == "Get" || mr.Name == "Set" || mr.Name == "Address" || mr.Name == ".ctor") return true; return null; } return CanAccess(md, git); } if (mr.FieldSig != null) return CanAccess(td.FindFieldCheckBaseType(mr.Name, mr.FieldSig), git); return null; } bool? CanAccess(MethodDef md, MemberRef mr) { if (mr == null || md == null) return null; return CanAccess(md); } bool? CanAccess(ModuleRef mod, MemberRef mr) { if (mr == null || mod == null || mod.Module == null) return null; var userModule = userType.Module; if (userModule == null) return null; var userAsm = userModule.Assembly; if (!IsSameAssembly(userAsm, mod.Module.Assembly)) return false; if (userAsm == null) return false; var otherMod = userAsm.FindModule(mod.Name); if (otherMod == null) return false; return CanAccess(otherMod.GlobalType, mr); } /// /// Checks whether it can access a /// /// The type spec /// true if it has access to it, false if not, and null /// if we can't determine it (eg. we couldn't resolve a type or input was null) public bool? CanAccess(TypeSpec ts) { return CanAccess(ts.ResolveTypeDef()); } /// /// Checks whether it can access a /// /// The method spec /// true if it has access to it, false if not, and null /// if we can't determine it (eg. we couldn't resolve a type or input was null) public bool? CanAccess(MethodSpec ms) { if (ms == null) return null; var mdr = ms.Method; var md = mdr as MethodDef; if (md != null) return CanAccess(md); var mr = mdr as MemberRef; if (mr != null) return CanAccess(mr); return null; } /// public override string ToString() { return string.Format("{0}", userType); } } }