This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-bcel.git
The following commit(s) were added to refs/heads/master by this push: new 6f0f9bb improve test case coverage; fix Utility.encode bug (#46) 6f0f9bb is described below commit 6f0f9bb10a276412aebbb7a06d9fa566382222f0 Author: Mark Roberts <markr...@users.noreply.github.com> AuthorDate: Sat Aug 29 15:52:40 2020 -0700 improve test case coverage; fix Utility.encode bug (#46) * improve test case coverage; fix Utility.encode bug * add bcel license * Replace new data test file with one with correct source license. --- .../java/org/apache/bcel/classfile/Utility.java | 1 + src/test/java/org/apache/bcel/PLSETestCase.java | 23 ++ .../java/org/apache/bcel/data/ConstantPoolX.java | 316 +++++++++++++++++++++ .../GeneratingAnnotatedClassesTestCase.java | 15 +- 4 files changed, 352 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/apache/bcel/classfile/Utility.java b/src/main/java/org/apache/bcel/classfile/Utility.java index 93c9977..c1bbb2d 100644 --- a/src/main/java/org/apache/bcel/classfile/Utility.java +++ b/src/main/java/org/apache/bcel/classfile/Utility.java @@ -1457,6 +1457,7 @@ public abstract class Utility { try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); GZIPOutputStream gos = new GZIPOutputStream(baos)) { gos.write(bytes, 0, bytes.length); + gos.close(); bytes = baos.toByteArray(); } } diff --git a/src/test/java/org/apache/bcel/PLSETestCase.java b/src/test/java/org/apache/bcel/PLSETestCase.java index c4e980a..78fa5f1 100644 --- a/src/test/java/org/apache/bcel/PLSETestCase.java +++ b/src/test/java/org/apache/bcel/PLSETestCase.java @@ -18,10 +18,12 @@ package org.apache.bcel; +import org.apache.bcel.classfile.Code; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.classfile.LocalVariable; import org.apache.bcel.classfile.LocalVariableTable; import org.apache.bcel.classfile.Method; +import org.apache.bcel.classfile.Utility; import org.apache.bcel.generic.ClassGen; import org.apache.bcel.generic.ConstantPoolGen; import org.apache.bcel.generic.InstructionHandle; @@ -106,4 +108,25 @@ public class PLSETestCase extends AbstractTestCase //System.out.println(new_lv); assertEquals("live range length", lv.getLength(), new_lv.getLength()); } + + /** + * Test to improve BCEL tests code coverage for classfile/Utility.java. + */ + public void testCoverage() throws ClassNotFoundException, java.io.IOException + { + // load a class with a wide variety of byte codes - including tableswitch and lookupswitch + final JavaClass clazz = getTestClass(PACKAGE_BASE_NAME+".data.ConstantPoolX"); + for (final Method m: clazz.getMethods()) { + String signature = m.getSignature(); + Utility.methodTypeToSignature(Utility.methodSignatureReturnType(signature), + Utility.methodSignatureArgumentTypes(signature)); // discard result + final Code code = m.getCode(); + if (code != null) { + final String encoded = Utility.encode(code.getCode(), true); + // following statement will throw exeception without classfile/Utility.encode fix + Utility.decode(encoded, true); // discard result + code.toString(); // discard result + } + } + } } diff --git a/src/test/java/org/apache/bcel/data/ConstantPoolX.java b/src/test/java/org/apache/bcel/data/ConstantPoolX.java new file mode 100644 index 0000000..8523e06 --- /dev/null +++ b/src/test/java/org/apache/bcel/data/ConstantPoolX.java @@ -0,0 +1,316 @@ +/* + * 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.bcel.data; + +/* + * This file is a modified copy of org.apache.bcel.classfile.ConstantPool.java. + * It was chosen as it generates both tableswitch and lookupswitch byte codes. + * It has been modified to compile stand alone within the BCEL test environment. + * The code is not executed but the classfile is used as input to a BCEL test case. + */ + +import java.io.DataInput; +import java.io.DataOutputStream; +import java.io.IOException; + +import org.apache.bcel.Const; +import org.apache.bcel.classfile.*; + +/** + * This class represents the constant pool, i.e., a table of constants, of + * a parsed classfile. It may contain null references, due to the JVM + * specification that skips an entry after an 8-byte constant (double, + * long) entry. Those interested in generating constant pools + * programatically should see <a href="../generic/ConstantPoolGen.html"> + * ConstantPoolGen</a>. + + * @see Constant + * @see org.apache.bcel.generic.ConstantPoolGen + */ +public abstract class ConstantPoolX implements Cloneable, Node { + + private Constant[] constantPool; + + /** + * Resolves constant to a string representation. + * + * @param c Constant to be printed + * @return String representation + */ + public String constantToString( Constant c ) throws ClassFormatException { + String str; + int i; + final byte tag = c.getTag(); + switch (tag) { + case Const.CONSTANT_Class: + i = ((ConstantClass) c).getNameIndex(); + c = getConstant(i, Const.CONSTANT_Utf8); + str = Utility.compactClassName(((ConstantUtf8) c).getBytes(), false); + break; + case Const.CONSTANT_String: + i = ((ConstantString) c).getStringIndex(); + c = getConstant(i, Const.CONSTANT_Utf8); + str = "\"" + escape(((ConstantUtf8) c).getBytes()) + "\""; + break; + case Const.CONSTANT_Utf8: + str = ((ConstantUtf8) c).getBytes(); + break; + case Const.CONSTANT_Double: + str = String.valueOf(((ConstantDouble) c).getBytes()); + break; + case Const.CONSTANT_Float: + str = String.valueOf(((ConstantFloat) c).getBytes()); + break; + case Const.CONSTANT_Long: + str = String.valueOf(((ConstantLong) c).getBytes()); + break; + case Const.CONSTANT_Integer: + str = String.valueOf(((ConstantInteger) c).getBytes()); + break; + case Const.CONSTANT_NameAndType: + str = constantToString(((ConstantNameAndType) c).getNameIndex(), + Const.CONSTANT_Utf8) + + " " + constantToString(((ConstantNameAndType) c).getSignatureIndex(), + Const.CONSTANT_Utf8); + break; + case Const.CONSTANT_InterfaceMethodref: + case Const.CONSTANT_Methodref: + case Const.CONSTANT_Fieldref: + str = constantToString(((ConstantCP) c).getClassIndex(), Const.CONSTANT_Class) + + "." + constantToString(((ConstantCP) c).getNameAndTypeIndex(), + Const.CONSTANT_NameAndType); + break; + case Const.CONSTANT_MethodHandle: + // Note that the ReferenceIndex may point to a Fieldref, Methodref or + // InterfaceMethodref - so we need to peek ahead to get the actual type. + final ConstantMethodHandle cmh = (ConstantMethodHandle) c; + str = Const.getMethodHandleName(cmh.getReferenceKind()) + + " " + constantToString(cmh.getReferenceIndex(), + getConstant(cmh.getReferenceIndex()).getTag()); + break; + case Const.CONSTANT_MethodType: + final ConstantMethodType cmt = (ConstantMethodType) c; + str = constantToString(cmt.getDescriptorIndex(), Const.CONSTANT_Utf8); + break; + case Const.CONSTANT_InvokeDynamic: + final ConstantInvokeDynamic cid = (ConstantInvokeDynamic) c; + str = cid.getBootstrapMethodAttrIndex() + + ":" + constantToString(cid.getNameAndTypeIndex(), + Const.CONSTANT_NameAndType); + break; + case Const.CONSTANT_Module: + i = ((ConstantModule) c).getNameIndex(); + c = getConstant(i, Const.CONSTANT_Utf8); + str = Utility.compactClassName(((ConstantUtf8) c).getBytes(), false); + break; + case Const.CONSTANT_Package: + i = ((ConstantPackage) c).getNameIndex(); + c = getConstant(i, Const.CONSTANT_Utf8); + str = Utility.compactClassName(((ConstantUtf8) c).getBytes(), false); + break; + default: // Never reached + throw new IllegalArgumentException("Unknown constant type " + tag); + } + return str; + } + + private static String escape( final String str ) { + final int len = str.length(); + final StringBuilder buf = new StringBuilder(len + 5); + final char[] ch = str.toCharArray(); + for (int i = 0; i < len; i++) { + switch (ch[i]) { + case '\n': + buf.append("\\n"); + break; + case '\r': + buf.append("\\r"); + break; + case '\t': + buf.append("\\t"); + break; + case '\b': + buf.append("\\b"); + break; + case '"': + buf.append("\\\""); + break; + default: + buf.append(ch[i]); + } + } + return buf.toString(); + } + + /** + * Retrieves constant at `index' from constant pool and resolve it to + * a string representation. + * + * @param index of constant in constant pool + * @param tag expected type + * @return String representation + */ + public String constantToString( final int index, final byte tag ) throws ClassFormatException { + final Constant c = getConstant(index, tag); + return constantToString(c); + } + + /** + * Dump constant pool to file stream in binary format. + * + * @param file Output file stream + * @throws IOException + */ + public void dump( final DataOutputStream file ) throws IOException { + file.writeShort(constantPool.length); + for (int i = 1; i < constantPool.length; i++) { + if (constantPool[i] != null) { + constantPool[i].dump(file); + } + } + } + + /** + * Gets constant from constant pool. + * + * @param index Index in constant pool + * @return Constant value + * @see Constant + */ + public Constant getConstant( final int index ) { + if (index >= constantPool.length || index < 0) { + throw new ClassFormatException("Invalid constant pool reference: " + index + + ". Constant pool size is: " + constantPool.length); + } + return constantPool[index]; + } + + /** + * Gets constant from constant pool and check whether it has the + * expected type. + * + * @param index Index in constant pool + * @param tag Tag of expected constant, i.e., its type + * @return Constant value + * @see Constant + * @throws ClassFormatException + */ + public Constant getConstant( final int index, final byte tag ) throws ClassFormatException { + Constant c; + c = getConstant(index); + if (c == null) { + throw new ClassFormatException("Constant pool at index " + index + " is null."); + } + if (c.getTag() != tag) { + throw new ClassFormatException("Expected class `" + Const.getConstantName(tag) + + "' at index " + index + " and got " + c); + } + return c; + } + + /** + * @return Array of constants. + * @see Constant + */ + public Constant[] getConstantPool() { + return constantPool; + } + + /** + * Gets string from constant pool and bypass the indirection of + * `ConstantClass' and `ConstantString' objects. I.e. these classes have + * an index field that points to another entry of the constant pool of + * type `ConstantUtf8' which contains the real data. + * + * @param index Index in constant pool + * @param tag Tag of expected constant, either ConstantClass or ConstantString + * @return Contents of string reference + * @see ConstantClass + * @see ConstantString + * @throws ClassFormatException + */ + public String getConstantString( final int index, final byte tag ) throws ClassFormatException { + Constant c; + int i; + c = getConstant(index, tag); + /* This switch() is not that elegant, since the four classes have the + * same contents, they just differ in the name of the index + * field variable. + * But we want to stick to the JVM naming conventions closely though + * we could have solved these more elegantly by using the same + * variable name or by subclassing. + */ + switch (tag) { + case Const.CONSTANT_Class: + i = ((ConstantClass) c).getNameIndex(); + break; + case Const.CONSTANT_String: + i = ((ConstantString) c).getStringIndex(); + break; + case Const.CONSTANT_Module: + i = ((ConstantModule) c).getNameIndex(); + break; + case Const.CONSTANT_Package: + i = ((ConstantPackage) c).getNameIndex(); + break; + default: + throw new IllegalArgumentException("getConstantString called with illegal tag " + tag); + } + // Finally get the string from the constant pool + c = getConstant(i, Const.CONSTANT_Utf8); + return ((ConstantUtf8) c).getBytes(); + } + + + /** + * @return Length of constant pool. + */ + public int getLength() { + return constantPool == null ? 0 : constantPool.length; + } + + + /** + * @param constant Constant to set + */ + public void setConstant( final int index, final Constant constant ) { + constantPool[index] = constant; + } + + + /** + * @param constantPool + */ + public void setConstantPool( final Constant[] constantPool ) { + this.constantPool = constantPool; + } + + + /** + * @return String representation. + */ + @Override + public String toString() { + final StringBuilder buf = new StringBuilder(); + for (int i = 1; i < constantPool.length; i++) { + buf.append(i).append(")").append(constantPool[i]).append("\n"); + } + return buf.toString(); + } + +} diff --git a/src/test/java/org/apache/bcel/generic/GeneratingAnnotatedClassesTestCase.java b/src/test/java/org/apache/bcel/generic/GeneratingAnnotatedClassesTestCase.java index 95c4d8b..079adb4 100644 --- a/src/test/java/org/apache/bcel/generic/GeneratingAnnotatedClassesTestCase.java +++ b/src/test/java/org/apache/bcel/generic/GeneratingAnnotatedClassesTestCase.java @@ -153,6 +153,7 @@ public class GeneratingAnnotatedClassesTestCase extends AbstractTestCase * Going further than the last test - when we reload the method back in, * let's change it (adding a new annotation) and then store that, read it * back in and verify both annotations are there ! + * Also check that we can remove method annotations. */ public void testGenerateMethodLevelAnnotations2() throws ClassNotFoundException @@ -172,11 +173,11 @@ public class GeneratingAnnotatedClassesTestCase extends AbstractTestCase .getAnnotationEntries().length == 1); final MethodGen mainMethod2 = new MethodGen(mainMethod1, cg2.getClassName(), cg2.getConstantPool()); - assertTrue("The 'MethodGen' should have one annotations but has " + assertTrue("The 'MethodGen' should have one annotation but has " + mainMethod2.getAnnotationEntries().length, mainMethod2 .getAnnotationEntries().length == 1); - mainMethod2.addAnnotationEntry(createFruitAnnotation(cg2 - .getConstantPool(), "Pear")); + AnnotationEntryGen fruit = createFruitAnnotation(cg2.getConstantPool(), "Pear"); + mainMethod2.addAnnotationEntry(fruit); cg2.removeMethod(mainMethod1); cg2.addMethod(mainMethod2.getMethod()); dumpClass(cg2, "temp3", "HelloWorld.class"); @@ -186,6 +187,14 @@ public class GeneratingAnnotatedClassesTestCase extends AbstractTestCase final int i = mainMethod3.getAnnotationEntries().length; assertTrue("The 'Method' should now have two annotations but has " + i, i == 2); + mainMethod2.removeAnnotationEntry(fruit); + assertTrue("The 'MethodGen' should have one annotation but has " + + mainMethod2.getAnnotationEntries().length, mainMethod2 + .getAnnotationEntries().length == 1); + mainMethod2.removeAnnotationEntries(); + assertTrue("The 'MethodGen' should have no annotations but has " + + mainMethod2.getAnnotationEntries().length, mainMethod2 + .getAnnotationEntries().length == 0); assertTrue(wipe("temp2", "HelloWorld.class")); assertTrue(wipe("temp3", "HelloWorld.class")); }