8168444: (jdeprscan) improper handling of primitives and primitive array types
Reviewed-by: psandoz, jjg
--- a/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeprscan/scan/Scan.java Mon Apr 24 14:59:43 2017 -0700
+++ b/langtools/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
--- a/langtools/test/tools/jdeprscan/tests/jdk/jdeprscan/TestLoadExpected.csv Mon Apr 24 14:59:43 2017 -0700
+++ b/langtools/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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/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 <classname> 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()
+ };
+ }
+ }
+}