blob: a8c94a49ab63de5a239357f80fbb5118d1850637 [file] [log] [blame]
/**
* Copyright (c) 2010, 2011 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution
* refactoring of FileLineEntryProvider to separate class
*
* Contributors:
* Broadcom - convert private class extensions to final
*
*/
package org.eclipse.cdt.debug.edc.internal.symbols;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.cdt.core.IAddress;
import org.eclipse.cdt.debug.edc.internal.PathUtils;
import org.eclipse.cdt.debug.edc.internal.services.dsf.Modules.EDCLineAddresses;
import org.eclipse.cdt.debug.edc.symbols.ICompileUnitScope;
import org.eclipse.cdt.debug.edc.symbols.IFunctionScope;
import org.eclipse.cdt.debug.edc.symbols.ILineEntry;
import org.eclipse.cdt.debug.edc.symbols.ILineEntryProvider;
import org.eclipse.cdt.debug.edc.symbols.IModuleLineEntryProvider;
import org.eclipse.cdt.debug.edc.symbols.IScope;
import org.eclipse.core.runtime.IPath;
/**
* This class holds a conglomeration of line entry data for an entire
* module.
*/
public class ModuleLineEntryProvider implements IModuleLineEntryProvider {
/**
* basically, a typedef of {@link ArrayList}<{@link FileLineEntryProvider}>
*/
private static final class FileLineEntryProviders extends ArrayList<FileLineEntryProvider> {
private static final long serialVersionUID = -2157263701372708990L;
}
/**
* basically, a typedef of {@link HashMap}&lt;{@link IPath},{@link FileLineEntryProvider}&gt;
*/
private static final class PathToLineEntryMap extends HashMap<IPath, FileLineEntryProviders> {
private static final long serialVersionUID = 7064789571684986782L;
}
// CUs we've already considered
private Set<ICompileUnitScope> parsedCUs = new HashSet<ICompileUnitScope>();
// mapping to find info for a given path
private PathToLineEntryMap pathToLineEntryMap = new PathToLineEntryMap();
// all known providers
private FileLineEntryProviders fileProviders = new FileLineEntryProviders();
// cached array of providers
private FileLineEntryProvider[] fileProviderArray;
public ModuleLineEntryProvider() {
}
/**
* Add the line entries from a compilation unit to the mapper.
* @param scope
*/
public void addCompileUnit(ICompileUnitScope cu) {
if (parsedCUs.contains(cu))
return;
parsedCUs.add(cu);
Collection<ILineEntry> lineEntries = cu.getLineEntries();
if (lineEntries.size() > 0) {
// files created for this compile unit scope (union of all CUs in this.lineEntryMap)
// (kept because we visit the same file a lot in this function)
Map<IPath, FileLineEntryProvider> fileProviders = new HashMap<IPath, FileLineEntryProvider>(4);
// go through each entry and extract entries for each file.
//
// allocate one FileLineEntryProvider per CU
//
for (ILineEntry entry : lineEntries) {
IPath path = entry.getFilePath();
FileLineEntryProvider provider = fileProviders.get(path);
if (provider == null) {
// This will look for an existing one and create a
// new one if none exits.
provider = getFileLineProviderForCU(cu, path);
provider.setCULineEntries(lineEntries);
fileProviders.put(path, provider);
}
provider.addLineEntry(entry);
}
}
// then, look for lines provided by decl file/line/column entries
for (IScope child : cu.getChildren()) {
addCompileUnitChild(cu, child);
}
}
/**
* Add (or update) a compile unit child entry (function) by adding a
* line entry for its declaration location, which may differ from the
* first line inside the function to which the low PC refers.
* @param cu
* @param child
*/
public void addCompileUnitChild(ICompileUnitScope cu, IScope child) {
if (child instanceof IFunctionScope) {
IFunctionScope func = (IFunctionScope) child;
IPath declFile = func.getDeclFile();
if (declFile != null) {
// this is the slow path for dynamic parsing
FileLineEntryProvider provider
= getFileLineProviderForCU(cu, declFile);
int declLine = func.getDeclLine();
int declColumn = func.getDeclColumn();
// is there already an entry at this line?
Collection<ILineEntry> curEntries
= provider.getLineEntriesForLines(declFile, declLine, declLine);
if (curEntries.isEmpty()) {
// no, add one, and make it range from our start to the first actual line
LineEntry entry
= new LineEntry(declFile, declLine, declColumn,
func.getLowAddress(), func.getLowAddress());
provider.addLineEntry(entry);
}
}
}
}
private FileLineEntryProvider getFileLineProviderForCU(
ICompileUnitScope cu, IPath declFile) {
FileLineEntryProviders providers = pathToLineEntryMap.get(declFile);
if (providers != null) {
for (FileLineEntryProvider p : providers) {
if (p.getCU().equals(cu)) {
return p;
}
}
}
FileLineEntryProvider provider = new FileLineEntryProvider(cu, declFile);
registerFileLineEntryProvider(declFile, provider);
return provider;
}
private void registerFileLineEntryProvider(IPath path,
FileLineEntryProvider provider) {
FileLineEntryProviders fileEntries = pathToLineEntryMap.get(path);
if (fileEntries == null) {
fileEntries = new FileLineEntryProviders();
pathToLineEntryMap.put(path, fileEntries);
}
fileEntries.add(provider);
fileProviders.add(provider);
fileProviderArray = null;
}
/**
* Get the line entry providers for the given source file.
* @path sourceFile the absolute path to the source file
* @return the unmodifiable list of providers for the file, possibly empty.
*/
public Collection<ILineEntryProvider> getLineEntryProvidersForFile(IPath sourceFile) {
List<? extends ILineEntryProvider> cus = pathToLineEntryMap.get(sourceFile);
if (cus != null)
return Collections.unmodifiableCollection(cus);
for (Map.Entry<IPath, FileLineEntryProviders> entry : pathToLineEntryMap.entrySet()) {
if (!PathUtils.isCaseSensitive() && entry.getKey().toOSString().compareToIgnoreCase(sourceFile.toOSString()) == 0) {
cus = entry.getValue();
pathToLineEntryMap.put(sourceFile, entry.getValue());
return Collections.unmodifiableCollection(cus);
}
}
for (Map.Entry<IPath, FileLineEntryProviders> entry : pathToLineEntryMap.entrySet()) {
if (entry.getKey().equals(sourceFile)) {
cus = entry.getValue();
return Collections.unmodifiableCollection(cus);
}
}
return Collections.emptyList();
}
/* (non-Javadoc)
* @see org.eclipse.cdt.debug.edc.internal.symbols.ILineEntryProvider#getLineEntriesForLines(org.eclipse.core.runtime.IPath, int, int)
*/
public Collection<ILineEntry> getLineEntriesForLines(IPath file,
int startLineNumber, int endLineNumber) {
FileLineEntryProviders matches = pathToLineEntryMap.get(file);
if (matches == null)
return Collections.emptyList();
List<ILineEntry> ret = null;
for (FileLineEntryProvider provider : matches) {
Collection<ILineEntry> entries
= provider.getLineEntriesForLines(file, startLineNumber, endLineNumber);
if (!entries.isEmpty()) {
if (ret == null)
ret = new ArrayList<ILineEntry>(entries);
else
ret.addAll(entries);
}
}
if (ret == null)
return Collections.emptyList();
return ret;
}
/* (non-Javadoc)
* @see org.eclipse.cdt.debug.edc.internal.symbols.ILineEntryProvider#getLineEntryAtAddress(org.eclipse.cdt.core.IAddress)
*/
public ILineEntry getLineEntryAtAddress(IAddress linkAddress) {
// scanning files can introduce new file providers; avoid ConcurrentModificationException
if (fileProviderArray == null) {
fileProviderArray = fileProviders.toArray(new FileLineEntryProvider[fileProviders.size()]);
}
for (FileLineEntryProvider provider : fileProviderArray) {
// Narrow down the search to avoid iterating potentially hundreds
// of duplicates of the same file
// (e.g. for stl_vector.h, expanded N times for N std::vector<T> uses).
// (Don't use #getScopeAtAddress() since this preparses too much.)
if (provider.getCU().getLowAddress().compareTo(linkAddress) <= 0
&& provider.getCU().getHighAddress().compareTo(linkAddress) > 0) {
ILineEntry entry = provider.getLineEntryAtAddress(linkAddress);
if (entry != null
/* FIXME: sigh ...
*
* yet another RVCT DWARF inlined LNT entry generation bug ...
*
* we just can't have entry.highAddr == entry.lowAddr!
*
* that just TOTALLY ruins the illusion of stepping.
*
* see, if we pass back the address we're on,
* no step-over range will get created,
* and the debugger will just run ...
*
* ... and that's just NotGood-TM .
*/
&& !entry.getLowAddress().equals(entry.getHighAddress())
) {
return entry;
}
}
}
return null;
}
public ILineEntry getLineEntryInFunction(IAddress linkAddress, IFunctionScope parentFunction) {
ILineEntry functionEntry = getLineEntryAtAddress(parentFunction.getLowAddress());
if (functionEntry == null)
return null;
Collection<ILineEntryProvider> parentProviders
= getLineEntryProvidersForFile(functionEntry.getFilePath());
for (ILineEntryProvider iLineEntryProvider : parentProviders) {
if (iLineEntryProvider instanceof FileLineEntryProvider) {
ILineEntry entry
= ((FileLineEntryProvider)iLineEntryProvider).getLineEntryInFunction(linkAddress, parentFunction);
if (entry != null)
return entry;
}
}
return null;
}
/* (non-Javadoc)
* @see org.eclipse.cdt.debug.edc.internal.symbols.ILineEntryProvider#getNextLineEntry(org.eclipse.cdt.debug.edc.internal.symbols.ILineEntry)
*/
public ILineEntry getNextLineEntry(final ILineEntry entry, final boolean collapseInlineFunctions) {
if (entry == null)
return null;
final IAddress entryLowAddr = entry.getLowAddress();
final IAddress entryHighAddr = entry.getHighAddress();
final IPath entryPath = entry.getFilePath();
FileLineEntryProviders matches = pathToLineEntryMap.get(entryPath);
if (matches == null)
return null;
// avoid possible concurrent access if we read new files while searching
FileLineEntryProvider[] matchArray = matches.toArray(new FileLineEntryProvider[matches.size()]);
for (FileLineEntryProvider provider : matchArray) {
ICompileUnitScope cuScope = provider.getCU();
// Narrow down the search to avoid iterating potentially hundreds of duplicates of the same file
// (e.g. for stl_vector.h, expanded N times for N std::vector<T> uses).
// (Don't use #getScopeAtAddress() since this preparses too much.).
if (cuScope.getLowAddress().compareTo(entryLowAddr) <= 0
// NOTE: high addrs for both scope & line entries are inclusive: thus >= 0
&& cuScope.getHighAddress().compareTo(entryHighAddr) >= 0) {
// provider.getNextLineEntry() returns null for only 1 reason:
// 1) there are no more entries at all for the compileUnitScope
//
// so there's no need to continue looking in other providers
return provider.getNextLineEntry(entry, collapseInlineFunctions);
}
}
return null;
}
/* (non-Javadoc)
* @see org.eclipse.cdt.debug.edc.internal.symbols.ILineEntryProvider#getPreviousLineEntry
*/
public ILineEntry getPreviousLineEntry(ILineEntry entry,
boolean collapseInlineFunctions) {
if (entry == null)
return null;
FileLineEntryProviders matches = pathToLineEntryMap.get(entry.getFilePath());
if (matches != null) {
final IAddress entryLowAddr = entry.getLowAddress();
final IAddress entryHighAddr = entry.getHighAddress();
boolean entryIsInline = false, inlineEstablished = false;
// avoid possible concurrent access if we read new files while searching
FileLineEntryProvider[] matchArray = matches.toArray(new FileLineEntryProvider[matches.size()]);
for (FileLineEntryProvider provider : matchArray) {
ICompileUnitScope cuScope = provider.getCU();
// Narrow down the search to avoid iterating potentially hundreds of duplicates of the same file
// (e.g. for stl_vector.h, expanded N times for N std::vector<T> uses).
// (Don't use #getScopeAtAddress() since this preparses too much.).
//
//
if (cuScope.getLowAddress().compareTo(entryLowAddr) <= 0
// NOTE: high addrs for both scope & line entries are inclusive: thus >= 0
&& cuScope.getHighAddress().compareTo(entryHighAddr) >= 0) {
if (!inlineEstablished) {
entryIsInline = FileLineEntryProvider.isInlinedFunction(cuScope.getFunctionAtAddress(entryLowAddr));
inlineEstablished = true;
}
ILineEntry prev = provider.getPreviousLineEntry(entry, collapseInlineFunctions);
if (prev == null && collapseInlineFunctions && entryIsInline) {
// retry with the provider mapped from the compileUnitScope.filePath
return provider.getPreviousLineEntryInCU(entry);
}
if (prev != null) { // in case there's another provider
return prev;
}
}
}
}
return null;
}
/*
* (non-Javadoc)
* @see org.eclipse.cdt.debug.edc.symbols.ILineEntryProvider#findClosestLineWithCode(org.eclipse.core.runtime.IPath, int, int)
*/
public List<ILineAddresses> findClosestLineWithCode(IPath sourceFile,
int anchorLine, int neighbor_limit) {
List<ILineAddresses> ret = new ArrayList<ILineAddresses>(1);
/* Check all compile units in the module for code line.
*/
Collection<? extends ILineEntryProvider> fileProviders =
getLineEntryProvidersForFile(sourceFile);
if (fileProviders.isEmpty())
return ret;
for (ILineEntryProvider fileProvider : fileProviders) {
// Find code line in one CU using the source file
List<ILineAddresses> la = fileProvider.findClosestLineWithCode(sourceFile, anchorLine, neighbor_limit);
if (la.isEmpty())
continue;
assert (la.size() == 1);
ILineAddresses newCodeLine = la.get(0);
if (ret.isEmpty())
ret.add(newCodeLine);
else {
boolean merged = false;
for (ILineAddresses e : ret)
if (newCodeLine.getLineNumber() == e.getLineNumber()) {
((EDCLineAddresses)e).addAddress(newCodeLine.getAddress());
merged = true;
break;
}
if (!merged)
ret.add(newCodeLine);
}
}
return ret;
}
public boolean hasSourceFile(IPath sourceFile) {
Collection<? extends ILineEntryProvider> fileProviders =
getLineEntryProvidersForFile(sourceFile);
return fileProviders != null && ! fileProviders.isEmpty();
}
}