| /******************************************************************************* |
| * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Marc R. Hoffmann - initial API and implementation |
| * |
| *******************************************************************************/ |
| package org.jacoco.core.test.validation; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.jacoco.core.instr.Instrumenter; |
| import org.jacoco.core.internal.Java9Support; |
| import org.jacoco.core.runtime.IRuntime; |
| import org.jacoco.core.runtime.SystemPropertiesRuntime; |
| import org.jacoco.core.test.TargetLoader; |
| import org.jacoco.core.test.validation.targets.Target12; |
| import org.junit.Test; |
| import org.objectweb.asm.ClassReader; |
| import org.objectweb.asm.Opcodes; |
| import org.objectweb.asm.tree.AbstractInsnNode; |
| import org.objectweb.asm.tree.ClassNode; |
| import org.objectweb.asm.tree.MethodNode; |
| import org.objectweb.asm.tree.TryCatchBlockNode; |
| import org.objectweb.asm.tree.VarInsnNode; |
| import org.objectweb.asm.tree.analysis.Analyzer; |
| import org.objectweb.asm.tree.analysis.AnalyzerException; |
| import org.objectweb.asm.tree.analysis.BasicInterpreter; |
| import org.objectweb.asm.tree.analysis.BasicValue; |
| import org.objectweb.asm.tree.analysis.Frame; |
| import org.objectweb.asm.tree.analysis.Interpreter; |
| |
| /** |
| * Tests that the invariants specified in chapter 2.11.10 of the JVM Spec do |
| * also hold for instrumented classes. Note that only some runtimes like Android |
| * ART do actually check these invariants. |
| * |
| * https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.11.10 |
| */ |
| public class StructuredLockingTest { |
| |
| @Test |
| public void testTarget12() throws Exception { |
| testMonitorExit(Target12.class); |
| } |
| |
| private void testMonitorExit(Class<?> target) throws Exception { |
| assertStructuredLocking(TargetLoader.getClassDataAsBytes(target)); |
| } |
| |
| private void assertStructuredLocking(byte[] source) throws Exception { |
| IRuntime runtime = new SystemPropertiesRuntime(); |
| Instrumenter instrumenter = new Instrumenter(runtime); |
| byte[] instrumented = instrumenter.instrument(source, "TestTarget"); |
| |
| ClassNode cn = new ClassNode(); |
| new ClassReader(Java9Support.downgradeIfRequired(instrumented)) |
| .accept(cn, 0); |
| for (MethodNode mn : cn.methods) { |
| assertStructuredLocking(cn.name, mn); |
| } |
| } |
| |
| private void assertStructuredLocking(String owner, MethodNode mn) |
| throws Exception { |
| Analyzer<BasicValue> analyzer = new Analyzer<BasicValue>( |
| new BasicInterpreter()) { |
| |
| @Override |
| protected Frame<BasicValue> newFrame(int nLocals, int nStack) { |
| return new LockFrame(nLocals, nStack); |
| } |
| |
| @Override |
| protected Frame<BasicValue> newFrame(Frame<? extends BasicValue> src) { |
| return new LockFrame(src); |
| } |
| }; |
| |
| Frame<BasicValue>[] frames = analyzer.analyze(owner, mn); |
| |
| // Make sure no locks are left when method exits: |
| for (int i = 0; i < frames.length; i++) { |
| AbstractInsnNode insn = mn.instructions.get(i); |
| switch (insn.getOpcode()) { |
| case Opcodes.IRETURN: |
| case Opcodes.LRETURN: |
| case Opcodes.FRETURN: |
| case Opcodes.DRETURN: |
| case Opcodes.ARETURN: |
| case Opcodes.RETURN: |
| ((LockFrame) frames[i]).assertNoLock("Exit with lock"); |
| break; |
| case Opcodes.ATHROW: |
| List<TryCatchBlockNode> handlers = analyzer.getHandlers(i); |
| if (handlers == null || handlers.isEmpty()) { |
| ((LockFrame) frames[i]).assertNoLock("Exit with lock"); |
| } |
| break; |
| } |
| } |
| |
| // Only instructions protected by a catch-all handler can hold locks: |
| for (int i = 0; i < frames.length; i++) { |
| AbstractInsnNode insn = mn.instructions.get(i); |
| if (insn.getOpcode() > 0) { |
| boolean catchAll = false; |
| List<TryCatchBlockNode> handlers = analyzer.getHandlers(i); |
| if (handlers != null) { |
| for (TryCatchBlockNode node : handlers) { |
| catchAll |= node.type == null; |
| } |
| } |
| if (!catchAll) { |
| ((LockFrame) frames[i]) |
| .assertNoLock("No handlers for insn with lock"); |
| } |
| } |
| } |
| |
| } |
| |
| /** |
| * A Frame implementation that keeps track of the locking state. It is |
| * assumed that the monitor objects are stored in local variables. |
| */ |
| private static class LockFrame extends Frame<BasicValue> { |
| |
| Set<Integer> locks; |
| |
| public LockFrame(final int nLocals, final int nStack) { |
| super(nLocals, nStack); |
| locks = new HashSet<Integer>(); |
| } |
| |
| public LockFrame(Frame<? extends BasicValue> src) { |
| super(src); |
| } |
| |
| @Override |
| public Frame<BasicValue> init(Frame<? extends BasicValue> src) { |
| locks = new HashSet<Integer>(((LockFrame) src).locks); |
| return super.init(src); |
| } |
| |
| @Override |
| public void execute(AbstractInsnNode insn, |
| Interpreter<BasicValue> interpreter) throws AnalyzerException { |
| super.execute(insn, interpreter); |
| switch (insn.getOpcode()) { |
| case Opcodes.MONITORENTER: |
| // Lock is stored in a local variable: |
| enter(((VarInsnNode) insn.getPrevious()).var); |
| break; |
| case Opcodes.MONITOREXIT: |
| // Lock is stored in a local variable: |
| exit(((VarInsnNode) insn.getPrevious()).var); |
| break; |
| } |
| } |
| |
| void enter(int lock) { |
| assertTrue("multiple ENTER for lock " + lock, |
| locks.add(Integer.valueOf(lock))); |
| } |
| |
| void exit(int lock) { |
| assertTrue("invalid EXIT for lock " + lock, |
| locks.remove(Integer.valueOf(lock))); |
| } |
| |
| @Override |
| public boolean merge(Frame<? extends BasicValue> frame, |
| Interpreter<BasicValue> interpreter) throws AnalyzerException { |
| this.locks.addAll(((LockFrame) frame).locks); |
| return super.merge(frame, interpreter); |
| } |
| |
| void assertNoLock(String message) { |
| assertEquals(message, Collections.emptySet(), locks); |
| |
| } |
| } |
| |
| } |