org.jacoco.core.test.validation.StructuredLockingTest.java Source code

Java tutorial

Introduction

Here is the source code for org.jacoco.core.test.validation.StructuredLockingTest.java

Source

/*******************************************************************************
 * Copyright (c) 2009, 2015 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);

        }
    }

}