/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.felix.ipojo.manipulation; import org.objectweb.asm.*; import java.util.*; /** * Checks that a POJO is already manipulated or not. * Moreover it allows to get manipulation data about this class. * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> */ public class ClassChecker extends ClassVisitor implements Opcodes { /** * True if the class is already manipulated. */ private boolean m_isAlreadyManipulated = false; /** * Interfaces implemented by the component. */ private List<String> m_itfs = new ArrayList<String>(); /** * Field map [field name, type] discovered in the component class. */ private Map<String, String> m_fields = new TreeMap<String, String>(); /** * Method List of method descriptor discovered in the component class. */ private List<MethodDescriptor> m_methods = new ArrayList<MethodDescriptor>(); /** * Super class if not java.lang.Object. */ private String m_superClass; /** * Class name. */ private String m_className; /** * List of visited inner class owned by the implementation class. */ private Map<String, List<MethodDescriptor>> m_inners = new LinkedHashMap<String, List<MethodDescriptor>>(); /** * Class Version. * Used to determine the frame format. */ private int m_classVersion; public ClassChecker() { super(Opcodes.ASM5); } /** * Check if the _cm field already exists. * Update the field list. * * @param access : access of the field * @param name : name of the field * @param desc : description of the field * @param signature : signature of the field * @param value : value of the field (for static field only) * @return the field visitor * @see org.objectweb.asm.ClassVisitor#visitField(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object) */ public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { if (name.equals(ClassManipulator.IM_FIELD) && desc.equals("Lorg/apache/felix/ipojo/InstanceManager;")) { m_isAlreadyManipulated = true; } else if (name.startsWith("class$")) { // Does not add class$* field generated by 'x.class' return null; } else if ((access & ACC_STATIC) == ACC_STATIC) { return null; } if (isManipulatedField(name)) { return null; } Type type = Type.getType(desc); if (type.getSort() == Type.ARRAY) { if (type.getInternalName().startsWith("L")) { String internalType = type.getInternalName().substring(1); String nameType = internalType.replace('/', '.'); m_fields.put(name, nameType + "[]"); } else { String nameType = type.getClassName().substring(0, type.getClassName().length() - 2); m_fields.put(name, nameType + "[]"); } } else { m_fields.put(name, type.getClassName()); } return null; } private boolean isManipulatedField(String name) { return ((ClassManipulator.IM_FIELD.equals(name)) || (name.startsWith(ClassManipulator.FIELD_FLAG_PREFIX)) || (name.startsWith(ClassManipulator.METHOD_FLAG_PREFIX))); } /** * Add the inner class to the list of inner class to manipulate. * The method checks that the inner class is really owned by the implementation class. * * @param name inner class qualified name * @param outerName outer class name (may be null for anonymous class) * @param innerName inner class simple (i.e. short) name * @param access inner class visibility * @see org.objectweb.asm.ClassVisitor#visitInnerClass(java.lang.String, java.lang.String, java.lang.String, int) */ public void visitInnerClass(String name, String outerName, String innerName, int access) { if (m_className.equals(outerName) || outerName == null) { // Anonymous classes does not have an outer class. // Do not include inner static class if (!((access & ACC_STATIC) == ACC_STATIC)) { m_inners.put(name, new ArrayList<MethodDescriptor>()); } } } /** * Check if the class was already manipulated. * * @return true if the class is already manipulated. */ public boolean isAlreadyManipulated() { return m_isAlreadyManipulated; } /** * Gets the extracted class version * * @return the class version. */ public int getClassVersion() { return m_classVersion; } /** * Visit the class. * Update the implemented interface list. * * @param version : version of the class * @param access : access of the class * @param name : name of the class * @param signature : signature of the class * @param superName : super class of the class * @param interfaces : implemented interfaces. * @see org.objectweb.asm.ClassVisitor#visit(int, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[]) */ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { m_classVersion = version; if (!superName.equals("java/lang/Object")) { m_superClass = superName.replace('/', '.'); } for (String anInterface : interfaces) { if (!anInterface.equals("org/apache/felix/ipojo/Pojo")) { m_itfs.add(anInterface.replace('/', '.')); } } m_className = name; } /** * Visit a method. * Update the method list (except if it init or clinit. * * @param access - the method's access flags (see Opcodes). This parameter also indicates if the method is synthetic and/or deprecated. * @param name - the method's name. * @param desc - the method's descriptor (see Type). * @param signature - the method's signature. May be null if the method parameters, return type and exceptions do not use generic types. * @param exceptions - the internal names of the method's exception classes (see getInternalName). May be null. * @return nothing. * @see org.objectweb.asm.ClassVisitor#visitMethod(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[]) */ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (!name.equals("<clinit>")) { if (name.equals("<init>")) { if (!isGeneratedConstructor(name, desc)) { final MethodDescriptor md = new MethodDescriptor("$init", desc, (access & ACC_STATIC) == ACC_STATIC); m_methods.add(md); return new MethodInfoCollector(md); } } else { // no constructors. if (!isGeneratedMethod(name, desc)) { final MethodDescriptor md = new MethodDescriptor(name, desc, (access & ACC_STATIC) == ACC_STATIC); m_methods.add(md); return new MethodInfoCollector(md); } } } if (name.equals("<clinit>")) { return new InnerClassAssignedToStaticFieldDetector(); } return null; } public static boolean isGeneratedConstructor(String name, String desc) { return ("<init>".equals(name) && isFirstArgumentInstanceManager(desc)); } public static boolean isFirstArgumentInstanceManager(String desc) { Type[] types = Type.getArgumentTypes(desc); return types != null && (types.length >= 1) && Type.getType("Lorg/apache/felix/ipojo/InstanceManager;").equals(types[0]); } public static boolean isGeneratedMethod(String name, String desc) { return isGetterMethod(name, desc) || isSetterMethod(name, desc) || isSetInstanceManagerMethod(name) || isGetComponentInstanceMethod(name, desc) || isManipulatedMethod(name); } private static boolean isGetterMethod(String name, String desc) { // TYPE __getXXX() Type[] arguments = Type.getArgumentTypes(desc); return (name.startsWith("__get") && (arguments.length == 0) && !Type.VOID_TYPE.equals(Type.getReturnType(desc))); } private static boolean isSetterMethod(String name, String desc) { // void __setXXX(TYPE) Type[] arguments = Type.getArgumentTypes(desc); return (name.startsWith("__set") && (arguments.length == 1) && Type.VOID_TYPE.equals(Type.getReturnType(desc))); } private static boolean isSetInstanceManagerMethod(String name) { return name.startsWith("_setInstanceManager"); } private static boolean isGetComponentInstanceMethod(String name, String desc) { return (name.startsWith("getComponentInstance") && Type.getType("Lorg/apache/felix/ipojo/ComponentInstance;").equals(Type.getReturnType(desc))); } private static boolean isManipulatedMethod(String name) { return (name.startsWith(ClassManipulator.PREFIX)); } /** * Get collected interfaces. * * @return the interfaces implemented by the component class. */ public List<String> getInterfaces() { return m_itfs; } /** * Get collected fields. * * @return the field map [field_name, type]. */ public Map<String, String> getFields() { return m_fields; } /** * Get collected methods. * * @return the method list of [method, signature]. */ public List<MethodDescriptor> getMethods() { return m_methods; } public String getSuperClass() { return m_superClass; } public Collection<String> getInnerClasses() { return m_inners.keySet(); } public Map<String, List<MethodDescriptor>> getInnerClassesAndMethods() { return m_inners; } public String getClassName() { return m_className; } /** * This class collects annotations in a method. * This class creates an {@link AnnotationDescriptor} * if an annotation is found during the visit. * It also collects local variables definition. * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> */ private final class MethodInfoCollector extends MethodVisitor { /** * The method descriptor of the visited method. */ private MethodDescriptor m_method; /** * Creates an annotation collector. * * @param md the method descriptor of the visited method. */ private MethodInfoCollector(MethodDescriptor md) { super(Opcodes.ASM5); m_method = md; } /** * Visits an annotation. * This class checks the visibility. If the annotation is visible, * creates the {@link AnnotationDescriptor} corresponding to this annotation * to visit this annotation. This {@link AnnotationDescriptor} is added to * the {@link MethodDescriptor} of the visited method. * * @param name the name of the annotation * @param visible is the annotation visible at runtime * @return the {@link AnnotationDescriptor} to visit this annotation or * <code>null</code> if the annotation is not visible. * @see org.objectweb.asm.MethodVisitor#visitAnnotation(java.lang.String, boolean) */ public AnnotationVisitor visitAnnotation(String name, boolean visible) { if (visible) { AnnotationDescriptor ann = new AnnotationDescriptor(name, true); m_method.addAnnotation(ann); return ann; } return null; } public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { m_method.addLocalVariable(name, desc, signature, index); } public void visitEnd() { m_method.end(); } public AnnotationVisitor visitParameterAnnotation(int id, String name, boolean visible) { if (visible) { AnnotationDescriptor ann = new AnnotationDescriptor(name, true); m_method.addParameterAnnotation(id, ann); return ann; } /* * It is harmless to keep injected parameter annotations on original constructor * for correct property resolution in case of re-manipulation */ if (m_method.getName().equals("$init")) { AnnotationDescriptor ann = new AnnotationDescriptor(name, false); m_method.addParameterAnnotation(id, ann); return ann; } return null; } } /** * Describes a method or constructor annotation. * This allows creating a copy of the annotations found in the original class * to move them on inserted method. This class implements an * {@link AnnotationVisitor} in order to create the copy. * This class contains a <code>visit</code> method re-injecting the * annotation in the generated method. * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> */ public class AnnotationDescriptor extends AnnotationVisitor { /** * The name of the annotation. */ private String m_name; /** * Is the annotation visible at runtime? */ private boolean m_visible; /** * The description of the annotation. * This attribute is set only for nested annotations. */ private String m_desc; /** * The list of 'simple' attributes. */ private List<SimpleAttribute> m_simples = new ArrayList<SimpleAttribute>(0); /** * The list of attribute containing an * enumeration value. */ private List<EnumAttribute> m_enums = new ArrayList<EnumAttribute>(0); /** * The list of attribute which are * annotations. */ private List<AnnotationDescriptor> m_nested = new ArrayList<AnnotationDescriptor>(0); /** * The list of attribute which are * arrays. */ private List<ArrayAttribute> m_arrays = new ArrayList<ArrayAttribute>(0); /** * Creates an annotation descriptor. * This constructor is used for 'root' annotations. * * @param name the name of the annotation * @param visible the visibility of the annotation at runtime */ public AnnotationDescriptor(String name, boolean visible) { super(Opcodes.ASM5); m_name = name; m_visible = visible; } /** * Creates an annotation descriptor. * This constructor is used for nested annotations. * * @param name the name of the annotation * @param desc the descriptor of the annotation */ public AnnotationDescriptor(String name, String desc) { super(Opcodes.ASM5); m_name = name; m_visible = true; m_desc = desc; } /** * Visits a simple attribute. * * @param arg0 the attribute name * @param arg1 the attribute value * @see org.objectweb.asm.AnnotationVisitor#visit(java.lang.String, java.lang.Object) */ public void visit(String arg0, Object arg1) { m_simples.add(new SimpleAttribute(arg0, arg1)); } /** * Visits a nested annotation. * * @param arg0 the attribute name * @param arg1 the annotation descriptor * @return the annotation visitor parsing the nested annotation * @see org.objectweb.asm.AnnotationVisitor#visitAnnotation(java.lang.String, java.lang.String) */ public AnnotationVisitor visitAnnotation(String arg0, String arg1) { AnnotationDescriptor ad = new AnnotationDescriptor(arg0, arg1); m_nested.add(ad); return ad; } /** * Visits an array attribute. * * @param arg0 the name of the attribute * @return the annotation visitor parsing the content of the array, * uses a specific {@link ArrayAttribute} to parse this array * @see org.objectweb.asm.AnnotationVisitor#visitArray(java.lang.String) */ public AnnotationVisitor visitArray(String arg0) { ArrayAttribute aa = new ArrayAttribute(arg0); m_arrays.add(aa); return aa; } /** * End of the visit. * * @see org.objectweb.asm.AnnotationVisitor#visitEnd() */ public void visitEnd() { } /** * Visits an enumeration attribute. * * @param arg0 the attribute name * @param arg1 the enumeration descriptor * @param arg2 the attribute value * @see org.objectweb.asm.AnnotationVisitor#visitEnum(java.lang.String, java.lang.String, java.lang.String) */ public void visitEnum(String arg0, String arg1, String arg2) { m_enums.add(new EnumAttribute(arg0, arg1, arg2)); } /** * Methods allowing to recreate the visited (stored) annotation * into the destination method. * This method recreate the annotations itself and any other * attributes. * * @param mv the method visitor visiting the destination method. */ public void visitAnnotation(MethodVisitor mv) { AnnotationVisitor av = mv.visitAnnotation(m_name, m_visible); for (SimpleAttribute simple : m_simples) { simple.visit(av); } for (EnumAttribute en : m_enums) { en.visit(av); } for (AnnotationDescriptor nested : m_nested) { nested.visit(av); } for (ArrayAttribute array : m_arrays) { array.visit(av); } av.visitEnd(); } /** * Methods allowing to recreate the visited (stored) parameter annotations * into the destination method. * This method recreate the annotations itself and any other * attributes. * * @param id the paramter id * @param mv the method visitor visiting the destination method. */ public void visitParameterAnnotation(int id, MethodVisitor mv) { AnnotationVisitor av = mv.visitParameterAnnotation(id, m_name, m_visible); for (SimpleAttribute simple : m_simples) { simple.visit(av); } for (EnumAttribute en : m_enums) { en.visit(av); } for (AnnotationDescriptor nested : m_nested) { nested.visit(av); } for (ArrayAttribute array : m_arrays) { array.visit(av); } av.visitEnd(); } /** * Method allowing to recreate the visited (stored) annotation * into the destination annotation. This method is used only * for nested annotation. * * @param mv the annotation visitor to populate with the stored * annotation */ public void visit(AnnotationVisitor mv) { AnnotationVisitor av = mv.visitAnnotation(m_name, m_desc); for (SimpleAttribute simple : m_simples) { simple.visit(av); } for (EnumAttribute enu : m_enums) { enu.visit(av); } for (AnnotationDescriptor nested : m_nested) { nested.visit(av); } for (ArrayAttribute array : m_arrays) { array.visit(av); } av.visitEnd(); } } /** * Describes an array attribute. * This class is able to visit an annotation array attribute, and to * recreate this array on another annotation. * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> */ public class ArrayAttribute extends AnnotationVisitor { /** * The name of the attribute. */ private String m_name; /** * The content of the parsed array. */ private List<Object> m_content = new ArrayList<Object>(); /** * Creates an array attribute. * * @param name the name of the attribute. */ public ArrayAttribute(String name) { super(Opcodes.ASM5); m_name = name; } /** * Visits the content of the array. This method is called for * simple values. * * @param arg0 <code>null</code> * @param arg1 the value * @see org.objectweb.asm.AnnotationVisitor#visit(java.lang.String, java.lang.Object) */ public void visit(String arg0, Object arg1) { m_content.add(arg1); } /** * Visits the content of the array. This method is called for * nested annotations (annotations contained in the array). * * @param arg0 <code>null</code> * @param arg1 the annotation descriptor * @return an {@link AnnotationDescriptor} which creates a copy of * the contained annotation. * @see org.objectweb.asm.AnnotationVisitor#visitAnnotation(String, String) */ public AnnotationVisitor visitAnnotation(String arg0, String arg1) { AnnotationDescriptor ad = new AnnotationDescriptor(null, arg1); m_content.add(ad); return ad; } /** * Visits the content of the array. This method is called for * nested arrays (arrays contained in the array). * * @param arg0 <code>null</code> * @return an {@link AnnotationVisitor} which creates a copy of * the contained array. * @see org.objectweb.asm.AnnotationVisitor#visitArray(String) */ public AnnotationVisitor visitArray(String arg0) { ArrayAttribute aa = new ArrayAttribute(null); m_content.add(aa); return aa; } /** * End of the array attribute visit. * * @see org.objectweb.asm.AnnotationVisitor#visitEnd() */ public void visitEnd() { } /** * Visits the content of the array. This method is called for * enumeration values. * * @param arg0 <code>null</code> * @param arg1 the enumeration descriptor * @param arg2 the value * @see org.objectweb.asm.AnnotationVisitor#visitEnum(String, String, String) */ public void visitEnum(String arg0, String arg1, String arg2) { EnumAttribute ea = new EnumAttribute(null, arg1, arg2); m_content.add(ea); } /** * Recreates the visited array attribute. This method * handle the generation of the object embedded in the * array. * * @param av the annotation visitor on which the array attribute * needs to be injected. */ public void visit(AnnotationVisitor av) { AnnotationVisitor content = av.visitArray(m_name); for (Object component : m_content) { if (component instanceof AnnotationDescriptor) { ((AnnotationDescriptor) component).visit(content); } else if (component instanceof EnumAttribute) { ((EnumAttribute) component).visit(content); } else if (component instanceof ArrayAttribute) { ((ArrayAttribute) component).visit(content); } else { // Simple content.visit(null, component); } } content.visitEnd(); } } /** * Describes a simple attribute. * This class is able to visit an annotation simple attribute, and to * recreate this attribute on another annotation. * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> */ public static final class SimpleAttribute { /** * The name of the attribute. */ private String m_name; /** * The value of the attribute. */ private Object m_value; /** * Creates a simple attribute. * * @param name the name of the attribute * @param object the value of the attribute */ private SimpleAttribute(String name, Object object) { m_name = name; m_value = object; } /** * Recreates the attribute on the given annotation. * * @param visitor the visitor on which the attribute needs * to be injected. */ public void visit(AnnotationVisitor visitor) { visitor.visit(m_name, m_value); } } /** * Describes an attribute. The value of this attribute is an enumerated * value. * This class is able to visit an annotation enumeration attribute, and to * recreate this attribute on another annotation. * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> */ public static final class EnumAttribute { /** * The name of the attribute. */ private String m_name; /** * The descriptor of the enumeration. */ private String m_desc; /** * The value of the attribute. */ private String m_value; /** * Creates a enumeration attribute. * * @param name the name of the attribute. * @param desc the descriptor of the {@link Enum} * @param value the enumerated value */ private EnumAttribute(String name, String desc, String value) { m_name = name; m_value = value; m_desc = desc; } /** * Recreates the attribute on the given annotation. * * @param visitor the visitor on which the attribute needs * to be injected. */ public void visit(AnnotationVisitor visitor) { visitor.visitEnum(m_name, m_desc, m_value); } } /** * Class required to detect inner classes assigned to static field and thus must not be manipulated (FELIX-4347). * If an inner class is assigned to a static field, it must not be manipulated. * <p/> * However notice that this is only useful when AspectJ is used, because aspectJ is changing the 'staticity' of * the inner class. */ private class InnerClassAssignedToStaticFieldDetector extends MethodVisitor { public InnerClassAssignedToStaticFieldDetector() { super(Opcodes.ASM5); } @Override public void visitTypeInsn(int opcode, String type) { if (opcode == NEW && m_inners.containsKey(type)) { m_inners.remove(type); } } } }