| /********************************************************************** |
| * Copyright (c) 2005, 2010 IBM Corporation. |
| * 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: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.ptp.pldt.common.analysis; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.StringTokenizer; |
| |
| import org.eclipse.cdt.core.dom.IName; |
| import org.eclipse.cdt.core.dom.ast.ASTVisitor; |
| import org.eclipse.cdt.core.dom.ast.IASTDeclaration; |
| import org.eclipse.cdt.core.dom.ast.IASTExpression; |
| import org.eclipse.cdt.core.dom.ast.IASTFileLocation; |
| import org.eclipse.cdt.core.dom.ast.IASTIdExpression; |
| import org.eclipse.cdt.core.dom.ast.IASTLiteralExpression; |
| import org.eclipse.cdt.core.dom.ast.IASTMacroExpansion; |
| import org.eclipse.cdt.core.dom.ast.IASTName; |
| import org.eclipse.cdt.core.dom.ast.IASTNode; |
| import org.eclipse.cdt.core.dom.ast.IASTNodeLocation; |
| import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroDefinition; |
| import org.eclipse.cdt.core.dom.ast.IASTStatement; |
| import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; |
| import org.eclipse.cdt.core.dom.ast.IBinding; |
| import org.eclipse.cdt.core.dom.ast.c.CASTVisitor; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.jface.dialogs.MessageDialogWithToggle; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.ptp.pldt.common.Artifact; |
| import org.eclipse.ptp.pldt.common.CommonPlugin; |
| import org.eclipse.ptp.pldt.common.ScanReturn; |
| import org.eclipse.ptp.pldt.common.messages.Messages; |
| import org.eclipse.ptp.pldt.common.util.SourceInfo; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.ui.IWorkbench; |
| import org.eclipse.ui.IWorkbenchWindow; |
| import org.eclipse.ui.PlatformUI; |
| |
| /** |
| * This dom-walker helper collects interesting constructs (currently |
| * function calls and constants), and adds markers to the source file for |
| * C/C++ code. <br> |
| * This base class encapsulates the common behaviors for both C and C++ |
| * code and for visitors looking for MPI, OpenMP, LAPI, etc. etc. types of artifacts |
| * |
| * @author Beth Tibbitts |
| * @since 4.0 |
| * |
| */ |
| public class PldtAstVisitor extends CASTVisitor { |
| |
| |
| /** |
| * @since 4.0 |
| */ |
| public static String ARTIFACT_CALL = "Artifact Call"; //$NON-NLS-1$ |
| /** |
| * @since 4.0 |
| */ |
| public static String ARTIFACT_CONSTANT = "Artifact Constant"; //$NON-NLS-1$ |
| protected static String ARTIFACT_NAME = "Artifact Name"; //$NON-NLS-1$ |
| protected static String PREFIX = ""; //$NON-NLS-1$ |
| private static /*final*/ boolean traceOn=false; |
| |
| private static boolean dontAskToModifyIncludePathAgain=false; |
| protected boolean allowPrefixOnlyMatch=false; |
| |
| /** |
| * List of include paths that we'll probably want to consider in the work that this visitor does. |
| * For example, only paths found in this list (specified in PLDT preferences) would be considered |
| * to be a path from which definitions of "Artifacts" would be found. |
| * <br> |
| * Note that this can now be dynamically modified during artifact analysis, thus no longer final |
| */ |
| private /*final*/ List<String> includes_; |
| private final String fileName; |
| private final ScanReturn scanReturn; |
| |
| /** |
| * |
| * @param includes |
| * list of include paths that we'll probably want to consider in |
| * the work that this visitor does |
| * @param fileName |
| * the name of the file that this visitor is visiting(?) |
| * @param prefixOnlyMatch |
| * if true, then artifact is recognized if it starts with the plugin-specific prefix |
| * (e.g. "MPI_" etc.) instead of forcing a lookup of the location of the header file |
| * in which the API is found. This proves to be difficult for users to get right, so prefix-only |
| * recognition of artifacts is allowed here. |
| * @param scanReturn |
| * the ScanReturn object to which the artifacts that we find will |
| * be appended. |
| |
| */ |
| public PldtAstVisitor(List<String> includes, String fileName, boolean prefixOnlyMatch, ScanReturn scanReturn ) { |
| this.includes_ = includes; |
| this.fileName = fileName; |
| this.scanReturn = scanReturn; |
| dontAskToModifyIncludePathAgain=false; |
| this.allowPrefixOnlyMatch=prefixOnlyMatch; |
| if(!traceOn) traceOn=CommonPlugin.getTraceOn(); |
| if(traceOn)System.out.println("PldtAstVisitor, traceOn="+traceOn); //$NON-NLS-1$ |
| } |
| /** |
| * Constructor without prefixOnlyMatch arg, assumes false |
| */ |
| public PldtAstVisitor(List<String> includes, String fileName, ScanReturn scanReturn) { |
| this(includes, fileName, false, scanReturn); |
| } |
| |
| |
| /** |
| * Skip statements that are included. |
| */ |
| public int visit(IASTStatement statement) { |
| if (preprocessorIncluded(statement)) { |
| return ASTVisitor.PROCESS_SKIP; |
| } |
| return ASTVisitor.PROCESS_CONTINUE; |
| } |
| |
| /** |
| * Skip decls that are included. |
| * |
| * @param declaration |
| * @return |
| */ |
| public int visit(IASTDeclaration declaration) {//called; both paths get taken |
| if (preprocessorIncluded(declaration)) { |
| return ASTVisitor.PROCESS_SKIP; |
| } |
| return ASTVisitor.PROCESS_CONTINUE; |
| } |
| |
| /** |
| * Process a function name from an expression and determine if it should be |
| * marked as an Artifact. If so, append it to the scanReturn object that |
| * this visitor is populating. |
| * |
| * An artifact is a function name that was found in the MPI include path, |
| * as defined in the MPI preferences. |
| * |
| * @param astExpr |
| * @param funcName |
| */ |
| public void processFuncName(IASTName funcName, IASTExpression astExpr) { |
| //IASTTranslationUnit tu = funcName.getTranslationUnit(); |
| String strName=funcName.toString(); |
| if ((this.allowPrefixOnlyMatch&&matchesPrefix(strName)) || isArtifact(funcName)) { // brt C++ test 2/16/10 |
| SourceInfo sourceInfo = getSourceInfo(astExpr, Artifact.FUNCTION_CALL); |
| if (sourceInfo != null) { |
| if(traceOn) System.out.println("found artifact: " + funcName.toString()); //$NON-NLS-1$ |
| // Note: we're determining the artifact name twice. (also in chooseName()) |
| String artName=funcName.toString(); |
| String rawName=funcName.getRawSignature(); |
| //String bName=funcName.getBinding().getName(); |
| if(!artName.equals(rawName)) { |
| if(rawName.length()==0)rawName=" "; //$NON-NLS-1$ |
| artName=artName+" ("+rawName+")"; // indicate orig pre-pre-processor value in parens //$NON-NLS-1$ //$NON-NLS-2$ |
| // note: currently rawName seems to always be empty. |
| } |
| scanReturn.addArtifact(new Artifact(fileName, sourceInfo |
| .getStartingLine(), 1, artName, sourceInfo)); |
| |
| } |
| } |
| } |
| |
| /** |
| * |
| * @param astExpr |
| */ |
| public void processExprWithConstant(IASTExpression astExpr) { |
| IASTName funcName = ((IASTIdExpression) astExpr).getName(); |
| //IASTTranslationUnit tu = funcName.getTranslationUnit(); |
| String strName=funcName.toString(); |
| if ((this.allowPrefixOnlyMatch&& matchesPrefix(strName)) || isArtifact(funcName)) { |
| SourceInfo sourceInfo = getSourceInfo(astExpr, Artifact.FUNCTION_CALL); |
| if (sourceInfo != null) { |
| //System.out.println("found MPI artifact: " + funcName.toString()); |
| scanReturn.addArtifact(new Artifact(fileName, sourceInfo.getStartingLine(), 1, |
| funcName.toString(), sourceInfo)); |
| } |
| } |
| } |
| |
| /** |
| * Determines if the funcName is an instance of the type of artifact in |
| * which we are interested. <br> |
| * An artifact is a function name that was found in the include path (e.g. MPI or OpenMP), |
| * as defined in the PLDT preferences. |
| * |
| * @param funcName |
| */ |
| protected boolean isArtifact(IASTName funcName) { |
| IBinding binding = funcName.resolveBinding(); |
| String name = binding.getName(); |
| String rawSig = funcName.getRawSignature(); |
| name = chooseName(name, rawSig); |
| |
| IASTTranslationUnit tu = funcName.getTranslationUnit(); |
| |
| // Use index instead of full AST for the header file inclusion stuff |
| // Without full AST, further introspection into APIs will need to |
| // explicitly ask for it from the Index |
| IName[] names = tu.getDeclarations(binding); // get from the index not ast of e.g. header files |
| for (int i = 0; i < names.length; i++) { |
| IName name2 = names[i]; |
| IASTFileLocation floc = name2.getFileLocation(); |
| if (floc == null) { |
| if (traceOn) |
| System.out.println("PldtAstVisitor IASTFileLocn null for "+ name2 + " (" + funcName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| return false; // (e.g. 'ptr' ) |
| } |
| String filename = floc.getFileName(); |
| IPath path = new Path(filename); |
| if (isInIncludePath(path)) { |
| // System.out.println(" found "+path+" in artifact path (via index)!"); |
| return true; |
| } else { |
| if (traceOn) { |
| System.out.println(name+ " was found in "+ path+ " but PLDT preferences have been set to only include: "+ includes_.toString()); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| // add them here? |
| if (allowIncludePathAdd()) { |
| boolean addit = addIncludePath(path, name,dontAskToModifyIncludePathAgain); |
| if (addit) |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Choose how to distinguish between binding name, and raw signature.<br> |
| * Could be overridden by subclasses if, for example, a name with a prefix e.g. "MPI::foo" should be preferred over "foo".<br> |
| * Here, the default case is that we always choose the regular/binding name, unless it's empty, in which case we choose the rawSignature. |
| * @param bindingName |
| * @param rawSignature |
| * @return |
| */ |
| protected String chooseName(String bindingName, String rawSignature){ |
| String name=bindingName; |
| if(bindingName.length()==0) { |
| name=rawSignature; |
| } |
| return name; |
| } |
| |
| public void processMacroLiteral(IASTLiteralExpression expression) { |
| IASTNodeLocation[] locations = expression.getNodeLocations(); |
| if ((locations.length == 1) && (locations[0] instanceof IASTMacroExpansion)) {//path taken ¬ |
| // found a macro, does it come from the include path required to be "one of ours"? |
| IASTMacroExpansion astMacroExpansion = (IASTMacroExpansion) locations[0]; |
| IASTPreprocessorMacroDefinition preprocessorMacroDefinition = astMacroExpansion |
| .getMacroDefinition(); |
| // String shortName = |
| // preprocessorMacroDefinition.getName().toString()+'='+literal; |
| String shortName = preprocessorMacroDefinition.getName().toString(); |
| IASTNodeLocation[] preprocessorLocations = preprocessorMacroDefinition |
| .getNodeLocations(); |
| while ((preprocessorLocations.length == 1) |
| && (preprocessorLocations[0] instanceof IASTMacroExpansion)) { |
| preprocessorLocations = ((IASTMacroExpansion) preprocessorLocations[0]) |
| .getMacroDefinition().getNodeLocations(); |
| } |
| |
| if ((preprocessorLocations.length == 1) |
| && isInIncludePath(new Path(preprocessorLocations[0].asFileLocation() |
| .getFileName()))) { |
| SourceInfo sourceInfo = getSourceInfo(astMacroExpansion); |
| if (sourceInfo != null) { |
| scanReturn.addArtifact(new Artifact(fileName, sourceInfo.getStartingLine(), 1, // column: |
| shortName, sourceInfo)); |
| } |
| |
| } |
| } |
| } |
| |
| /** |
| * Is this path found in the include path in which we are interested? |
| * E.g. is it in the include path specified in PLDT preferences, |
| * which would identify it as an artifact of interest? |
| * |
| * @param includeFilePath under consideration |
| * @return true if this is found in the include path from PLDT preferences |
| */ |
| private boolean isInIncludePath(IPath includeFilePath) { |
| if (includeFilePath == null) |
| return false; |
| for (String includeDir : includes_) { |
| IPath includePath = new Path(includeDir); |
| if(traceOn)System.out.println("PldtAstVisitor: is "+includeFilePath+" found in "+includeDir+"?"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| if (includePath.isPrefixOf(includeFilePath)) |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Get exact source location info for a function call |
| * |
| * @param astExpr |
| * @param constructType |
| * @return |
| */ |
| private SourceInfo getSourceInfo(IASTExpression astExpr, int constructType) { |
| SourceInfo sourceInfo = null; |
| IASTNodeLocation[] locations = astExpr.getNodeLocations(); |
| if (locations.length == 1) { |
| IASTFileLocation astFileLocation = null; |
| if (locations[0] instanceof IASTFileLocation) { |
| astFileLocation = (IASTFileLocation) locations[0]; |
| } |
| // handle the case e.g. #define foo MPI_fn - recognize foo() as MPI_fn() |
| else if (locations[0] instanceof IASTMacroExpansion) { |
| IASTMacroExpansion me=(IASTMacroExpansion)locations[0]; |
| astFileLocation=me.asFileLocation(); |
| } |
| if(astFileLocation!=null) { |
| sourceInfo = new SourceInfo(); |
| sourceInfo.setStartingLine(astFileLocation.getStartingLineNumber()); |
| sourceInfo.setStart(astFileLocation.getNodeOffset()); |
| sourceInfo.setEnd(astFileLocation.getNodeOffset() + astFileLocation.getNodeLength()); |
| sourceInfo.setConstructType(constructType); |
| } |
| } |
| return sourceInfo; |
| } |
| |
| /** |
| * Get exact source location info for a constant originated from Macro |
| * expansion(s) |
| * |
| * @param iASTMacroExpansion |
| * represents the original macro |
| * @return |
| */ |
| private SourceInfo getSourceInfo(IASTMacroExpansion iASTMacroExpansion) { |
| SourceInfo sourceInfo = null; |
| IASTFileLocation iASTFileLocation = iASTMacroExpansion.asFileLocation(); |
| sourceInfo = new SourceInfo(); |
| sourceInfo.setStartingLine(iASTFileLocation.getStartingLineNumber()); |
| sourceInfo.setStart(iASTFileLocation.getNodeOffset()); |
| sourceInfo.setEnd(iASTFileLocation.getNodeOffset() + iASTFileLocation.getNodeLength()); |
| sourceInfo.setConstructType(Artifact.CONSTANT); |
| |
| return sourceInfo; |
| } |
| |
| private boolean preprocessorIncluded(IASTNode astNode) { |
| if (astNode.getFileLocation() == null) |
| return false; |
| String location = astNode.getFileLocation().getFileName(); |
| String tuFilePath = astNode.getTranslationUnit().getFilePath(); |
| return !location.equals(tuFilePath); |
| } |
| |
| public void processIdExprAsLiteral(IASTIdExpression expression) { |
| IASTName name = expression.getName(); |
| String strName=name.toString(); |
| if ((this.allowPrefixOnlyMatch&& matchesPrefix(strName)) || isArtifact(name)) {// brt C++ test 2/16/10 |
| SourceInfo sourceInfo = getSourceInfo(expression, Artifact.CONSTANT); |
| if (sourceInfo != null) { |
| scanReturn.addArtifact(new Artifact(fileName, sourceInfo.getStartingLine(), 1, // column: |
| name.toString(), sourceInfo)); |
| } |
| } |
| } |
| |
| /** |
| * allow dynamic adding to include path? Can be overridden by derived classes. |
| * @return |
| */ |
| public boolean allowIncludePathAdd() { |
| return !dontAskToModifyIncludePathAgain; |
| } |
| /** |
| * Replace the includes list in this visitor so the change will be recognized. |
| * @param includes |
| */ |
| @SuppressWarnings("unchecked") |
| protected void replaceIncludes(String includes) { |
| includes_=convertToList(includes); |
| } |
| @SuppressWarnings({ "unchecked"}) |
| public List convertToList(String stringList) |
| { |
| StringTokenizer st = new StringTokenizer(stringList, File.pathSeparator + "\n\r");//$NON-NLS-1$ |
| List dirs = new ArrayList(); |
| while (st.hasMoreElements()) { |
| dirs.add(st.nextToken()); |
| } |
| return dirs; |
| } |
| /** |
| * Add an include path to the prefs - probably found dynamically during analysis |
| * and requested to be added by the user |
| * <br> |
| * Note that the path will be to the actual file in which the name was found; |
| * the path that will be added to the prefs is the parent directory of that file. |
| * @param path |
| * @param name the name (function etc) that was found in the path |
| * @param dontAskAgain initial value of toggle "don't ask again" |
| * |
| * @returns whether the user chose to add the path or not |
| */ |
| public boolean addIncludePath( IPath path, String name/*, IPreferenceStore store, String id*/, boolean dontAskAgain) { |
| |
| IPreferenceStore store=getPreferenceStore(); |
| String id=getIncludesPrefID(); |
| String type=getTypeName(); |
| boolean doitThisTime=false; |
| |
| if(store==null || id == null) { |
| CommonPlugin.log(IStatus.ERROR, "PLDT: Visitor subclass does not implement getPreferenceStore() or "+ //$NON-NLS-1$ |
| "getIncludesPrefID() to return non-null values."); //$NON-NLS-1$ |
| return false; |
| } |
| |
| try { |
| String value = store.getString(id); |
| if(traceOn)System.out.println("value: "+value); //$NON-NLS-1$ |
| |
| if (!dontAskAgain) { |
| // probably inefficient string construction, but rarely called. |
| String msg = Messages.PldtAstVisitor_20 |
| + name |
| + Messages.PldtAstVisitor_21 |
| + path.toString() |
| + Messages.PldtAstVisitor_22 |
| + type |
| + Messages.PldtAstVisitor_23 |
| + value |
| + Messages.PldtAstVisitor_24; |
| String title = Messages.PldtAstVisitor_25 + type + Messages.PldtAstVisitor_26; |
| boolean[] twoAnswers = askUI(title, msg, dontAskToModifyIncludePathAgain); |
| doitThisTime = twoAnswers[0]; |
| dontAskAgain = twoAnswers[1]; |
| dontAskToModifyIncludePathAgain = dontAskAgain; |
| } |
| |
| if (doitThisTime) { |
| String s = java.io.File.pathSeparator; |
| String parent = path.toFile().getParent(); |
| // add path separator (: or ; ?) if necessary |
| if (!value.endsWith(s)) { |
| value += s; |
| } |
| // add this new include path location to the value stored in |
| // preferences, and add it to the value within this class as well. |
| value += parent + s; |
| store.putValue(id, value); |
| replaceIncludes(value); |
| } |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| return doitThisTime; |
| } |
| |
| /** |
| * needs to be overrridden for derived classes that need to dynamically update the pref store |
| * e.g. for the includes path. This type name is used for messages, etc. |
| * @return artifact type name such as "MPI", "OpenMP" etc. |
| */ |
| protected String getTypeName() { |
| return ""; //$NON-NLS-1$ |
| } |
| |
| /** |
| * needs to be overrridden for derived classes that need to dynamically update the pref store |
| * e.g. for the includes path |
| * @return |
| */ |
| protected String getIncludesPrefID() { |
| return null; |
| } |
| /** |
| * needs to be overrridden for derived classes that need to dynamically update the pref store |
| * e.g. for the includes path |
| * @return |
| */ |
| protected IPreferenceStore getPreferenceStore() { |
| return null; |
| } |
| |
| |
| /** |
| * Dialog to ask a question in the UI thread |
| * @author beth |
| * |
| */ |
| public boolean[] askUI(final String title, final String message, boolean dontAskAgain) { |
| boolean[] twoAnswers = new boolean[2]; |
| RunGetAnswer runner = new RunGetAnswer(title,message,dontAskAgain); |
| |
| Display.getDefault().syncExec(runner); |
| boolean answer=runner.getAnswer(); |
| dontAskAgain=runner.getDontAskAgain(); |
| twoAnswers[0]=answer; |
| twoAnswers[1]=dontAskAgain; |
| return twoAnswers; |
| } |
| /** |
| * Runnable used by askUI to ask a question in the UI thread |
| * @author beth |
| * |
| */ |
| class RunGetAnswer implements Runnable { |
| boolean answer, dontAskAgain; |
| String title, message; |
| |
| RunGetAnswer(String title, String message, boolean initialToggleState) { |
| this.title = title; |
| this.message = message; |
| this.dontAskAgain=initialToggleState; |
| } |
| |
| public void run() { |
| IWorkbench wb = PlatformUI.getWorkbench(); |
| IWorkbenchWindow w = wb.getActiveWorkbenchWindow(); |
| Shell shell = w.getShell(); |
| if (shell == null) { |
| Display display = CommonPlugin.getStandardDisplay(); |
| shell = display.getActiveShell(); |
| } |
| |
| // see also: openYesNoCancelQuestion |
| String toggleMessage=Messages.PldtAstVisitor_28; |
| IPreferenceStore store = null; |
| String key = null; |
| MessageDialogWithToggle md; |
| md=MessageDialogWithToggle.openYesNoQuestion(shell, title, message, toggleMessage, dontAskAgain, store, key); |
| int retCode=md.getReturnCode(); // yes=2 |
| answer = (retCode==2); |
| dontAskAgain= md.getToggleState(); |
| } |
| |
| public boolean getAnswer() { |
| return answer; |
| } |
| public boolean getDontAskAgain() { |
| return dontAskAgain; |
| } |
| } |
| |
| /** |
| * will be overridden where needed; note that for C code, the test for if |
| * the prefix matches has already been done before this is called so this |
| * test isn't necessary. FIXME improve this convoluted logic |
| * |
| * @param name |
| * @return |
| * @since 4.0 |
| */ |
| public boolean matchesPrefix(String name) { |
| return true; |
| } |
| |
| } |