view truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/LineBreakpointFactory.java @ 22548:6b76a24fffbd default tip

Use all variables: a, b, u,v and x, y
author Jaroslav Tulach <jaroslav.tulach@oracle.com>
date Thu, 14 Jan 2016 14:20:57 +0100
parents c04f6f51b9bf
children
line wrap: on
line source

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

import static com.oracle.truffle.api.debug.Breakpoint.State.DISABLED;
import static com.oracle.truffle.api.debug.Breakpoint.State.DISABLED_UNRESOLVED;
import static com.oracle.truffle.api.debug.Breakpoint.State.DISPOSED;
import static com.oracle.truffle.api.debug.Breakpoint.State.ENABLED;
import static com.oracle.truffle.api.debug.Breakpoint.State.ENABLED_UNRESOLVED;

import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.debug.Debugger.BreakpointCallback;
import com.oracle.truffle.api.debug.Debugger.WarningLog;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrument.EvalInstrumentListener;
import com.oracle.truffle.api.instrument.Instrumenter;
import com.oracle.truffle.api.instrument.Probe;
import com.oracle.truffle.api.instrument.ProbeInstrument;
import com.oracle.truffle.api.instrument.StandardSyntaxTag;
import com.oracle.truffle.api.instrument.SyntaxTag;
import com.oracle.truffle.api.instrument.impl.DefaultProbeListener;
import com.oracle.truffle.api.instrument.impl.DefaultStandardInstrumentListener;
import com.oracle.truffle.api.nodes.InvalidAssumptionException;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.source.LineLocation;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.utilities.CyclicAssumption;

//TODO (mlvdv) some common functionality could be factored out of this and TagBreakpointSupport

/**
 * Support class for creating and managing all existing ordinary (user visible) line breakpoints.
 * <p>
 * Notes:
 * <ol>
 * <li>Line breakpoints can only be set at nodes tagged as {@link StandardSyntaxTag#STATEMENT}.</li>
 * <li>A newly created breakpoint looks for probes matching the location, attaches to them if found
 * by installing an {@link ProbeInstrument} that calls back to the breakpoint.</li>
 * <li>When Truffle "splits" or otherwise copies an AST, any attached {@link ProbeInstrument} will
 * be copied along with the rest of the AST and will call back to the same breakpoint.</li>
 * <li>When notification is received of a new Node being tagged as a statement, and if a
 * breakpoint's line location matches the Probe's line location, then the breakpoint will attach a
 * new Instrument at the probe to activate the breakpoint at that location.</li>
 * <li>A breakpoint may have multiple Instruments deployed, one attached to each Probe that matches
 * the breakpoint's line location; this might happen when a source is reloaded.</li>
 * </ol>
 *
 */
final class LineBreakpointFactory {

    private static final boolean TRACE = false;
    private static final PrintStream OUT = System.out;

    private static final String BREAKPOINT_NAME = "Line Breakpoints";

    @TruffleBoundary
    private static void trace(String format, Object... args) {
        if (TRACE) {
            OUT.println(String.format("%s: %s", BREAKPOINT_NAME, String.format(format, args)));
        }
    }

    private static final Comparator<Entry<LineLocation, LineBreakpointImpl>> BREAKPOINT_COMPARATOR = new Comparator<Entry<LineLocation, LineBreakpointImpl>>() {

        @Override
        public int compare(Entry<LineLocation, LineBreakpointImpl> entry1, Entry<LineLocation, LineBreakpointImpl> entry2) {
            final LineLocation line1 = entry1.getKey();
            final LineLocation line2 = entry2.getKey();
            final int nameOrder = line1.getSource().getShortName().compareTo(line2.getSource().getShortName());
            if (nameOrder != 0) {
                return nameOrder;
            }
            return Integer.compare(line1.getLineNumber(), line2.getLineNumber());
        }
    };

    private final Debugger debugger;
    private final BreakpointCallback breakpointCallback;
    private final WarningLog warningLog;

    /**
     * Map: Source lines ==> attached breakpoints. There may be no more than one line breakpoint
     * associated with a line.
     */
    private final Map<LineLocation, LineBreakpointImpl> lineToBreakpoint = new HashMap<>();

