1 /* 2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @bug 8170708 27 * @summary javap -m <module> cannot read a module-info.class 28 * @library /tools/lib 29 * @modules 30 * jdk.compiler/com.sun.tools.javac.api 31 * jdk.compiler/com.sun.tools.javac.main 32 * jdk.jdeps/com.sun.tools.classfile 33 * jdk.jdeps/com.sun.tools.javap 34 * @build toolbox.JavacTask toolbox.JavapTask toolbox.ToolBox toolbox.TestRunner 35 * @run main TestClassNameWarning 36 */ 37 38 import java.nio.file.Files; 39 import java.nio.file.Path; 40 import java.nio.file.Paths; 41 import java.util.Arrays; 42 import java.util.List; 43 import java.util.regex.Pattern; 44 import java.util.stream.Collectors; 45 46 import com.sun.tools.classfile.ClassFile; 47 import com.sun.tools.classfile.ClassWriter; 48 49 import toolbox.JavacTask; 50 import toolbox.JavapTask; 51 import toolbox.Task; 52 import toolbox.TestRunner; 53 import toolbox.ToolBox; 54 55 public class TestClassNameWarning extends TestRunner { main(String... args)56 public static void main(String... args) throws Exception { 57 new TestClassNameWarning().runTests(m -> new Object[] { Paths.get(m.getName()) }); 58 } 59 60 private ToolBox tb = new ToolBox(); 61 TestClassNameWarning()62 TestClassNameWarning() { 63 super(System.err); 64 } 65 66 /** 67 * Baseline test for normal classes. 68 */ 69 @Test testStandardClass(Path base)70 public void testStandardClass(Path base) throws Exception { 71 Path src = base.resolve("src"); 72 Path classes = Files.createDirectories(base.resolve("classes")); 73 tb.writeJavaFiles(src, "class A { }"); 74 75 new JavacTask(tb) 76 .outdir(classes.toString()) 77 .files(tb.findJavaFiles(src)) 78 .run() 79 .writeAll(); 80 81 List<String> log = new JavapTask(tb) 82 .classpath(classes.toString()) 83 .classes("A") 84 .run() 85 .writeAll() 86 .getOutputLines(Task.OutputKind.DIRECT); 87 88 checkOutput(log, false, "^(Warning|Error)"); 89 checkOutput(log, true, "class A"); 90 } 91 92 /** 93 * Test that module-info can be used to name the .class file 94 * for a module declaration (i.e. ACC_MODULE, this_class == 0) 95 * This is the primary test case for the bug as reported. 96 */ 97 @Test testStandardModuleInfo(Path base)98 public void testStandardModuleInfo(Path base) throws Exception { 99 Path src = base.resolve("src"); 100 Path classes = Files.createDirectories(base.resolve("classes")); 101 tb.writeJavaFiles(src, "module m { }"); 102 103 new JavacTask(tb) 104 .outdir(classes.toString()) 105 .files(tb.findJavaFiles(src)) 106 .run() 107 .writeAll(); 108 109 List<String> log = new JavapTask(tb) 110 .options("--module-path", classes.toString(), 111 "--module", "m") 112 .classes("module-info") 113 .run() 114 .writeAll() 115 .getOutputLines(Task.OutputKind.DIRECT); 116 117 checkOutput(log, false, "^(Warning|Error)"); 118 checkOutput(log, true, "module m"); 119 } 120 121 /** 122 * Test module-info can still be used to find a weirdly named 123 * class equivalent to "class module-info { }" if that were legal in JLS. 124 * Such a class file would arguably be legal in certain selected contexts. 125 */ 126 @Test testLegacyModuleInfo(Path base)127 public void testLegacyModuleInfo(Path base) throws Exception { 128 Path src = base.resolve("src"); 129 Path classes = Files.createDirectories(base.resolve("classes")); 130 tb.writeJavaFiles(src, "class module_info { }"); 131 132 new JavacTask(tb) 133 .outdir(classes.toString()) 134 .files(tb.findJavaFiles(src)) 135 .run() 136 .writeAll(); 137 138 byte[] bytes = Files.readAllBytes(classes.resolve("module_info.class")); 139 byte[] searchBytes = "module_info".getBytes("UTF-8"); 140 byte[] replaceBytes = "module-info".getBytes("UTF-8"); 141 for (int i = 0; i < bytes.length - searchBytes.length; i++) { 142 if (Arrays.equals(bytes, i, i + searchBytes.length, 143 searchBytes, 0, searchBytes.length)) { 144 System.arraycopy(replaceBytes, 0, bytes, i, replaceBytes.length); 145 } 146 } 147 Files.write(classes.resolve("module-info.class"), bytes); 148 149 List<String> log = new JavapTask(tb) 150 .classpath(classes.toString()) 151 .options("-bootclasspath", "") // hide all system classes 152 .classes("module-info") 153 .run() 154 .writeAll() 155 .getOutputLines(Task.OutputKind.DIRECT); 156 157 checkOutput(log, false, "^(Warning|Error)"); 158 checkOutput(log, true, "class module-info"); 159 } 160 161 /** 162 * Test an invalid class, with this_class == 0. 163 */ 164 @Test testNoNameClass(Path base)165 public void testNoNameClass(Path base) throws Exception { 166 Path src = base.resolve("src"); 167 Path classes = Files.createDirectories(base.resolve("classes")); 168 tb.writeJavaFiles(src, "class A { }"); 169 170 new JavacTask(tb) 171 .outdir(classes.toString()) 172 .files(tb.findJavaFiles(src)) 173 .run() 174 .writeAll(); 175 176 ClassFile cf = ClassFile.read(classes.resolve("A.class")); 177 ClassFile cf2 = new ClassFile( 178 cf.magic, cf.minor_version, cf.major_version, cf.constant_pool, 179 cf.access_flags, 180 0, // this_class, 181 cf.super_class, cf.interfaces, cf.fields, cf.methods, cf.attributes); 182 new ClassWriter().write(cf2, Files.newOutputStream(classes.resolve("Z.class"))); 183 184 List<String> log = new JavapTask(tb) 185 .classpath(classes.toString()) 186 .classes("Z") 187 .run() 188 .writeAll() 189 .getOutputLines(Task.OutputKind.DIRECT); 190 191 checkOutput(log, true, "Warning:.*Z.class does not contain class Z"); 192 } 193 194 /** 195 * Test a class with unexpected contents. 196 * This is the arguably the most common negative case. 197 */ 198 @Test testWrongNameClass(Path base)199 public void testWrongNameClass(Path base) throws Exception { 200 Path src = base.resolve("src"); 201 Path classes = Files.createDirectories(base.resolve("classes")); 202 tb.writeJavaFiles(src, "class A { }"); 203 204 new JavacTask(tb) 205 .outdir(classes.toString()) 206 .files(tb.findJavaFiles(src)) 207 .run() 208 .writeAll(); 209 210 Files.move(classes.resolve("A.class"), classes.resolve("B.class")); 211 212 List<String> log = new JavapTask(tb) 213 .classpath(classes.toString()) 214 .classes("B") 215 .run() 216 .writeAll() 217 .getOutputLines(Task.OutputKind.DIRECT); 218 219 checkOutput(log, true, "Warning:.*B.class does not contain class B"); 220 } 221 222 /** 223 * Check that the output does, or does not, contain lines matching a regex. 224 */ checkOutput(List<String> log, boolean expect, String regex)225 private void checkOutput(List<String> log, boolean expect, String regex) { 226 Pattern p = Pattern.compile(regex); 227 List<String> matches = log.stream() 228 .filter(line -> p.matcher(line).find()) 229 .collect(Collectors.toList()); 230 if (expect) { 231 if (matches.isEmpty()) { 232 error("expected output not found: " + regex); 233 } 234 } else { 235 if (!matches.isEmpty()) { 236 error("unexpected output found: " + matches); 237 } 238 } 239 } 240 } 241 242