/******************************************************************************* * Copyright (c) 2009, 2010 Google, Inc and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Sergey Prigogin (Google) - initial API and implementation * Markus Schorn (Wind River Systems) *******************************************************************************/ package org.eclipse.cdt.internal.core.dom.parser.cpp.semantics; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.dom.ast.DOMException; import org.eclipse.cdt.core.dom.ast.IASTName; import org.eclipse.cdt.core.dom.ast.IBinding; import org.eclipse.cdt.core.dom.ast.ICompositeType; import org.eclipse.cdt.core.dom.ast.IProblemBinding; import org.eclipse.cdt.core.dom.ast.cpp.ICPPBase; import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassScope; import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType; import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunction; import org.eclipse.cdt.core.dom.ast.cpp.ICPPMember; import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod; import org.eclipse.cdt.core.dom.ast.cpp.ICPPScope; import org.eclipse.cdt.core.parser.util.ArrayUtil; import org.eclipse.cdt.internal.core.dom.parser.cpp.ClassTypeHelper; import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPDeferredClassInstance; /** * The context that determines access to private and protected class members. */ public class AccessContext { private static final int v_private = ICPPMember.v_private; private static final int v_protected = ICPPMember.v_protected; public static final int v_public = ICPPMember.v_public; /** * Checks if a binding is accessible from a given name. * @param binding A binding to check access for. * @param from A name corresponding to the binding. * @return <code>true</code> if the binding is accessible. */ public static boolean isAccessible(IBinding binding, IASTName from) { return new AccessContext(from).isAccessible(binding); } private final IASTName name; /** * A chain of nested classes or/and a function that determine accessibility of private/protected members * by participating in friendship or class inheritance relationships. If both, classes and a function * are present in the context, the outermost class has to be local to the function. * {@link "http://www.open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#45"} */ private IBinding[] context; /** * A class through which the bindings are accessed (11.2.4). */ private boolean isUnqualifiedLookup; private ICPPClassType namingClass; // depends on the binding for which we check the access private ICPPClassType firstCandidateForNamingClass; // the first candidate is independent of the binding for which we do the access-check private DOMException initializationException; public AccessContext(IASTName name) { this.name = name; } /** * Checks if a binding is accessible in a given context. * @param binding A binding to check access for. * @return <code>true</code> if the binding is accessible. */ public boolean isAccessible(IBinding binding) { IBinding owner; while ((owner = binding.getOwner()) instanceof ICompositeType && ((ICompositeType) owner).isAnonymous()) { binding = owner; } if (!(owner instanceof ICPPClassType)) { return true; // The binding is not a class member. } ICPPClassType accessOwner= (ICPPClassType) owner; if (!initialize(accessOwner)) { return true; // Assume visibility if anything goes wrong. } if (namingClass == null) { return true; } return isAccessible(binding, (ICPPClassType) owner, namingClass, v_public, 0); } /** * @return <code>true</code> if initialization succeeded. */ private boolean initialize(ICPPClassType accessOwner) { if (context == null) { if (initializationException != null) { return false; } try { context = getContext(name); firstCandidateForNamingClass= getFirstCandidateForNamingClass(name); } catch (DOMException e) { CCorePlugin.log(e); initializationException = e; return false; } } namingClass = getNamingClass(accessOwner); return true; } private boolean isAccessible(IBinding binding, ICPPClassType owner, ICPPClassType derivedClass, int accessLevel, int depth) { if (depth > CPPSemantics.MAX_INHERITANCE_DEPTH) return false; accessLevel = getMemberAccessLevel(derivedClass, accessLevel); if (owner.isSameType(derivedClass)) { if (binding instanceof ICPPMember) { return isAccessible(((ICPPMember) binding).getVisibility(), accessLevel); } else { // TODO(sprigogin): Handle visibility of nested types return true; } } else { ICPPBase[] bases = derivedClass.getBases(); if (bases != null) { for (ICPPBase base : bases) { IBinding baseBinding = base.getBaseClass(); if (baseBinding instanceof ICPPDeferredClassInstance) { // Support content assist for members of deferred instances. baseBinding= ((ICPPDeferredClassInstance) baseBinding).getTemplateDefinition(); } if (!(baseBinding instanceof ICPPClassType)) { continue; } if (!isAccessible(base.getVisibility(), accessLevel)) { continue; } if (isAccessible(binding, owner, (ICPPClassType) baseBinding, accessLevel == v_private ? v_protected : accessLevel, depth + 1)) { return true; } } } return false; } } /** * Returns access level to the members of a class. * @param classType A class * @param inheritedAccessLevel Access level inherited from derived class. One of: v_public, v_protected, * v_private. * @return One of: v_public, v_protected, v_private. */ private int getMemberAccessLevel(ICPPClassType classType, int inheritedAccessLevel) { int accessLevel = inheritedAccessLevel; for (IBinding contextBinding : context) { if (ClassTypeHelper.isFriend(contextBinding, classType)) return v_private; if (accessLevel == v_public && contextBinding instanceof ICPPClassType) { ICPPClassType contextClass = (ICPPClassType) contextBinding; if (isAccessibleBaseClass(classType, contextClass, 0)) accessLevel = v_protected; } } return accessLevel; } private boolean isAccessibleBaseClass(ICPPClassType classType, ICPPClassType defived, int depth) { if (depth > CPPSemantics.MAX_INHERITANCE_DEPTH) return false; if (defived.isSameType(classType)) return true; ICPPBase[] bases = defived.getBases(); if (bases != null) { for (ICPPBase base : bases) { IBinding baseClass = base.getBaseClass(); if (!(baseClass instanceof ICPPClassType)) { continue; } if (depth > 0 && !isAccessible(base.getVisibility(), v_protected)) { continue; } if (isAccessibleBaseClass(classType, (ICPPClassType) baseClass, depth + 1)) { return true; } } } return false; } private ICPPClassType getFirstCandidateForNamingClass(IASTName name) throws DOMException { LookupData data = new LookupData(name); isUnqualifiedLookup= !data.qualified(); ICPPScope scope= CPPSemantics.getLookupScope(name, data); while (scope != null && !(scope instanceof ICPPClassScope)) { scope = CPPSemantics.getParentScope(scope, data.tu); } if (scope instanceof ICPPClassScope) { return ((ICPPClassScope) scope).getClassType(); } return null; } private ICPPClassType getNamingClass(ICPPClassType accessOwner) { ICPPClassType classType = firstCandidateForNamingClass; if (classType != null && isUnqualifiedLookup) { IBinding owner = classType.getOwner(); while (owner instanceof ICPPClassType && !derivesFrom(classType, accessOwner, CPPSemantics.MAX_INHERITANCE_DEPTH)) { classType= (ICPPClassType) owner; owner= classType.getOwner(); } } return classType; } private static boolean derivesFrom(ICPPClassType derived, ICPPClassType target, int maxdepth) { if (derived == target || derived.isSameType(target)) { return true; } if (maxdepth > 0) { for (ICPPBase cppBase : derived.getBases()) { IBinding base= cppBase.getBaseClass(); if (base instanceof ICPPClassType) { ICPPClassType tbase= (ICPPClassType) base; if (tbase.isSameType(target)) { return true; } if (derivesFrom(tbase, target, maxdepth - 1)) return true; } } } return false; } private static IBinding[] getContext(IASTName name) { IBinding[] accessibilityContext = IBinding.EMPTY_BINDING_ARRAY; for (IBinding binding = CPPVisitor.findEnclosingFunctionOrClass(name); binding != null; binding = binding.getOwner()) { if (binding instanceof ICPPMethod || // Definition of an undeclared method. binding instanceof IProblemBinding && ((IProblemBinding) binding).getID() == IProblemBinding.SEMANTIC_MEMBER_DECLARATION_NOT_FOUND) { continue; } if (binding instanceof ICPPFunction || binding instanceof ICPPClassType) { accessibilityContext = ArrayUtil.append(accessibilityContext, binding); } } return ArrayUtil.trim(accessibilityContext); } /** * Checks if objects with the given visibility are accessible at the given access level. * @param visibility one of: v_public, v_protected, v_private. * @param accessLevel one of: v_public, v_protected, v_private. * @return <code>true</code> if the access level is sufficiently high. */ private static boolean isAccessible(int visibility, int accessLevel) { // Note the ordering of numeric visibility values: v_public < v_protected < v_private. return accessLevel >= visibility; } }