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                            try {
155                                checkClass(metaAccess, getSnippetReflection(), className);
156                            } catch (ClassNotFoundException e) {
157                                throw new AssertionError(e);
158                            }
159                        }
160                    }
161                } catch (IOException ex) {
162                    Assert.fail(ex.toString());
163                }
164            }
165        }
166    }
167
168    protected void checkClass(MetaAccessProvider metaAccess, SnippetReflectionProvider snippetReflection, String className) throws ClassNotFoundException {
169        Class<?> c = Class.forName(className, true, getClass().getClassLoader());
170        ClassfileBytecodeProvider cbp = new ClassfileBytecodeProvider(metaAccess, snippetReflection);
171        for (Method method : c.getDeclaredMethods()) {
172            checkMethod(cbp, metaAccess, method);
173        }
174    }
175
176    private static void checkMethod(ClassfileBytecodeProvider cbp, MetaAccessProvider metaAccess, Executable executable) {
177        ResolvedJavaMethod method = metaAccess.lookupJavaMethod(executable);
178        if (method.hasBytecodes()) {
179            ResolvedJavaMethodBytecode expected = new ResolvedJavaMethodBytecode(method);
180            Bytecode actual = getBytecode(cbp, method);
181            new BytecodeComparer(expected, actual).compare();
182        }
183    }
184
185    protected static Bytecode getBytecode(ClassfileBytecodeProvider cbp, ResolvedJavaMethod method) {
186        try {
187            return cbp.getBytecode(method);
188        } catch (Throwable e) {
189            throw new AssertionError(String.format("Error getting bytecode for %s", method.format("%H.%n(%p)")), e);
190        }
191    }
192
193    static class BytecodeComparer {
194
195        private Bytecode expected;
196        private Bytecode actual;
197        private ConstantPool eCp;
198        private ConstantPool aCp;
199        BytecodeStream eStream;
200        BytecodeStream aStream;
201        int bci = -1;
202
203        BytecodeComparer(Bytecode expected, Bytecode actual) {
204            this.expected = expected;
205            this.actual = actual;
206            this.eCp = expected.getConstantPool();
207            this.aCp = actual.getConstantPool();
208            Assert.assertEquals(expected.getMethod().toString(), expected.getCodeSize(), actual.getCodeSize());
209            this.eStream = new BytecodeStream(expected.getCode());
210            this.aStream = new BytecodeStream(actual.getCode());
211        }
212
213        public void compare() {
214            try {
215                compare0();
216            } catch (Throwable e) {
217                BytecodeDisassembler dis = new BytecodeDisassembler(true, false);
218                Formatter msg = new Formatter();
219                msg.format("Error comparing bytecode for %s", expected.getMethod().format("%H.%n(%p)"));
220                if (bci >= 0) {
221                    msg.format("%nexpected: %s", dis.disassemble(expected, bci, eStream.nextBCI() - 1));
222                    msg.format("%nactual:   %s", dis.disassemble(actual, bci, aStream.nextBCI() - 1));
223                }
224                throw new AssertionError(msg.toString(), e);
225            }
226        }
227
228        public void compare0() {
229            int opcode = eStream.currentBC();
230            ResolvedJavaMethod method = expected.getMethod();
231            while (opcode != Bytecodes.END) {
232                bci = eStream.currentBCI();
233                int actualOpcode = aStream.currentBC();
234                if (opcode != actualOpcode) {
235                    Assert.assertEquals(opcode, actualOpcode);
236                }
237                if (eStream.nextBCI() > bci + 1) {
238                    switch (opcode) {
239                        case BIPUSH:
240                            Assert.assertEquals(eStream.readByte(), aStream.readByte());
241                            break;
242                        case SIPUSH:
243                            Assert.assertEquals(eStream.readShort(), aStream.readShort());
244                            break;
245                        case NEW:
246                        case CHECKCAST:
247                        case INSTANCEOF:
248                        case ANEWARRAY: {
249                            ResolvedJavaType e = lookupType(eCp, eStream.readCPI(), opcode);
250                            ResolvedJavaType a = lookupType(aCp, aStream.readCPI(), opcode);
251                            assertEqualTypes(e, a);
252                            break;
253                        }
254                        case GETSTATIC:
255                        case PUTSTATIC:
256                        case GETFIELD:
257                        case PUTFIELD: {
258                            ResolvedJavaField e = lookupField(eCp, eStream.readCPI(), method, opcode);
259                            ResolvedJavaField a = lookupField(aCp, aStream.readCPI(), method, opcode);
260                            assertEqualFields(e, a);
261                            break;
262                        }
263                        case INVOKEVIRTUAL:
264                        case INVOKESPECIAL:
265                        case INVOKESTATIC: {
266                            ResolvedJavaMethod e = lookupMethod(eCp, eStream.readCPI(), opcode);
267                            ResolvedJavaMethod a = lookupMethodOrNull(aCp, aStream.readCPI(), opcode);
268                            assertEqualMethods(e, a);
269                            break;
270                        }
271                        case INVOKEINTERFACE: {
272                            ResolvedJavaMethod e = lookupMethod(eCp, eStream.readCPI(), opcode);
273                            ResolvedJavaMethod a = lookupMethod(aCp, aStream.readCPI(), opcode);
274                            assertEqualMethods(e, a);
275                            break;
276                        }
277                        case INVOKEDYNAMIC: {
278                            // INVOKEDYNAMIC is not supported by ClassfileBytecodeProvider
279                            return;
280                        }
281                        case LDC:
282                        case LDC_W:
283                        case LDC2_W: {
284                            Object e = lookupConstant(eCp, eStream.readCPI(), opcode);
285                            Object a = lookupConstant(aCp, aStream.readCPI(), opcode);
286                            assertEqualsConstants(e, a);
287                            break;
288                        }
289                        case RET:
290                        case ILOAD:
291                        case LLOAD:
292                        case FLOAD:
293                        case DLOAD:
294                        case ALOAD:
295                        case ISTORE:
296                        case LSTORE:
297                        case FSTORE:
298                        case DSTORE:
299                        case ASTORE: {
300                            Assert.assertEquals(eStream.readLocalIndex(), aStream.readLocalIndex());
301                            break;
302                        }
303                        case IFEQ:
304                        case IFNE:
305                        case IFLT:
306                        case IFGE:
307                        case IFGT:
308                        case IFLE:
309                        case IF_ICMPEQ:
310                        case IF_ICMPNE:
311                        case IF_ICMPLT:
312                        case IF_ICMPGE:
313                        case IF_ICMPGT:
314                        case IF_ICMPLE:
315                        case IF_ACMPEQ:
316                        case IF_ACMPNE:
317                        case GOTO:
318                        case JSR:
319                        case IFNULL:
320                        case IFNONNULL:
321                        case GOTO_W:
322                        case JSR_W: {
323                            Assert.assertEquals(eStream.readBranchDest(), aStream.readBranchDest());
324                            break;
325                        }
326                        case LOOKUPSWITCH:
327                        case TABLESWITCH: {
328                            BytecodeSwitch e = opcode == LOOKUPSWITCH ? new BytecodeLookupSwitch(eStream, bci) : new BytecodeTableSwitch(eStream, bci);
329                            BytecodeSwitch a = opcode == LOOKUPSWITCH ? new BytecodeLookupSwitch(aStream, bci) : new BytecodeTableSwitch(aStream, bci);
330                            Assert.assertEquals(e.numberOfCases(), a.numberOfCases());
331                            for (int i = 0; i < e.numberOfCases(); i++) {
332                                Assert.assertEquals(e.keyAt(i), a.keyAt(i));
333                                Assert.assertEquals(e.targetAt(i), a.targetAt(i));
334                            }
335                            Assert.assertEquals(e.defaultTarget(), a.defaultTarget());
336                            Assert.assertEquals(e.defaultOffset(), a.defaultOffset());
337                            break;
338                        }
339                        case NEWARRAY: {
340                            Assert.assertEquals(eStream.readLocalIndex(), aStream.readLocalIndex());
341                            break;
342                        }
343                        case MULTIANEWARRAY: {
344                            ResolvedJavaType e = lookupType(eCp, eStream.readCPI(), opcode);
345                            ResolvedJavaType a = lookupType(aCp, aStream.readCPI(), opcode);
346                            Assert.assertEquals(e, a);
347                            break;
348                        }
349                    }
350                }
351                eStream.next();
352                aStream.next();
353                opcode = eStream.currentBC();
354            }
355        }
356
357        static Object lookupConstant(ConstantPool cp, int cpi, int opcode) {
358            cp.loadReferencedType(cpi, opcode);
359            return cp.lookupConstant(cpi);
360        }
361
362        static ResolvedJavaField lookupField(ConstantPool cp, int cpi, ResolvedJavaMethod method, int opcode) {
363            cp.loadReferencedType(cpi, opcode);
364            return (ResolvedJavaField) cp.lookupField(cpi, method, opcode);
365        }
366
367        static ResolvedJavaMethod lookupMethod(ConstantPool cp, int cpi, int opcode) {
368            cp.loadReferencedType(cpi, opcode);
369            return (ResolvedJavaMethod) cp.lookupMethod(cpi, opcode);
370        }
371
372        static ResolvedJavaMethod lookupMethodOrNull(ConstantPool cp, int cpi, int opcode) {
373            try {
374                return lookupMethod(cp, cpi, opcode);
375            } catch (NoSuchMethodError e) {
376                // A method hidden to reflection
377                return null;
378            }
379        }
380
381        static ResolvedJavaType lookupType(ConstantPool cp, int cpi, int opcode) {
382            cp.loadReferencedType(cpi, opcode);
383            return (ResolvedJavaType) cp.lookupType(cpi, opcode);
384        }
385
386        static void assertEqualsConstants(Object e, Object a) {
387            if (!e.equals(a)) {
388                Assert.assertEquals(String.valueOf(e), String.valueOf(a));
389            }
390        }
391
392        static void assertEqualFields(JavaField e, JavaField a) {
393            if (!e.equals(a)) {
394                Assert.assertEquals(e.format("%H.%n %T"), a.format("%H.%n %T"));
395            }
396        }
397
398        static void assertEqualTypes(JavaType e, JavaType a) {
399            if (!e.equals(a)) {
400                Assert.assertEquals(e.toJavaName(), a.toJavaName());
401            }
402        }
403
404        static void assertEqualMethods(ResolvedJavaMethod e, ResolvedJavaMethod a) {
405            if (a != null) {
406                if (!e.equals(a)) {
407                    if (!e.equals(a)) {
408                        if (!e.getDeclaringClass().equals(a.getDeclaringClass())) {
409
410                            if (!typesAreRelated(e, a)) {
411                                throw new AssertionError(String.format("%s and %s are unrelated", a.getDeclaringClass().toJavaName(), e.getDeclaringClass().toJavaName()));
412                            }
413                        }
414                        Assert.assertEquals(e.getName(), a.getName());
415                        Assert.assertEquals(e.getSignature(), a.getSignature());
416                    } else {
417                        Assert.assertEquals(e, a);
418                    }
419                }
420            }
421        }
422
423        /**
424         * The VM can resolve references to methods not available via reflection. For example, the
425         * javap output for {@link ProfiledMethod#toString()} includes:
426         *
427         * <pre>
428         *     16: invokeinterface #40, 1 // InterfaceMethod jdk/vm/ci/meta/ResolvedJavaMethod.getName:()Ljava/lang/String;
429         * </pre>
430         *
431         * When resolving via {@code HotSpotConstantPool}, we get:
432         *
433         * <pre>
434         *     16: invokeinterface#4, 1   // jdk.vm.ci.meta.ResolvedJavaMethod.getName:()java.lang.String
435         * </pre>
436         *
437         * However resolving via {@code ClassfileConstantPool}, we get:
438         *
439         * <pre>
440         *     16: invokeinterface#40, 1  // jdk.vm.ci.meta.JavaMethod.getName:()java.lang.String
441         * </pre>
442         *
443         * since the latter relies on {@link ResolvedJavaType#getDeclaredMethods()} which only
444         * returns methods originating from class files.
445         *
446         * We accept such differences for the purpose of this test if the declaring class of two
447         * otherwise similar methods are related (i.e. one is a subclass of the other).
448         */
449        protected static boolean typesAreRelated(ResolvedJavaMethod e, ResolvedJavaMethod a) {
450            return a.getDeclaringClass().isAssignableFrom(e.getDeclaringClass()) || e.getDeclaringClass().isAssignableFrom(a.getDeclaringClass());
451        }
452    }
453}
454