blob: 7443ca2325fac8874d53420b224b2b18bf64b5d8 [file] [log] [blame]
/**********************************************************************
* 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 &not
// 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;
}
}