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

org.jruby.truffle.language.backtrace.BacktraceFormatter Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2014, 2016 Oracle and/or its affiliates. All rights reserved. This
 * code is released under a tri EPL/GPL/LGPL license. You can use it,
 * redistribute it and/or modify it under the terms of the:
 *
 * Eclipse Public License version 1.0
 * GNU General Public License version 2
 * GNU Lesser General Public License version 2.1
 */
package org.jruby.truffle.language.backtrace;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import org.jruby.truffle.Layouts;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.RubyLanguage;
import org.jruby.truffle.core.string.StringUtils;
import org.jruby.truffle.language.RubyGuards;
import org.jruby.truffle.language.RubyRootNode;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.truffle.language.loader.SourceLoader;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;

public class BacktraceFormatter {

    public enum FormattingFlags {
        OMIT_EXCEPTION,
        OMIT_FROM_PREFIX,
        INCLUDE_CORE_FILES,
        INTERLEAVE_JAVA
    }

    private final RubyContext context;
    private final EnumSet flags;

    public static BacktraceFormatter createDefaultFormatter(RubyContext context) {
        final EnumSet flags = EnumSet.noneOf(FormattingFlags.class);

        if (!context.getOptions().BACKTRACES_HIDE_CORE_FILES) {
            flags.add(FormattingFlags.INCLUDE_CORE_FILES);
        }

        if (context.getOptions().BACKTRACES_INTERLEAVE_JAVA) {
            flags.add(FormattingFlags.INTERLEAVE_JAVA);
        }

        return new BacktraceFormatter(context, flags);
    }

    private static List rubyBacktrace(RubyContext context, Node node) {
        return new BacktraceFormatter(context, EnumSet.of(FormattingFlags.INCLUDE_CORE_FILES)).
                        formatBacktrace(context, null, context.getCallStack().getBacktrace(node));
    }

    // For debugging:
    // org.jruby.truffle.language.backtrace.BacktraceFormatter.printableRubyBacktrace(getContext(), this)
    public static String printableRubyBacktrace(RubyContext context, Node node) {
        final StringBuilder builder = new StringBuilder();
        for (String line : rubyBacktrace(context, node)) {
            builder.append("\n");
            builder.append(line);
        }
        return builder.toString().substring(1);
    }

    public BacktraceFormatter(RubyContext context, EnumSet flags) {
        this.context = context;
        this.flags = flags;
    }

    @TruffleBoundary
    public void printBacktrace(RubyContext context, DynamicObject exception, Backtrace backtrace) {
        printBacktrace(context, exception, backtrace, new PrintWriter(System.err, true));
    }

    @TruffleBoundary
    public void printBacktrace(RubyContext context, DynamicObject exception, Backtrace backtrace, PrintWriter writer) {
        for (String line : formatBacktrace(context, exception, backtrace)) {
            writer.println(line);
        }
    }

    public List formatBacktrace(RubyContext context, DynamicObject exception, Backtrace backtrace) {
        if (backtrace == null) {
            backtrace = context.getCallStack().getBacktrace(null);
        }

        final List activations = backtrace.getActivations();
        final ArrayList lines = new ArrayList<>();

        for (int n = 0; n < activations.size(); n++) {
            try {
                lines.add(formatLine(activations, n, exception));
            } catch (Exception e) {
                if (context.getOptions().EXCEPTIONS_PRINT_JAVA) {
                    e.printStackTrace();
                }

                lines.add(StringUtils.format("(exception %s %s", e.getMessage(), e.getStackTrace()[0].toString()));
            }
        }

        if (backtrace.getJavaThrowable() != null && flags.contains(FormattingFlags.INTERLEAVE_JAVA)) {
            return BacktraceInterleaver.interleave(lines, backtrace.getJavaThrowable().getStackTrace());
        }

        return lines;
    }

