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