From 4ba36cc7a892ffd949940253ff835d838deafd29 Mon Sep 17 00:00:00 2001 From: AnonymousAnalyst <> Date: Tue, 2 Mar 2021 22:23:14 +0100 Subject: [PATCH 1/7] add support for fd result --- pom.xml | 2 +- .../swt/tbviewer/FlowDroidResultParser.java | 173 ++++++++++++++++++ .../de/upb/swt/tbviewer/InputValidation.java | 11 ++ .../java/de/upb/swt/tbviewer/Location.java | 9 + src/main/java/de/upb/swt/tbviewer/Main.java | 1 + .../java/de/upb/swt/tbviewer/TaintFlow.java | 13 ++ .../upb/swt/tbviewer/TaintLanguageServer.java | 4 - .../upb/swt/tbviewer/TaintServerAnalysis.java | 45 ++++- 8 files changed, 244 insertions(+), 14 deletions(-) create mode 100644 src/main/java/de/upb/swt/tbviewer/FlowDroidResultParser.java diff --git a/pom.xml b/pom.xml index c1e541f..7f7f2b3 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ de.upb.swt TB-Viewer - 0.0.2-SNAPSHOT + 0.0.3-SNAPSHOT jar TB-Viewer diff --git a/src/main/java/de/upb/swt/tbviewer/FlowDroidResultParser.java b/src/main/java/de/upb/swt/tbviewer/FlowDroidResultParser.java new file mode 100644 index 0000000..15c5126 --- /dev/null +++ b/src/main/java/de/upb/swt/tbviewer/FlowDroidResultParser.java @@ -0,0 +1,173 @@ +package de.upb.swt.tbviewer; + +import com.ibm.wala.util.collections.Pair; +import de.foellix.aql.datastructure.Reference; +import de.foellix.aql.datastructure.Statement; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import javax.xml.stream.FactoryConfigurationError; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +public class FlowDroidResultParser { + + class Tags { + + public static final String root = "DataFlowResults"; + + public static final String results = "Results"; + public static final String result = "Result"; + + public static final String performanceData = "PerformanceData"; + public static final String performanceEntry = "PerformanceEntry"; + + public static final String sink = "Sink"; + public static final String accessPath = "AccessPath"; + + public static final String fields = "Fields"; + public static final String field = "Field"; + + public static final String sources = "Sources"; + public static final String source = "Source"; + + public static final String taintPath = "TaintPath"; + public static final String pathElement = "PathElement"; + } + + class Attributes { + + public static final String fileFormatVersion = "FileFormatVersion"; + public static final String terminationState = "TerminationState"; + public static final String statement = "Statement"; + public static final String method = "Method"; + + public static final String value = "Value"; + public static final String type = "Type"; + public static final String taintSubFields = "TaintSubFields"; + + public static final String category = "Category"; + + public static final String name = "Name"; + } + + public static boolean hasFormat(String fileName) + throws XMLStreamException, FileNotFoundException, FactoryConfigurationError { + + XMLStreamReader reader = + XMLInputFactory.newInstance().createXMLStreamReader(new FileInputStream(fileName)); + while (reader.hasNext()) { + reader.next(); + if (reader.hasName() && reader.isStartElement()) { + if (reader.getLocalName().equals(Tags.root)) { + return true; + } + } + } + return false; + } + + public static Set readResultsWithPath(String fileName) + throws XMLStreamException, IOException { + XMLStreamReader reader = + XMLInputFactory.newInstance().createXMLStreamReader(new FileInputStream(fileName)); + Set results = new HashSet<>(); + boolean hasFormat = false; + Location sink = null; + Location source = null; + ArrayList pathElements = new ArrayList<>(); + String statement = null; + String method = null; + while (reader.hasNext()) { + reader.next(); + if (reader.hasName()) { + if (reader.getLocalName().equals(Tags.root) && reader.isStartElement()) { + hasFormat = true; + } else if (reader.getLocalName().equals(Tags.sink) && reader.isStartElement()) { + statement = getAttributeByName(reader, Attributes.statement); + method = getAttributeByName(reader, Attributes.method); + } else if (reader.getLocalName().equals(Tags.source) && reader.isStartElement()) { + statement = getAttributeByName(reader, Attributes.statement); + method = getAttributeByName(reader, Attributes.method); + } else if (reader.getLocalName().equals(Tags.pathElement) && reader.isStartElement()) { + statement = getAttributeByName(reader, Attributes.statement); + method = getAttributeByName(reader, Attributes.method); + } else if (reader.isEndElement()) { + if (reader.getLocalName().equals(Tags.sink)) sink = new Location(method, statement, -1); + else if (reader.getLocalName().equals(Tags.source)) { + source = new Location(method, statement, -1); + results.add(new TaintFlow(source, sink, pathElements)); + pathElements = new ArrayList<>(); + } else if (reader.getLocalName().equals(Tags.pathElement)) + pathElements.add(new Location(method, statement, -1)); + } + } + } + if (!hasFormat) return null; + return results; + } + + public static Map>> convertToAQL( + Set flows) { + Map>> res = new HashMap<>(); + int i = 1; + for (TaintFlow flow : flows) { + ArrayList all = new ArrayList<>(); + all.add(flow.getSource()); + all.addAll(flow.getIntermediate()); + all.add(flow.getSink()); + String id = i + ""; + if (!res.containsKey(id)) res.put(id, new TreeMap<>()); + for (int j = 0; j < all.size() - 1; j++) { + Location f = all.get(j); + Statement sf = new Statement(); + Reference from = new Reference(); + from.setType("from"); + from.setClassname(f.getClassSignature()); + from.setMethod(f.getMethodSignature()); + sf.setLinenumber(-1); + sf.setStatementfull(f.getStatement()); + String s = f.getStatement(); + if (s.contains("<") && s.contains(">")) { + s = s.substring(s.indexOf("<") + 1); + s = s.substring(0, s.indexOf(">")); + } + sf.setStatementgeneric(s); + from.setStatement(sf); + + Location t = all.get(j + 1); + Statement st = new Statement(); + Reference to = new Reference(); + to.setType("to"); + to.setClassname(t.getClassSignature()); + to.setMethod(t.getMethodSignature()); + st.setLinenumber(-1); + st.setStatementfull(t.getStatement()); + s = t.getStatement(); + if (s.contains("<") && s.contains(">")) { + s = s.substring(s.indexOf("<") + 1); + s = s.substring(0, s.indexOf(">")); + } + st.setStatementgeneric(s); + to.setStatement(st); + Pair step = Pair.make(from, to); + res.get(id).put(j, step); + } + i++; + } + return res; + } + + public static String getAttributeByName(XMLStreamReader reader, String id) { + for (int i = 0; i < reader.getAttributeCount(); i++) + if (reader.getAttributeLocalName(i).equals(id)) return reader.getAttributeValue(i); + return ""; + } +} diff --git a/src/main/java/de/upb/swt/tbviewer/InputValidation.java b/src/main/java/de/upb/swt/tbviewer/InputValidation.java index dbe94f6..7bc96a1 100644 --- a/src/main/java/de/upb/swt/tbviewer/InputValidation.java +++ b/src/main/java/de/upb/swt/tbviewer/InputValidation.java @@ -10,6 +10,8 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; +import javax.xml.stream.FactoryConfigurationError; +import javax.xml.stream.XMLStreamException; /** @author Linghui Luo */ public class InputValidation { @@ -68,4 +70,13 @@ public static boolean isAQLformat(File file) { if (answer == null) return false; return true; } + + public static boolean isFlowDroidFormat(File file) { + try { + return FlowDroidResultParser.hasFormat(file.getAbsolutePath()); + } catch (FileNotFoundException | XMLStreamException | FactoryConfigurationError e) { + e.printStackTrace(); + return false; + } + } } diff --git a/src/main/java/de/upb/swt/tbviewer/Location.java b/src/main/java/de/upb/swt/tbviewer/Location.java index b2d7508..51a0e86 100644 --- a/src/main/java/de/upb/swt/tbviewer/Location.java +++ b/src/main/java/de/upb/swt/tbviewer/Location.java @@ -149,6 +149,15 @@ public Location( this.linenumber = linenumber; } + public Location(String method, String statement, int linenumber) { + this.kind = LocationKind.Jimple; + this.statement = statement; + String[] splits = method.split(":"); + this.classSignature = splits[0].replace("<", "").trim(); + this.methodSignature = splits[1].replace(">", "").trim(); + this.linenumber = linenumber; + } + public static boolean maybeEqual(Location a, Location b, boolean compareStatements) { if (a.kind == b.kind) { return a.classSignature.equals(b.classSignature) diff --git a/src/main/java/de/upb/swt/tbviewer/Main.java b/src/main/java/de/upb/swt/tbviewer/Main.java index d1ad37c..f87f1ee 100644 --- a/src/main/java/de/upb/swt/tbviewer/Main.java +++ b/src/main/java/de/upb/swt/tbviewer/Main.java @@ -20,6 +20,7 @@ public static void main(String... args) { ServerConfiguration config = new ServerConfiguration(); config.setDoAnalysisByOpen(false); config.setDoAnalysisBySave(false); + config.setDoAnalysisByFirstOpen(false); TaintLanguageServer server = new TaintLanguageServer(config); String language = "java"; IProjectService javaProjectService = new JavaProjectService(); diff --git a/src/main/java/de/upb/swt/tbviewer/TaintFlow.java b/src/main/java/de/upb/swt/tbviewer/TaintFlow.java index c791f46..efc02a2 100644 --- a/src/main/java/de/upb/swt/tbviewer/TaintFlow.java +++ b/src/main/java/de/upb/swt/tbviewer/TaintFlow.java @@ -7,6 +7,19 @@ public class TaintFlow { private Location source; private Location sink; + + public Location getSource() { + return source; + } + + public Location getSink() { + return sink; + } + + public ArrayList getIntermediate() { + return intermediate; + } + private ArrayList intermediate; public TaintFlow(Location source, Location sink, ArrayList intermediate) { diff --git a/src/main/java/de/upb/swt/tbviewer/TaintLanguageServer.java b/src/main/java/de/upb/swt/tbviewer/TaintLanguageServer.java index 2167381..b4ba58c 100644 --- a/src/main/java/de/upb/swt/tbviewer/TaintLanguageServer.java +++ b/src/main/java/de/upb/swt/tbviewer/TaintLanguageServer.java @@ -27,8 +27,6 @@ import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.DiagnosticSeverity; import org.eclipse.lsp4j.InitializedParams; -import org.eclipse.lsp4j.MessageParams; -import org.eclipse.lsp4j.MessageType; import org.eclipse.lsp4j.PublishDiagnosticsParams; import org.eclipse.lsp4j.jsonrpc.Launcher; import org.eclipse.lsp4j.launch.LSPLauncher.Builder; @@ -53,8 +51,6 @@ public void initialized(InitializedParams params) { this.getProjectService("java").get().setRootPath(this.rootPath.get()); } } - this.client.showMessage( - new MessageParams(MessageType.Info, "The analyzer started analyzing the code.")); doAnalysis("java", true); } diff --git a/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java b/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java index 0346c09..c58a59c 100644 --- a/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java +++ b/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java @@ -42,6 +42,7 @@ import java.util.TreeMap; import java.util.regex.Pattern; import java.util.stream.Stream; +import javax.xml.stream.XMLStreamException; import magpiebridge.core.AnalysisConsumer; import magpiebridge.core.AnalysisResult; import magpiebridge.core.Kind; @@ -62,6 +63,7 @@ public class TaintServerAnalysis implements ServerAnalysis { private String aqlResultPath; // Path to XML-file in AQLResult-format defines the taint analysis analysis // results. + private String fdResultPath; private Map>> loadedAqlResults; // > @@ -560,15 +562,27 @@ protected void searchInputFiles(String rootPath) { continue; } else groundTruthPath = rootFile.getAbsolutePath() + File.separator + f.getName(); } - if (f.getName().endsWith(".xml") && InputValidation.isAQLformat(f)) - if (aqlResultPath != null) { - Logger.log( - TaintAnalysisResult.class.getName(), - "There are multiple XML files with AQLResult-format in " - + rootPath - + ". Please only keep one."); - continue; - } else aqlResultPath = rootFile.getAbsolutePath() + File.separator + f.getName(); + if (f.getName().endsWith(".xml")) + if (InputValidation.isAQLformat(f)) { + if (aqlResultPath != null) { + Logger.log( + TaintAnalysisResult.class.getName(), + "There are multiple XML files with AQLResult-format in " + + rootPath + + ". Please only keep one."); + continue; + } else aqlResultPath = rootFile.getAbsolutePath() + File.separator + f.getName(); + } else if (InputValidation.isFlowDroidFormat(f)) { + if (fdResultPath != null) { + Logger.log( + TaintAnalysisResult.class.getName(), + "There are multiple XML files with FlowDroid-Result-format in " + + rootPath + + ". Please only keep one."); + } else { + fdResultPath = rootFile.getAbsolutePath() + File.separator + f.getName(); + } + } } } } @@ -598,6 +612,19 @@ public void analyze( loadAqlResults(); readAqlResults(); } + + if (fdResultPath != null) { + try { + this.loadedAqlResults = + FlowDroidResultParser.convertToAQL( + FlowDroidResultParser.readResultsWithPath(fdResultPath)); + } catch (XMLStreamException | IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + readAqlResults(); + } + Collection allResults = new ArrayList<>(); allResults.addAll(groundTruth); allResults.addAll(aqlResults); From cf0b06bbd1154c51e8ab30cac08afdea19093ceb Mon Sep 17 00:00:00 2001 From: AnonymousAnalyst <> Date: Tue, 9 Mar 2021 14:10:56 +0100 Subject: [PATCH 2/7] support loading flowDroid result --- .../swt/tbviewer/FlowDroidResultParser.java | 40 +- .../java/de/upb/swt/tbviewer/Location.java | 2 +- .../upb/swt/tbviewer/TaintAnalysisResult.java | 24 ++ .../upb/swt/tbviewer/TaintServerAnalysis.java | 120 +++++- .../upb/swt/tbviewer/InputValidationTest.java | 20 + vscode/src/extension.ts | 400 +++++++++--------- 6 files changed, 388 insertions(+), 218 deletions(-) create mode 100644 src/test/java/de/upb/swt/tbviewer/InputValidationTest.java diff --git a/src/main/java/de/upb/swt/tbviewer/FlowDroidResultParser.java b/src/main/java/de/upb/swt/tbviewer/FlowDroidResultParser.java index 15c5126..2837945 100644 --- a/src/main/java/de/upb/swt/tbviewer/FlowDroidResultParser.java +++ b/src/main/java/de/upb/swt/tbviewer/FlowDroidResultParser.java @@ -9,6 +9,7 @@ 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 java.util.TreeMap; @@ -83,30 +84,37 @@ public static Set readResultsWithPath(String fileName) Location sink = null; Location source = null; ArrayList pathElements = new ArrayList<>(); - String statement = null; - String method = null; + String sinkStatement = null; + String sinkMethod = null; + String sourceStatement = null; + String sourceMethod = null; + String pathStatement = null; + String pathMethod = null; while (reader.hasNext()) { reader.next(); if (reader.hasName()) { if (reader.getLocalName().equals(Tags.root) && reader.isStartElement()) { hasFormat = true; } else if (reader.getLocalName().equals(Tags.sink) && reader.isStartElement()) { - statement = getAttributeByName(reader, Attributes.statement); - method = getAttributeByName(reader, Attributes.method); + sinkStatement = getAttributeByName(reader, Attributes.statement); + sinkMethod = getAttributeByName(reader, Attributes.method); } else if (reader.getLocalName().equals(Tags.source) && reader.isStartElement()) { - statement = getAttributeByName(reader, Attributes.statement); - method = getAttributeByName(reader, Attributes.method); + sourceStatement = getAttributeByName(reader, Attributes.statement); + sourceMethod = getAttributeByName(reader, Attributes.method); } else if (reader.getLocalName().equals(Tags.pathElement) && reader.isStartElement()) { - statement = getAttributeByName(reader, Attributes.statement); - method = getAttributeByName(reader, Attributes.method); + pathStatement = getAttributeByName(reader, Attributes.statement); + pathMethod = getAttributeByName(reader, Attributes.method); } else if (reader.isEndElement()) { - if (reader.getLocalName().equals(Tags.sink)) sink = new Location(method, statement, -1); - else if (reader.getLocalName().equals(Tags.source)) { - source = new Location(method, statement, -1); + if (reader.getLocalName().equals(Tags.sink)) { + sink = new Location(sinkMethod, sinkStatement, -1); + } else if (reader.getLocalName().equals(Tags.source)) { + source = new Location(sourceMethod, sourceStatement, -1); results.add(new TaintFlow(source, sink, pathElements)); pathElements = new ArrayList<>(); - } else if (reader.getLocalName().equals(Tags.pathElement)) - pathElements.add(new Location(method, statement, -1)); + } else if (reader.getLocalName().equals(Tags.pathElement)) { + Location pathElement = new Location(pathMethod, pathStatement, -1); + pathElements.add(pathElement); + } } } } @@ -121,7 +129,11 @@ public static Map>> convertT for (TaintFlow flow : flows) { ArrayList all = new ArrayList<>(); all.add(flow.getSource()); - all.addAll(flow.getIntermediate()); + ArrayList inter = flow.getIntermediate(); + if (inter.size() >= 2) { + List path = inter.subList(1, inter.size() - 1); + all.addAll(path); + } all.add(flow.getSink()); String id = i + ""; if (!res.containsKey(id)) res.put(id, new TreeMap<>()); diff --git a/src/main/java/de/upb/swt/tbviewer/Location.java b/src/main/java/de/upb/swt/tbviewer/Location.java index 51a0e86..be46ac8 100644 --- a/src/main/java/de/upb/swt/tbviewer/Location.java +++ b/src/main/java/de/upb/swt/tbviewer/Location.java @@ -154,7 +154,7 @@ public Location(String method, String statement, int linenumber) { this.statement = statement; String[] splits = method.split(":"); this.classSignature = splits[0].replace("<", "").trim(); - this.methodSignature = splits[1].replace(">", "").trim(); + this.methodSignature = method.replace("<", "").replace(">", ""); this.linenumber = linenumber; } diff --git a/src/main/java/de/upb/swt/tbviewer/TaintAnalysisResult.java b/src/main/java/de/upb/swt/tbviewer/TaintAnalysisResult.java index e7f254c..b677088 100644 --- a/src/main/java/de/upb/swt/tbviewer/TaintAnalysisResult.java +++ b/src/main/java/de/upb/swt/tbviewer/TaintAnalysisResult.java @@ -1,7 +1,9 @@ package de.upb.swt.tbviewer; +import com.google.gson.JsonObject; import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position; import com.ibm.wala.util.collections.Pair; +import java.util.Optional; import magpiebridge.core.AnalysisResult; import magpiebridge.core.Kind; import org.eclipse.lsp4j.DiagnosticSeverity; @@ -15,6 +17,7 @@ public class TaintAnalysisResult implements AnalysisResult { private final DiagnosticSeverity severity; private final Pair repair; private final String code; + private final Optional finding; public TaintAnalysisResult( Kind kind, @@ -24,6 +27,19 @@ public TaintAnalysisResult( DiagnosticSeverity severity, Pair repair, String code) { + this(Optional.empty(), kind, pos, msg, relatedInfo, severity, repair, code); + } + + public TaintAnalysisResult( + Optional finding, + Kind kind, + Position pos, + String msg, + Iterable> relatedInfo, + DiagnosticSeverity severity, + Pair repair, + String code) { + this.finding = finding; this.kind = kind; this.position = pos; this.message = msg; @@ -79,4 +95,12 @@ public String toString() { public String code() { return code; } + + public TaintAnalysisResult copy(String msg) { + return new TaintAnalysisResult(kind, position, msg, related, severity, repair, code); + } + + public Optional getFinding() { + return this.finding; + } } diff --git a/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java b/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java index c58a59c..b6f1e07 100644 --- a/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java +++ b/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java @@ -153,8 +153,8 @@ protected void readGroundTruth() { JsonObject finding = findings.get(i).getAsJsonObject(); boolean isNegativeFlow = finding.get("isNegative").getAsBoolean(); String ID = finding.get("ID").toString(); - StringBuilder message = new StringBuilder(ID + ". Malicious taint flow"); - if (isNegativeFlow) message = new StringBuilder(ID + ". Negative taint flow"); + StringBuilder message = new StringBuilder(ID + "P. Malicious taint flow"); + if (isNegativeFlow) message = new StringBuilder(ID + "N. Negative taint flow"); String description = finding.get("description").getAsString(); // process source info JsonObject source = finding.getAsJsonObject("source"); @@ -303,10 +303,18 @@ protected void readGroundTruth() { if (!isNegativeFlow) { sinkResult = new TaintAnalysisResult( - Kind.Diagnostic, sinkPos, msg, related, DiagnosticSeverity.Error, null, null); + Optional.of(finding), + Kind.Diagnostic, + sinkPos, + msg, + related, + DiagnosticSeverity.Error, + null, + null); } else { sinkResult = new TaintAnalysisResult( + Optional.of(finding), Kind.Diagnostic, sinkPos, msg, @@ -372,7 +380,7 @@ protected void readAqlResults() { this.nonMatchedResults.clear(); for (Entry>> entry : this.loadedAqlResults.entrySet()) { - String id = entry.getKey(); + String id = entry.getKey() + "D"; TreeMap> flow = entry.getValue(); ArrayList> related = new ArrayList>(); // process source info @@ -419,9 +427,39 @@ protected void readAqlResults() { prefix = "(AMBIGUOUS) "; } sinkMsg = prefix + sinkMsg; + for (Pair sinkInfo : possibleSinkInfos) { if (sinkInfo != null) { related.add(Pair.make(sinkInfo.fst, "SINK: " + sinkInfo.snd)); + String matchedFlowID = null; + // for (Pair possibleSource : possibleSourceInfos) { + // search if there is match in ground truth + // Optional res = findFlowMatchUsingSourceCode(possibleSource, sinkInfo); + + // if (res.isPresent()) { + // matchedFlowID = res.get().toString(false).split("\\.")[0]; + + // } + // } + // search if there is match in ground truth + Map res = findFlowMatchUsingJimple(source, sink); + if (!res.isEmpty()) { + for (Entry pair : res.entrySet()) { + String certain = ""; + if (!pair.getValue()) { + certain = "?"; + } + if (matchedFlowID == null) { + matchedFlowID = certain + pair.getKey().toString(false).split("\\.")[0]; + } else { + matchedFlowID += ", " + certain + pair.getKey().toString(false).split("\\.")[0]; + } + } + } + if (matchedFlowID != null) { + String detectedFlowID = sinkMsg.split("\\.")[0]; + sinkMsg = sinkMsg.replace(detectedFlowID, detectedFlowID + "[" + matchedFlowID + "]"); + } TaintAnalysisResult aqlresult = new TaintAnalysisResult( Kind.Diagnostic, @@ -432,12 +470,9 @@ protected void readAqlResults() { null, null); aqlResults.add(aqlresult); - for (Pair possibleSource : possibleSourceInfos) { - // search if there is match in ground truth - Optional res = findFlowMatch(possibleSource, sinkInfo); - if (res.isPresent()) { + if (matchedFlowID != null) { + if (!(matchedFlowID.contains("N") && matchedFlowID.contains("P"))) this.matchedResults.add(aqlresult); - } } } } @@ -462,7 +497,7 @@ protected Optional> findLocationMatch(Location * @param aqlflow * @return */ - protected Optional findFlowMatch( + protected Optional findFlowMatchUsingSourceCode( Pair source, Pair sink) { for (AnalysisResult groundTruthFlow : this.groundTruthFlows.values()) { Position sinkP = groundTruthFlow.position(); @@ -475,6 +510,71 @@ protected Optional findFlowMatch( return Optional.empty(); } + protected Map findFlowMatchUsingJimple( + Reference source, Reference sink) { + Map res = new HashMap<>(); + for (AnalysisResult groundTruthFlow : this.groundTruthFlows.values()) { + TaintAnalysisResult flow = (TaintAnalysisResult) groundTruthFlow; + if (flow.getFinding().isPresent()) { + + JsonObject finding = flow.getFinding().get(); + JsonObject jsonsource = finding.getAsJsonObject("source"); + String sourceClassName = jsonsource.get("className").getAsString(); + String sourceMethodName = jsonsource.get("methodName").getAsString(); + String sourceJimpleStatement = null; + if (jsonsource.has("IRs") && jsonsource.get("IRs").getAsJsonArray().size() > 0) { + JsonObject IR = jsonsource.get("IRs").getAsJsonArray().get(0).getAsJsonObject(); + if (IR.get("type").getAsString().equals("Jimple")) { + sourceJimpleStatement = IR.get("IRstatement").getAsString(); + } + } + JsonObject jsonsink = finding.getAsJsonObject("sink"); + String sinkClassName = jsonsink.get("className").getAsString(); + String sinkMethodName = jsonsink.get("methodName").getAsString(); + String sinkJimpleStatement = null; + if (jsonsource.has("IRs") && jsonsink.get("IRs").getAsJsonArray().size() > 0) { + JsonObject IR = jsonsink.get("IRs").getAsJsonArray().get(0).getAsJsonObject(); + if (IR.get("type").getAsString().equals("Jimple")) { + sinkJimpleStatement = IR.get("IRstatement").getAsString(); + } + } + boolean isNegativeFlow = finding.get("isNegative").getAsBoolean(); + if (sourceJimpleStatement != null && sinkJimpleStatement != null) { + Location jsonSource = + new Location(LocationKind.Java, sourceClassName, sourceMethodName, null, -1); + Location jsonSink = + new Location(LocationKind.Java, sinkClassName, sinkMethodName, null, -1); + Location xmlSource = + new Location( + LocationKind.Jimple, source.getClassname(), source.getMethod(), null, -1); + Location xmlSink = + new Location(LocationKind.Jimple, sink.getClassname(), sink.getMethod(), null, -1); + String xmlSourceStmt = source.getStatement().getStatementfull(); + String xmlSinkStmt = sink.getStatement().getStatementfull(); + if (Location.maybeEqual(jsonSource, xmlSource, false) + && Location.maybeEqual(jsonSink, xmlSink, false)) { + if (sourceJimpleStatement.equals(xmlSourceStmt) + && sinkJimpleStatement.equals(xmlSinkStmt)) { + res.put(groundTruthFlow, true); + } else { + if (!isNegativeFlow) { + sourceJimpleStatement = sourceJimpleStatement.replaceAll("\\$r[0-9]+", "\\$rX"); + sinkJimpleStatement = sinkJimpleStatement.replaceAll("\\$r[0-9]+", "\\$rX"); + xmlSourceStmt = xmlSourceStmt.replaceAll("\\$r[0-9]+", "\\$rX"); + xmlSinkStmt = xmlSinkStmt.replaceAll("\\$r[0-9]+", "\\$rX"); + if (sourceJimpleStatement.equals(xmlSourceStmt) + && sinkJimpleStatement.equals(xmlSinkStmt)) { + res.put(groundTruthFlow, false); + } + } + } + } + } + } + } + return res; + } + protected URL classNameToURL(String className) { try { StringBuilder url = new StringBuilder(); diff --git a/src/test/java/de/upb/swt/tbviewer/InputValidationTest.java b/src/test/java/de/upb/swt/tbviewer/InputValidationTest.java new file mode 100644 index 0000000..36f5614 --- /dev/null +++ b/src/test/java/de/upb/swt/tbviewer/InputValidationTest.java @@ -0,0 +1,20 @@ +package de.upb.swt.tbviewer; + +import java.io.File; +import java.io.IOException; +import javax.xml.stream.XMLStreamException; +import org.junit.Test; + +public class InputValidationTest { + @Test + public void test() throws XMLStreamException, IOException { + String fileName = + "E:\\Git\\Github\\taintbench\\GitPod-GITHUB\\AppRepos\\backflash\\backflash_fd_result.xml"; + // String fileName= + // "C:\\Users\\linghui\\Downloads\\Raw-Results-of-FlowDroid-2.7.1-GenCG-Spring\\backflash_result.xml"; + File file = new File(fileName); + InputValidation.isFlowDroidFormat(file); + + FlowDroidResultParser.convertToAQL(FlowDroidResultParser.readResultsWithPath(fileName)); + } +} diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index f458d20..b2c64c5 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -1,26 +1,26 @@ 'use strict'; import * as net from 'net'; import * as path from 'path'; -import { workspace, ExtensionContext, window, TreeDataProvider,EventEmitter, Event, TreeItem, ProviderResult,Uri,commands, TreeItemCollapsibleState, Location,TextEditorRevealType, DecorationOptions, TreeView, Selection, Position, CodeAction } from 'vscode'; -import { LanguageClient, LanguageClientOptions, ServerOptions, StreamInfo, PublishDiagnosticsParams, Diagnostic, DiagnosticSeverity, Command, VersionedTextDocumentIdentifier, Range} from 'vscode-languageclient'; +import { workspace, ExtensionContext, window, TreeDataProvider, EventEmitter, Event, TreeItem, ProviderResult, Uri, commands, TreeItemCollapsibleState, Location, TextEditorRevealType, DecorationOptions, TreeView, Selection, Position, CodeAction } from 'vscode'; +import { LanguageClient, LanguageClientOptions, ServerOptions, StreamInfo, PublishDiagnosticsParams, Diagnostic, DiagnosticSeverity, Command, VersionedTextDocumentIdentifier, Range } from 'vscode-languageclient'; // this method is called when your extension is activated // your extension is activated the very first time the command is executed export async function activate(context: ExtensionContext) { - // Startup options for the language server - //const lspTransport = workspace.getConfiguration().get("taintbench.lspTransport", "socket") - const lspTransport = workspace.getConfiguration().get("taintbench.lspTransport", "stdio") + // Startup options for the language server + const lspTransport = workspace.getConfiguration().get("taintbench.lspTransport", "socket") + //const lspTransport = workspace.getConfiguration().get("taintbench.lspTransport", "stdio") + + let script = 'java'; + let args = ['-jar', context.asAbsolutePath(path.join('TB-Viewer-0.0.2-SNAPSHOT.jar'))]; - let script = 'java'; - let args = ['-jar',context.asAbsolutePath(path.join('TB-Viewer-0.0.2-SNAPSHOT.jar'))]; - const serverOptionsStdio = { - run : { command: script, args: args }, - debug: { command: script, args: args} //, options: { env: createDebugEnv() } + run: { command: script, args: args }, + debug: { command: script, args: args } //, options: { env: createDebugEnv() } } - const serverOptionsSocket = () => { + const serverOptionsSocket = () => { const socket = net.connect({ port: 5007 }) const result: StreamInfo = { writer: socket, @@ -34,19 +34,19 @@ export async function activate(context: ExtensionContext) { "-or- configure the extension to connect via standard IO.")) }) } - + const serverOptions: ServerOptions = - (lspTransport === "stdio") ? serverOptionsStdio : (lspTransport === "socket") ? serverOptionsSocket : null - //(lspTransport === "socket") ? serverOptionsSocket : (lspTransport === "stdio") ? serverOptionsStdio : null - + //(lspTransport === "stdio") ? serverOptionsStdio : (lspTransport === "socket") ? serverOptionsSocket : null + (lspTransport === "socket") ? serverOptionsSocket : (lspTransport === "stdio") ? serverOptionsStdio : null + let clientOptions: LanguageClientOptions = { - documentSelector: [{ scheme: 'file', language: 'java' }], - synchronize: { - configurationSection: 'java', - fileEvents: [ workspace.createFileSystemWatcher('**/*.java') ] - } - }; - + documentSelector: [{ scheme: 'file', language: 'java' }], + synchronize: { + configurationSection: 'java', + fileEvents: [workspace.createFileSystemWatcher('**/*.java')] + } + }; + // Register commands commands.registerCommand("taintbench.goto", async (args: TreeViewNode) => { try { @@ -54,16 +54,16 @@ export async function activate(context: ExtensionContext) { const fileUri = location.uri; const doc = await workspace.openTextDocument(fileUri) const editor = await window.showTextDocument(doc, { - preserveFocus: true, - preview: false - }) + preserveFocus: true, + preview: false + }) const deco = window.createTextEditorDecorationType({ textDecoration: 'underline' }); - const ops = []; + const ops = []; ops.push(location.range); - const start=location.range.start; - const end= location.range.end; + const start = location.range.start; + const end = location.range.end; window.activeTextEditor.selection = new Selection(new Position(start.line, start.character), new Position(end.line, end.character)); editor.setDecorations(deco, ops); editor.revealRange(location.range, TextEditorRevealType.InCenter); @@ -83,15 +83,15 @@ export async function activate(context: ExtensionContext) { window.registerTreeDataProvider('taintbench.matchedresults', matchedresultsProvider); window.registerTreeDataProvider('taintbench.unmatchedresults', unmatchedresultsProvider); - // Create the language client and start the client. - let client : LanguageClient = new LanguageClient('TaintBench','TaintBench', serverOptions, clientOptions); - client.start(); + // Create the language client and start the client. + let client: LanguageClient = new LanguageClient('TaintBench', 'TaintBench', serverOptions, clientOptions); + client.start(); await client.onReady(); - client.onNotification("taintbench/groundtruth",handleTreeDataNotification(groundtruthProvider,"taint.groundtruth", true)); - client.onNotification("taintbench/aqlresults",handleTreeDataNotification(aqlresultsProvider,"taint.aqlresults", false)); - client.onNotification("taintbench/matchedresults",handleTreeDataNotification(matchedresultsProvider,"taint.matchedresults", false)); - client.onNotification("taintbench/unmatchedresults",handleTreeDataNotification(unmatchedresultsProvider,"taint.unmatchedresults", false)); - + client.onNotification("taintbench/groundtruth", handleTreeDataNotification(groundtruthProvider, "taint.groundtruth", true, false)); + client.onNotification("taintbench/aqlresults", handleTreeDataNotification(aqlresultsProvider, "taint.aqlresults", false, true)); + client.onNotification("taintbench/matchedresults", handleTreeDataNotification(matchedresultsProvider, "taint.matchedresults", true, true)); + client.onNotification("taintbench/unmatchedresults", handleTreeDataNotification(unmatchedresultsProvider, "taint.unmatchedresults", false, true)); + } @@ -103,243 +103,257 @@ export class SimpleTreeDataProvider implements TreeDataProvider { emitter = new EventEmitter(); onDidChangeTreeData?: Event = this.emitter.event; rootItems: Array = new Array(); - positiveFlows: Array= new Array(); - negativeFlows: Array= new Array(); - flows: Array= new Array(); - sources: Array= new Array(); - sinks: Array= new Array(); + positiveFlows: Array = new Array(); + negativeFlows: Array = new Array(); + truePositives: Array = new Array(); + falsePositives: Array = new Array(); + triageRequired: Array = new Array(); + flows: Array = new Array(); + sources: Array = new Array(); + sinks: Array = new Array(); - sourceStrs: Array= new Array(); - sinkStrs: Array= new Array(); + sourceStrs: Array = new Array(); + sinkStrs: Array = new Array(); - update(uri: string, diagnostics: Diagnostic[],viewId: String, seperation: boolean) { + update(uri: string, diagnostics: Diagnostic[], viewId: String, seperation: boolean, isDetected: boolean) { var summaryPositive = null; var summaryNegative = null; - var summary=null; + var summary = null; var source = null; var sink = null; - if(this.rootItems.length === 0 ) - { - if(seperation) - { - summaryPositive=new TreeViewNode(""); + if (this.rootItems.length === 0) { + if (seperation) { + summaryPositive = new TreeViewNode(""); this.rootItems.push(summaryPositive); - summaryNegative=new TreeViewNode(""); + summaryNegative = new TreeViewNode(""); this.rootItems.push(summaryNegative); } - else - { - summary=new TreeViewNode(""); + else { + summary = new TreeViewNode(""); this.rootItems.push(summary); } - source=new TreeViewNode("Sources: "); + source = new TreeViewNode("Sources: "); this.rootItems.push(source); - sink =new TreeViewNode("Sinks: "); + sink = new TreeViewNode("Sinks: "); this.rootItems.push(sink); } - else - { - if(seperation) - { + else { + if (seperation) { summaryPositive = this.rootItems[0]; - summaryNegative =this.rootItems[1]; + summaryNegative = this.rootItems[1]; source = this.rootItems[2]; - sink =this.rootItems[3]; + sink = this.rootItems[3]; } - else - { + else { summary = this.rootItems[0]; source = this.rootItems[1]; - sink =this.rootItems[2]; + sink = this.rootItems[2]; } } - - for(var i =0; i < diagnostics.length; i++) - { - let d = diagnostics[i]; + + for (var i = 0; i < diagnostics.length; i++) { + let d = diagnostics[i]; let flowNode = this.convert(uri, d); - if(seperation) - { - if(d.severity === DiagnosticSeverity.Error) - this.positiveFlows.push(flowNode); - else if(d.severity === DiagnosticSeverity.Information) - this.negativeFlows.push(flowNode); - }else - { + if (seperation) { + if (d.severity === DiagnosticSeverity.Error) + this.positiveFlows.push(flowNode); + else if (d.severity === DiagnosticSeverity.Information) + this.negativeFlows.push(flowNode); + else if (d.severity === DiagnosticSeverity.Warning) { + if (d.message.includes("P].")) { + this.truePositives.push(flowNode); + } else if (d.message.includes("N].")) { + this.falsePositives.push(flowNode); + } + } + } else { this.flows.push(flowNode); } } - if(seperation) - { + + + if (seperation) { //sort the flows according to their IDs - this.positiveFlows.sort((n1,n2) => - { - var id1=parseInt(n1.label.split(".")[0]); - var id2=parseInt(n2.label.split(".")[0]); - return id1-id2; + this.positiveFlows.sort((n1, n2) => { + var id1 = parseInt(n1.label.split(".")[0]); + var id2 = parseInt(n2.label.split(".")[0]); + return id1 - id2; + } + ); + + this.negativeFlows.sort((n1, n2) => { + var id1 = parseInt(n1.label.split(".")[0]); + var id2 = parseInt(n2.label.split(".")[0]); + return id1 - id2; + } + ); + + this.truePositives.sort((n1, n2) => { + var id1 = parseInt(n1.label.split("[")[1].split("P]")[0]); + var id2 = parseInt(n2.label.split("[")[1].split("P]")[0]); + return id1 - id2; } ); - - this.negativeFlows.sort((n1,n2) => - { - var id1=parseInt(n1.label.split(".")[0]); - var id2=parseInt(n2.label.split(".")[0]); - return id1-id2; + + this.falsePositives.sort((n1, n2) => { + var id1 = parseInt(n1.label.split("[")[1].split("N]")[0]); + var id2 = parseInt(n2.label.split("[")[1].split("N]")[0]); + return id1 - id2; } ); } - else - { - this.flows.sort((n1,n2) => - { - var id1=parseInt(n1.label.split(".")[0]); - var id2=parseInt(n2.label.split(".")[0]); - return id1-id2; + else { + this.flows.sort((n1, n2) => { + var id1 = parseInt(n1.label.split(".")[0]); + var id2 = parseInt(n2.label.split(".")[0]); + return id1 - id2; } ); } - if(seperation) - { - summaryPositive.label="Positive Flows: "+this.positiveFlows.length; - summaryPositive.children=this.positiveFlows; - summaryPositive.iconPath = path.join(__filename, '..','..','media/summary.svg'); - summaryPositive.collapsibleState=TreeItemCollapsibleState.Expanded; - - summaryNegative.label="Negative Flows: "+this.negativeFlows.length; - summaryNegative.children=this.negativeFlows; - summaryNegative.iconPath = path.join(__filename, '..','..','media/summary.svg'); - summaryNegative.collapsibleState=TreeItemCollapsibleState.Expanded; + if (seperation) { + if (!isDetected) { + summaryPositive.label = "Positive Flows: " + this.positiveFlows.length; + summaryPositive.children = this.positiveFlows; + summaryPositive.iconPath = path.join(__filename, '..', '..', 'media/summary.svg'); + summaryPositive.collapsibleState = TreeItemCollapsibleState.Expanded; + + summaryNegative.label = "Negative Flows: " + this.negativeFlows.length; + summaryNegative.children = this.negativeFlows; + summaryNegative.iconPath = path.join(__filename, '..', '..', 'media/summary.svg'); + summaryNegative.collapsibleState = TreeItemCollapsibleState.Expanded; + } else { + summaryPositive.label = "True Positives: " + this.truePositives.length; + summaryPositive.children = this.truePositives; + summaryPositive.iconPath = path.join(__filename, '..', '..', 'media/summary.svg'); + summaryPositive.collapsibleState = TreeItemCollapsibleState.Expanded; + + summaryNegative.label = "False Positives: " + this.truePositives.length; + summaryNegative.children = this.falsePositives; + summaryNegative.iconPath = path.join(__filename, '..', '..', 'media/summary.svg'); + summaryNegative.collapsibleState = TreeItemCollapsibleState.Expanded; + } } - else - { - summary.label="Detected Flows: "+this.flows.length; - summary.children=this.flows; - summary.iconPath = path.join(__filename, '..','..','media/summary.svg'); - summary.collapsibleState=TreeItemCollapsibleState.Expanded; + else { + summary.label = "Detected Flows: " + this.flows.length; + summary.children = this.flows; + summary.iconPath = path.join(__filename, '..', '..', 'media/summary.svg'); + summary.collapsibleState = TreeItemCollapsibleState.Expanded; } - source.label="Sources: "+this.sources.length; - source.children=this.sources; - source.iconPath = path.join(__filename, '..','..','media/summary.svg'); - source.collapsibleState=TreeItemCollapsibleState.Collapsed; + source.label = "Sources: " + this.sources.length; + source.children = this.sources; + source.iconPath = path.join(__filename, '..', '..', 'media/summary.svg'); + source.collapsibleState = TreeItemCollapsibleState.Collapsed; - sink.label="Sinks: "+this.sinks.length; - sink.children=this.sinks; - sink.iconPath = path.join(__filename, '..','..','media/summary.svg'); - sink.collapsibleState=TreeItemCollapsibleState.Collapsed; + sink.label = "Sinks: " + this.sinks.length; + sink.children = this.sinks; + sink.iconPath = path.join(__filename, '..', '..', 'media/summary.svg'); + sink.collapsibleState = TreeItemCollapsibleState.Collapsed; this.emitter.fire() } - - convert(uri: string, d : Diagnostic) - { + + convert(uri: string, d: Diagnostic) { var label = d.message; - let node : TreeViewNode = new TreeViewNode(label); + let node: TreeViewNode = new TreeViewNode(label); node.collapsibleState = TreeItemCollapsibleState.Collapsed; node.tooltip = d.message; - node.iconPath = path.join(__filename, '..','..','media/warning.svg'); - node.resourceUri= Uri.parse(uri); - node.command = { + node.iconPath = path.join(__filename, '..', '..', 'media/warning.svg'); + node.resourceUri = Uri.parse(uri); + node.command = { command: 'taintbech.goto', title: 'goto', arguments: [ - { - location: { - uri: node.resourceUri, - range: d.range + { + location: { + uri: node.resourceUri, + range: d.range + } } - } ] }; - node.children=new Array(); + node.children = new Array(); //extract sink from aql result - if(d.severity == DiagnosticSeverity.Warning) - { - var sinkInfo ="SINK: "+d.message.split("Detected taint flow to ")[1]; - let aqlSink : TreeViewNode = new TreeViewNode(sinkInfo); - aqlSink.collapsibleState=TreeItemCollapsibleState.None; - aqlSink.tooltip=sinkInfo; - aqlSink.resourceUri=node.resourceUri; - aqlSink.iconPath = path.join(__filename, '..','..','media/sink.svg'); - aqlSink.command = { + if (d.severity == DiagnosticSeverity.Warning) { + var sinkInfo = "SINK: " + d.message.split("Detected taint flow to ")[1]; + let aqlSink: TreeViewNode = new TreeViewNode(sinkInfo); + aqlSink.collapsibleState = TreeItemCollapsibleState.None; + aqlSink.tooltip = sinkInfo; + aqlSink.resourceUri = node.resourceUri; + aqlSink.iconPath = path.join(__filename, '..', '..', 'media/sink.svg'); + aqlSink.command = { command: 'taintbech.goto', title: 'goto', arguments: [ - { - location: { - uri: aqlSink.resourceUri, - range: d.range + { + location: { + uri: aqlSink.resourceUri, + range: d.range + } } - } ] } - d.range.start.line+ - d.range.start.character+ - d.range.end.line+ - d.range.start.character; + d.range.start.line + + d.range.start.character + + d.range.end.line + + d.range.start.character; } // create nodes from related information. - for(var i =0; i { if (typeof element.resourceUri === 'string') element.resourceUri = Uri.parse(element.resourceUri) @@ -368,9 +382,9 @@ export class SimpleTreeDataProvider implements TreeDataProvider { } } -export function handleTreeDataNotification(dataProvider: SimpleTreeDataProvider, viewId: string, seperation: boolean) { +export function handleTreeDataNotification(dataProvider: SimpleTreeDataProvider, viewId: string, seperation: boolean, isDetected: boolean) { return (args: PublishDiagnosticsParams) => { if (dataProvider) - dataProvider.update(args.uri, args.diagnostics, viewId, seperation); + dataProvider.update(args.uri, args.diagnostics, viewId, seperation, isDetected); } } From bd1c1f86883cd64f0898883f2128add48bcb5d50 Mon Sep 17 00:00:00 2001 From: AnonymousAnalyst <> Date: Tue, 9 Mar 2021 18:06:27 +0100 Subject: [PATCH 3/7] bug fix --- .../swt/tbviewer/FlowDroidResultParser.java | 43 ++++++++++-- .../java/de/upb/swt/tbviewer/Location.java | 12 +++- .../upb/swt/tbviewer/TaintServerAnalysis.java | 68 ++++++++++++++----- 3 files changed, 98 insertions(+), 25 deletions(-) diff --git a/src/main/java/de/upb/swt/tbviewer/FlowDroidResultParser.java b/src/main/java/de/upb/swt/tbviewer/FlowDroidResultParser.java index 2837945..b057e66 100644 --- a/src/main/java/de/upb/swt/tbviewer/FlowDroidResultParser.java +++ b/src/main/java/de/upb/swt/tbviewer/FlowDroidResultParser.java @@ -49,6 +49,7 @@ class Attributes { public static final String terminationState = "TerminationState"; public static final String statement = "Statement"; public static final String method = "Method"; + public static final String linenumber = "LineNumber"; public static final String value = "Value"; public static final String type = "Type"; @@ -57,6 +58,7 @@ class Attributes { public static final String category = "Category"; public static final String name = "Name"; + public static final String id = "ID"; } public static boolean hasFormat(String fileName) @@ -86,10 +88,14 @@ public static Set readResultsWithPath(String fileName) ArrayList pathElements = new ArrayList<>(); String sinkStatement = null; String sinkMethod = null; + int sinkLineNo = -1; String sourceStatement = null; String sourceMethod = null; + int sourceLineNo = -1; String pathStatement = null; String pathMethod = null; + int pathLineNo = -1; + String ID = ""; while (reader.hasNext()) { reader.next(); if (reader.hasName()) { @@ -97,22 +103,41 @@ public static Set readResultsWithPath(String fileName) hasFormat = true; } else if (reader.getLocalName().equals(Tags.sink) && reader.isStartElement()) { sinkStatement = getAttributeByName(reader, Attributes.statement); + String re = getAttributeByName(reader, Attributes.linenumber); + if (!re.equals("")) { + sinkLineNo = Integer.parseInt(re); + } else { + sinkLineNo = -1; + } sinkMethod = getAttributeByName(reader, Attributes.method); } else if (reader.getLocalName().equals(Tags.source) && reader.isStartElement()) { sourceStatement = getAttributeByName(reader, Attributes.statement); + ID = getAttributeByName(reader, Attributes.id); + String re = getAttributeByName(reader, Attributes.linenumber); + if (!re.equals("")) { + sourceLineNo = Integer.parseInt(getAttributeByName(reader, Attributes.linenumber)); + } else { + sourceLineNo = -1; + } sourceMethod = getAttributeByName(reader, Attributes.method); } else if (reader.getLocalName().equals(Tags.pathElement) && reader.isStartElement()) { pathStatement = getAttributeByName(reader, Attributes.statement); + String re = getAttributeByName(reader, Attributes.linenumber); + if (!re.equals("")) { + pathLineNo = Integer.parseInt(getAttributeByName(reader, Attributes.linenumber)); + } else { + pathLineNo = -1; + } pathMethod = getAttributeByName(reader, Attributes.method); } else if (reader.isEndElement()) { if (reader.getLocalName().equals(Tags.sink)) { - sink = new Location(sinkMethod, sinkStatement, -1); + sink = new Location(sinkMethod, sinkStatement, sinkLineNo, null); } else if (reader.getLocalName().equals(Tags.source)) { - source = new Location(sourceMethod, sourceStatement, -1); + source = new Location(sourceMethod, sourceStatement, sourceLineNo, ID); results.add(new TaintFlow(source, sink, pathElements)); pathElements = new ArrayList<>(); } else if (reader.getLocalName().equals(Tags.pathElement)) { - Location pathElement = new Location(pathMethod, pathStatement, -1); + Location pathElement = new Location(pathMethod, pathStatement, pathLineNo, null); pathElements.add(pathElement); } } @@ -135,7 +160,12 @@ public static Map>> convertT all.addAll(path); } all.add(flow.getSink()); - String id = i + ""; + String id = null; + if (!flow.getSource().getID().isEmpty()) { + id = flow.getSource().getID(); + } else { + id = i + ""; + } if (!res.containsKey(id)) res.put(id, new TreeMap<>()); for (int j = 0; j < all.size() - 1; j++) { Location f = all.get(j); @@ -144,7 +174,7 @@ public static Map>> convertT from.setType("from"); from.setClassname(f.getClassSignature()); from.setMethod(f.getMethodSignature()); - sf.setLinenumber(-1); + sf.setLinenumber(f.getLinenumber()); sf.setStatementfull(f.getStatement()); String s = f.getStatement(); if (s.contains("<") && s.contains(">")) { @@ -155,12 +185,13 @@ public static Map>> convertT from.setStatement(sf); Location t = all.get(j + 1); + Statement st = new Statement(); Reference to = new Reference(); to.setType("to"); to.setClassname(t.getClassSignature()); to.setMethod(t.getMethodSignature()); - st.setLinenumber(-1); + st.setLinenumber(t.getLinenumber()); st.setStatementfull(t.getStatement()); s = t.getStatement(); if (s.contains("<") && s.contains(">")) { diff --git a/src/main/java/de/upb/swt/tbviewer/Location.java b/src/main/java/de/upb/swt/tbviewer/Location.java index be46ac8..15591ed 100644 --- a/src/main/java/de/upb/swt/tbviewer/Location.java +++ b/src/main/java/de/upb/swt/tbviewer/Location.java @@ -32,6 +32,7 @@ public int getLinenumber() { private String methodSignature; private String statement; private int linenumber; + private String id; private static Pattern javaMethodPattern = createJavaMethodPattern(); private static Pattern jimpleMethodPattern = createJimpleMethodPattern(); @@ -149,13 +150,14 @@ public Location( this.linenumber = linenumber; } - public Location(String method, String statement, int linenumber) { + public Location(String method, String statement, int linenumber, String ID) { this.kind = LocationKind.Jimple; this.statement = statement; String[] splits = method.split(":"); this.classSignature = splits[0].replace("<", "").trim(); this.methodSignature = method.replace("<", "").replace(">", ""); this.linenumber = linenumber; + this.id = ID; } public static boolean maybeEqual(Location a, Location b, boolean compareStatements) { @@ -175,9 +177,11 @@ public static boolean maybeEqual(Location a, Location b, boolean compareStatemen if (aClass.equals(bClass)) { if (compareMethod(a.methodSignature, b.methodSignature)) - if (a.linenumber == b.linenumber && a.linenumber != -1) { + // sometimes soot returns wrong line numbers, so we set a threshold to 3 + if (Math.abs(a.linenumber - b.linenumber) <= 3 && a.linenumber != -1) { return true; } else { + if (a.linenumber != -1 && b.linenumber != -1) return false; if (!compareStatements) return true; else return compareStatements(a.statement, b.statement); } @@ -189,6 +193,10 @@ public static boolean maybeEqual(Location a, Location b, boolean compareStatemen return false; } + public String getID() { + return this.id; + } + @Override public String toString() { return "Location [kind=" diff --git a/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java b/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java index b6f1e07..7ee5e9b 100644 --- a/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java +++ b/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java @@ -427,14 +427,14 @@ protected void readAqlResults() { prefix = "(AMBIGUOUS) "; } sinkMsg = prefix + sinkMsg; - for (Pair sinkInfo : possibleSinkInfos) { if (sinkInfo != null) { related.add(Pair.make(sinkInfo.fst, "SINK: " + sinkInfo.snd)); String matchedFlowID = null; // for (Pair possibleSource : possibleSourceInfos) { // search if there is match in ground truth - // Optional res = findFlowMatchUsingSourceCode(possibleSource, sinkInfo); + // Optional res = findFlowMatchUsingSourceCode(possibleSource, + // sinkInfo); // if (res.isPresent()) { // matchedFlowID = res.get().toString(false).split("\\.")[0]; @@ -458,7 +458,7 @@ protected void readAqlResults() { } if (matchedFlowID != null) { String detectedFlowID = sinkMsg.split("\\.")[0]; - sinkMsg = sinkMsg.replace(detectedFlowID, detectedFlowID + "[" + matchedFlowID + "]"); + sinkMsg = sinkMsg.replace(detectedFlowID, detectedFlowID + " [" + matchedFlowID + "]"); } TaintAnalysisResult aqlresult = new TaintAnalysisResult( @@ -521,6 +521,7 @@ protected Map findFlowMatchUsingJimple( JsonObject jsonsource = finding.getAsJsonObject("source"); String sourceClassName = jsonsource.get("className").getAsString(); String sourceMethodName = jsonsource.get("methodName").getAsString(); + int sourceLineNo = jsonsource.get("lineNo").getAsInt(); String sourceJimpleStatement = null; if (jsonsource.has("IRs") && jsonsource.get("IRs").getAsJsonArray().size() > 0) { JsonObject IR = jsonsource.get("IRs").getAsJsonArray().get(0).getAsJsonObject(); @@ -531,6 +532,7 @@ protected Map findFlowMatchUsingJimple( JsonObject jsonsink = finding.getAsJsonObject("sink"); String sinkClassName = jsonsink.get("className").getAsString(); String sinkMethodName = jsonsink.get("methodName").getAsString(); + int sinkLineNo = jsonsink.get("lineNo").getAsInt(); String sinkJimpleStatement = null; if (jsonsource.has("IRs") && jsonsink.get("IRs").getAsJsonArray().size() > 0) { JsonObject IR = jsonsink.get("IRs").getAsJsonArray().get(0).getAsJsonObject(); @@ -540,30 +542,50 @@ protected Map findFlowMatchUsingJimple( } boolean isNegativeFlow = finding.get("isNegative").getAsBoolean(); if (sourceJimpleStatement != null && sinkJimpleStatement != null) { + if (source.getStatement().getLinenumber() == -1 + && sink.getStatement().getLinenumber() == -1) { + // the xml result doesn't contain line numbers. + sourceLineNo = -1; + sinkLineNo = -1; + } Location jsonSource = - new Location(LocationKind.Java, sourceClassName, sourceMethodName, null, -1); + new Location( + LocationKind.Java, sourceClassName, sourceMethodName, null, sourceLineNo); Location jsonSink = - new Location(LocationKind.Java, sinkClassName, sinkMethodName, null, -1); + new Location(LocationKind.Java, sinkClassName, sinkMethodName, null, sinkLineNo); Location xmlSource = new Location( - LocationKind.Jimple, source.getClassname(), source.getMethod(), null, -1); + LocationKind.Jimple, + source.getClassname(), + source.getMethod(), + null, + source.getStatement().getLinenumber()); Location xmlSink = - new Location(LocationKind.Jimple, sink.getClassname(), sink.getMethod(), null, -1); + new Location( + LocationKind.Jimple, + sink.getClassname(), + sink.getMethod(), + null, + sink.getStatement().getLinenumber()); String xmlSourceStmt = source.getStatement().getStatementfull(); String xmlSinkStmt = sink.getStatement().getStatementfull(); if (Location.maybeEqual(jsonSource, xmlSource, false) && Location.maybeEqual(jsonSink, xmlSink, false)) { - if (sourceJimpleStatement.equals(xmlSourceStmt) - && sinkJimpleStatement.equals(xmlSinkStmt)) { - res.put(groundTruthFlow, true); + if (source.getStatement().getLinenumber() != -1 + && sink.getStatement().getLinenumber() != -1) { + // also compared line numbers in maybeEqual, + if (compareGenericStmts( + sourceJimpleStatement, sinkJimpleStatement, xmlSourceStmt, xmlSinkStmt)) { + res.put(groundTruthFlow, true); + } } else { - if (!isNegativeFlow) { - sourceJimpleStatement = sourceJimpleStatement.replaceAll("\\$r[0-9]+", "\\$rX"); - sinkJimpleStatement = sinkJimpleStatement.replaceAll("\\$r[0-9]+", "\\$rX"); - xmlSourceStmt = xmlSourceStmt.replaceAll("\\$r[0-9]+", "\\$rX"); - xmlSinkStmt = xmlSinkStmt.replaceAll("\\$r[0-9]+", "\\$rX"); - if (sourceJimpleStatement.equals(xmlSourceStmt) - && sinkJimpleStatement.equals(xmlSinkStmt)) { + // no line numbers are available + if (sourceJimpleStatement.equals(xmlSourceStmt) + && sinkJimpleStatement.equals(xmlSinkStmt)) { + res.put(groundTruthFlow, true); + } else { + if (compareGenericStmts( + sourceJimpleStatement, sinkJimpleStatement, xmlSourceStmt, xmlSinkStmt)) { res.put(groundTruthFlow, false); } } @@ -575,6 +597,18 @@ protected Map findFlowMatchUsingJimple( return res; } + private boolean compareGenericStmts( + String sourceJimpleStatement, + String sinkJimpleStatement, + String xmlSourceStmt, + String xmlSinkStmt) { + sourceJimpleStatement = sourceJimpleStatement.replaceAll("\\$r[0-9]+", "\\$rX"); + sinkJimpleStatement = sinkJimpleStatement.replaceAll("\\$r[0-9]+", "\\$rX"); + xmlSourceStmt = xmlSourceStmt.replaceAll("\\$r[0-9]+", "\\$rX"); + xmlSinkStmt = xmlSinkStmt.replaceAll("\\$r[0-9]+", "\\$rX"); + return sourceJimpleStatement.equals(xmlSourceStmt) && sinkJimpleStatement.equals(xmlSinkStmt); + } + protected URL classNameToURL(String className) { try { StringBuilder url = new StringBuilder(); From 68a100d9def65d755a2154d1f6ad83098f308dd7 Mon Sep 17 00:00:00 2001 From: AnonymousAnalyst <> Date: Mon, 22 Mar 2021 11:05:09 +0100 Subject: [PATCH 4/7] bug fix for library code in taint flow --- .../swt/tbviewer/FlowDroidResultParser.java | 16 ++++++++++++ .../java/de/upb/swt/tbviewer/Location.java | 17 +++++++----- src/main/java/de/upb/swt/tbviewer/Main.java | 7 +++-- .../java/de/upb/swt/tbviewer/TaintFlow.java | 7 ++--- .../upb/swt/tbviewer/TaintServerAnalysis.java | 26 ++++++++++++------- vscode/package.json | 17 ++++++------ vscode/src/extension.ts | 10 +++---- 7 files changed, 64 insertions(+), 36 deletions(-) diff --git a/src/main/java/de/upb/swt/tbviewer/FlowDroidResultParser.java b/src/main/java/de/upb/swt/tbviewer/FlowDroidResultParser.java index b057e66..0237ab9 100644 --- a/src/main/java/de/upb/swt/tbviewer/FlowDroidResultParser.java +++ b/src/main/java/de/upb/swt/tbviewer/FlowDroidResultParser.java @@ -177,10 +177,18 @@ public static Map>> convertT sf.setLinenumber(f.getLinenumber()); sf.setStatementfull(f.getStatement()); String s = f.getStatement(); + if (s.contains("")) { + s = s.replace("", "[init]"); + } + if (s.contains("")) { + s = s.replace("", "[clinit]"); + } if (s.contains("<") && s.contains(">")) { s = s.substring(s.indexOf("<") + 1); s = s.substring(0, s.indexOf(">")); } + s = s.replace("[init]", ""); + s = s.replace("[clinit]", ""); sf.setStatementgeneric(s); from.setStatement(sf); @@ -194,10 +202,18 @@ public static Map>> convertT st.setLinenumber(t.getLinenumber()); st.setStatementfull(t.getStatement()); s = t.getStatement(); + if (s.contains("")) { + s = s.replace("", "[init]"); + } + if (s.contains("")) { + s = s.replace("", "[clinit]"); + } if (s.contains("<") && s.contains(">")) { s = s.substring(s.indexOf("<") + 1); s = s.substring(0, s.indexOf(">")); } + s = s.replace("[init]", ""); + s = s.replace("[clinit]", ""); st.setStatementgeneric(s); to.setStatement(st); Pair step = Pair.make(from, to); diff --git a/src/main/java/de/upb/swt/tbviewer/Location.java b/src/main/java/de/upb/swt/tbviewer/Location.java index 15591ed..7dea128 100644 --- a/src/main/java/de/upb/swt/tbviewer/Location.java +++ b/src/main/java/de/upb/swt/tbviewer/Location.java @@ -133,7 +133,7 @@ public static boolean compareMethod(String javaMethod, String jimpleMethod) { } public static boolean compareStatements(String javaStatement, String jimpleStatement) { - return false; + return true; } public Location( @@ -160,7 +160,8 @@ public Location(String method, String statement, int linenumber, String ID) { this.id = ID; } - public static boolean maybeEqual(Location a, Location b, boolean compareStatements) { + public static boolean maybeEqual( + Location a, Location b, boolean compareStatements, int threshold) { if (a.kind == b.kind) { return a.classSignature.equals(b.classSignature) && a.methodSignature.equals(b.methodSignature) @@ -177,9 +178,13 @@ public static boolean maybeEqual(Location a, Location b, boolean compareStatemen if (aClass.equals(bClass)) { if (compareMethod(a.methodSignature, b.methodSignature)) - // sometimes soot returns wrong line numbers, so we set a threshold to 3 - if (Math.abs(a.linenumber - b.linenumber) <= 3 && a.linenumber != -1) { - return true; + // sometimes soot returns wrong line numbers, so we set a threshold + if (Math.abs(a.linenumber - b.linenumber) < threshold && a.linenumber != -1) { + if (!compareStatements) { + return true; + } else { + return compareStatements(a.statement, b.statement); + } } else { if (a.linenumber != -1 && b.linenumber != -1) return false; if (!compareStatements) return true; @@ -187,7 +192,7 @@ public static boolean maybeEqual(Location a, Location b, boolean compareStatemen } } } else if (a.kind.equals(LocationKind.Jimple) && b.kind.equals(LocationKind.Java)) { - return maybeEqual(b, a, compareStatements); + return maybeEqual(b, a, compareStatements, threshold); } } return false; diff --git a/src/main/java/de/upb/swt/tbviewer/Main.java b/src/main/java/de/upb/swt/tbviewer/Main.java index f87f1ee..f305492 100644 --- a/src/main/java/de/upb/swt/tbviewer/Main.java +++ b/src/main/java/de/upb/swt/tbviewer/Main.java @@ -30,7 +30,10 @@ public static void main(String... args) { server.addAnalysis(either, language); return server; }; - // supplier.get().launchOnStdio(); - TaintLanguageServer.launchOnSocketPort(5007, supplier); + if (args.length > 0) { + if (args[0].equals("-debug")) TaintLanguageServer.launchOnSocketPort(5007, supplier); + } else { + supplier.get().launchOnStdio(); + } } } diff --git a/src/main/java/de/upb/swt/tbviewer/TaintFlow.java b/src/main/java/de/upb/swt/tbviewer/TaintFlow.java index efc02a2..a630198 100644 --- a/src/main/java/de/upb/swt/tbviewer/TaintFlow.java +++ b/src/main/java/de/upb/swt/tbviewer/TaintFlow.java @@ -37,14 +37,15 @@ public TaintFlow(Location source, Location sink) { } public boolean mayEqual(TaintFlow other, boolean compareIntermediate) { - if (Location.maybeEqual(this.source, other.source, false) - && Location.maybeEqual(this.sink, other.sink, false)) + if (Location.maybeEqual(this.source, other.source, false, 1) + && Location.maybeEqual(this.sink, other.sink, false, 1)) if (!compareIntermediate) return true; else { if (this.intermediate.size() == other.intermediate.size()) { boolean allEqual = true; for (int i = 0; i < this.intermediate.size(); i++) { - if (!Location.maybeEqual(this.intermediate.get(i), other.intermediate.get(i), false)) { + if (!Location.maybeEqual( + this.intermediate.get(i), other.intermediate.get(i), false, 1)) { allEqual = false; } } diff --git a/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java b/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java index 7ee5e9b..6230ba1 100644 --- a/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java +++ b/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java @@ -364,9 +364,12 @@ protected Set> getPossibleSourceCodePosition(Re Optional> op = findLocationMatch(location); Set> possibleCodePositions = new HashSet<>(); if (op.isPresent()) possibleCodePositions.add(op.get()); - else - possibleCodePositions.addAll( - SourceCodePositionFinder.getWithRegex(sourceUrl, hostMethod, stmt, constantParams)); + else { + if (sourceUrl != null) { + possibleCodePositions.addAll( + SourceCodePositionFinder.getWithRegex(sourceUrl, hostMethod, stmt, constantParams)); + } + } return possibleCodePositions; } @@ -482,7 +485,7 @@ protected void readAqlResults() { /** Find ground truth location which matches the aqlLocation. */ protected Optional> findLocationMatch(Location aqlLocation) { for (Location loc : this.groundTruthLocations.keySet()) { - if (Location.maybeEqual(loc, aqlLocation, true)) { + if (Location.maybeEqual(loc, aqlLocation, true, 1)) { Pair pos = this.groundTruthLocations.get(loc); if (pos.fst != null) return Optional.of(pos); else Optional.empty(); @@ -569,8 +572,8 @@ protected Map findFlowMatchUsingJimple( sink.getStatement().getLinenumber()); String xmlSourceStmt = source.getStatement().getStatementfull(); String xmlSinkStmt = sink.getStatement().getStatementfull(); - if (Location.maybeEqual(jsonSource, xmlSource, false) - && Location.maybeEqual(jsonSink, xmlSink, false)) { + if (Location.maybeEqual(jsonSource, xmlSource, false, 3) + && Location.maybeEqual(jsonSink, xmlSink, false, 3)) { if (source.getStatement().getLinenumber() != -1 && sink.getStatement().getLinenumber() != -1) { // also compared line numbers in maybeEqual, @@ -620,8 +623,11 @@ protected URL classNameToURL(String className) { url.append(File.separator); url.append("java"); if (!new File(url.toString()).exists()) return searchFile(rootPath, className); - url.append(File.separator); - url.append(className.replace(".", File.separator)); + String[] strs = className.split("\\."); + for (int i = 0; i < strs.length; i++) { + url.append(File.separator); + url.append(strs[i]); + } url.append(".java"); File file = new File(url.toString()); if (file.exists()) { @@ -631,6 +637,7 @@ protected URL classNameToURL(String className) { return new URL("file://" + url.toString().split("\\$")[0] + ".java"); } else { String enclosingClassName = removeAnonymousInnerClass(className); + if (enclosingClassName == null) return null; return classNameToURL(enclosingClassName); } } @@ -672,7 +679,8 @@ protected String removeAnonymousInnerClass(String className) { } } String fileName = str.toString(); - fileName = fileName.substring(0, fileName.length() - 1); + if (fileName.length() > 1) fileName = fileName.substring(0, fileName.length() - 1); + else return null; return fileName; } diff --git a/vscode/package.json b/vscode/package.json index f68bcb5..442a1e5 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -3,7 +3,7 @@ "description": "A language server for TaintBench", "author": "TaintBench", "license": "EPL-2.0", - "version": "0.0.2", + "version": "0.0.3", "repository": { "type": "git", "url": "" @@ -83,24 +83,23 @@ ], "configuration": { "type": "object", - "title": "TaintBench", + "title": "taintbench", "properties": { - "TaintBench.trace.server": { + "taintbench.lspTransport": { "scope": "window", "type": "string", "enum": [ - "off", - "messages", - "verbose" + "stdio", + "socket" ], - "default": "off", - "description": "Traces the communication between VS Code and the language server." + "default": "socket", + "description": "Specifies the mode of transport used to communicate with the InferIDE language server." } } } }, "scripts": { - "vscode:prepublish": "cp ../target/TB-Viewer-0.0.2-SNAPSHOT.jar TB-Viewer-0.0.2-SNAPSHOT.jar && npm run compile", + "vscode:prepublish": "cp ../target/TB-Viewer-0.0.3-SNAPSHOT.jar TB-Viewer-0.0.3-SNAPSHOT.jar && npm run compile", "compile": "tsc -b", "watch": "tsc -b -w", "postinstall": "node ./node_modules/vscode/bin/install" diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index b2c64c5..661d022 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -8,12 +8,10 @@ import { LanguageClient, LanguageClientOptions, ServerOptions, StreamInfo, Publi // this method is called when your extension is activated // your extension is activated the very first time the command is executed export async function activate(context: ExtensionContext) { - // Startup options for the language server - const lspTransport = workspace.getConfiguration().get("taintbench.lspTransport", "socket") - //const lspTransport = workspace.getConfiguration().get("taintbench.lspTransport", "stdio") + const lspTransport = workspace.getConfiguration("taintbench").get("lspTransport"); let script = 'java'; - let args = ['-jar', context.asAbsolutePath(path.join('TB-Viewer-0.0.2-SNAPSHOT.jar'))]; + let args = ['-jar', context.asAbsolutePath(path.join('TB-Viewer-0.0.3-SNAPSHOT.jar'))]; const serverOptionsStdio = { run: { command: script, args: args }, @@ -36,9 +34,7 @@ export async function activate(context: ExtensionContext) { } const serverOptions: ServerOptions = - //(lspTransport === "stdio") ? serverOptionsStdio : (lspTransport === "socket") ? serverOptionsSocket : null - (lspTransport === "socket") ? serverOptionsSocket : (lspTransport === "stdio") ? serverOptionsStdio : null - + (lspTransport === "stdio") ? serverOptionsStdio : (lspTransport === "socket") ? serverOptionsSocket : null let clientOptions: LanguageClientOptions = { documentSelector: [{ scheme: 'file', language: 'java' }], synchronize: { From fae3bb657b0c6ef62a4e24b0144bc95a52d4835a Mon Sep 17 00:00:00 2001 From: AnonymousAnalyst <> Date: Mon, 12 Apr 2021 13:10:27 +0200 Subject: [PATCH 5/7] bug fix for generic return type --- src/main/java/de/upb/swt/tbviewer/Location.java | 9 +++++---- .../java/de/upb/swt/tbviewer/TaintServerAnalysis.java | 4 ++++ .../java/de/upb/swt/tbviewer/InputValidationTest.java | 3 +-- src/test/java/de/upb/swt/tbviewer/LocationTest.java | 10 ++++++++++ vscode/package.json | 2 +- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/main/java/de/upb/swt/tbviewer/Location.java b/src/main/java/de/upb/swt/tbviewer/Location.java index 7dea128..d9c5914 100644 --- a/src/main/java/de/upb/swt/tbviewer/Location.java +++ b/src/main/java/de/upb/swt/tbviewer/Location.java @@ -40,7 +40,7 @@ public int getLinenumber() { public static Pattern createJavaMethodPattern() { // non capture modifier String group0 = - "(?:public|private|protected|static|final|native|synchronized|abstract|transient)"; + "(?:public|private|protected|static|final|native|synchronized|abstract|default)"; String group1 = "(.+)"; // return type String group2 = "(.+)"; // method name String group3 = "(.*?)"; // parameters @@ -90,19 +90,20 @@ public static boolean compareMethod(String javaMethod, String jimpleMethod) { String javaMethodName = javaMatcher.group(2); String javaParameterString = javaMatcher.group(3); // replace all types between < > for generic types - javaParameterString = javaParameterString.replaceAll("\\<.+\\>", ""); + javaParameterString = javaParameterString.replaceAll("\\<[^\\>]+\\>", ""); String[] javaParameters = javaParameterString.split(","); if (jimpleMatcher.find()) { String jimpleReturenType = jimpleMatcher.group(1); String jimpleMethodName = jimpleMatcher.group(2); String[] jimpleParameterTypes = jimpleMatcher.group(3).split(","); - if (jimpleReturenType.endsWith(javaReturnType)) { + if (jimpleReturenType.endsWith(javaReturnType) + || jimpleReturenType.equals("java.lang.Object")) { if (jimpleMethodName.equals(javaMethodName)) { if (javaParameters.length == jimpleParameterTypes.length) { boolean paraMatch = true; for (int i = 0; i < javaParameters.length; i++) { - String javaPara = javaParameters[i].split("\\s")[0]; + String javaPara = javaParameters[i].trim().split("\\s")[0]; String jimplePara = jimpleParameterTypes[i]; if (javaPara.endsWith("...")) // take care of varargs { diff --git a/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java b/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java index 6230ba1..72fe56e 100644 --- a/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java +++ b/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java @@ -606,9 +606,13 @@ private boolean compareGenericStmts( String xmlSourceStmt, String xmlSinkStmt) { sourceJimpleStatement = sourceJimpleStatement.replaceAll("\\$r[0-9]+", "\\$rX"); + sourceJimpleStatement = sourceJimpleStatement.replaceAll("r[0-9]+", "\\$rX"); sinkJimpleStatement = sinkJimpleStatement.replaceAll("\\$r[0-9]+", "\\$rX"); + sinkJimpleStatement = sinkJimpleStatement.replaceAll("r[0-9]+", "\\$rX"); xmlSourceStmt = xmlSourceStmt.replaceAll("\\$r[0-9]+", "\\$rX"); + xmlSourceStmt = xmlSourceStmt.replaceAll("r[0-9]+", "\\$rX"); xmlSinkStmt = xmlSinkStmt.replaceAll("\\$r[0-9]+", "\\$rX"); + xmlSinkStmt = xmlSinkStmt.replaceAll("r[0-9]+", "\\$rX"); return sourceJimpleStatement.equals(xmlSourceStmt) && sinkJimpleStatement.equals(xmlSinkStmt); } diff --git a/src/test/java/de/upb/swt/tbviewer/InputValidationTest.java b/src/test/java/de/upb/swt/tbviewer/InputValidationTest.java index 36f5614..0696b17 100644 --- a/src/test/java/de/upb/swt/tbviewer/InputValidationTest.java +++ b/src/test/java/de/upb/swt/tbviewer/InputValidationTest.java @@ -3,10 +3,9 @@ import java.io.File; import java.io.IOException; import javax.xml.stream.XMLStreamException; -import org.junit.Test; public class InputValidationTest { - @Test + public void test() throws XMLStreamException, IOException { String fileName = "E:\\Git\\Github\\taintbench\\GitPod-GITHUB\\AppRepos\\backflash\\backflash_fd_result.xml"; diff --git a/src/test/java/de/upb/swt/tbviewer/LocationTest.java b/src/test/java/de/upb/swt/tbviewer/LocationTest.java index 25b6695..b758471 100644 --- a/src/test/java/de/upb/swt/tbviewer/LocationTest.java +++ b/src/test/java/de/upb/swt/tbviewer/LocationTest.java @@ -1,5 +1,6 @@ package de.upb.swt.tbviewer; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.regex.Matcher; @@ -8,6 +9,15 @@ public class LocationTest { + @Test + public void test() { + String javaMethod = + "public T post(HttpPost request, String url, Map data, Class clazz)"; + assertEquals( + javaMethod.replaceAll("\\<[^\\>]+\\>", ""), + "public T post(HttpPost request, String url, Map data, Class clazz)"); + } + @Test public void testPatterns() { String javaMethod = "public void onCreate(Bundle savedInstanceState)"; diff --git a/vscode/package.json b/vscode/package.json index 442a1e5..83919b3 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -92,7 +92,7 @@ "stdio", "socket" ], - "default": "socket", + "default": "stdio", "description": "Specifies the mode of transport used to communicate with the InferIDE language server." } } From 96b0256d1bede8989a39f7f71ecbfec55c562c88 Mon Sep 17 00:00:00 2001 From: AnonymousAnalyst <> Date: Fri, 16 Jul 2021 12:14:32 +0200 Subject: [PATCH 6/7] add a screenshot --- vscode/media/screenshot.PNG | Bin 0 -> 122597 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 vscode/media/screenshot.PNG diff --git a/vscode/media/screenshot.PNG b/vscode/media/screenshot.PNG new file mode 100644 index 0000000000000000000000000000000000000000..d4a4c6e442ff5e5dda51ad604a0e285a4f680a34 GIT binary patch literal 122597 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%0A?~I!3W633U9HtSmqpmu{?7*E1lj-WX#c-97wd_O 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 literal 0 HcmV?d00001 From c4163699561cf84cf93fa735a7521ae3ded32979 Mon Sep 17 00:00:00 2001 From: AnonymousAnalyst <> Date: Fri, 16 Jul 2021 12:24:28 +0200 Subject: [PATCH 7/7] update v0.0.3 for deploy --- LICENSE.txt | 12 ++++++++++++ .../java/de/upb/swt/tbviewer/InputValidation.java | 3 +-- .../de/upb/swt/tbviewer/TaintServerAnalysis.java | 7 +++++-- vscode/README.md | 15 +++++++++++++++ vscode/package.json | 14 ++++++++------ vscode/src/extension.ts | 4 ++-- 6 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 LICENSE.txt create mode 100644 vscode/README.md diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..31b87ba --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,12 @@ +=============================================================================== +InferIDE Release License (MIT license) +=============================================================================== +MIT License + +Copyright (c) 2020 - 2023 Linghui Luo + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/main/java/de/upb/swt/tbviewer/InputValidation.java b/src/main/java/de/upb/swt/tbviewer/InputValidation.java index 7bc96a1..aa80c79 100644 --- a/src/main/java/de/upb/swt/tbviewer/InputValidation.java +++ b/src/main/java/de/upb/swt/tbviewer/InputValidation.java @@ -46,8 +46,7 @@ public static boolean isTAFformat(File file) { if (!sink.has("methodName")) return false; if (!sink.has("className")) return false; } - if (!finding.has("intermediateFlows")) return false; - else { + if (finding.has("intermediateFlows")) { JsonArray flows = finding.getAsJsonArray("intermediateFlows"); for (int j = 0; j < flows.size(); j++) { JsonObject inter = flows.get(j).getAsJsonObject(); diff --git a/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java b/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java index 72fe56e..bee5988 100644 --- a/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java +++ b/src/main/java/de/upb/swt/tbviewer/TaintServerAnalysis.java @@ -151,7 +151,11 @@ protected void readGroundTruth() { ArrayList> relatedWithoutPath = new ArrayList>(); JsonObject finding = findings.get(i).getAsJsonObject(); - boolean isNegativeFlow = finding.get("isNegative").getAsBoolean(); + boolean isNegativeFlow = false; + if (finding.has("isNegative")) isNegativeFlow = finding.get("isNegative").getAsBoolean(); + else if (finding.has("isUnexpected")) { + isNegativeFlow = finding.get("isUnexpected").getAsBoolean(); + } String ID = finding.get("ID").toString(); StringBuilder message = new StringBuilder(ID + "P. Malicious taint flow"); if (isNegativeFlow) message = new StringBuilder(ID + "N. Negative taint flow"); @@ -543,7 +547,6 @@ protected Map findFlowMatchUsingJimple( sinkJimpleStatement = IR.get("IRstatement").getAsString(); } } - boolean isNegativeFlow = finding.get("isNegative").getAsBoolean(); if (sourceJimpleStatement != null && sinkJimpleStatement != null) { if (source.getStatement().getLinenumber() == -1 && sink.getStatement().getLinenumber() == -1) { diff --git a/vscode/README.md b/vscode/README.md new file mode 100644 index 0000000..44f9244 --- /dev/null +++ b/vscode/README.md @@ -0,0 +1,15 @@ +# TB-Viewer +This extension displays the baseline ground truth defined in the [TaintBench](https://taintbench.github.io/) suite for benchmarking Android taint analyses. +# Usage +- Short introduction video: https://youtu.be/UQSHwN_aC9g +- For online usage: + - Find access to use it in GitPod on https://taintbench.github.io/taintbenchSuite +- For local usage: + - Install the extension in Visual Studio Code + - Clone a benchmark repository from https://taintbench.github.io/taintbenchSuite + - Open the folder of your local benchmark repository in Visual Studio Code. Make sure the folder you opened in Visual Studio Code is the root folder of the local repository which contains the ***findings.json file. + - Open any java file in local repository + + +The baseline ground truth are displayed on the left panel of the editor as shown in the screenshot below. Clicking the magnifying glass on the items in the left panel will open and highlight the relevant code in the editor. +![screenshot](https://github.com/TaintBench/TB-Viewer/blob/develop/vscode/media/screenshot.PNG?raw=true) diff --git a/vscode/package.json b/vscode/package.json index 83919b3..3b367b5 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -1,17 +1,19 @@ { "name": "TB-Viewer", - "description": "A language server for TaintBench", - "author": "TaintBench", - "license": "EPL-2.0", + "description": "A language server for displaying the baseline ground truth in TaintBench.", + "author": "Linghui Luo", + "license": "MIT", "version": "0.0.3", + "homepage": "https://taintbench.github.io", + "bugs": "https://github.com/TaintBench/TB-Viewer/issues", "repository": { "type": "git", - "url": "" + "url": "https://github.com/TaintBench/TB-Viewer" }, "publisher": "taintbench", - "categories": [], + "categories": ["Programming Languages"], "keywords": [ - "multi-root ready" + "static taint analysis" ], "engines": { "vscode": "^1.30.0" diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index 661d022..dbea990 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -210,12 +210,12 @@ export class SimpleTreeDataProvider implements TreeDataProvider { if (seperation) { if (!isDetected) { - summaryPositive.label = "Positive Flows: " + this.positiveFlows.length; + summaryPositive.label = "Expected (Positive) Flows: " + this.positiveFlows.length; summaryPositive.children = this.positiveFlows; summaryPositive.iconPath = path.join(__filename, '..', '..', 'media/summary.svg'); summaryPositive.collapsibleState = TreeItemCollapsibleState.Expanded; - summaryNegative.label = "Negative Flows: " + this.negativeFlows.length; + summaryNegative.label = "Unexpected (Negative) Flows: " + this.negativeFlows.length; summaryNegative.children = this.negativeFlows; summaryNegative.iconPath = path.join(__filename, '..', '..', 'media/summary.svg'); summaryNegative.collapsibleState = TreeItemCollapsibleState.Expanded;