    public String formatLine(List activations, int n, DynamicObject exception) {
        final Activation activation = activations.get(n);

        if (activation == Activation.OMITTED_LIMIT) {
            return context.getCoreStrings().BACKTRACE_OMITTED_LIMIT.toString();
        }

        if (activation == Activation.OMITTED_UNUSED) {
            return context.getCoreStrings().BACKTRACE_OMITTED_UNUSED.toString();
        }

        final StringBuilder builder = new StringBuilder();

        if (!flags.contains(FormattingFlags.OMIT_FROM_PREFIX) && n > 0) {
            builder.append("\tfrom ");
        }

        final RootNode rootNode = activation.getCallNode().getRootNode().getRootNode();

        if (rootNode instanceof RubyRootNode) {
            final SourceSection sourceSection = activation.getCallNode().getEncapsulatingSourceSection();
            final SourceSection reportedSourceSection;
            String reportedName;

            if (isJavaCore(sourceSection) ||
                    (isCore(context, sourceSection) && !flags.contains(FormattingFlags.INCLUDE_CORE_FILES))) {
                final SourceSection nextUserSourceSection = nextUserSourceSection(activations, n);
                // if there is no next source section use a core one to avoid ???
                reportedSourceSection = nextUserSourceSection != null ? nextUserSourceSection : sourceSection;
                reportedName = getMethodNameFromActivation(activation);
            } else {
                reportedSourceSection = sourceSection;
                reportedName = rootNode.getName();
            }

            if (reportedSourceSection == null) {
                builder.append("???");
            } else {
                builder.append(reportedSourceSection.getSource().getName());
                builder.append(":");
                builder.append(reportedSourceSection.getStartLine());
            }

            builder.append(":in `");
            builder.append(reportedName);
            builder.append("'");
        } else {
            builder.append(formatForeign(activation.getCallNode()));
        }

        if (!flags.contains(FormattingFlags.OMIT_EXCEPTION) && exception != null && n == 0) {
            String message;
            try {
                Object messageObject = context.send(exception, "message", null);
                if (RubyGuards.isRubyString(messageObject)) {
                    message = messageObject.toString();
                } else {
                    message = Layouts.EXCEPTION.getMessage(exception).toString();
                }
            } catch (RaiseException e) {
                message = Layouts.EXCEPTION.getMessage(exception).toString();
            }

            builder.append(": ");
            builder.append(message);
            builder.append(" (");
            builder.append(Layouts.MODULE.getFields(Layouts.BASIC_OBJECT.getLogicalClass(exception)).getName());
            builder.append(")");
        }

        return builder.toString();
    }

    private String getMethodNameFromActivation(Activation activation) {
        try {
            return activation.getMethod().getName();
        } catch (Exception e) {
            return "???";
        }
    }

    private SourceSection nextUserSourceSection(List activations, int n) {
        while (n < activations.size()) {
            final Node callNode = activations.get(n).getCallNode();

            if (callNode != null) {
                final SourceSection sourceSection = callNode.getEncapsulatingSourceSection();

                if (!isCore(context, sourceSection)) {
                    return sourceSection;
                }
            }

            n++;
        }
        return null;
    }

    public boolean isJavaCore(SourceSection sourceSection) {
        return sourceSection == context.getCoreLibrary().getSourceSection();
    }

    public static boolean isCore(RubyContext context, SourceSection sourceSection) {
        if (sourceSection == null) {
            return true;
        }

        if (sourceSection.getSource() == context.getCoreLibrary().getSource()) {
            return true;
        }

        final Source source = sourceSection.getSource();
        if (source == null) {
            return true;
        }

        final String path = source.getPath();
        if (path != null) {
            return path.startsWith(SourceLoader.TRUFFLE_SCHEME);
        }

        final String name = source.getName();
        if (name != null) {
            return name.startsWith(SourceLoader.TRUFFLE_SCHEME);
        }

        return true;
    }

    /** For debug purposes. */
    public static boolean isUserSourceSection(RubyContext context, SourceSection sourceSection) {
        if (!BacktraceFormatter.isCore(context, sourceSection)) {
            return false;
        }

        final String path = sourceSection.getSource().getPath();
        if (path.startsWith(context.getCoreLibrary().getCoreLoadPath())) {
            return false;
        }

        return path.indexOf("/lib/ruby/stdlib/rubygems") == -1;
    }

    private String formatForeign(Node callNode) {
        final StringBuilder builder = new StringBuilder();

        final SourceSection sourceSection = callNode.getEncapsulatingSourceSection();

        if (sourceSection != null) {
            final String shortDescription = RubyLanguage.fileLine(sourceSection);


            builder.append(shortDescription);

            final RootNode rootNode = callNode.getRootNode();
            final String identifier = rootNode.getName();

            if (identifier != null && !identifier.isEmpty()) {
                builder.append(":in `");
                builder.append(identifier);
                builder.append("'");
            }
        } else {
            builder.append(getRootOrTopmostNode(callNode).getClass().getSimpleName());
        }

        return builder.toString();
    }

    private Node getRootOrTopmostNode(Node node) {
        while (node.getParent() != null) {
            node = node.getParent();
        }

        return node;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy