Sat, 29 Dec 2012 17:33:17 -0800
8004727: Add compiler support for parameter reflection
Reviewed-by: jjg
Contributed-by: eric.mccorkle@oracle.com
1 /*
2 * Copyright (c) 2012, 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 */
24 /*
25 * @test
26 * @summary javac should generate method parameters correctly.
27 */
28 // key: opt.arg.parameters
29 import com.sun.tools.classfile.*;
30 import com.sun.tools.javac.file.JavacFileManager;
31 import com.sun.tools.javac.main.Main;
32 import com.sun.tools.javac.util.Context;
33 import com.sun.tools.javac.util.Name;
34 import com.sun.tools.javac.util.Names;
35 import java.io.*;
36 import javax.lang.model.element.*;
37 import java.util.*;
39 public class MethodParameters {
41 static final String Foo_name = "Foo";
42 static final String Foo_contents =
43 "public class Foo {\n" +
44 " Foo() {}\n" +
45 " void foo0() {}\n" +
46 " void foo2(int j, int k) {}\n" +
47 "}";
48 static final String Bar_name = "Bar";
49 static final String Bar_contents =
50 "public class Bar {\n" +
51 " Bar(int i) {}" +
52 " Foo foo() { return new Foo(); }\n" +
53 "}";
54 static final String Baz_name = "Baz";
55 static final String Baz_contents =
56 "public class Baz {\n" +
57 " int baz;" +
58 " Baz(int i) {}" +
59 "}";
60 static final String Qux_name = "Qux";
61 static final String Qux_contents =
62 "public class Qux extends Baz {\n" +
63 " Qux(int i) { super(i); }" +
64 "}";
65 static final File classesdir = new File("methodparameters");
67 public static void main(String... args) throws Exception {
68 new MethodParameters().run();
69 }
71 void run() throws Exception {
72 classesdir.mkdir();
73 final File Foo_java =
74 writeFile(classesdir, Foo_name + ".java", Foo_contents);
75 final File Bar_java =
76 writeFile(classesdir, Bar_name + ".java", Bar_contents);
77 final File Baz_java =
78 writeFile(classesdir, Baz_name + ".java", Baz_contents);
79 System.err.println("Test compile with -parameter");
80 compile("-parameters", "-d", classesdir.getPath(), Foo_java.getPath());
81 // First test: make sure javac doesn't choke to death on
82 // MethodParameter attributes
83 System.err.println("Test compile with classfile containing MethodParameter attributes");
84 compile("-parameters", "-d", classesdir.getPath(),
85 "-cp", classesdir.getPath(), Bar_java.getPath());
86 System.err.println("Examine class foo");
87 checkFoo();
88 checkBar();
89 System.err.println("Test debug information conflict");
90 compile("-g", "-parameters", "-d", classesdir.getPath(),
91 "-cp", classesdir.getPath(), Baz_java.getPath());
92 System.err.println("Introducing debug information conflict");
93 Baz_java.delete();
94 modifyBaz(false);
95 System.err.println("Checking language model");
96 inspectBaz();
97 System.err.println("Permuting attributes");
98 modifyBaz(true);
99 System.err.println("Checking language model");
100 inspectBaz();
102 if(0 != errors)
103 throw new Exception("MethodParameters test failed with " +
104 errors + " errors");
105 }
107 void inspectBaz() throws Exception {
108 final File Qux_java =
109 writeFile(classesdir, Qux_name + ".java", Qux_contents);
110 final String[] args = { "-XDsave-parameter-names", "-d",
111 classesdir.getPath(),
112 "-cp", classesdir.getPath(),
113 Qux_java.getPath() };
114 final StringWriter sw = new StringWriter();
115 final PrintWriter pw = new PrintWriter(sw);
117 // We need to be able to crack open javac and look at its data
118 // structures. We'll rig up a compiler instance, but keep its
119 // Context, thus allowing us to get at the ClassReader.
120 Context context = new Context();
121 Main comp = new Main("javac", pw);
122 JavacFileManager.preRegister(context);
124 // Compile Qux, which uses Baz.
125 comp.compile(args, context);
126 pw.close();
127 final String out = sw.toString();
128 if (out.length() > 0)
129 System.err.println(out);
131 // Now get the class reader, construct a name for Baz, and load it.
132 com.sun.tools.javac.jvm.ClassReader cr =
133 com.sun.tools.javac.jvm.ClassReader.instance(context);
134 Name name = Names.instance(context).fromString(Baz_name);
136 // Now walk down the language model and check the name of the
137 // parameter.
138 final Element baz = cr.loadClass(name);
139 for (Element e : baz.getEnclosedElements()) {
140 if (e instanceof ExecutableElement) {
141 final ExecutableElement ee = (ExecutableElement) e;
142 final List<? extends VariableElement> params =
143 ee.getParameters();
144 if (1 != params.size())
145 throw new Exception("Classfile Baz badly formed: wrong number of methods");
146 final VariableElement param = params.get(0);
147 if (!param.getSimpleName().contentEquals("baz")) {
148 errors++;
149 System.err.println("javac did not correctly resolve the metadata conflict, parameter's name reads as " + param.getSimpleName());
150 } else
151 System.err.println("javac did correctly resolve the metadata conflict");
152 }
153 }
154 }
156 void modifyBaz(boolean flip) throws Exception {
157 final File Baz_class = new File(classesdir, Baz_name + ".class");
158 final ClassFile baz = ClassFile.read(Baz_class);
159 final int ind = baz.constant_pool.getUTF8Index("baz");
160 MethodParameters_attribute mpattr = null;
161 int mpind = 0;
162 Code_attribute cattr = null;
163 int cind = 0;
165 // Find the indexes of the MethodParameters and the Code attributes
166 if (baz.methods.length != 1)
167 throw new Exception("Classfile Baz badly formed: wrong number of methods");
168 if (!baz.methods[0].getName(baz.constant_pool).equals("<init>"))
169 throw new Exception("Classfile Baz badly formed: method has name " +
170 baz.methods[0].getName(baz.constant_pool));
171 for (int i = 0; i < baz.methods[0].attributes.attrs.length; i++) {
172 if (baz.methods[0].attributes.attrs[i] instanceof
173 MethodParameters_attribute) {
174 mpattr = (MethodParameters_attribute)
175 baz.methods[0].attributes.attrs[i];
176 mpind = i;
177 } else if (baz.methods[0].attributes.attrs[i] instanceof
178 Code_attribute) {
179 cattr = (Code_attribute) baz.methods[0].attributes.attrs[i];
180 cind = i;
181 }
182 }
183 if (null == mpattr)
184 throw new Exception("Classfile Baz badly formed: no method parameters info");
185 if (null == cattr)
186 throw new Exception("Classfile Baz badly formed: no local variable table");
188 int flags = mpattr.method_parameter_table[0].flags;
190 // Alter the MethodParameters attribute, changing the name of
191 // the parameter from i to baz. This requires Black Magic...
192 //
193 // The (well-designed) classfile library (correctly) does not
194 // allow us to mess around with the attribute data structures,
195 // or arbitrarily generate new ones.
196 //
197 // Instead, we install a new subclass of Attribute that
198 // hijacks the Visitor pattern and outputs the sequence of
199 // bytes that we want. This only works in this particular
200 // instance, because we know we'll only every see one kind of
201 // visitor.
202 //
203 // If anyone ever changes the makeup of the Baz class, or
204 // tries to install some kind of visitor that gets run prior
205 // to serialization, this will break.
206 baz.methods[0].attributes.attrs[mpind] =
207 new Attribute(mpattr.attribute_name_index,
208 mpattr.attribute_length) {
209 public <R, D> R accept(Visitor<R, D> visitor, D data) {
210 if (data instanceof ByteArrayOutputStream) {
211 ByteArrayOutputStream out =
212 (ByteArrayOutputStream) data;
213 out.write(1);
214 out.write((ind >> 8) & 0xff);
215 out.write(ind & 0xff);
216 out.write((flags >> 24) & 0xff);
217 out.write((flags >> 16) & 0xff);
218 out.write((flags >> 8) & 0xff);
219 out.write(flags & 0xff);
220 } else
221 throw new RuntimeException("Output stream is of type " + data.getClass() + ", which is not handled by this test. Update the test and it should work.");
222 return null;
223 }
224 };
226 // Flip the code and method attributes. This is for checking
227 // that order doesn't matter.
228 if (flip) {
229 baz.methods[0].attributes.attrs[mpind] = cattr;
230 baz.methods[0].attributes.attrs[cind] = mpattr;
231 }
233 new ClassWriter().write(baz, Baz_class);
234 }
236 // Run a bunch of structural tests on foo to make sure it looks right.
237 void checkFoo() throws Exception {
238 final File Foo_class = new File(classesdir, Foo_name + ".class");
239 final ClassFile foo = ClassFile.read(Foo_class);
240 for (int i = 0; i < foo.methods.length; i++) {
241 System.err.println("Examine method Foo." + foo.methods[i].getName(foo.constant_pool));
242 if (foo.methods[i].getName(foo.constant_pool).equals("foo2")) {
243 for (int j = 0; j < foo.methods[i].attributes.attrs.length; j++)
244 if (foo.methods[i].attributes.attrs[j] instanceof
245 MethodParameters_attribute) {
246 MethodParameters_attribute mp =
247 (MethodParameters_attribute)
248 foo.methods[i].attributes.attrs[j];
249 System.err.println("Foo.foo2 should have 2 parameters: j and k");
250 if (2 != mp.method_parameter_table_length)
251 error("expected 2 method parameter entries in foo2, got " +
252 mp.method_parameter_table_length);
253 else if (!foo.constant_pool.getUTF8Value(mp.method_parameter_table[0].name_index).equals("j"))
254 error("expected first parameter to foo2 to be \"j\", got \"" +
255 foo.constant_pool.getUTF8Value(mp.method_parameter_table[0].name_index) +
256 "\" instead");
257 else if (!foo.constant_pool.getUTF8Value(mp.method_parameter_table[1].name_index).equals("k"))
258 error("expected first parameter to foo2 to be \"k\", got \"" +
259 foo.constant_pool.getUTF8Value(mp.method_parameter_table[1].name_index) +
260 "\" instead");
261 }
262 }
263 else if (foo.methods[i].getName(foo.constant_pool).equals("<init>")) {
264 for (int j = 0; j < foo.methods[i].attributes.attrs.length; j++) {
265 if (foo.methods[i].attributes.attrs[j] instanceof
266 MethodParameters_attribute)
267 error("Zero-argument constructor shouldn't have MethodParameters");
268 }
269 }
270 else if (foo.methods[i].getName(foo.constant_pool).equals("foo0")) {
271 for (int j = 0; j < foo.methods[i].attributes.attrs.length; j++)
272 if (foo.methods[i].attributes.attrs[j] instanceof
273 MethodParameters_attribute)
274 error("Zero-argument method shouldn't have MethodParameters");
275 }
276 else
277 error("Unknown method " + foo.methods[i].getName(foo.constant_pool) + " showed up in class Foo");
278 }
279 }
281 // Run a bunch of structural tests on Bar to make sure it looks right.
282 void checkBar() throws Exception {
283 final File Bar_class = new File(classesdir, Bar_name + ".class");
284 final ClassFile bar = ClassFile.read(Bar_class);
285 for (int i = 0; i < bar.methods.length; i++) {
286 System.err.println("Examine method Bar." + bar.methods[i].getName(bar.constant_pool));
287 if (bar.methods[i].getName(bar.constant_pool).equals("<init>")) {
288 for (int j = 0; j < bar.methods[i].attributes.attrs.length; j++)
289 if (bar.methods[i].attributes.attrs[j] instanceof
290 MethodParameters_attribute) {
291 MethodParameters_attribute mp =
292 (MethodParameters_attribute)
293 bar.methods[i].attributes.attrs[j];
294 System.err.println("Bar constructor should have 1 parameter: i");
295 if (1 != mp.method_parameter_table_length)
296 error("expected 1 method parameter entries in constructor, got " +
297 mp.method_parameter_table_length);
298 else if (!bar.constant_pool.getUTF8Value(mp.method_parameter_table[0].name_index).equals("i"))
299 error("expected first parameter to foo2 to be \"i\", got \"" +
300 bar.constant_pool.getUTF8Value(mp.method_parameter_table[0].name_index) +
301 "\" instead");
302 }
303 }
304 else if (bar.methods[i].getName(bar.constant_pool).equals("foo")) {
305 for (int j = 0; j < bar.methods[i].attributes.attrs.length; j++) {
306 if (bar.methods[i].attributes.attrs[j] instanceof
307 MethodParameters_attribute)
308 error("Zero-argument constructor shouldn't have MethodParameters");
309 }
310 }
311 }
312 }
314 String compile(String... args) throws Exception {
315 System.err.println("compile: " + Arrays.asList(args));
316 StringWriter sw = new StringWriter();
317 PrintWriter pw = new PrintWriter(sw);
318 int rc = com.sun.tools.javac.Main.compile(args, pw);
319 pw.close();
320 String out = sw.toString();
321 if (out.length() > 0)
322 System.err.println(out);
323 if (rc != 0)
324 error("compilation failed, rc=" + rc);
325 return out;
326 }
328 File writeFile(File dir, String path, String body) throws IOException {
329 File f = new File(dir, path);
330 f.getParentFile().mkdirs();
331 FileWriter out = new FileWriter(f);
332 out.write(body);
333 out.close();
334 return f;
335 }
337 void error(String msg) {
338 System.err.println("Error: " + msg);
339 errors++;
340 }
342 int errors;
343 }