diff --git a/examples/falo.ini b/examples/falo.ini index 2a7d3ca..7e8f017 100644 --- a/examples/falo.ini +++ b/examples/falo.ini @@ -8,13 +8,13 @@ logLevel: 4 logConsole: false # Do not remove duplicate edges -multiGraph: true +multiGraph: false # One graph per or groupBy: ENTRY # store dot files and coverage matrix -# writeTo: DOT,TRACE,COVERAGE,DD_DOT,LINES -writeTo: DOT,DD_DOT,LINES +writeTo: DOT,TRACE,COVERAGE,DD_DOT,LINES,COMBINED_DOT +# writeTo: DOT,DD_DOT,LINES format: {class}#{line} diff --git a/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/FieldAccessRecorder.java b/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/FieldAccessRecorder.java index bf99c4a..2026c13 100644 --- a/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/FieldAccessRecorder.java +++ b/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/FieldAccessRecorder.java @@ -26,7 +26,9 @@ import com.dkarv.jdcallgraph.callgraph.CallGraph; import com.dkarv.jdcallgraph.data.DataDependenceGraph; import com.dkarv.jdcallgraph.util.StackItem; +import com.dkarv.jdcallgraph.util.config.*; import com.dkarv.jdcallgraph.util.log.Logger; +import com.dkarv.jdcallgraph.util.options.*; import java.io.IOException; import java.util.HashMap; @@ -40,6 +42,17 @@ public class FieldAccessRecorder { * Collect the call graph per thread. */ static final Map GRAPHS = new HashMap<>(); + private static final boolean needCombined; + + static { + boolean combined = false; + for (Target t : Config.getInst().writeTo()) { + if (t == Target.COMBINED_DOT) { + combined = true; + } + } + needCombined = combined; + } public static void write(String fromClass, String fromMethod, int lineNumber, String fieldClass, String fieldName) { try { @@ -65,7 +78,14 @@ public static void read(String fromClass, String fromMethod, int lineNumber, Str LOG.trace("Read not interesting because there was no write before"); return; } - graph.addRead(new StackItem(fromClass, fromMethod, lineNumber), fieldClass + "::" + fieldName); + if (needCombined) { + CallGraph callGraph = CallRecorder.GRAPHS.get(threadId); + graph.addRead(new StackItem(fromClass, fromMethod, lineNumber), fieldClass + "::" + + fieldName, callGraph); + } else { + graph.addRead(new StackItem(fromClass, fromMethod, lineNumber), fieldClass + "::" + + fieldName, null); + } } catch (Exception e) { LOG.error("Error in read", e); } diff --git a/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/Tracer.java b/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/Tracer.java index 92e1b9d..a25a53a 100644 --- a/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/Tracer.java +++ b/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/Tracer.java @@ -203,24 +203,6 @@ public static int getLineNumber(CtBehavior method) { public static String getMethodName(CtBehavior method) throws NotFoundException { return method.getName() + Descriptor.toString(method.getSignature()); - - /* - StringBuilder methodName = new StringBuilder(); - // boolean isConstructor = method.getMethodInfo().isConstructor(); - - // TODO replace with method.getLongName() - methodName.append(method.getName()); - methodName.append('('); - CtClass[] params = method.getParameterTypes(); - for (int i = 0; i < params.length; i++) { - if (i != 0) { - methodName.append(','); - } - methodName.append(getShortName(params[i])); - } - methodName.append(')'); - return methodName.toString(); - */ } static String getShortName(final CtClass clazz) { diff --git a/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/callgraph/CallGraph.java b/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/callgraph/CallGraph.java index 4a24c4c..acc92d8 100644 --- a/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/callgraph/CallGraph.java +++ b/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/callgraph/CallGraph.java @@ -137,6 +137,18 @@ public void returned(StackItem method) throws IOException { } } + public void dataEdge(StackItem from, StackItem to) throws IOException { + if (calls.isEmpty()) { + LOG.info("Ignore dd egde {} -> {}", from, to); + } else { + for (GraphWriter w : writers) { + if (w instanceof DotFileWriter || w instanceof RemoveDuplicatesWriter) { + w.edge(from, to, "[style=dotted]"); + } + } + } + } + public void finish() throws IOException { if (!calls.isEmpty()) { LOG.error("Shutdown but call graph not empty: {}", calls); diff --git a/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/data/DataDependenceGraph.java b/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/data/DataDependenceGraph.java index 2dbbd58..edab9ee 100644 --- a/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/data/DataDependenceGraph.java +++ b/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/data/DataDependenceGraph.java @@ -23,6 +23,7 @@ */ package com.dkarv.jdcallgraph.data; +import com.dkarv.jdcallgraph.callgraph.*; import com.dkarv.jdcallgraph.util.StackItem; import com.dkarv.jdcallgraph.util.options.Target; import com.dkarv.jdcallgraph.util.config.Config; @@ -48,7 +49,7 @@ public class DataDependenceGraph { public DataDependenceGraph(long threadId) throws IOException { Target[] targets = Config.getInst().writeTo(); for (Target target : targets) { - if (target.isDataDependency()) { + if (target.isDataDependency() && target != Target.COMBINED_DOT) { GraphWriter writer = createWriter(target); writer.start(FOLDER + "data_" + threadId); writers.add(writer); @@ -75,7 +76,7 @@ public void addWrite(StackItem location, String field) throws IOException { // } } - public void addRead(StackItem location, String field) throws IOException { + public void addRead(StackItem location, String field, CallGraph callGraph) throws IOException { LOG.trace("Read to {} from {}", field, location); StackItem lastWrite = lastWrites.get(field); if (lastWrite != null) { @@ -85,6 +86,9 @@ public void addRead(StackItem location, String field) throws IOException { for (GraphWriter writer : writers) { writer.edge(lastWrite, location, field); } + if (callGraph != null) { + callGraph.dataEdge(lastWrite, location); + } } } } diff --git a/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/util/options/Target.java b/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/util/options/Target.java index 95f26a2..b0c8e09 100644 --- a/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/util/options/Target.java +++ b/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/util/options/Target.java @@ -54,9 +54,14 @@ public enum Target { /** * Data dependence graph as csv. */ - DD_TRACE; + DD_TRACE, + /** + * Combined call and dd graph in dot file format. + */ + COMBINED_DOT; public boolean isDataDependency() { - return this == Target.DD_DOT || this == Target.DD_TRACE; + return this == Target.DD_DOT || this == Target.DD_TRACE || + this == Target.COMBINED_DOT; } } diff --git a/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/writer/DotFileWriter.java b/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/writer/DotFileWriter.java index 7f063a5..7500fa6 100644 --- a/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/writer/DotFileWriter.java +++ b/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/writer/DotFileWriter.java @@ -54,8 +54,19 @@ public void edge(StackItem from, StackItem to) throws IOException { } @Override - public void edge(StackItem from, StackItem to, String label) throws IOException { - writer.append("\t\"" + from.toString() + "\" -> \"" + to.toString() + "\" [label=\"" + label + "\"];\n"); + public void edge(StackItem from, StackItem to, String attributes) throws IOException { + writer.append('\t'); + writer.append('"'); + writer.append(from.toString()); + writer.append('"'); + writer.append(" -> "); + writer.append('"'); + writer.append(to.toString()); + writer.append('"'); + writer.append(' '); + writer.append(attributes); + writer.append(';'); + writer.append('\n'); } @Override diff --git a/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/writer/GraphWriter.java b/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/writer/GraphWriter.java index e85d4d0..7d18781 100644 --- a/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/writer/GraphWriter.java +++ b/jdcallgraph/src/main/java/com/dkarv/jdcallgraph/writer/GraphWriter.java @@ -52,9 +52,10 @@ public interface GraphWriter extends Closeable { void edge(StackItem from, StackItem to) throws IOException; /** - * Write an edge with a label. If the writer does not support labels it will write the edge without. + * Write an edge with additional attributes. If the writer does not support labels it will write + * the edge without. For dot files the attributes should look like [style=dotted, label="asdf"] */ - void edge(StackItem from, StackItem to, String label) throws IOException; + void edge(StackItem from, StackItem to, String attributes) throws IOException; /** * Finish the graph. diff --git a/jdcallgraph/src/test/java/com/dkarv/jdcallgraph/EnhanceMethodTest.java b/jdcallgraph/src/test/java/com/dkarv/jdcallgraph/EnhanceMethodTest.java index 80f3340..80df383 100644 --- a/jdcallgraph/src/test/java/com/dkarv/jdcallgraph/EnhanceMethodTest.java +++ b/jdcallgraph/src/test/java/com/dkarv/jdcallgraph/EnhanceMethodTest.java @@ -4,6 +4,7 @@ import javassist.CtBehavior; import javassist.CtClass; import javassist.NotFoundException; +import javassist.bytecode.*; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -41,7 +42,8 @@ private String expected(String cName, String mName) { @Test public void testConstructor() throws NotFoundException, CannotCompileException { - Mockito.when(behavior.getLongName()).thenReturn("Example()"); + Mockito.when(behavior.getName()).thenReturn("Example"); + Mockito.when(behavior.getSignature()).thenReturn("()"); p.enhanceMethod(behavior, className); @@ -52,19 +54,24 @@ public void testConstructor() throws NotFoundException, CannotCompileException { @Test public void testParameters() throws NotFoundException, CannotCompileException { - Mockito.when(behavior.getLongName()).thenReturn("method(String,int,byte)"); + Mockito.when(behavior.getName()).thenReturn("method"); + Mockito.when(behavior.getSignature()).thenReturn( + Descriptor.ofMethod(null, + new CtClass[]{CtClass.booleanType, CtClass.intType, CtClass.byteType})); p.enhanceMethod(behavior, className); ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); Mockito.verify(behavior).insertBefore(captor.capture()); - Assert.assertEquals(expected(className, "method(String,int,byte)"), captor.getValue()); + Assert.assertEquals(expected(className, "method(boolean,int,byte)"), captor.getValue()); } @Test @SuppressWarnings("unchecked") public void testIsTest() throws NotFoundException, CannotCompileException, ClassNotFoundException { - Mockito.when(behavior.getLongName()).thenReturn("method()"); + Mockito.when(behavior.getName()).thenReturn("method"); + Mockito.when(behavior.getSignature()).thenReturn( + Descriptor.ofMethod(null, new CtClass[0])); Annotation[] annotations = new Annotation[]{ Mockito.mock(Annotation.class, Mockito.RETURNS_DEEP_STUBS),