BadConstantValue.java revision 3850:8e69054abeeb
1/* 2 * Copyright 2016 Google, Inc. 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 */ 21 22/* 23 * @test 24 * @bug 8171132 25 * @summary Improve class reading of invalid or out-of-range ConstantValue attributes 26 * @modules jdk.jdeps/com.sun.tools.classfile 27 * jdk.compiler/com.sun.tools.javac.api 28 * jdk.compiler/com.sun.tools.javac.code 29 * jdk.compiler/com.sun.tools.javac.jvm 30 * jdk.compiler/com.sun.tools.javac.main 31 * jdk.compiler/com.sun.tools.javac.util 32 * @build BadConstantValue 33 * @run main BadConstantValue 34 */ 35 36import com.sun.tools.classfile.Attribute; 37import com.sun.tools.classfile.ClassFile; 38import com.sun.tools.classfile.ClassWriter; 39import com.sun.tools.classfile.ConstantPool.CONSTANT_Integer_info; 40import com.sun.tools.classfile.ConstantValue_attribute; 41import com.sun.tools.classfile.Field; 42import com.sun.tools.javac.api.JavacTaskImpl; 43import com.sun.tools.javac.code.ClassFinder.BadClassFile; 44import com.sun.tools.javac.code.Symtab; 45import com.sun.tools.javac.jvm.Target; 46import com.sun.tools.javac.util.Assert; 47import com.sun.tools.javac.util.JCDiagnostic; 48import java.io.File; 49import java.io.FileWriter; 50import java.io.IOException; 51import java.io.PrintWriter; 52import java.io.StringWriter; 53import java.util.Arrays; 54import java.util.Objects; 55import javax.tools.JavaCompiler; 56import javax.tools.ToolProvider; 57 58public class BadConstantValue { 59 60 static final File classesdir = new File("badconstants"); 61 62 public static void main(String[] args) throws Exception { 63 // report errors for ConstantValues of the wrong type 64 testInvalidConstantType("int"); 65 testInvalidConstantType("short"); 66 testInvalidConstantType("byte"); 67 testInvalidConstantType("char"); 68 testInvalidConstantType("boolean"); 69 70 // report errors for ConstantValues outside the expected range 71 testValidConstRange("int", Integer.MAX_VALUE); 72 testValidConstRange("int", Integer.MIN_VALUE); 73 74 testValidConstRange("short", Short.MAX_VALUE); 75 testValidConstRange("short", Short.MIN_VALUE); 76 testInvalidConstRange("short", Short.MAX_VALUE + 1); 77 testInvalidConstRange("short", Short.MIN_VALUE - 1); 78 79 testValidConstRange("byte", Byte.MAX_VALUE); 80 testValidConstRange("byte", Byte.MIN_VALUE); 81 testInvalidConstRange("byte", Byte.MAX_VALUE + 1); 82 testInvalidConstRange("byte", Byte.MIN_VALUE - 1); 83 84 testValidConstRange("char", Character.MAX_VALUE); 85 testValidConstRange("char", Character.MIN_VALUE); 86 testInvalidConstRange("char", Character.MAX_VALUE + 1); 87 testInvalidConstRange("char", Character.MIN_VALUE - 1); 88 89 testValidConstRange("boolean", 0); 90 testValidConstRange("boolean", 1); 91 testInvalidConstRange("boolean", 2); 92 testInvalidConstRange("boolean", Integer.MIN_VALUE); 93 testInvalidConstRange("boolean", Integer.MAX_VALUE); 94 } 95 96 /** 97 * Tests that a constant value of the given {@code type} and initialized with an out-of-range 98 * {@code value} is rejected. 99 */ 100 private static void testInvalidConstRange(String type, int value) throws Exception { 101 createConstantWithValue(type, value); 102 BadClassFile badClassFile = loadBadClass("Lib"); 103 if (badClassFile == null) { 104 throw new AssertionError("did not see expected error"); 105 } 106 JCDiagnostic diagnostic = (JCDiagnostic) badClassFile.getDiagnostic().getArgs()[1]; 107 assertEquals("compiler.misc.bad.constant.range", diagnostic.getCode()); 108 assertEquals(3, diagnostic.getArgs().length); 109 assertEquals(value, diagnostic.getArgs()[0]); 110 assertEquals("B", diagnostic.getArgs()[1].toString()); 111 assertEquals(type, String.valueOf(diagnostic.getArgs()[2])); 112 } 113 114 /** 115 * Tests that a constant value of the given {@code type} and initialized with {@code value} is 116 * accepted. 117 */ 118 private static void testValidConstRange(String type, int value) throws Exception { 119 createConstantWithValue(type, value); 120 BadClassFile badClassFile = loadBadClass("Lib"); 121 if (badClassFile != null) { 122 throw new AssertionError("saw unexpected error", badClassFile); 123 } 124 } 125 126 /** 127 * Creates a class file containing a constant field with the given type and value, which may be 128 * outside the expected range. 129 */ 130 private static void createConstantWithValue(String type, int value) throws Exception { 131 // Create a class with two constants, A and B. A is of type int and has value "actual"; 132 // B is of type "type" and is initialized to that type's default value. 133 File lib = writeFile(classesdir, "Lib.java", String.format( 134 "class Lib { static final int A = %s; static final %s B = %s; }", 135 value, type, (type.equals("boolean") ? "false" : "0"))); 136 compile("-d", classesdir.getPath(), lib.getPath()); 137 File libClass = new File(classesdir, "Lib.class"); 138 // Rewrite the class to only have field B of type "type" and with "value" (potentially 139 // out of range). 140 swapConstantValues(libClass); 141 } 142 143 /** Tests that a field of the given integral type with a constant string value is rejected. */ 144 private static void testInvalidConstantType(String type) throws Exception { 145 // create a class file with field that has an invalid CONSTANT_String ConstantValue 146 File lib = writeFile(classesdir, "Lib.java", String.format( 147 "class Lib { static final String A = \"hello\"; static final %s CONST = %s; }", 148 type, type.equals("boolean") ? "false" : "0")); 149 compile("-d", classesdir.getPath(), lib.getPath()); 150 File libClass = new File(classesdir, "Lib.class"); 151 swapConstantValues(libClass); 152 153 BadClassFile badClassFile = loadBadClass("Lib"); 154 155 JCDiagnostic diagnostic = (JCDiagnostic) badClassFile.getDiagnostic().getArgs()[1]; 156 assertEquals("compiler.misc.bad.constant.value", diagnostic.getCode()); 157 assertEquals(3, diagnostic.getArgs().length); 158 assertEquals("hello", diagnostic.getArgs()[0]); 159 assertEquals("CONST", diagnostic.getArgs()[1].toString()); 160 assertEquals("Integer", diagnostic.getArgs()[2]); 161 } 162 163 private static BadClassFile loadBadClass(String className) { 164 // load the class, and save the thrown BadClassFile exception 165 JavaCompiler c = ToolProvider.getSystemJavaCompiler(); 166 JavacTaskImpl task = (JavacTaskImpl) c.getTask(null, null, null, 167 Arrays.asList("-classpath", classesdir.getPath()), null, null); 168 Symtab syms = Symtab.instance(task.getContext()); 169 task.ensureEntered(); 170 BadClassFile badClassFile; 171 try { 172 com.sun.tools.javac.main.JavaCompiler.instance(task.getContext()) 173 .resolveIdent(syms.unnamedModule, className).complete(); 174 } catch (BadClassFile e) { 175 return e; 176 } 177 return null; 178 } 179 180 /** 181 * Given a class file with two constant fields A and B, replaces both with a single field with 182 * B's type and A's ConstantValue attribute. 183 */ 184 private static void swapConstantValues(File file) throws Exception { 185 ClassFile cf = ClassFile.read(file); 186 Field a = cf.fields[0]; 187 Field b = cf.fields[1]; 188 Field[] fields = { 189 new Field(b.access_flags, b.name_index, b.descriptor, a.attributes), 190 }; 191 cf = new ClassFile(cf.magic, Target.JDK1_7.minorVersion, Target.JDK1_7.majorVersion, 192 cf.constant_pool, cf.access_flags, cf.this_class, cf.super_class, cf.interfaces, 193 fields, cf.methods, cf.attributes); 194 new ClassWriter().write(cf, file); 195 } 196 197 static String compile(String... args) throws Exception { 198 System.err.println("compile: " + Arrays.asList(args)); 199 StringWriter sw = new StringWriter(); 200 PrintWriter pw = new PrintWriter(sw); 201 int rc = com.sun.tools.javac.Main.compile(args, pw); 202 pw.close(); 203 String out = sw.toString(); 204 if (out.length() > 0) { 205 System.err.println(out); 206 } 207 if (rc != 0) { 208 throw new AssertionError("compilation failed, rc=" + rc); 209 } 210 return out; 211 } 212 213 static File writeFile(File dir, String path, String body) throws IOException { 214 File f = new File(dir, path); 215 f.getParentFile().mkdirs(); 216 FileWriter out = new FileWriter(f); 217 out.write(body); 218 out.close(); 219 return f; 220 } 221 222 static void assertEquals(Object expected, Object actual) { 223 Assert.check(Objects.equals(expected, actual), 224 String.format("expected: %s, but was: %s", expected, actual)); 225 } 226} 227