All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oracle.truffle.tools.coverage.CoverageTracker Maven / Gradle / Ivy

There is a newer version: 24.1.1
Show newest version
/*
 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.oracle.truffle.tools.coverage;

import static com.oracle.truffle.api.instrumentation.TruffleInstrument.Env;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.instrumentation.EventBinding;
import com.oracle.truffle.api.instrumentation.EventContext;
import com.oracle.truffle.api.instrumentation.ExecutionEventNode;
import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory;
import com.oracle.truffle.api.instrumentation.Instrumenter;
import com.oracle.truffle.api.instrumentation.LoadSourceSectionEvent;
import com.oracle.truffle.api.instrumentation.LoadSourceSectionListener;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.tools.coverage.impl.CoverageInstrument;

public final class CoverageTracker implements AutoCloseable {

    private static final SourceSectionFilter DEFAULT_FILTER = SourceSectionFilter.newBuilder().includeInternal(false).build();

    static {
        CoverageInstrument.setFactory(new Function() {
            @Override
            public CoverageTracker apply(Env env) {
                return new CoverageTracker(env);
            }

        });
    }

    private final List coverageNodes = new ArrayList<>();
    private final List loadedRoots = new ArrayList<>();
    private final List loadedStatements = new ArrayList<>();
    private final Env env;
    private boolean tracking;
    private boolean closed;
    private EventBinding loadedRootsBinding;
    private EventBinding coveredBinding;
    private EventBinding loadedStatementBinding;
    private Assumption noReset;

    private CoverageTracker(Env env) {
        this.env = env;
    }

    private static SourceCoverage[] sourceCoverage(Map> mapping) {
        SourceCoverage[] coverage = new SourceCoverage[mapping.size()];
        int i = 0;
        for (Map.Entry> entry : mapping.entrySet()) {
            coverage[i++] = new SourceCoverage(entry.getKey(), rootCoverage(entry.getValue()));
        }
        return coverage;
    }

    private static RootCoverage[] rootCoverage(Map perRootData) {
        RootCoverage[] rootCoverage = new RootCoverage[perRootData.size()];
        int i = 0;
        for (Map.Entry entry : perRootData.entrySet()) {
            final RootData rootData = entry.getValue();
            rootCoverage[i++] = new RootCoverage(sectionCoverage(rootData),
                            rootData.covered, rootData.count, rootData.sourceSection, rootData.name);
        }
        return rootCoverage;
    }

    private static SectionCoverage[] sectionCoverage(RootData rootData) {
        final Set loadedStatements = rootData.loadedStatements;
        SectionCoverage[] sectionCoverage = new SectionCoverage[loadedStatements.size()];
        int i = 0;
        for (SourceSection statement : loadedStatements) {
            final Long count = rootData.coveredStatements.get(statement);
            sectionCoverage[i++] = new SectionCoverage(statement, count != null, count == null ? -1 : count);
        }
        return sectionCoverage;
    }

    private static AbstractCoverageNode makeCoverageNode(EventContext context, Config config, Assumption noReset) {
        final boolean isRoot = context.hasTag(StandardTags.RootTag.class);
        final boolean isStatement = context.hasTag(StandardTags.StatementTag.class);
        if (config.count) {
            return new CountingCoverageNode(context.getInstrumentedSourceSection(), context.getInstrumentedNode(), isRoot, isStatement);
        } else {
            return new BooleanCoverageNode(context.getInstrumentedSourceSection(), context.getInstrumentedNode(), isRoot, isStatement, noReset);
        }
    }

    private static long getCount(AbstractCoverageNode coverageNode) {
        return coverageNode instanceof CountingCoverageNode ? ((CountingCoverageNode) coverageNode).getCount() : -1;
    }

    /**
     * Start coverage tracking with the given config.
     * 
     * @param config The configuration for the coverage tracking.
     * @throws IllegalStateException if the tracker is {@link CoverageTracker#close() closed} or
     *             already started.
     * @since 19.3.0
     */
    public synchronized void start(Config config) {
        if (closed) {
            throw new IllegalStateException("Coverage Tracker is closed");
        }
        if (tracking) {
            throw new IllegalStateException("Coverage Tracker is already tracking");
        }
        clearData();
        tracking = true;
        noReset = Truffle.getRuntime().createAssumption("No reset assumption");
        final Instrumenter instrumenter = env.getInstrumenter();
        instrument(config, instrumenter);
    }

    private synchronized void clearData() {
        this.loadedRoots.clear();
        this.loadedStatements.clear();
        this.coverageNodes.clear();
    }

    /**
     * Stop tracking coverage.
     * 
     * @throws IllegalStateException if called on a tracker that has not been
     *             {@link CoverageTracker#start(Config) started}
     * @since 19.3.0
     */
    public synchronized void end() {
        if (!tracking) {
            throw new IllegalStateException("Coverage tracker is not tracking");
        }
        tracking = false;
        disposeBindings();
    }

    /**
     * @return the coverage gathered thus far.
     * @since 19.3.0
     */
    public synchronized SourceCoverage[] getCoverage() {
        return sourceCoverage(mapping(false));
    }

    /**
     * Destructive read of the coverage data thus far (i.e. since beginning execution or or last
     * reset)
     * 
     * @return the coverage gathered since last reset or beginning of execution.
     * @since 20.3.0
     */
    public synchronized SourceCoverage[] resetCoverage() {
        SourceCoverage[] coverages = sourceCoverage(mapping(true));
        noReset.invalidate();
        return coverages;
    }

    private Map> mapping(boolean reset) {
        Map> sourceCoverage = new HashMap<>();
        processLoaded(sourceCoverage);
        processCovered(sourceCoverage, reset);
        return sourceCoverage;
    }

    private void processLoaded(Map> sourceCoverage) {
        processLoadedRoots(sourceCoverage);
        processLoadedSections(sourceCoverage);
    }

    private void processLoadedSections(Map> sourceCoverage) {
        for (LoadSourceSectionEvent loadedEvent : loadedStatements) {
            final SourceSection section = loadedEvent.getSourceSection();
            final Source source = section.getSource();
            final Node node = loadedEvent.getNode();
            final RootNode rootNode = node.getRootNode();
            final Map perSourceData = sourceCoverage.computeIfAbsent(source, s -> new HashMap<>());
            final RootData rootData = perSourceData.computeIfAbsent(rootNode.getSourceSection(), s -> new RootData(s, rootNode.getName()));
            rootData.loadedStatements.add(section);
        }
    }

    private void processLoadedRoots(Map> sourceCoverage) {
        for (LoadSourceSectionEvent loadedEvent : loadedRoots) {
            final SourceSection section = loadedEvent.getSourceSection();
            final Source source = section.getSource();
            final Map perRootData = sourceCoverage.computeIfAbsent(source, s -> new HashMap<>());
            final Node node = loadedEvent.getNode();
            final RootNode rootNode = node.getRootNode();
            if (rootNode == null) {
                continue;
            }
            perRootData.put(rootNode.getSourceSection(), new RootData(section, rootNode.getName()));
        }
    }

    private void processCovered(Map> mapping, boolean reset) {
        for (AbstractCoverageNode coverageNode : coverageNodes) {
            if (!coverageNode.isCovered()) {
                continue;
            }
            final RootNode rootNode = coverageNode.instrumentedNode.getRootNode();
            if (rootNode == null) {
                continue;
            }
            final SourceSection section = coverageNode.sourceSection;
            final RootData rootData = mapping.computeIfAbsent(section.getSource(), source -> new HashMap<>()).computeIfAbsent(rootNode.getSourceSection(),
                            sourceSection -> new RootData(sourceSection, rootNode.getName()));
            final long count = getCount(coverageNode);
            if (coverageNode.isRoot && coverageNode.isCovered()) {
                rootData.covered = true;
                rootData.count = count;
            } else if (coverageNode.isStatement) {
                rootData.coveredStatements.put(section, count);
            }
            if (reset) {
                coverageNode.reset();
            }
        }
    }

    /**
     * Closes the CoverageTracker. This makes it unusable further.
     * 
     * @since 19.3.0
     */
    @Override
    public synchronized void close() {
        closed = true;
        if (tracking) {
            end();
        }
    }

    private void instrument(Config config, Instrumenter instrumenter) {
        SourceSectionFilter f = config.sourceSectionFilter;
        if (f == null) {
            f = DEFAULT_FILTER;
        }
        instrumentLoadedRoots(instrumenter, f);
        instrumentLoadedStatements(instrumenter, f);
        instrumentExecution(config, instrumenter, f);

    }

    private void instrumentExecution(Config config, Instrumenter instrumenter, SourceSectionFilter f) {
        final SourceSectionFilter filter = SourceSectionFilter.newBuilder().tagIs(StandardTags.RootTag.class, StandardTags.StatementTag.class).and(f).build();
        coveredBinding = instrumenter.attachExecutionEventFactory(filter, new ExecutionEventNodeFactory() {
            @Override
            public ExecutionEventNode create(EventContext context) {
                final AbstractCoverageNode coverageNode = makeCoverageNode(context, config, noReset);
                addCoverageNode(coverageNode);
                return coverageNode;
            }
        });
    }

    private synchronized void addCoverageNode(AbstractCoverageNode coverageNode) {
        coverageNodes.add(coverageNode);
    }

    private void instrumentLoadedStatements(Instrumenter instrumenter, SourceSectionFilter f) {
        final SourceSectionFilter statementFilter = SourceSectionFilter.newBuilder().tagIs(StandardTags.StatementTag.class).and(f).build();
        loadedStatementBinding = instrumenter.attachLoadSourceSectionListener(statementFilter, new LoadSourceSectionListener() {
            @Override
            public void onLoad(LoadSourceSectionEvent event) {
                addStatement(event);
            }
        }, true);
    }

    private void instrumentLoadedRoots(Instrumenter instrumenter, SourceSectionFilter f) {
        final SourceSectionFilter rootFilter = SourceSectionFilter.newBuilder().tagIs(StandardTags.RootTag.class).and(f).build();
        loadedRootsBinding = instrumenter.attachLoadSourceSectionListener(rootFilter, new LoadSourceSectionListener() {
            @Override
            public void onLoad(LoadSourceSectionEvent event) {
                addRoot(event);
            }
        }, true);
    }

    private synchronized void addRoot(LoadSourceSectionEvent event) {
        loadedRoots.add(event);
    }

    private synchronized void addStatement(LoadSourceSectionEvent event) {
        loadedStatements.add(event);
    }

    private void disposeBindings() {
        loadedRootsBinding.dispose();
        loadedStatementBinding.dispose();
        coveredBinding.dispose();
    }

    private static class RootData {
        private final SourceSection sourceSection;
        private final Set loadedStatements = new HashSet<>();
        private final Map coveredStatements = new HashMap<>();
        private final String name;
        private long count;
        private boolean covered;

        RootData(SourceSection sourceSection, String name) {
            this.sourceSection = sourceSection;
            this.name = name;
        }

    }

    /**
     * Configuration for the {@link CoverageTracker}. Specifies the {@link SourceSectionFilter
     * filter} for which {@link SourceSection source sections} to include in tracking as well as
     * whether to keep track of how many times a particular source section was executed.
     * 
     * @since 19.3.0
     */
    public static class Config {
        private final SourceSectionFilter sourceSectionFilter;
        private final boolean count;

        public Config(SourceSectionFilter sourceSectionFilter, boolean count) {
            this.sourceSectionFilter = sourceSectionFilter;
            this.count = count;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy