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