/*
* Copyright (c) 2007, 2012, 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.
*
* 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.sun.max.ins.debug;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.event.*;
import com.sun.cri.ci.*;
import com.sun.max.gui.*;
import com.sun.max.ins.*;
import com.sun.max.ins.gui.*;
import com.sun.max.ins.util.*;
import com.sun.max.ins.value.*;
import com.sun.max.ins.view.*;
import com.sun.max.ins.view.InspectionViews.ViewKind;
import com.sun.max.program.*;
import com.sun.max.tele.*;
import com.sun.max.tele.data.*;
import com.sun.max.tele.object.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.actor.member.*;
/**
* A singleton view that displays stack contents for the thread in the VM that is the current user focus.
*/
public final class StackView extends AbstractView<StackView> {
private static final int TRACE_VALUE = 1;
private static final ViewKind VIEW_KIND = ViewKind.STACK;
private static final String SHORT_NAME = "Stack";
private static final String LONG_NAME = "Stack View";
private static final String GEOMETRY_SETTINGS_KEY = "stackViewGeometry";
public static final int DEFAULT_MAX_FRAMES_DISPLAY;
private static final String MAX_FRAMES_DISPLAY_PROPERTY = "inspector.max.stack.frames.display";
static {
int def = 500;
final String value = System.getProperty(MAX_FRAMES_DISPLAY_PROPERTY);
if (value != null) {
try {
def = Integer.parseInt(value);
} catch (NumberFormatException ex) {
InspectorError.unexpected(MAX_FRAMES_DISPLAY_PROPERTY + " value " + value + " not an integer");
}
}
DEFAULT_MAX_FRAMES_DISPLAY = def;
}
public static final class StackViewManager extends AbstractSingletonViewManager<StackView> {
protected StackViewManager(Inspection inspection) {
super(inspection, VIEW_KIND, SHORT_NAME, LONG_NAME);
}
@Override
protected StackView createView(Inspection inspection) {
return new StackView(inspection);
}
}
// Will be non-null before any instances created.
private static StackViewManager viewManager = null;
public static StackViewManager makeViewManager(Inspection inspection) {
if (viewManager == null) {
viewManager = new StackViewManager(inspection);
}
return viewManager;
}
private final class StackFrameListCellRenderer extends MachineCodeLabel implements ListCellRenderer {
StackFrameListCellRenderer(Inspection inspection) {
super(inspection, "");
}
public Component getListCellRendererComponent(JList list, Object value, int modelIndex, boolean isSelected, boolean cellHasFocus) {
final MaxStackFrame stackFrame = (MaxStackFrame) value;
String methodName = "";
String toolTip = null;
if (stackFrame instanceof MaxStackFrame.Compiled) {
final MaxCompilation compilation = stackFrame.compilation();
methodName += inspection().nameDisplay().veryShortName(compilation);
CiDebugInfo debugInfo = stackFrame.codeLocation().debugInfo();
if (debugInfo == null || debugInfo.frame() == null || debugInfo.frame().caller() == null) {
toolTip = htmlify(inspection().nameDisplay().longName(compilation, stackFrame.ip()));
if (compilation != null) {
try {
vm().acquireLegacyVMAccess();
try {
final TeleClassMethodActor teleClassMethodActor = compilation.getTeleClassMethodActor();
if (teleClassMethodActor != null && teleClassMethodActor.isSubstituted()) {
methodName += inspection().nameDisplay().methodSubstitutionShortAnnotation(teleClassMethodActor);
try {
toolTip += inspection().nameDisplay().methodSubstitutionLongAnnotation(teleClassMethodActor);
} catch (Exception e) {
// There's corner cases where we can't obtain detailed information for the tool tip (e.g., the method we're trying to get the substitution info about
// is being constructed. Instead of propagating the exception, just use a default tool tip. [Laurent].
toolTip += inspection().nameDisplay().unavailableDataLongText();
}
}
} finally {
vm().releaseLegacyVMAccess();
}
} catch (DataIOError e) {
methodName += inspection().nameDisplay().unavailableDataShortText();
toolTip = inspection().nameDisplay().unavailableDataLongText();
} catch (MaxVMBusyException e) {
methodName += inspection().nameDisplay().unavailableDataShortText();
toolTip = inspection().nameDisplay().unavailableDataLongText();
}
}
} else {
// There is at least one inlined frame available in debug info
final Stack<CiFrame> frames = new Stack<CiFrame>();
CiFrame frame = debugInfo.frame();
while (frame != null) {
frames.push(frame);
frame = frame.caller();
}
final StringBuilder sb = new StringBuilder();
// Display the immediate frame using the standard format
sb.append(htmlify(inspection().nameDisplay().longName(compilation, stackFrame.ip())));
frame = frames.pop();
while (!frames.isEmpty()) {
frame = frames.pop();
sb.append("<br> Inlined from: <br>");
CiUtil.appendLocation(sb, frame.method, frame.bci).toString();
}
toolTip = sb.toString();
}
} else if (stackFrame instanceof MaxStackFrame.Truncated) {
final MaxStackFrame.Truncated truncated = (MaxStackFrame.Truncated) stackFrame;
if (truncated.error() != null) {
methodName += "*a stack walker error occurred*";
toolTip = truncated.error().toString();
} else {
methodName += "*select here to extend the display*";
}
} else {
InspectorWarning.check(inspection(), stackFrame instanceof MaxStackFrame.Native, "Unhandled type of non-native stack frame: " + stackFrame.getClass().getName());
final Pointer instructionPointer = stackFrame.ip();
final MaxNativeFunction externalCode = vm().machineCode().findNativeFunction(instructionPointer);
if (externalCode != null) {
// native that we know something about
methodName += inspection().nameDisplay().shortName(externalCode);
toolTip = "native function: " + inspection().nameDisplay().longName(externalCode);
} else {
methodName += "nativeMethod:" + instructionPointer.to0xHexString();
toolTip = "nativeMethod";
}
}
if (modelIndex == 0) {
setToolTipPrefix("IP in frame " + modelIndex + " points at:<br>");
setForeground(preference().style().wordCallEntryPointColor());
} else {
setToolTipPrefix("call return in frame " + modelIndex + " points at:<br>");
setForeground(preference().style().wordCallReturnPointColor());
}
setText(Integer.toString(modelIndex) + ": " + methodName);
setWrappedToolTipHtmlText(toolTip);
setBackground(isSelected ? stackFrameList.getSelectionBackground() : stackFrameList.getBackground());
return this;
}
}
/**
* Listens for mouse events over the stack frame list so that the right button
* will bring up a contextual menu.
*/
private final MouseListener frameMouseListener = new InspectorMouseClickAdapter(inspection()) {
@Override
public void procedure(final MouseEvent mouseEvent) {
switch(inspection().gui().getButton(mouseEvent)) {
case MouseEvent.BUTTON3:
int index = stackFrameList.locationToIndex(mouseEvent.getPoint());
if (index >= 0 && index < stackFrameList.getModel().getSize()) {
getPopupMenu(index, mouseEvent).show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
}
break;
}
}
};
/**
* Listens for change of selection in the list of stack frames and passes this along
* to the global focus setting.
*/
private final ListSelectionListener frameSelectionListener = new ListSelectionListener() {
public void valueChanged(ListSelectionEvent listSelectionEvent) {
if (!listSelectionEvent.getValueIsAdjusting()) {
final int index = stackFrameList.getSelectedIndex();
if (index >= 0 && index < stackFrameListModel.getSize()) {
MaxStackFrame stackFrame = (MaxStackFrame) stackFrameListModel.get(index);
// New stack frame selection; set the global focus.
inspection().focus().setStackFrame(stackFrame, false);
if (stackFrame instanceof MaxStackFrame.Truncated && ((MaxStackFrame.Truncated) stackFrame).omitted() >= 0) {
// A user selection of the pseudo frame that marks the end
// of a partial (truncated) stack list has the effect of
// doubling the number of frames so that you can see more.
maxFramesDisplay *= 2;
lastChangedState = vm().state();
// Reconstruct the frame display with the new, extended cap
forceRefresh();
// Finally, reset the focus on the actual stack, frame, not the
// special frame that was just replaced after the maximum increased.
stackFrame = (MaxStackFrame) stackFrameListModel.get(index);
focus().setStackFrame(stackFrame, true);
}
}
}
}
};
private final StackFrameListCellRenderer stackFrameListCellRenderer = new StackFrameListCellRenderer(inspection());
private final InspectorAction copyStackToClipboardAction = new CopyStackToClipboardAction();
private MaxStack stack = null;
/**
* Marks the most recent time in VM state history that we refreshed from the stack.
*/
private MaxVMState lastUpdatedState = null;
/**
* Marks the most recent time in VM state history when the stack was observed to have changed structurally.
*/
private MaxVMState lastChangedState = null;
private InspectorPanel contentPane = null;
private DefaultListModel stackFrameListModel = null; // TODO (mlvdv) generic in Java 7
private DefaultListModel emptyModel = new DefaultListModel(); // TODO (mlvdv) generic in Java 7
private JList stackFrameList = null; // TODO (mlvdv) generic in Java 7
/**
* The maximum number of frames to be displayed at any given time, to defend against extremely large
* stacks. The user can click in the final pseudo-frame in the display when it has been limited
* this way, and this will cause the number displayed to grow.
*/
private int maxFramesDisplay = DEFAULT_MAX_FRAMES_DISPLAY;
public StackView(Inspection inspection) {
super(inspection, VIEW_KIND, GEOMETRY_SETTINGS_KEY);
Trace.begin(TRACE_VALUE, tracePrefix() + " initializing");
createFrame(true);
forceRefresh();
Trace.end(TRACE_VALUE, tracePrefix() + " initializing");
}
@Override
public String getTextForTitle() {
String title = viewManager.shortName() + ": ";
if (!inspection().hasProcess()) {
title += inspection().nameDisplay().noProcessShortText();
} else if (stack != null && stack.thread() != null) {
title += inspection().nameDisplay().longNameWithState(stack.thread());
}
return title;
}
@SuppressWarnings("unchecked")
@Override
public void createViewContent() {
lastUpdatedState = null;
lastChangedState = null;
final MaxThread thread = inspection().focus().thread();
contentPane = new InspectorPanel(inspection(), new BorderLayout());
if (thread != null) {
stack = thread.stack();
lastUpdatedState = stack.lastUpdated();
lastChangedState = stack.lastChanged();
assert stack != null;
stackFrameListModel = new DefaultListModel();
stackFrameList = new JList(stackFrameListModel);
stackFrameList.setCellRenderer(stackFrameListCellRenderer);
final JPanel header = new InspectorPanel(inspection(), new SpringLayout());
final TextLabel stackStartLabel = new TextLabel(inspection(), "start: ");
stackStartLabel.setToolTipText("Stack memory start location");
header.add(stackStartLabel);
final WordValueLabel stackStartValueLabel = new WordValueLabel(inspection(), WordValueLabel.ValueMode.WORD, stack.memoryRegion().start(), contentPane);
stackStartValueLabel.setToolTipPrefix("Stack memory start @ ");
header.add(stackStartValueLabel);
final TextLabel stackSizeLabel = new TextLabel(inspection(), "size: ");
stackSizeLabel.setToolTipText("Stack size");
header.add(stackSizeLabel);
final DataLabel.LongAsDecimal stackSizeValueLabel = new DataLabel.LongAsDecimal(inspection());
stackSizeValueLabel.setToolTipPrefix("Stack size ");
stackSizeValueLabel.setValue(stack.memoryRegion().nBytes());
header.add(stackSizeValueLabel);
SpringUtilities.makeCompactGrid(header, 2);
contentPane.add(header, BorderLayout.NORTH);
stackFrameList.setSelectionInterval(1, 0);
stackFrameList.setVisibleRowCount(10);
stackFrameList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
stackFrameList.setLayoutOrientation(JList.VERTICAL);
stackFrameList.addMouseListener(frameMouseListener);
stackFrameList.addListSelectionListener(frameSelectionListener);
final JScrollPane listScrollPane = new InspectorScrollPane(inspection(), stackFrameList);
contentPane.add(listScrollPane, BorderLayout.CENTER);
}
setContentPane(contentPane);
// Populate menu bar
makeMenu(MenuKind.DEFAULT_MENU).add(defaultMenuItems(MenuKind.DEFAULT_MENU));
final InspectorMenu editMenu = makeMenu(MenuKind.EDIT_MENU);
editMenu.add(copyStackToClipboardAction);
final InspectorMenu memoryMenu = makeMenu(MenuKind.MEMORY_MENU);
memoryMenu.add(actions().viewSelectedThreadStackMemory("View memory for stack"));
memoryMenu.add(defaultMenuItems(MenuKind.MEMORY_MENU));
memoryMenu.add(views().activateSingletonViewAction(ViewKind.ALLOCATIONS));
makeMenu(MenuKind.VIEW_MENU).add(defaultMenuItems(MenuKind.VIEW_MENU));
forceRefresh();
// TODO (mlvdv) try to set frame selection to match global focus at creation; doesn't display.
frameFocusChanged(null, inspection().focus().stackFrame());
}
@SuppressWarnings("unchecked")
@Override
protected void refreshState(boolean force) {
if (stack != null && stack.thread() != null && stack.thread().isLive()) {
if (force || stack.lastUpdated() == null || vm().state().newerThan(lastUpdatedState)) {
final List<MaxStackFrame> frames = stack.frames(maxFramesDisplay);
if (!frames.isEmpty()) {
if (force || stack.lastChanged().newerThan(this.lastChangedState)) {
// Set the list to an empty model while doing the update so that all
// the addition of each element to the list doing not fire a whole
// bunch of list GUI update events. This really helps for deep stacks.
stackFrameList.setModel(emptyModel);
stackFrameListModel.clear();
int omitted = -1;
for (MaxStackFrame stackFrame : frames) {
if (stackFrame instanceof MaxStackFrame.Truncated) {
final MaxStackFrame.Truncated tr = (MaxStackFrame.Truncated) stackFrame;
if (tr.omitted() >= 0) {
stackFrameListModel.addElement(stackFrame);
omitted = tr.omitted();
break;
}
}
stackFrameListModel.addElement(stackFrame);
}
stackFrameList.setModel(stackFrameListModel);
if (omitted >= 0) {
inspection().gui().informationMessage("stack depth exceeds " + maxFramesDisplay + ": truncated " + omitted + " frames", "Stack View");
}
this.lastChangedState = stack.lastChanged();
} else {
// The stack is structurally unchanged with respect to methods,
// which typically happens after a single step.
// Avoid a complete redisplay for performance reasons.
// However, the object representing the top frame may be different,
// in which case the state of the old frame object is out of date.
final MaxStackFrame newTopFrame = frames.get(0);
stackFrameListModel.set(0, newTopFrame);
}
lastUpdatedState = stack.lastUpdated();
}
}
}
// The title displays thread state, so must be updated.
setTitle();
}
@Override
public void threadFocusSet(MaxThread oldThread, MaxThread thread) {
reconstructView();
}
@Override
public void frameFocusChanged(MaxStackFrame oldStackFrame, MaxStackFrame newStackFrame) {
if (stackFrameList != null) {
if (newStackFrame == null || newStackFrame.stack().thread() != this.stack.thread()) {
stackFrameList.clearSelection();
} else {
final int oldIndex = stackFrameList.getSelectedIndex();
for (int index = 0; index < stackFrameListModel.getSize(); index++) {
final MaxStackFrame stackFrame = (MaxStackFrame) stackFrameListModel.get(index);
if (stackFrame.isSameFrame(newStackFrame)) {
if (index != oldIndex) {
stackFrameList.setSelectedIndex(index);
stackFrameList.ensureIndexIsVisible(index);
}
break;
}
}
}
}
}
@Override
public void vmProcessTerminated() {
reconstructView();
}
@Override
public void viewClosing() {
// Unsubscribe to view preferences, when we get them.
super.viewClosing();
}
private String javaStackFrameName(MaxStackFrame.Compiled javaStackFrame) {
final Address address = javaStackFrame.ip();
final MaxCompilation compilation = vm().machineCode().findCompilation(address);
String name;
if (compilation != null) {
name = inspection().nameDisplay().veryShortName(compilation);
final TeleClassMethodActor teleClassMethodActor = compilation.getTeleClassMethodActor();
if (teleClassMethodActor != null && teleClassMethodActor.isSubstituted()) {
name = name + inspection().nameDisplay().methodSubstitutionShortAnnotation(teleClassMethodActor);
}
} else {
final MethodActor classMethodActor = javaStackFrame.compilation().classMethodActor();
name = classMethodActor.format("%h.%n");
}
return name;
}
private InspectorPopupMenu getPopupMenu(int row, MouseEvent mouseEvent) {
final MaxStackFrame stackFrame = (MaxStackFrame) stackFrameListModel.get(row);
final InspectorPopupMenu menu = new InspectorPopupMenu("Stack Frame");
menu.add(new InspectorAction(inspection(), "Select frame (Left-Button)") {
@Override
protected void procedure() {
inspection().focus().setStackFrame(stackFrame, false);
}
});
if (stackFrame instanceof MaxStackFrame.Compiled) {
final MaxStackFrame.Compiled javaStackFrame = (MaxStackFrame.Compiled) stackFrame;
final String frameName = javaStackFrameName(javaStackFrame);
menu.add(actions().viewtackFrameMemory(javaStackFrame, "View memory for frame" + frameName));
menu.add(new InspectorAction(inspection(), "View frame " + frameName) {
@Override
protected void procedure() {
inspection().focus().setStackFrame(stackFrame, false);
views().activateSingletonView(ViewKind.STACK_FRAME).highlight();
}
});
}
if (stackFrame instanceof MaxStackFrame.Native) {
final Pointer instructionPointer = stackFrame.ip();
final MaxNativeFunction externalCode = vm().machineCode().findNativeFunction(instructionPointer);
if (externalCode == null) {
menu.add(new InspectorAction(inspection(), "Open external code dialog...") {
@Override
protected void procedure() {
MaxCodeLocation codeLocation = stackFrame.codeLocation();
if (codeLocation == null) {
gui().errorMessage("Stack frame has no code location");
} else {
focus().setCodeLocation(codeLocation, true);
}
}
});
}
}
return menu;
}
private final class CopyStackToClipboardAction extends InspectorAction {
private CopyStackToClipboardAction() {
super(inspection(), "Copy stack list to clipboard");
}
@SuppressWarnings("unchecked")
@Override
public void procedure() {
// (mlvdv) This is pretty awkward, but has the virtue that it reproduces exactly what's displayed. Could be improved.
final StringBuilder result = new StringBuilder(100);
final ListCellRenderer cellRenderer = stackFrameList.getCellRenderer();
for (int index = 0; index < stackFrameListModel.getSize(); index++) {
final Object elementAt = stackFrameListModel.getElementAt(index);
final InspectorLabel label = (InspectorLabel) cellRenderer.getListCellRendererComponent(stackFrameList, elementAt, index, false, false);
result.append(label.getTextDeHtmlify()).append("\n");
}
gui().postToClipboard(result.toString());
}
}
}