blob: e3627bea3e7e2cc93f6b30aaec2e973eb7a18dcd [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2017 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.api.tools.internal.builder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.Stack;
import java.util.TreeSet;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.api.tools.internal.model.AbstractApiTypeRoot;
import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
import org.eclipse.pde.api.tools.internal.provisional.builder.IReference;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiField;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiMember;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiMethod;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiType;
import org.eclipse.pde.api.tools.internal.util.Signatures;
import org.eclipse.pde.api.tools.internal.util.Util;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.TypePath;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
import org.objectweb.asm.tree.ClassNode;
/**
* Extracts references from a class file
*
* @since 1.0.0
*/
public class ReferenceExtractor extends ClassVisitor {
/**
* A visitor for visiting java 5+ signatures
*
* ClassSignature = (visitFormalTypeParameter visitClassBound?
* visitInterfaceBound* )* (visitSuperClass visitInterface* )
* MethodSignature = (visitFormalTypeParameter visitClassBound?
* visitInterfaceBound* )* (visitParameterType visitReturnType
* visitExceptionType* ) TypeSignature = visitBaseType | visitTypeVariable |
* visitArrayType | (visitClassType visitTypeArgument* (visitInnerClassType
* visitTypeArgument* )* visitEnd</tt> ) )
*/
class ClassFileSignatureVisitor extends SignatureVisitor {
protected int kind = -1;
protected int originalkind = -1;
protected int argumentcount = 0;
protected int type = 0;
protected String signature = null;
protected String name = null;
protected List<Reference> references;
public ClassFileSignatureVisitor() {
super(Opcodes.ASM5);
this.references = new ArrayList<>();
}
/**
* Resets the visitor to its initial state. This method should be called
* after processing is done with the visitor
*/
protected void reset() {
// do not reset argument count, as it is needed once the signature
// visitor is done
this.kind = -1;
this.originalkind = -1;
this.name = null;
this.signature = null;
this.type = 0;
this.references.clear();
}
/**
* Processes the type specified by the name for the current signature
* context. The kind flag is set to a parameterized type as subsequent
* calls to this method without visiting other nodes only occurs when we
* are processing parameterized types of generic declarations
*
* @param name the name of the type
*/
protected void processType(String name) {
Type type = ReferenceExtractor.this.resolveType(Type.getObjectType(name).getDescriptor());
if (type != null) {
String tname = type.getClassName();
if (tname.equals("E") || tname.equals("T")) { //$NON-NLS-1$//$NON-NLS-2$
type = Type.getObjectType("java.lang.Object"); //$NON-NLS-1$
tname = type.getClassName();
}
if (ReferenceExtractor.this.consider(tname) && this.kind != -1) {
if (this.name != null && this.signature != null) {
this.references.add(Reference.typeReference(ReferenceExtractor.this.getMember(), tname, this.signature, this.kind));
}
}
}
this.kind = this.originalkind;
}
@Override
public void visitClassType(String name) {
this.processType(name);
}
@Override
public void visitFormalTypeParameter(String name) {
if (this.type != TYPE) {
this.processType(name);
}
}
@Override
public void visitTypeVariable(String name) {
}
@Override
public void visitInnerClassType(String name) {
this.processType(name);
}
@Override
public SignatureVisitor visitParameterType() {
this.argumentcount++;
this.kind = IReference.REF_PARAMETER;
return this;
}
@Override
public SignatureVisitor visitInterface() {
this.kind = IReference.REF_IMPLEMENTS;
return this;
}
@Override
public SignatureVisitor visitExceptionType() {
this.kind = IReference.REF_THROWS;
return this;
}
@Override
public SignatureVisitor visitArrayType() {
return this;
}
@Override
public SignatureVisitor visitReturnType() {
this.kind = IReference.REF_RETURNTYPE;
return this;
}
@Override
public SignatureVisitor visitClassBound() {
this.kind = IReference.REF_PARAMETERIZED_TYPEDECL;
return this;
}
@Override
public SignatureVisitor visitInterfaceBound() {
this.kind = IReference.REF_PARAMETERIZED_TYPEDECL;
return this;
}
@Override
public SignatureVisitor visitSuperclass() {
this.kind = IReference.REF_EXTENDS;
return this;
}
@Override
public SignatureVisitor visitTypeArgument(char wildcard) {
return this;
}
@Override
public void visitEnd() {
}
@Override
public void visitBaseType(char descriptor) {
switch (descriptor) {
case 'J':
case 'D':
argumentcount += 2;
break;
default:
this.argumentcount++;
}
}
@Override
public void visitTypeArgument() {
}
}
/**
* Visitor used to visit the methods of a type [ visitCode ( visitFrame |
* visit<i>X</i>Insn | visitLabel | visitTryCatchBlock | visitLocalVariable
* | visitLineNumber)* visitMaxs ] visitEnd
*/
class ClassFileMethodVisitor extends MethodVisitor {
int argumentcount = 0;
LinePositionTracker linePositionTracker;
/**
* Most recent string literal encountered. Used to infer
* Class.forName("...") references.
*/
String stringLiteral;
String methodName;
int lastLineNumber;
boolean implicitConstructor = false;
LocalLineNumberMarker localVariableMarker;
HashMap<Label, List<LocalLineNumberMarker>> labelsToLocalMarkers;
/**
* Constructor
*
* @param mv
*/
public ClassFileMethodVisitor(MethodVisitor mv, String name, int argumentcount) {
super(Opcodes.ASM5, mv);
this.argumentcount = argumentcount;
this.linePositionTracker = new LinePositionTracker();
this.lastLineNumber = -1;
this.labelsToLocalMarkers = new HashMap<>();
this.methodName = name;
}
@Override
public void visitEnd() {
this.implicitConstructor = false;
this.argumentcount = 0;
ReferenceExtractor.this.exitMember();
this.linePositionTracker.computeLineNumbers();
this.labelsToLocalMarkers = null;
}
@Override
public void visitVarInsn(int opcode, int var) {
this.stringLiteral = null;
switch (opcode) {
case Opcodes.ASTORE: {
if (this.lastLineNumber != -1) {
this.localVariableMarker = new LocalLineNumberMarker(this.lastLineNumber, var);
}
break;
}
default: {
break;
}
}
}
@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
int refType = -1;
switch (opcode) {
case Opcodes.PUTSTATIC: {
refType = IReference.REF_PUTSTATIC;
break;
}
case Opcodes.PUTFIELD: {
refType = IReference.REF_PUTFIELD;
break;
}
case Opcodes.GETSTATIC: {
refType = IReference.REF_GETSTATIC;
break;
}
case Opcodes.GETFIELD: {
refType = IReference.REF_GETFIELD;
break;
}
default: {
break;
}
}
if (refType != -1) {
Reference reference = ReferenceExtractor.this.addFieldReference(Type.getObjectType(owner), name, refType);
if (reference != null) {
this.linePositionTracker.addLocation(reference);
if (refType == IReference.REF_GETFIELD || refType == IReference.REF_PUTFIELD) {
ReferenceExtractor.this.fieldtracker.addField(reference);
}
}
}
}
@Override
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
if (type != null) {
Type ctype = Type.getObjectType(type);
Reference reference = ReferenceExtractor.this.addTypeReference(ctype, IReference.REF_CATCHEXCEPTION);
if (reference != null) {
this.linePositionTracker.addCatchLabelInfos(reference, handler);
this.linePositionTracker.addLocation(reference);
}
}
}
@Override
public void visitLabel(Label label) {
this.linePositionTracker.addLabel(label);
if (this.localVariableMarker != null) {
List<LocalLineNumberMarker> list = this.labelsToLocalMarkers.get(label);
if (list != null) {
list.add(this.localVariableMarker);
} else {
list = new ArrayList<>();
list.add(this.localVariableMarker);
this.labelsToLocalMarkers.put(label, list);
}
this.localVariableMarker = null;
}
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean inf) {
Type declaringType = Type.getObjectType(owner);
int kind = -1;
int flags = 0;
switch (opcode) {
case Opcodes.INVOKESPECIAL: {
kind = ("<init>".equals(name) ? IReference.REF_CONSTRUCTORMETHOD : IReference.REF_SPECIALMETHOD); //$NON-NLS-1$
if (kind == IReference.REF_CONSTRUCTORMETHOD) {
if (!implicitConstructor && this.methodName.equals("<init>") && !fSuperStack.isEmpty() && (fSuperStack.peek()).equals(declaringType.getClassName())) { //$NON-NLS-1$
implicitConstructor = true;
kind = IReference.REF_SUPER_CONSTRUCTORMETHOD;
} else {
Reference reference = ReferenceExtractor.this.addTypeReference(declaringType, IReference.REF_INSTANTIATE);
if (reference != null) {
this.linePositionTracker.addLocation(reference);
}
}
}
break;
}
case Opcodes.INVOKESTATIC: {
kind = IReference.REF_STATICMETHOD;
// check for reference to a class literal
if (name.equals("forName")) { //$NON-NLS-1$
if (ReferenceExtractor.this.processName(owner).equals("java.lang.Class")) { //$NON-NLS-1$
if (this.stringLiteral != null) {
try {
Type classLiteral = Type.getObjectType(this.stringLiteral);
Reference reference = ReferenceExtractor.this.addTypeReference(classLiteral, IReference.REF_CONSTANTPOOL);
if (reference != null) {
this.linePositionTracker.addLocation(reference);
}
} catch (Exception e) {
// do nothing, but prevent bogus strings
// from causing problems in ASM
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=399898
}
}
}
}
break;
}
case Opcodes.INVOKEVIRTUAL: {
kind = IReference.REF_VIRTUALMETHOD;
// try to determine if this is a default method
if (fVersion >= Opcodes.V1_8) {
IApiMember member = ReferenceExtractor.this.getMember();
if (member != null) {
try {
IApiComponent comp = fType.getApiComponent();
if (comp != null) {
String owner_sig = processName(owner);
AbstractApiTypeRoot root = (AbstractApiTypeRoot) comp.findTypeRoot(owner_sig);
if (root == null) {
// a quick look did not find it, now ask
// for the components that provide the
// package
IApiBaseline baseline = comp.getBaseline();
IApiComponent[] comps = baseline.resolvePackage(comp, Signatures.getPackageName(owner_sig));
for (IApiComponent c : comps) {
root = (AbstractApiTypeRoot) c.findTypeRoot(owner_sig);
if (root != null) {
break;
}
}
}
if (root != null) {
IApiType type = root.getStructure();
if (type != null && getDefaultDefined(type, name, desc, false) != null) {
flags = IReference.F_DEFAULT_METHOD;
}
}
}
} catch (CoreException ce) {
// do nothing, give up
}
}
}
break;
}
case Opcodes.INVOKEINTERFACE: {
kind = IReference.REF_INTERFACEMETHOD;
break;
}
default: {
break;
}
}
if (kind != -1) {
Reference reference = ReferenceExtractor.this.addMethodReference(declaringType, name, desc, kind, flags);
if (reference != null) {
this.linePositionTracker.addLocation(reference);
if (kind == IReference.REF_STATICMETHOD) {
ReferenceExtractor.this.fieldtracker.addAccessor(reference);
}
}
}
this.stringLiteral = null;
}
@Override
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
for (Object arg : bsmArgs) {
if (arg instanceof Handle) {
Handle handle = (Handle) arg;
Type declaringType = Type.getObjectType(handle.getOwner());
Reference reference = ReferenceExtractor.this.addMethodReference(declaringType, handle.getName(), handle.getDesc(), IReference.REF_VIRTUALMETHOD, 0);
if (reference != null) {
this.linePositionTracker.addLocation(reference);
}
}
}
}
@Override
public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
return null;
}
@Override
public void visitMultiANewArrayInsn(String desc, int dims) {
Type type = this.getTypeFromDescription(desc);
Reference reference = ReferenceExtractor.this.addTypeReference(type, IReference.REF_ARRAYALLOC);
if (reference != null) {
this.linePositionTracker.addLocation(reference);
}
}
@Override
public void visitLineNumber(int line, Label start) {
this.lastLineNumber = line;
this.linePositionTracker.addLineInfo(line, start);
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("Method visitor for: "); //$NON-NLS-1$
buffer.append(methodName);
buffer.append("\nCurrent line number: "); //$NON-NLS-1$
buffer.append(lastLineNumber);
return buffer.toString();
}
/**
* Creates a type from a type description. Works around bugs creating
* types from array type signatures in ASM.
*
* @param desc signature
* @return Type
*/
private Type getTypeFromDescription(String desc) {
String ldesc = desc;
while (ldesc.charAt(0) == '[') {
ldesc = ldesc.substring(1);
}
Type type = null;
if (ldesc.endsWith(";")) { //$NON-NLS-1$
type = Type.getType(ldesc);
} else {
type = Type.getObjectType(ldesc);
}
return type;
}
@Override
public void visitTypeInsn(int opcode, String desc) {
Type type = this.getTypeFromDescription(desc);
int kind = -1;
switch (opcode) {
case Opcodes.ANEWARRAY: {
kind = IReference.REF_ARRAYALLOC;
break;
}
case Opcodes.CHECKCAST: {
kind = IReference.REF_CHECKCAST;
break;
}
case Opcodes.INSTANCEOF: {
kind = IReference.REF_INSTANCEOF;
break;
}
case Opcodes.NEW: {
// we can omit the NEW case as it is caught by the
// constructor call
// handle it only for anonymous / local types
List<Reference> refs = fAnonymousTypes.get(processName(type.getInternalName()));
if (refs != null) {
for (Reference reference : refs) {
this.linePositionTracker.addLocation(reference);
}
}
break;
}
default: {
break;
}
}
if (kind != -1) {
Reference reference = ReferenceExtractor.this.addTypeReference(type, kind);
if (reference != null) {
this.linePositionTracker.addLocation(reference);
}
}
}
@Override
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
if (desc.length() == 1) {
// base type
return;
}
if (index > this.argumentcount) {
List<LocalLineNumberMarker> list = this.labelsToLocalMarkers.get(start);
int lineNumber = -1;
if (list != null) {
// list of potential localMarker
// iterate the list to find the one that matches the
// index
LocalLineNumberMarker removeMarker = null;
loop: for (LocalLineNumberMarker marker : list) {
if (marker.varIndex == index) {
lineNumber = marker.lineNumber;
removeMarker = marker;
break loop;
}
}
if (removeMarker != null) {
list.remove(removeMarker);
if (list.isEmpty()) {
this.labelsToLocalMarkers.remove(start);
}
}
}
if (lineNumber == -1) {
return;
}
if (signature != null) {
List<Reference> references = ReferenceExtractor.this.processSignature(name, signature, IReference.REF_PARAMETERIZED_VARIABLE, METHOD);
for (Reference reference : references) {
reference.setLineNumber(lineNumber);
}
} else {
Type type = Type.getType(desc);
if (type.getSort() == Type.OBJECT) {
Reference reference = ReferenceExtractor.this.addTypeReference(type, IReference.REF_LOCALVARIABLEDECL);
if (reference != null) {
reference.setLineNumber(lineNumber);
}
}
}
}
}
@Override
public void visitLdcInsn(Object cst) {
if (cst instanceof Type) {
Type type = (Type) cst;
Reference reference = ReferenceExtractor.this.addTypeReference(type, IReference.REF_CONSTANTPOOL);
if (reference != null) {
this.linePositionTracker.addLocation(reference);
}
} else if (cst instanceof String) {
String str = (String) cst;
this.stringLiteral = (Util.EMPTY_STRING.equals(str) ? null : str);
}
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
Type ctype = this.getTypeFromDescription(desc);
Reference reference = ReferenceExtractor.this.addTypeReference(ctype, IReference.REF_ANNOTATION_USE);
if (reference != null) {
linePositionTracker.addLocation(reference);
}
return null;
}
}
/**
* {@link FieldVisitor} to track use of types in annotations
*
* @since 1.0.600
*/
class ClassFileFieldVisitor extends FieldVisitor {
ClassFileFieldVisitor() {
super(Opcodes.ASM5);
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
addTypeReference(Type.getType(desc), IReference.REF_ANNOTATION_USE);
return null;
}
@Override
public void visitAttribute(Attribute attr) {
}
@Override
public void visitEnd() {
exitMember();
}
}
/**
* @since 1.1
*/
static class FieldTracker {
HashMap<String, List<Reference>> accessors = new HashMap<>();
ArrayList<Reference> fields = new ArrayList<>();
ReferenceExtractor extractor = null;
/**
* Constructor
*/
public FieldTracker(ReferenceExtractor extractor) {
this.extractor = extractor;
}
/**
* Add a field to be tracked
*
* @param field
*/
public void addField(Reference ref) {
if (ref != null) {
fields.add(ref);
}
}
/**
* Add an accessor to be tracked
*
* @param accessor
*/
public void addAccessor(Reference ref) {
if (ref != null) {
String key = ref.getReferencedMemberName();
List<Reference> refs = accessors.get(key);
if (refs == null) {
refs = new ArrayList<>();
accessors.put(key, refs);
}
refs.add(ref);
}
}
/**
* Resolve any synthetic field access to their accessor
*/
public void resolveSyntheticFields() {
Reference field = null;
List<Reference> refs = null;
for (int i = 0; i < fields.size(); i++) {
field = fields.get(i);
refs = accessors.get(field.getMember().getName());
if (refs != null) {
for (Reference accessor : refs) {
Reference refer = Reference.fieldReference(accessor.getMember(), field.getReferencedTypeName(), field.getReferencedMemberName(), field.getReferenceKind());
refer.setLineNumber(accessor.getLineNumber());
this.extractor.collector.add(refer);
}
// we resolved it, remove it
this.extractor.collector.remove(field);
}
}
}
}
static class LinePositionTracker {
List<Object> labelsAndLocations;
SortedSet<LineInfo> lineInfos;
List<LabelInfo> catchLabelInfos;
HashMap<Label, Integer> lineMap;
public LinePositionTracker() {
this.labelsAndLocations = new ArrayList<>();
this.lineInfos = new TreeSet<>();
this.catchLabelInfos = new ArrayList<>();
this.lineMap = new HashMap<>();
}
void addLocation(Reference location) {
this.labelsAndLocations.add(location);
}
void addLineInfo(int line, Label label) {
this.lineInfos.add(new LineInfo(line, label));
this.lineMap.put(label, Integer.valueOf(line));
}
void addCatchLabelInfos(Reference location, Label label) {
this.catchLabelInfos.add(new LabelInfo(location, label));
}
void addLabel(Label label) {
this.labelsAndLocations.add(label);
}
public void computeLineNumbers() {
if (this.lineInfos.size() < 1 || this.labelsAndLocations.size() < 1) {
// nothing to do
return;
}
Iterator<LineInfo> lineInfosIterator = this.lineInfos.iterator();
LineInfo firstLineInfo = lineInfosIterator.next();
int currentLineNumber = firstLineInfo.line;
List<LabelInfo> remainingCatchLabelInfos = new ArrayList<>();
for (LabelInfo catchLabelInfo : this.catchLabelInfos) {
Integer lineValue = this.lineMap.get(catchLabelInfo.label);
if (lineValue != null) {
catchLabelInfo.location.setLineNumber(lineValue.intValue());
} else {
remainingCatchLabelInfos.add(catchLabelInfo);
}
}
// Iterate over List of Labels and SourceLocations.
List<Object> computedEntries = new ArrayList<>();
for (Object current : this.labelsAndLocations) {
if (current instanceof Label) {
// label
Integer lineValue = this.lineMap.get(current);
if (lineValue != null) {
computedEntries.add(new LineInfo(lineValue.intValue(), (Label) current));
} else {
computedEntries.add(current);
}
} else {
// location
computedEntries.add(current);
}
}
List<LabelInfo> remaingEntriesTemp;
for (Object current : computedEntries) {
if (current instanceof Label) {
// try to set the line number for remaining catch labels
if (remainingCatchLabelInfos != null) {
remaingEntriesTemp = new ArrayList<>();
loop: for (LabelInfo catchLabelInfo : remainingCatchLabelInfos) {
if (!current.equals(catchLabelInfo.label)) {
remaingEntriesTemp.add(catchLabelInfo);
continue loop;
}
catchLabelInfo.location.setLineNumber(currentLineNumber);
}
if (remaingEntriesTemp.size() == 0) {
remainingCatchLabelInfos = null;
} else {
remainingCatchLabelInfos = remaingEntriesTemp;
}
}
} else if (current instanceof Reference) {
Reference ref = (Reference) current;
if (ref.getLineNumber() == -1) {
ref.setLineNumber(currentLineNumber);
} else {
currentLineNumber = ref.getLineNumber();
}
} else if (current instanceof LineInfo) {
LineInfo lineInfo = (LineInfo) current;
currentLineNumber = lineInfo.line;
}
}
}
}
static class LabelInfo {
public Reference location;
public Label label;
public LabelInfo(Reference location, Label label) {
this.location = location;
this.label = label;
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append('(').append(this.label).append(',').append(this.location).append(')');
return String.valueOf(buffer);
}
}
static class LineInfo implements Comparable<Object> {
int line;
Label label;
LineInfo(int line, Label label) {
this.line = line;
this.label = label;
}
@Override
public int compareTo(Object o) {
return this.line - ((LineInfo) o).line;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof LineInfo) {
LineInfo lineInfo2 = (LineInfo) obj;
return this.line == lineInfo2.line && this.label.equals(lineInfo2.label);
}
return super.equals(obj);
}
@Override
public int hashCode() {
return this.line + (this.label != null ? this.label.hashCode() : 0);
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append('(').append(this.line).append(',').append(this.label).append(')');
return String.valueOf(buffer);
}
}
static class LocalLineNumberMarker {
int lineNumber;
int varIndex;
public LocalLineNumberMarker(int line, int varIndex) {
this.lineNumber = line;
this.varIndex = varIndex;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof LocalLineNumberMarker) {
LocalLineNumberMarker marker = (LocalLineNumberMarker) obj;
return this.lineNumber == marker.lineNumber && this.varIndex == marker.varIndex;
}
return false;
}
@Override
public int hashCode() {
return this.varIndex;
}
}
/**
* The list we collect references in. Entries in the list are of the type
* {@link org.eclipse.pde.api.tools.internal.provisional.builder.IReference}
*/
Set<Reference> collector = null;
/**
* The full internal name of the class we are extracting references from
*/
private String classname = null;
/**
* Current type being visited.
*/
IApiType fType;
/**
* Stack of members being visited. When a member is entered its element
* descriptor is pushed onto the stack. When a member is exited, the stack
* is popped.
*/
Stack<IApiMember> fMemberStack = new Stack<>();
/**
* Stack of super types *names* (String) being visited. When a type is
* entered, its super type is pushed onto the stack. When a type is exited,
* the stack is popped.
*/
Stack<String> fSuperStack = new Stack<>();
/**
* Mapping of anonymous type names to their reference
*/
HashMap<String, List<Reference>> fAnonymousTypes = new HashMap<>();
/**
* Whether to extract references to elements within the classfile being
* scanned.
*/
private boolean fIncludeLocalRefs = false;
/**
* Bit mask of {@link ReferenceModifiers} to extract.
*/
private int fReferenceKinds = 0;
/**
* Track synthetic field / accessor
*
* @since 1.1
*/
FieldTracker fieldtracker = null;
/**
* The version for the class being visited
*
* @since 1.0.600
*/
private int fVersion;
/**
* Bit mask that determines if we need to visit members
*/
private static final int VISIT_MEMBERS_MASK = IReference.MASK_REF_ALL ^ (IReference.REF_EXTENDS | IReference.REF_IMPLEMENTS);
/**
* If members should be visited for type visits
*/
private boolean fIsVisitMembers = false;
/**
* Current field being visited, or <code>null</code> (when not within a
* field).
*/
private ClassFileSignatureVisitor signaturevisitor = new ClassFileSignatureVisitor();
static int TYPE = 0, FIELD = 1, METHOD = 2;
/**
* {@link FieldVisitor} used to track and collect references to annotation
* types
*
* @since 1.0.600
*/
private ClassFileFieldVisitor fieldvisitor = new ClassFileFieldVisitor();
/**
* Constructor
*
* @param type the type to extract references from
* @param collector the listing of references to annotate from this pass
* @param referenceKinds kinds of references to extract as defined by
* {@link ReferenceModifiers}
*/
public ReferenceExtractor(IApiType type, Set<Reference> collector, int referenceKinds) {
super(Opcodes.ASM5, new ClassNode());
fType = type;
this.collector = collector;
fReferenceKinds = referenceKinds;
fIsVisitMembers = (VISIT_MEMBERS_MASK & fReferenceKinds) > 0;
fieldtracker = new FieldTracker(this);
}
/**
* Constructor
*
* @param type
* @param collector
* @param referenceKinds
* @param tracker
*/
protected ReferenceExtractor(IApiType type, Set<Reference> collector, int referenceKinds, FieldTracker tracker) {
super(Opcodes.ASM5, new ClassNode());
fType = type;
this.collector = collector;
fReferenceKinds = referenceKinds;
fIsVisitMembers = (VISIT_MEMBERS_MASK & fReferenceKinds) > 0;
fieldtracker = tracker;
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("Reference extractor for: "); //$NON-NLS-1$
buffer.append(fType.getName());
buffer.append("\n"); //$NON-NLS-1$
buffer.append("Reference kinds: "); //$NON-NLS-1$
buffer.append(Reference.getReferenceText(fReferenceKinds));
buffer.append("\n"); //$NON-NLS-1$
buffer.append("Is visiting members: "); //$NON-NLS-1$
buffer.append(fIsVisitMembers);
return buffer.toString();
}
/**
* Returns whether to consider a reference to the specified type. Configured
* by setting to include references within the same class file.
*
* @param owner
* @return true if considered, false otherwise
*/
protected boolean consider(String owner) {
if (this.fIncludeLocalRefs) {
return true;
}
return !(this.classname.equals(owner) || this.classname.startsWith(owner) || "<clinit>".equals(owner) || "this".equals(owner)); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* Returns whether the specified reference should be considered when
* extracting references. Configured by setting on whether to include
* references within the same class file.
*
* @param ref reference
* @return whether to include the reference
*/
protected boolean consider(Reference ref) {
int kind = ref.getReferenceKind();
if ((kind & fReferenceKinds) == 0) {
return false;
}
if (this.fIncludeLocalRefs) {
return true;
}
// don't consider references to anonymous types or elements in them
String referencedTypeName = ref.getReferencedTypeName();
if (kind == IReference.REF_VIRTUALMETHOD || kind == IReference.REF_OVERRIDE || kind == IReference.REF_GETFIELD || kind == IReference.REF_PUTFIELD) {
return true;
}
if (referencedTypeName.startsWith(fType.getName())) {
// don't include references within this type or a member type
if (referencedTypeName.length() > fType.getName().length()) {
return referencedTypeName.charAt(fType.getName().length()) != '$';
}
return false;
}
return true;
}
/**
* Returns the full internal name (if available) from the given simple name.
* The returned name has been modified to be '.' separated
*
* @param name
* @return
*/
protected String processName(String name) {
String newname = name;
Type type = Type.getObjectType(name);
if (type != null && type.getSort() == Type.OBJECT) {
newname = type.getInternalName();
}
return newname.replaceAll("/", "."); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* Adds a reference to the given type from the current member. Discards the
* reference if the type corresponds to the class file being scanned or if
* the type is a primitive type.
*
* @param type referenced type
* @param linenumber line number where referenced
* @param kind kind of reference
* @return reference added, or <code>null</code> if none
*/
protected Reference addTypeReference(Type type, int kind) {
Type rtype = this.resolveType(type.getDescriptor());
if (rtype != null) {
return addReference(Reference.typeReference(getMember(), rtype.getClassName(), kind));
}
return null;
}
/**
* Adds a reference to the given field from the current member. Discards the
* reference if the field is defined in the class file being scanned.
*
* @param declaringType type declaring the field being referenced
* @param name of the field being referenced
* @param linenumber line number where referenced
* @param kind kind of reference
* @return reference added, or <code>null</code> if none
*/
protected Reference addFieldReference(Type declaringType, String name, int kind) {
Type rtype = this.resolveType(declaringType.getDescriptor());
if (rtype != null) {
return addReference(Reference.fieldReference(getMember(), rtype.getClassName(), name, kind));
}
return null;
}
/**
* Adds a reference to the given method from the current member. Discards
* the reference if the method is defined in the class file being scanned.
*
* @param declaringType type declaring the method (but could be a virtual
* lookup)
* @param name of the method being referenced
* @param signature signature of the method
* @param linenumber line number where referenced
* @param kind kind of reference
* @param flags the flags for the reference
* @return reference added, or <code>null</code> if none
*/
protected Reference addMethodReference(Type declaringType, String name, String signature, int kind, int flags) {
Type rtype = this.resolveType(declaringType.getDescriptor());
if (rtype != null) {
return this.addReference(Reference.methodReference(getMember(), rtype.getClassName(), name, signature, kind, flags));
}
return null;
}
/**
* Adds a reference to the given target member from the given line number in
* the class file being scanned. If the target member is contained in the
* class file being scanned it is discarded based on the setting to include
* local references.
*
* @param target reference
* @param reference added, or <code>null</code> if none
*/
protected Reference addReference(Reference target) {
if (this.consider(target)) {
this.collector.add(target);
return target;
}
return null;
}
/**
* Processes the member signature from the specified type with the given
* signature and kind. A member can be either a type, method, field or local
* variable
*
* @param name the name of the member to process
* @param signature the signature of the member to process
* @param kind the kind
* @param type the type of member wanting to use the visitor
*
* @return the collection of references created for this signature
*/
protected List<Reference> processSignature(String name, String signature, int kind, int type) {
SignatureReader reader = new SignatureReader(signature);
this.signaturevisitor.kind = kind;
this.signaturevisitor.name = this.processName(name);
this.signaturevisitor.signature = signature;
this.signaturevisitor.originalkind = kind;
this.signaturevisitor.argumentcount = 0;
this.signaturevisitor.type = type;
if (kind == IReference.REF_PARAMETERIZED_TYPEDECL || kind == IReference.REF_PARAMETERIZED_METHODDECL) {
reader.accept(this.signaturevisitor);
} else {
reader.acceptType(this.signaturevisitor);
}
List<Reference> result = new ArrayList<>();
result.addAll(this.signaturevisitor.references);
this.collector.addAll(this.signaturevisitor.references);
this.signaturevisitor.reset();
return result;
}
/**
* Resolves the type from the string description. This method takes only
* type descriptions as a parameter, all else will throw an exception from
* the ASM framework If the description is an array, the underlying type of
* the array is returned.
*
* @param desc
* @return the {@link Type} of the description or <code>null</code>
*/
protected Type resolveType(String desc) {
Type type = Type.getType(desc);
if (type.getSort() == Type.OBJECT) {
return type;
}
if (type.getSort() == Type.ARRAY) {
type = type.getElementType();
if (type.getSort() == Type.OBJECT) {
return type;
}
}
return null;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.fVersion = version;
this.classname = this.processName(name);
if (ApiPlugin.DEBUG_REFERENCE_EXTRACTOR) {
System.out.println("Starting visit of type: [" + this.fType.getName() + "]"); //$NON-NLS-1$ //$NON-NLS-2$
}
this.enterMember(this.fType);
// if there is a signature we get more information from it, so we don't
// need to do both
if (signature != null) {
this.processSignature(name, signature, IReference.REF_PARAMETERIZED_TYPEDECL, TYPE);
} else {
if ((access & Opcodes.ACC_INTERFACE) != 0) {
// the type is an interface and we need to treat the interfaces
// set as extends, not implements
Type supertype = null;
for (String interfaceName : interfaces) {
supertype = Type.getObjectType(interfaceName);
this.addTypeReference(supertype, IReference.REF_EXTENDS);
this.fSuperStack.add(supertype.getClassName());
}
} else {
Type supertype = null;
if (superName != null) {
supertype = Type.getObjectType(superName);
this.addTypeReference(supertype, IReference.REF_EXTENDS);
this.fSuperStack.add(supertype.getClassName());
}
for (String interfaceName : interfaces) {
supertype = Type.getObjectType(interfaceName);
this.addTypeReference(supertype, IReference.REF_IMPLEMENTS);
}
}
}
}
@Override
public void visitEnd() {
this.exitMember();
if (!this.fSuperStack.isEmpty()) {
String typeName = this.fSuperStack.pop();
if (ApiPlugin.DEBUG_REFERENCE_EXTRACTOR) {
System.out.println("ending visit of type: [" + typeName + "]"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
if (!this.fType.isMemberType()) {
fieldtracker.resolveSyntheticFields();
}
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
if (fIsVisitMembers) {
IApiType owner = (IApiType) this.getMember();
IApiField field = owner.getField(name);
if (field == null) {
ApiPlugin.log(new Status(IStatus.WARNING, ApiPlugin.PLUGIN_ID, NLS.bind(BuilderMessages.ReferenceExtractor_failed_to_lookup_field, new String[] {
name, Signatures.getQualifiedTypeSignature(owner) })));
// if we can't find the method there is no point trying to
// process it
return null;
}
this.enterMember(field);
if ((access & Opcodes.ACC_SYNTHETIC) == 0) {
if (signature != null) {
this.processSignature(name, signature, IReference.REF_PARAMETERIZED_FIELDDECL, FIELD);
} else {
this.addTypeReference(Type.getType(desc), IReference.REF_FIELDDECL);
}
} else {
fieldtracker.addField(addTypeReference(Type.getType(desc), IReference.REF_FIELDDECL));
}
return fieldvisitor;
}
return null;
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
try {
String pname = processName(name);
if (fType.getName().equals(pname) || !pname.startsWith(fType.getName())) {
return;
}
IApiComponent comp = fType.getApiComponent();
if (comp == null) {
return;
}
AbstractApiTypeRoot root = (AbstractApiTypeRoot) comp.findTypeRoot(pname);
if (root != null) {
IApiType type = root.getStructure();
if (type == null) {
// do nothing for a bad classfile
return;
}
Set<Reference> refs = processInnerClass(type, fReferenceKinds);
if (type.isAnonymous() || type.isLocal()) {
// visit the class files for the dependent anonymous and
// local inner types
// set a line number for all references with no line numbers
List<Reference> allRefs = new ArrayList<>();
for (Reference reference : refs) {
if (reference.getLineNumber() < 0) {
allRefs.add(reference);
}
}
fAnonymousTypes.put(pname, allRefs);
}
if (refs != null && !refs.isEmpty()) {
this.collector.addAll(refs);
}
}
} catch (CoreException ce) {
}
}
/**
* Processes the dependent inner class
*
* @param type
* @param refkinds
* @return
* @throws CoreException
*/
private Set<Reference> processInnerClass(IApiType type, int refkinds) throws CoreException {
HashSet<Reference> refs = new HashSet<>();
ReferenceExtractor extractor = new ReferenceExtractor(type, refs, refkinds, this.fieldtracker);
ClassReader reader = new ClassReader(((AbstractApiTypeRoot) type.getTypeRoot()).getContents());
reader.accept(extractor, ClassReader.SKIP_FRAMES);
return refs;
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
try {
addTypeReference(Type.getType(desc), IReference.REF_ANNOTATION_USE);
} catch (ArrayIndexOutOfBoundsException e) {
// when file has compile errors this gets thrown, but we can ignore
// it
}
return null;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (fIsVisitMembers) {
IApiMember member = this.getMember();
IApiType owner = null;
if (member instanceof IApiType) {
owner = (IApiType) member;
} else {
try {
owner = member.getEnclosingType();
} catch (CoreException e) {
// should not happen for field or method
ApiPlugin.log(e.getStatus());
}
}
if (owner == null) {
return null;
}
IApiMethod method = owner.getMethod(name, desc);
if (method == null) {
ApiPlugin.log(new Status(IStatus.WARNING, ApiPlugin.PLUGIN_ID, NLS.bind(BuilderMessages.ReferenceExtractor_failed_to_lookup_method, new String[] {
name, desc, Signatures.getQualifiedTypeSignature(owner) })));
// if we can't find the method there is no point trying to
// process it
return null;
}
this.enterMember(method);
// record potential method override reference
if ((access & (Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC)) > 0) {
try {
IApiType def = null;
if (fVersion >= Opcodes.V1_8) {
// See if we are overriding a default interface method
def = getDefaultDefined(owner, name, desc, true);
}
if (def != null) {
addReference(Reference.methodReference(method, def.getName(), method.getName(), method.getSignature(), IReference.REF_OVERRIDE, IReference.F_DEFAULT_METHOD));
} else if (!this.fSuperStack.isEmpty()) {
String superTypeName = this.fSuperStack.peek();
addReference(Reference.methodReference(method, superTypeName, method.getName(), method.getSignature(), IReference.REF_OVERRIDE));
}
} catch (CoreException e) {
// Do nothing, skip this reference
}
}
int argumentcount = 0;
if ((access & Opcodes.ACC_SYNTHETIC) == 0) {
if (signature != null) {
this.processSignature(name, signature, IReference.REF_PARAMETERIZED_METHODDECL, METHOD);
argumentcount = this.signaturevisitor.argumentcount;
} else {
Type[] arguments = Type.getArgumentTypes(desc);
for (Type type : arguments) {
this.addTypeReference(type, IReference.REF_PARAMETER);
argumentcount += type.getSize();
}
this.addTypeReference(Type.getReturnType(desc), IReference.REF_RETURNTYPE);
if (exceptions != null) {
for (String exception : exceptions) {
this.addTypeReference(Type.getObjectType(exception), IReference.REF_THROWS);
}
}
}
}
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if (mv != null && ((access & (Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT)) == 0)) {
return new ClassFileMethodVisitor(mv, name, argumentcount);
}
}
return null;
}
/**
* Called when a member is entered. Pushes the member onto the member stack.
*
* @param member current member
*/
protected void enterMember(IApiMember member) {
this.fMemberStack.push(member);
}
/**
* Called when a member is exited. Pops the top member off the stack.
*/
protected void exitMember() {
this.fMemberStack.pop();
}
/**
* Returns the member currently being visited.
*
* @return current member
*/
protected IApiMember getMember() {
return this.fMemberStack.peek();
}
/**
* Find out if the method declaration is a default method and return the
* type defining it. Uses the JLS specified order of lookup between
* superclasses and superinterfaces.
*
* @param type the type used as a starting point for the search, will not be
* searched if <code>isOverride</code> is <code>true</code>
* @param name name of the method
* @param signature signature of the method
* @param isOverride is <code>true</code> the provided IApiType will not be
* searched for a declaration
* @return the IApiType containing the default method definition or
* <code>null</code>
* @throws CoreException
*/
static IApiType getDefaultDefined(IApiType type, String name, String signature, boolean isOverride) throws CoreException {
if (type != null && type.getName().startsWith("java.")) { //$NON-NLS-1$
return null;
}
if (type != null) {
if (!isOverride) {
IApiMethod method = type.getMethod(name, signature);
if (method != null) {
if (method.isDefaultMethod()) {
return type;
}
}
}
IApiType superclass = getDefaultDefined(type.getSuperclass(), name, signature, false);
if (superclass != null) {
return superclass;
}
IApiType ints[] = type.getSuperInterfaces();
for (IApiType j : ints) {
IApiType superint = getDefaultDefined(j, name, signature, false);
if (superint != null) {
return superint;
}
}
}
return null;
}
}