1 /******************************************************************************* 2 * Copyright (c) 2009, 2015 Mountainminds GmbH & Co. KG and Contributors 3 * All rights reserved. This program and the accompanying materials 4 * are made available under the terms of the Eclipse Public License v1.0 5 * which accompanies this distribution, and is available at 6 * http://www.eclipse.org/legal/epl-v10.html 7 * 8 * Contributors: 9 * Marc R. Hoffmann - initial API and implementation 10 * 11 *******************************************************************************/ 12 package org.jacoco.core.test.validation; 13 14 import static org.junit.Assert.assertEquals; 15 import static org.junit.Assert.assertTrue; 16 17 import java.util.Collections; 18 import java.util.HashSet; 19 import java.util.List; 20 import java.util.Set; 21 22 import org.jacoco.core.instr.Instrumenter; 23 import org.jacoco.core.runtime.IRuntime; 24 import org.jacoco.core.runtime.SystemPropertiesRuntime; 25 import org.jacoco.core.test.TargetLoader; 26 import org.jacoco.core.test.validation.targets.Target12; 27 import org.junit.Test; 28 import org.objectweb.asm.ClassReader; 29 import org.objectweb.asm.Opcodes; 30 import org.objectweb.asm.tree.AbstractInsnNode; 31 import org.objectweb.asm.tree.ClassNode; 32 import org.objectweb.asm.tree.MethodNode; 33 import org.objectweb.asm.tree.TryCatchBlockNode; 34 import org.objectweb.asm.tree.VarInsnNode; 35 import org.objectweb.asm.tree.analysis.Analyzer; 36 import org.objectweb.asm.tree.analysis.AnalyzerException; 37 import org.objectweb.asm.tree.analysis.BasicInterpreter; 38 import org.objectweb.asm.tree.analysis.BasicValue; 39 import org.objectweb.asm.tree.analysis.Frame; 40 import org.objectweb.asm.tree.analysis.Interpreter; 41 42 /** 43 * Tests that the invariants specified in chapter 2.11.10 of the JVM Spec do 44 * also hold for instrumented classes. Note that only some runtimes like Android 45 * ART do actually check these invariants. 46 * 47 * https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.11.10 48 */ 49 public class StructuredLockingTest { 50 51 @Test 52 public void testTarget12() throws Exception { 53 testMonitorExit(Target12.class); 54 } 55 56 private void testMonitorExit(Class<?> target) throws Exception { 57 assertStructuredLocking(TargetLoader.getClassDataAsBytes(target)); 58 } 59 60 private void assertStructuredLocking(byte[] source) throws Exception { 61 IRuntime runtime = new SystemPropertiesRuntime(); 62 Instrumenter instrumenter = new Instrumenter(runtime); 63 byte[] instrumented = instrumenter.instrument(source, "TestTarget"); 64 65 ClassNode cn = new ClassNode(); 66 new ClassReader(instrumented).accept(cn, 0); 67 for (MethodNode mn : cn.methods) { 68 assertStructuredLocking(cn.name, mn); 69 } 70 } 71 72 private void assertStructuredLocking(String owner, MethodNode mn) 73 throws Exception { 74 Analyzer<BasicValue> analyzer = new Analyzer<BasicValue>( 75 new BasicInterpreter()) { 76 77 @Override 78 protected Frame<BasicValue> newFrame(int nLocals, int nStack) { 79 return new LockFrame(nLocals, nStack); 80 } 81 82 @Override 83 protected Frame<BasicValue> newFrame(Frame<? extends BasicValue> src) { 84 return new LockFrame(src); 85 } 86 }; 87 88 Frame<BasicValue>[] frames = analyzer.analyze(owner, mn); 89 90 // Make sure no locks are left when method exits: 91 for (int i = 0; i < frames.length; i++) { 92 AbstractInsnNode insn = mn.instructions.get(i); 93 switch (insn.getOpcode()) { 94 case Opcodes.IRETURN: 95 case Opcodes.LRETURN: 96 case Opcodes.FRETURN: 97 case Opcodes.DRETURN: 98 case Opcodes.ARETURN: 99 case Opcodes.RETURN: 100 ((LockFrame) frames[i]).assertNoLock("Exit with lock"); 101 break; 102 case Opcodes.ATHROW: 103 List<TryCatchBlockNode> handlers = analyzer.getHandlers(i); 104 if (handlers == null || handlers.isEmpty()) { 105 ((LockFrame) frames[i]).assertNoLock("Exit with lock"); 106 } 107 break; 108 } 109 } 110 111 // Only instructions protected by a catch-all handler can hold locks: 112 for (int i = 0; i < frames.length; i++) { 113 AbstractInsnNode insn = mn.instructions.get(i); 114 if (insn.getOpcode() > 0) { 115 boolean catchAll = false; 116 List<TryCatchBlockNode> handlers = analyzer.getHandlers(i); 117 if (handlers != null) { 118 for (TryCatchBlockNode node : handlers) { 119 catchAll |= node.type == null; 120 } 121 } 122 if (!catchAll) { 123 ((LockFrame) frames[i]) 124 .assertNoLock("No handlers for insn with lock"); 125 } 126 } 127 } 128 129 } 130 131 /** 132 * A Frame implementation that keeps track of the locking state. It is 133 * assumed that the monitor objects are stored in local variables. 134 */ 135 private static class LockFrame extends Frame<BasicValue> { 136 137 Set<Integer> locks; 138 139 public LockFrame(final int nLocals, final int nStack) { 140 super(nLocals, nStack); 141 locks = new HashSet<Integer>(); 142 } 143 144 public LockFrame(Frame<? extends BasicValue> src) { 145 super(src); 146 } 147 148 @Override 149 public Frame<BasicValue> init(Frame<? extends BasicValue> src) { 150 locks = new HashSet<Integer>(((LockFrame) src).locks); 151 return super.init(src); 152 } 153 154 @Override 155 public void execute(AbstractInsnNode insn, 156 Interpreter<BasicValue> interpreter) throws AnalyzerException { 157 super.execute(insn, interpreter); 158 switch (insn.getOpcode()) { 159 case Opcodes.MONITORENTER: 160 // Lock is stored in a local variable: 161 enter(((VarInsnNode) insn.getPrevious()).var); 162 break; 163 case Opcodes.MONITOREXIT: 164 // Lock is stored in a local variable: 165 exit(((VarInsnNode) insn.getPrevious()).var); 166 break; 167 } 168 } 169 170 void enter(int lock) { 171 assertTrue("multiple ENTER for lock " + lock, 172 locks.add(Integer.valueOf(lock))); 173 } 174 175 void exit(int lock) { 176 assertTrue("invalid EXIT for lock " + lock, 177 locks.remove(Integer.valueOf(lock))); 178 } 179 180 @Override 181 public boolean merge(Frame<? extends BasicValue> frame, 182 Interpreter<BasicValue> interpreter) throws AnalyzerException { 183 this.locks.addAll(((LockFrame) frame).locks); 184 return super.merge(frame, interpreter); 185 } 186 187 void assertNoLock(String message) { 188 assertEquals(message, Collections.emptySet(), locks); 189 190 } 191 } 192 193 } 194