org.eclipse.ptp.pldt.common.analysis.PldtAstVisitor.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.ptp.pldt.common.analysis.PldtAstVisitor.java

Source

/**********************************************************************
 * 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.IASTFunctionCallExpression;
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 abstract 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$
    /**
     * Note that this is not final beause it can be dynamically modified by user-enabled tracing:
     * See CommonPlugin.getTraceOn();
     */
    private static boolean traceOn = false;

    private static boolean dontAskToModifyIncludePathAgain = false;
    protected boolean allowPrefixOnlyMatch = true;

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

    /**
     * Visit an ast node of type IASTExpression. Most things tend to fall into this visit method. <br>
     * Version from MPI originally that seems best for all. Handles recognition within macro expansions
     * */
    public int visit(IASTExpression expression) {
        if (expression instanceof IASTFunctionCallExpression) {
            IASTExpression astExpr = ((IASTFunctionCallExpression) expression).getFunctionNameExpression();
            String signature = astExpr.getRawSignature();
            // note: getRawSig is the name BEFORE being processed by preprocessor!
            // but it seems to be empty if it's different.

            // can we get post-pre-processor name here?
            if (astExpr instanceof IASTIdExpression) {
                IASTName tempFN = ((IASTIdExpression) astExpr).getName();
                IBinding tempBIND = tempFN.resolveBinding();
                String tempNAME = tempBIND.getName();
                if (traceOn)
                    System.out.println("MCAV name: " + tempNAME + " rawsig: " + signature); //$NON-NLS-1$ //$NON-NLS-2$
                // if e.g. preprocessor substitution used, use that for function name
                boolean preProcUsed = !signature.equals(tempNAME);
                if (preProcUsed) {
                    signature = tempNAME;
                }
            }
            // still is IASTFunctionCallExpression
            if (matchesPrefix(signature)) {
                if (astExpr instanceof IASTIdExpression) {
                    IASTName funcName = ((IASTIdExpression) astExpr).getName();
                    // IBinding binding = funcName.resolveBinding();
                    // String name=binding.getName();// name ok for stdMake
                    processFuncName(funcName, astExpr);
                }
            }
        } else if (expression instanceof IASTLiteralExpression) {
            processMacroLiteral((IASTLiteralExpression) expression);
        }
        return 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 (or other identifier) that was found in the include path, or matched with prefix,
     * as defined in the preferences.
     * 
     * @param astExpr
     * @param funcName
     */
    public void processFuncName(IASTName funcName, IASTExpression astExpr) {
        // IASTTranslationUnit tu = funcName.getTranslationUnit();
        String strName = funcName.toString();

        final boolean usePref = this.allowPrefixOnlyMatch;
        if ((usePref && matchesPrefix(strName)) || (!usePref && 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));

            }
        }
    }

    /**
     * Look for artifacts in an IASTExpression
     * @param astExpr
     */
    public void processExprWithConstant(IASTExpression astExpr) {
        IASTName funcName = ((IASTIdExpression) astExpr).getName();
        // IASTTranslationUnit tu = funcName.getTranslationUnit();
        String strName = funcName.toString();
        final boolean usePref = this.allowPrefixOnlyMatch;// only to make next line short/readable
        if ((usePref && matchesPrefix(strName)) || (!usePref && 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),
     * (or identified by prefix-only match, which is now the default)
     * 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();
            } // will it be a (new, replacing IASTMacroExpansion) IASTMacroExpansionLocation now??
            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);
    }

    /**
     * Look for artifacts within a IASTIdExpression
     * @param expression
     */
    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);
    }

    /**
     * Convert a string to a list with given delimiters
     * @param stringList
     * @return
     */
    @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, and return its answer plus a persistent
     * setting for not asking the same question again.  Users get tired of the same old question!
     * 
     * @author beth tibbitts
     * 
     * @param title
     * @param message
     * @param dontAskAgain allows persistent setting to not ask this question again
     * @return
     */
    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. In this case the subclass should implement it and always return true,
     * but with increased use of "recognize artifact by prefix only" this becomes more important.
     * FIXME improve this convoluted logic
     * 
     * @param name
     * @return
     * @since 4.0
     */
    abstract public boolean matchesPrefix(String name);

}