diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/source/ConfigurationBasedCategoryFilter.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/source/ConfigurationBasedCategoryFilter.java index 78bca277f..82aa54524 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/source/ConfigurationBasedCategoryFilter.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/source/ConfigurationBasedCategoryFilter.java @@ -4,7 +4,8 @@ import soot.jimple.infoflow.InfoflowConfiguration.SourceSinkFilterMode; import soot.jimple.infoflow.android.InfoflowAndroidConfiguration.SourceSinkConfiguration; import soot.jimple.infoflow.android.data.CategoryDefinition; -import soot.jimple.infoflow.android.source.parsers.xml.XMLSourceSinkParser.ICategoryFilter; +import soot.jimple.infoflow.android.source.parsers.xml.AbstractXMLSourceSinkParser.ICategoryFilter; +import soot.jimple.infoflow.sourcesSinks.definitions.ISourceSinkCategory; import soot.jimple.infoflow.sourcesSinks.definitions.SourceSinkType; /** @@ -28,26 +29,29 @@ public ConfigurationBasedCategoryFilter(SourceSinkConfiguration config) { } @Override - public boolean acceptsCategory(CategoryDefinition category) { + public boolean acceptsCategory(ISourceSinkCategory category) { // We cannot compare descriptions to the configuration file - category = category.getIdOnlyDescription(); + if (category instanceof CategoryDefinition) { + CategoryDefinition catDef = (CategoryDefinition) category; + catDef = catDef.getIdOnlyDescription(); - CategoryMode sourceType = config.getSourceCategoriesAndModes().get(category); - CategoryMode sinkType = config.getSinkCategoriesAndModes().get(category); + CategoryMode sourceType = config.getSourceCategoriesAndModes().get(catDef); + CategoryMode sinkType = config.getSinkCategoriesAndModes().get(catDef); - // Check whether the category is excluded for everything - if (sourceType != null && sourceType == CategoryMode.Exclude) - if (sinkType != null && sinkType == CategoryMode.Exclude) - return false; + // Check whether the category is excluded for everything + if (sourceType != null && sourceType == CategoryMode.Exclude) + if (sinkType != null && sinkType == CategoryMode.Exclude) + return false; - // Check whether the category is included for something - if (config.getSinkFilterMode() == SourceSinkFilterMode.UseOnlyIncluded) { - if (sourceType != null && sourceType == CategoryMode.Include) - return true; - if (sinkType != null && sinkType == CategoryMode.Include) - return true; + // Check whether the category is included for something + if (config.getSinkFilterMode() == SourceSinkFilterMode.UseOnlyIncluded) { + if (sourceType != null && sourceType == CategoryMode.Include) + return true; + if (sinkType != null && sinkType == CategoryMode.Include) + return true; - return false; + return false; + } } // There is no reason to exclude the category @@ -55,34 +59,36 @@ public boolean acceptsCategory(CategoryDefinition category) { } @Override - public SourceSinkType filter(CategoryDefinition category, SourceSinkType sourceSinkType) { + public SourceSinkType filter(ISourceSinkCategory category, SourceSinkType sourceSinkType) { // We cannot compare descriptions to the configuration file - category = category.getIdOnlyDescription(); - - CategoryMode sourceMode = config.getSourceCategoriesAndModes().get(category); - CategoryMode sinkMode = config.getSinkCategoriesAndModes().get(category); - - if (sourceSinkType == SourceSinkType.Source || sourceSinkType == SourceSinkType.Both) { - if (config.getSourceFilterMode() == SourceSinkFilterMode.UseAllButExcluded) { - if (sourceMode != null && sourceMode == CategoryMode.Exclude) - sourceSinkType = sourceSinkType.removeType(SourceSinkType.Source); - } else if (config.getSourceFilterMode() == SourceSinkFilterMode.UseOnlyIncluded) { - if (sourceMode == null || sourceMode != CategoryMode.Include) - sourceSinkType = sourceSinkType.removeType(SourceSinkType.Source); + if (category instanceof CategoryDefinition) { + CategoryDefinition catDef = (CategoryDefinition) category; + catDef = catDef.getIdOnlyDescription(); + CategoryMode sourceMode = config.getSourceCategoriesAndModes().get(category); + CategoryMode sinkMode = config.getSinkCategoriesAndModes().get(category); + if (sourceSinkType == SourceSinkType.Source || sourceSinkType == SourceSinkType.Both) { + if (config.getSourceFilterMode() == SourceSinkFilterMode.UseAllButExcluded) { + if (sourceMode != null && sourceMode == CategoryMode.Exclude) + sourceSinkType = sourceSinkType.removeType(SourceSinkType.Source); + } else if (config.getSourceFilterMode() == SourceSinkFilterMode.UseOnlyIncluded) { + if (sourceMode == null || sourceMode != CategoryMode.Include) + sourceSinkType = sourceSinkType.removeType(SourceSinkType.Source); + } } - } - if (sourceSinkType == SourceSinkType.Sink || sourceSinkType == SourceSinkType.Both) { - if (config.getSinkFilterMode() == SourceSinkFilterMode.UseAllButExcluded) { - if (sinkMode != null && sinkMode == CategoryMode.Exclude) - sourceSinkType = sourceSinkType.removeType(SourceSinkType.Sink); - } else if (config.getSinkFilterMode() == SourceSinkFilterMode.UseOnlyIncluded) { - if (sinkMode == null || sinkMode != CategoryMode.Include) - sourceSinkType = sourceSinkType.removeType(SourceSinkType.Sink); + if (sourceSinkType == SourceSinkType.Sink || sourceSinkType == SourceSinkType.Both) { + if (config.getSinkFilterMode() == SourceSinkFilterMode.UseAllButExcluded) { + if (sinkMode != null && sinkMode == CategoryMode.Exclude) + sourceSinkType = sourceSinkType.removeType(SourceSinkType.Sink); + } else if (config.getSinkFilterMode() == SourceSinkFilterMode.UseOnlyIncluded) { + if (sinkMode == null || sinkMode != CategoryMode.Include) + sourceSinkType = sourceSinkType.removeType(SourceSinkType.Sink); + } } + return sourceSinkType; } + return SourceSinkType.Undefined; - return sourceSinkType; } } diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/source/parsers/xml/AbstractXMLSourceSinkParser.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/source/parsers/xml/AbstractXMLSourceSinkParser.java new file mode 100644 index 000000000..f5be84b30 --- /dev/null +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/source/parsers/xml/AbstractXMLSourceSinkParser.java @@ -0,0 +1,577 @@ +package soot.jimple.infoflow.android.source.parsers.xml; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import soot.jimple.infoflow.android.data.AndroidMethod; +import soot.jimple.infoflow.android.data.CategoryDefinition; +import soot.jimple.infoflow.android.data.CategoryDefinition.CATEGORY; +import soot.jimple.infoflow.data.AbstractMethodAndClass; +import soot.jimple.infoflow.sourcesSinks.definitions.AccessPathTuple; +import soot.jimple.infoflow.sourcesSinks.definitions.IAccessPathBasedSourceSinkDefinition; +import soot.jimple.infoflow.sourcesSinks.definitions.ISourceSinkCategory; +import soot.jimple.infoflow.sourcesSinks.definitions.ISourceSinkDefinition; +import soot.jimple.infoflow.sourcesSinks.definitions.MethodSourceSinkDefinition; +import soot.jimple.infoflow.sourcesSinks.definitions.MethodSourceSinkDefinition.CallType; +import soot.jimple.infoflow.sourcesSinks.definitions.SourceSinkType; + +/** + * Abstract class for all Flowdroid XML parsers. Returns a Set of Methods when + * calling the function parse. Subclasses may implement parser specifics to + * different language engines. + * + * @author Anna-Katharina Wickert + * @author Joern Tillmans + * @author Steven Arzt + * @author Niklas Vogel + * + */ +public abstract class AbstractXMLSourceSinkParser { + private final static Logger logger = LoggerFactory.getLogger(AbstractXMLSourceSinkParser.class); + + /** + * Filter interface for excluding certain categories from the source/sink set. + * + * @author Steven Arzt + * + */ + public interface ICategoryFilter { + + /** + * Checks whether methods from the given category shall be parsed or not + * + * @param category The category in which the source or sink is defined + * @return True if the given category shall be parsed, otherwise false + */ + public boolean acceptsCategory(ISourceSinkCategory category); + + /** + * Filters sources and sinks by category + * + * @param category The category in which the source or sink is defined + * @param sourceSinkType Specifies whether the category is used for sources or + * sinks + * @return The acceptable use of the given category. This is a reduction from + * the given type. If the requested type, for example, is "Both", this + * method may return "Source", if the category shall only be used for + * sources, but not for sinks. + */ + public SourceSinkType filter(ISourceSinkCategory category, SourceSinkType sourceSinkType); + + } + + /** + * Handler class for the XML parser + * + * @author Anna-Katharina Wickert + * @author Joern Tillmans + * @author Steven Arzt + * + */ + protected class SAXHandler extends DefaultHandler { + + // Holding temporary values for handling with SAX + protected String methodSignature = null; + protected String fieldSignature = null; + + protected ISourceSinkCategory category; + protected boolean isSource, isSink; + protected List pathElements; + protected List pathElementTypes; + protected int paramIndex; + protected List paramTypes = new ArrayList(); + protected CallType callType; + + protected String accessPathParentElement = ""; + protected String description = ""; + + protected Set baseAPs = new HashSet<>(); + protected List> paramAPs = new ArrayList<>(); + protected Set returnAPs = new HashSet<>(); + + protected Map sourcesAndSinks; + protected ICategoryFilter categoryFilter = null; + + /** + * Event Handler for the starting element for SAX. Possible start elements for + * filling AndroidMethod objects with the new data format: - method: Setting + * parsingvalues to false or null and get and set the signature and category, - + * accessPath: To get the information whether the AndroidMethod is a sink or + * source, - and the other elements doesn't care for creating the AndroidMethod + * object. At these element we will look, if calling the method getAccessPath + * (using an new SAX Handler) + */ + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + String qNameLower = qName.toLowerCase(); + switch (qNameLower) { + case XMLConstants.CATEGORY_TAG: + handleStarttagCategory(attributes); + break; + + case XMLConstants.FIELD_TAG: + handleStarttagField(attributes); + break; + + case XMLConstants.METHOD_TAG: + handleStarttagMeethod(attributes); + break; + + case XMLConstants.ACCESSPATH_TAG: + handleStarttagAccesspath(attributes); + break; + + case XMLConstants.BASE_TAG: + accessPathParentElement = qNameLower; + description = attributes.getValue(XMLConstants.DESCRIPTION_ATTRIBUTE); + break; + + case XMLConstants.RETURN_TAG: + accessPathParentElement = qNameLower; + description = attributes.getValue(XMLConstants.DESCRIPTION_ATTRIBUTE); + break; + + case XMLConstants.PARAM_TAG: + handleStarttagParam(attributes, qNameLower); + break; + + case XMLConstants.PATHELEMENT_TAG: + handleStarttagPathelement(attributes); + break; + } + } + + protected void handleStarttagCategory(Attributes attributes) { + String strSysCategory = attributes.getValue(XMLConstants.ID_ATTRIBUTE).trim(); + String strCustomCategory = attributes.getValue(XMLConstants.CUSTOM_ID_ATTRIBUTE); + if (strCustomCategory != null && !strCustomCategory.isEmpty()) + strCustomCategory = strCustomCategory.trim(); + String strCustomDescription = attributes.getValue(XMLConstants.DESCRIPTION_ATTRIBUTE); + if (strCustomDescription != null && !strCustomDescription.isEmpty()) + strCustomDescription = strCustomDescription.trim(); + + category = getOrMakeCategory(CATEGORY.valueOf(strSysCategory), strCustomCategory, strCustomDescription); + + // Check for excluded categories + if (categoryFilter != null && !categoryFilter.acceptsCategory(category)) { + category = null; + } + } + + protected void handleStarttagField(Attributes attributes) { + if (category != null && attributes != null) { + fieldSignature = parseSignature(attributes); + accessPathParentElement = XMLConstants.BASE_TAG; + } + } + + protected void handleStarttagMeethod(Attributes attributes) { + if (category != null && attributes != null) { + methodSignature = parseSignature(attributes); + + // Get the call type + callType = CallType.MethodCall; + String strCallType = attributes.getValue(XMLConstants.CALL_TYPE); + if (strCallType != null && !strCallType.isEmpty()) { + strCallType = strCallType.trim(); + if (strCallType.equalsIgnoreCase("MethodCall")) + callType = CallType.MethodCall; + else if (strCallType.equalsIgnoreCase("Callback")) + callType = CallType.Callback; + } + } + } + + protected void handleStarttagAccesspath(Attributes attributes) { + if ((methodSignature != null && !methodSignature.isEmpty()) + || (fieldSignature != null && !fieldSignature.isEmpty())) { + pathElements = new ArrayList<>(); + pathElementTypes = new ArrayList<>(); + + if (attributes != null) { + String tempStr = attributes.getValue(XMLConstants.IS_SOURCE_ATTRIBUTE); + if (tempStr != null && !tempStr.isEmpty()) + isSource = tempStr.equalsIgnoreCase(XMLConstants.TRUE); + + tempStr = attributes.getValue(XMLConstants.IS_SINK_ATTRIBUTE); + if (tempStr != null && !tempStr.isEmpty()) + isSink = tempStr.equalsIgnoreCase(XMLConstants.TRUE); + + String newDesc = attributes.getValue(XMLConstants.DESCRIPTION_ATTRIBUTE); + if (newDesc != null && !newDesc.isEmpty()) + description = newDesc; + } + } + } + + protected void handleStarttagParam(Attributes attributes, String qNameLower) { + if ((methodSignature != null || fieldSignature != null) && attributes != null) { + String tempStr = attributes.getValue(XMLConstants.INDEX_ATTRIBUTE); + if (tempStr != null && !tempStr.isEmpty()) + paramIndex = Integer.parseInt(tempStr); + + tempStr = attributes.getValue(XMLConstants.TYPE_ATTRIBUTE); + if (tempStr != null && !tempStr.isEmpty()) + paramTypes.add(tempStr.trim()); + } + accessPathParentElement = qNameLower; + description = attributes.getValue(XMLConstants.DESCRIPTION_ATTRIBUTE); + } + + protected void handleStarttagPathelement(Attributes attributes) { + if (methodSignature != null && attributes != null) { + String tempStr = attributes.getValue(XMLConstants.FIELD_ATTRIBUTE); + if (tempStr != null && !tempStr.isEmpty()) { + pathElements.add(tempStr); + } + + tempStr = attributes.getValue(XMLConstants.TYPE_ATTRIBUTE); + if (tempStr != null && !tempStr.isEmpty()) { + pathElementTypes.add(tempStr); + } + } + } + + /** + * Reads the method or field signature from the given attribute map + * + * @param attributes The attribute map from which to read the signature + * @return The signature that identifies a Soot method or field + */ + private String parseSignature(Attributes attributes) { + String signature = attributes.getValue(XMLConstants.SIGNATURE_ATTRIBUTE).trim(); + + // If the user did not specify the brackets, we add them on + // the fly + if (signature != null && !signature.isEmpty()) + if (!signature.startsWith("<")) + signature = "<" + signature + ">"; + return signature; + } + + /** + * PathElement is the only element having values inside, so nothing to do here. + * Doesn't care at the current state of parsing. + **/ + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + } + + /** + * EventHandler for the End of an element. Putting the values into the objects. + * For additional information: startElement description. Starting with the + * innerst elements and switching up to the outer elements + * + * - pathElement -> means field sensitive, adding SootFields + */ + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + String qNameLower = qName.toLowerCase(); + switch (qNameLower) { + + case XMLConstants.CATEGORY_TAG: + category = null; + break; + + case XMLConstants.METHOD_TAG: + handleEndtagMethod(); + break; + + case XMLConstants.FIELD_TAG: + handleEndtagField(); + break; + + case XMLConstants.ACCESSPATH_TAG: + handleEndtagAccesspath(); + break; + + case XMLConstants.BASE_TAG: + accessPathParentElement = ""; + break; + + case XMLConstants.RETURN_TAG: + accessPathParentElement = ""; + break; + + case XMLConstants.PARAM_TAG: + accessPathParentElement = ""; + paramIndex = -1; + paramTypes.clear(); + break; + + case XMLConstants.PATHELEMENT_TAG: + break; + } + } + + protected void handleEndtagMethod() { + if (methodSignature == null) + return; + + // Check whether we have data + if (!baseAPs.isEmpty() || !paramAPs.isEmpty() || !returnAPs.isEmpty()) { + AndroidMethod tempMeth = AndroidMethod.createFromSignature(methodSignature); + + @SuppressWarnings("unchecked") + ISourceSinkDefinition ssd = createMethodSourceSinkDefinition(tempMeth, baseAPs, + paramAPs.toArray(new Set[paramAPs.size()]), returnAPs, callType, category); + addSourceSinkDefinition(methodSignature, ssd); + } + + // Start a new method and discard our old data + methodSignature = null; + fieldSignature = null; + baseAPs = new HashSet<>(); + paramAPs = new ArrayList<>(); + returnAPs = new HashSet<>(); + description = null; + } + + protected void handleEndtagField() { + // Create the field source + if (!baseAPs.isEmpty()) { + ISourceSinkDefinition ssd = createFieldSourceSinkDefinition(fieldSignature, baseAPs, paramAPs); + ssd.setCategory(category); + addSourceSinkDefinition(fieldSignature, ssd); + } + + // Start a new field and discard our old data + methodSignature = null; + fieldSignature = null; + baseAPs = new HashSet<>(); + paramAPs = new ArrayList<>(); + returnAPs = new HashSet<>(); + description = null; + } + + protected void handleEndtagAccesspath() { + // Record the access path for the current element + if (isSource || isSink) { + // Clean up the path elements + if (pathElements != null && pathElements.isEmpty() && pathElementTypes != null + && pathElementTypes.isEmpty()) { + pathElements = null; + pathElementTypes = null; + } + + // Sanity check + if (pathElements != null && pathElementTypes != null && pathElements.size() != pathElementTypes.size()) + throw new RuntimeException( + String.format("Length mismatch between path elements (%d) and their types (%d)", + pathElements.size(), pathElementTypes.size())); + if (pathElements == null || pathElements.isEmpty()) + if (pathElementTypes != null && !pathElementTypes.isEmpty()) + throw new RuntimeException("Got types for path elements, but no elements (i.e., fields)"); + + // Filter the sources and sinks + SourceSinkType sstype = SourceSinkType.fromFlags(isSink, isSource); + if (categoryFilter != null) + sstype = categoryFilter.filter(category, sstype); + + if (sstype != SourceSinkType.Neither) { + AccessPathTuple apt = AccessPathTuple.fromPathElements(pathElements, pathElementTypes, sstype); + + // Optional description + if (description != null && !description.isEmpty()) + apt.setDescription(description); + + // Simplify the AP after setting the description for not breaking the generic + // source definition + apt = apt.simplify(); + + switch (accessPathParentElement) { + case XMLConstants.BASE_TAG: + baseAPs.add(apt); + break; + case XMLConstants.RETURN_TAG: + returnAPs.add(apt); + break; + case XMLConstants.PARAM_TAG: + while (paramAPs.size() <= paramIndex) + paramAPs.add(new HashSet()); + paramAPs.get(paramIndex).add(apt); + } + } + } + + pathElements = null; + pathElementTypes = null; + + isSource = false; + isSink = false; + pathElements = null; + pathElementTypes = null; + + description = null; + } + + } + + protected Map sourcesAndSinks; + + protected Set sources = new HashSet<>(); + protected Set sinks = new HashSet<>(); + protected ICategoryFilter categoryFilter = null; + + protected final Map categories = new HashMap<>(); + + /** + * gets the category for the given values. If this is the first time such a + * category is requested, the respective definition is created. Otherwise, the + * existing one is returned. + * + * @param systemCategory The system-defined category name + * @param customCategory The user-defined category name + * @param customDescription The human-readable description for the custom + * category + * @return The category definition object for the given category names + */ + private ISourceSinkCategory getOrMakeCategory(CATEGORY systemCategory, String customCategory, + String customDescription) { + // For the key in the map, we ignore the description. We do not want to + // have the + // same category id just with different descriptions. + + ISourceSinkCategory keyDef = new CategoryDefinition(systemCategory, customCategory); + return categories.computeIfAbsent(keyDef, + d -> new CategoryDefinition(systemCategory, customCategory, customDescription)); + } + + /** + * Parses the given input stream to obtain the source/sink definitions + * + * @param stream The stream whose data to parse + */ + protected void parseInputStream(InputStream stream) { + SAXParserFactory pf = SAXParserFactory.newInstance(); + try { + pf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + pf.setFeature("http://xml.org/sax/features/external-general-entities", false); + pf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + SAXParser parser = pf.newSAXParser(); + runParse(parser, stream); + } catch (ParserConfigurationException e) { + e.printStackTrace(); + } catch (SAXException e) { + e.printStackTrace(); + } + // Build the source and sink lists + buildSourceSinkLists(); + } + + /** + * Builds the lists of sources and sinks from the data that we have parsed + */ + protected abstract void buildSourceSinkLists(); + + /** + * Runs the parse method on the given parser + * + * @param parser Parser to run parse on + * @param stream Input stream of XML file + */ + protected abstract void runParse(SAXParser parser, InputStream stream); + + /** + * Adds a new source or sink definition + * + * @param signature The signature of the method or field that is considered a + * source or a sink + * @param ssd The source or sink definition + */ + protected void addSourceSinkDefinition(String signature, IAccessPathBasedSourceSinkDefinition ssd) { + if (sourcesAndSinks.containsKey(signature)) + sourcesAndSinks.get(signature).merge(ssd); + else + sourcesAndSinks.put(signature, ssd); + } + + public Set getSources() { + return sources; + } + + public Set getSinks() { + return sinks; + } + + protected static InputStream getStream(String fileName) throws IOException { + File f = new File(fileName); + if (f.exists()) + return new FileInputStream(f); + + return ResourceUtils.getResourceStream(fileName); + } + + public Set getAllMethods() { + Set sourcesSinks = new HashSet<>(sources.size() + sinks.size()); + sourcesSinks.addAll(sources); + sourcesSinks.addAll(sinks); + return sourcesSinks; + } + + protected void addSourceSinkDefinition(String signature, ISourceSinkDefinition ssd) { + if (sourcesAndSinks.containsKey(signature)) + sourcesAndSinks.get(signature).merge(ssd); + else + sourcesAndSinks.put(signature, ssd); + } + + protected abstract ISourceSinkDefinition createFieldSourceSinkDefinition(String signature, + Set baseAPs, List> paramAPs); + + /** + * Factory method for {@link MethodSourceSinkDefinition} instances + * + * @param method The method that is to be defined as a source or sink + * @param baseAPs The access paths rooted in the base object that shall be + * considered as sources or sinks + * @param paramAPs The access paths rooted in parameters that shall be + * considered as sources or sinks. The index in the set + * corresponds to the index of the formal parameter to which + * the respective set of access paths belongs. + * @param returnAPs The access paths rooted in the return object that shall be + * considered as sources or sinks + * @param callType The type of call (normal call, callback, etc.) + * @return The newly created {@link MethodSourceSinkDefinition} instance + */ + protected abstract ISourceSinkDefinition createMethodSourceSinkDefinition(AbstractMethodAndClass method, + Set baseAPs, Set[] paramAPs, Set returnAPs, + CallType callType, ISourceSinkCategory category); + + /** + * Reads the method or field signature from the given attribute map + * + * @param attributes The attribute map from which to read the signature + * @return The signature that identifies a Soot method or field + */ + protected String parseSignature(Attributes attributes) { + String signature = attributes.getValue(XMLConstants.SIGNATURE_ATTRIBUTE).trim(); + + // If the user did not specify the brackets, we add them on + // the fly + if (signature != null && !signature.isEmpty()) + if (!signature.startsWith("<")) + signature = "<" + signature + ">"; + return signature; + } + +} diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/source/parsers/xml/XMLSourceSinkParser.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/source/parsers/xml/XMLSourceSinkParser.java index 38bb4c496..4d2b3d2ae 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/source/parsers/xml/XMLSourceSinkParser.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/source/parsers/xml/XMLSourceSinkParser.java @@ -4,16 +4,11 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; -import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; @@ -21,18 +16,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.xml.sax.Attributes; import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; import soot.jimple.infoflow.InfoflowConfiguration; import soot.jimple.infoflow.android.data.AndroidMethod; -import soot.jimple.infoflow.android.data.CategoryDefinition; -import soot.jimple.infoflow.android.data.CategoryDefinition.CATEGORY; +import soot.jimple.infoflow.data.AbstractMethodAndClass; import soot.jimple.infoflow.sourcesSinks.definitions.AccessPathTuple; import soot.jimple.infoflow.sourcesSinks.definitions.FieldSourceSinkDefinition; import soot.jimple.infoflow.sourcesSinks.definitions.IAccessPathBasedSourceSinkDefinition; import soot.jimple.infoflow.sourcesSinks.definitions.ISourceSinkCategory; +import soot.jimple.infoflow.sourcesSinks.definitions.ISourceSinkDefinition; import soot.jimple.infoflow.sourcesSinks.definitions.ISourceSinkDefinitionProvider; import soot.jimple.infoflow.sourcesSinks.definitions.MethodSourceSinkDefinition; import soot.jimple.infoflow.sourcesSinks.definitions.MethodSourceSinkDefinition.CallType; @@ -46,361 +39,13 @@ * @author Anna-Katharina Wickert * @author Joern Tillmans * @author Steven Arzt + * @author Niklas Vogel */ -public class XMLSourceSinkParser implements ISourceSinkDefinitionProvider { +public class XMLSourceSinkParser extends AbstractXMLSourceSinkParser implements ISourceSinkDefinitionProvider { private final static Logger logger = LoggerFactory.getLogger(XMLSourceSinkParser.class); - /** - * Filter interface for excluding certain categories from the source/sink set - * - * @author Steven Arzt - * - */ - public interface ICategoryFilter { - - /** - * Checks whether methods from the given category shall be parsed or not - * - * @param category The category in which the source or sink is defined - * @return True if the given category shall be parsed, otherwise false - */ - public boolean acceptsCategory(CategoryDefinition category); - - /** - * Filters sources and sinks by category - * - * @param category The category in which the source or sink is defined - * @param sourceSinkType Specifies whether the category is used for sources or - * sinks - * @return The acceptable use of the given category. This is a reduction from - * the given type. If the requested type, for example, is "Both", this - * method may return "Source", if the category shall only be used for - * sources, but not for sinks. - */ - public SourceSinkType filter(CategoryDefinition category, SourceSinkType sourceSinkType); - - } - - /** - * Handler class for the XML parser - * - * @author Anna-Katharina Wickert - * @author Joern Tillmans - * @author Steven Arzt - * - */ - protected class SAXHandler extends DefaultHandler { - - // Holding temporary values for handling with SAX - protected String methodSignature = null; - protected String fieldSignature = null; - - protected CategoryDefinition category; - protected boolean isSource, isSink; - protected List pathElements; - protected List pathElementTypes; - protected int paramIndex; - protected List paramTypes = new ArrayList(); - protected CallType callType; - - protected String accessPathParentElement = ""; - protected String description = ""; - - protected Set baseAPs = new HashSet<>(); - protected List> paramAPs = new ArrayList<>(); - protected Set returnAPs = new HashSet<>(); - - /** - * Event Handler for the starting element for SAX. Possible start elements for - * filling AndroidMethod objects with the new data format: - method: Setting - * parsingvalues to false or null and get and set the signature and category, - - * accessPath: To get the information whether the AndroidMethod is a sink or - * source, - and the other elements doesn't care for creating the AndroidMethod - * object. At these element we will look, if calling the method getAccessPath - * (using an new SAX Handler) - */ - @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) - throws SAXException { - String qNameLower = qName.toLowerCase(); - switch (qNameLower) { - case XMLConstants.CATEGORY_TAG: - String strSysCategory = attributes.getValue(XMLConstants.ID_ATTRIBUTE).trim(); - String strCustomCategory = attributes.getValue(XMLConstants.CUSTOM_ID_ATTRIBUTE); - if (strCustomCategory != null && !strCustomCategory.isEmpty()) - strCustomCategory = strCustomCategory.trim(); - String strCustomDescription = attributes.getValue(XMLConstants.DESCRIPTION_ATTRIBUTE); - if (strCustomDescription != null && !strCustomDescription.isEmpty()) - strCustomDescription = strCustomDescription.trim(); - - category = getOrMakeCategory(CATEGORY.valueOf(strSysCategory), strCustomCategory, strCustomDescription); - - // Check for excluded categories - if (categoryFilter != null && !categoryFilter.acceptsCategory(category)) { - category = null; - } - break; - - case XMLConstants.FIELD_TAG: - if (category != null && attributes != null) { - fieldSignature = parseSignature(attributes); - accessPathParentElement = XMLConstants.BASE_TAG; - } - break; - - case XMLConstants.METHOD_TAG: - if (category != null && attributes != null) { - methodSignature = parseSignature(attributes); - - // Get the call type - callType = CallType.MethodCall; - String strCallType = attributes.getValue(XMLConstants.CALL_TYPE); - if (strCallType != null && !strCallType.isEmpty()) { - strCallType = strCallType.trim(); - if (strCallType.equalsIgnoreCase("MethodCall")) - callType = CallType.MethodCall; - else if (strCallType.equalsIgnoreCase("Callback")) - callType = CallType.Callback; - } - } - break; - - case XMLConstants.ACCESSPATH_TAG: - if ((methodSignature != null && !methodSignature.isEmpty()) - || (fieldSignature != null && !fieldSignature.isEmpty())) { - pathElements = new ArrayList<>(); - pathElementTypes = new ArrayList<>(); - - if (attributes != null) { - String tempStr = attributes.getValue(XMLConstants.IS_SOURCE_ATTRIBUTE); - if (tempStr != null && !tempStr.isEmpty()) - isSource = tempStr.equalsIgnoreCase(XMLConstants.TRUE); - - tempStr = attributes.getValue(XMLConstants.IS_SINK_ATTRIBUTE); - if (tempStr != null && !tempStr.isEmpty()) - isSink = tempStr.equalsIgnoreCase(XMLConstants.TRUE); - - String newDesc = attributes.getValue(XMLConstants.DESCRIPTION_ATTRIBUTE); - if (newDesc != null && !newDesc.isEmpty()) - description = newDesc; - } - } - break; - - case XMLConstants.BASE_TAG: - accessPathParentElement = qNameLower; - description = attributes.getValue(XMLConstants.DESCRIPTION_ATTRIBUTE); - break; - - case XMLConstants.RETURN_TAG: - accessPathParentElement = qNameLower; - description = attributes.getValue(XMLConstants.DESCRIPTION_ATTRIBUTE); - break; - - case XMLConstants.PARAM_TAG: - if (methodSignature != null && attributes != null) { - String tempStr = attributes.getValue(XMLConstants.INDEX_ATTRIBUTE); - if (tempStr != null && !tempStr.isEmpty()) - paramIndex = Integer.parseInt(tempStr); - - tempStr = attributes.getValue(XMLConstants.TYPE_ATTRIBUTE); - if (tempStr != null && !tempStr.isEmpty()) - paramTypes.add(tempStr.trim()); - } - accessPathParentElement = qNameLower; - description = attributes.getValue(XMLConstants.DESCRIPTION_ATTRIBUTE); - break; - - case XMLConstants.PATHELEMENT_TAG: - if (methodSignature != null && attributes != null) { - String tempStr = attributes.getValue(XMLConstants.FIELD_ATTRIBUTE); - if (tempStr != null && !tempStr.isEmpty()) { - pathElements.add(tempStr); - } - - tempStr = attributes.getValue(XMLConstants.TYPE_ATTRIBUTE); - if (tempStr != null && !tempStr.isEmpty()) { - pathElementTypes.add(tempStr); - } - } - break; - } - } - - /** - * Reads the method or field signature from the given attribute map - * - * @param attributes The attribute map from which to read the signature - * @return The signature that identifies a Soot method or field - */ - private String parseSignature(Attributes attributes) { - String signature = attributes.getValue(XMLConstants.SIGNATURE_ATTRIBUTE).trim(); - - // If the user did not specify the brackets, we add them on - // the fly - if (signature != null && !signature.isEmpty()) - if (!signature.startsWith("<")) - signature = "<" + signature + ">"; - return signature; - } - - /** - * PathElement is the only element having values inside, so nothing to do here. - * Doesn't care at the current state of parsing. - **/ - @Override - public void characters(char[] ch, int start, int length) throws SAXException { - } - - /** - * EventHandler for the End of an element. Putting the values into the objects. - * For additional information: startElement description. Starting with the - * innerst elements and switching up to the outer elements - * - * - pathElement -> means field sensitive, adding SootFields - */ - @Override - public void endElement(String uri, String localName, String qName) throws SAXException { - String qNameLower = qName.toLowerCase(); - switch (qNameLower) { - - case XMLConstants.CATEGORY_TAG: - category = null; - break; - - case XMLConstants.METHOD_TAG: - if (methodSignature == null) - break; - - // Check whether we have data - if (!baseAPs.isEmpty() || !paramAPs.isEmpty() || !returnAPs.isEmpty()) { - AndroidMethod tempMeth = AndroidMethod.createFromSignature(methodSignature); - - @SuppressWarnings("unchecked") - IAccessPathBasedSourceSinkDefinition ssd = createMethodSourceSinkDefinition(tempMeth, baseAPs, - paramAPs.toArray(new Set[paramAPs.size()]), returnAPs, callType, category); - addSourceSinkDefinition(methodSignature, ssd); - } - - // Start a new method and discard our old data - methodSignature = null; - fieldSignature = null; - baseAPs = new HashSet<>(); - paramAPs = new ArrayList<>(); - returnAPs = new HashSet<>(); - description = null; - break; - - case XMLConstants.FIELD_TAG: - // Create the field source - if (!baseAPs.isEmpty()) { - IAccessPathBasedSourceSinkDefinition ssd = createFieldSourceSinkDefinition(fieldSignature, baseAPs); - ssd.setCategory(category); - addSourceSinkDefinition(fieldSignature, ssd); - } - - // Start a new field and discard our old data - methodSignature = null; - fieldSignature = null; - baseAPs = new HashSet<>(); - paramAPs = new ArrayList<>(); - returnAPs = new HashSet<>(); - description = null; - break; - - case XMLConstants.ACCESSPATH_TAG: - // Record the access path for the current element - if (isSource || isSink) { - // Clean up the path elements - if (pathElements != null && pathElements.isEmpty() && pathElementTypes != null - && pathElementTypes.isEmpty()) { - pathElements = null; - pathElementTypes = null; - } - - // Sanity check - if (pathElements != null && pathElementTypes != null - && pathElements.size() != pathElementTypes.size()) - throw new RuntimeException( - String.format("Length mismatch between path elements (%d) and their types (%d)", - pathElements.size(), pathElementTypes.size())); - if (pathElements == null || pathElements.isEmpty()) - if (pathElementTypes != null && !pathElementTypes.isEmpty()) - throw new RuntimeException("Got types for path elements, but no elements (i.e., fields)"); - - // Filter the sources and sinks - SourceSinkType sstype = SourceSinkType.fromFlags(isSink, isSource); - if (categoryFilter != null) - sstype = categoryFilter.filter(category, sstype); - - if (sstype != SourceSinkType.Neither) { - AccessPathTuple apt = AccessPathTuple.fromPathElements(pathElements, pathElementTypes, sstype); - - // Optional description - if (description != null && !description.isEmpty()) - apt.setDescription(description); - - // Simplify the AP after setting the description for not breaking the generic - // source definition - apt = apt.simplify(); - - switch (accessPathParentElement) { - case XMLConstants.BASE_TAG: - baseAPs.add(apt); - break; - case XMLConstants.RETURN_TAG: - returnAPs.add(apt); - break; - case XMLConstants.PARAM_TAG: - while (paramAPs.size() <= paramIndex) - paramAPs.add(new HashSet()); - paramAPs.get(paramIndex).add(apt); - } - } - } - - pathElements = null; - pathElementTypes = null; - - isSource = false; - isSink = false; - pathElements = null; - pathElementTypes = null; - - description = null; - break; - - case XMLConstants.BASE_TAG: - accessPathParentElement = ""; - break; - - case XMLConstants.RETURN_TAG: - accessPathParentElement = ""; - break; - - case XMLConstants.PARAM_TAG: - accessPathParentElement = ""; - paramIndex = -1; - paramTypes.clear(); - break; - - case XMLConstants.PATHELEMENT_TAG: - break; - } - } - - } - - protected Map sourcesAndSinks; - - protected Set sources = new HashSet<>(); - protected Set sinks = new HashSet<>(); - protected ICategoryFilter categoryFilter = null; - - protected final Map categories = new HashMap<>(); - // XML stuff incl. Verification against XSD protected static final String XSD_FILE_PATH = "schema/SourcesAndSinks.xsd"; protected static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"; @@ -439,36 +84,9 @@ public static XMLSourceSinkParser fromStream(InputStream inputStream, ICategoryF return pmp; } - @Override - public Set getSources() { - return sources; - } - - @Override - public Set getSinks() { - return sinks; - } - /** - * gets the category for the given values. If this is the first time such a - * category is requested, the respective definition is created. Otherwise, the - * existing one is returned. - * - * @param systemCategory The system-defined category name - * @param customCategory The user-defined category name - * @param customDescription The human-readable description for the custom - * category - * @return The category definition object for the given category names + * Builds the lists of sources and sinks from the data that we have parsed */ - private CategoryDefinition getOrMakeCategory(CATEGORY systemCategory, String customCategory, - String customDescription) { - // For the key in the map, we ignore the description. We do not want to - // have the - // same category id just with different descriptions. - CategoryDefinition keyDef = new CategoryDefinition(systemCategory, customCategory); - return categories.computeIfAbsent(keyDef, - d -> new CategoryDefinition(systemCategory, customCategory, customDescription)); - } /** * Creates a new instance of the {@link XMLSourceSinkParser} class @@ -480,39 +98,13 @@ protected XMLSourceSinkParser(ICategoryFilter categoryFilter) { this.categoryFilter = categoryFilter; } - /** - * Parses the given input stream to obtain the source/sink definitions - * - * @param stream The stream whose data to parse - */ - protected void parseInputStream(InputStream stream) { - SAXParserFactory pf = SAXParserFactory.newInstance(); - try { - // Prevent XXE - pf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - pf.setFeature("http://xml.org/sax/features/external-general-entities", false); - pf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - - SAXParser parser = pf.newSAXParser(); - parser.parse(stream, new SAXHandler()); - } catch (ParserConfigurationException e) { - e.printStackTrace(); - } catch (SAXException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - - // Build the source and sink lists - buildSourceSinkLists(); - } - /** * Builds the lists of sources and sinks from the data that we have parsed */ + @Override protected void buildSourceSinkLists() { - for (IAccessPathBasedSourceSinkDefinition def : sourcesAndSinks.values()) { - IAccessPathBasedSourceSinkDefinition sourceDef = def.getSourceOnlyDefinition(); + for (ISourceSinkDefinition def : sourcesAndSinks.values()) { + ISourceSinkDefinition sourceDef = def.getSourceOnlyDefinition(); if (sourceDef != null && !sourceDef.isEmpty()) { if (sourceDef instanceof MethodSourceSinkDefinition) { MethodSourceSinkDefinition methodSrc = (MethodSourceSinkDefinition) sourceDef; @@ -524,7 +116,7 @@ protected void buildSourceSinkLists() { sources.add(sourceDef); } - IAccessPathBasedSourceSinkDefinition sinkDef = def.getSinkOnlyDefinition(); + ISourceSinkDefinition sinkDef = def.getSinkOnlyDefinition(); if (sinkDef != null && !sinkDef.isEmpty()) { if (sourceDef instanceof MethodSourceSinkDefinition) { MethodSourceSinkDefinition methodSink = (MethodSourceSinkDefinition) sourceDef; @@ -570,28 +162,6 @@ private static void verifyXML(InputStream inp) throws IOException { } } - @Override - public Set getAllMethods() { - Set sourcesSinks = new HashSet<>(sources.size() + sinks.size()); - sourcesSinks.addAll(sources); - sourcesSinks.addAll(sinks); - return sourcesSinks; - } - - /** - * Adds a new source or sink definition - * - * @param signature The signature of the method or field that is considered a - * source or a sink - * @param ssd The source or sink definition - */ - protected void addSourceSinkDefinition(String signature, IAccessPathBasedSourceSinkDefinition ssd) { - if (sourcesAndSinks.containsKey(signature)) - sourcesAndSinks.get(signature).merge(ssd); - else - sourcesAndSinks.put(signature, ssd); - } - /** * Factory method for {@link MethodSourceSinkDefinition} instances * @@ -607,10 +177,15 @@ protected void addSourceSinkDefinition(String signature, IAccessPathBasedSourceS * @param callType The type of call (normal call, callback, etc.) * @return The newly created {@link MethodSourceSinkDefinition} instance */ - protected IAccessPathBasedSourceSinkDefinition createMethodSourceSinkDefinition(AndroidMethod method, + @Override + protected ISourceSinkDefinition createMethodSourceSinkDefinition(AbstractMethodAndClass method, Set baseAPs, Set[] paramAPs, Set returnAPs, CallType callType, ISourceSinkCategory category) { - return new MethodSourceSinkDefinition(method, baseAPs, paramAPs, returnAPs, callType, category); + if (method instanceof AndroidMethod) { + AndroidMethod amethod = (AndroidMethod) method; + return new MethodSourceSinkDefinition(amethod, baseAPs, paramAPs, returnAPs, callType, category); + } + return null; } /** @@ -621,9 +196,25 @@ protected IAccessPathBasedSourceSinkDefinition createMethodSourceSinkDefinition( * sinks * @return The newly created {@link FieldSourceSinkDefinition} instance */ + @Override protected IAccessPathBasedSourceSinkDefinition createFieldSourceSinkDefinition(String signature, - Set baseAPs) { + Set baseAPs, List> paramAPs) { return new FieldSourceSinkDefinition(signature, baseAPs); } + /** + * Run parse method on given parser + * + * @param parser The parser which should be used to parse + * @param stream The XML text input stream which should be parsed + */ + @Override + protected void runParse(SAXParser parser, InputStream stream) { + try { + parser.parse(stream, new SAXHandler()); + } catch (SAXException | IOException e) { + e.printStackTrace(); + } + } + } diff --git a/soot-infoflow/src/soot/jimple/infoflow/Infoflow.java b/soot-infoflow/src/soot/jimple/infoflow/Infoflow.java index 9f0f16c7f..9cb7280d0 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/Infoflow.java +++ b/soot-infoflow/src/soot/jimple/infoflow/Infoflow.java @@ -28,7 +28,6 @@ import soot.MethodOrMethodContext; import soot.PackManager; import soot.PatchingChain; -import soot.PointsToAnalysis; import soot.Scene; import soot.SootMethod; import soot.Unit; @@ -99,7 +98,6 @@ import soot.jimple.infoflow.util.SootMethodRepresentationParser; import soot.jimple.infoflow.util.SystemClassHandler; import soot.jimple.toolkits.callgraph.ReachableMethods; -import soot.jimple.toolkits.pointer.DumbPointerAnalysis; import soot.options.Options; /** @@ -1097,10 +1095,6 @@ protected void eliminateDeadCode(ISourceSinkManager sourcesSinks) { icfgFactory.buildBiDirICFG(config.getCallgraphAlgorithm(), config.getEnableExceptionTracking()), null, null, null, new AccessPathFactory(config), null); - // Dead code elimination may drop the points-to analysis. We need to restore it. - final Scene scene = Scene.v(); - PointsToAnalysis pta = scene.getPointsToAnalysis(); - // We need to exclude the dummy main method and all other artificial methods // that the entry point creator may have generated as well Set excludedMethods = new HashSet<>(); @@ -1111,16 +1105,6 @@ protected void eliminateDeadCode(ISourceSinkManager sourcesSinks) { ICodeOptimizer dce = new DeadCodeEliminator(); dce.initialize(config); dce.run(dceManager, excludedMethods, sourcesSinks, taintWrapper); - - // Restore the points-to analysis. This may restore a PAG that contains outdated - // methods, but it's still better than running the entire callgraph algorithm - // again. Continuing with the DumbPointerAnalysis is not a viable solution - // either, since it heavily over-approximates. - if (pta != null && !(pta instanceof DumbPointerAnalysis)) { - PointsToAnalysis newPta = scene.getPointsToAnalysis(); - if (newPta == null || newPta instanceof DumbPointerAnalysis) - scene.setPointsToAnalysis(pta); - } } protected Collection getMethodsForSeeds(IInfoflowCFG icfg) { diff --git a/soot-infoflow/src/soot/jimple/infoflow/data/AbstractMethodAndClass.java b/soot-infoflow/src/soot/jimple/infoflow/data/AbstractMethodAndClass.java new file mode 100644 index 000000000..4d5c80ac9 --- /dev/null +++ b/soot-infoflow/src/soot/jimple/infoflow/data/AbstractMethodAndClass.java @@ -0,0 +1,104 @@ +package soot.jimple.infoflow.data; + +import java.util.List; + +/** + * Abstract class for all MethodAndClass containers. + * + * @author Steven Arzt + * @author Niklas Vogel + * + */ +public abstract class AbstractMethodAndClass { + protected final String methodName; + protected final String className; + protected final String returnType; + protected final List parameters; + + private String subSignature = null; + private String signature = null; + + public AbstractMethodAndClass(String methodName, String className, String returnType, List parameters) { + this.methodName = methodName; + this.className = className; + this.returnType = returnType; + this.parameters = parameters; + } + + public String getMethodName() { + return this.methodName; + } + + public String getClassName() { + return this.className; + } + + public String getReturnType() { + return this.returnType; + } + + public List getParameters() { + return this.parameters; + } + + /** + * Get subsignature of stored method and class + * + * @return Subsignature + */ + public String getSubSignature() { + if (subSignature != null) + return subSignature; + + StringBuilder sb = new StringBuilder( + 10 + this.returnType.length() + this.methodName.length() + (this.parameters.size() * 30)); + if (!this.returnType.isEmpty()) { + sb.append(this.returnType); + sb.append(" "); + } + sb.append(this.methodName); + sb.append("("); + + for (int i = 0; i < this.parameters.size(); i++) { + if (i > 0) + sb.append(","); + sb.append(this.parameters.get(i).trim()); + } + sb.append(")"); + this.subSignature = sb.toString(); + + return this.subSignature; + } + + /** + * Get the signature of the stored method and class + * + * @return Signature + */ + public String getSignature() { + if (signature != null) + return signature; + + StringBuilder sb = new StringBuilder(10 + this.className.length() + this.returnType.length() + + this.methodName.length() + (this.parameters.size() * 30)); + sb.append("<"); + sb.append(this.className); + sb.append(": "); + if (!this.returnType.isEmpty()) { + sb.append(this.returnType); + sb.append(" "); + } + sb.append(this.methodName); + sb.append("("); + + for (int i = 0; i < this.parameters.size(); i++) { + if (i > 0) + sb.append(","); + sb.append(this.parameters.get(i).trim()); + } + sb.append(")>"); + this.signature = sb.toString(); + + return this.signature; + } +} diff --git a/soot-infoflow/src/soot/jimple/infoflow/data/SootMethodAndClass.java b/soot-infoflow/src/soot/jimple/infoflow/data/SootMethodAndClass.java index bc98eebcc..749aa8960 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/data/SootMethodAndClass.java +++ b/soot-infoflow/src/soot/jimple/infoflow/data/SootMethodAndClass.java @@ -11,6 +11,7 @@ package soot.jimple.infoflow.data; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import soot.SootMethod; @@ -20,117 +21,39 @@ * Data container which stores the string representation of a SootMethod and its * corresponding class */ -public class SootMethodAndClass { - private final String methodName; - private final String className; - private final String returnType; - private final List parameters; +public class SootMethodAndClass extends AbstractMethodAndClass { - private String subSignature = null; - private String signature = null; private int hashCode = 0; public SootMethodAndClass(String methodName, String className, String returnType, List parameters) { - this.methodName = methodName; - this.className = className; - this.returnType = returnType; - this.parameters = parameters; + super(methodName, className, returnType, parameters); } public SootMethodAndClass(String methodName, String className, String returnType, String parameters) { - this.methodName = methodName; - this.className = className; - this.returnType = returnType; + super(methodName, className, returnType, parameterFromString(parameters)); + } - this.parameters = new ArrayList<>(); + private static List parameterFromString(String parameters) { if (parameters != null && !parameters.isEmpty()) { - String[] params = parameters.split(","); - for (String s : params) - this.parameters.add(s); + return Arrays.asList(parameters.split(",")); } + return new ArrayList<>(); } public SootMethodAndClass(SootMethod sm) { - this.methodName = sm.getName(); - this.className = sm.getDeclaringClass().getName(); - this.returnType = sm.getReturnType().toString(); - this.parameters = new ArrayList(); - for (Type p : sm.getParameterTypes()) - this.parameters.add(p.toString()); - } - - public SootMethodAndClass(SootMethodAndClass methodAndClass) { - this.methodName = methodAndClass.methodName; - this.className = methodAndClass.className; - this.returnType = methodAndClass.returnType; - this.parameters = new ArrayList(methodAndClass.parameters); - } - - public String getMethodName() { - return this.methodName; - } - - public String getClassName() { - return this.className; - } - - public String getReturnType() { - return this.returnType; - } - - public List getParameters() { - return this.parameters; + super(sm.getName(), sm.getDeclaringClass().getName(), sm.getReturnType().toString(), parameterFromMethod(sm)); } - public String getSubSignature() { - if (subSignature != null) - return subSignature; - - StringBuilder sb = new StringBuilder( - 10 + this.returnType.length() + this.methodName.length() + (this.parameters.size() * 30)); - if (!this.returnType.isEmpty()) { - sb.append(this.returnType); - sb.append(" "); - } - sb.append(this.methodName); - sb.append("("); - - for (int i = 0; i < this.parameters.size(); i++) { - if (i > 0) - sb.append(","); - sb.append(this.parameters.get(i).trim()); - } - sb.append(")"); - this.subSignature = sb.toString(); - - return this.subSignature; + private static List parameterFromMethod(SootMethod sm) { + ArrayList parameters = new ArrayList(); + for (Type p : sm.getParameterTypes()) + parameters.add(p.toString()); + return parameters; } - public String getSignature() { - if (signature != null) - return signature; - - StringBuilder sb = new StringBuilder(10 + this.className.length() + this.returnType.length() - + this.methodName.length() + (this.parameters.size() * 30)); - sb.append("<"); - sb.append(this.className); - sb.append(": "); - if (!this.returnType.isEmpty()) { - sb.append(this.returnType); - sb.append(" "); - } - sb.append(this.methodName); - sb.append("("); - - for (int i = 0; i < this.parameters.size(); i++) { - if (i > 0) - sb.append(","); - sb.append(this.parameters.get(i).trim()); - } - sb.append(")>"); - this.signature = sb.toString(); - - return this.signature; + public SootMethodAndClass(SootMethodAndClass methodAndClass) { + super(methodAndClass.methodName, methodAndClass.className, methodAndClass.returnType, + new ArrayList(methodAndClass.parameters)); } @Override diff --git a/soot-infoflow/src/soot/jimple/infoflow/sourcesSinks/definitions/ISourceSinkDefinition.java b/soot-infoflow/src/soot/jimple/infoflow/sourcesSinks/definitions/ISourceSinkDefinition.java index bd8f468c5..9ed969943 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/sourcesSinks/definitions/ISourceSinkDefinition.java +++ b/soot-infoflow/src/soot/jimple/infoflow/sourcesSinks/definitions/ISourceSinkDefinition.java @@ -46,4 +46,11 @@ public interface ISourceSinkDefinition { */ public abstract void merge(ISourceSinkDefinition other); + /** + * Indicates if the definition contains any sources or sinks + * + * @return The boolean if this definition is empty + */ + public abstract boolean isEmpty(); + }