| /******************************************************************************* |
| * Copyright (c) 2007, 2018 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.model; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.DataInputStream; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; |
| import org.eclipse.jdt.launching.AbstractVMInstall; |
| import org.eclipse.jdt.launching.IVMInstall; |
| import org.eclipse.jdt.launching.JavaRuntime; |
| import org.eclipse.jdt.launching.environments.IExecutionEnvironment; |
| import org.eclipse.jdt.launching.environments.IExecutionEnvironmentsManager; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.pde.api.tools.internal.model.StubArchiveApiTypeContainer.ArchiveApiTypeRoot; |
| import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; |
| import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent; |
| import org.eclipse.pde.api.tools.internal.provisional.model.IApiType; |
| import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeRoot; |
| import org.objectweb.asm.AnnotationVisitor; |
| import org.objectweb.asm.ClassReader; |
| import org.objectweb.asm.ClassVisitor; |
| import org.objectweb.asm.FieldVisitor; |
| import org.objectweb.asm.Label; |
| import org.objectweb.asm.MethodVisitor; |
| import org.objectweb.asm.Opcodes; |
| import org.objectweb.asm.tree.ClassNode; |
| |
| /** |
| * Class adapter used to create an API type structure |
| */ |
| public class TypeStructureBuilder extends ClassVisitor { |
| ApiType fType; |
| IApiComponent fComponent; |
| IApiTypeRoot fFile; |
| |
| /** |
| * Builds a type structure for a class file. Note that if an API component |
| * is not specified, then some operations on the resulting {@link IApiType} |
| * will not be available (navigating super types, member types, etc). This |
| * constructor uses ASM7_EXPERIMENTAL |
| * |
| * @param useExperimental |
| * @param cv class file visitor |
| * @param component originating API component or <code>null</code> if |
| * unknown |
| */ |
| TypeStructureBuilder(boolean useExperimental, ClassVisitor cv, IApiComponent component, IApiTypeRoot file) { |
| super(Opcodes.ASM7_EXPERIMENTAL, cv); |
| fComponent = component; |
| fFile = file; |
| } |
| /** |
| * Builds a type structure for a class file. Note that if an API component |
| * is not specified, then some operations on the resulting {@link IApiType} |
| * will not be available (navigating super types, member types, etc). |
| * |
| * @param cv class file visitor |
| * @param component originating API component or <code>null</code> if |
| * unknown |
| */ |
| TypeStructureBuilder(ClassVisitor cv, IApiComponent component, IApiTypeRoot file) { |
| super(Opcodes.ASM6, cv); |
| fComponent = component; |
| fFile = file; |
| } |
| |
| @Override |
| public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { |
| StringBuilder simpleSig = new StringBuilder(); |
| simpleSig.append('L'); |
| simpleSig.append(name); |
| simpleSig.append(';'); |
| String enclosingName = null; |
| int index = name.lastIndexOf('$'); |
| if (index > -1) { |
| enclosingName = name.substring(0, index).replace('/', '.'); |
| } |
| int laccess = access; |
| // TODO: inner types should be have enclosing type as parent instead of |
| // component |
| if ((laccess & Opcodes.ACC_DEPRECATED) != 0) { |
| laccess &= ~Opcodes.ACC_DEPRECATED; |
| laccess |= ClassFileConstants.AccDeprecated; |
| } |
| fType = new ApiType(fComponent, name.replace('/', '.'), simpleSig.toString(), signature, laccess, enclosingName, fFile); |
| if (superName != null) { |
| fType.setSuperclassName(superName.replace('/', '.')); |
| } |
| if (interfaces != null && interfaces.length > 0) { |
| String[] names = new String[interfaces.length]; |
| for (int i = 0; i < names.length; i++) { |
| names[i] = interfaces[i].replace('/', '.'); |
| } |
| fType.setSuperInterfaceNames(names); |
| } |
| super.visit(version, laccess, name, signature, superName, interfaces); |
| } |
| |
| @Override |
| public void visitInnerClass(String name, String outerName, String innerName, int access) { |
| super.visitInnerClass(name, outerName, innerName, access); |
| String currentName = name.replace('/', '.'); |
| if (currentName.equals(fType.getName())) { |
| if (innerName == null) { |
| fType.setAnonymous(); |
| } else if (outerName == null) { |
| fType.setLocal(); |
| fType.setSimpleName(innerName); |
| } |
| } |
| if (outerName != null && innerName != null) { |
| // technically speaking innerName != null is not necessary, but this |
| // is a workaround for some |
| // bogus synthetic types created by another compiler |
| String currentOuterName = outerName.replace('/', '.'); |
| if (currentOuterName.equals(fType.getName())) { |
| // this is a real type member defined in the descriptor (not |
| // just a reference to a type member) |
| fType.addMemberType(currentName, access); |
| } else if (currentName.equals(fType.getName())) { |
| fType.setModifiers(access); |
| fType.setSimpleName(innerName); |
| fType.setMemberType(); |
| } |
| } |
| } |
| |
| @Override |
| public void visitOuterClass(String owner, String name, String desc) { |
| fType.setEnclosingMethodInfo(name, desc); |
| } |
| |
| @Override |
| public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { |
| int laccess = access; |
| if ((access & Opcodes.ACC_DEPRECATED) != 0) { |
| laccess &= ~Opcodes.ACC_DEPRECATED; |
| laccess |= ClassFileConstants.AccDeprecated; |
| } |
| fType.addField(name, desc, signature, laccess, value); |
| return null; |
| } |
| |
| @Override |
| public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { |
| String[] names = null; |
| int laccess = access; |
| if ((laccess & Opcodes.ACC_DEPRECATED) != 0) { |
| laccess &= ~Opcodes.ACC_DEPRECATED; |
| laccess |= ClassFileConstants.AccDeprecated; |
| } |
| if (exceptions != null && exceptions.length > 0) { |
| names = new String[exceptions.length]; |
| for (int i = 0; i < names.length; i++) { |
| names[i] = exceptions[i].replace('/', '.'); |
| } |
| } |
| final ApiMethod method = fType.addMethod(name, desc, signature, laccess, names); |
| return new MethodVisitor(Opcodes.ASM6, super.visitMethod(laccess, name, desc, signature, exceptions)) { |
| @Override |
| public AnnotationVisitor visitAnnotation(String sig, boolean visible) { |
| if (visible && "Ljava/lang/invoke/MethodHandle$PolymorphicSignature;".equals(sig)) { //$NON-NLS-1$ |
| method.isPolymorphic(); |
| } |
| return super.visitAnnotation(sig, visible); |
| } |
| |
| @Override |
| public AnnotationVisitor visitAnnotationDefault() { |
| return new AnnotationDefaultVisitor(method); |
| } |
| }; |
| } |
| |
| private static IApiType logAndReturn(IApiTypeRoot file, Exception e) { |
| if (ApiPlugin.DEBUG_BUILDER) { |
| IStatus status = new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, NLS.bind(Messages.TypeStructureBuilder_badClassFileEncountered, file.getTypeName()), e); |
| ApiPlugin.log(status); |
| } |
| return null; |
| } |
| |
| /** |
| * Visit the default value for an annotation |
| */ |
| static class AnnotationDefaultVisitor extends AnnotationVisitor { |
| ApiMethod method; |
| Object value; |
| StringBuilder buff = new StringBuilder(); |
| boolean trace = false; |
| int traceCount = 0; |
| |
| public AnnotationDefaultVisitor(ApiMethod method) { |
| super(Opcodes.ASM6); |
| this.method = method; |
| } |
| |
| @Override |
| public void visit(String name, Object value) { |
| if (trace) { |
| appendValue(value); |
| traceCount++; |
| return; |
| } |
| this.value = value; |
| } |
| |
| @Override |
| public AnnotationVisitor visitAnnotation(String name, String desc) { |
| trace = true; |
| return this; |
| } |
| |
| @Override |
| public void visitEnum(String name, String desc, String value) { |
| if (trace) { |
| appendValue(value); |
| traceCount++; |
| return; |
| } |
| this.value = value; |
| } |
| |
| @Override |
| public AnnotationVisitor visitArray(String name) { |
| trace = true; |
| return this; |
| } |
| |
| @Override |
| public void visitEnd() { |
| if (trace) { |
| this.value = buff.toString(); |
| traceCount--; |
| trace = traceCount != 0; |
| } else { |
| method.setDefaultValue(this.value == null ? null : this.value.toString()); |
| } |
| } |
| |
| void appendValue(Object val) { |
| if (val != null) { |
| if (buff.length() < 1) { |
| buff.append(val.toString()); |
| } else { |
| buff.append(',').append(val.toString()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Builds a type structure with the given .class file bytes in the specified |
| * API component. |
| * |
| * @param bytes class file bytes |
| * @param component originating API component |
| * @param file associated class file |
| * @return |
| */ |
| public static IApiType buildTypeStructure(byte[] bytes, IApiComponent component, IApiTypeRoot file) { |
| boolean useExperimental = false; |
| try { |
| String[] executionEnvironments = component.getExecutionEnvironments(); |
| if (executionEnvironments.length == 1) { |
| String string = executionEnvironments[0]; |
| IExecutionEnvironmentsManager manager = JavaRuntime.getExecutionEnvironmentsManager(); |
| IExecutionEnvironment env = manager.getEnvironment(string); |
| IVMInstall[] compatibleVMs = env.getCompatibleVMs(); |
| Arrays.sort(compatibleVMs, new Comparator<IVMInstall>() { |
| @Override |
| public int compare(IVMInstall o1, IVMInstall o2) { |
| return o1.getName().compareTo(o2.getName()); |
| } |
| }); |
| for (int i = 0; i < compatibleVMs.length; i++) { |
| if (compatibleVMs[i] instanceof AbstractVMInstall) { |
| AbstractVMInstall ivmInstall = (AbstractVMInstall) compatibleVMs[i]; |
| String javaVersion = ivmInstall.getJavaVersion(); |
| if (i == 0) { |
| useExperimental = javaVersion.equals("11"); //$NON-NLS-1$ |
| } |
| boolean strictlyCompatible = env.isStrictlyCompatible(ivmInstall); |
| // if strictly compatible, then take that value |
| if (strictlyCompatible) { |
| useExperimental = javaVersion.equals("11"); //$NON-NLS-1$ |
| break; |
| } |
| } |
| } |
| } |
| |
| } catch (Exception e1) { |
| // do nothing |
| } |
| // Till ASM7 is incorporated, use ASM7_EXPERIMENTAL to |
| // avoidOperationUnsupportedException if Java 11 is on build path. If |
| // not Java |
| // 11 then use ASM6 |
| TypeStructureBuilder visitor = useExperimental ? new TypeStructureBuilder(useExperimental, new ClassNode(), component, file) : new TypeStructureBuilder(new ClassNode(), component, file); |
| try { |
| ClassReader classReader = new ClassReader(bytes); |
| classReader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); |
| } catch (ArrayIndexOutOfBoundsException e) { |
| logAndReturn(file, e); |
| return null; |
| } catch (IllegalArgumentException iae) { |
| // thrown from ASM 5.0 for bad bytecodes |
| return logAndReturn(file, iae); |
| } |
| return visitor.fType; |
| } |
| |
| /** |
| * Builds a type structure with the given .class file bytes in the specified |
| * API component. |
| * |
| * @param bytes class file bytes |
| * @param component originating API component |
| * @param file associated class file |
| * @return |
| */ |
| public static void setEnclosingMethod(IApiType enclosingType, ApiType currentAnonymousLocalType) { |
| IApiTypeRoot typeRoot = enclosingType.getTypeRoot(); |
| if (typeRoot instanceof AbstractApiTypeRoot) { |
| AbstractApiTypeRoot abstractApiTypeRoot = (AbstractApiTypeRoot) typeRoot; |
| EnclosingMethodSetter visitor = new EnclosingMethodSetter(new ClassNode(), currentAnonymousLocalType.getName()); |
| try { |
| ClassReader classReader = new ClassReader(abstractApiTypeRoot.getContents()); |
| classReader.accept(visitor, ClassReader.SKIP_FRAMES); |
| } catch (ArrayIndexOutOfBoundsException e) { |
| ApiPlugin.log(e); |
| } catch (CoreException e) { |
| // bytes could not be retrieved for abstractApiTypeRoot |
| ApiPlugin.log(e); |
| } |
| if (visitor.found) { |
| currentAnonymousLocalType.setEnclosingMethodInfo(visitor.name, visitor.signature); |
| } |
| } |
| } |
| |
| static class EnclosingMethodSetter extends ClassVisitor { |
| String name; |
| String signature; |
| boolean found = false; |
| String typeName; |
| |
| public EnclosingMethodSetter(ClassVisitor cv, String typeName) { |
| super(Opcodes.ASM6, cv); |
| this.typeName = typeName.replace('.', '/'); |
| } |
| |
| @Override |
| public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { |
| if ("<clinit>".equals(name)) { //$NON-NLS-1$ |
| return null; |
| } |
| if (!this.found) { |
| if ((access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) == 0) { |
| this.name = name; |
| this.signature = desc; |
| if (signature != null) { |
| this.signature = signature; |
| } |
| MethodVisitor mv; |
| if ("<init>".equals(name)) { //$NON-NLS-1$ |
| mv = new TypeNameFinderInConstructor(cv.visitMethod(access, name, desc, signature, exceptions), this); |
| } else { |
| mv = new TypeNameFinder(cv.visitMethod(access, name, desc, signature, exceptions), this); |
| } |
| return mv; |
| } |
| } |
| return null; |
| } |
| } |
| |
| static class TypeNameFinder extends MethodVisitor { |
| protected EnclosingMethodSetter setter; |
| |
| public TypeNameFinder(MethodVisitor mv, EnclosingMethodSetter enclosingMethodSetter) { |
| super(Opcodes.ASM6, mv); |
| this.setter = enclosingMethodSetter; |
| } |
| |
| @Override |
| public void visitTypeInsn(int opcode, String type) { |
| if (setter.typeName.equals(type)) { |
| setter.found = true; |
| } |
| } |
| } |
| |
| static class TypeNameFinderInConstructor extends TypeNameFinder { |
| int lineNumberStart; |
| int matchingLineNumber; |
| int currentLineNumber = -1; |
| |
| public TypeNameFinderInConstructor(MethodVisitor mv, EnclosingMethodSetter enclosingMethodSetter) { |
| super(mv, enclosingMethodSetter); |
| } |
| |
| @Override |
| public void visitFieldInsn(int opcode, String owner, String name, String desc) { |
| super.visitFieldInsn(opcode, owner, name, desc); |
| } |
| |
| @Override |
| public void visitTypeInsn(int opcode, String type) { |
| if (!setter.found && setter.typeName.equals(type)) { |
| this.matchingLineNumber = this.currentLineNumber; |
| setter.found = true; |
| } |
| } |
| |
| @Override |
| public void visitLineNumber(int line, Label start) { |
| if (this.currentLineNumber == -1) { |
| this.lineNumberStart = line; |
| } |
| this.currentLineNumber = line; |
| } |
| |
| @Override |
| public void visitEnd() { |
| if (setter.found) { |
| // check that the line number is between the constructor bounds |
| if (this.matchingLineNumber < this.lineNumberStart || this.matchingLineNumber > this.currentLineNumber) { |
| setter.found = false; |
| } |
| } |
| } |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder buffer = new StringBuilder(); |
| buffer.append("Type structure builder for: ").append(fType.getName()); //$NON-NLS-1$ |
| buffer.append("\nBacked by file: ").append(fFile.getName()); //$NON-NLS-1$ |
| return buffer.toString(); |
| } |
| |
| public static IApiType buildStubTypeStructure(byte[] contents, IApiComponent apiComponent, ArchiveApiTypeRoot archiveApiTypeRoot) { |
| // decode the byte[] |
| DataInputStream inputStream = new DataInputStream(new ByteArrayInputStream(contents)); |
| ApiType type = null; |
| try { |
| Map<Integer, String> pool = new HashMap<>(); |
| short currentVersion = inputStream.readShort(); // read file version |
| // (for now there is |
| // only one version) |
| short poolSize = inputStream.readShort(); |
| for (int i = 0; i < poolSize; i++) { |
| String readUtf = inputStream.readUTF(); |
| int index = inputStream.readShort(); |
| pool.put(Integer.valueOf(index), readUtf); |
| } |
| int access = 0; |
| // access flag was added in version 2 of the stub format |
| if (currentVersion >= 2) { |
| access = inputStream.readChar(); |
| } |
| int classIndex = inputStream.readShort(); |
| String name = pool.get(Integer.valueOf(classIndex)); |
| StringBuilder simpleSig = new StringBuilder(); |
| simpleSig.append('L'); |
| simpleSig.append(name); |
| simpleSig.append(';'); |
| type = new ApiType(apiComponent, name.replace('/', '.'), simpleSig.toString(), null, access, null, archiveApiTypeRoot); |
| int superclassNameIndex = inputStream.readShort(); |
| if (superclassNameIndex != -1) { |
| String superclassName = pool.get(Integer.valueOf(superclassNameIndex)); |
| type.setSuperclassName(superclassName.replace('/', '.')); |
| } |
| int interfacesLength = inputStream.readShort(); |
| if (interfacesLength != 0) { |
| String[] names = new String[interfacesLength]; |
| for (int i = 0; i < names.length; i++) { |
| String interfaceName = pool.get(Integer.valueOf(inputStream.readShort())); |
| names[i] = interfaceName.replace('/', '.'); |
| } |
| type.setSuperInterfaceNames(names); |
| } |
| int fieldsLength = inputStream.readShort(); |
| for (int i = 0; i < fieldsLength; i++) { |
| String fieldName = pool.get(Integer.valueOf(inputStream.readShort())); |
| type.addField(fieldName, null, null, 0, null); |
| } |
| int methodsLength = inputStream.readShort(); |
| for (int i = 0; i < methodsLength; i++) { |
| int isPolymorphic = 0; |
| String methodSelector = pool.get(Integer.valueOf(inputStream.readShort())); |
| String methodSignature = pool.get(Integer.valueOf(inputStream.readShort())); |
| if (currentVersion == 3) { |
| isPolymorphic = inputStream.readByte(); |
| } |
| type.addMethod(methodSelector, methodSignature, null, isPolymorphic == 1 ? ApiMethod.Polymorphic : 0, null); |
| } |
| } catch (IOException e) { |
| ApiPlugin.log(e); |
| } finally { |
| try { |
| inputStream.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| return type; |
| } |
| } |