    /**
     * A map of {@link LineLocation} to a collection of {@link Probe}s. This list must be
     * initialized and filled prior to being used by this class.
     */
    private final LineToProbesMap lineToProbesMap;

    /**
     * Globally suspends all line breakpoint activity when {@code false}, ignoring whether
     * individual breakpoints are enabled.
     */
    @CompilationFinal private boolean breakpointsActive = true;
    private final CyclicAssumption breakpointsActiveUnchanged = new CyclicAssumption(BREAKPOINT_NAME + " globally active");

    LineBreakpointFactory(Debugger debugger, BreakpointCallback breakpointCallback, final WarningLog warningLog) {
        this.debugger = debugger;
        this.breakpointCallback = breakpointCallback;
        this.warningLog = warningLog;

        final Instrumenter instrumenter = debugger.getInstrumenter();
        this.lineToProbesMap = new LineToProbesMap();
        instrumenter.install(lineToProbesMap);

        instrumenter.addProbeListener(new DefaultProbeListener() {

            @Override
            public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) {
                if (tag == StandardSyntaxTag.STATEMENT) {
                    final SourceSection sourceSection = probe.getProbedSourceSection();
                    if (sourceSection != null) {
                        final LineLocation lineLocation = sourceSection.getLineLocation();
                        if (lineLocation != null) {
                            // A Probe with line location tagged STATEMENT we haven't seen before.
                            final LineBreakpointImpl breakpoint = lineToBreakpoint.get(lineLocation);
                            if (breakpoint != null) {
                                breakpoint.attach(probe);
                            }
                        }
                    }
                }
            }
        });
    }

    /**
     * Globally enables line breakpoint activity; all breakpoints are ignored when set to
     * {@code false}. When set to {@code true}, the enabled/disabled status of each breakpoint
     * determines whether it will trigger when flow of execution reaches it.
     *
     * @param breakpointsActive
     */
    void setActive(boolean breakpointsActive) {
        if (this.breakpointsActive != breakpointsActive) {
            breakpointsActiveUnchanged.invalidate();
            this.breakpointsActive = breakpointsActive;
        }
    }

    /**
     * Gets all current line breakpoints,regardless of status; sorted and modification safe.
     */
    List<LineBreakpoint> getAll() {
        ArrayList<Entry<LineLocation, LineBreakpointImpl>> entries = new ArrayList<>(lineToBreakpoint.entrySet());
        Collections.sort(entries, BREAKPOINT_COMPARATOR);

        final ArrayList<LineBreakpoint> breakpoints = new ArrayList<>(entries.size());
        for (Entry<LineLocation, LineBreakpointImpl> entry : entries) {
            breakpoints.add(entry.getValue());
        }
        return breakpoints;
    }

    /**
     * Creates a new line breakpoint if one doesn't already exist. If one does exist, then resets
     * the <em>ignore count</em>.
     *
     * @param lineLocation where to set the breakpoint
     * @param ignoreCount number of initial hits before the breakpoint starts causing breaks.
     * @param oneShot whether the breakpoint should dispose itself after one hit
     * @return a possibly new breakpoint
     * @throws IOException if a breakpoint already exists at the location and the ignore count is
     *             the same
     */
    LineBreakpoint create(int ignoreCount, LineLocation lineLocation, boolean oneShot) throws IOException {

        LineBreakpointImpl breakpoint = lineToBreakpoint.get(lineLocation);

        if (breakpoint == null) {
            breakpoint = new LineBreakpointImpl(ignoreCount, lineLocation, oneShot);

            if (TRACE) {
                trace("NEW " + breakpoint.getShortDescription());
            }

            lineToBreakpoint.put(lineLocation, breakpoint);

            for (Probe probe : lineToProbesMap.findProbes(lineLocation)) {
                if (probe.isTaggedAs(StandardSyntaxTag.STATEMENT)) {
                    breakpoint.attach(probe);
                    break;
                }
            }
        } else {
            if (ignoreCount == breakpoint.getIgnoreCount()) {
                throw new IOException(BREAKPOINT_NAME + " already set at line " + lineLocation);
            }
            breakpoint.setIgnoreCount(ignoreCount);
            if (TRACE) {
                trace("CHANGED ignoreCount %s", breakpoint.getShortDescription());
            }
        }
        return breakpoint;
    }

    /**
     * Returns the {@link LineBreakpoint} for a given line. There should only ever be one breakpoint
     * per line.
     *
     * @param lineLocation The {@link LineLocation} to get the breakpoint for.
     * @return The breakpoint for the given line.
     */
    LineBreakpoint get(LineLocation lineLocation) {
        return lineToBreakpoint.get(lineLocation);
    }

    /**
     * Removes the associated instrumentation for all one-shot breakpoints only.
     */
    void disposeOneShots() {
        List<LineBreakpointImpl> breakpoints = new ArrayList<>(lineToBreakpoint.values());
        for (LineBreakpointImpl breakpoint : breakpoints) {
            if (breakpoint.isOneShot()) {
                breakpoint.dispose();
            }
        }
    }

    /**
     * Removes all knowledge of a breakpoint, presumed disposed.
     */
    private void forget(LineBreakpointImpl breakpoint) {
        lineToBreakpoint.remove(breakpoint.getLineLocation());
    }

    /**
     * Concrete representation of a line breakpoint, implemented by attaching an instrument to a
     * probe at the designated source location.
     */
    private final class LineBreakpointImpl extends LineBreakpoint implements EvalInstrumentListener {

        private static final String SHOULD_NOT_HAPPEN = "LineBreakpointImpl:  should not happen";

        private final LineLocation lineLocation;

        // Cached assumption that the global status of line breakpoint activity has not changed.
        private Assumption breakpointsActiveAssumption;

        // Whether this breakpoint is enable/disabled
        @CompilationFinal private boolean isEnabled;
        private Assumption enabledUnchangedAssumption;

        private Source conditionSource;

        /**
         * The instrument(s) that this breakpoint currently has attached to a {@link Probe}:
         * {@code null} if not attached.
         */
        private List<ProbeInstrument> instruments = new ArrayList<>();

        public LineBreakpointImpl(int ignoreCount, LineLocation lineLocation, boolean oneShot) {
            super(ENABLED_UNRESOLVED, ignoreCount, oneShot);
            this.lineLocation = lineLocation;

            this.breakpointsActiveAssumption = LineBreakpointFactory.this.breakpointsActiveUnchanged.getAssumption();
            this.isEnabled = true;
            this.enabledUnchangedAssumption = Truffle.getRuntime().createAssumption(BREAKPOINT_NAME + " enabled state unchanged");
        }

        @Override
        public boolean isEnabled() {
            return isEnabled;
        }

        @Override
        public void setEnabled(boolean enabled) {
            if (enabled != isEnabled) {
                switch (getState()) {
                    case ENABLED:
                        assert !enabled : SHOULD_NOT_HAPPEN;
                        doSetEnabled(false);
                        changeState(DISABLED);
                        break;
                    case ENABLED_UNRESOLVED:
                        assert !enabled : SHOULD_NOT_HAPPEN;
                        doSetEnabled(false);
                        changeState(DISABLED_UNRESOLVED);
                        break;
                    case DISABLED:
                        assert enabled : SHOULD_NOT_HAPPEN;
                        doSetEnabled(true);
                        changeState(ENABLED);
                        break;
                    case DISABLED_UNRESOLVED:
                        assert enabled : SHOULD_NOT_HAPPEN;
                        doSetEnabled(true);
                        changeState(DISABLED_UNRESOLVED);
                        break;
                    case DISPOSED:
                        assert false : "breakpoint disposed";
                        break;
                    default:
                        assert false : SHOULD_NOT_HAPPEN;
                        break;
                }
            }
        }

        @Override
        public void setCondition(String expr) throws IOException {
            if (this.conditionSource != null || expr != null) {
                // De-instrument the Probes instrumented by this breakpoint
                final ArrayList<Probe> probes = new ArrayList<>();
                for (ProbeInstrument instrument : instruments) {
                    probes.add(instrument.getProbe());
                    instrument.dispose();
                }
                instruments.clear();
                this.conditionSource = Source.fromText(expr, "breakpoint condition from text: " + expr);
                // Re-instrument the probes previously instrumented
                for (Probe probe : probes) {
                    attach(probe);
                }
            }
        }

        @Override
        public Source getCondition() {
            return conditionSource;
        }

        @TruffleBoundary
        @Override
        public void dispose() {
            if (getState() != DISPOSED) {
                for (ProbeInstrument instrument : instruments) {
                    instrument.dispose();
                }
                changeState(DISPOSED);
                LineBreakpointFactory.this.forget(this);
            }
        }

        private void attach(Probe newProbe) {
            if (getState() == DISPOSED) {
                throw new IllegalStateException("Attempt to attach a disposed " + BREAKPOINT_NAME);
            }
            ProbeInstrument newInstrument = null;
            final Instrumenter instrumenter = debugger.getInstrumenter();
            if (conditionSource == null) {
                newInstrument = instrumenter.attach(newProbe, new UnconditionalLineBreakInstrumentListener(), BREAKPOINT_NAME);
            } else {
                newInstrument = instrumenter.attach(newProbe, conditionSource, this, BREAKPOINT_NAME, null);
            }
            instruments.add(newInstrument);
            changeState(isEnabled ? ENABLED : DISABLED);
        }

        private void doSetEnabled(boolean enabled) {
            if (this.isEnabled != enabled) {
                enabledUnchangedAssumption.invalidate();
                this.isEnabled = enabled;
            }
        }

        @TruffleBoundary
        private String getShortDescription() {
            return BREAKPOINT_NAME + "@" + getLineLocation().getShortDescription();
        }

        private void changeState(State after) {
            if (TRACE) {
                trace("STATE %s-->%s %s", getState().getName(), after.getName(), getShortDescription());
            }
            setState(after);
        }

        private void doBreak(Node node, VirtualFrame vFrame) {
            if (incrHitCountCheckIgnore()) {
                breakpointCallback.haltedAt(node, vFrame.materialize(), BREAKPOINT_NAME);
            }
        }

        /**
         * Receives notification from the attached instrument that execution is about to enter node
         * where the breakpoint is set. Designed so that when in the fast path, there is either an
         * unconditional "halt" call to the debugger or nothing.
         */
        private void nodeEnter(Node astNode, VirtualFrame vFrame) {

            // Deopt if the global active/inactive flag has changed
            try {
                this.breakpointsActiveAssumption.check();
            } catch (InvalidAssumptionException ex) {
                this.breakpointsActiveAssumption = LineBreakpointFactory.this.breakpointsActiveUnchanged.getAssumption();
            }

            // Deopt if the enabled/disabled state of this breakpoint has changed
            try {
                this.enabledUnchangedAssumption.check();
            } catch (InvalidAssumptionException ex) {
                this.enabledUnchangedAssumption = Truffle.getRuntime().createAssumption("LineBreakpoint enabled state unchanged");
            }

            if (LineBreakpointFactory.this.breakpointsActive && this.isEnabled) {
                if (isOneShot()) {
                    dispose();
                }
                LineBreakpointImpl.this.doBreak(astNode, vFrame);
            }
        }

        public void onExecution(Node node, VirtualFrame vFrame, Object result) {
            if (result instanceof Boolean) {
                final boolean condition = (Boolean) result;
                if (TRACE) {
                    trace("breakpoint condition = %b  %s", condition, getShortDescription());
                }
                if (condition) {
                    nodeEnter(node, vFrame);
                }
            } else {
                onFailure(node, vFrame, new RuntimeException("breakpint failure = non-boolean condition " + result.toString()));
            }
        }

        public void onFailure(Node node, VirtualFrame vFrame, Exception ex) {
            addExceptionWarning(ex);
            if (TRACE) {
                trace("breakpoint failure = %s  %s", ex, getShortDescription());
            }
            // Take the breakpoint if evaluation fails.
            nodeEnter(node, vFrame);
        }

        @TruffleBoundary
        private void addExceptionWarning(Exception ex) {
            warningLog.addWarning(String.format("Exception in %s:  %s", getShortDescription(), ex.getMessage()));
        }

        @Override
        public String getLocationDescription() {
            return "Line: " + lineLocation.getShortDescription();
        }

        @Override
        public LineLocation getLineLocation() {
            return lineLocation;
        }

        private final class UnconditionalLineBreakInstrumentListener extends DefaultStandardInstrumentListener {

            @Override
            public void onEnter(Probe probe, Node node, VirtualFrame vFrame) {
                LineBreakpointImpl.this.nodeEnter(node, vFrame);
            }
        }
    }

}