/*******************************************************************************
* Copyright (c) 2012, 2015 Nathan Ridge.
* 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
*******************************************************************************/
package org.eclipse.cdt.internal.core.dom.parser.cpp.semantics;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBase;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
import org.eclipse.cdt.core.parser.util.CollectionUtils;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTTranslationUnit;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ClassTypeHelper;
/**
* Semantic analysis related to inheritance.
*/
public class CPPInheritance {
/**
* This class represents a mapping of virtual methods in a class hierarchy
* to their final overriders (see [class.virtual] p2). Since a class hierarchy
* can contain multiple subobjects of the same type (if multiple, non-virtual
* inheritance is used), and the pure virtual methods of each subobject must
* be implemented independently, we give each subobject of a given type a
* number, and for each method we keep track of the final overriders for each
* subobject number. Generally, there should be only one final overrider per
* subobject (in fact the program is ill-formed if there is more than one),
* but to accurately detect pure virtual methods that haven't been overridden,
* we need to be able to keep track of more than one at a time.
*/
public static class FinalOverriderMap {
private Map<ICPPMethod, Map<Integer, List<ICPPMethod>>> fMap = new HashMap<>();
/**
* Returns the completed final overrider map.
*/
public Map<ICPPMethod, Map<Integer, List<ICPPMethod>>> getMap() {
return fMap;
}
// The methods below are meant to be used while computing the final overrider map.
/**
* Adds 'overrider' as a final overrider of 'method' in subobject 'subobjectNumber'.
*/
void add(ICPPMethod method, int subobjectNumber, ICPPMethod overrider) {
Map<Integer, List<ICPPMethod>> overriders = fMap.get(method);
if (overriders == null) {
overriders = new HashMap<>();
fMap.put(method, overriders);
}
CollectionUtils.listMapGet(overriders, subobjectNumber).add(overrider);
}
/**
* For each subobject for which 'method' has been overridden, sets
* 'overrider' to be its (only) final overrider.
*/
void replaceForAllSubobjects(ICPPMethod method, ICPPMethod overrider) {
Map<Integer, List<ICPPMethod>> overriders = fMap.get(method);
if (overriders == null)
return;
for (Integer i : overriders.keySet()) {
List<ICPPMethod> overridersForSubobject = CollectionUtils.listMapGet(overriders, i);
overridersForSubobject.clear();
overridersForSubobject.add(overrider);
}
}
/**
* Merges the final overriders from another FinalOverriderMap into this one.
*/
void addOverriders(FinalOverriderMap other) {
for (ICPPMethod method : other.fMap.keySet()) {
Map<Integer, List<ICPPMethod>> overriders = fMap.get(method);
if (overriders == null) {
overriders = new HashMap<>();
fMap.put(method, overriders);
}
Map<Integer, List<ICPPMethod>> otherOverriders = other.fMap.get(method);
for (Integer i : otherOverriders.keySet()) {
mergeOverriders(CollectionUtils.listMapGet(overriders, i), otherOverriders.get(i));
}
}
}
/**
* Merges the overriders from 'source' into 'target'.
* Excludes methods in 'source' which themselves have an overrider in 'target'.
*/
private void mergeOverriders(List<ICPPMethod> target, List<ICPPMethod> source) {
List<ICPPMethod> toAdd = new ArrayList<>();
for (ICPPMethod candidate : source) {
boolean superseded = false;
for (ICPPMethod existing : target) {
if (existing != candidate && ClassTypeHelper.isOverrider(existing, candidate)) {
superseded = true;
}
}
if (!superseded) {
toAdd.add(candidate);
}
}
target.addAll(toAdd);
}
}
/**
* Returns the final overrider map for a class hierarchy.
* Final overrider maps are cached in the AST.
*
* @param classType the root of the class hierarchy
* @param point The point of template instantiation, if applicable.
* Also used to access the cache in the AST.
* @return the computed final overrider map
*/
public static FinalOverriderMap getFinalOverriderMap(ICPPClassType classType, IASTNode point) {
Map<ICPPClassType, FinalOverriderMap> cache = null;
if (point != null && point.getTranslationUnit() instanceof CPPASTTranslationUnit) {
cache = ((CPPASTTranslationUnit) point.getTranslationUnit()).getFinalOverriderMapCache();
}
FinalOverriderMap result = null;
if (cache != null) {
result = cache.get(classType);
}
if (result == null) {
result = FinalOverriderAnalysis.computeFinalOverriderMap(classType, point);
}
if (cache != null) {
cache.put(classType, result);
}
return result;
}
/**
* If a given virtual method has a unique final overrider in the class hierarchy rooted at the
* given class, returns that final overrider. Otherwise, returns null.
* @param point The point of template instantiation, if applicable.
* Also used to access the final overrider map cache in the AST.
*/
public static ICPPMethod getFinalOverrider(ICPPMethod method, ICPPClassType hierarchyRoot,
IASTNode point) {
FinalOverriderMap map = getFinalOverriderMap(hierarchyRoot, point);
Map<Integer, List<ICPPMethod>> finalOverriders = map.getMap().get(method);
if (finalOverriders != null && finalOverriders.size() == 1) {
for (Integer subobjectNumber : finalOverriders.keySet()) {
List<ICPPMethod> overridersForSubobject = finalOverriders.get(subobjectNumber);
if (overridersForSubobject.size() == 1) {
return overridersForSubobject.get(0);
}
}
}
return null;
}
private static class FinalOverriderAnalysis {
/**
* Computes the final overrider map for a class hierarchy.
*
* @param classType the root of the class hierarchy
* @param point the point of template instantiation, if applicable
* @return the computed final overrider map
*/
public static FinalOverriderMap computeFinalOverriderMap(ICPPClassType classType, IASTNode point) {
return new FinalOverriderAnalysis().collectFinalOverriders(classType, false,
new HashSet<ICPPClassType>(), CPPSemantics.MAX_INHERITANCE_DEPTH, point);
}
// The last subobject number used for each type in the hierarchy. This is used to
// assign subobject numbers to subobjects. Virtual subobjects get a subobject
// number of zero, while non-virtual subobjects are number starting from one.
private Map<ICPPClassType, Integer> subobjectNumbers = new HashMap<>();
// Cache of final overrider maps for virtual base subobjects. Since such subobjects
// only occur once in the hierarchy, we can cache the final overrider maps we
// compute for them.
private Map<ICPPClassType, FinalOverriderMap> virtualBaseCache = new HashMap<>();
/**
* Recursive helper function for computeFinalOverriderMap() which computes the final overrider map
* for a subtree of a class hierarchy.
*
* @param classType the root of the subtree in question
* @param isVirtualBase whether 'classType' is inherited virtually
* @param inheritanceChain the chain of classes from the entire hierarchy's root to 'classType'.
* This is used to guard against circular inheritance.
* @param point the point of template instantiation, if applicable
* @return the computed final overrider map for the subtree
*/
private FinalOverriderMap collectFinalOverriders(ICPPClassType classType, boolean isVirtualBase,
Set<ICPPClassType> inheritanceChain, int maxdepth, IASTNode point) {
FinalOverriderMap result = new FinalOverriderMap();
inheritanceChain.add(classType);
// Determine the subobject number for the current class.
int subobjectNumber = 0;
if (!isVirtualBase) {
Integer lastNumber = subobjectNumbers.get(classType);
subobjectNumber = (lastNumber == null ? 0 : lastNumber) + 1;
subobjectNumbers.put(classType, subobjectNumber);
}
// Go through our base classes.
for (ICPPBase base : ClassTypeHelper.getBases(classType, point)) {
IBinding baseClass = base.getBaseClass();
if (!(baseClass instanceof ICPPClassType))
continue;
ICPPClassType baseType = (ICPPClassType) baseClass;
// Guard against circular inheritance.
if (inheritanceChain.contains(baseType))
continue;
// Guard against infinite recursion in inheritance
// for example A<I> deriving from A<I - 1> without
// a base case to end the recursion.
if (maxdepth <= 0)
continue;
// Collect final overrider information from the base class.
// If it's a virtual base class and we've already processed it
// in this class hierarchy, don't process it again.
FinalOverriderMap baseOverriderMap;
if (base.isVirtual()) {
baseOverriderMap = virtualBaseCache.get(baseType);
if (baseOverriderMap == null) {
baseOverriderMap = collectFinalOverriders(baseType, true, inheritanceChain, maxdepth - 1, point);
virtualBaseCache.put(baseType, baseOverriderMap);
}
} else {
baseOverriderMap = collectFinalOverriders(baseType, false, inheritanceChain, maxdepth - 1, point);
}
// Merge final overrider information from base class into this class.
result.addOverriders(baseOverriderMap);
}
// Go through our own methods.
for (ICPPMethod method : ClassTypeHelper.getOwnMethods(classType, point)) {
// Skip methods that don't actually belong to us, such as methods brought
// into scope via a using-declaration.
if (!(method.getOwner() instanceof ICPPClassType &&
((ICPPClassType) method.getOwner()).isSameType(classType))) {
continue;
}
// For purposes of this computation, every virtual method is
// deemed for override itself.
result.add(method, subobjectNumber, method);
// Find all methods overridden by this method, and set their final overrider
// to be this method.
ICPPMethod[] overriddenMethods = ClassTypeHelper.findOverridden(method, point);
for (ICPPMethod overriddenMethod : overriddenMethods)
result.replaceForAllSubobjects(overriddenMethod, method);
}
inheritanceChain.remove(classType);
return result;
}
}
}