diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/javap/ClassWriter.java b/src/jdk.jdeps/share/classes/com/sun/tools/javap/ClassWriter.java index 798d2f2d3a8d2..edf3d803af321 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/javap/ClassWriter.java +++ b/src/jdk.jdeps/share/classes/com/sun/tools/javap/ClassWriter.java @@ -42,6 +42,7 @@ import java.lang.classfile.ClassSignature; import java.lang.classfile.ClassFile; import static java.lang.classfile.ClassFile.*; +import java.lang.classfile.ClassHierarchyResolver; import java.lang.classfile.constantpool.*; import java.lang.classfile.FieldModel; import java.lang.classfile.MethodModel; @@ -248,10 +249,30 @@ public boolean write(ClassModel cm) { if (options.verbose) { attrWriter.write(classModel.attributes()); } + + if (options.verify) { + var vErrors = VERIFIER.verify(classModel); + if (!vErrors.isEmpty()) { + println(); + for (var ve : vErrors) { + println(ve.getMessage()); + } + errorReported = true; + } + } return !errorReported; } // where + private static final ClassFile VERIFIER = ClassFile.of(ClassFile.ClassHierarchyResolverOption.of( + ClassHierarchyResolver.defaultResolver().orElse(new ClassHierarchyResolver() { + @Override + public ClassHierarchyResolver.ClassHierarchyInfo getClassInfo(ClassDesc classDesc) { + // mark all unresolved classes as interfaces to exclude them from assignability verification + return ClassHierarchyInfo.ofInterface(); + } + }))); + final SignaturePrinter sigPrinter; public static record SignaturePrinter(boolean verbose) { diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/javap/JavapTask.java b/src/jdk.jdeps/share/classes/com/sun/tools/javap/JavapTask.java index ac8ab164e629b..d8fa59c2dd0e7 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/javap/JavapTask.java +++ b/src/jdk.jdeps/share/classes/com/sun/tools/javap/JavapTask.java @@ -221,6 +221,13 @@ void process(JavapTask task, String opt, String arg) { } }, + new Option(false, "-verify") { + @Override + void process(JavapTask task, String opt, String arg) { + task.options.verify = true; + } + }, + new Option(false, "-XDdetails") { @Override void process(JavapTask task, String opt, String arg) { diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/javap/Options.java b/src/jdk.jdeps/share/classes/com/sun/tools/javap/Options.java index a1b642711f0e0..e93dd2daf85ae 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/javap/Options.java +++ b/src/jdk.jdeps/share/classes/com/sun/tools/javap/Options.java @@ -86,6 +86,7 @@ else if ((showAccess == 0) && (isPrivate)) public boolean showConstants; public boolean sysInfo; public boolean showInnerClasses; + public boolean verify; public int indentWidth = 2; // #spaces per indentWidth level; must be > 0 public int tabColumn = 40; // column number for comments; must be > 0 public String moduleName; diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/javap/resources/javap.properties b/src/jdk.jdeps/share/classes/com/sun/tools/javap/resources/javap.properties index 2cab137cb6349..5a50662afa0b9 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/javap/resources/javap.properties +++ b/src/jdk.jdeps/share/classes/com/sun/tools/javap/resources/javap.properties @@ -126,6 +126,9 @@ main.opt.sysinfo=\ \ -sysinfo Show system info (path, size, date, SHA-256 hash)\n\ \ of class being processed +main.opt.verify=\ +\ -verify Print additional class verification info + main.opt.module=\ \ --module -m Specify module containing classes to be disassembled diff --git a/src/jdk.jdeps/share/man/javap.1 b/src/jdk.jdeps/share/man/javap.1 index 68290d25bfb45..f9ec998e0af83 100644 --- a/src/jdk.jdeps/share/man/javap.1 +++ b/src/jdk.jdeps/share/man/javap.1 @@ -126,6 +126,9 @@ Prints internal type signatures. Shows system information (path, size, date, SHA-256 hash) of the class being processed. .TP +\f[V]-verify\f[R] +Prints additional class verification info. +.TP \f[V]-constants\f[R] Shows \f[V]static final\f[R] constants. .TP diff --git a/test/langtools/tools/javap/VerificationTest.java b/test/langtools/tools/javap/VerificationTest.java new file mode 100644 index 0000000000000..7caec462f165d --- /dev/null +++ b/test/langtools/tools/javap/VerificationTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024, 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 8182774 + * @enablePreview + * @summary test on class with a verification error + * @modules jdk.jdeps/com.sun.tools.javap + */ + +import java.io.*; +import java.lang.classfile.ClassFile; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.nio.file.Path; +import java.util.*; + +public class VerificationTest { + public static void main(String... args) throws Exception { + new VerificationTest().run(); + } + + void run() throws Exception { + String testClasses = System.getProperty("test.classes"); + String invalidClass = "InvalidClass"; + ClassFile.of(ClassFile.StackMapsOption.DROP_STACK_MAPS).buildTo(Path.of(testClasses, invalidClass + ".class"), ClassDesc.of(invalidClass), clb -> + clb.withMethodBody("methodWithMissingStackMap", ConstantDescs.MTD_void, 0, cob -> + cob.iconst_0().ifThen(tb -> tb.nop()).return_())); + String out = javap("-verify", "-classpath", testClasses, invalidClass); + if (!out.contains("Expecting a stackmap frame at branch target")) { + throw new Exception("Expected output not found"); + } + } + + String javap(String... args) throws Exception { + StringWriter sw = new StringWriter(); + PrintWriter out = new PrintWriter(sw); + int rc = com.sun.tools.javap.Main.run(args, out); + out.close(); + System.out.println(sw.toString()); + if (rc < 0) + throw new Exception("javap exited, rc=" + rc); + return sw.toString(); + } +}