/******************************************************************************* * 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)); } }