1 /*
   2  * Copyright (c) 2015, 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 package org.graalvm.compiler.core.test;
  24 
  25 import jdk.vm.ci.code.BailoutException;
  26 import jdk.vm.ci.meta.ResolvedJavaMethod;
  27 import jdk.internal.org.objectweb.asm.ClassWriter;
  28 import jdk.internal.org.objectweb.asm.Label;
  29 import jdk.internal.org.objectweb.asm.MethodVisitor;
  30 import jdk.internal.org.objectweb.asm.Opcodes;
  31 
  32 import static org.graalvm.compiler.core.common.CompilationIdentifier.INVALID_COMPILATION_ID;
  33 
  34 import org.junit.Test;
  35 
  36 import org.graalvm.compiler.java.GraphBuilderPhase;
  37 import org.graalvm.compiler.nodes.StructuredGraph;
  38 import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
  39 import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
  40 import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins;
  41 import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.Plugins;
  42 import org.graalvm.compiler.phases.OptimisticOptimizations;
  43 
  44 /**
  45  * Exercise handling of unbalanced monitor operations by the parser. Algorithmically Graal assumes
  46  * that locks are statically block structured but that isn't something enforced by the bytecodes. In
  47  * HotSpot a dataflow is performed to ensure they are properly structured and methods with
  48  * unstructured locking aren't compiled and fall back to the interpreter. Having the Graal parser
  49  * handle this directly is simplifying for targets of Graal since they don't have to provide a data
  50  * flow that checks this property.
  51  */
  52 public class UnbalancedMonitorsTest extends GraalCompilerTest implements Opcodes {
  53     private static final String CLASS_NAME = UnbalancedMonitorsTest.class.getName();
  54     private static final String INNER_CLASS_NAME = CLASS_NAME + "$UnbalancedMonitors";
  55     private static final String CLASS_NAME_INTERNAL = CLASS_NAME.replace('.', '/');
  56     private static final String INNER_CLASS_NAME_INTERNAL = INNER_CLASS_NAME.replace('.', '/');
  57 
  58     private static AsmLoader LOADER = new AsmLoader(UnbalancedMonitorsTest.class.getClassLoader());
  59 
  60     @Test
  61     public void runWrongOrder() throws Exception {
  62         checkForBailout("wrongOrder");
  63     }
  64 
  65     @Test
  66     public void runTooFewExits() throws Exception {
  67         checkForBailout("tooFewExits");
  68     }
  69 
  70     @Test
  71     public void runTooManyExits() throws Exception {
  72         checkForBailout("tooManyExits");
  73     }
  74 
  75     @Test
  76     public void runTooFewExitsExceptional() throws Exception {
  77         checkForBailout("tooFewExitsExceptional");
  78     }
  79 
  80     @Test
  81     public void runTooManyExitsExceptional() throws Exception {
  82         checkForBailout("tooManyExitsExceptional");
  83     }
  84 
  85     private void checkForBailout(String name) throws ClassNotFoundException {
  86         ResolvedJavaMethod method = getResolvedJavaMethod(LOADER.findClass(INNER_CLASS_NAME), name);
  87         try {
  88             StructuredGraph graph = new StructuredGraph(method, AllowAssumptions.NO, INVALID_COMPILATION_ID);
  89             Plugins plugins = new Plugins(new InvocationPlugins(getMetaAccess()));
  90             GraphBuilderConfiguration graphBuilderConfig = GraphBuilderConfiguration.getDefault(plugins).withEagerResolving(true);
  91             OptimisticOptimizations optimisticOpts = OptimisticOptimizations.NONE;
  92 
  93             GraphBuilderPhase.Instance graphBuilder = new GraphBuilderPhase.Instance(getMetaAccess(), getProviders().getStampProvider(), null, null, graphBuilderConfig, optimisticOpts, null);
  94             graphBuilder.apply(graph);
  95         } catch (BailoutException e) {
  96             if (e.getMessage().contains("unbalanced monitors")) {
  97                 return;
  98             }
  99             throw e;
 100         }
 101         assertTrue("should have bailed out", false);
 102     }
 103 
 104     // @formatter:off
 105     // Template class used with Bytecode Outline to generate ASM code
 106     //    public static class UnbalancedMonitors {
 107     //
 108     //        public UnbalancedMonitors() {
 109     //        }
 110     //
 111     //        public Object wrongOrder(Object a, Object b) {
 112     //            synchronized (a) {
 113     //                synchronized (b) {
 114     //                    return b;
 115     //                }
 116     //            }
 117     //        }
 118     //
 119     //        public Object tooFewExits(Object a, Object b) {
 120     //            synchronized (a) {
 121     //                synchronized (b) {
 122     //                    return b;
 123     //                }
 124     //            }
 125     //        }
 126     //
 127     //        public boolean tooFewExitsExceptional(Object a, Object b) {
 128     //            synchronized (a) {
 129     //                synchronized (b) {
 130     //                    return b.equals(a);
 131     //                }
 132     //            }
 133     //        }
 134     //    }
 135     // @formatter:on
 136 
 137     public static byte[] generateClass() {
 138 
 139         ClassWriter cw = new ClassWriter(0);
 140 
 141         cw.visit(52, ACC_SUPER | ACC_PUBLIC, INNER_CLASS_NAME_INTERNAL, null, "java/lang/Object", null);
 142 
 143         cw.visitSource("UnbalancedMonitorsTest.java", null);
 144 
 145         cw.visitInnerClass(INNER_CLASS_NAME_INTERNAL, CLASS_NAME_INTERNAL, "UnbalancedMonitors", ACC_STATIC);
 146 
 147         visitConstructor(cw);
 148         visitWrongOrder(cw);
 149         visitBlockStructured(cw, true, false);
 150         visitBlockStructured(cw, true, true);
 151         visitBlockStructured(cw, false, false);
 152         visitBlockStructured(cw, false, true);
 153         cw.visitEnd();
 154 
 155         return cw.toByteArray();
 156     }
 157 
 158     private static void visitBlockStructured(ClassWriter cw, boolean normalReturnError, boolean tooMany) {
 159         String name = (tooMany ? "tooMany" : "tooFew") + "Exits" + (normalReturnError ? "" : "Exceptional");
 160         // Generate too many or too few exits down the either the normal or exceptional return paths
 161         int exceptionalExitCount = normalReturnError ? 1 : (tooMany ? 2 : 0);
 162         int normalExitCount = normalReturnError ? (tooMany ? 2 : 0) : 1;
 163         MethodVisitor mv;
 164         mv = cw.visitMethod(ACC_PUBLIC, name, "(Ljava/lang/Object;Ljava/lang/Object;)Z", null, null);
 165         mv.visitCode();
 166         Label l0 = new Label();
 167         Label l1 = new Label();
 168         Label l2 = new Label();
 169         mv.visitTryCatchBlock(l0, l1, l2, null);
 170         Label l3 = new Label();
 171         mv.visitTryCatchBlock(l2, l3, l2, null);
 172         Label l4 = new Label();
 173         Label l5 = new Label();
 174         Label l6 = new Label();
 175         mv.visitTryCatchBlock(l4, l5, l6, null);
 176         Label l7 = new Label();
 177         mv.visitTryCatchBlock(l2, l7, l6, null);
 178         Label l8 = new Label();
 179         mv.visitLabel(l8);
 180         mv.visitVarInsn(ALOAD, 1);
 181         mv.visitInsn(DUP);
 182         mv.visitVarInsn(ASTORE, 3);
 183         mv.visitInsn(MONITORENTER);
 184         mv.visitLabel(l4);
 185         mv.visitVarInsn(ALOAD, 2);
 186         mv.visitInsn(DUP);
 187         mv.visitVarInsn(ASTORE, 4);
 188         mv.visitInsn(MONITORENTER);
 189         mv.visitLabel(l0);
 190         mv.visitVarInsn(ALOAD, 2);
 191         mv.visitVarInsn(ALOAD, 1);
 192         mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false);
 193         mv.visitVarInsn(ALOAD, 4);
 194         mv.visitInsn(MONITOREXIT);
 195         mv.visitLabel(l1);
 196         for (int i = 0; i < normalExitCount; i++) {
 197             mv.visitVarInsn(ALOAD, 3);
 198             mv.visitInsn(MONITOREXIT);
 199         }
 200         mv.visitLabel(l5);
 201         mv.visitInsn(IRETURN);
 202         mv.visitLabel(l2);
 203         mv.visitFrame(Opcodes.F_FULL, 5, new Object[]{INNER_CLASS_NAME_INTERNAL, "java/lang/Object", "java/lang/Object", "java/lang/Object",
 204                         "java/lang/Object"}, 1, new Object[]{"java/lang/Throwable"});
 205         mv.visitVarInsn(ALOAD, 4);
 206         mv.visitInsn(MONITOREXIT);
 207         mv.visitLabel(l3);
 208         mv.visitInsn(ATHROW);
 209         mv.visitLabel(l6);
 210         mv.visitFrame(Opcodes.F_FULL, 4, new Object[]{INNER_CLASS_NAME_INTERNAL, "java/lang/Object", "java/lang/Object", "java/lang/Object"}, 1,
 211                         new Object[]{"java/lang/Throwable"});
 212         for (int i = 0; i < exceptionalExitCount; i++) {
 213             mv.visitVarInsn(ALOAD, 3);
 214             mv.visitInsn(MONITOREXIT);
 215         }
 216         mv.visitLabel(l7);
 217         mv.visitInsn(ATHROW);
 218         Label l9 = new Label();
 219         mv.visitLabel(l9);
 220         mv.visitMaxs(2, 5);
 221         mv.visitEnd();
 222     }
 223 
 224     private static void visitWrongOrder(ClassWriter cw) {
 225         MethodVisitor mv;
 226         mv = cw.visitMethod(ACC_PUBLIC, "wrongOrder", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", null, null);
 227         mv.visitCode();
 228         Label l0 = new Label();
 229         Label l1 = new Label();
 230         Label l2 = new Label();
 231         mv.visitTryCatchBlock(l0, l1, l2, null);
 232         Label l3 = new Label();
 233         mv.visitTryCatchBlock(l2, l3, l2, null);
 234         Label l4 = new Label();
 235         Label l5 = new Label();
 236         Label l6 = new Label();
 237         mv.visitTryCatchBlock(l4, l5, l6, null);
 238         Label l7 = new Label();
 239         mv.visitTryCatchBlock(l2, l7, l6, null);
 240         Label l8 = new Label();
 241         mv.visitLabel(l8);
 242         mv.visitVarInsn(ALOAD, 1);
 243         mv.visitInsn(DUP);
 244         mv.visitVarInsn(ASTORE, 3);
 245         mv.visitInsn(MONITORENTER);
 246         mv.visitLabel(l4);
 247         mv.visitVarInsn(ALOAD, 2);
 248         mv.visitInsn(DUP);
 249         mv.visitVarInsn(ASTORE, 4);
 250         mv.visitInsn(MONITORENTER);
 251         mv.visitLabel(l0);
 252         mv.visitVarInsn(ALOAD, 2);
 253         mv.visitVarInsn(ALOAD, 3);
 254         mv.visitInsn(MONITOREXIT);
 255         mv.visitLabel(l1);
 256         // Swapped exit order with exit above
 257         mv.visitVarInsn(ALOAD, 4);
 258         mv.visitInsn(MONITOREXIT);
 259         mv.visitLabel(l5);
 260         mv.visitInsn(ARETURN);
 261         mv.visitLabel(l2);
 262         mv.visitFrame(Opcodes.F_FULL, 5, new Object[]{INNER_CLASS_NAME_INTERNAL, "java/lang/Object", "java/lang/Object", "java/lang/Object",
 263                         "java/lang/Object"}, 1, new Object[]{"java/lang/Throwable"});
 264         mv.visitVarInsn(ALOAD, 4);
 265         mv.visitInsn(MONITOREXIT);
 266         mv.visitLabel(l3);
 267         mv.visitInsn(ATHROW);
 268         mv.visitLabel(l6);
 269         mv.visitFrame(Opcodes.F_FULL, 4, new Object[]{INNER_CLASS_NAME_INTERNAL, "java/lang/Object", "java/lang/Object", "java/lang/Object"}, 1,
 270                         new Object[]{"java/lang/Throwable"});
 271         mv.visitVarInsn(ALOAD, 3);
 272         mv.visitInsn(MONITOREXIT);
 273         mv.visitLabel(l7);
 274         mv.visitInsn(ATHROW);
 275         Label l9 = new Label();
 276         mv.visitLabel(l9);
 277         mv.visitMaxs(2, 5);
 278         mv.visitEnd();
 279     }
 280 
 281     private static void visitConstructor(ClassWriter cw) {
 282         MethodVisitor mv;
 283         mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
 284         mv.visitCode();
 285         Label l0 = new Label();
 286         mv.visitLabel(l0);
 287         mv.visitVarInsn(ALOAD, 0);
 288         mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
 289         Label l1 = new Label();
 290         mv.visitLabel(l1);
 291         mv.visitInsn(RETURN);
 292         Label l2 = new Label();
 293         mv.visitLabel(l2);
 294         mv.visitMaxs(1, 1);
 295         mv.visitEnd();
 296     }
 297 
 298     public static class AsmLoader extends ClassLoader {
 299         Class<?> loaded;
 300 
 301         public AsmLoader(ClassLoader parent) {
 302             super(parent);
 303         }
 304 
 305         @Override
 306         protected Class<?> findClass(String name) throws ClassNotFoundException {
 307             if (name.equals(INNER_CLASS_NAME)) {
 308                 if (loaded != null) {
 309                     return loaded;
 310                 }
 311                 byte[] bytes = generateClass();
 312                 return (loaded = defineClass(name, bytes, 0, bytes.length));
 313             } else {
 314                 return super.findClass(name);
 315             }
 316         }
 317     }
 318 }