diff options
author | Marc R. Hoffmann <hoffmann@mountainminds.com> | 2015-01-04 18:27:41 +0100 |
---|---|---|
committer | Marc R. Hoffmann <hoffmann@mountainminds.com> | 2015-01-04 18:27:41 +0100 |
commit | 7946741470a5c7efc95af2079ad72d334a511b83 (patch) | |
tree | de862aec25921ed1fdd7b821425b79887307d24f | |
parent | d31ce3dcc90dead13a1ca8acb0d7a5bfe99e61d6 (diff) | |
download | jacoco-7946741470a5c7efc95af2079ad72d334a511b83.tar.gz |
Validation test for issue #265.
3 files changed, 248 insertions, 0 deletions
diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/validation/FramesTest.java b/org.jacoco.core.test/src/org/jacoco/core/test/validation/FramesTest.java index 73ebc6dc..7d8fb1b2 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/test/validation/FramesTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/test/validation/FramesTest.java @@ -33,6 +33,7 @@ import org.jacoco.core.test.validation.targets.Target08; import org.jacoco.core.test.validation.targets.Target09; import org.jacoco.core.test.validation.targets.Target10; import org.jacoco.core.test.validation.targets.Target11; +import org.jacoco.core.test.validation.targets.Target12; import org.junit.Test; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; @@ -165,4 +166,9 @@ public class FramesTest { testFrames(Target11.class); } + @Test + public void testTarget12() throws IOException { + testFrames(Target12.class); + } + } diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/validation/StructuredLockingTest.java b/org.jacoco.core.test/src/org/jacoco/core/test/validation/StructuredLockingTest.java new file mode 100644 index 00000000..f35b8ccb --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/test/validation/StructuredLockingTest.java @@ -0,0 +1,193 @@ +/******************************************************************************* + * Copyright (c) 2009, 2014 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.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(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); + + } + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/validation/targets/Target12.java b/org.jacoco.core.test/src/org/jacoco/core/test/validation/targets/Target12.java new file mode 100644 index 00000000..804b2b82 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/test/validation/targets/Target12.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2009, 2014 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.targets; + +import static org.jacoco.core.test.validation.targets.Stubs.nop; + +/** + * This target uses synchronized blocks which compile to try/catch statements. + */ +public class Target12 implements Runnable { + + public void run() { + simple(); + nested(); + } + + void simple() { + synchronized (this) { + nop(); + } + } + + void nested() { + Object lock1 = new Object(); + synchronized (lock1) { + nop(); + Object lock2 = new Object(); + synchronized (lock2) { + nop(); + } + nop(); + } + + } + + public static void main(String[] args) { + new Target12().run(); + } + +} |