Home | History | Annotate | Download | only in validation
      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