diff --git a/com.ibm.wala.core.testdata/src/annotations/AnnotatedClass3.java b/com.ibm.wala.core.testdata/src/annotations/AnnotatedClass3.java new file mode 100644 index 000000000..430487dcd --- /dev/null +++ b/com.ibm.wala.core.testdata/src/annotations/AnnotatedClass3.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2008 IBM Corporation. + * 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 annotations; + +@AnnotationWithParams(strParam="classStrParam") +public class AnnotatedClass3 { + + @AnnotationWithParams(enumParam=AnnotationEnum.VAL1,strArrParam={"biz","boz"},annotParam=@AnnotationWithSingleParam("sdfevs"),strParam="sdfsevs",intParam=25,klassParam=Integer.class) + public static void foo() { + + } + + @AnnotationWithParams(strArrParam={}) + public static void emptyArray() {} + +} diff --git a/com.ibm.wala.core.testdata/src/annotations/AnnotationEnum.java b/com.ibm.wala.core.testdata/src/annotations/AnnotationEnum.java new file mode 100644 index 000000000..c20f6e8e7 --- /dev/null +++ b/com.ibm.wala.core.testdata/src/annotations/AnnotationEnum.java @@ -0,0 +1,16 @@ +/******************************************************************************* + * Copyright (c) 2008 IBM Corporation. + * 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 annotations; + +public enum AnnotationEnum { + + VAL1, VAL2 +} diff --git a/com.ibm.wala.core.testdata/src/annotations/AnnotationWithParams.java b/com.ibm.wala.core.testdata/src/annotations/AnnotationWithParams.java new file mode 100644 index 000000000..b7b7fedf1 --- /dev/null +++ b/com.ibm.wala.core.testdata/src/annotations/AnnotationWithParams.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2008 IBM Corporation. + * 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 annotations; + +public @interface AnnotationWithParams { + + String strParam() default "strdef"; + int intParam() default 0; + Class klassParam() default Object.class; + AnnotationEnum enumParam() default AnnotationEnum.VAL2; + String[] strArrParam() default {"foo","baz"}; + int[] intArrParam() default {3,4}; + AnnotationWithSingleParam annotParam() default @AnnotationWithSingleParam("fsf"); +} diff --git a/com.ibm.wala.core.testdata/src/annotations/AnnotationWithSingleParam.java b/com.ibm.wala.core.testdata/src/annotations/AnnotationWithSingleParam.java new file mode 100644 index 000000000..1bf483ba8 --- /dev/null +++ b/com.ibm.wala.core.testdata/src/annotations/AnnotationWithSingleParam.java @@ -0,0 +1,16 @@ +/******************************************************************************* + * Copyright (c) 2008 IBM Corporation. + * 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 annotations; + +public @interface AnnotationWithSingleParam { + + String value() default ""; +} diff --git a/com.ibm.wala.core.tests/src/com/ibm/wala/core/tests/ir/AnnotationTest.java b/com.ibm.wala.core.tests/src/com/ibm/wala/core/tests/ir/AnnotationTest.java index bb4a4d1fb..e003fac7c 100644 --- a/com.ibm.wala.core.tests/src/com/ibm/wala/core/tests/ir/AnnotationTest.java +++ b/com.ibm.wala.core.tests/src/com/ibm/wala/core/tests/ir/AnnotationTest.java @@ -4,10 +4,14 @@ import java.io.IOException; import java.util.Collection; import java.util.Collections; +import org.junit.AfterClass; import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Test; import com.ibm.wala.classLoader.IClass; +import com.ibm.wala.classLoader.IMethod; +import com.ibm.wala.classLoader.ShrikeCTMethod; import com.ibm.wala.classLoader.ShrikeClass; import com.ibm.wala.core.tests.callGraph.CallGraphTestUtil; import com.ibm.wala.core.tests.util.TestConstants; @@ -15,8 +19,11 @@ import com.ibm.wala.core.tests.util.WalaTestCase; import com.ibm.wala.ipa.callgraph.AnalysisScope; import com.ibm.wala.ipa.cha.ClassHierarchy; import com.ibm.wala.ipa.cha.ClassHierarchyException; +import com.ibm.wala.ipa.cha.IClassHierarchy; import com.ibm.wala.shrikeCT.InvalidClassFileException; import com.ibm.wala.types.ClassLoaderReference; +import com.ibm.wala.types.MethodReference; +import com.ibm.wala.types.Selector; import com.ibm.wala.types.TypeReference; import com.ibm.wala.types.annotations.Annotation; import com.ibm.wala.util.collections.HashSetFactory; @@ -29,39 +36,59 @@ public class AnnotationTest extends WalaTestCase { justThisTest(AnnotationTest.class); } - @Test public void testClassAnnotations1() throws Exception { + private static IClassHierarchy cha; + + @BeforeClass + public static void before() throws IOException, ClassHierarchyException { + AnalysisScope scope = AnalysisScopeReader.readJavaScope(TestConstants.WALA_TESTDATA, + FileProvider.getFile(CallGraphTestUtil.REGRESSION_EXCLUSIONS), AnnotationTest.class.getClassLoader()); + cha = ClassHierarchy.make(scope); + } + + @AfterClass + public static void after() { + cha = null; + + } + @Test + public void testClassAnnotations1() throws Exception { TypeReference typeUnderTest = TypeReference.findOrCreate(ClassLoaderReference.Application, "Lannotations/AnnotatedClass1"); Collection expectedRuntimeInvisibleAnnotations = HashSetFactory.make(); - expectedRuntimeInvisibleAnnotations.add(Annotation.make(TypeReference.findOrCreate(ClassLoaderReference.Application, "Lannotations/RuntimeInvisableAnnotation"))); - expectedRuntimeInvisibleAnnotations.add(Annotation.make(TypeReference.findOrCreate(ClassLoaderReference.Application, "Lannotations/DefaultVisableAnnotation"))); + expectedRuntimeInvisibleAnnotations.add(Annotation.make(TypeReference.findOrCreate(ClassLoaderReference.Application, + "Lannotations/RuntimeInvisableAnnotation"))); + expectedRuntimeInvisibleAnnotations.add(Annotation.make(TypeReference.findOrCreate(ClassLoaderReference.Application, + "Lannotations/DefaultVisableAnnotation"))); Collection expectedRuntimeVisibleAnnotations = HashSetFactory.make(); - expectedRuntimeVisibleAnnotations.add(Annotation.make(TypeReference.findOrCreate(ClassLoaderReference.Application, "Lannotations/RuntimeVisableAnnotation"))); - - testClassAnnontations(typeUnderTest, expectedRuntimeInvisibleAnnotations, expectedRuntimeVisibleAnnotations); + expectedRuntimeVisibleAnnotations.add(Annotation.make(TypeReference.findOrCreate(ClassLoaderReference.Application, + "Lannotations/RuntimeVisableAnnotation"))); + + testClassAnnotations(typeUnderTest, expectedRuntimeInvisibleAnnotations, expectedRuntimeVisibleAnnotations); } - @Test public void testClassAnnotations2() throws Exception { + @Test + public void testClassAnnotations2() throws Exception { TypeReference typeUnderTest = TypeReference.findOrCreate(ClassLoaderReference.Application, "Lannotations/AnnotatedClass2"); Collection expectedRuntimeInvisibleAnnotations = HashSetFactory.make(); - expectedRuntimeInvisibleAnnotations.add(Annotation.make(TypeReference.findOrCreate(ClassLoaderReference.Application, "Lannotations/RuntimeInvisableAnnotation"))); - expectedRuntimeInvisibleAnnotations.add(Annotation.make(TypeReference.findOrCreate(ClassLoaderReference.Application, "Lannotations/RuntimeInvisableAnnotation2"))); + expectedRuntimeInvisibleAnnotations.add(Annotation.make(TypeReference.findOrCreate(ClassLoaderReference.Application, + "Lannotations/RuntimeInvisableAnnotation"))); + expectedRuntimeInvisibleAnnotations.add(Annotation.make(TypeReference.findOrCreate(ClassLoaderReference.Application, + "Lannotations/RuntimeInvisableAnnotation2"))); Collection expectedRuntimeVisibleAnnotations = HashSetFactory.make(); - expectedRuntimeVisibleAnnotations.add(Annotation.make(TypeReference.findOrCreate(ClassLoaderReference.Application, "Lannotations/RuntimeVisableAnnotation"))); - expectedRuntimeVisibleAnnotations.add(Annotation.make(TypeReference.findOrCreate(ClassLoaderReference.Application, "Lannotations/RuntimeVisableAnnotation2"))); - - testClassAnnontations(typeUnderTest, expectedRuntimeInvisibleAnnotations, expectedRuntimeVisibleAnnotations); + expectedRuntimeVisibleAnnotations.add(Annotation.make(TypeReference.findOrCreate(ClassLoaderReference.Application, + "Lannotations/RuntimeVisableAnnotation"))); + expectedRuntimeVisibleAnnotations.add(Annotation.make(TypeReference.findOrCreate(ClassLoaderReference.Application, + "Lannotations/RuntimeVisableAnnotation2"))); + + testClassAnnotations(typeUnderTest, expectedRuntimeInvisibleAnnotations, expectedRuntimeVisibleAnnotations); } - private void testClassAnnontations(TypeReference typeUnderTest, Collection expectedRuntimeInvisibleAnnotations, + private void testClassAnnotations(TypeReference typeUnderTest, Collection expectedRuntimeInvisibleAnnotations, Collection expectedRuntimeVisibleAnnotations) throws IOException, ClassHierarchyException, - InvalidClassFileException, com.ibm.wala.shrikeCT.AnnotationsReader.UnimplementedException { - AnalysisScope scope = AnalysisScopeReader.readJavaScope(TestConstants.WALA_TESTDATA, FileProvider.getFile(CallGraphTestUtil.REGRESSION_EXCLUSIONS), getClass().getClassLoader()); - ClassHierarchy cha = ClassHierarchy.make(scope); - + InvalidClassFileException { IClass classUnderTest = cha.lookupClass(typeUnderTest); Assert.assertNotNull(typeUnderTest.toString() + " not found", classUnderTest); Assert.assertTrue(classUnderTest instanceof ShrikeClass); @@ -74,23 +101,45 @@ public class AnnotationTest extends WalaTestCase { assertEqualCollections(expectedRuntimeVisibleAnnotations, runtimeVisibleAnnotations); } - private void assertEqualCollections(Collection expected, - Collection actual) { - if (expected == null){ + private void assertEqualCollections(Collection expected, Collection actual) { + if (expected == null) { expected = Collections.emptySet(); } - if (actual == null){ + if (actual == null) { actual = Collections.emptySet(); } - - if (expected.size() != actual.size()){ + + if (expected.size() != actual.size()) { Assert.assertTrue("expected=" + expected + " actual=" + actual, false); } - for (T a : expected){ - Assert.assertTrue ("missing " + a.toString(), actual.contains(a)); + for (T a : expected) { + Assert.assertTrue("missing " + a.toString(), actual.contains(a)); } } -// String methodSig = "annotations.AnnotatedClass1.m1()V;"; -// MethodReference mr = StringStuff.makeMethodReference(methodSig ); -// + + @Test + public void testClassAnnotations3() throws Exception { + + TypeReference typeRef = TypeReference.findOrCreate(ClassLoaderReference.Application, "Lannotations/AnnotatedClass3"); + IClass klass = cha.lookupClass(typeRef); + Assert.assertNotNull(klass); + ShrikeClass shrikeClass = (ShrikeClass) klass; + Collection classAnnotations = shrikeClass.getAnnotations(true); + Assert.assertEquals("[Annotation type {strParam=classStrParam}]", + classAnnotations.toString()); + + MethodReference methodRefUnderTest = MethodReference.findOrCreate(typeRef, Selector.make("foo()V")); + + IMethod methodUnderTest = cha.resolveMethod(methodRefUnderTest); + Assert.assertNotNull(methodRefUnderTest.toString() + " not found", methodUnderTest); + Assert.assertTrue(methodUnderTest instanceof ShrikeCTMethod); + ShrikeCTMethod shrikeCTMethodUnderTest = (ShrikeCTMethod) methodUnderTest; + + Collection runtimeInvisibleAnnotations = shrikeCTMethodUnderTest.getAnnotations(true); + Assert + .assertEquals( + "[Annotation type {enumParam=EnumElementValue [type=Lannotations/AnnotationEnum;, val=VAL1], strArrParam=ArrayElementValue [vals=[biz, boz]], annotParam=AnnotationElementValue [type=Lannotations/AnnotationWithSingleParam;, elementValues={value=sdfevs}], strParam=sdfsevs, intParam=25, klassParam=Ljava/lang/Integer;}]", + runtimeInvisibleAnnotations.toString()); + + } } diff --git a/com.ibm.wala.core/src/com/ibm/wala/classLoader/ShrikeCTMethod.java b/com.ibm.wala.core/src/com/ibm/wala/classLoader/ShrikeCTMethod.java index 0a578111b..3257e66ec 100644 --- a/com.ibm.wala.core/src/com/ibm/wala/classLoader/ShrikeCTMethod.java +++ b/com.ibm.wala.core/src/com/ibm/wala/classLoader/ShrikeCTMethod.java @@ -11,15 +11,12 @@ package com.ibm.wala.classLoader; import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; import com.ibm.wala.ipa.cha.IClassHierarchy; import com.ibm.wala.shrikeBT.Decoder; import com.ibm.wala.shrikeBT.IndirectionData; import com.ibm.wala.shrikeBT.shrikeCT.CTDecoder; import com.ibm.wala.shrikeCT.AnnotationsReader; -import com.ibm.wala.shrikeCT.AnnotationsReader.UnimplementedException; import com.ibm.wala.shrikeCT.ClassReader; import com.ibm.wala.shrikeCT.ClassReader.AttrIterator; import com.ibm.wala.shrikeCT.CodeReader; @@ -33,7 +30,6 @@ import com.ibm.wala.shrikeCT.SignatureReader; import com.ibm.wala.types.TypeReference; import com.ibm.wala.types.annotations.Annotation; import com.ibm.wala.types.generics.MethodTypeSignature; -import com.ibm.wala.util.collections.HashSetFactory; import com.ibm.wala.util.debug.Assertions; /** @@ -276,7 +272,7 @@ public final class ShrikeCTMethod extends ShrikeBTMethod implements IBytecodeMet return result; } - private AnnotationsReader getAnnotationsReader(boolean runtimeInvisable) { + private AnnotationsReader getAnnotationsReader(boolean runtimeInvisible) { ClassReader.AttrIterator iter = new AttrIterator(); getClassReader().initMethodAttributeIterator(shrikeMethodIndex, iter); @@ -284,7 +280,7 @@ public final class ShrikeCTMethod extends ShrikeBTMethod implements IBytecodeMet AnnotationsReader result = null; try { for (; iter.isValid(); iter.advance()) { - if (runtimeInvisable) { + if (runtimeInvisible) { if (iter.getName().equals(RuntimeInvisibleAnnotationsReader.attrName)) { result = new RuntimeInvisibleAnnotationsReader(iter); break; @@ -342,56 +338,22 @@ public final class ShrikeCTMethod extends ShrikeBTMethod implements IBytecodeMet /** * read the runtime-invisible annotations from the class file */ - public Collection getRuntimeInvisibleAnnotations() throws InvalidClassFileException, UnimplementedException { + public Collection getRuntimeInvisibleAnnotations() throws InvalidClassFileException { return getAnnotations(true); } /** * read the runtime-visible annotations from the class file */ - public Collection getRuntimeVisibleAnnotations() throws InvalidClassFileException, UnimplementedException { + public Collection getRuntimeVisibleAnnotations() throws InvalidClassFileException { return getAnnotations(false); } - public Collection getAnnotations(boolean runtimeInvisable) throws InvalidClassFileException, UnimplementedException { - AnnotationsReader r = getAnnotationsReader(runtimeInvisable); - if (r != null) { - int[] offsets = r.getAnnotationOffsets(); - Collection result = HashSetFactory.make(); - for (int i : offsets) { - String type = r.getAnnotationType(i); - type = type.replaceAll(";", ""); - TypeReference t = TypeReference.findOrCreate(getDeclaringClass().getClassLoader().getReference(), type); - result.add(Annotation.make(t)); - } - return result; - } else { - return Collections.emptySet(); - } + public Collection getAnnotations(boolean runtimeInvisible) throws InvalidClassFileException { + AnnotationsReader r = getAnnotationsReader(runtimeInvisible); + return Annotation.getAnnotationsFromReader(r, getDeclaringClass().getClassLoader().getReference()); } - /** - * Retrieves all runtime-invisible annotations associated with a given index. Returns null if the index is not valid or the - * annotation contains arrays. - */ - public HashMap getAnnotations(int index) { - AnnotationsReader r = getAnnotationsReader(true); - if (r == null) - return null; - int offsets[]; - try { - offsets = r.getAnnotationOffsets(); - if (offsets.length <= index) - return null; - } catch (Exception e) { - return null; - } - - int curOffset = offsets[index]; - HashMap res = r.getAnnotationValues(curOffset); - - return res; - } private static final IndirectionData NO_INDIRECTIONS = new IndirectionData() { diff --git a/com.ibm.wala.core/src/com/ibm/wala/classLoader/ShrikeClass.java b/com.ibm.wala.core/src/com/ibm/wala/classLoader/ShrikeClass.java index 452525b69..3d35b1585 100644 --- a/com.ibm.wala.core/src/com/ibm/wala/classLoader/ShrikeClass.java +++ b/com.ibm.wala.core/src/com/ibm/wala/classLoader/ShrikeClass.java @@ -12,14 +12,12 @@ package com.ibm.wala.classLoader; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; import java.util.List; import com.ibm.wala.ipa.cha.IClassHierarchy; import com.ibm.wala.shrikeBT.Constants; import com.ibm.wala.shrikeCT.AnnotationsReader; -import com.ibm.wala.shrikeCT.AnnotationsReader.UnimplementedException; import com.ibm.wala.shrikeCT.ClassConstants; import com.ibm.wala.shrikeCT.ClassReader; import com.ibm.wala.shrikeCT.ClassReader.AttrIterator; @@ -32,7 +30,6 @@ import com.ibm.wala.types.TypeName; import com.ibm.wala.types.TypeReference; import com.ibm.wala.types.annotations.Annotation; import com.ibm.wala.types.generics.ClassSignature; -import com.ibm.wala.util.collections.HashSetFactory; import com.ibm.wala.util.debug.Assertions; import com.ibm.wala.util.shrike.ShrikeClassReaderHandle; import com.ibm.wala.util.strings.Atom; @@ -85,13 +82,8 @@ public final class ShrikeClass extends JVMClass { Atom name = Atom.findOrCreateUnicodeAtom(cr.getFieldName(i)); ImmutableByteArray b = ImmutableByteArray.make(cr.getFieldType(i)); Collection annotations = null; - try { annotations = getRuntimeInvisibleAnnotations(i); annotations = annotations.isEmpty() ? null : annotations; - } catch (UnimplementedException e) { - e.printStackTrace(); - // keep going - } if ((accessFlags & ClassConstants.ACC_STATIC) == 0) { addFieldToList(instanceList, name, b, accessFlags, annotations); @@ -226,29 +218,17 @@ public final class ShrikeClass extends JVMClass { reader.clear(); } - public Collection getRuntimeInvisibleAnnotations() throws InvalidClassFileException, UnimplementedException { + public Collection getRuntimeInvisibleAnnotations() throws InvalidClassFileException { return getAnnotations(true); } - public Collection getRuntimeVisibleAnnotations() throws InvalidClassFileException, UnimplementedException { + public Collection getRuntimeVisibleAnnotations() throws InvalidClassFileException { return getAnnotations(false); } - public Collection getAnnotations(boolean runtimeInvisable) throws InvalidClassFileException, UnimplementedException { - AnnotationsReader r = getAnnotationsReader(runtimeInvisable); - if (r != null) { - int[] offsets = r.getAnnotationOffsets(); - Collection result = HashSetFactory.make(); - for (int i : offsets) { - String type = r.getAnnotationType(i); - type = type.replaceAll(";", ""); - TypeReference t = TypeReference.findOrCreate(getClassLoader().getReference(), type); - result.add(Annotation.make(t)); - } - return result; - } else { - return Collections.emptySet(); - } + public Collection getAnnotations(boolean runtimeInvisible) throws InvalidClassFileException { + AnnotationsReader r = getAnnotationsReader(runtimeInvisible); + return Annotation.getAnnotationsFromReader(r, getClassLoader().getReference()); } private AnnotationsReader getAnnotationsReader(boolean runtimeInvisable) throws InvalidClassFileException { @@ -278,6 +258,7 @@ public final class ShrikeClass extends JVMClass { return result; } + private InnerClassesReader getInnerClassesReader() throws InvalidClassFileException { ClassReader r = reader.get(); ClassReader.AttrIterator attrs = new ClassReader.AttrIterator(); @@ -320,22 +301,9 @@ public final class ShrikeClass extends JVMClass { /** * read the runtime-invisible annotations from the class file */ - public Collection getRuntimeInvisibleAnnotations(int fieldIndex) throws InvalidClassFileException, - UnimplementedException { + public Collection getRuntimeInvisibleAnnotations(int fieldIndex) throws InvalidClassFileException { RuntimeInvisibleAnnotationsReader r = getRuntimeInvisibleAnnotationsReader(fieldIndex); - if (r != null) { - int[] offsets = r.getAnnotationOffsets(); - Collection result = HashSetFactory.make(); - for (int i : offsets) { - String type = r.getAnnotationType(i); - type = type.replaceAll(";", ""); - TypeReference t = TypeReference.findOrCreate(getClassLoader().getReference(), type); - result.add(Annotation.make(t)); - } - return result; - } else { - return Collections.emptySet(); - } + return Annotation.getAnnotationsFromReader(r, getClassLoader().getReference()); } private SignatureReader getSignatureReader() throws InvalidClassFileException { diff --git a/com.ibm.wala.core/src/com/ibm/wala/types/annotations/Annotation.java b/com.ibm.wala.core/src/com/ibm/wala/types/annotations/Annotation.java index e56e2b996..63d46b1b8 100644 --- a/com.ibm.wala.core/src/com/ibm/wala/types/annotations/Annotation.java +++ b/com.ibm.wala.core/src/com/ibm/wala/types/annotations/Annotation.java @@ -11,41 +11,95 @@ package com.ibm.wala.types.annotations; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import com.ibm.wala.shrikeCT.AnnotationsReader; +import com.ibm.wala.shrikeCT.AnnotationsReader.AnnotationAttribute; +import com.ibm.wala.shrikeCT.AnnotationsReader.ElementValue; +import com.ibm.wala.shrikeCT.InvalidClassFileException; +import com.ibm.wala.types.ClassLoaderReference; import com.ibm.wala.types.TypeReference; +import com.ibm.wala.util.collections.HashSetFactory; import com.ibm.wala.util.collections.Pair; /** - * Represents a Java 5.0 class file annotation + * Represents a member annotation, e.g., Java 5.0 class file annotations */ public class Annotation { - + private final TypeReference type; - private final Pair[] arguments; - - private Annotation(TypeReference type, Pair[] arguments) { + + /** + * named arguments to the annotation, represented as a mapping from name to + * value. Note that for Java annotation arguments, the values are always + * Strings, independent of their actual type in the bytecode. + */ + private final Map namedArguments; + + /** + * unnamed arguments to the annotation (e.g., constructor arguments for C# + * attributes), represented as an array of pairs (T,V), where T is the + * argument type and V is the value. The array preserves the order in which + * the arguments were passed. If null, there are no unnamed arguments. + */ + private final Pair[] unnamedArguments; + + private Annotation(TypeReference type, Map namedArguments, Pair[] unnamedArguments) { this.type = type; - this.arguments = arguments; + if (namedArguments == null) { + throw new IllegalArgumentException("namedArguments is null"); + } + this.namedArguments = namedArguments; + this.unnamedArguments = unnamedArguments; } - - public static Annotation make(TypeReference t, Pair[] arguments) { - return new Annotation(t, arguments); + + public static Annotation makeUnnamedAndNamed(TypeReference t, Map namedArguments, Pair[] unnamedArguments) { + return new Annotation(t, namedArguments, unnamedArguments); + } + public static Annotation makeWithUnnamed(TypeReference t, Pair[] unnamedArguments) { + return new Annotation(t, Collections.emptyMap(), unnamedArguments); } public static Annotation make(TypeReference t) { - return make(t, null); + return new Annotation(t, Collections.emptyMap(), null); + } + + public static Annotation makeWithNamed(TypeReference t, Map namedArguments) { + return new Annotation(t, namedArguments, null); + } + + public static Collection getAnnotationsFromReader(AnnotationsReader r, ClassLoaderReference clRef) throws InvalidClassFileException { + if (r != null) { + AnnotationAttribute[] allAnnotations = r.getAllAnnotations(); + Collection result = HashSetFactory.make(); + for (AnnotationAttribute annot : allAnnotations) { + String type = annot.type; + type = type.replaceAll(";", ""); + TypeReference t = TypeReference.findOrCreate(clRef, type); + result.add(makeWithNamed(t, annot.elementValues)); + } + return result; + } else { + return Collections.emptySet(); + } + } @Override public String toString() { StringBuffer sb = new StringBuffer("Annotation type " + type); - if (arguments != null) { + if (unnamedArguments != null) { sb.append("["); - for(Pair arg : arguments) { + for (Pair arg : unnamedArguments) { sb.append(" " + arg.fst.getName().getClassName() + ":" + arg.snd); } sb.append(" ]"); } + if (!namedArguments.isEmpty()) { + sb.append(" " + namedArguments); + } return sb.toString(); } @@ -53,7 +107,7 @@ public class Annotation { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + Arrays.hashCode(arguments); + result = prime * result + Arrays.hashCode(unnamedArguments); result = prime * result + ((type == null) ? 0 : type.hashCode()); return result; } @@ -67,7 +121,7 @@ public class Annotation { if (getClass() != obj.getClass()) return false; Annotation other = (Annotation) obj; - if (!Arrays.equals(arguments, other.arguments)) + if (!Arrays.equals(unnamedArguments, other.unnamedArguments)) return false; if (type == null) { if (other.type != null) @@ -77,8 +131,12 @@ public class Annotation { return true; } - public Pair[] getArguments() { - return arguments; + public Pair[] getUnnamedArguments() { + return unnamedArguments; + } + + public Map getNamedArguments() { + return namedArguments; } public TypeReference getType() { diff --git a/com.ibm.wala.core/src/com/ibm/wala/types/annotations/Annotations.java b/com.ibm.wala.core/src/com/ibm/wala/types/annotations/Annotations.java index 5177665b2..af4b8408f 100644 --- a/com.ibm.wala.core/src/com/ibm/wala/types/annotations/Annotations.java +++ b/com.ibm.wala.core/src/com/ibm/wala/types/annotations/Annotations.java @@ -18,7 +18,6 @@ import com.ibm.wala.classLoader.IField; import com.ibm.wala.classLoader.IMethod; import com.ibm.wala.classLoader.ShrikeCTMethod; import com.ibm.wala.classLoader.ShrikeClass; -import com.ibm.wala.shrikeCT.AnnotationsReader.UnimplementedException; import com.ibm.wala.shrikeCT.InvalidClassFileException; import com.ibm.wala.types.TypeName; import com.ibm.wala.util.debug.Assertions; @@ -39,10 +38,7 @@ public class Annotations { } catch (InvalidClassFileException e) { e.printStackTrace(); Assertions.UNREACHABLE(); - } catch (UnimplementedException e) { - e.printStackTrace(); - Assertions.UNREACHABLE(); - } + } for (Annotation a : annotations) { if (a.getType().getName().equals(type)) { return true; @@ -63,9 +59,6 @@ public class Annotations { } catch (InvalidClassFileException e) { e.printStackTrace(); Assertions.UNREACHABLE(); - } catch (UnimplementedException e) { - e.printStackTrace(); - Assertions.UNREACHABLE(); } for (Annotation a : annotations) { if (a.getType().getName().equals(type)) { diff --git a/com.ibm.wala.shrike/META-INF/MANIFEST.MF b/com.ibm.wala.shrike/META-INF/MANIFEST.MF index d2148c876..6a8bf08e0 100644 --- a/com.ibm.wala.shrike/META-INF/MANIFEST.MF +++ b/com.ibm.wala.shrike/META-INF/MANIFEST.MF @@ -16,3 +16,4 @@ Export-Package: com.ibm.wala.shrike.bench, com.ibm.wala.shrikeBT.tools, com.ibm.wala.shrikeCT Bundle-RequiredExecutionEnvironment: J2SE-1.5 +Require-Bundle: com.ibm.wala.util diff --git a/com.ibm.wala.shrike/src/com/ibm/wala/shrikeBT/shrikeCT/tools/ClassPrinter.java b/com.ibm.wala.shrike/src/com/ibm/wala/shrikeBT/shrikeCT/tools/ClassPrinter.java index e5b981910..f51bbcb3e 100644 --- a/com.ibm.wala.shrike/src/com/ibm/wala/shrikeBT/shrikeCT/tools/ClassPrinter.java +++ b/com.ibm.wala.shrike/src/com/ibm/wala/shrikeBT/shrikeCT/tools/ClassPrinter.java @@ -23,6 +23,7 @@ import com.ibm.wala.shrikeBT.shrikeCT.CTDecoder; import com.ibm.wala.shrikeBT.shrikeCT.ClassInstrumenter; import com.ibm.wala.shrikeBT.shrikeCT.OfflineInstrumenter; import com.ibm.wala.shrikeCT.AnnotationsReader; +import com.ibm.wala.shrikeCT.AnnotationsReader.AnnotationAttribute; import com.ibm.wala.shrikeCT.ClassConstants; import com.ibm.wala.shrikeCT.ClassReader; import com.ibm.wala.shrikeCT.CodeReader; @@ -286,20 +287,8 @@ public class ClassPrinter { private void printAnnotations(ClassReader cr, ClassReader.AttrIterator attrs, AnnotationsReader r) throws InvalidClassFileException { - try { - int[] annotations = r.getAnnotationOffsets(); - for (int j : annotations) { - w.write(" Annotation type: " + r.getAnnotationType(j) + "\n"); - } - } catch (AnnotationsReader.UnimplementedException e) { - int len = attrs.getDataSize(); - int pos = attrs.getDataOffset(); - while (len > 0) { - int amount = Math.min(16, len); - w.write(" " + makeHex(cr.getBytes(), pos, amount, 32) + " " + makeChars(cr.getBytes(), pos, amount) + "\n"); - len -= amount; - pos += amount; - } + for (AnnotationAttribute annot : r.getAllAnnotations()) { + w.write(" Annotation type: " + annot.type + "\n"); } } diff --git a/com.ibm.wala.shrike/src/com/ibm/wala/shrikeCT/AnnotationsReader.java b/com.ibm.wala.shrike/src/com/ibm/wala/shrikeCT/AnnotationsReader.java index 60fa5c1a2..471733934 100644 --- a/com.ibm.wala.shrike/src/com/ibm/wala/shrikeCT/AnnotationsReader.java +++ b/com.ibm.wala.shrike/src/com/ibm/wala/shrikeCT/AnnotationsReader.java @@ -10,10 +10,14 @@ *******************************************************************************/ package com.ibm.wala.shrikeCT; -import java.util.HashMap; +import java.util.Arrays; +import java.util.Map; + +import com.ibm.wala.util.collections.HashMapFactory; +import com.ibm.wala.util.collections.Pair; /** - * This class reads Annotations attributes. + * This class reads Annotations attributes, e.g., RuntimeInvisibleAnnotations. * * @author sjfink */ @@ -40,7 +44,8 @@ public class AnnotationsReader extends AttributeReader { } /** - * @return total length of this attribute in bytes, including the first 6 bytes + * @return total length of this attribute in bytes, including the + * first 6 bytes * @throws InvalidClassFileException */ public int getAttributeSize() throws InvalidClassFileException { @@ -50,118 +55,238 @@ public class AnnotationsReader extends AttributeReader { } /** - * @return the offsets into the class file of the annotations of this attribute - * @throws InvalidClassFileException - * @throws UnimplementedException - */ - public int[] getAnnotationOffsets() throws InvalidClassFileException, UnimplementedException { - int[] result = new int[getAnnotationCount()]; - int offset = beginOffset + 8; - for (int i = 0; i < result.length; i++) { - result[i] = offset; - offset += getAnnotationSize(offset); - } - return result; - } - - /** - * @param begin offset in the constant pool - * @return the size, in bytes, of the annotation structure starting at a given offset - * @throws InvalidClassFileException - * @throws UnimplementedException - */ - private int getAnnotationSize(int begin) throws InvalidClassFileException, UnimplementedException { - int offset = begin + 2; - checkSize(offset, 2); - int numElementValuePairs = cr.getUShort(offset); - offset += 2; - for (int i = 0; i < numElementValuePairs; i++) { - offset += 2; - offset += getElementValueSize(offset); - } - return offset - begin; - } - - /** - * temporary migration aid until I've implemented everything. - * - * @author sjfink + * get the Utf8 constant pool value, where the constant pool offset is given + * in the class * + * @param offset + * offset in the class file at which the constant pool offset is + * given */ - public static class UnimplementedException extends Exception { - } - - /** - * @return the type of the annotation stating at a given offset - * @throws InvalidClassFileException - */ - public String getAnnotationType(int offset) throws InvalidClassFileException { + private String getUtf8ConstantPoolValue(int offset) throws InvalidClassFileException { checkSize(offset, 2); int cpOffset = cr.getUShort(offset); return cr.getCP().getCPUtf8(cpOffset); } - public static final int INT_TYPE = 3; - - public static final int STRING_TYPE = 1; - - /* - * This method maps the internal type representation of annotation types to java types and stringifies all annotations. + /** + * Marker interface for possible element values in an annotation attribute. + * + * @see AnnotationsReader#readElementValueAndSize(int) + * */ - private String getFromConstantPool(int offset) { - byte type = cr.getCP().getItemType(cr.getByte(offset)); - - if (type == INT_TYPE) { - String res = ""; - try { - res = "" + cr.getCP().getCPInt(cr.getByte(offset)); - } catch (InvalidClassFileException e) { - } - return res; - } - if (type == STRING_TYPE) { - String res = ""; - try { - res = cr.getCP().getCPUtf8(cr.getByte(offset)); - } catch (Exception e) { - } - return res; - } - return ""; + public static interface ElementValue { } /** - * This method returns all the annotations as map key->stringified value starting at the index begin in the class file. + * @see AnnotationsReader#readElementValueAndSize(int) + * * - * @param begin - * @return HashMap */ - public HashMap getAnnotationValues(int begin) { - HashMap res = new HashMap(); - int offset = begin + 2; + public static class ConstantElementValue implements ElementValue { - int numElementValuePairs = cr.getUShort(offset); + public final Object val; - offset += 3; + public ConstantElementValue(Object val) { + this.val = val; + } + + @Override + public String toString() { + return val.toString(); + } + + } + + /** + * @see AnnotationsReader#readElementValueAndSize(int) + */ + public static class EnumElementValue implements ElementValue { + public final String enumType; + public final String enumVal; + + public EnumElementValue(String enumType, String enumVal) { + super(); + this.enumType = enumType; + this.enumVal = enumVal; + } + + @Override + public String toString() { + return "EnumElementValue [type=" + enumType + ", val=" + enumVal + "]"; + } + + } + + /** + * @see AnnotationsReader#readElementValueAndSize(int) + */ + public static class ArrayElementValue implements ElementValue { + + public final ElementValue[] vals; + + public ArrayElementValue(ElementValue[] vals) { + super(); + this.vals = vals; + } + + @Override + public String toString() { + return "ArrayElementValue [vals=" + Arrays.toString(vals) + "]"; + } + + } + + /** + * get all the annotations declared in this attribute. + * + * @throws InvalidClassFileException + */ + public AnnotationAttribute[] getAllAnnotations() throws InvalidClassFileException { + AnnotationAttribute[] result = new AnnotationAttribute[getAnnotationCount()]; + int offset = beginOffset + 8; // skip attribute_name_index, + // attribute_length, and num_annotations + for (int i = 0; i < result.length; i++) { + Pair attributeAndSize = getAttributeAndSize(offset); + result[i] = attributeAndSize.fst; + offset += attributeAndSize.snd; + } + return result; + } + + /** + *
+   * annotation { 
+   *   u2 type_index;
+   *   u2 num_element_value_pairs; 
+   *   { u2 element_name_index; 
+   *     element_value value; 
+   *   } element_value_pairs[num_element_value_pairs]
+   * 
+ * + * @throws InvalidClassFileException + */ + private Pair getAttributeAndSize(int begin) throws InvalidClassFileException { + String type = getUtf8ConstantPoolValue(begin); + int numElementValuePairs = cr.getUShort(begin + 2); + int size = 4; + int offset = begin + 4; + Map elementName2Val = HashMapFactory.make(); for (int i = 0; i < numElementValuePairs; i++) { - String res1 = getFromConstantPool(offset); - offset += 3; - String res2 = getFromConstantPool(offset); + String elementName = getUtf8ConstantPoolValue(offset); offset += 2; - res.put(res1, res2); + Pair elementValAndSize = readElementValueAndSize(offset); + offset += elementValAndSize.snd; + size += elementValAndSize.snd + 2; + elementName2Val.put(elementName, elementValAndSize.fst); } - return res; + return Pair.make(new AnnotationAttribute(type, elementName2Val), size); } /** - * @return the size, in bytes, of the element-value structure starting at a given offset + * Representation of an annotation attribute. An annotation has the following + * format in the bytecode: * + *
+   * annotation {
+   *   u2 type_index;
+   *   u2 num_element_value_pairs;
+   *   {  u2 element_name_index;
+   *      element_value value;
+   * } element_value_pairs[num_element_value_pairs];
+   * 
* + * See the JVM spec section 4.7.16 for details. */ - private int getElementValueSize(int begin) { - return 3; // this is correct for any primitive type annotations. - // TODO: Integrate array annotations + public static class AnnotationAttribute implements ElementValue { + public final String type; + + public final Map elementValues; + + public AnnotationAttribute(String type, Map elementValues) { + super(); + this.type = type; + this.elementValues = elementValues; + } + + @Override + public String toString() { + return "AnnotationElementValue [type=" + type + ", elementValues=" + elementValues + "]"; + } + + } + + /** + *
+   * element_value { 
+   *   u1 tag; 
+   *   union {
+   *     u2 const_value_index; 
+   *     {  u2 type_name_index;
+   *        u2 const_name_index; 
+   *     } enum_const_value; 
+   *     u2 class_info_index; 
+   *     annotation annotation_value; 
+   *     {  u2 num_values;
+   *        element_value values[num_values]; 
+   *     } array_value;
+   *   } value;
+   * 
+ * + * A constant value (including class info) is represented by a + * {@link ConstantElementValue}. An enum constant value is represented by an + * {@link EnumElementValue}. An array value is represented by an + * {@link ArrayElementValue}. Finally, a nested annotation is represented by + * an {@link AnnotationAttribute}. + * + * @throws InvalidClassFileException + * @throws IllegalArgumentException + */ + private Pair readElementValueAndSize(int offset) throws IllegalArgumentException, + InvalidClassFileException { + char tag = (char) cr.getByte(offset); + // meaning of this short depends on the tag + int nextShort = cr.getUShort(offset + 1); + switch (tag) { + case 'B': + case 'C': + case 'I': + case 'S': + case 'Z': + return Pair. make(new ConstantElementValue(cr.getCP().getCPInt(nextShort)), 3); + case 'J': + return Pair. make(new ConstantElementValue(cr.getCP().getCPLong(nextShort)), 3); + case 'D': + return Pair. make(new ConstantElementValue(cr.getCP().getCPDouble(nextShort)), 3); + case 'F': + return Pair. make(new ConstantElementValue(cr.getCP().getCPFloat(nextShort)), 3); + case 's': // string + case 'c': // class; just represent as a constant element with the type name + return Pair. make(new ConstantElementValue(cr.getCP().getCPUtf8(nextShort)), 3); + case 'e': // enum + return Pair. make( + new EnumElementValue(cr.getCP().getCPUtf8(nextShort), cr.getCP().getCPUtf8(cr.getUShort(offset + 3))), 5); + case '[': // array + int numValues = nextShort; + int numArrayBytes = 3; // start with 3 for the tag and num_values bytes + ElementValue[] vals = new ElementValue[numValues]; + // start curOffset at beginning of array values + int curArrayOffset = offset + 3; + for (int i = 0; i < numValues; i++) { + Pair arrayElemValueAndSize = readElementValueAndSize(curArrayOffset); + vals[i] = arrayElemValueAndSize.fst; + curArrayOffset += arrayElemValueAndSize.snd; + numArrayBytes += arrayElemValueAndSize.snd; + } + return Pair. make(new ArrayElementValue(vals), numArrayBytes); + case '@': // annotation + Pair attributeAndSize = getAttributeAndSize(offset+1); + // add 1 to size for the tag + return Pair. make(attributeAndSize.fst, attributeAndSize.snd + 1); + default: + assert false; + return null; + } } } \ No newline at end of file