aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc R. Hoffmann <hoffmann@mountainminds.com>2015-01-04 18:27:41 +0100
committerMarc R. Hoffmann <hoffmann@mountainminds.com>2015-01-04 18:27:41 +0100
commit7946741470a5c7efc95af2079ad72d334a511b83 (patch)
treede862aec25921ed1fdd7b821425b79887307d24f
parentd31ce3dcc90dead13a1ca8acb0d7a5bfe99e61d6 (diff)
downloadjacoco-7946741470a5c7efc95af2079ad72d334a511b83.tar.gz
Validation test for issue #265.
-rw-r--r--org.jacoco.core.test/src/org/jacoco/core/test/validation/FramesTest.java6
-rw-r--r--org.jacoco.core.test/src/org/jacoco/core/test/validation/StructuredLockingTest.java193
-rw-r--r--org.jacoco.core.test/src/org/jacoco/core/test/validation/targets/Target12.java49
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();
+ }
+
+}