| 1 | /******************************************************************************* |
| 2 | * Copyright (c) 2007, 2009 IBM Corporation and others. |
| 3 | * All rights reserved. This program and the accompanying materials |
| 4 | * are made available under the terms of the Eclipse Public License v1.0 |
| 5 | * which accompanies this distribution, and is available at |
| 6 | * http://www.eclipse.org/legal/epl-v10.html |
| 7 | * |
| 8 | * Contributors: |
| 9 | * IBM Corporation - initial API and implementation |
| 10 | *******************************************************************************/ |
| 11 | |
| 12 | package org.eclipse.pde.api.tools.internal.model; |
| 13 | |
| 14 | import java.io.ByteArrayInputStream; |
| 15 | import java.io.DataInputStream; |
| 16 | import java.io.IOException; |
| 17 | import java.io.PrintWriter; |
| 18 | import java.io.StringWriter; |
| 19 | import java.util.HashMap; |
| 20 | import java.util.Map; |
| 21 | |
| 22 | import org.eclipse.core.runtime.CoreException; |
| 23 | import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; |
| 24 | import org.eclipse.pde.api.tools.internal.model.StubArchiveApiTypeContainer.ArchiveApiTypeRoot; |
| 25 | import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; |
| 26 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent; |
| 27 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiType; |
| 28 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeRoot; |
| 29 | import org.objectweb.asm.AnnotationVisitor; |
| 30 | import org.objectweb.asm.ClassAdapter; |
| 31 | import org.objectweb.asm.ClassReader; |
| 32 | import org.objectweb.asm.ClassVisitor; |
| 33 | import org.objectweb.asm.FieldVisitor; |
| 34 | import org.objectweb.asm.Label; |
| 35 | import org.objectweb.asm.MethodAdapter; |
| 36 | import org.objectweb.asm.MethodVisitor; |
| 37 | import org.objectweb.asm.Opcodes; |
| 38 | import org.objectweb.asm.tree.ClassNode; |
| 39 | import org.objectweb.asm.util.TraceAnnotationVisitor; |
| 40 | |
| 41 | /** |
| 42 | * Class adapter used to create an API type structure |
| 43 | */ |
| 44 | public class TypeStructureBuilder extends ClassAdapter { |
| 45 | ApiType fType; |
| 46 | IApiComponent fComponent; |
| 47 | IApiTypeRoot fFile; |
| 48 | |
| 49 | /** |
| 50 | * Builds a type structure for a class file. Note that if an API |
| 51 | * component is not specified, then some operations on the resulting |
| 52 | * {@link IApiType} will not be available (navigating super types, |
| 53 | * member types, etc). |
| 54 | * |
| 55 | * @param cv class file visitor |
| 56 | * @param component originating API component or <code>null</code> if unknown |
| 57 | */ |
| 58 | TypeStructureBuilder(ClassVisitor cv, IApiComponent component, IApiTypeRoot file) { |
| 59 | super(cv); |
| 60 | fComponent = component; |
| 61 | fFile = file; |
| 62 | } |
| 63 | |
| 64 | /** |
| 65 | * @see org.objectweb.asm.ClassAdapter#visit(int, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[]) |
| 66 | */ |
| 67 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { |
| 68 | StringBuffer simpleSig = new StringBuffer(); |
| 69 | simpleSig.append('L'); |
| 70 | simpleSig.append(name); |
| 71 | simpleSig.append(';'); |
| 72 | String enclosingName = null; |
| 73 | int index = name.lastIndexOf('$'); |
| 74 | if (index > -1) { |
| 75 | enclosingName = name.substring(0, index).replace('/', '.'); |
| 76 | } |
| 77 | int laccess = access; |
| 78 | // TODO: inner types should be have enclosing type as parent instead of component |
| 79 | if ((laccess & Opcodes.ACC_DEPRECATED) != 0) { |
| 80 | laccess &= ~Opcodes.ACC_DEPRECATED; |
| 81 | laccess |= ClassFileConstants.AccDeprecated; |
| 82 | } |
| 83 | fType = new ApiType(fComponent, name.replace('/', '.'), simpleSig.toString(), signature, laccess, enclosingName, fFile); |
| 84 | if (superName != null) { |
| 85 | fType.setSuperclassName(superName.replace('/', '.')); |
| 86 | } |
| 87 | if (interfaces != null && interfaces.length > 0) { |
| 88 | String[] names = new String[interfaces.length]; |
| 89 | for (int i = 0; i < names.length; i++) { |
| 90 | names[i] = interfaces[i].replace('/', '.'); |
| 91 | } |
| 92 | fType.setSuperInterfaceNames(names); |
| 93 | } |
| 94 | super.visit(version, laccess, name, signature, superName, interfaces); |
| 95 | } |
| 96 | /** |
| 97 | * @see org.objectweb.asm.ClassAdapter#visitInnerClass(java.lang.String, java.lang.String, java.lang.String, int) |
| 98 | */ |
| 99 | public void visitInnerClass(String name, String outerName, String innerName, int access) { |
| 100 | super.visitInnerClass(name, outerName, innerName, access); |
| 101 | String currentName = name.replace('/', '.'); |
| 102 | if (currentName.equals(fType.getName())) { |
| 103 | if (innerName == null) { |
| 104 | fType.setAnonymous(); |
| 105 | } |
| 106 | else if(outerName == null) { |
| 107 | fType.setLocal(); |
| 108 | fType.setSimpleName(innerName); |
| 109 | } |
| 110 | } |
| 111 | if (outerName != null && innerName != null) { |
| 112 | // technically speaking innerName != null is not necessary, but this is a workaround for some |
| 113 | // bogus synthetic types created by another compiler |
| 114 | String currentOuterName = outerName.replace('/', '.'); |
| 115 | if (currentOuterName.equals(fType.getName())) { |
| 116 | // this is a real type member defined in the descriptor (not just a reference to a type member) |
| 117 | fType.addMemberType(currentName, access); |
| 118 | } else if (currentName.equals(fType.getName())) { |
| 119 | fType.setModifiers(access); |
| 120 | fType.setSimpleName(innerName); |
| 121 | fType.setMemberType(); |
| 122 | } |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | /* (non-Javadoc) |
| 127 | * @see org.objectweb.asm.ClassAdapter#visitOuterClass(java.lang.String, java.lang.String, java.lang.String) |
| 128 | */ |
| 129 | public void visitOuterClass(String owner, String name, String desc) { |
| 130 | fType.setEnclosingMethodInfo(name, desc); |
| 131 | } |
| 132 | |
| 133 | /* (non-Javadoc) |
| 134 | * @see org.objectweb.asm.ClassAdapter#visitField(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object) |
| 135 | */ |
| 136 | public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { |
| 137 | int laccess = access; |
| 138 | if ((access & Opcodes.ACC_DEPRECATED) != 0) { |
| 139 | laccess &= ~Opcodes.ACC_DEPRECATED; |
| 140 | laccess |= ClassFileConstants.AccDeprecated; |
| 141 | } |
| 142 | fType.addField(name, desc, signature, laccess, value); |
| 143 | return null; |
| 144 | } |
| 145 | |
| 146 | /* (non-Javadoc) |
| 147 | * @see org.objectweb.asm.ClassAdapter#visitMethod(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[]) |
| 148 | */ |
| 149 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { |
| 150 | String[] names = null; |
| 151 | int laccess = access; |
| 152 | if ((laccess & Opcodes.ACC_DEPRECATED) != 0) { |
| 153 | laccess &= ~Opcodes.ACC_DEPRECATED; |
| 154 | laccess |= ClassFileConstants.AccDeprecated; |
| 155 | } |
| 156 | if (exceptions != null && exceptions.length > 0) { |
| 157 | names = new String[exceptions.length]; |
| 158 | for (int i = 0; i < names.length; i++) { |
| 159 | names[i] = exceptions[i].replace('/', '.'); |
| 160 | } |
| 161 | } |
| 162 | final ApiMethod method = fType.addMethod(name, desc, signature, laccess, names); |
| 163 | return new MethodAdapter(super.visitMethod(laccess, name, desc, signature, exceptions)) { |
| 164 | public AnnotationVisitor visitAnnotationDefault() { |
| 165 | return new TraceAnnotationVisitor() { |
| 166 | public void visitEnd() { |
| 167 | super.visitEnd(); |
| 168 | StringWriter stringWriter = new StringWriter(); |
| 169 | PrintWriter writer = new PrintWriter(stringWriter); |
| 170 | print(writer); |
| 171 | writer.flush(); |
| 172 | writer.close(); |
| 173 | String def = String.valueOf(stringWriter.getBuffer()); |
| 174 | method.setDefaultValue(def); |
| 175 | } |
| 176 | }; |
| 177 | } |
| 178 | }; |
| 179 | } |
| 180 | |
| 181 | /** |
| 182 | * Builds a type structure with the given .class file bytes in the specified |
| 183 | * API component. |
| 184 | * |
| 185 | * @param bytes class file bytes |
| 186 | * @param component originating API component |
| 187 | * @param file associated class file |
| 188 | * @return |
| 189 | */ |
| 190 | public static IApiType buildTypeStructure(byte[] bytes, IApiComponent component, IApiTypeRoot file) { |
| 191 | TypeStructureBuilder visitor = new TypeStructureBuilder(new ClassNode(), component, file); |
| 192 | try { |
| 193 | ClassReader classReader = new ClassReader(bytes); |
| 194 | classReader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); |
| 195 | } catch (ArrayIndexOutOfBoundsException e) { |
| 196 | ApiPlugin.log(e); |
| 197 | } |
| 198 | return visitor.fType; |
| 199 | } |
| 200 | /** |
| 201 | * Builds a type structure with the given .class file bytes in the specified |
| 202 | * API component. |
| 203 | * |
| 204 | * @param bytes class file bytes |
| 205 | * @param component originating API component |
| 206 | * @param file associated class file |
| 207 | * @return |
| 208 | */ |
| 209 | public static void setEnclosingMethod(IApiType enclosingType, ApiType currentAnonymousLocalType) { |
| 210 | IApiTypeRoot typeRoot = enclosingType.getTypeRoot(); |
| 211 | if (typeRoot instanceof AbstractApiTypeRoot) { |
| 212 | AbstractApiTypeRoot abstractApiTypeRoot = (AbstractApiTypeRoot) typeRoot; |
| 213 | EnclosingMethodSetter visitor = new EnclosingMethodSetter(new ClassNode(), currentAnonymousLocalType.getName()); |
| 214 | try { |
| 215 | ClassReader classReader = new ClassReader(abstractApiTypeRoot.getContents()); |
| 216 | classReader.accept(visitor, ClassReader.SKIP_FRAMES); |
| 217 | } catch (ArrayIndexOutOfBoundsException e) { |
| 218 | ApiPlugin.log(e); |
| 219 | } catch(CoreException e) { |
| 220 | // bytes could not be retrieved for abstractApiTypeRoot |
| 221 | ApiPlugin.log(e); |
| 222 | } |
| 223 | if (visitor.found) { |
| 224 | currentAnonymousLocalType.setEnclosingMethodInfo(visitor.name, visitor.signature); |
| 225 | } |
| 226 | } |
| 227 | } |
| 228 | static class EnclosingMethodSetter extends ClassAdapter { |
| 229 | String name; |
| 230 | String signature; |
| 231 | boolean found = false; |
| 232 | String typeName; |
| 233 | |
| 234 | public EnclosingMethodSetter(ClassVisitor cv, String typeName) { |
| 235 | super(cv); |
| 236 | this.typeName = typeName.replace('.', '/'); |
| 237 | } |
| 238 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { |
| 239 | if ("<clinit>".equals(name)) { //$NON-NLS-1$ |
| 240 | return null; |
| 241 | } |
| 242 | if (!this.found) { |
| 243 | if ((access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) == 0) { |
| 244 | this.name = name; |
| 245 | this.signature = desc; |
| 246 | if (signature != null) { |
| 247 | this.signature = signature; |
| 248 | } |
| 249 | MethodVisitor mv; |
| 250 | if ("<init>".equals(name)) { //$NON-NLS-1$ |
| 251 | mv = new TypeNameFinderInConstructor(cv.visitMethod(access, name, desc, signature, exceptions), this); |
| 252 | } else { |
| 253 | mv = new TypeNameFinder(cv.visitMethod(access, name, desc, signature, exceptions), this); |
| 254 | } |
| 255 | return mv; |
| 256 | } |
| 257 | } |
| 258 | return null; |
| 259 | } |
| 260 | } |
| 261 | static class TypeNameFinder extends MethodAdapter { |
| 262 | protected EnclosingMethodSetter setter; |
| 263 | |
| 264 | public TypeNameFinder(MethodVisitor mv, EnclosingMethodSetter enclosingMethodSetter) { |
| 265 | super(mv); |
| 266 | this.setter = enclosingMethodSetter; |
| 267 | } |
| 268 | public void visitTypeInsn(int opcode, String type) { |
| 269 | if (setter.typeName.equals(type)) { |
| 270 | setter.found = true; |
| 271 | } |
| 272 | } |
| 273 | } |
| 274 | static class TypeNameFinderInConstructor extends TypeNameFinder { |
| 275 | int lineNumberStart; |
| 276 | int matchingLineNumber; |
| 277 | int currentLineNumber = -1; |
| 278 | |
| 279 | public TypeNameFinderInConstructor(MethodVisitor mv, EnclosingMethodSetter enclosingMethodSetter) { |
| 280 | super(mv, enclosingMethodSetter); |
| 281 | } |
| 282 | /* (non-Javadoc) |
| 283 | * @see org.objectweb.asm.MethodAdapter#visitFieldInsn(int, java.lang.String, java.lang.String, java.lang.String) |
| 284 | */ |
| 285 | public void visitFieldInsn(int opcode, String owner, String name, |
| 286 | String desc) { |
| 287 | super.visitFieldInsn(opcode, owner, name, desc); |
| 288 | } |
| 289 | public void visitTypeInsn(int opcode, String type) { |
| 290 | if (!setter.found && setter.typeName.equals(type)) { |
| 291 | this.matchingLineNumber = this.currentLineNumber; |
| 292 | setter.found = true; |
| 293 | } |
| 294 | } |
| 295 | public void visitLineNumber(int line, Label start) { |
| 296 | if (this.currentLineNumber == -1) { |
| 297 | this.lineNumberStart = line; |
| 298 | } |
| 299 | this.currentLineNumber = line; |
| 300 | } |
| 301 | public void visitEnd() { |
| 302 | if (setter.found) { |
| 303 | // check that the line number is between the constructor bounds |
| 304 | if (this.matchingLineNumber < this.lineNumberStart || this.matchingLineNumber > this.currentLineNumber) { |
| 305 | setter.found = false; |
| 306 | } |
| 307 | } |
| 308 | } |
| 309 | } |
| 310 | /* (non-Javadoc) |
| 311 | * @see java.lang.Object#toString() |
| 312 | */ |
| 313 | public String toString() { |
| 314 | StringBuffer buffer = new StringBuffer(); |
| 315 | buffer.append("Type structure builder for: ").append(fType.getName()); //$NON-NLS-1$ |
| 316 | buffer.append("\nBacked by file: ").append(fFile.getName()); //$NON-NLS-1$ |
| 317 | return buffer.toString(); |
| 318 | } |
| 319 | |
| 320 | public static IApiType buildStubTypeStructure(byte[] contents, |
| 321 | IApiComponent apiComponent, ArchiveApiTypeRoot archiveApiTypeRoot) { |
| 322 | // decode the byte[] |
| 323 | DataInputStream inputStream = new DataInputStream(new ByteArrayInputStream(contents)); |
| 324 | ApiType type = null; |
| 325 | try { |
| 326 | Map pool = new HashMap(); |
| 327 | short currentVersion = inputStream.readShort(); // read file version (for now there is only one version) |
| 328 | short poolSize = inputStream.readShort(); |
| 329 | for (int i = 0; i < poolSize; i++) { |
| 330 | String readUtf = inputStream.readUTF(); |
| 331 | int index = inputStream.readShort(); |
| 332 | pool.put(new Integer(index), readUtf); |
| 333 | } |
| 334 | int access = 0; |
| 335 | // access flag was added in version 2 of the stub format |
| 336 | if (currentVersion == 2) { |
| 337 | access = inputStream.readChar(); |
| 338 | } |
| 339 | int classIndex = inputStream.readShort(); |
| 340 | String name = (String) pool.get(new Integer(classIndex)); |
| 341 | StringBuffer simpleSig = new StringBuffer(); |
| 342 | simpleSig.append('L'); |
| 343 | simpleSig.append(name); |
| 344 | simpleSig.append(';'); |
| 345 | type = new ApiType(apiComponent, name.replace('/', '.'), simpleSig.toString(), null, access, null, archiveApiTypeRoot); |
| 346 | int superclassNameIndex = inputStream.readShort(); |
| 347 | if (superclassNameIndex != -1) { |
| 348 | String superclassName = (String) pool.get(new Integer(superclassNameIndex)); |
| 349 | type.setSuperclassName(superclassName.replace('/', '.')); |
| 350 | } |
| 351 | int interfacesLength = inputStream.readShort(); |
| 352 | if (interfacesLength != 0) { |
| 353 | String[] names = new String[interfacesLength]; |
| 354 | for (int i = 0; i < names.length; i++) { |
| 355 | String interfaceName = (String) pool.get(new Integer(inputStream.readShort())); |
| 356 | names[i] = interfaceName.replace('/', '.'); |
| 357 | } |
| 358 | type.setSuperInterfaceNames(names); |
| 359 | } |
| 360 | int fieldsLength = inputStream.readShort(); |
| 361 | for (int i = 0; i < fieldsLength; i++) { |
| 362 | String fieldName = (String) pool.get(new Integer(inputStream.readShort())); |
| 363 | type.addField(fieldName, null, null, 0, null); |
| 364 | } |
| 365 | int methodsLength = inputStream.readShort(); |
| 366 | for (int i = 0; i < methodsLength; i++) { |
| 367 | String methodSelector = (String) pool.get(new Integer(inputStream.readShort())); |
| 368 | String methodSignature = (String) pool.get(new Integer(inputStream.readShort())); |
| 369 | type.addMethod(methodSelector, methodSignature, null, 0, null); |
| 370 | } |
| 371 | } catch (IOException e) { |
| 372 | ApiPlugin.log(e); |
| 373 | } finally { |
| 374 | try { |
| 375 | inputStream.close(); |
| 376 | } catch (IOException e) { |
| 377 | // ignore |
| 378 | } |
| 379 | } |
| 380 | return type; |
| 381 | } |
| 382 | } |