blob: 9aa67a6c7ac2bf637e20328a2253547047764410 [file] [log] [blame]
/*******************************************************************************
* 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);
}
}
}