/*******************************************************************************
* Copyright (c) 2008, 2011 Red Hat, Inc. and others.
* 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:
* Elliott Baron <ebaron@redhat.com> - initial API and implementation
* Martin Oberhuber (Wind River) - [360085] Fix valgrind problem marker lifecycle
*******************************************************************************/
package org.eclipse.linuxtools.internal.valgrind.launch;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;
import org.eclipse.cdt.debug.core.CDebugUtils;
import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.cdt.launch.AbstractCLaunchDelegate;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.ISourceLocator;
import org.eclipse.debug.core.sourcelookup.containers.LocalFileStorage;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.sourcelookup.ISourceLookupResult;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.linuxtools.internal.valgrind.core.ValgrindCommand;
import org.eclipse.linuxtools.internal.valgrind.core.ValgrindCoreParser;
import org.eclipse.linuxtools.internal.valgrind.core.ValgrindError;
import org.eclipse.linuxtools.internal.valgrind.core.ValgrindInfo;
import org.eclipse.linuxtools.internal.valgrind.core.ValgrindStackFrame;
import org.eclipse.linuxtools.internal.valgrind.ui.ValgrindUIPlugin;
import org.eclipse.linuxtools.internal.valgrind.ui.ValgrindViewPart;
import org.eclipse.linuxtools.valgrind.core.CommandLineConstants;
import org.eclipse.linuxtools.valgrind.core.IValgrindMessage;
import org.eclipse.linuxtools.valgrind.launch.IValgrindLaunchDelegate;
import org.eclipse.linuxtools.valgrind.launch.IValgrindOutputDirectoryProvider;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.Version;
public class ValgrindLaunchConfigurationDelegate extends AbstractCLaunchDelegate {
private static final String NO = "no"; //$NON-NLS-1$
private static final String YES = "yes"; //$NON-NLS-1$
private static final String EQUALS = "="; //$NON-NLS-1$
private static final String LOG_FILE = CommandLineConstants.LOG_PREFIX + "%p.txt"; //$NON-NLS-1$
private static final Pattern CORE_PATTERN = Pattern.compile("^.*\\.txt\\.core\\.[0-9]+$"); //$NON-NLS-1$
private static final FileFilter LOG_FILTER = pathname -> pathname.getName().startsWith(CommandLineConstants.LOG_PREFIX) && !CORE_PATTERN.matcher(pathname.getName()).matches();
protected String toolID;
protected ValgrindCommand command;
protected IPath outputPath;
protected IValgrindLaunchDelegate dynamicDelegate;
protected ILaunchConfiguration config;
protected ILaunch launch;
protected IProcess process;
protected String launchStr;
protected Version valgrindVersion; // null if not used
@Override
public void launch(ILaunchConfiguration config, String mode,
ILaunch launch, IProgressMonitor m) throws CoreException {
if (m == null) {
m = new NullProgressMonitor();
}
SubMonitor monitor = SubMonitor.convert(m, Messages.getString("ValgrindLaunchConfigurationDelegate.Profiling_Local_CCPP_Application"), 10); //$NON-NLS-1$
// check for cancellation
if (monitor.isCanceled()) {
return;
}
this.config = config;
this.launch = launch;
try {
IProject project = CDebugUtils.verifyCProject(config).getProject();
ValgrindUIPlugin.getDefault().setProfiledProject(project);
command = getValgrindCommand();
// remove any output from previous run
ValgrindUIPlugin.getDefault().resetView();
// reset stored launch data
getPlugin().setCurrentLaunchConfiguration(null);
getPlugin().setCurrentLaunch(null);
String valgrindCommand= getValgrindCommand().getValgrindCommand();
// also ensure Valgrind version is usable
try {
valgrindVersion = getPlugin().getValgrindVersion(project);
} catch(CoreException e) {
// if versioning failed, issue an error dialog and return
errorDialog(Messages.getString("ValgrindLaunchConfigurationDelegate.Valgrind_version_failed_msg"), //$NON-NLS-1$
e.getMessage());
return;
}
monitor.worked(1);
IPath exePath = CDebugUtils.verifyProgramPath(config);
String[] arguments = getProgramArgumentsArray(config);
File workDir = getWorkingDirectory(config);
if (workDir == null) {
workDir = new File(System.getProperty("user.home", ".")); //$NON-NLS-1$ //$NON-NLS-2$
}
// set output directory in config
IValgrindOutputDirectoryProvider provider = getPlugin().getOutputDirectoryProvider();
setOutputPath(config, provider.getOutputPath());
outputPath = verifyOutputPath(config);
// create/empty output directory
createDirectory(outputPath);
// tool that was launched
toolID = getTool(config);
// ask tool extension for arguments
dynamicDelegate = getDynamicDelegate(toolID);
String[] opts = getValgrindArgumentsArray(config);
// set the default source locator if required
setDefaultSourceLocator(launch, config);
ArrayList<String> cmdLine = new ArrayList<>(1 + arguments.length);
cmdLine.add(valgrindCommand);
cmdLine.addAll(Arrays.asList(opts));
cmdLine.add(exePath.toPortableString());
cmdLine.addAll(Arrays.asList(arguments));
String[] commandArray = cmdLine.toArray(new String[cmdLine.size()]);
boolean usePty = config.getAttribute(ICDTLaunchConfigurationConstants.ATTR_USE_TERMINAL, ICDTLaunchConfigurationConstants.USE_TERMINAL_DEFAULT);
monitor.worked(1);
// check for cancellation
if (monitor.isCanceled()) {
return;
}
// call Valgrind
command.execute(commandArray, getEnvironment(config), workDir, usePty, project);
monitor.worked(3);
process = createNewProcess(launch, command.getProcess(), commandArray[0]);
// set the command line used
process.setAttribute(IProcess.ATTR_CMDLINE, command.getCommandLine());
while (!process.isTerminated()) {
Thread.sleep(100);
}
// store these for use by other classes
getPlugin().setCurrentLaunchConfiguration(config);
getPlugin().setCurrentLaunch(launch);
// parse Valgrind logs
IValgrindMessage[] messages = parseLogs(outputPath);
// create launch summary string to distinguish this launch
launchStr = createLaunchStr();
// create view
ValgrindUIPlugin.getDefault().createView(launchStr, toolID);
// set log messages
ValgrindViewPart view = ValgrindUIPlugin.getDefault().getView();
view.setMessages(messages);
monitor.worked(1);
// pass off control to extender
dynamicDelegate.handleLaunch(config, launch, outputPath, monitor.newChild(2));
// initialize tool-specific part of view
dynamicDelegate.initializeView(view.getDynamicView(), launchStr, monitor.newChild(1));
// refresh view
ValgrindUIPlugin.getDefault().refreshView();
// show view
ValgrindUIPlugin.getDefault().showView();
// set up resource listener for post-build events.
ResourcesPlugin.getWorkspace().addResourceChangeListener(
new ProjectBuildListener(project), IResourceChangeEvent.POST_BUILD);
monitor.worked(1);
} catch (IOException e) {
abort(Messages.getString("ValgrindLaunchConfigurationDelegate.Error_starting_process"), e, ICDTLaunchConfigurationConstants.ERR_INTERNAL_ERROR); //$NON-NLS-1$
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
m.done();
}
}
protected IValgrindMessage[] parseLogs(IPath outputPath) throws IOException, CoreException {
List<IValgrindMessage> messages = new ArrayList<>();
for (File log : outputPath.toFile().listFiles(LOG_FILTER)) {
ValgrindCoreParser parser = new ValgrindCoreParser(log, launch);
IValgrindMessage[] results = parser.getMessages();
if (results.length == 0){
results = new IValgrindMessage[1];
results[0] = new ValgrindInfo(null, Messages.getString("ValgrindOutputView.No_output"), launch); //$NON-NLS-1$
}
messages.addAll(Arrays.asList(results));
createMarkers(results);
}
return messages.toArray(new IValgrindMessage[messages.size()]);
}
private void createMarkers(IValgrindMessage[] messages) throws CoreException {
// find the topmost stack frame within the workspace to annotate with marker
// traverse nested errors as well
Stack<IValgrindMessage> messageStack = new Stack<>();
messageStack.addAll(Arrays.asList(messages));
while (!messageStack.isEmpty()) {
IValgrindMessage message = messageStack.pop();
IMarker marker = null;
IValgrindMessage[] children = message.getChildren();
for (int i = 0; i < children.length; i++) {
// if we've found our marker we don't care about any further frames in this stack
if (children[i] instanceof ValgrindStackFrame && marker == null) {
ValgrindStackFrame frame = (ValgrindStackFrame) children[i];
if (frame.getLine() > 0) {
ISourceLocator locator = frame.getSourceLocator();
ISourceLookupResult result = DebugUITools.lookupSource(frame.getFile(), locator);
Object sourceElement = result.getSourceElement();
if (sourceElement != null) {
// Resolve IResource in case we get a LocalFileStorage object
if (sourceElement instanceof LocalFileStorage) {
IPath filePath = ((LocalFileStorage) sourceElement).getFullPath();
URI fileURI = URIUtil.toURI(filePath);
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IFile[] files = root.findFilesForLocationURI(fileURI);
if (files.length > 0) {
// Take the first match
sourceElement = files[0];
}
}
if (sourceElement instanceof IResource) {
IResource resource = (IResource) sourceElement;
marker = resource.createMarker(ValgrindLaunchPlugin.MARKER_TYPE);
marker.setAttribute(IMarker.MESSAGE, message.getText());
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
marker.setAttribute(IMarker.LINE_NUMBER, frame.getLine());
}
}
}
}
else if (children[i] instanceof ValgrindError) {
// nested error
messageStack.push(children[i]);
}
}
}
}
protected IProcess createNewProcess(ILaunch launch, Process systemProcess, String programName) {
return DebugPlugin.newProcess(launch, systemProcess, renderProcessLabel(programName));
}
protected ValgrindCommand getValgrindCommand() {
return getPlugin().getValgrindCommand();
}
protected ValgrindLaunchPlugin getPlugin() {
return ValgrindLaunchPlugin.getDefault();
}
protected IValgrindLaunchDelegate getDynamicDelegate(String toolID) throws CoreException {
return getPlugin().getToolDelegate(toolID);
}
private IPath verifyOutputPath(ILaunchConfiguration config) throws CoreException {
IPath result = null;
String strPath = config.getAttribute(LaunchConfigurationConstants.ATTR_INTERNAL_OUTPUT_DIR, (String) null);
if (strPath != null) {
result = Path.fromPortableString(strPath);
}
if (result == null) {
abort(Messages.getString("ValgrindLaunchConfigurationDelegate.Retrieving_location_failed"), null, ICDTLaunchConfigurationConstants.ERR_INTERNAL_ERROR); //$NON-NLS-1$
}
return result;
}
protected void setOutputPath(ILaunchConfiguration config, IPath outputPath) throws CoreException {
ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
wc.setAttribute(LaunchConfigurationConstants.ATTR_INTERNAL_OUTPUT_DIR, outputPath.toPortableString());
wc.doSave();
}
protected void createDirectory(IPath path) throws IOException {
File outputDir = path.toFile();
if (outputDir.exists()) {
// delete any preexisting files
for (File outputFile : outputDir.listFiles()) {
if (outputFile.isFile() && !outputFile.delete()) {
throw new IOException(NLS.bind(Messages.getString("ValgrindOutputDirectory.Couldnt_delete"), outputFile.getAbsolutePath())); //$NON-NLS-1$
}
}
} else if (!outputDir.mkdir()) {
throw new IOException(NLS.bind(Messages.getString("ValgrindOutputDirectory.Couldnt_create"), outputDir.getAbsolutePath())); //$NON-NLS-1$
}
}
private String createLaunchStr() {
return config.getName() + " [" + getPlugin().getToolName(toolID) + "] " + process.getLabel(); //$NON-NLS-1$ //$NON-NLS-2$
}
protected String[] getValgrindArgumentsArray(ILaunchConfiguration config) throws CoreException {
ArrayList<String> opts = new ArrayList<>();
opts.add(CommandLineConstants.OPT_TOOL + EQUALS + getPlugin().getToolName(toolID));
opts.add(CommandLineConstants.OPT_QUIET); // suppress uninteresting output
opts.add(CommandLineConstants.OPT_LOGFILE + EQUALS + outputPath.append(LOG_FILE).toPortableString());
opts.add(CommandLineConstants.OPT_TRACECHILD + EQUALS + (config.getAttribute(LaunchConfigurationConstants.ATTR_GENERAL_TRACECHILD, LaunchConfigurationConstants.DEFAULT_GENERAL_TRACECHILD) ? YES : NO));
opts.add(CommandLineConstants.OPT_CHILDSILENT + EQUALS + YES); // necessary for parsing
opts.add(CommandLineConstants.OPT_FREERES + EQUALS + (config.getAttribute(LaunchConfigurationConstants.ATTR_GENERAL_FREERES, LaunchConfigurationConstants.DEFAULT_GENERAL_FREERES) ? YES : NO));
opts.add(CommandLineConstants.OPT_DEMANGLE + EQUALS + (config.getAttribute(LaunchConfigurationConstants.ATTR_GENERAL_DEMANGLE, LaunchConfigurationConstants.DEFAULT_GENERAL_DEMANGLE) ? YES : NO));
opts.add(CommandLineConstants.OPT_NUMCALLERS + EQUALS + config.getAttribute(LaunchConfigurationConstants.ATTR_GENERAL_NUMCALLERS, LaunchConfigurationConstants.DEFAULT_GENERAL_NUMCALLERS));
opts.add(CommandLineConstants.OPT_ERRLIMIT + EQUALS + (config.getAttribute(LaunchConfigurationConstants.ATTR_GENERAL_ERRLIMIT, LaunchConfigurationConstants.DEFAULT_GENERAL_ERRLIMIT) ? YES : NO));
opts.add(CommandLineConstants.OPT_BELOWMAIN + EQUALS + (config.getAttribute(LaunchConfigurationConstants.ATTR_GENERAL_BELOWMAIN, LaunchConfigurationConstants.DEFAULT_GENERAL_BELOWMAIN) ? YES : NO));
opts.add(CommandLineConstants.OPT_MAXFRAME + EQUALS + config.getAttribute(LaunchConfigurationConstants.ATTR_GENERAL_MAXFRAME, LaunchConfigurationConstants.DEFAULT_GENERAL_MAXFRAME));
// 3.4.0 specific
if (valgrindVersion == null || valgrindVersion.compareTo(ValgrindLaunchPlugin.VER_3_4_0) >= 0) {
boolean useMainStack = config.getAttribute(LaunchConfigurationConstants.ATTR_GENERAL_MAINSTACK_BOOL, LaunchConfigurationConstants.DEFAULT_GENERAL_MAINSTACK_BOOL);
if (useMainStack) {
opts.add(CommandLineConstants.OPT_MAINSTACK + EQUALS + config.getAttribute(LaunchConfigurationConstants.ATTR_GENERAL_MAINSTACK, LaunchConfigurationConstants.DEFAULT_GENERAL_MAINSTACK));
}
}
// 3.6.0 specific
if (valgrindVersion == null || valgrindVersion.compareTo(ValgrindLaunchPlugin.VER_3_6_0) >= 0) {
if (config.getAttribute(LaunchConfigurationConstants.ATTR_FULLPATH_AFTER, LaunchConfigurationConstants.DEFAULT_FULLPATH_AFTER) == LaunchConfigurationConstants.DEFAULT_FULLPATH_AFTER)
opts.add("--fullpath-after" + EQUALS); //$NON-NLS-1$ //TODO: Make this API in CommandLineConstants Interface.
if (config.getAttribute(LaunchConfigurationConstants.ATTR_GENERAL_DSYMUTIL, LaunchConfigurationConstants.DEFAULT_GENERAL_DSYMUTIL) != LaunchConfigurationConstants.DEFAULT_GENERAL_DSYMUTIL)
opts.add(CommandLineConstants.OPT_DSYMUTIL + EQUALS + (config.getAttribute(LaunchConfigurationConstants.ATTR_GENERAL_DSYMUTIL, LaunchConfigurationConstants.DEFAULT_GENERAL_DSYMUTIL) ? YES : NO));
}
List<?> suppFiles = config.getAttribute(LaunchConfigurationConstants.ATTR_GENERAL_SUPPFILES, LaunchConfigurationConstants.DEFAULT_GENERAL_SUPPFILES);
for (Object strpath : suppFiles) {
IPath suppfile = getPlugin().parseWSPath((String) strpath);
if (suppfile != null) {
opts.add(CommandLineConstants.OPT_SUPPFILE + EQUALS + suppfile.toPortableString());
}
}
opts.addAll(Arrays.asList(dynamicDelegate.getCommandArray(config, valgrindVersion, outputPath)));
String otherOptions = config.getAttribute(LaunchConfigurationConstants.ATTR_GENERAL_EXTRA_OPTIONS,"").trim(); //$NON-NLS-1$
if (!otherOptions.isEmpty()) {
opts.addAll(Arrays.asList(otherOptions.split("\\s+"))); //$NON-NLS-1$
}
String[] ret = new String[opts.size()];
return opts.toArray(ret);
}
protected String getTool(ILaunchConfiguration config) throws CoreException {
return config.getAttribute(LaunchConfigurationConstants.ATTR_TOOL, LaunchConfigurationConstants.DEFAULT_TOOL);
}
@Override
protected String getPluginID() {
return ValgrindLaunchPlugin.PLUGIN_ID;
}
@Override
public boolean finalLaunchCheck(ILaunchConfiguration configuration,
String mode, IProgressMonitor monitor) throws CoreException {
//Delete our own problem markers
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
root.deleteMarkers(ValgrindLaunchPlugin.MARKER_TYPE, true, IResource.DEPTH_INFINITE);
return super.finalLaunchCheck(configuration, mode, monitor);
}
// Display an error dialog to denote an error scenario.
private void errorDialog(final String title, final String message) {
ValgrindLaunchPlugin.getShell().getDisplay().asyncExec( () -> MessageDialog.openError(ValgrindLaunchPlugin.getShell(), title, message));
}
}