/*******************************************************************************
* Copyright (c) 2007, 2011 Wind River Systems, 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:
* Markus Schorn - initial API and implementation
* IBM Corporation
* Sergey Prigogin (Google)
*******************************************************************************/
package org.eclipse.cdt.internal.core.pdom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIncludeStatement;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroDefinition;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorStatement;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorUndefStatement;
import org.eclipse.cdt.core.dom.ast.IASTProblem;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.ICompositeType;
import org.eclipse.cdt.core.dom.ast.IEnumeration;
import org.eclipse.cdt.core.dom.ast.IProblemBinding;
import org.eclipse.cdt.core.dom.ast.ITypedef;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier.ICPPASTBaseSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateId;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTUsingDirective;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassTemplate;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionTemplate;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPNamespace;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPNamespaceAlias;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateParameter;
import org.eclipse.cdt.core.index.IIndexFile;
import org.eclipse.cdt.core.index.IIndexFileLocation;
import org.eclipse.cdt.core.index.IIndexInclude;
import org.eclipse.cdt.core.parser.IProblem;
import org.eclipse.cdt.core.parser.ISignificantMacros;
import org.eclipse.cdt.internal.core.dom.parser.ASTInternal;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPUnknownBinding;
import org.eclipse.cdt.internal.core.index.FileContentKey;
import org.eclipse.cdt.internal.core.index.IIndexFragmentFile;
import org.eclipse.cdt.internal.core.index.IWritableIndex;
import org.eclipse.cdt.internal.core.index.IWritableIndex.IncludeInformation;
import org.eclipse.cdt.internal.core.parser.scanner.LocationMap;
import org.eclipse.cdt.internal.core.pdom.dom.PDOMASTAdapter;
import org.eclipse.cdt.internal.core.pdom.indexer.IndexerASTVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.util.NLS;
/**
* Abstract class to write information from AST
* @since 4.0
*/
abstract public class PDOMWriter {
public static class FileInAST {
final IASTPreprocessorIncludeStatement fIncludeStatement;
final FileContentKey fFileContentKey;
final long fContentsHash;
public FileInAST(IASTPreprocessorIncludeStatement includeStmt, FileContentKey key, long contentsHash) {
fIncludeStatement= includeStmt;
fFileContentKey= key;
fContentsHash= contentsHash;
}
@Override
public String toString() {
return fFileContentKey.toString();
}
}
public static class FileContext {
final IIndexFragmentFile fContext;
final IIndexFragmentFile fOldFile;
IIndexFragmentFile fNewFile;
public boolean fLostPragmaOnceSemantics;
public FileContext(IIndexFragmentFile context, IIndexFragmentFile oldFile) {
fContext= context;
fOldFile= oldFile;
fNewFile= null;
}
}
public static int SKIP_ALL_REFERENCES= -1;
public static int SKIP_TYPE_REFERENCES= 1;
public static int SKIP_MACRO_REFERENCES= 2;
public static int SKIP_IMPLICIT_REFERENCES= 4;
public static int SKIP_NO_REFERENCES= 0;
private static class Symbols {
final ArrayList<IASTName[]> fNames= new ArrayList<IASTName[]>();
final ArrayList<IASTPreprocessorStatement> fMacros= new ArrayList<IASTPreprocessorStatement>();
final ArrayList<IASTPreprocessorIncludeStatement> fIncludes= new ArrayList<IASTPreprocessorIncludeStatement>();
}
private static class Data {
final IASTTranslationUnit fAST;
final FileInAST[] fSelectedFiles;
final IWritableIndex fIndex;
final Map<IASTPreprocessorIncludeStatement, Symbols> fSymbolMap = new HashMap<IASTPreprocessorIncludeStatement, Symbols>();
final Set<IASTPreprocessorIncludeStatement> fContextIncludes = new HashSet<IASTPreprocessorIncludeStatement>();
final List<IStatus> fStati= new ArrayList<IStatus>();
public Data(IASTTranslationUnit ast, FileInAST[] selectedFiles, IWritableIndex index) {
fAST= ast;
fSelectedFiles= selectedFiles;
fIndex= index;
}
}
private boolean fShowProblems;
protected boolean fShowInclusionProblems;
private boolean fShowScannerProblems;
private boolean fShowSyntaxProblems;
protected boolean fShowActivity;
protected final IndexerStatistics fStatistics;
protected final IndexerInputAdapter fResolver;
private int fSkipReferences= SKIP_NO_REFERENCES;
public PDOMWriter(IndexerInputAdapter resolver) {
fStatistics= new IndexerStatistics();
fResolver= resolver;
}
protected IndexerInputAdapter getInputAdapter() {
return fResolver;
}
public void setShowActivity(boolean val) {
fShowActivity= val;
}
public void setShowInclusionProblems(boolean val) {
fShowInclusionProblems= val;
}
public void setShowScannerProblems(boolean val) {
fShowScannerProblems= val;
}
public void setShowSyntaxProblems(boolean val) {
fShowSyntaxProblems= val;
}
public void setShowProblems(boolean val) {
fShowProblems= val;
}
/**
* Determines whether references are skipped or not. Provide one of
* {@link #SKIP_ALL_REFERENCES}, {@link #SKIP_NO_REFERENCES} or a combination of
* {@link #SKIP_IMPLICIT_REFERENCES}, {@link #SKIP_TYPE_REFERENCES} and {@link #SKIP_MACRO_REFERENCES}.
*/
public void setSkipReferences(int options) {
fSkipReferences= options;
}
public int getSkipReferences() {
return fSkipReferences;
}
/**
* Extracts symbols from the given AST and adds them to the index.
*
* When flushIndex is set to <code>false</code>, you must make sure to flush
* the index after your last write operation.
*/
final protected void addSymbols(IASTTranslationUnit ast, FileInAST[] selectedFiles,
IWritableIndex index, boolean flushIndex, FileContext ctx,
ITodoTaskUpdater taskUpdater, IProgressMonitor pm) throws InterruptedException,
CoreException {
if (fShowProblems) {
fShowInclusionProblems= true;
fShowScannerProblems= true;
fShowSyntaxProblems= true;
}
Data data= new Data(ast, selectedFiles, index);
for (FileInAST file : selectedFiles) {
data.fSymbolMap.put(file.fIncludeStatement, new Symbols());
}
// Extract symbols from AST
extractSymbols(data);
// Name resolution
resolveNames(data, pm);
// Index update
storeSymbolsInIndex(data, ctx, flushIndex, pm);
// Tasks update
if (taskUpdater != null) {
Set<IIndexFileLocation> locations= new HashSet<IIndexFileLocation>();
for (FileInAST file : selectedFiles) {
locations.add(file.fFileContentKey.getLocation());
}
taskUpdater.updateTasks(ast.getComments(), locations.toArray(new IIndexFileLocation[locations.size()]));
}
if (!data.fStati.isEmpty()) {
List<IStatus> stati = data.fStati;
String path= null;
if (selectedFiles.length > 0) {
path= selectedFiles[selectedFiles.length - 1].fFileContentKey.getLocation().getURI().getPath();
} else {
path= ast.getFilePath().toString();
}
String msg= NLS.bind(Messages.PDOMWriter_errorWhileParsing, path);
if (stati.size() == 1) {
IStatus status= stati.get(0);
if (msg.equals(status.getMessage())) {
throw new CoreException(status);
}
throw new CoreException(new Status(status.getSeverity(), status.getPlugin(), status.getCode(),
msg + ':' + status.getMessage(), status.getException()));
}
throw new CoreException(new MultiStatus(CCorePlugin.PLUGIN_ID, 0,
stati.toArray(new IStatus[stati.size()]), msg, null));
}
}
private void storeSymbolsInIndex(final Data data, FileContext ctx, boolean flushIndex, IProgressMonitor pm)
throws InterruptedException, CoreException {
final IIndexFragmentFile newFile= ctx == null ? null : ctx.fNewFile;
final int linkageID= data.fAST.getLinkage().getLinkageID();
for (int i= 0; i < data.fSelectedFiles.length; i++) {
if (pm.isCanceled())
return;
final FileInAST fileInAST= data.fSelectedFiles[i];
if (fileInAST != null) {
if (fShowActivity) {
trace("Indexer: adding " + fileInAST.fFileContentKey.getLocation().getURI()); //$NON-NLS-1$
}
Throwable th= null;
YieldableIndexLock lock = new YieldableIndexLock(data.fIndex, flushIndex);
lock.acquire();
try {
final boolean isReplacement= ctx != null && fileInAST.fIncludeStatement == null;
IIndexFragmentFile ifile= null;
if (!isReplacement || newFile == null) {
ifile= storeFileInIndex(data, fileInAST, linkageID, lock);
reportFileWrittenToIndex(fileInAST, ifile);
}
if (isReplacement) {
if (ifile == null)
ifile= newFile;
if (ctx != null && !ctx.fOldFile.equals(ifile) && ifile != null) {
if (ctx.fOldFile.hasPragmaOnceSemantics() &&
!ifile.hasPragmaOnceSemantics()) {
data.fIndex.transferContext(ctx.fOldFile, ifile);
ctx.fLostPragmaOnceSemantics= true;
} else {
data.fIndex.transferIncluders(ctx.fOldFile, ifile);
}
}
}
} catch (RuntimeException e) {
th= e;
} catch (StackOverflowError e) {
th= e;
} catch (AssertionError e) {
th= e;
} finally {
// Because the caller holds a read-lock, the result cache of the index is never cleared.
// ==> Before releasing the lock for the last time in this ast, we clear the result cache.
if (i == data.fSelectedFiles.length-1) {
data.fIndex.clearResultCache();
}
lock.release();
}
if (th != null) {
data.fStati.add(createStatus(NLS.bind(Messages.PDOMWriter_errorWhileParsing,
fileInAST.fFileContentKey.getLocation().getURI().getPath()), th));
}
fStatistics.fAddToIndexTime += lock.getCumulativeLockTime();
}
}
}
private void resolveNames(Data data, IProgressMonitor pm) {
long start= System.currentTimeMillis();
for (FileInAST file : data.fSelectedFiles) {
if (pm.isCanceled()) {
return;
}
Symbols symbols= data.fSymbolMap.get(file.fIncludeStatement);
final ArrayList<IASTName[]> names= symbols.fNames;
boolean reported= false;
for (Iterator<IASTName[]> j = names.iterator(); j.hasNext();) {
final IASTName[] na= j.next();
final IASTName name = na[0];
if (name != null) { // should not be null, just be defensive.
Throwable th= null;
try {
final IBinding binding = name.resolveBinding();
if (name.getPropertyInParent() == ICPPASTTemplateId.TEMPLATE_NAME &&
(((IASTName) name.getParent()).getBinding() == binding ||
binding instanceof ICPPFunctionTemplate)) {
na[0]= null;
} else if (binding instanceof IProblemBinding) {
fStatistics.fProblemBindingCount++;
if (fShowProblems) {
reportProblem((IProblemBinding) binding);
}
} else if (name.isReference()) {
if (binding instanceof ICPPTemplateParameter ||
binding instanceof ICPPUnknownBinding ||
((fSkipReferences & SKIP_TYPE_REFERENCES) != 0 &&
isTypeReferenceBinding(binding))) {
if (!isRequiredReference(name)) {
na[0]= null;
} else {
fStatistics.fReferenceCount++;
}
} else {
fStatistics.fReferenceCount++;
}
} else {
fStatistics.fDeclarationCount++;
}
} catch (RuntimeException e) {
th= e;
} catch (StackOverflowError e) {
th= e;
}
if (th != null) {
if (!reported) {
data.fStati.add(CCorePlugin.createStatus(NLS.bind(Messages.PDOMWriter_errorResolvingName,
name.toString(), file.fFileContentKey.getLocation().getURI().getPath()), th));
}
reported= true;
j.remove();
}
}
}
}
fStatistics.fResolutionTime += System.currentTimeMillis()-start;
}
private void extractSymbols(Data data) throws CoreException {
int unresolvedIncludes= 0;
final IASTTranslationUnit ast = data.fAST;
final Map<IASTPreprocessorIncludeStatement, Symbols> symbolMap = data.fSymbolMap;
IASTPreprocessorStatement[] stmts = ast.getAllPreprocessorStatements();
for (final IASTPreprocessorStatement stmt : stmts) {
// Includes.
if (stmt instanceof IASTPreprocessorIncludeStatement) {
IASTPreprocessorIncludeStatement include= (IASTPreprocessorIncludeStatement) stmt;
final IASTFileLocation astLoc= include.getFileLocation();
IASTPreprocessorIncludeStatement owner = astLoc.getContextInclusionStatement();
final boolean updateSource= symbolMap.containsKey(owner);
if (updateSource) {
addToMap(symbolMap, owner, include);
}
if (include.isActive()) {
if (!include.isResolved()) {
unresolvedIncludes++;
} else if (updateSource) {
// The include was parsed, check if we want to update the included file in the index.
if (symbolMap.containsKey(include)) {
data.fContextIncludes.add(include);
}
}
}
} else if (stmt.isActive() &&
(stmt instanceof IASTPreprocessorUndefStatement || stmt instanceof IASTPreprocessorMacroDefinition)) {
IASTFileLocation sourceLoc = stmt.getFileLocation();
if (sourceLoc != null) { // skip built-ins and command line macros
IASTPreprocessorIncludeStatement owner = sourceLoc.getContextInclusionStatement();
addToMap(symbolMap, owner, stmt);
}
}
}
// Names.
final IndexerASTVisitor visitor = new IndexerASTVisitor((fSkipReferences & SKIP_IMPLICIT_REFERENCES) == 0) {
@Override
public void visit(IASTName name, IASTName caller) {
if (fSkipReferences == SKIP_ALL_REFERENCES) {
if (name.isReference()) {
if (!isRequiredReference(name)) {
return;
}
}
}
// Assign a location to anonymous types.
name= PDOMASTAdapter.getAdapterIfAnonymous(name);
if (name != null) {
IASTFileLocation nameLoc = name.getFileLocation();
if (nameLoc != null) {
IASTPreprocessorIncludeStatement owner= nameLoc.getContextInclusionStatement();
addToMap(symbolMap, owner, new IASTName[] { name, caller });
}
}
}
};
ast.accept(visitor);
if ((fSkipReferences & SKIP_MACRO_REFERENCES) == 0) {
LocationMap lm= (LocationMap) ast.getAdapter(LocationMap.class);
if (lm != null) {
IASTName[] refs= lm.getMacroReferences();
for (IASTName name : refs) {
IASTFileLocation nameLoc = name.getFileLocation();
if (nameLoc != null) {
IASTPreprocessorIncludeStatement owner= nameLoc.getContextInclusionStatement();
addToMap(symbolMap, owner, new IASTName[] { name, null });
}
}
}
}
fStatistics.fUnresolvedIncludesCount += unresolvedIncludes;
fStatistics.fPreprocessorProblemCount += ast.getPreprocessorProblemsCount() - unresolvedIncludes;
if (fShowScannerProblems || fShowInclusionProblems) {
final boolean reportAll= fShowScannerProblems && fShowInclusionProblems;
IASTProblem[] scannerProblems= ast.getPreprocessorProblems();
for (IASTProblem problem : scannerProblems) {
if (reportAll || (problem.getID() == IProblem.PREPROCESSOR_INCLUSION_NOT_FOUND) == fShowInclusionProblems) {
reportProblem(problem);
}
}
}
final List<IASTProblem> problems= visitor.getProblems();
fStatistics.fSyntaxProblemsCount += problems.size();
if (fShowSyntaxProblems) {
for (IASTProblem problem : problems) {
reportProblem(problem);
}
}
}
protected final boolean isRequiredReference(IASTName name) {
IASTNode parentNode= name.getParent();
if (parentNode instanceof ICPPASTQualifiedName) {
if (name != ((ICPPASTQualifiedName) parentNode).getLastName())
return false;
parentNode= parentNode.getParent();
}
if (parentNode instanceof ICPPASTBaseSpecifier) {
return true;
} else if (parentNode instanceof IASTDeclSpecifier) {
IASTDeclSpecifier ds= (IASTDeclSpecifier) parentNode;
return ds.getStorageClass() == IASTDeclSpecifier.sc_typedef;
} else if (parentNode instanceof ICPPASTUsingDirective) {
return true;
}
return false;
}
private boolean isTypeReferenceBinding(IBinding binding) {
if (binding instanceof ICompositeType ||
binding instanceof IEnumeration ||
binding instanceof ITypedef ||
binding instanceof ICPPNamespace ||
binding instanceof ICPPNamespaceAlias ||
binding instanceof ICPPClassTemplate) {
return true;
}
return false;
}
private void addToMap(Map<IASTPreprocessorIncludeStatement, Symbols> symbolMap, IASTPreprocessorIncludeStatement owner, IASTName[] thing) {
Symbols lists= symbolMap.get(owner);
if (lists != null)
lists.fNames.add(thing);
}
private void addToMap(Map<IASTPreprocessorIncludeStatement, Symbols> symbolMap, IASTPreprocessorIncludeStatement owner, IASTPreprocessorIncludeStatement thing) {
Symbols lists= symbolMap.get(owner);
if (lists != null)
lists.fIncludes.add(thing);
}
private void addToMap(Map<IASTPreprocessorIncludeStatement, Symbols> symbolMap,
IASTPreprocessorIncludeStatement owner, IASTPreprocessorStatement thing) {
Symbols lists= symbolMap.get(owner);
if (lists != null)
lists.fMacros.add(thing);
}
private IIndexFragmentFile storeFileInIndex(Data data, FileInAST astFile, int linkageID,
YieldableIndexLock lock) throws CoreException, InterruptedException {
final IWritableIndex index = data.fIndex;
IIndexFragmentFile file;
// We create a temporary PDOMFile with zero timestamp, add names to it, then replace
// contents of the old file from the temporary one, then delete the temporary file.
// The write lock on the index can be yielded between adding names to the temporary file,
// if another thread is waiting for a read lock.
final FileContentKey fileKey = astFile.fFileContentKey;
final IASTPreprocessorIncludeStatement owner= astFile.fIncludeStatement;
IIndexFileLocation location = fileKey.getLocation();
ISignificantMacros significantMacros = fileKey.getSignificantMacros();
IIndexFragmentFile oldFile = index.getWritableFile(linkageID, location, significantMacros);
file= index.addUncommittedFile(linkageID, location, significantMacros);
try {
boolean pragmaOnce= owner != null ? owner.hasPragmaOnceSemantics() : data.fAST.hasPragmaOnceSemantics();
file.setPragmaOnceSemantics(pragmaOnce);
Symbols lists= data.fSymbolMap.get(owner);
if (lists != null) {
IASTPreprocessorStatement[] macros= lists.fMacros.toArray(new IASTPreprocessorStatement[lists.fMacros.size()]);
IASTName[][] names= lists.fNames.toArray(new IASTName[lists.fNames.size()][]);
for (IASTName[] name2 : names) {
final IASTName name= name2[0];
if (name != null) {
ASTInternal.setFullyResolved(name.getBinding(), true);
}
}
List<IncludeInformation> includeInfos= new ArrayList<IncludeInformation>();
for (int i= 0; i < lists.fIncludes.size(); i++) {
final IASTPreprocessorIncludeStatement stmt = lists.fIncludes.get(i);
if (!stmt.isResolved()) {
includeInfos.add(new IncludeInformation(stmt, null, ISignificantMacros.NONE, false));
} else {
IIndexFileLocation targetLoc = fResolver.resolveASTPath(stmt.getPath());
ISignificantMacros mainSig= stmt.getSignificantMacros();
for (ISignificantMacros sig : stmt.getLoadedVersions()) {
if (!sig.equals(mainSig)) {
includeInfos.add(new IncludeInformation(stmt, targetLoc, sig, false));
}
}
final boolean isContext = stmt.isActive() && stmt.isResolved() &&
(data.fContextIncludes.contains(stmt) || isContextFor(oldFile, stmt));
includeInfos.add(new IncludeInformation(stmt, targetLoc, mainSig, isContext));
}
}
IncludeInformation[] includeInfoArray= includeInfos.toArray(new IncludeInformation[includeInfos.size()]);
index.setFileContent(file, linkageID, includeInfoArray, macros, names, fResolver, lock);
}
file.setTimestamp(fResolver.getLastModified(location));
file.setEncodingHashcode(fResolver.getEncoding(location).hashCode());
file.setContentsHash(astFile.fContentsHash);
file = index.commitUncommittedFile();
} finally {
index.clearUncommittedFile();
}
return file;
}
private boolean isContextFor(IIndexFragmentFile oldFile, IASTPreprocessorIncludeStatement stmt)
throws CoreException {
IIndexFile target= stmt.getImportedIndexFile();
if (oldFile != null && target != null) {
IIndexInclude ctxInclude = target.getParsedInContext();
if (ctxInclude != null && oldFile.equals(ctxInclude.getIncludedBy()))
return true;
}
return false;
}
/**
* Informs the subclass that a file has been stored in the index.
*/
protected abstract void reportFileWrittenToIndex(FileInAST file, IIndexFragmentFile iFile) throws CoreException;
private String getLocationInfo(String filename, int lineNumber) {
return " at " + filename + "(" + lineNumber + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
private void reportProblem(IProblemBinding problem) {
String msg= "Indexer: unresolved name" + getLocationInfo(problem.getFileName(), problem.getLineNumber()); //$NON-NLS-1$
String pmsg= problem.getMessage();
if (pmsg != null && pmsg.length() > 0)
msg += "; " + problem.getMessage(); //$NON-NLS-1$
trace(msg);
}
private void reportProblem(IASTProblem problem) {
String msg= "Indexer: " + problem.getMessageWithLocation(); //$NON-NLS-1$
trace(msg);
}
protected void trace(String message) {
System.out.println(message);
}
protected IStatus createStatus(String msg) {
return CCorePlugin.createStatus(msg);
}
protected IStatus createStatus(String msg, Throwable e) {
return CCorePlugin.createStatus(msg, e);
}
}