blob: 1167f6f9302bbba04596cd19b01dc46ea6cd2dd8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 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
*******************************************************************************/
package org.eclipse.cdt.ui.tests.reducer;
import static org.eclipse.cdt.internal.core.dom.parser.ASTQueries.findInnermostDeclarator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
import org.eclipse.text.edits.DeleteEdit;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.cdt.core.dom.ast.ASTNodeProperty;
import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.IASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTDeclarationListOwner;
import org.eclipse.cdt.core.dom.ast.IASTDeclarationStatement;
import org.eclipse.cdt.core.dom.ast.IASTDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTElaboratedTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTEnumerationSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTEnumerationSpecifier.IASTEnumerator;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroExpansion;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.INodeFactory;
import org.eclipse.cdt.core.dom.ast.IProblemBinding;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTAliasDeclaration;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTConversionName;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTEnumerationSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTExplicitTemplateInstantiation;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTLambdaExpression;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTLinkageSpecification;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamespaceAlias;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamespaceDefinition;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTOperatorName;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateDeclaration;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateId;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateSpecialization;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTUsingDeclaration;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTUsingDirective;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTVisibilityLabel;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPConstructor;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPPartialSpecialization;
import org.eclipse.cdt.core.formatter.DefaultCodeFormatterOptions;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.parser.util.CharArrayUtils;
import org.eclipse.cdt.ui.refactoring.CTextFileChange;
import org.eclipse.cdt.internal.core.dom.parser.ASTQueries;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTName;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPInternalBinding;
import org.eclipse.cdt.internal.core.dom.rewrite.util.ASTNodes;
import org.eclipse.cdt.internal.ui.refactoring.CRefactoring;
import org.eclipse.cdt.internal.ui.refactoring.ModificationCollector;
import org.eclipse.cdt.internal.ui.refactoring.changes.CCompositeChange;
import org.eclipse.cdt.internal.ui.refactoring.utils.SelectionHelper;
public class RemoveUnusedDeclarationsRefactoring extends CRefactoring {
private static final IASTName UNUSED_NAME = new CPPASTName(null);
private static final ProblemFinder problemFinder = new ProblemFinder();
private INodeFactory nodeFactory;
private final DefaultCodeFormatterOptions formattingOptions;
private IIndex index;
private IASTTranslationUnit ast;
private IRegion region;
public RemoveUnusedDeclarationsRefactoring(ICElement element, ISelection selection, ICProject project) {
super(element, selection, project);
name = Messages.RemoveUnusedDeclarationsRefactoring_RemoveUnusedDeclarations;
formattingOptions = new DefaultCodeFormatterOptions(project.getOptions(true));
}
@Override
public RefactoringStatus checkInitialConditions(IProgressMonitor pm)
throws CoreException, OperationCanceledException {
SubMonitor progress = SubMonitor.convert(pm, 10);
RefactoringStatus status = super.checkInitialConditions(progress.newChild(8));
if (status.hasError()) {
return status;
}
ast = getAST(tu, progress.newChild(1));
index = getIndex();
nodeFactory = ast.getASTNodeFactory();
region = selectedRegion.getLength() == 0 ?
new Region(0, ast.getFileLocation().getNodeLength()) : selectedRegion;
if (isProgressMonitorCanceled(progress, initStatus))
return initStatus;
return initStatus;
}
private ICPPASTFunctionDeclarator getDeclaration(IASTNode node) {
while (node != null && !(node instanceof IASTFunctionDefinition)) {
node = node.getParent();
}
if (node != null) {
IASTFunctionDeclarator declarator = ((IASTFunctionDefinition) node).getDeclarator();
if (declarator instanceof ICPPASTFunctionDeclarator) {
return (ICPPASTFunctionDeclarator) declarator;
}
}
return null;
}
@Override
public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditionsContext checkContext) {
return new RefactoringStatus();
}
@Override
public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException {
// This method bypasses the standard refactoring framework involving ModificationCollector and ASTRewrite since
// it is too slow for the gigantic changes this refactoring has to deal with.
NavigableSet<IASTName> names = NameCollector.getContainedNames(ast);
SortedNodeSet<IASTNode> nodesToDelete = new SortedNodeSet<>();
IASTPreprocessorMacroExpansion[] macroExpansions = ast.getMacroExpansions();
for (IASTPreprocessorMacroExpansion macroExpansion : macroExpansions) {
IASTName name = macroExpansion.getMacroReference();
if (SelectionHelper.isNodeInsideRegion(name, region)
&& macroExpansion.getMacroDefinition().getExpansion().isEmpty()) {
nodesToDelete.add(macroExpansion);
}
}
CandidateDeclarationFinder finder = new CandidateDeclarationFinder();
ast.accept(finder);
List<IASTDeclaration> declarations = finder.declarations;
for (int i = declarations.size(); --i >= 0;) {
IASTDeclaration declaration = declarations.get(i);
if (SelectionHelper.isNodeInsideRegion(declaration, region)
&& !problemFinder.containsProblemBinding(declaration)
&& !isPossiblyUsed(declaration, names, nodesToDelete)) {
nodesToDelete.add(declaration);
removeContainedNames(declaration, names);
}
}
String code = ast.getRawSignature();
CTextFileChange fileChange = new CTextFileChange(tu.getElementName(), tu);
fileChange.setEdit(new MultiTextEdit());
int maxOffset = 0;
TextEdit lastEdit = null;
IASTNode lastNode = null;
for (IASTNode node : nodesToDelete) {
int offset = ASTNodes.offset(node);
int endOffset = ASTNodes.endOffset(node);
offset = skipWhitespaceBefore(offset, code);
if (offset < region.getOffset())
offset = region.getOffset();
// Do not attempt to delete nodes inside a deleted region.
if (endOffset > maxOffset) {
DeleteEdit edit = new DeleteEdit(offset, endOffset - offset);
fileChange.addEdit(edit);
if (maxOffset < endOffset)
maxOffset = endOffset;
lastEdit = edit;
lastNode = node;
}
}
CCompositeChange change = new CCompositeChange(""); //$NON-NLS-1$
change.markAsSynthetic();
change.add(fileChange);
change.setDescription(new RefactoringChangeDescriptor(getRefactoringDescriptor()));
return change;
}
private boolean containsAncestor(Collection<IASTNode> nodes, IASTNode node) {
while ((node = node.getParent()) != null) {
if (nodes.contains(node))
return true;
}
return false;
}
private IASTNode getTopMostContainer(IASTNode node) {
while (node != null) {
IASTNode prevNode = node;
node = node.getParent();
if (node instanceof IASTTranslationUnit)
return prevNode;
}
return null;
}
private boolean isPossiblyUsed(IASTDeclaration declaration, NavigableSet<IASTName> names,
SortedNodeSet<IASTNode> nodesToDelete) {
if (declaration instanceof ICPPASTNamespaceDefinition) {
// An empty namespace definition can be removed.
IASTDeclaration[] children = ((ICPPASTNamespaceDefinition) declaration).getDeclarations(false);
for (IASTDeclaration child : children) {
if (!nodesToDelete.contains(child))
return true;
}
return false;
} else if (declaration instanceof ICPPASTLinkageSpecification) {
// An empty linkage specification can be removed.
IASTDeclaration[] children = ((ICPPASTLinkageSpecification) declaration).getDeclarations(false);
for (IASTDeclaration child : children) {
if (!nodesToDelete.contains(child))
return true;
}
return false;
} else if (declaration instanceof ICPPASTVisibilityLabel) {
// A visibility label not followed by a member declaration can be removed.
IASTNode parent = declaration.getParent();
IASTDeclaration[] siblings = ((ICPPASTCompositeTypeSpecifier) parent).getDeclarations(false);
boolean after = false;
for (IASTDeclaration sibling : siblings) {
if (after) {
if (sibling instanceof ICPPASTVisibilityLabel)
break;
if (!nodesToDelete.contains(sibling))
return true;
} else if (sibling == declaration) {
after = true;
}
}
return false;
}
if (ASTQueries.findAncestorWithType(declaration, ICPPASTLambdaExpression.class) != null)
return true; // Removing inside lambda expressions is unsafe.
Collection<IASTName> declaredNames = getDeclaredNames(declaration);
if (declaredNames == null)
return true;
for (IASTName declName : declaredNames) {
char[] declNameChars = declName.getSimpleID();
IASTNode startPoint = declName;
int startOffset = ASTNodes.endOffset(declaration);
if (declaration.getPropertyInParent() == IASTCompositeTypeSpecifier.MEMBER_DECLARATION
&& !(declName.resolveBinding() instanceof ICPPConstructor)
&& (declNameChars.length == 0 || declNameChars[0] != '~')) {
// Member declarations can be referenced by other members declared before them.
startPoint = declaration.getParent();
startOffset = ASTNodes.offset(startPoint);
} else {
ASTNodeProperty property = declName.getPropertyInParent();
if (property == IASTCompositeTypeSpecifier.TYPE_NAME
|| property == ICPPASTEnumerationSpecifier.ENUMERATION_NAME && ((ICPPASTEnumerationSpecifier) declName.getParent()).isScoped()) {
// Start from the first forward declaration of class or scoped enumeration.
while (declName instanceof ICPPASTTemplateId) {
declName = ((ICPPASTTemplateId) declName).getTemplateName();
}
IBinding binding = declName.resolveBinding();
if (binding instanceof IProblemBinding)
return true;
if (binding instanceof ICPPInternalBinding) {
IASTNode[] declarations = ((ICPPInternalBinding) binding).getDeclarations();
if (declarations != null && declarations.length != 0) {
IASTNode firstDeclaration = declarations[0];
int firstDeclarationOffset = ASTNodes.endOffset(firstDeclaration);
if (startOffset > firstDeclarationOffset) {
startPoint = firstDeclaration;
startOffset = firstDeclarationOffset;
}
}
}
}
}
if (declNameChars.length != 0 && declNameChars[0] == '~')
declNameChars = Arrays.copyOfRange(declNameChars, 1, declNameChars.length);
int declOffset = ASTNodes.offset(declaration);
int declEndOffset = ASTNodes.endOffset(declaration);
for (IASTName name : names) {
if (name != declName) {
char[] nameChars = name.getSimpleID();
int offset = nameChars.length != 0 && nameChars[0] == '~' ? 1 : 0;
if (CharArrayUtils.equals(nameChars, offset, nameChars.length - offset, declNameChars)) {
int nameOffset = ASTNodes.offset(name);
if (nameOffset >= declEndOffset || nameOffset >= startOffset && nameOffset < declOffset) {
IASTDeclaration decl = findTopmostNonTemplateDeclaration(name);
if (decl == null)
return true;
if (!isDeclaredBy(name, decl))
return true;
}
}
}
}
}
return false;
};
private static Collection<IASTName> getDeclaredNames(IASTDeclaration declaration) {
while (declaration instanceof ICPPASTTemplateDeclaration) {
declaration = ((ICPPASTTemplateDeclaration) declaration).getDeclaration();
}
while (declaration instanceof ICPPASTTemplateSpecialization) {
declaration = ((ICPPASTTemplateSpecialization) declaration).getDeclaration();
}
while (declaration instanceof ICPPASTExplicitTemplateInstantiation) {
declaration = ((ICPPASTExplicitTemplateInstantiation) declaration).getDeclaration();
}
if (declaration instanceof IASTSimpleDeclaration) {
List<IASTName> names = new ArrayList<>();
IASTDeclarator[] declarators = ((IASTSimpleDeclaration) declaration).getDeclarators();
for (IASTDeclarator declarator : declarators) {
declarator = findInnermostDeclarator(declarator);
IASTName name = declarator.getName();
if (name instanceof ICPPASTConversionName || name instanceof ICPPASTOperatorName)
return null; // Do not remove operators.
addNameIfNotEmpty(name, names);
}
IASTDeclSpecifier declSpecifier = ((IASTSimpleDeclaration) declaration).getDeclSpecifier();
if (declSpecifier instanceof IASTCompositeTypeSpecifier) {
addNameIfNotEmpty(((IASTCompositeTypeSpecifier) declSpecifier).getName(), names);
} else if (declSpecifier instanceof IASTElaboratedTypeSpecifier) {
addNameIfNotEmpty(((IASTElaboratedTypeSpecifier) declSpecifier).getName(), names);
} else if (declSpecifier instanceof IASTEnumerationSpecifier) {
IASTEnumerationSpecifier enumSpecifier = (IASTEnumerationSpecifier) declSpecifier;
IASTName name = enumSpecifier.getName();
addNameIfNotEmpty(name, names);
for (IASTEnumerator enumerator : enumSpecifier.getEnumerators()) {
addNameIfNotEmpty(enumerator.getName(), names);
}
}
return names;
} else if (declaration instanceof IASTFunctionDefinition) {
IASTDeclarator declarator = ((IASTFunctionDefinition) declaration).getDeclarator();
declarator = findInnermostDeclarator(declarator);
IASTName name = declarator.getName();
if (name instanceof ICPPASTConversionName)
return null; // Do not remove conversion operators.
return Collections.singletonList(name);
} else if (declaration instanceof ICPPASTUsingDirective) {
return Collections.singletonList(((ICPPASTUsingDirective) declaration).getQualifiedName());
} else if (declaration instanceof ICPPASTUsingDeclaration) {
return Collections.singletonList(((ICPPASTUsingDeclaration) declaration).getName());
} else if (declaration instanceof ICPPASTNamespaceAlias) {
return Collections.singletonList(((ICPPASTNamespaceAlias) declaration).getAlias());
} else if (declaration instanceof ICPPASTAliasDeclaration) {
return Collections.singletonList(((ICPPASTAliasDeclaration) declaration).getAlias());
}
return null;
}
private static void addNameIfNotEmpty(IASTName name, List<IASTName> names) {
if (name.getSimpleID().length != 0)
names.add(name);
}
/**
* Returns the topmost non-template declaration declaring the given name, or {@code null}
* if the name appears in a non-declaration context.
*/
private static IASTDeclaration findTopmostNonTemplateDeclaration(IASTName name) {
IASTNode node = name;
ASTNodeProperty property = name.getPropertyInParent();
while (property == ICPPASTTemplateId.TEMPLATE_NAME
|| property == ICPPASTQualifiedName.SEGMENT_NAME && node == ((ICPPASTQualifiedName) node.getParent()).getLastName()) {
node = node.getParent();
property = node.getPropertyInParent();
}
if (property == IASTDeclarator.DECLARATOR_NAME
|| property == IASTCompositeTypeSpecifier.TYPE_NAME
|| property == IASTElaboratedTypeSpecifier.TYPE_NAME) {
node = node.getParent().getParent();
}
for (IASTNode parent; (parent = node.getParent()) != null; node = parent) {
if (!(parent instanceof IASTDeclaration)
|| parent instanceof ICPPASTTemplateDeclaration
|| parent instanceof ICPPASTTemplateSpecialization
|| parent instanceof ICPPASTNamespaceDefinition) {
if (node instanceof IASTDeclaration) {
return (IASTDeclaration) node;
}
break;
}
}
return null;
}
/**
* Checks if {@code name} is declared by the {@code declaration}.
*/
private static boolean isDeclaredBy(IASTName name, IASTDeclaration declaration) {
if (declaration.getPropertyInParent() == ICPPASTTemplateSpecialization.OWNED_DECLARATION)
return false;
if (name.getPropertyInParent() == ICPPASTTemplateId.TEMPLATE_NAME && ((ICPPASTTemplateId) name.getParent()).resolveBinding() instanceof ICPPPartialSpecialization)
return false;
if (declaration instanceof IASTSimpleDeclaration) {
IASTDeclSpecifier declSpec = ((IASTSimpleDeclaration) declaration).getDeclSpecifier();
if (declSpec instanceof ICPPASTDeclSpecifier && ((ICPPASTDeclSpecifier) declSpec).isFriend())
return false;
}
while (name instanceof ICPPASTTemplateId) {
name = ((ICPPASTTemplateId) name).getTemplateName();
}
char[] nameChars = name.getSimpleID();
Collection<IASTName> declaredNames = getDeclaredNames(declaration);
if (declaredNames != null) {
for (IASTName declaredName : declaredNames) {
while (declaredName instanceof ICPPASTTemplateId) {
declaredName = ((ICPPASTTemplateId) declaredName).getTemplateName();
}
char[] declaredNameChars = declaredName.getSimpleID();
if (Arrays.equals(nameChars, declaredNameChars))
return true;
}
}
return false;
}
private static void removeContainedNames(IASTNode node, Set<IASTName> names) {
NavigableSet<IASTName> containedNames = NameCollector.getContainedNames(node);
names.removeAll(containedNames);
}
private static int skipWhitespaceBefore(int offset, String text) {
while (--offset >= 0) {
char c = text.charAt(offset);
if (!Character.isWhitespace(c))
break;
}
return offset + 1;
}
@Override
protected void collectModifications(IProgressMonitor pm, ModificationCollector collector)
throws CoreException, OperationCanceledException {
// This method is no-op for this refactoring. The change is created in the createChange method.
}
private class CandidateDeclarationFinder extends ASTVisitor {
final List<IASTDeclaration> declarations = new ArrayList<>();
CandidateDeclarationFinder() {
shouldVisitDeclarations = true;
shouldVisitNamespaces = true;
}
@Override
public int visit(IASTDeclaration declaration) {
if (!declaration.isPartOfTranslationUnitFile())
return PROCESS_SKIP;
if (declaration.getParent() instanceof IASTDeclarationListOwner)
declarations.add(declaration);
return PROCESS_CONTINUE;
}
@Override
public int visit(ICPPASTNamespaceDefinition namespaceDefinition) {
if (!namespaceDefinition.isPartOfTranslationUnitFile())
return PROCESS_SKIP;
declarations.add(namespaceDefinition);
return PROCESS_CONTINUE;
}
}
/**
* Collects all simple names.
*/
private static class NameCollector extends ASTVisitor {
NavigableSet<IASTName> names = new SortedNodeSet<>();
static NavigableSet<IASTName> getContainedNames(IASTNode node) {
NameCollector collector = new NameCollector();
node.accept(collector);
return collector.names;
}
NameCollector() {
this.shouldVisitNames = true;
this.shouldVisitImplicitNames = true;
}
@Override
public int visit(IASTName name) {
if (name instanceof ICPPASTQualifiedName || name instanceof ICPPASTTemplateId
|| name instanceof ICPPASTConversionName) {
return PROCESS_CONTINUE;
}
names.add(name);
return PROCESS_CONTINUE;
}
}
/**
* A set of AST nodes sorted by their offsets, or, if the offsets are equal, by the end offsets
* in the reverse order.
*/
private static class SortedNodeSet<T extends IASTNode> extends TreeSet<T> {
private static final Comparator<IASTNode> COMPARATOR = new Comparator<IASTNode>() {
@Override
public int compare(IASTNode node1, IASTNode node2) {
int c = Integer.compare(ASTNodes.offset(node1), ASTNodes.offset(node2));
if (c != 0)
return c;
return -Integer.compare(ASTNodes.endOffset(node1), ASTNodes.endOffset(node2));
}
};
public SortedNodeSet() {
super(COMPARATOR);
}
}
private static IASTDeclarationStatement getDeclarationStatement(IASTDeclaration declaration) {
while (true) {
IASTNode parent = declaration.getParent();
if (parent instanceof IASTDeclarationStatement)
return (IASTDeclarationStatement) parent;
if (!(parent instanceof ICPPASTTemplateDeclaration))
return null;
declaration = (IASTDeclaration) parent;
}
}
private static IASTName getAstName(IASTDeclarator decl) {
IASTName astName = null;
do {
astName = decl.getName();
if (astName != null && astName.getSimpleID().length != 0)
return astName;
// Resolve parenthesis if need to.
decl = decl.getNestedDeclarator();
} while (decl != null);
return astName;
}
@Override
protected RefactoringDescriptor getRefactoringDescriptor() {
return null;
}
}