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 package selectionresolution;
25 
26 import java.io.File;
27 import java.io.FileWriter;
28 import java.util.HashMap;
29 
30 /**
31  * One individual test case.  This class also defines a builder, which
32  * can be used to build up cases.
33  */
34 public class SelectionResolutionTestCase {
35 
36     public enum InvokeInstruction {
37         INVOKESTATIC,
38         INVOKESPECIAL,
39         INVOKEINTERFACE,
40         INVOKEVIRTUAL;
41     }
42 
43     /**
44      * The class data (includes interface data).
45      */
46     public final HashMap<Integer, ClassData> classdata;
47     /**
48      * The hierarchy shape.
49      */
50     public final HierarchyShape hier;
51     /**
52      * The invoke instruction to use.
53      */
54     public final InvokeInstruction invoke;
55     /**
56      * Which class is the methodref (or interface methodref).
57      */
58     public final int methodref;
59     /**
60      * Which class is the objectref.
61      */
62     public final int objectref;
63     /**
64      * Which class is the callsite (this must be a class, not an interface.
65      */
66     public final int callsite;
67     /**
68      * The expected result.
69      */
70     public final Result result;
71 
SelectionResolutionTestCase(final HashMap<Integer, ClassData> classdata, final HierarchyShape hier, final InvokeInstruction invoke, final int methodref, final int objectref, final int callsite, final int expected)72     private SelectionResolutionTestCase(final HashMap<Integer, ClassData> classdata,
73                                         final HierarchyShape hier,
74                                         final InvokeInstruction invoke,
75                                         final int methodref,
76                                         final int objectref,
77                                         final int callsite,
78                                         final int expected) {
79         this.classdata = classdata;
80         this.hier = hier;
81         this.invoke = invoke;
82         this.methodref = methodref;
83         this.objectref = objectref;
84         this.callsite = callsite;
85         this.result = Result.is(expected);
86     }
87 
SelectionResolutionTestCase(final HashMap<Integer, ClassData> classdata, final HierarchyShape hier, final InvokeInstruction invoke, final int methodref, final int objectref, final int callsite, final Result result)88     private SelectionResolutionTestCase(final HashMap<Integer, ClassData> classdata,
89                                         final HierarchyShape hier,
90                                         final InvokeInstruction invoke,
91                                         final int methodref,
92                                         final int objectref,
93                                         final int callsite,
94                                         final Result result) {
95         this.classdata = classdata;
96         this.hier = hier;
97         this.invoke = invoke;
98         this.methodref = methodref;
99         this.objectref = objectref;
100         this.callsite = callsite;
101         this.result = result;
102     }
103 
104     private static int currError = 0;
105 
dumpClasses(final ClassConstruct[] classes)106     private String dumpClasses(final ClassConstruct[] classes)
107         throws Exception {
108         final String errorDirName = "error_" + currError++;
109         final File errorDir = new File(errorDirName);
110         errorDir.mkdirs();
111         for (int i = 0; i < classes.length; i++) {
112             classes[i].writeClass(errorDir);
113         }
114         try (final FileWriter fos =
115              new FileWriter(new File(errorDir, "description.txt"))) {
116             fos.write(this.toString());
117         }
118         return errorDirName;
119     }
120 
121     /**
122      * Run this case, return an error message, or null.
123      *
124      * @return An error message, or null if the case succeeded.
125      */
run()126     public String run() {
127         /* Uncomment this line to print EVERY case */
128         //System.err.println("Running\n" + this);
129         final ClassBuilder builder =
130             new ClassBuilder(this, ClassBuilder.ExecutionMode.DIRECT);
131         try {
132             final ByteCodeClassLoader bcl = new ByteCodeClassLoader();
133             final ClassConstruct[] classes = builder.build();
134 
135             try {
136                 bcl.addClasses(classes);
137                 bcl.loadAll();
138 
139                 // Grab the callsite class.
140                 final Class testclass =
141                     bcl.findClass(builder.getCallsiteClass().getDottedName());
142 
143                 // Get the 'test' method out of it and call it.  The
144                 // return value tess which class that got selected.
145                 final java.lang.reflect.Method method =
146                     testclass.getDeclaredMethod("test");
147                 final int actual = (Integer) method.invoke(null);
148                 // Check the result.
149                 if (!result.complyWith(actual)) {
150                     final String dump = dumpClasses(classes);
151                     return "Failed:\n" + this + "\nExpected " + result + " got " + actual + "\nClasses written to " + dump;
152                 }
153             } catch (Throwable t) {
154                 // This catch block is handling exceptions that we
155                 // might expect to see.
156                 final Throwable actual = t.getCause();
157                 if (actual == null) {
158                     final String dump = dumpClasses(classes);
159                     System.err.println("Unexpected exception in test\n" + this + "\nClasses written to " + dump);
160                     throw t;
161                 } else if (result == null) {
162                     final String dump = dumpClasses(classes);
163                     return "Failed:\n" + this + "\nUnexpected exception " + actual + "\nClasses written to " + dump;
164                 } else if (!result.complyWith(actual)) {
165                     final String dump = dumpClasses(classes);
166                     return "Failed:\n" + this + "\nExpected " + this.result + " got " + actual + "\nClasses written to " + dump;
167                 }
168             }
169         } catch(Throwable e) {
170             throw new RuntimeException(e);
171         }
172         return null;
173     }
174 
addPackage(final StringBuilder sb, final ClassData cd)175     private static void addPackage(final StringBuilder sb,
176                                   final ClassData cd) {
177         switch (cd.packageId) {
178         case SAME: sb.append("Same."); break;
179         case DIFFERENT: sb.append("Different."); break;
180         case OTHER: sb.append("Other."); break;
181         case PLACEHOLDER: sb.append("_."); break;
182         default: throw new RuntimeException("Impossible case");
183         }
184     }
185 
toString()186     public String toString() {
187         final StringBuilder sb = new StringBuilder();
188         //sb.append("hierarchy:\n" + hier + "\n");
189         sb.append("invoke:    " + invoke + "\n");
190         if (methodref != -1) {
191             if (hier.isClass(methodref)) {
192                 sb.append("methodref: C" + methodref + "\n");
193             } else {
194                 sb.append("methodref: I" + methodref + "\n");
195             }
196         }
197         if (objectref != -1) {
198             if (hier.isClass(objectref)) {
199                 sb.append("objectref: C" + objectref + "\n");
200             } else {
201                 sb.append("objectref: I" + objectref + "\n");
202             }
203         }
204         if (callsite != -1) {
205             if (hier.isClass(callsite)) {
206                 sb.append("callsite: C" + callsite + "\n");
207             } else {
208                 sb.append("callsite: I" + callsite + "\n");
209             }
210         }
211         sb.append("result: " + result + "\n");
212         sb.append("classes:\n\n");
213 
214         for(int i = 0; classdata.containsKey(i); i++) {
215             final ClassData cd = classdata.get(i);
216 
217             if (hier.isClass(i)) {
218                 sb.append("class ");
219                 addPackage(sb, cd);
220                 sb.append("C" + i);
221             } else {
222                 sb.append("interface ");
223                 addPackage(sb, cd);
224                 sb.append("I" + i);
225             }
226 
227             boolean first = true;
228             for(final int j : hier.classes()) {
229                 if (hier.inherits(i, j)) {
230                     if (first) {
231                         sb.append(" extends C" + j);
232                     } else {
233                         sb.append(", C" + j);
234                     }
235                 }
236             }
237 
238             first = true;
239             for(final int j : hier.interfaces()) {
240                 if (hier.inherits(i, j)) {
241                     if (first) {
242                         sb.append(" implements I" + j);
243                     } else {
244                         sb.append(", I" + j);
245                     }
246                 }
247             }
248 
249             sb.append(cd);
250         }
251 
252         return sb.toString();
253     }
254 
255     /**
256      * A builder, facilitating building up test cases.
257      */
258     public static class Builder {
259         /**
260          * A map from class (or interface) id's to ClassDatas
261          */
262         public final HashMap<Integer, ClassData> classdata;
263         /**
264          * The hierarchy shape.
265          */
266         public final HierarchyShape hier;
267         /**
268          * Which invoke instruction to use.
269          */
270         public InvokeInstruction invoke;
271         /**
272          * The id of the methodref (or interface methodref).
273          */
274         public int methodref = -1;
275         /**
276          * The id of the object ref.  Note that for the generator
277          * framework to work, this must be set to something.  If an
278          * objectref isn't used, just set it to the methodref.
279          */
280         public int objectref = -1;
281         /**
282          * The id of the callsite.
283          */
284         public int callsite = -1;
285         /**
286          * The id of the expected result.  This is used to store the
287          * expected resolution result.
288          */
289         public int expected;
290         /**
291          * The expected result.  This needs to be set before the final
292          * test case is built.
293          */
294         public Result result;
295 
296         /**
297          * Create an empty Builder object.
298          */
Builder()299         public Builder() {
300             classdata = new HashMap<>();
301             hier = new HierarchyShape();
302         }
303 
Builder(final HashMap<Integer, ClassData> classdata, final HierarchyShape hier, final InvokeInstruction invoke, final int methodref, final int objectref, final int callsite, final int expected, final Result result)304         private Builder(final HashMap<Integer, ClassData> classdata,
305                         final HierarchyShape hier,
306                         final InvokeInstruction invoke,
307                         final int methodref,
308                         final int objectref,
309                         final int callsite,
310                         final int expected,
311                         final Result result) {
312             this.classdata = classdata;
313             this.hier = hier;
314             this.invoke = invoke;
315             this.methodref = methodref;
316             this.objectref = objectref;
317             this.callsite = callsite;
318             this.expected = expected;
319             this.result = result;
320         }
321 
Builder(final Builder other)322         private Builder(final Builder other) {
323             this((HashMap<Integer, ClassData>) other.classdata.clone(),
324                  other.hier.copy(), other.invoke, other.methodref, other.objectref,
325                  other.callsite, other.expected, other.result);
326         }
327 
build()328         public SelectionResolutionTestCase build() {
329             if (result != null) {
330                 return new SelectionResolutionTestCase(classdata, hier, invoke,
331                                                        methodref, objectref,
332                                                        callsite, result);
333             } else {
334                 return new SelectionResolutionTestCase(classdata, hier, invoke,
335                                                        methodref, objectref,
336                                                        callsite, expected);
337             }
338         }
339 
340         /**
341          * Set the expected result.
342          */
setResult(final Result result)343         public void setResult(final Result result) {
344             this.result = result;
345         }
346 
347         /**
348          * Add a class, and return its id.
349          *
350          * @return The new class' id.
351          */
addClass(final ClassData data)352         public int addClass(final ClassData data) {
353             final int id = hier.addClass();
354             classdata.put(id, data);
355             return id;
356         }
357 
358         /**
359          * Add an interface, and return its id.
360          *
361          * @return The new class' id.
362          */
addInterface(final ClassData data)363         public int addInterface(final ClassData data) {
364             final int id = hier.addInterface();
365             classdata.put(id, data);
366             return id;
367         }
368 
369         /**
370          * Make a copy of this builder.
371          */
copy()372         public Builder copy() {
373             return new Builder(this);
374         }
375 
toString()376         public String toString() {
377             final StringBuilder sb = new StringBuilder();
378             //sb.append("hierarchy:\n" + hier + "\n");
379             sb.append("invoke:    " + invoke + "\n");
380             if (methodref != -1) {
381                 if (hier.isClass(methodref)) {
382                     sb.append("methodref: C" + methodref + "\n");
383                 } else {
384                     sb.append("methodref: I" + methodref + "\n");
385                 }
386             }
387             if (objectref != -1) {
388                 if (hier.isClass(objectref)) {
389                     sb.append("objectref: C" + objectref + "\n");
390                 } else {
391                     sb.append("objectref: I" + objectref + "\n");
392                 }
393             }
394             if (callsite != -1) {
395                 if (hier.isClass(callsite)) {
396                     sb.append("callsite: C" + callsite + "\n");
397                 } else {
398                     sb.append("callsite: I" + callsite + "\n");
399                 }
400             }
401             if (expected != -1) {
402                 if (hier.isClass(expected)) {
403                     sb.append("expected: C" + expected + "\n");
404                 } else {
405                     sb.append("expected: I" + expected + "\n");
406                 }
407             }
408             sb.append("result: " + result + "\n");
409             sb.append("classes:\n\n");
410 
411             for(int i = 0; classdata.containsKey(i); i++) {
412                 final ClassData cd = classdata.get(i);
413 
414                 if (hier.isClass(i)) {
415                     sb.append("class ");
416                     addPackage(sb, cd);
417                     sb.append("C" + i);
418                 } else {
419                     sb.append("interface ");
420                     addPackage(sb, cd);
421                     sb.append("I" + i);
422                 }
423 
424                 boolean first = true;
425                 for(final int j : hier.classes()) {
426                     if (hier.inherits(i, j)) {
427                         if (first) {
428                             sb.append(" extends C" + j);
429                         } else {
430                             sb.append(", C" + j);
431                         }
432                     }
433                 }
434 
435                 first = true;
436                 for(final int j : hier.interfaces()) {
437                     if (hier.inherits(i, j)) {
438                         if (first) {
439                             sb.append(" implements I" + j);
440                         } else {
441                             sb.append(", I" + j);
442                         }
443                     }
444                 }
445 
446                 sb.append(cd);
447             }
448 
449             return sb.toString();
450         }
451     }
452 }
453