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 */
23package org.graalvm.compiler.replacements.test.classfile;
24
25import static org.graalvm.compiler.bytecode.Bytecodes.ALOAD;
26import static org.graalvm.compiler.bytecode.Bytecodes.ANEWARRAY;
27import static org.graalvm.compiler.bytecode.Bytecodes.ASTORE;
28import static org.graalvm.compiler.bytecode.Bytecodes.BIPUSH;
29import static org.graalvm.compiler.bytecode.Bytecodes.CHECKCAST;
30import static org.graalvm.compiler.bytecode.Bytecodes.DLOAD;
31import static org.graalvm.compiler.bytecode.Bytecodes.DSTORE;
32import static org.graalvm.compiler.bytecode.Bytecodes.FLOAD;
33import static org.graalvm.compiler.bytecode.Bytecodes.FSTORE;
34import static org.graalvm.compiler.bytecode.Bytecodes.GETFIELD;
35import static org.graalvm.compiler.bytecode.Bytecodes.GETSTATIC;
36import static org.graalvm.compiler.bytecode.Bytecodes.GOTO;
37import static org.graalvm.compiler.bytecode.Bytecodes.GOTO_W;
38import static org.graalvm.compiler.bytecode.Bytecodes.IFEQ;
39import static org.graalvm.compiler.bytecode.Bytecodes.IFGE;
40import static org.graalvm.compiler.bytecode.Bytecodes.IFGT;
41import static org.graalvm.compiler.bytecode.Bytecodes.IFLE;
42import static org.graalvm.compiler.bytecode.Bytecodes.IFLT;
43import static org.graalvm.compiler.bytecode.Bytecodes.IFNE;
44import static org.graalvm.compiler.bytecode.Bytecodes.IFNONNULL;
45import static org.graalvm.compiler.bytecode.Bytecodes.IFNULL;
46import static org.graalvm.compiler.bytecode.Bytecodes.IF_ACMPEQ;
47import static org.graalvm.compiler.bytecode.Bytecodes.IF_ACMPNE;
48import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPEQ;
49import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPGE;
50import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPGT;
51import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPLE;
52import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPLT;
53import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPNE;
54import static org.graalvm.compiler.bytecode.Bytecodes.ILOAD;
55import static org.graalvm.compiler.bytecode.Bytecodes.INSTANCEOF;
56import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEDYNAMIC;
57import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEINTERFACE;
58import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESPECIAL;
59import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESTATIC;
60import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEVIRTUAL;
61import static org.graalvm.compiler.bytecode.Bytecodes.ISTORE;
62import static org.graalvm.compiler.bytecode.Bytecodes.JSR;
63import static org.graalvm.compiler.bytecode.Bytecodes.JSR_W;
64import static org.graalvm.compiler.bytecode.Bytecodes.LDC;
65import static org.graalvm.compiler.bytecode.Bytecodes.LDC2_W;
66import static org.graalvm.compiler.bytecode.Bytecodes.LDC_W;
67import static org.graalvm.compiler.bytecode.Bytecodes.LLOAD;
68import static org.graalvm.compiler.bytecode.Bytecodes.LOOKUPSWITCH;
69import static org.graalvm.compiler.bytecode.Bytecodes.LSTORE;
70import static org.graalvm.compiler.bytecode.Bytecodes.MULTIANEWARRAY;
71import static org.graalvm.compiler.bytecode.Bytecodes.NEW;
72import static org.graalvm.compiler.bytecode.Bytecodes.NEWARRAY;
73import static org.graalvm.compiler.bytecode.Bytecodes.PUTFIELD;
74import static org.graalvm.compiler.bytecode.Bytecodes.PUTSTATIC;
75import static org.graalvm.compiler.bytecode.Bytecodes.RET;
76import static org.graalvm.compiler.bytecode.Bytecodes.SIPUSH;
77import static org.graalvm.compiler.bytecode.Bytecodes.TABLESWITCH;
78
79import java.io.File;
80import java.io.IOException;
81import java.lang.reflect.Executable;
82import java.lang.reflect.Method;
83import java.util.Enumeration;
84import java.util.Formatter;
85import java.util.zip.ZipEntry;
86import java.util.zip.ZipFile;
87
88import org.junit.Assert;
89import org.junit.Assume;
90import org.junit.Test;
91
92import org.graalvm.compiler.api.replacements.SnippetReflectionProvider;
93import org.graalvm.compiler.api.test.Graal;
94import org.graalvm.compiler.bytecode.Bytecode;
95import org.graalvm.compiler.bytecode.BytecodeDisassembler;
96import org.graalvm.compiler.bytecode.BytecodeLookupSwitch;
97import org.graalvm.compiler.bytecode.BytecodeStream;
98import org.graalvm.compiler.bytecode.BytecodeSwitch;
99import org.graalvm.compiler.bytecode.BytecodeTableSwitch;
100import org.graalvm.compiler.bytecode.Bytecodes;
101import org.graalvm.compiler.bytecode.ResolvedJavaMethodBytecode;
102import org.graalvm.compiler.core.test.GraalCompilerTest;
103import org.graalvm.compiler.phases.VerifyPhase;
104import org.graalvm.compiler.phases.util.Providers;
105import org.graalvm.compiler.replacements.classfile.ClassfileBytecode;
106import org.graalvm.compiler.replacements.classfile.ClassfileBytecodeProvider;
107import org.graalvm.compiler.runtime.RuntimeProvider;
108
109import jdk.vm.ci.meta.ConstantPool;
110import jdk.vm.ci.meta.JavaField;
111import jdk.vm.ci.meta.JavaMethodProfile.ProfiledMethod;
112import jdk.vm.ci.meta.JavaType;
113import jdk.vm.ci.meta.MetaAccessProvider;
114import jdk.vm.ci.meta.ResolvedJavaField;
115import jdk.vm.ci.meta.ResolvedJavaMethod;
116import jdk.vm.ci.meta.ResolvedJavaType;
117
118/**
119 * Tests that bytecode exposed via {@link ClassfileBytecode} objects is the same as the bytecode
120 * (modulo minor differences in constant pool resolution) obtained directly from
121 * {@link ResolvedJavaMethod} objects.
122 */
123public class ClassfileBytecodeProviderTest extends GraalCompilerTest {
124
125    private static boolean shouldProcess(String classpathEntry) {
126        if (classpathEntry.endsWith(".jar")) {
127            String name = new File(classpathEntry).getName();
128            return name.contains("jvmci") || name.contains("graal");
129        }
130        return false;
131    }
132
133    @Test
134    public void test() {
135        RuntimeProvider rt = Graal.getRequiredCapability(RuntimeProvider.class);
136        Providers providers = rt.getHostBackend().getProviders();
137        MetaAccessProvider metaAccess = providers.getMetaAccess();
138
139        Assume.assumeTrue(VerifyPhase.class.desiredAssertionStatus());
140
141        String propertyName = Java8OrEarlier ? "sun.boot.class.path" : "jdk.module.path";
142        String bootclasspath = System.getProperty(propertyName);
143        Assert.assertNotNull("Cannot find value of " + propertyName, bootclasspath);
144
145        for (String path : bootclasspath.split(File.pathSeparator)) {
146            if (shouldProcess(path)) {
147                try {
148                    final ZipFile zipFile = new ZipFile(new File(path));
149                    for (final Enumeration<? extends ZipEntry> entry = zipFile.entries(); entry.hasMoreElements();) {
150                        final ZipEntry zipEntry = entry.nextElement();
151                        String name = zipEntry.getName();
152                        if (name.endsWith(".class") && !name.equals("module-info.class")) {
153                            String className = name.substring(0, name.length() - ".class".length()).replace('/', '.');
154                            if (isInNativeImage(className)) {
155                                /*
156                                 * Native image requires non-graalsdk classes to be present in the
157                                 * classpath.
158                                 */
159                                continue;
160                            }
161                            try {
162                                checkClass(metaAccess, getSnippetReflection(), className);
163                            } catch (ClassNotFoundException e) {
164                                throw new AssertionError(e);
165                            }
166                        }
167                    }
168                } catch (IOException ex) {
169                    Assert.fail(ex.toString());
170                }
171            }
172        }
173    }
174
175    private static boolean isInNativeImage(String className) {
176        return className.startsWith("org.graalvm.nativeimage");
177    }
178
179    protected void checkClass(MetaAccessProvider metaAccess, SnippetReflectionProvider snippetReflection, String className) throws ClassNotFoundException {
180        Class<?> c = Class.forName(className, true, getClass().getClassLoader());
181        ClassfileBytecodeProvider cbp = new ClassfileBytecodeProvider(metaAccess, snippetReflection);
182        for (Method method : c.getDeclaredMethods()) {
183            checkMethod(cbp, metaAccess, method);
184        }
185    }
186
187    private static void checkMethod(ClassfileBytecodeProvider cbp, MetaAccessProvider metaAccess, Executable executable) {
188        ResolvedJavaMethod method = metaAccess.lookupJavaMethod(executable);
189        if (method.hasBytecodes()) {
190            ResolvedJavaMethodBytecode expected = new ResolvedJavaMethodBytecode(method);
191            Bytecode actual = getBytecode(cbp, method);
192            new BytecodeComparer(expected, actual).compare();
193        }
194    }
195
196    protected static Bytecode getBytecode(ClassfileBytecodeProvider cbp, ResolvedJavaMethod method) {
197        try {
198            return cbp.getBytecode(method);
199        } catch (Throwable e) {
200            throw new AssertionError(String.format("Error getting bytecode for %s", method.format("%H.%n(%p)")), e);
201        }
202    }
203
204    static class BytecodeComparer {
205
206        private Bytecode expected;
207        private Bytecode actual;
208        private ConstantPool eCp;
209        private ConstantPool aCp;
210        BytecodeStream eStream;
211        BytecodeStream aStream;
212        int bci = -1;
213
214        BytecodeComparer(Bytecode expected, Bytecode actual) {
215            this.expected = expected;
216            this.actual = actual;
217            this.eCp = expected.getConstantPool();
218            this.aCp = actual.getConstantPool();
219            Assert.assertEquals(expected.getMethod().toString(), expected.getCodeSize(), actual.getCodeSize());
220            this.eStream = new BytecodeStream(expected.getCode());
221            this.aStream = new BytecodeStream(actual.getCode());
222        }
223
224        public void compare() {
225            try {
226                compare0();
227            } catch (Throwable e) {
228                BytecodeDisassembler dis = new BytecodeDisassembler(true, false);
229                Formatter msg = new Formatter();
230                msg.format("Error comparing bytecode for %s", expected.getMethod().format("%H.%n(%p)"));
231                if (bci >= 0) {
232                    msg.format("%nexpected: %s", dis.disassemble(expected, bci, eStream.nextBCI() - 1));
233                    msg.format("%nactual:   %s", dis.disassemble(actual, bci, aStream.nextBCI() - 1));
234                }
235                throw new AssertionError(msg.toString(), e);
236            }
237        }
238
239        public void compare0() {
240            int opcode = eStream.currentBC();
241            ResolvedJavaMethod method = expected.getMethod();
242            while (opcode != Bytecodes.END) {
243                bci = eStream.currentBCI();
244                int actualOpcode = aStream.currentBC();
245                if (opcode != actualOpcode) {
246                    Assert.assertEquals(opcode, actualOpcode);
247                }
248                if (eStream.nextBCI() > bci + 1) {
249                    switch (opcode) {
250                        case BIPUSH:
251                            Assert.assertEquals(eStream.readByte(), aStream.readByte());
252                            break;
253                        case SIPUSH:
254                            Assert.assertEquals(eStream.readShort(), aStream.readShort());
255                            break;
256                        case NEW:
257                        case CHECKCAST:
258                        case INSTANCEOF:
259                        case ANEWARRAY: {
260                            ResolvedJavaType e = lookupType(eCp, eStream.readCPI(), opcode);
261                            ResolvedJavaType a = lookupType(aCp, aStream.readCPI(), opcode);
262                            assertEqualTypes(e, a);
263                            break;
264                        }
265                        case GETSTATIC:
266                        case PUTSTATIC:
267                        case GETFIELD:
268                        case PUTFIELD: {
269                            ResolvedJavaField e = lookupField(eCp, eStream.readCPI(), method, opcode);
270                            ResolvedJavaField a = lookupField(aCp, aStream.readCPI(), method, opcode);
271                            assertEqualFields(e, a);
272                            break;
273                        }
274                        case INVOKEVIRTUAL:
275                        case INVOKESPECIAL:
276                        case INVOKESTATIC: {
277                            ResolvedJavaMethod e = lookupMethod(eCp, eStream.readCPI(), opcode);
278                            ResolvedJavaMethod a = lookupMethodOrNull(aCp, aStream.readCPI(), opcode);
279                            assertEqualMethods(e, a);
280                            break;
281                        }
282                        case INVOKEINTERFACE: {
283                            ResolvedJavaMethod e = lookupMethod(eCp, eStream.readCPI(), opcode);
284                            ResolvedJavaMethod a = lookupMethod(aCp, aStream.readCPI(), opcode);
285                            assertEqualMethods(e, a);
286                            break;
287                        }
288                        case INVOKEDYNAMIC: {
289                            // INVOKEDYNAMIC is not supported by ClassfileBytecodeProvider
290                            return;
291                        }
292                        case LDC:
293                        case LDC_W:
294                        case LDC2_W: {
295                            Object e = lookupConstant(eCp, eStream.readCPI(), opcode);
296                            Object a = lookupConstant(aCp, aStream.readCPI(), opcode);
297                            assertEqualsConstants(e, a);
298                            break;
299                        }
300                        case RET:
301                        case ILOAD:
302                        case LLOAD:
303                        case FLOAD:
304                        case DLOAD:
305                        case ALOAD:
306                        case ISTORE:
307                        case LSTORE:
308                        case FSTORE:
309                        case DSTORE:
310                        case ASTORE: {
311                            Assert.assertEquals(eStream.readLocalIndex(), aStream.readLocalIndex());
312                            break;
313                        }
314                        case IFEQ:
315                        case IFNE:
316                        case IFLT:
317                        case IFGE:
318                        case IFGT:
319                        case IFLE:
320                        case IF_ICMPEQ:
321                        case IF_ICMPNE:
322                        case IF_ICMPLT:
323                        case IF_ICMPGE:
324                        case IF_ICMPGT:
325                        case IF_ICMPLE:
326                        case IF_ACMPEQ:
327                        case IF_ACMPNE:
328                        case GOTO:
329                        case JSR:
330                        case IFNULL:
331                        case IFNONNULL:
332                        case GOTO_W:
333                        case JSR_W: {
334                            Assert.assertEquals(eStream.readBranchDest(), aStream.readBranchDest());
335                            break;
336                        }
337                        case LOOKUPSWITCH:
338                        case TABLESWITCH: {
339                            BytecodeSwitch e = opcode == LOOKUPSWITCH ? new BytecodeLookupSwitch(eStream, bci) : new BytecodeTableSwitch(eStream, bci);
340                            BytecodeSwitch a = opcode == LOOKUPSWITCH ? new BytecodeLookupSwitch(aStream, bci) : new BytecodeTableSwitch(aStream, bci);
341                            Assert.assertEquals(e.numberOfCases(), a.numberOfCases());
342                            for (int i = 0; i < e.numberOfCases(); i++) {
343                                Assert.assertEquals(e.keyAt(i), a.keyAt(i));
344                                Assert.assertEquals(e.targetAt(i), a.targetAt(i));
345                            }
346                            Assert.assertEquals(e.defaultTarget(), a.defaultTarget());
347                            Assert.assertEquals(e.defaultOffset(), a.defaultOffset());
348                            break;
349                        }
350                        case NEWARRAY: {
351                            Assert.assertEquals(eStream.readLocalIndex(), aStream.readLocalIndex());
352                            break;
353                        }
354                        case MULTIANEWARRAY: {
355                            ResolvedJavaType e = lookupType(eCp, eStream.readCPI(), opcode);
356                            ResolvedJavaType a = lookupType(aCp, aStream.readCPI(), opcode);
357                            Assert.assertEquals(e, a);
358                            break;
359                        }
360                    }
361                }
362                eStream.next();
363                aStream.next();
364                opcode = eStream.currentBC();
365            }
366        }
367
368        static Object lookupConstant(ConstantPool cp, int cpi, int opcode) {
369            cp.loadReferencedType(cpi, opcode);
370            return cp.lookupConstant(cpi);
371        }
372
373        static ResolvedJavaField lookupField(ConstantPool cp, int cpi, ResolvedJavaMethod method, int opcode) {
374            cp.loadReferencedType(cpi, opcode);
375            return (ResolvedJavaField) cp.lookupField(cpi, method, opcode);
376        }
377
378        static ResolvedJavaMethod lookupMethod(ConstantPool cp, int cpi, int opcode) {
379            cp.loadReferencedType(cpi, opcode);
380            return (ResolvedJavaMethod) cp.lookupMethod(cpi, opcode);
381        }
382
383        static ResolvedJavaMethod lookupMethodOrNull(ConstantPool cp, int cpi, int opcode) {
384            try {
385                return lookupMethod(cp, cpi, opcode);
386            } catch (NoSuchMethodError e) {
387                // A method hidden to reflection
388                return null;
389            }
390        }
391
392        static ResolvedJavaType lookupType(ConstantPool cp, int cpi, int opcode) {
393            cp.loadReferencedType(cpi, opcode);
394            return (ResolvedJavaType) cp.lookupType(cpi, opcode);
395        }
396
397        static void assertEqualsConstants(Object e, Object a) {
398            if (!e.equals(a)) {
399                Assert.assertEquals(String.valueOf(e), String.valueOf(a));
400            }
401        }
402
403        static void assertEqualFields(JavaField e, JavaField a) {
404            if (!e.equals(a)) {
405                Assert.assertEquals(e.format("%H.%n %T"), a.format("%H.%n %T"));
406            }
407        }
408
409        static void assertEqualTypes(JavaType e, JavaType a) {
410            if (!e.equals(a)) {
411                Assert.assertEquals(e.toJavaName(), a.toJavaName());
412            }
413        }
414
415        static void assertEqualMethods(ResolvedJavaMethod e, ResolvedJavaMethod a) {
416            if (a != null) {
417                if (!e.equals(a)) {
418                    if (!e.equals(a)) {
419                        if (!e.getDeclaringClass().equals(a.getDeclaringClass())) {
420
421                            if (!typesAreRelated(e, a)) {
422                                throw new AssertionError(String.format("%s and %s are unrelated", a.getDeclaringClass().toJavaName(), e.getDeclaringClass().toJavaName()));
423                            }
424                        }
425                        Assert.assertEquals(e.getName(), a.getName());
426                        Assert.assertEquals(e.getSignature(), a.getSignature());
427                    } else {
428                        Assert.assertEquals(e, a);
429                    }
430                }
431            }
432        }
433
434        /**
435         * The VM can resolve references to methods not available via reflection. For example, the
436         * javap output for {@link ProfiledMethod#toString()} includes:
437         *
438         * <pre>
439         *     16: invokeinterface #40, 1 // InterfaceMethod jdk/vm/ci/meta/ResolvedJavaMethod.getName:()Ljava/lang/String;
440         * </pre>
441         *
442         * When resolving via {@code HotSpotConstantPool}, we get:
443         *
444         * <pre>
445         *     16: invokeinterface#4, 1   // jdk.vm.ci.meta.ResolvedJavaMethod.getName:()java.lang.String
446         * </pre>
447         *
448         * However resolving via {@code ClassfileConstantPool}, we get:
449         *
450         * <pre>
451         *     16: invokeinterface#40, 1  // jdk.vm.ci.meta.JavaMethod.getName:()java.lang.String
452         * </pre>
453         *
454         * since the latter relies on {@link ResolvedJavaType#getDeclaredMethods()} which only
455         * returns methods originating from class files.
456         *
457         * We accept such differences for the purpose of this test if the declaring class of two
458         * otherwise similar methods are related (i.e. one is a subclass of the other).
459         */
460        protected static boolean typesAreRelated(ResolvedJavaMethod e, ResolvedJavaMethod a) {
461            return a.getDeclaringClass().isAssignableFrom(e.getDeclaringClass()) || e.getDeclaringClass().isAssignableFrom(a.getDeclaringClass());
462        }
463    }
464}
465