# HG changeset patch # User smarks # Date 1493162075 25200 # Node ID 13f457e05af0cf1c58e370b2f5d1a5c80af4cf55 # Parent f260f1a2acf616509a4ee5a29bc7f2acca3853e3 8168444: (jdeprscan) improper handling of primitives and primitive array types Reviewed-by: psandoz, jjg diff -r f260f1a2acf6 -r 13f457e05af0 src/jdk.jdeps/share/classes/com/sun/tools/jdeprscan/scan/Scan.java --- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeprscan/scan/Scan.java Mon Apr 24 14:59:43 2017 -0700 +++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeprscan/scan/Scan.java Tue Apr 25 16:14:35 2017 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -97,21 +97,70 @@ finder = f; } - Pattern typePattern = Pattern.compile("\\[*L(.*);"); - - // "flattens" an array type name to its component type - // and a reference type "Lpkg/pkg/pkg/name;" to its base name - // "pkg/pkg/pkg/name". - // TODO: deal with primitive types - String flatten(String typeName) { - Matcher matcher = typePattern.matcher(typeName); + /** + * Given a descriptor type, extracts and returns the class name from it, if any. + * These types are obtained from field descriptors (JVMS 4.3.2) and method + * descriptors (JVMS 4.3.3). They have one of the following forms: + * + * I // or any other primitive, or V for void + * [I // array of primitives, including multi-dimensional + * Lname; // the named class + * [Lname; // array whose component is the named class (also multi-d) + * + * This method extracts and returns the class name, or returns empty for primitives, void, + * or array of primitives. + * + * Returns nullable reference instead of Optional because downstream + * processing can throw checked exceptions. + * + * @param descType the type from a descriptor + * @return the extracted class name, or null + */ + String nameFromDescType(String descType) { + Matcher matcher = descTypePattern.matcher(descType); if (matcher.matches()) { return matcher.group(1); } else { - return typeName; + return null; } } + Pattern descTypePattern = Pattern.compile("\\[*L(.*);"); + + /** + * Given a ref type name, extracts and returns the class name from it, if any. + * Ref type names are obtained from a Class_info structure (JVMS 4.4.1) and from + * Fieldref_info, Methodref_info, and InterfaceMethodref_info structures (JVMS 4.4.2). + * They represent named classes or array classes mentioned by name, and they + * represent class or interface types that have the referenced field or method + * as a member. They have one of the following forms: + * + * [I // array of primitives, including multi-dimensional + * name // the named class + * [Lname; // array whose component is the named class (also multi-d) + * + * Notably, a plain class name doesn't have the L prefix and ; suffix, and + * primitives and void do not occur. + * + * Returns nullable reference instead of Optional because downstream + * processing can throw checked exceptions. + * + * @param refType a reference type name + * @return the extracted class name, or null + */ + String nameFromRefType(String refType) { + Matcher matcher = refTypePattern.matcher(refType); + if (matcher.matches()) { + return matcher.group(1); + } else if (refType.startsWith("[")) { + return null; + } else { + return refType; + } + } + + Pattern refTypePattern = Pattern.compile("\\[+L(.*);"); + String typeKind(ClassFile cf) { AccessFlags flags = cf.access_flags; if (flags.is(ACC_ENUM)) { @@ -381,10 +430,12 @@ */ void checkClasses(ClassFile cf, CPEntries entries) throws ConstantPoolException { for (ConstantPool.CONSTANT_Class_info ci : entries.classes) { - String className = ci.getName(); - DeprData dd = db.getTypeDeprecated(flatten(className)); - if (dd != null) { - printType("scan.out.usesclass", cf, className, dd.isForRemoval()); + String name = nameFromRefType(ci.getName()); + if (name != null) { + DeprData dd = db.getTypeDeprecated(name); + if (dd != null) { + printType("scan.out.usesclass", cf, name, dd.isForRemoval()); + } } } } @@ -393,8 +444,8 @@ * Checks methods referred to from the constant pool. * * @param cf the ClassFile of this class + * @param clname the class name * @param nti the NameAndType_info from a MethodRef or InterfaceMethodRef entry - * @param clname the class name * @param msgKey message key for localization * @throws ConstantPoolException if a constant pool entry cannot be found */ @@ -404,10 +455,13 @@ String msgKey) throws ConstantPoolException { String name = nti.getName(); String type = nti.getType(); - clname = resolveMember(cf, flatten(clname), name, type, true, true); - DeprData dd = db.getMethodDeprecated(clname, name, type); - if (dd != null) { - printMethod(msgKey, cf, clname, name, type, dd.isForRemoval()); + clname = nameFromRefType(clname); + if (clname != null) { + clname = resolveMember(cf, clname, name, type, true, true); + DeprData dd = db.getMethodDeprecated(clname, name, type); + if (dd != null) { + printMethod(msgKey, cf, clname, name, type, dd.isForRemoval()); + } } } @@ -419,15 +473,17 @@ */ void checkFieldRef(ClassFile cf, ConstantPool.CONSTANT_Fieldref_info fri) throws ConstantPoolException { - String clname = fri.getClassName(); + String clname = nameFromRefType(fri.getClassName()); CONSTANT_NameAndType_info nti = fri.getNameAndTypeInfo(); String name = nti.getName(); String type = nti.getType(); - clname = resolveMember(cf, flatten(clname), name, type, false, true); - DeprData dd = db.getFieldDeprecated(clname, name); - if (dd != null) { - printField("scan.out.usesfield", cf, clname, name, dd.isForRemoval()); + if (clname != null) { + clname = resolveMember(cf, clname, name, type, false, true); + DeprData dd = db.getFieldDeprecated(clname, name); + if (dd != null) { + printField("scan.out.usesfield", cf, clname, name, dd.isForRemoval()); + } } } @@ -439,10 +495,12 @@ */ void checkFields(ClassFile cf) throws ConstantPoolException { for (Field f : cf.fields) { - String type = cf.constant_pool.getUTF8Value(f.descriptor.index); - DeprData dd = db.getTypeDeprecated(flatten(type)); - if (dd != null) { - printHasField(cf, f.getName(cf.constant_pool), type, dd.isForRemoval()); + String type = nameFromDescType(cf.constant_pool.getUTF8Value(f.descriptor.index)); + if (type != null) { + DeprData dd = db.getTypeDeprecated(type); + if (dd != null) { + printHasField(cf, f.getName(cf.constant_pool), type, dd.isForRemoval()); + } } } } @@ -461,16 +519,21 @@ DeprData dd; for (String parm : sig.getParameters()) { - dd = db.getTypeDeprecated(flatten(parm)); - if (dd != null) { - printHasMethodParmType(cf, mname, parm, dd.isForRemoval()); + parm = nameFromDescType(parm); + if (parm != null) { + dd = db.getTypeDeprecated(parm); + if (dd != null) { + printHasMethodParmType(cf, mname, parm, dd.isForRemoval()); + } } } - String ret = sig.getReturnType(); - dd = db.getTypeDeprecated(flatten(ret)); - if (dd != null) { - printHasMethodRetType(cf, mname, ret, dd.isForRemoval()); + String ret = nameFromDescType(sig.getReturnType()); + if (ret != null) { + dd = db.getTypeDeprecated(ret); + if (dd != null) { + printHasMethodRetType(cf, mname, ret, dd.isForRemoval()); + } } // check overrides diff -r f260f1a2acf6 -r 13f457e05af0 test/tools/jdeprscan/tests/jdk/jdeprscan/TestLoadExpected.csv --- a/test/tools/jdeprscan/tests/jdk/jdeprscan/TestLoadExpected.csv Mon Apr 24 14:59:43 2017 -0700 +++ b/test/tools/jdeprscan/tests/jdk/jdeprscan/TestLoadExpected.csv Tue Apr 25 16:14:35 2017 -0700 @@ -1,4 +1,4 @@ -#jdepr 1 +#jdepr1 METHOD,jdk/deprcases/members/ExampleAnnotation,name()Ljava/lang/String;,,false FIELD,jdk/deprcases/members/ExampleClass,field1,,false FIELD,jdk/deprcases/members/ExampleClass,field2,,false diff -r f260f1a2acf6 -r 13f457e05af0 test/tools/jdeprscan/tests/jdk/jdeprscan/TestPrims.csv --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/tools/jdeprscan/tests/jdk/jdeprscan/TestPrims.csv Tue Apr 25 16:14:35 2017 -0700 @@ -0,0 +1,18 @@ +#jdepr1 +CLASS,V,,,false +CLASS,Z,,,false +CLASS,B,,,false +CLASS,S,,,false +CLASS,C,,,false +CLASS,I,,,false +CLASS,J,,,false +CLASS,F,,,false +CLASS,D,,,false +CLASS,[Z,,,false +CLASS,[B,,,false +CLASS,[S,,,false +CLASS,[C,,,false +CLASS,[I,,,false +CLASS,[J,,,false +CLASS,[F,,,false +CLASS,[D,,,false diff -r f260f1a2acf6 -r 13f457e05af0 test/tools/jdeprscan/tests/jdk/jdeprscan/TestPrims.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/tools/jdeprscan/tests/jdk/jdeprscan/TestPrims.java Tue Apr 25 16:14:35 2017 -0700 @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8168444 + * @summary Test of jdeprscan handling of primitives and primitive arrays. + * @modules jdk.jdeps/com.sun.tools.jdeprscan + * @build jdk.jdeprscan.TestPrims + * @run testng jdk.jdeprscan.TestPrims + */ + +package jdk.jdeprscan; + +import com.sun.tools.jdeprscan.Main; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.util.regex.Pattern; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +public class TestPrims { + + @Test + public void test() throws IOException { + final String TESTSRC = System.getProperty("test.src"); + final String TESTCLASSPATH = System.getProperty("test.class.path"); + String CSV_FILE = TESTSRC + File.separator + "TestPrims.csv"; + + ByteArrayOutputStream outBaos = new ByteArrayOutputStream(); + ByteArrayOutputStream errBaos = new ByteArrayOutputStream(); + boolean mainResult; + + try (PrintStream out = new PrintStream(outBaos, false, "UTF-8"); + PrintStream err = new PrintStream(errBaos, false, "UTF-8")) { + mainResult = Main.call( + out, err, + "--class-path", TESTCLASSPATH, + "--Xload-csv", CSV_FILE, + "jdk.jdeprscan.TestPrims$Usage"); + // assertion is checked below after output is dumped + } + + byte[] outBytes = outBaos.toByteArray(); + byte[] errBytes = errBaos.toByteArray(); + ByteArrayInputStream outbais = new ByteArrayInputStream(outBytes); + ByteArrayInputStream errbais = new ByteArrayInputStream(errBytes); + + System.out.println("--- stdout ---"); + outbais.transferTo(System.out); + System.out.println("--- end stdout ---"); + + System.out.println("--- stderr ---"); + errbais.transferTo(System.out); + System.out.println("--- end stderr ---"); + + String outString = new String(outBytes, "UTF-8"); + String errString = new String(errBytes, "UTF-8"); + + // matches message "class uses deprecated class [I" + boolean outMatch = Pattern.compile("^class ").matcher(outString).find(); + + // matches message "error: cannot find class [I" + boolean errMatch = Pattern.compile("^error: ").matcher(errString).find(); + + if (!mainResult) { + System.out.println("FAIL: Main.call returned false"); + } + + if (outMatch) { + System.out.println("FAIL: stdout contains unexpected error message"); + } + + if (errMatch) { + System.out.println("FAIL: stderr contains unexpected error message"); + } + + assertTrue(mainResult && !outMatch && !errMatch); + } + + static class Usage { + void prims(boolean z, byte b, short s, char c, + int i, long j, float f, double d) { } + + void primsArrays(boolean[] z, byte[] b, short[] s, char[] c, + int[] i, long[] j, float[] f, double[] d) { } + + boolean zfield; + byte bfield; + short sfield; + char cfield; + int ifield; + long jfield; + float ffield; + double dfield; + + boolean[] azfield; + byte[] abfield; + short[] asfield; + char[] acfield; + int[] aifield; + long[] ajfield; + float[] affield; + double[] adfield; + + + Object[] clones() { + return new Object[] { + azfield.clone(), + abfield.clone(), + asfield.clone(), + acfield.clone(), + aifield.clone(), + ajfield.clone(), + affield.clone(), + adfield.clone() + }; + } + } +}