From d64825d44cd9d9168d470e7b0d1da1f878707e1e Mon Sep 17 00:00:00 2001 From: Roopa Mohan Date: Tue, 14 Jun 2022 09:52:34 -0700 Subject: [PATCH 1/8] Add more information while handling error --- sfge/src/main/java/com/salesforce/Main.java | 5 +++++ .../main/java/com/salesforce/graph/build/GremlinUtil.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/sfge/src/main/java/com/salesforce/Main.java b/sfge/src/main/java/com/salesforce/Main.java index d7f4e0c31..9493ac13f 100644 --- a/sfge/src/main/java/com/salesforce/Main.java +++ b/sfge/src/main/java/com/salesforce/Main.java @@ -4,6 +4,7 @@ import com.salesforce.cli.OutputFormatter; import com.salesforce.exception.SfgeException; import com.salesforce.exception.SfgeRuntimeException; +import com.salesforce.exception.UnexpectedException; import com.salesforce.graph.ops.GraphUtil; import com.salesforce.messaging.CliMessager; import com.salesforce.metainfo.MetaInfoCollector; @@ -129,6 +130,10 @@ private int execute(String... args) { LOGGER.error("Error while loading graph", ex); System.err.println(formatError(ex)); return INTERNAL_ERROR; + } catch (UnexpectedException ex) { + LOGGER.error("Unexpected exception while loading graph", ex); + System.err.println("Unexpected exception while loading graph. See logs for more information."); + return INTERNAL_ERROR; } // Run all of the rules. diff --git a/sfge/src/main/java/com/salesforce/graph/build/GremlinUtil.java b/sfge/src/main/java/com/salesforce/graph/build/GremlinUtil.java index 72c3ac09b..a7a5099a6 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/GremlinUtil.java +++ b/sfge/src/main/java/com/salesforce/graph/build/GremlinUtil.java @@ -32,7 +32,7 @@ public static Optional getOnlyChild( if (children.isEmpty()) { return Optional.empty(); } else if (children.size() > 1) { - throw new UnexpectedException(children); + throw new UnexpectedException("Did not expect more than one child node. Actual count: " + children.size()); } else { return Optional.of(children.get(0)); } From 1dfce6a11e085930beaa19ba93c1925f47d874aa Mon Sep 17 00:00:00 2001 From: Roopa Mohan Date: Wed, 22 Jun 2022 15:29:29 -0700 Subject: [PATCH 2/8] work in progress - static block recognition part --- .../java/com/salesforce/graph/Schema.java | 3 + .../build/AbstractApexVertexBuilder.java | 134 +++++++++++++++--- .../ApexStandardLibraryVertexBuilder.java | 5 +- .../salesforce/graph/build/GremlinUtil.java | 15 +- .../graph/build/MethodPathBuilderVisitor.java | 53 +++++-- .../com/salesforce/graph/ops/MethodUtil.java | 1 + .../graph/symbols/ClassStaticScope.java | 42 +++++- .../salesforce/graph/vertex/MethodVertex.java | 44 +++++- 8 files changed, 260 insertions(+), 37 deletions(-) diff --git a/sfge/src/main/java/com/salesforce/graph/Schema.java b/sfge/src/main/java/com/salesforce/graph/Schema.java index b38a1d413..a6a07f757 100644 --- a/sfge/src/main/java/com/salesforce/graph/Schema.java +++ b/sfge/src/main/java/com/salesforce/graph/Schema.java @@ -69,4 +69,7 @@ public static final class JorjeNodeType { public static final String CHILD = "Child"; public static final String PARENT = "Parent"; public static final String NEXT_SIBLING = "NextSibling"; + + /** Indicates if a method is a synthetic static block method */ + public static final String IS_STATIC_BLOCK_METHOD = "IsStaticBlockMethod"; } diff --git a/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java b/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java index e9c02ffe1..7a3a93d64 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java +++ b/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java @@ -6,10 +6,13 @@ import com.salesforce.collections.CollectionUtil; import com.salesforce.exception.UnexpectedException; import com.salesforce.graph.Schema; +import com.salesforce.graph.ops.MethodUtil; + import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.TreeSet; import org.apache.commons.lang3.StringUtils; @@ -71,23 +74,20 @@ private void buildVertices(JorjeNode node, Vertex vNodeParam, String fileName) { vNode.property(Schema.FILE_NAME, fileName); } + final boolean needsStaticBlockSplHandling = isClintMethod(node) && containsStaticBlock(node); Vertex vPreviousSibling = null; - for (JorjeNode child : node.getChildren()) { - Vertex vChild = g.addV(child.getLabel()).next(); - addProperties(g, child, vChild); - // We are currently adding PARENT and CHILD, in theory the same edge could be navigated - // both ways, however - // the code looks messy when that is done. - // TODO: Determine if this causes performance or resource issues. Consider a single edge - g.addE(Schema.PARENT).from(vChild).to(vNode).iterate(); - g.addE(Schema.CHILD).from(vNode).to(vChild).iterate(); - if (vPreviousSibling != null) { - g.addE(Schema.NEXT_SIBLING).from(vPreviousSibling).to(vChild).iterate(); - } - vPreviousSibling = vChild; - // To save memory in the graph, don't pass the source name into recursive calls. - buildVertices(child, vChild, null); - } + final List children = node.getChildren(); + + for (int i = 0; i < children.size(); i++) { + final JorjeNode child = children.get(i); + if (needsStaticBlockSplHandling + && ASTConstants.NodeType.BLOCK_STATEMENT.equals(child.getLabel())) { + // todo: add static blocks as new methods + vPreviousSibling = addStaticBlockAsNewMethod(vNode, vPreviousSibling, child, node, i); + } else { + vPreviousSibling = addChildNodeToGraph(vNode, vPreviousSibling, child); + } + } afterInsert(g, node, vNode); if (rootVNode != null) { // Only call this for the root node @@ -95,13 +95,67 @@ private void buildVertices(JorjeNode node, Vertex vNodeParam, String fileName) { } } - /** + private Vertex addChildNodeToGraph(Vertex vNode, Vertex vPreviousSibling, JorjeNode child) { + Vertex vChild = g.addV(child.getLabel()).next(); + addProperties(g, child, vChild); + return addParentChildRelationship(vNode, vChild, child, vPreviousSibling); + } + + private Vertex addParentChildRelationship(Vertex parentVertex, Vertex childVertex, JorjeNode child, Vertex vPreviousSibling) { + addParentChildRelationship(parentVertex, childVertex); + if (vPreviousSibling != null) { + g.addE(Schema.NEXT_SIBLING).from(vPreviousSibling).to(childVertex).iterate(); + } + vPreviousSibling = childVertex; + // To save memory in the graph, don't pass the source name into recursive calls. + buildVertices(child, childVertex, null); + return vPreviousSibling; + } + + private void addParentChildRelationship(Vertex parentVertex, Vertex childVertex) { + // We are currently adding PARENT and CHILD, in theory the same edge could be navigated + // both ways, however + // the code looks messy when that is done. + // TODO: Determine if this causes performance or resource issues. Consider a single edge + g.addE(Schema.PARENT).from(childVertex).to(parentVertex).iterate(); + g.addE(Schema.CHILD).from(parentVertex).to(childVertex).iterate(); + } + + private Vertex addStaticBlockAsNewMethod(Vertex clintVertex, Vertex vPreviousSibling, JorjeNode child, JorjeNode clintNode, int staticBlockIndex) { + // Add properties of () to syntheticMethodVertex, + // override properties with a new name and an indicator to + // show this is a synthetic static block method + final Vertex syntheticMethodVertex = g.addV(ASTConstants.NodeType.METHOD).next(); + + final HashMap overrides = new HashMap<>(); + overrides.put(Schema.NAME, String.format(MethodUtil.SYNTHETIC_STATIC_BLOCK_METHOD_NAME, staticBlockIndex)); + addProperties(g, clintNode, syntheticMethodVertex, overrides); + + // TODO: Add vertex's parent as Synthetic method vertex's parent + // how do i get 's parent vertex? + + final Optional grandParentVertex = GremlinUtil.getParent(g, clintVertex); + if (grandParentVertex.isEmpty()) { + throw new UnexpectedException("Did not expect () to not have a parent vertex. clintVertex=" + clintVertex); + } + + addParentChildRelationship(grandParentVertex.get(), syntheticMethodVertex); + + // Create a vertex for BlockStatement + final Vertex vChild = g.addV(child.getLabel()).next(); + addProperties(g, child, vChild); + + // add BlockStatement as child of SyntheticMethodVertex + return addParentChildRelationship(syntheticMethodVertex, vChild, child, vPreviousSibling); + } + + /** * Adds edges to Method vertices after they are inserted. TODO: Replace this with a listener or * visitor pattern for more general purpose solutions */ private final void afterInsert(GraphTraversalSource g, JorjeNode node, Vertex vNode) { if (node.getLabel().equals(ASTConstants.NodeType.METHOD)) { - MethodPathBuilderVisitor.apply(g, vNode); + MethodPathBuilderVisitor.apply(g, vNode); } } @@ -114,7 +168,11 @@ protected void afterFileInsert(GraphTraversalSource g, Vertex vNode) { // Intentionally left blank } - protected void addProperties(GraphTraversalSource g, JorjeNode node, Vertex vNode) { + protected void addProperties(GraphTraversalSource g, JorjeNode node, Vertex vNode) { + addProperties(g, node, vNode, new HashMap<>()); + } + + protected void addProperties(GraphTraversalSource g, JorjeNode node, Vertex vNode, HashMap overrides) { GraphTraversal traversal = g.V(vNode.id()); TreeSet previouslyInsertedKeys = CollectionUtil.newTreeSet(); @@ -122,10 +180,14 @@ protected void addProperties(GraphTraversalSource g, JorjeNode node, Vertex vNod String key = entry.getKey(); Object value = entry.getValue(); value = adjustPropertyValue(node, key, value); + // Override value for key if requested + if (overrides.containsKey(key)) { + value = overrides.get(key); + } addProperty(previouslyInsertedKeys, traversal, key, value); } - for (Map.Entry entry : getAdditionalProperties(node).entrySet()) { + for (Map.Entry entry : getAdditionalProperties(node, overrides).entrySet()) { addProperty(previouslyInsertedKeys, traversal, entry.getKey(), entry.getValue()); } @@ -139,11 +201,37 @@ protected Object adjustPropertyValue(JorjeNode node, String key, Object value) { } /** Add additional properties to the node that aren't present in the orginal AST */ - protected Map getAdditionalProperties(JorjeNode node) { - return new HashMap<>(); + protected Map getAdditionalProperties(JorjeNode node, HashMap overrides) { + final HashMap returnMap = new HashMap<>(); + + // FIXME: very crude + if (!overrides.isEmpty()) { + returnMap.put(Schema.IS_STATIC_BLOCK_METHOD, Boolean.valueOf(true)); + } + return returnMap; } - /** Add a property to the traversal, throwing an exception if any keys are duplicated. */ + /** + * @return true if given node represents () + */ + private boolean isClintMethod(JorjeNode node) { + return ASTConstants.NodeType.METHOD.equals(node.getLabel()) + && MethodUtil.STATIC_CONSTRUCTOR_CANONICAL_NAME.equals(node.getProperties().get(Schema.NAME)); + } + + /** + * @return true if () node contains any static block definitions + */ + private boolean containsStaticBlock(JorjeNode node) { + for (JorjeNode childNode : node.getChildren()) { + if (ASTConstants.NodeType.BLOCK_STATEMENT.equals(childNode.getLabel())) { + return true; + } + } + return false; + } + + /** Add a property to the traversal, throwing an exception if any keys are duplicated. */ protected void addProperty( TreeSet previouslyInsertedKeys, GraphTraversal traversal, diff --git a/sfge/src/main/java/com/salesforce/graph/build/ApexStandardLibraryVertexBuilder.java b/sfge/src/main/java/com/salesforce/graph/build/ApexStandardLibraryVertexBuilder.java index 8486cd02f..beb8cda27 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/ApexStandardLibraryVertexBuilder.java +++ b/sfge/src/main/java/com/salesforce/graph/build/ApexStandardLibraryVertexBuilder.java @@ -5,6 +5,7 @@ import com.salesforce.exception.UnexpectedException; import com.salesforce.graph.Schema; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.logging.log4j.LogManager; @@ -72,8 +73,8 @@ protected Object adjustPropertyValue(JorjeNode node, String key, Object value) { } @Override - protected Map getAdditionalProperties(JorjeNode node) { - Map result = super.getAdditionalProperties(node); + protected Map getAdditionalProperties(JorjeNode node, HashMap overrides) { + Map result = super.getAdditionalProperties(node, overrides); // Mark all nodes as being from the Standard library // TODO: Reduce number of nodes where this is set diff --git a/sfge/src/main/java/com/salesforce/graph/build/GremlinUtil.java b/sfge/src/main/java/com/salesforce/graph/build/GremlinUtil.java index a7a5099a6..d98f77449 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/GremlinUtil.java +++ b/sfge/src/main/java/com/salesforce/graph/build/GremlinUtil.java @@ -32,7 +32,7 @@ public static Optional getOnlyChild( if (children.isEmpty()) { return Optional.empty(); } else if (children.size() > 1) { - throw new UnexpectedException("Did not expect more than one child node. Actual count: " + children.size()); + throw new UnexpectedException("Did not expect more than one child node of type " + childLabel +". Actual count: " + children.size()); } else { return Optional.of(children.get(0)); } @@ -56,6 +56,10 @@ public static List getChildren(GraphTraversalSource g, Vertex vertex) { .toList(); } + public static List getChildren(GraphTraversalSource g, Vertex vertex, String childLabel) { + return g.V(vertex).out(Schema.CHILD).hasLabel(childLabel).toList(); + } + public static Optional getPreviousSibling(GraphTraversalSource g, Vertex vertex) { Iterator it = g.V(vertex).in(Schema.NEXT_SIBLING); if (it.hasNext()) { @@ -87,5 +91,14 @@ public static Optional getFirstChild(GraphTraversalSource g, Vertex vert } } + public static Optional getParent(GraphTraversalSource g, Vertex vertex) { + Iterator it = g.V(vertex).out(Schema.PARENT); + if (it.hasNext()) { + return Optional.of(it.next()); + } else { + return Optional.empty(); + } + } + private GremlinUtil() {} } diff --git a/sfge/src/main/java/com/salesforce/graph/build/MethodPathBuilderVisitor.java b/sfge/src/main/java/com/salesforce/graph/build/MethodPathBuilderVisitor.java index 5d7ba6c86..805a13443 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/MethodPathBuilderVisitor.java +++ b/sfge/src/main/java/com/salesforce/graph/build/MethodPathBuilderVisitor.java @@ -5,6 +5,8 @@ import com.salesforce.exception.TodoException; import com.salesforce.exception.UnexpectedException; import com.salesforce.graph.Schema; +import com.salesforce.graph.ops.CloneUtil; +import com.salesforce.graph.ops.MethodUtil; import com.salesforce.graph.vertex.SFVertexFactory; import java.util.ArrayList; import java.util.Arrays; @@ -22,7 +24,8 @@ /** Visits a method and draws all control flow edges. */ public class MethodPathBuilderVisitor { private static final Logger LOGGER = LogManager.getLogger(MethodPathBuilderVisitor.class); - /** Load the vertices as SFVertices and log more information. Will impact performance. */ + private static final String NAME_PROPERTY = "Name_CaseSafe"; // FIXME: identify where this gets created for real + /** Load the vertices as SFVertices and log more information. Will impact performance. */ private static boolean SF_VERTEX_LOGGING = true; /** @@ -87,16 +90,48 @@ public static void apply(GraphTraversalSource g, Vertex methodVertex) { throw new UnexpectedException(methodVertex); } - Vertex blockStatement = - GremlinUtil.getOnlyChild(g, methodVertex, NodeType.BLOCK_STATEMENT).orElse(null); - // This can be null in cases such as default constructors that don't have an implementation - if (blockStatement != null) { - MethodPathBuilderVisitor visitor = new MethodPathBuilderVisitor(g); - visitor._visit(blockStatement, methodVertex, true); - } + // Handle () methods + if (methodVertex.value(NAME_PROPERTY).equals(MethodUtil.STATIC_CONSTRUCTOR_CANONICAL_NAME)) { + handleClintMethod(g, methodVertex); + } else { + + Vertex blockStatement = + GremlinUtil.getOnlyChild(g, methodVertex, NodeType.BLOCK_STATEMENT).orElse(null); + // This can be null in cases such as default constructors that don't have an implementation + if (blockStatement != null) { + MethodPathBuilderVisitor visitor = new MethodPathBuilderVisitor(g); + visitor._visit(blockStatement, methodVertex, true); + } + } } - private void _visit(Vertex vertex, Vertex parent, boolean lastChild) { + + /** + * Handles default method generated by jorje for every class. + * Block statements under are static code blocks such as + * class MyClass { + * static { + * System.debug('hello'); + * } + * } + * @param g reference to graph + * @param methodVertex represents vertex of () + */ + private static void handleClintMethod(GraphTraversalSource g, Vertex methodVertex) { + final List blockStatements = GremlinUtil.getChildren(g, methodVertex, NodeType.BLOCK_STATEMENT); + for (Vertex blockStatement : blockStatements) { + // We should handle each block statement one after another, as if they were in the same method + // since they will all be executed one after another. + // TODO: Confirm that static blocks get executed in the same order they are listed in the class + if (blockStatement != null) { +// final Vertex staticBlockVertex = CloneUtil.clone(methodVertex); + final MethodPathBuilderVisitor visitor = new MethodPathBuilderVisitor(g); + visitor._visit(blockStatement, methodVertex, true); + } + } + } + + private void _visit(Vertex vertex, Vertex parent, boolean lastChild) { try { String label = vertex.label(); diff --git a/sfge/src/main/java/com/salesforce/graph/ops/MethodUtil.java b/sfge/src/main/java/com/salesforce/graph/ops/MethodUtil.java index 9632bb1d6..8a81a5830 100644 --- a/sfge/src/main/java/com/salesforce/graph/ops/MethodUtil.java +++ b/sfge/src/main/java/com/salesforce/graph/ops/MethodUtil.java @@ -83,6 +83,7 @@ public final class MethodUtil { private static final String PAGE_REFERENCE = "PageReference"; public static final String INSTANCE_CONSTRUCTOR_CANONICAL_NAME = ""; public static final String STATIC_CONSTRUCTOR_CANONICAL_NAME = ""; + public static final String SYNTHETIC_STATIC_BLOCK_METHOD_NAME = "SyntheticStaticBlock_%d"; public static List getTargetedMethods( GraphTraversalSource g, List targets) { diff --git a/sfge/src/main/java/com/salesforce/graph/symbols/ClassStaticScope.java b/sfge/src/main/java/com/salesforce/graph/symbols/ClassStaticScope.java index 940f23704..984016cd2 100644 --- a/sfge/src/main/java/com/salesforce/graph/symbols/ClassStaticScope.java +++ b/sfge/src/main/java/com/salesforce/graph/symbols/ClassStaticScope.java @@ -2,6 +2,9 @@ import static com.salesforce.apex.jorje.ASTConstants.NodeType; +import com.salesforce.Collectible; +import com.salesforce.apex.jorje.ASTConstants; +import com.salesforce.collections.CollectionUtil; import com.salesforce.exception.UnexpectedException; import com.salesforce.graph.ApexPath; import com.salesforce.graph.DeepCloneable; @@ -10,7 +13,9 @@ import com.salesforce.graph.ops.MethodUtil; import com.salesforce.graph.symbols.apex.ApexValue; import com.salesforce.graph.vertex.BaseSFVertex; +import com.salesforce.graph.vertex.BlockStatementVertex; import com.salesforce.graph.vertex.FieldDeclarationVertex; +import com.salesforce.graph.vertex.MethodVertex; import com.salesforce.graph.vertex.SFVertexFactory; import com.salesforce.graph.vertex.UserClassVertex; import java.util.ArrayList; @@ -105,6 +110,35 @@ private static List getFieldDeclarations( return results; } + private static List getStaticBlocks( + GraphTraversalSource g, UserClassVertex userClass) { + List results = new ArrayList<>(); + + String superClassName = userClass.getSuperClassName().orElse(null); + if (superClassName != null) { + UserClassVertex superClass = ClassUtil.getUserClass(g, superClassName).orElse(null); + if (superClass != null) { + results.addAll(getStaticBlocks(g, superClass)); + } + } + + results.addAll( + SFVertexFactory.loadVertices( + g, + g.V(userClass.getId()) + .out(Schema.CHILD) + .hasLabel(ASTConstants.NodeType.METHOD) + .has(Schema.IS_STATIC_BLOCK_METHOD, true) + .order(Scope.global) + .by(Schema.CHILD_INDEX, Order.asc) + /*.out(Schema.CHILD) + .hasLabel(ASTConstants.NodeType.BLOCK_STATEMENT) + .order(Scope.global) + .by(Schema.CHILD_INDEX, Order.asc)*/)); + + return results; + } + /** * Returns a path that represents the static properties defined by the class. The following * example would contain a path for 'MyClass' that contains the Field and FieldDeclarations for @@ -122,11 +156,17 @@ public static Optional getInitializationPath( GraphTraversalSource g, String classname) { ClassStaticScope classStaticScope = ClassStaticScope.get(g, classname); List vertices = new ArrayList<>(); + List methodVertices = new ArrayList<>(); vertices.addAll(classStaticScope.getFields()); vertices.addAll(getFieldDeclarations(g, classStaticScope.userClass)); - if (vertices.isEmpty()) { + methodVertices.addAll(getStaticBlocks(g, classStaticScope.userClass)); + if (vertices.isEmpty() && methodVertices.isEmpty()) { return Optional.empty(); } else { + ArrayList> apexPaths = new ArrayList<>(); + for (MethodVertex methodVertex : methodVertices) { + apexPaths.add(new ApexPath(methodVertex)); + } ApexPath apexPath = new ApexPath(null); apexPath.addVertices(vertices); return Optional.of(apexPath); diff --git a/sfge/src/main/java/com/salesforce/graph/vertex/MethodVertex.java b/sfge/src/main/java/com/salesforce/graph/vertex/MethodVertex.java index 3f0b59fd9..9c203c6d7 100644 --- a/sfge/src/main/java/com/salesforce/graph/vertex/MethodVertex.java +++ b/sfge/src/main/java/com/salesforce/graph/vertex/MethodVertex.java @@ -4,6 +4,7 @@ import com.salesforce.NullCollectible; import com.salesforce.apex.jorje.ASTConstants; import com.salesforce.graph.Schema; +import com.salesforce.graph.ops.MethodUtil; import com.salesforce.graph.symbols.SymbolProvider; import com.salesforce.graph.symbols.SymbolProviderVertexVisitor; import com.salesforce.graph.visitor.PathVertexVisitor; @@ -137,6 +138,41 @@ public ConstructorVertex getCollectible() { } } + public static final class StaticBlockVertex extends MethodVertex implements Collectible { + public static final NullCollectible NULL_VALUE = + new NullCollectible<>(StaticBlockVertex.class); + + private StaticBlockVertex(Map properties) { + super(properties); + } + + @Override + public boolean visit(PathVertexVisitor visitor, SymbolProvider symbols) { + return visitor.visit(this, symbols); + } + + @Override + public boolean visit(SymbolProviderVertexVisitor visitor) { + return visitor.visit(this); + } + + @Override + public void afterVisit(PathVertexVisitor visitor, SymbolProvider symbols) { + visitor.afterVisit(this, symbols); + } + + @Override + public void afterVisit(SymbolProviderVertexVisitor visitor) { + visitor.afterVisit(this); + } + + @Nullable + @Override + public StaticBlockVertex getCollectible() { + return this; + } + } + public static final class InstanceMethodVertex extends MethodVertex { private InstanceMethodVertex(Map properties) { super(properties); @@ -165,8 +201,14 @@ public void afterVisit(SymbolProviderVertexVisitor visitor) { public static final class Builder { public static MethodVertex create(Map vertex) { + final boolean isStaticBlockMethod = BaseSFVertex.toBoolean(vertex.get(Schema.IS_STATIC_BLOCK_METHOD)); boolean isConstructor = BaseSFVertex.toBoolean(vertex.get(Schema.CONSTRUCTOR)); - return isConstructor ? new ConstructorVertex(vertex) : new InstanceMethodVertex(vertex); + if (isStaticBlockMethod) { + return new StaticBlockVertex(vertex); + } else if (isConstructor) { + return new ConstructorVertex(vertex); + } + return new InstanceMethodVertex(vertex); } } } From d1eb1c51afbb045bcd21402e38213dc48eb3b944 Mon Sep 17 00:00:00 2001 From: Roopa Mohan Date: Fri, 24 Jun 2022 12:40:47 -0700 Subject: [PATCH 3/8] Solution to handle static blocks in code - draft --- .../java/com/salesforce/graph/ApexPath.java | 5 +- .../java/com/salesforce/graph/Schema.java | 6 + .../build/AbstractApexVertexBuilder.java | 181 ++-------- .../ApexStandardLibraryVertexBuilder.java | 4 +- .../graph/build/GremlinVertexUtil.java | 91 +++++ .../graph/build/MethodPathBuilderVisitor.java | 46 +-- .../graph/build/StaticBlockUtil.java | 322 ++++++++++++++++++ .../com/salesforce/graph/ops/MethodUtil.java | 3 +- .../graph/symbols/ClassStaticScope.java | 57 ++-- .../StaticAndAnonymousCodeBlockTest.java | 55 +++ 10 files changed, 555 insertions(+), 215 deletions(-) create mode 100644 sfge/src/main/java/com/salesforce/graph/build/GremlinVertexUtil.java create mode 100644 sfge/src/main/java/com/salesforce/graph/build/StaticBlockUtil.java create mode 100644 sfge/src/test/java/com/salesforce/graph/build/StaticAndAnonymousCodeBlockTest.java diff --git a/sfge/src/main/java/com/salesforce/graph/ApexPath.java b/sfge/src/main/java/com/salesforce/graph/ApexPath.java index 340c19c53..e18107183 100644 --- a/sfge/src/main/java/com/salesforce/graph/ApexPath.java +++ b/sfge/src/main/java/com/salesforce/graph/ApexPath.java @@ -269,7 +269,10 @@ public void addVertices(List vertices) { !(vertices.get(0) instanceof BlockStatementVertex) && // Class Instantiation Path - !(vertices.get(0) instanceof FieldVertex)) { + !(vertices.get(0) instanceof FieldVertex) + && + // Static blocks + !(vertices.get(0) instanceof MethodCallExpressionVertex)) { throw new UnexpectedException(vertices); } this.vertices.addAll(vertices); diff --git a/sfge/src/main/java/com/salesforce/graph/Schema.java b/sfge/src/main/java/com/salesforce/graph/Schema.java index a6a07f757..fb78d6258 100644 --- a/sfge/src/main/java/com/salesforce/graph/Schema.java +++ b/sfge/src/main/java/com/salesforce/graph/Schema.java @@ -70,6 +70,12 @@ public static final class JorjeNodeType { public static final String PARENT = "Parent"; public static final String NEXT_SIBLING = "NextSibling"; + /** Mark a vertex as synthetic */ + public static final String IS_SYNTHETIC = "IsSynthetic"; /** Indicates if a method is a synthetic static block method */ public static final String IS_STATIC_BLOCK_METHOD = "IsStaticBlockMethod"; + /** Indicates if a method is a synthetic static block invoker method */ + public static final String IS_STATIC_BLOCK_INVOKER_METHOD = "IsStaticBlockInvokerMethod"; + /** Indicates if a MethodCallExpression is a synthetic invocation of static block from invoker method*/ + public static final String IS_STATIC_BLOCK_INVOCATION = "IsStaticBlockInvocation"; } diff --git a/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java b/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java index 7a3a93d64..26d696a76 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java +++ b/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java @@ -6,13 +6,12 @@ import com.salesforce.collections.CollectionUtil; import com.salesforce.exception.UnexpectedException; import com.salesforce.graph.Schema; -import com.salesforce.graph.ops.MethodUtil; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.TreeSet; import org.apache.commons.lang3.StringUtils; @@ -74,81 +73,43 @@ private void buildVertices(JorjeNode node, Vertex vNodeParam, String fileName) { vNode.property(Schema.FILE_NAME, fileName); } - final boolean needsStaticBlockSplHandling = isClintMethod(node) && containsStaticBlock(node); - Vertex vPreviousSibling = null; + Vertex vPreviousSibling = null; final List children = node.getChildren(); + final Set verticesAddressed = new HashSet<>(); + verticesAddressed.add(vNode); for (int i = 0; i < children.size(); i++) { final JorjeNode child = children.get(i); - if (needsStaticBlockSplHandling - && ASTConstants.NodeType.BLOCK_STATEMENT.equals(child.getLabel())) { - // todo: add static blocks as new methods - vPreviousSibling = addStaticBlockAsNewMethod(vNode, vPreviousSibling, child, node, i); + final Vertex vChild = g.addV(child.getLabel()).next(); + addProperties(g, child, vChild); + + // Handle static block if needed + if (StaticBlockUtil.isStaticBlockStatement(node, child)) { + final Vertex parentVertexForChild = StaticBlockUtil.createSyntheticStaticBlockMethod(g, vNode, vChild, i); + GremlinVertexUtil.addParentChildRelationship(g, parentVertexForChild, vChild); + verticesAddressed.add(parentVertexForChild); } else { - vPreviousSibling = addChildNodeToGraph(vNode, vPreviousSibling, child); + GremlinVertexUtil.addParentChildRelationship(g, vNode, vChild); } + + if (vPreviousSibling != null) { + g.addE(Schema.NEXT_SIBLING).from(vPreviousSibling).to(vChild).iterate(); + } + vPreviousSibling = vChild; + + // To save memory in the graph, don't pass the source name into recursive calls. + buildVertices(child, vChild, null); + } + // Execute afterInsert() on each vertex we addressed + for (Vertex vertex: verticesAddressed) { + afterInsert(g, node, vertex); } - afterInsert(g, node, vNode); if (rootVNode != null) { // Only call this for the root node afterFileInsert(g, rootVNode); } } - private Vertex addChildNodeToGraph(Vertex vNode, Vertex vPreviousSibling, JorjeNode child) { - Vertex vChild = g.addV(child.getLabel()).next(); - addProperties(g, child, vChild); - return addParentChildRelationship(vNode, vChild, child, vPreviousSibling); - } - - private Vertex addParentChildRelationship(Vertex parentVertex, Vertex childVertex, JorjeNode child, Vertex vPreviousSibling) { - addParentChildRelationship(parentVertex, childVertex); - if (vPreviousSibling != null) { - g.addE(Schema.NEXT_SIBLING).from(vPreviousSibling).to(childVertex).iterate(); - } - vPreviousSibling = childVertex; - // To save memory in the graph, don't pass the source name into recursive calls. - buildVertices(child, childVertex, null); - return vPreviousSibling; - } - - private void addParentChildRelationship(Vertex parentVertex, Vertex childVertex) { - // We are currently adding PARENT and CHILD, in theory the same edge could be navigated - // both ways, however - // the code looks messy when that is done. - // TODO: Determine if this causes performance or resource issues. Consider a single edge - g.addE(Schema.PARENT).from(childVertex).to(parentVertex).iterate(); - g.addE(Schema.CHILD).from(parentVertex).to(childVertex).iterate(); - } - - private Vertex addStaticBlockAsNewMethod(Vertex clintVertex, Vertex vPreviousSibling, JorjeNode child, JorjeNode clintNode, int staticBlockIndex) { - // Add properties of () to syntheticMethodVertex, - // override properties with a new name and an indicator to - // show this is a synthetic static block method - final Vertex syntheticMethodVertex = g.addV(ASTConstants.NodeType.METHOD).next(); - - final HashMap overrides = new HashMap<>(); - overrides.put(Schema.NAME, String.format(MethodUtil.SYNTHETIC_STATIC_BLOCK_METHOD_NAME, staticBlockIndex)); - addProperties(g, clintNode, syntheticMethodVertex, overrides); - - // TODO: Add vertex's parent as Synthetic method vertex's parent - // how do i get 's parent vertex? - - final Optional grandParentVertex = GremlinUtil.getParent(g, clintVertex); - if (grandParentVertex.isEmpty()) { - throw new UnexpectedException("Did not expect () to not have a parent vertex. clintVertex=" + clintVertex); - } - - addParentChildRelationship(grandParentVertex.get(), syntheticMethodVertex); - - // Create a vertex for BlockStatement - final Vertex vChild = g.addV(child.getLabel()).next(); - addProperties(g, child, vChild); - - // add BlockStatement as child of SyntheticMethodVertex - return addParentChildRelationship(syntheticMethodVertex, vChild, child, vPreviousSibling); - } - /** * Adds edges to Method vertices after they are inserted. TODO: Replace this with a listener or * visitor pattern for more general purpose solutions @@ -156,7 +117,9 @@ private Vertex addStaticBlockAsNewMethod(Vertex clintVertex, Vertex vPreviousSib private final void afterInsert(GraphTraversalSource g, JorjeNode node, Vertex vNode) { if (node.getLabel().equals(ASTConstants.NodeType.METHOD)) { MethodPathBuilderVisitor.apply(g, vNode); - } + } else if (ROOT_VERTICES.contains(node.getLabel())) { + StaticBlockUtil.createSyntheticStaticBlockInvocation(g, vNode); + } } /** @@ -169,10 +132,6 @@ protected void afterFileInsert(GraphTraversalSource g, Vertex vNode) { } protected void addProperties(GraphTraversalSource g, JorjeNode node, Vertex vNode) { - addProperties(g, node, vNode, new HashMap<>()); - } - - protected void addProperties(GraphTraversalSource g, JorjeNode node, Vertex vNode, HashMap overrides) { GraphTraversal traversal = g.V(vNode.id()); TreeSet previouslyInsertedKeys = CollectionUtil.newTreeSet(); @@ -180,15 +139,11 @@ protected void addProperties(GraphTraversalSource g, JorjeNode node, Vertex vNod String key = entry.getKey(); Object value = entry.getValue(); value = adjustPropertyValue(node, key, value); - // Override value for key if requested - if (overrides.containsKey(key)) { - value = overrides.get(key); - } - addProperty(previouslyInsertedKeys, traversal, key, value); + GremlinVertexUtil.addProperty(previouslyInsertedKeys, traversal, key, value); } - for (Map.Entry entry : getAdditionalProperties(node, overrides).entrySet()) { - addProperty(previouslyInsertedKeys, traversal, entry.getKey(), entry.getValue()); + for (Map.Entry entry : getAdditionalProperties(node).entrySet()) { + GremlinVertexUtil.addProperty(previouslyInsertedKeys, traversal, entry.getKey(), entry.getValue()); } // Commit the changes. @@ -201,78 +156,8 @@ protected Object adjustPropertyValue(JorjeNode node, String key, Object value) { } /** Add additional properties to the node that aren't present in the orginal AST */ - protected Map getAdditionalProperties(JorjeNode node, HashMap overrides) { - final HashMap returnMap = new HashMap<>(); - - // FIXME: very crude - if (!overrides.isEmpty()) { - returnMap.put(Schema.IS_STATIC_BLOCK_METHOD, Boolean.valueOf(true)); - } - return returnMap; - } - - /** - * @return true if given node represents () - */ - private boolean isClintMethod(JorjeNode node) { - return ASTConstants.NodeType.METHOD.equals(node.getLabel()) - && MethodUtil.STATIC_CONSTRUCTOR_CANONICAL_NAME.equals(node.getProperties().get(Schema.NAME)); + protected Map getAdditionalProperties(JorjeNode node) { + return new HashMap<>(); } - /** - * @return true if () node contains any static block definitions - */ - private boolean containsStaticBlock(JorjeNode node) { - for (JorjeNode childNode : node.getChildren()) { - if (ASTConstants.NodeType.BLOCK_STATEMENT.equals(childNode.getLabel())) { - return true; - } - } - return false; - } - - /** Add a property to the traversal, throwing an exception if any keys are duplicated. */ - protected void addProperty( - TreeSet previouslyInsertedKeys, - GraphTraversal traversal, - String keyParam, - Object value) { - final String key = keyParam.intern(); - - if (!previouslyInsertedKeys.add(key)) { - throw new UnexpectedException(key); - } - - if (value instanceof List) { - List list = (List) value; - // Convert ArrayList to an Array. There seems to be a Tinkerpop bug where a singleton - // ArrayList - // isn't properly stored. Remote graphs also have issues with empty lists, so don't add - // an empty list. - if (!list.isEmpty()) { - traversal.property(key, list.toArray()); - } else { - // return so that we don't store a case insensitive version - return; - } - } else if (value instanceof Boolean - || value instanceof Double - || value instanceof Integer - || value instanceof Long - || value instanceof String) { - traversal.property(key, value); - } else { - if (value != null) { - if (!(value instanceof Enum)) { - if (LOGGER.isWarnEnabled()) { - LOGGER.warn( - "Using string for value. type=" + value.getClass().getSimpleName()); - } - } - final String strValue = String.valueOf(value).intern(); - traversal.property(key, strValue); - } - } - CaseSafePropertyUtil.addCaseSafeProperty(traversal, key, value); - } } diff --git a/sfge/src/main/java/com/salesforce/graph/build/ApexStandardLibraryVertexBuilder.java b/sfge/src/main/java/com/salesforce/graph/build/ApexStandardLibraryVertexBuilder.java index beb8cda27..a821e3804 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/ApexStandardLibraryVertexBuilder.java +++ b/sfge/src/main/java/com/salesforce/graph/build/ApexStandardLibraryVertexBuilder.java @@ -73,8 +73,8 @@ protected Object adjustPropertyValue(JorjeNode node, String key, Object value) { } @Override - protected Map getAdditionalProperties(JorjeNode node, HashMap overrides) { - Map result = super.getAdditionalProperties(node, overrides); + protected Map getAdditionalProperties(JorjeNode node) { + Map result = super.getAdditionalProperties(node); // Mark all nodes as being from the Standard library // TODO: Reduce number of nodes where this is set diff --git a/sfge/src/main/java/com/salesforce/graph/build/GremlinVertexUtil.java b/sfge/src/main/java/com/salesforce/graph/build/GremlinVertexUtil.java new file mode 100644 index 000000000..4de95cd58 --- /dev/null +++ b/sfge/src/main/java/com/salesforce/graph/build/GremlinVertexUtil.java @@ -0,0 +1,91 @@ +package com.salesforce.graph.build; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import com.salesforce.exception.UnexpectedException; +import com.salesforce.graph.Schema; + +import java.util.List; +import java.util.Optional; +import java.util.TreeSet; + +/** + * Handles common operations performed while creating vertices on Graph + */ +public final class GremlinVertexUtil { + private static final Logger LOGGER = LogManager.getLogger(GremlinVertexUtil.class); + private GremlinVertexUtil(){} + + /** + * Create parent-child relationship between vertices on graph + */ + static void addParentChildRelationship(GraphTraversalSource g, Vertex parentVertex, Vertex childVertex) { + // We are currently adding PARENT and CHILD, in theory the same edge could be navigated + // both ways, however + // the code looks messy when that is done. + // TODO: Determine if this causes performance or resource issues. Consider a single edge + g.addE(Schema.PARENT).from(childVertex).to(parentVertex).iterate(); + g.addE(Schema.CHILD).from(parentVertex).to(childVertex).iterate(); + } + + /** + * Make a synthetic vertex a sibling of an existing vertex on graph + */ + static void makeSiblings(GraphTraversalSource g, Vertex vertex, Vertex syntheticVertex) { + // Get parent node of vertex + final Optional rootVertex = GremlinUtil.getParent(g, vertex); + if (rootVertex.isEmpty()) { + throw new UnexpectedException("Did not expect vertex to not have a parent vertex. vertex=" + vertex); + } + + addParentChildRelationship(g, rootVertex.get(), syntheticVertex); + } + + /** Add a property to the traversal, throwing an exception if any keys are duplicated. */ + protected static void addProperty( + TreeSet previouslyInsertedKeys, + GraphTraversal traversal, + String keyParam, + Object value) { + final String key = keyParam.intern(); + + if (!previouslyInsertedKeys.add(key)) { + throw new UnexpectedException(key); + } + + if (value instanceof List) { + List list = (List) value; + // Convert ArrayList to an Array. There seems to be a Tinkerpop bug where a singleton + // ArrayList + // isn't properly stored. Remote graphs also have issues with empty lists, so don't add + // an empty list. + if (!list.isEmpty()) { + traversal.property(key, list.toArray()); + } else { + // return so that we don't store a case insensitive version + return; + } + } else if (value instanceof Boolean + || value instanceof Double + || value instanceof Integer + || value instanceof Long + || value instanceof String) { + traversal.property(key, value); + } else { + if (value != null) { + if (!(value instanceof Enum)) { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn( + "Using string for value. type=" + value.getClass().getSimpleName()); + } + } + final String strValue = String.valueOf(value).intern(); + traversal.property(key, strValue); + } + } + CaseSafePropertyUtil.addCaseSafeProperty(traversal, key, value); + } +} diff --git a/sfge/src/main/java/com/salesforce/graph/build/MethodPathBuilderVisitor.java b/sfge/src/main/java/com/salesforce/graph/build/MethodPathBuilderVisitor.java index 805a13443..33c53ae82 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/MethodPathBuilderVisitor.java +++ b/sfge/src/main/java/com/salesforce/graph/build/MethodPathBuilderVisitor.java @@ -24,7 +24,6 @@ /** Visits a method and draws all control flow edges. */ public class MethodPathBuilderVisitor { private static final Logger LOGGER = LogManager.getLogger(MethodPathBuilderVisitor.class); - private static final String NAME_PROPERTY = "Name_CaseSafe"; // FIXME: identify where this gets created for real /** Load the vertices as SFVertices and log more information. Will impact performance. */ private static boolean SF_VERTEX_LOGGING = true; @@ -90,47 +89,16 @@ public static void apply(GraphTraversalSource g, Vertex methodVertex) { throw new UnexpectedException(methodVertex); } - // Handle () methods - if (methodVertex.value(NAME_PROPERTY).equals(MethodUtil.STATIC_CONSTRUCTOR_CANONICAL_NAME)) { - handleClintMethod(g, methodVertex); - } else { - - Vertex blockStatement = - GremlinUtil.getOnlyChild(g, methodVertex, NodeType.BLOCK_STATEMENT).orElse(null); - // This can be null in cases such as default constructors that don't have an implementation - if (blockStatement != null) { - MethodPathBuilderVisitor visitor = new MethodPathBuilderVisitor(g); - visitor._visit(blockStatement, methodVertex, true); - } - } + Vertex blockStatement = + GremlinUtil.getOnlyChild(g, methodVertex, NodeType.BLOCK_STATEMENT).orElse(null); + // This can be null in cases such as default constructors that don't have an implementation + if (blockStatement != null) { + MethodPathBuilderVisitor visitor = new MethodPathBuilderVisitor(g); + visitor._visit(blockStatement, methodVertex, true); + } } - /** - * Handles default method generated by jorje for every class. - * Block statements under are static code blocks such as - * class MyClass { - * static { - * System.debug('hello'); - * } - * } - * @param g reference to graph - * @param methodVertex represents vertex of () - */ - private static void handleClintMethod(GraphTraversalSource g, Vertex methodVertex) { - final List blockStatements = GremlinUtil.getChildren(g, methodVertex, NodeType.BLOCK_STATEMENT); - for (Vertex blockStatement : blockStatements) { - // We should handle each block statement one after another, as if they were in the same method - // since they will all be executed one after another. - // TODO: Confirm that static blocks get executed in the same order they are listed in the class - if (blockStatement != null) { -// final Vertex staticBlockVertex = CloneUtil.clone(methodVertex); - final MethodPathBuilderVisitor visitor = new MethodPathBuilderVisitor(g); - visitor._visit(blockStatement, methodVertex, true); - } - } - } - private void _visit(Vertex vertex, Vertex parent, boolean lastChild) { try { String label = vertex.label(); diff --git a/sfge/src/main/java/com/salesforce/graph/build/StaticBlockUtil.java b/sfge/src/main/java/com/salesforce/graph/build/StaticBlockUtil.java new file mode 100644 index 000000000..78ccbef2a --- /dev/null +++ b/sfge/src/main/java/com/salesforce/graph/build/StaticBlockUtil.java @@ -0,0 +1,322 @@ +package com.salesforce.graph.build; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.structure.Vertex; + +import com.salesforce.apex.jorje.ASTConstants; +import com.salesforce.apex.jorje.JorjeNode; +import com.salesforce.collections.CollectionUtil; +import com.salesforce.exception.ProgrammingException; +import com.salesforce.graph.Schema; +import com.salesforce.graph.ops.MethodUtil; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; + +/** + * Handles creation of synthetic methods and vertices to gracefully invoke static code blocks. + * + * + * Consider this example: + * class StaticBlockClass { + * static { + * System.debug("inside static block 1"); + * } + * static { + * System.debug("inside static block 2"); + * } + * } + * In Jorje's compilation structure, static blocks are represented like this: + * class StaticBlockClass { + * private static void () { + * { + * System.debug("inside static block 1"); + * } + * { + * System.debug("inside static block 2"); + * } + * } + * } + * + * Having multiple block statements inside a method breaks SFGE's makes handling code blocks in () + * impossible. As an alternative, we are creating synthetic vertices in the Graph to represent the static + * blocks as individual methods. We also create one top-level synthetic method ("StaticBlockInvoker") that invokes individual static + * block methods. While creating static scope for this class, we should invoke the method call expressions + * inside the top-level synthetic method. + * + * New structure looks like this: + * class StaticBlockClass { + * private static void SyntheticStaticBlock_1() { + * System.debug("inside static block 1"); + * } + * + * private static void SyntheticStaticBlock_2() { + * System.debug("inside static block 2"); + * } + * + * private static void StaticBlockInvoker() { + * SyntheticStaticBlock_1(); + * SyntheticStaticBlock_2(); + * } + * } + * + */ +public final class StaticBlockUtil { + private static final Logger LOGGER = LogManager.getLogger(StaticBlockUtil.class); + + private static final String SYNTHETIC_STATIC_BLOCK_METHOD_NAME = "SyntheticStaticBlock_%d"; + private static final String STATIC_BLOCK_INVOKER_METHOD = "StaticBlockInvoker"; + + private StaticBlockUtil() {} + + /** + * Creates a synthetic method vertex to represent a static code block + * @param g traversal graph + * @param clintVertex () method's vertex + * @param blockStatementVertex static block originally placed by Jorje under () + * @param staticBlockIndex index to use for name uniqueness - + * TODO: this index is currently just the child count index and does not + * necessarily follow sequence + * @return new synthetic method vertex with name SyntheticStaticBlock_%d, which will be the parent + * for blockStatementVertex, and a sibling of () + */ + public static Vertex createSyntheticStaticBlockMethod(GraphTraversalSource g, Vertex clintVertex, Vertex blockStatementVertex, int staticBlockIndex) { + final Vertex syntheticMethodVertex = g.addV(ASTConstants.NodeType.METHOD).next(); + final String definingType = clintVertex.value(Schema.DEFINING_TYPE); + addSyntheticStaticBlockMethodProperties(g, definingType, syntheticMethodVertex, staticBlockIndex); + + final Vertex modifierNodeVertex = g.addV(ASTConstants.NodeType.MODIFIER_NODE).next(); + addStaticModifierProperties(g, definingType, modifierNodeVertex); + GremlinVertexUtil.addParentChildRelationship(g, syntheticMethodVertex, modifierNodeVertex); + + GremlinVertexUtil.makeSiblings(g, clintVertex, syntheticMethodVertex); + + return syntheticMethodVertex; + } + + /** + * If rootNode contains synthetic static block methods (created through {@link #createSyntheticStaticBlockMethod}), + * adds a top-level synthetic method that invokes each static block method. + */ + public static void createSyntheticStaticBlockInvocation(GraphTraversalSource g, Vertex rootNode) { + // Check if root node contains any static block methods + final List staticBlockMethods = getStaticBlockMethods(g, rootNode); + + // Create static block invocation method + if (!staticBlockMethods.isEmpty()) { + final String definingType = rootNode.value(Schema.DEFINING_TYPE); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Creating synthetic invocation method for {} to invoke {} static block(s)", definingType, staticBlockMethods.size()); + } + + final Vertex invokerMethodVertex = createSyntheticInvocationsMethod(g, rootNode, staticBlockMethods, definingType); + MethodPathBuilderVisitor.apply(g, invokerMethodVertex); + } + } + + static boolean isStaticBlockStatement(JorjeNode node, JorjeNode child) { + return isClintMethod(node) + && containsStaticBlock(node) + && isBlockStatement(child); + } + + private static Vertex createSyntheticInvocationsMethod(GraphTraversalSource g, Vertex rootNode, List staticBlockMethods, String definingType) { + // Create new synthetic method StaticBlockInvoker to invoke each synthetic static block method + final Vertex invokerMethodVertex = g.addV(ASTConstants.NodeType.METHOD).next(); + addStaticBlockInvokerProperties(g, definingType, invokerMethodVertex); + GremlinVertexUtil.addParentChildRelationship(g, rootNode, invokerMethodVertex); + + final Vertex modifierNodeVertex = g.addV(ASTConstants.NodeType.MODIFIER_NODE).next(); + addStaticModifierProperties(g, definingType, modifierNodeVertex); + GremlinVertexUtil.addParentChildRelationship(g, invokerMethodVertex, modifierNodeVertex); + + // Create synthetic BlockStatement inside StaticBlockInvoker to hold the method invocations + final Vertex blockStatementInInvoker = g.addV(ASTConstants.NodeType.BLOCK_STATEMENT).next(); + addBlockStatementProperties(g, definingType, blockStatementInInvoker); + GremlinVertexUtil.addParentChildRelationship(g, invokerMethodVertex, blockStatementInInvoker); + + for (int i = 0; i < staticBlockMethods.size(); i++) { + final Vertex staticBlockMethod = staticBlockMethods.get(i); + boolean isLastMethod = i == staticBlockMethods.size() - 1; + // Create a method call invocation to the synthetic static block method + final Vertex exprStaticBlockMethodCall = createMethodCallExpression(g, definingType, staticBlockMethod, isLastMethod); + + // Add the expression statement containing the method call inside StaticBlockInvoker's block statement + GremlinVertexUtil.addParentChildRelationship(g, blockStatementInInvoker, exprStaticBlockMethodCall); + } + + return invokerMethodVertex; + } + + private static Vertex createMethodCallExpression(GraphTraversalSource g, String definingType, Vertex staticBlockMethod, boolean isLastMethod) { + final Vertex expressionStmt = g.addV(ASTConstants.NodeType.EXPRESSION_STATEMENT).next(); + addExpressionStmtProperties(g, definingType, expressionStmt, isLastMethod); + + // Create new MethodCallExpression for the synthetic static block method + final Vertex staticBlockMethodCall = g.addV(ASTConstants.NodeType.METHOD_CALL_EXPRESSION).next(); + addMethodCallExpressionProperties(g, definingType, staticBlockMethod, staticBlockMethodCall); + GremlinVertexUtil.addParentChildRelationship(g, expressionStmt, staticBlockMethodCall); + + // Create EmptyReferenceExpression inside MethodCallExpression + final Vertex emptyMethodReference = g.addV(ASTConstants.NodeType.EMPTY_REFERENCE_EXPRESSION).next(); + addEmptyMethodReferenceProperties(g, definingType, emptyMethodReference); + GremlinVertexUtil.addParentChildRelationship(g, staticBlockMethodCall, emptyMethodReference); + return expressionStmt; + } + + private static void addExpressionStmtProperties(GraphTraversalSource g, String definingType, Vertex expressionStmt, boolean isLastMethod) { + verifyType(expressionStmt, ASTConstants.NodeType.EXPRESSION_STATEMENT); + + final Map properties = new HashMap<>(); + properties.put(Schema.IS_SYNTHETIC, true); + properties.put(Schema.DEFINING_TYPE, definingType); + properties.put(Schema.FIRST_CHILD, true); + properties.put(Schema.LAST_CHILD, true); + properties.put(Schema.CHILD_INDEX, 0); + + addProperties(g, expressionStmt, properties); + } + + private static void addEmptyMethodReferenceProperties(GraphTraversalSource g, String definingType, Vertex emptyMethodReference) { + verifyType(emptyMethodReference, ASTConstants.NodeType.EMPTY_REFERENCE_EXPRESSION); + + final Map properties = new HashMap<>(); + properties.put(Schema.IS_SYNTHETIC, true); + properties.put(Schema.DEFINING_TYPE, definingType); + properties.put(Schema.FIRST_CHILD, true); + properties.put(Schema.LAST_CHILD, true); + properties.put(Schema.CHILD_INDEX, 0); + + addProperties(g, emptyMethodReference, properties); + } + + private static void addMethodCallExpressionProperties(GraphTraversalSource g, String definingType, Vertex staticBlockMethod, Vertex staticBlockMethodCall) { + verifyType(staticBlockMethodCall, ASTConstants.NodeType.METHOD_CALL_EXPRESSION); + + final Map properties = new HashMap<>(); + properties.put(Schema.IS_SYNTHETIC, true); + properties.put(Schema.DEFINING_TYPE, definingType); + properties.put(Schema.METHOD_NAME, staticBlockMethod.value(Schema.NAME)); + properties.put(Schema.FULL_METHOD_NAME, staticBlockMethod.value(Schema.NAME)); + properties.put(Schema.IS_STATIC_BLOCK_INVOCATION, true); + properties.put(Schema.FIRST_CHILD, true); + properties.put(Schema.LAST_CHILD, true); + properties.put(Schema.CHILD_INDEX, 0); + + addProperties(g, staticBlockMethodCall, properties); + } + + private static void addBlockStatementProperties(GraphTraversalSource g, String definingType, Vertex blockStatementInInvoker) { + verifyType(blockStatementInInvoker, ASTConstants.NodeType.BLOCK_STATEMENT); + + final Map properties = new HashMap<>(); + properties.put(Schema.IS_SYNTHETIC, true); + properties.put(Schema.DEFINING_TYPE, definingType); + properties.put(Schema.CHILD_INDEX, 1); + properties.put(Schema.FIRST_CHILD, false); + properties.put(Schema.LAST_CHILD, true); + + addProperties(g, blockStatementInInvoker, properties); + } + + private static void addStaticModifierProperties(GraphTraversalSource g, String definingType, Vertex modifier) { + verifyType(modifier, ASTConstants.NodeType.MODIFIER_NODE); + + final Map properties = new HashMap<>(); + properties.put(Schema.IS_SYNTHETIC, true); + properties.put(Schema.DEFINING_TYPE, definingType); + properties.put(Schema.STATIC, true); + properties.put(Schema.ABSTRACT, false); + properties.put(Schema.GLOBAL, false); + properties.put(Schema.MODIFIERS, 8); // Apparently, static methods have modifiers 8 + properties.put(Schema.FIRST_CHILD, true); + properties.put(Schema.LAST_CHILD, false); + properties.put(Schema.CHILD_INDEX, 0); + + addProperties(g, modifier, properties); + } + + private static void addStaticBlockInvokerProperties(GraphTraversalSource g, String definingType, Vertex staticBlockInvokerVertex) { + verifyType(staticBlockInvokerVertex, ASTConstants.NodeType.METHOD); + final Map properties = new HashMap<>(); + properties.put(Schema.NAME, STATIC_BLOCK_INVOKER_METHOD); + properties.put(Schema.IS_STATIC_BLOCK_INVOKER_METHOD, true); + addCommonSynthMethodProperties(g, definingType, staticBlockInvokerVertex, properties); + } + + private static void addSyntheticStaticBlockMethodProperties(GraphTraversalSource g, String definingType, Vertex syntheticMethodVertex, int staticBlockIndex) { + verifyType(syntheticMethodVertex, ASTConstants.NodeType.METHOD); + final Map properties = new HashMap<>(); + properties.put(Schema.NAME, String.format(SYNTHETIC_STATIC_BLOCK_METHOD_NAME, staticBlockIndex)); + properties.put(Schema.IS_STATIC_BLOCK_METHOD, true); + + addCommonSynthMethodProperties(g, definingType, syntheticMethodVertex, properties); + } + + private static void addCommonSynthMethodProperties(GraphTraversalSource g, String definingType, Vertex staticBlockInvokerVertex, Map properties) { + properties.put(Schema.ARITY, 0); + properties.put(Schema.CONSTRUCTOR, false); + properties.put(Schema.DEFINING_TYPE, definingType); + properties.put(Schema.IS_SYNTHETIC, true); + properties.put(Schema.RETURN_TYPE, ASTConstants.TYPE_VOID); + + + addProperties(g, staticBlockInvokerVertex, properties); + } + + private static void addProperties(GraphTraversalSource g, Vertex vertex, Map properties) { + final TreeSet previouslyInsertedKeys = CollectionUtil.newTreeSet(); + final GraphTraversal traversal = g.V(vertex.id()); + + for (Map.Entry entry : properties.entrySet()) { + GremlinVertexUtil.addProperty(previouslyInsertedKeys, traversal, entry.getKey(), entry.getValue()); + previouslyInsertedKeys.add(entry.getKey()); + } + + // Commit the changes. + traversal.next(); + } + + /** + * @return true if given node represents () + */ + private static boolean isClintMethod(JorjeNode node) { + return ASTConstants.NodeType.METHOD.equals(node.getLabel()) + && MethodUtil.STATIC_CONSTRUCTOR_CANONICAL_NAME.equals(node.getProperties().get(Schema.NAME)); + } + + /** + * @return true if () node contains any static block definitions + */ + private static boolean containsStaticBlock(JorjeNode node) { + for (JorjeNode childNode : node.getChildren()) { + if (ASTConstants.NodeType.BLOCK_STATEMENT.equals(childNode.getLabel())) { + return true; + } + } + return false; + } + + private static List getStaticBlockMethods(GraphTraversalSource g, Vertex root) { + return g.V(root) + .out(Schema.CHILD) + .hasLabel(ASTConstants.NodeType.METHOD) + .has(Schema.IS_STATIC_BLOCK_METHOD, true) + .toList(); + } + + private static boolean isBlockStatement(JorjeNode child) { + return ASTConstants.NodeType.BLOCK_STATEMENT.equals(child.getLabel()); + } + + private static void verifyType(Vertex vertex, String expectedType) { + if (! expectedType.equals(vertex.label())) { + throw new ProgrammingException("Incorrect vertex type: " + vertex.label()); + } + } +} diff --git a/sfge/src/main/java/com/salesforce/graph/ops/MethodUtil.java b/sfge/src/main/java/com/salesforce/graph/ops/MethodUtil.java index 8a81a5830..bcc92ee77 100644 --- a/sfge/src/main/java/com/salesforce/graph/ops/MethodUtil.java +++ b/sfge/src/main/java/com/salesforce/graph/ops/MethodUtil.java @@ -83,9 +83,8 @@ public final class MethodUtil { private static final String PAGE_REFERENCE = "PageReference"; public static final String INSTANCE_CONSTRUCTOR_CANONICAL_NAME = ""; public static final String STATIC_CONSTRUCTOR_CANONICAL_NAME = ""; - public static final String SYNTHETIC_STATIC_BLOCK_METHOD_NAME = "SyntheticStaticBlock_%d"; - public static List getTargetedMethods( + public static List getTargetedMethods( GraphTraversalSource g, List targets) { // The targets passed into this method should exclusively be ones that target specific // methods instead of files. diff --git a/sfge/src/main/java/com/salesforce/graph/symbols/ClassStaticScope.java b/sfge/src/main/java/com/salesforce/graph/symbols/ClassStaticScope.java index 984016cd2..036857020 100644 --- a/sfge/src/main/java/com/salesforce/graph/symbols/ClassStaticScope.java +++ b/sfge/src/main/java/com/salesforce/graph/symbols/ClassStaticScope.java @@ -15,15 +15,18 @@ import com.salesforce.graph.vertex.BaseSFVertex; import com.salesforce.graph.vertex.BlockStatementVertex; import com.salesforce.graph.vertex.FieldDeclarationVertex; +import com.salesforce.graph.vertex.MethodCallExpressionVertex; import com.salesforce.graph.vertex.MethodVertex; import com.salesforce.graph.vertex.SFVertexFactory; import com.salesforce.graph.vertex.UserClassVertex; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Optional; import org.apache.tinkerpop.gremlin.process.traversal.Order; import org.apache.tinkerpop.gremlin.process.traversal.Scope; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.structure.T; /** Used when invoking a static method on a class. */ public final class ClassStaticScope extends AbstractClassScope @@ -110,9 +113,9 @@ private static List getFieldDeclarations( return results; } - private static List getStaticBlocks( + private static List getStaticBlocks( GraphTraversalSource g, UserClassVertex userClass) { - List results = new ArrayList<>(); + List results = new ArrayList<>(); String superClassName = userClass.getSuperClassName().orElse(null); if (superClassName != null) { @@ -122,24 +125,27 @@ private static List getStaticBlocks( } } - results.addAll( - SFVertexFactory.loadVertices( - g, - g.V(userClass.getId()) - .out(Schema.CHILD) - .hasLabel(ASTConstants.NodeType.METHOD) - .has(Schema.IS_STATIC_BLOCK_METHOD, true) - .order(Scope.global) - .by(Schema.CHILD_INDEX, Order.asc) - /*.out(Schema.CHILD) - .hasLabel(ASTConstants.NodeType.BLOCK_STATEMENT) - .order(Scope.global) - .by(Schema.CHILD_INDEX, Order.asc)*/)); + results.addAll(SFVertexFactory.loadVertices( + g, + g.V(userClass.getId()) + .out(Schema.CHILD) + .hasLabel(NodeType.METHOD) + .has(Schema.IS_STATIC_BLOCK_INVOKER_METHOD, true) + .order(Scope.global) + .by(Schema.CHILD_INDEX, Order.asc) + .out(Schema.CHILD) + .hasLabel(ASTConstants.NodeType.BLOCK_STATEMENT) + .order(Scope.global) + .by(Schema.CHILD_INDEX, Order.asc) + .out(Schema.CHILD) + .hasLabel(NodeType.EXPRESSION_STATEMENT) + .out(Schema.CHILD) + .hasLabel(NodeType.METHOD_CALL_EXPRESSION))); return results; } - /** + /** * Returns a path that represents the static properties defined by the class. The following * example would contain a path for 'MyClass' that contains the Field and FieldDeclarations for * 's'. {@code @@ -156,20 +162,25 @@ public static Optional getInitializationPath( GraphTraversalSource g, String classname) { ClassStaticScope classStaticScope = ClassStaticScope.get(g, classname); List vertices = new ArrayList<>(); - List methodVertices = new ArrayList<>(); vertices.addAll(classStaticScope.getFields()); vertices.addAll(getFieldDeclarations(g, classStaticScope.userClass)); - methodVertices.addAll(getStaticBlocks(g, classStaticScope.userClass)); - if (vertices.isEmpty() && methodVertices.isEmpty()) { + vertices.addAll(getStaticBlocks(g, classStaticScope.userClass)); + if (vertices.isEmpty()) { return Optional.empty(); } else { - ArrayList> apexPaths = new ArrayList<>(); - for (MethodVertex methodVertex : methodVertices) { - apexPaths.add(new ApexPath(methodVertex)); - } ApexPath apexPath = new ApexPath(null); apexPath.addVertices(vertices); return Optional.of(apexPath); } } + +// private static MethodCallExpressionVertex createSyntheticInvocation(MethodVertex.StaticBlockVertex methodVertex) { +// final HashMap map = new HashMap<>(); +// map.put(T.id, Long.valueOf(-1)); +// map.put(T.label, NodeType.METHOD_CALL_EXPRESSION); +// map.put(Schema.METHOD_NAME, methodVertex.getName()); +// map.put(Schema.DEFINING_TYPE, methodVertex.getDefiningType()); +// map.put(Schema.STATIC, Boolean.valueOf(true)); +// return new MethodCallExpressionVertex(map); +// } } diff --git a/sfge/src/test/java/com/salesforce/graph/build/StaticAndAnonymousCodeBlockTest.java b/sfge/src/test/java/com/salesforce/graph/build/StaticAndAnonymousCodeBlockTest.java new file mode 100644 index 000000000..e31367cbf --- /dev/null +++ b/sfge/src/test/java/com/salesforce/graph/build/StaticAndAnonymousCodeBlockTest.java @@ -0,0 +1,55 @@ +package com.salesforce.graph.build; + +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import com.salesforce.TestRunner; +import com.salesforce.TestUtil; +import com.salesforce.graph.symbols.apex.ApexStringValue; +import com.salesforce.graph.symbols.apex.ApexValue; +import com.salesforce.graph.visitor.SystemDebugAccumulator; +import com.salesforce.matchers.TestRunnerMatcher; + +import java.util.List; +import java.util.Optional; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; + +public class StaticAndAnonymousCodeBlockTest extends MethodPathBuilderTest { + @Test + public void testStaticBlock() { + String[] sourceCode = + { + "public class StaticBlockClass {\n" + + " static {\n" + + " System.debug('static block');\n" + + " }\n" + + " public static String foo() {\n" + + " return 'hello';\n" + + "}\n" + + "}", + "public class MyClass {\n" + + " public void doSomething() {\n" + + " StaticBlockClass sb = new StaticBlockClass();\n" + + " System.debug(sb.foo());\n" + + " }\n" + + "}" + }; + +// TestUtil.buildGraph(g, sourceCode); + + TestRunner.Result result = TestRunner.walkPath(g, + sourceCode); + SystemDebugAccumulator visitor = result.getVisitor(); + final List>> allResults = visitor.getAllResults(); + + assertThat(allResults, hasSize(2)); + + // verify that static block is invoked first + ApexStringValue stringValue = (ApexStringValue) allResults.get(0).get(); + assertThat(stringValue.getValue().get(), equalTo("static block")); + + } +} From 81ba383b14239f0666453974075a23e01244484a Mon Sep 17 00:00:00 2001 From: Roopa Mohan Date: Thu, 30 Jun 2022 10:50:15 -0700 Subject: [PATCH 4/8] Invoking static block from static class scope --- sfge/src/main/java/com/salesforce/Main.java | 3 +- .../java/com/salesforce/graph/ApexPath.java | 6 +- .../java/com/salesforce/graph/Schema.java | 19 +- .../build/AbstractApexVertexBuilder.java | 82 +-- .../ApexStandardLibraryVertexBuilder.java | 1 - .../salesforce/graph/build/GremlinUtil.java | 29 +- .../graph/build/GremlinVertexUtil.java | 72 +- .../graph/build/MethodPathBuilderVisitor.java | 7 +- .../graph/build/StaticBlockUtil.java | 616 +++++++++--------- .../com/salesforce/graph/ops/MethodUtil.java | 2 +- .../graph/ops/expander/ApexPathExpander.java | 16 +- .../graph/symbols/ClassStaticScope.java | 15 +- .../salesforce/graph/vertex/MethodVertex.java | 73 ++- .../graph/build/GraphBuildTestUtil.java | 61 ++ .../graph/build/MethodPathBuilderTest.java | 160 ++--- .../StaticAndAnonymousCodeBlockTest.java | 55 -- .../graph/build/StaticBlockUtilTest.java | 117 ++++ .../StaticCodeBlockInvocationTest.java | 300 +++++++++ 18 files changed, 1018 insertions(+), 616 deletions(-) create mode 100644 sfge/src/test/java/com/salesforce/graph/build/GraphBuildTestUtil.java delete mode 100644 sfge/src/test/java/com/salesforce/graph/build/StaticAndAnonymousCodeBlockTest.java create mode 100644 sfge/src/test/java/com/salesforce/graph/build/StaticBlockUtilTest.java create mode 100644 sfge/src/test/java/com/salesforce/graph/symbols/StaticCodeBlockInvocationTest.java diff --git a/sfge/src/main/java/com/salesforce/Main.java b/sfge/src/main/java/com/salesforce/Main.java index 9493ac13f..a4ea7e1f7 100644 --- a/sfge/src/main/java/com/salesforce/Main.java +++ b/sfge/src/main/java/com/salesforce/Main.java @@ -132,7 +132,8 @@ private int execute(String... args) { return INTERNAL_ERROR; } catch (UnexpectedException ex) { LOGGER.error("Unexpected exception while loading graph", ex); - System.err.println("Unexpected exception while loading graph. See logs for more information."); + System.err.println( + "Unexpected exception while loading graph. See logs for more information."); return INTERNAL_ERROR; } diff --git a/sfge/src/main/java/com/salesforce/graph/ApexPath.java b/sfge/src/main/java/com/salesforce/graph/ApexPath.java index e18107183..08dc08eb7 100644 --- a/sfge/src/main/java/com/salesforce/graph/ApexPath.java +++ b/sfge/src/main/java/com/salesforce/graph/ApexPath.java @@ -270,9 +270,9 @@ public void addVertices(List vertices) { && // Class Instantiation Path !(vertices.get(0) instanceof FieldVertex) - && - // Static blocks - !(vertices.get(0) instanceof MethodCallExpressionVertex)) { + && + // Static blocks + !(vertices.get(0) instanceof MethodCallExpressionVertex)) { throw new UnexpectedException(vertices); } this.vertices.addAll(vertices); diff --git a/sfge/src/main/java/com/salesforce/graph/Schema.java b/sfge/src/main/java/com/salesforce/graph/Schema.java index fb78d6258..26cd02b95 100644 --- a/sfge/src/main/java/com/salesforce/graph/Schema.java +++ b/sfge/src/main/java/com/salesforce/graph/Schema.java @@ -70,12 +70,15 @@ public static final class JorjeNodeType { public static final String PARENT = "Parent"; public static final String NEXT_SIBLING = "NextSibling"; - /** Mark a vertex as synthetic */ - public static final String IS_SYNTHETIC = "IsSynthetic"; - /** Indicates if a method is a synthetic static block method */ - public static final String IS_STATIC_BLOCK_METHOD = "IsStaticBlockMethod"; - /** Indicates if a method is a synthetic static block invoker method */ - public static final String IS_STATIC_BLOCK_INVOKER_METHOD = "IsStaticBlockInvokerMethod"; - /** Indicates if a MethodCallExpression is a synthetic invocation of static block from invoker method*/ - public static final String IS_STATIC_BLOCK_INVOCATION = "IsStaticBlockInvocation"; + /** Mark a vertex as synthetic */ + public static final String IS_SYNTHETIC = "IsSynthetic"; + /** Indicates if a method is a synthetic static block method */ + public static final String IS_STATIC_BLOCK_METHOD = "IsStaticBlockMethod"; + /** Indicates if a method is a synthetic static block invoker method */ + public static final String IS_STATIC_BLOCK_INVOKER_METHOD = "IsStaticBlockInvokerMethod"; + /** + * Indicates if a MethodCallExpression is a synthetic invocation of static block from invoker + * method + */ + public static final String IS_STATIC_BLOCK_INVOCATION = "IsStaticBlockInvocation"; } diff --git a/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java b/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java index 26d696a76..bdcef65c6 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java +++ b/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java @@ -6,7 +6,6 @@ import com.salesforce.collections.CollectionUtil; import com.salesforce.exception.UnexpectedException; import com.salesforce.graph.Schema; - import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -73,53 +72,54 @@ private void buildVertices(JorjeNode node, Vertex vNodeParam, String fileName) { vNode.property(Schema.FILE_NAME, fileName); } - Vertex vPreviousSibling = null; - final List children = node.getChildren(); - final Set verticesAddressed = new HashSet<>(); - verticesAddressed.add(vNode); - - for (int i = 0; i < children.size(); i++) { - final JorjeNode child = children.get(i); - final Vertex vChild = g.addV(child.getLabel()).next(); - addProperties(g, child, vChild); - - // Handle static block if needed - if (StaticBlockUtil.isStaticBlockStatement(node, child)) { - final Vertex parentVertexForChild = StaticBlockUtil.createSyntheticStaticBlockMethod(g, vNode, vChild, i); - GremlinVertexUtil.addParentChildRelationship(g, parentVertexForChild, vChild); - verticesAddressed.add(parentVertexForChild); - } else { - GremlinVertexUtil.addParentChildRelationship(g, vNode, vChild); - } - - if (vPreviousSibling != null) { - g.addE(Schema.NEXT_SIBLING).from(vPreviousSibling).to(vChild).iterate(); - } - vPreviousSibling = vChild; - - // To save memory in the graph, don't pass the source name into recursive calls. - buildVertices(child, vChild, null); - } - // Execute afterInsert() on each vertex we addressed - for (Vertex vertex: verticesAddressed) { - afterInsert(g, node, vertex); - } + Vertex vPreviousSibling = null; + final List children = node.getChildren(); + final Set verticesAddressed = new HashSet<>(); + verticesAddressed.add(vNode); + + for (int i = 0; i < children.size(); i++) { + final JorjeNode child = children.get(i); + final Vertex vChild = g.addV(child.getLabel()).next(); + addProperties(g, child, vChild); + + // Handle static block if needed + if (StaticBlockUtil.isStaticBlockStatement(node, child)) { + final Vertex parentVertexForChild = + StaticBlockUtil.createSyntheticStaticBlockMethod(g, vNode, vChild, i); + GremlinVertexUtil.addParentChildRelationship(g, parentVertexForChild, vChild); + verticesAddressed.add(parentVertexForChild); + } else { + GremlinVertexUtil.addParentChildRelationship(g, vNode, vChild); + } + + if (vPreviousSibling != null) { + g.addE(Schema.NEXT_SIBLING).from(vPreviousSibling).to(vChild).iterate(); + } + vPreviousSibling = vChild; + + // To save memory in the graph, don't pass the source name into recursive calls. + buildVertices(child, vChild, null); + } + // Execute afterInsert() on each vertex we addressed + for (Vertex vertex : verticesAddressed) { + afterInsert(g, node, vertex); + } if (rootVNode != null) { // Only call this for the root node afterFileInsert(g, rootVNode); } } - /** + /** * Adds edges to Method vertices after they are inserted. TODO: Replace this with a listener or * visitor pattern for more general purpose solutions */ private final void afterInsert(GraphTraversalSource g, JorjeNode node, Vertex vNode) { if (node.getLabel().equals(ASTConstants.NodeType.METHOD)) { - MethodPathBuilderVisitor.apply(g, vNode); + MethodPathBuilderVisitor.apply(g, vNode); } else if (ROOT_VERTICES.contains(node.getLabel())) { - StaticBlockUtil.createSyntheticStaticBlockInvocation(g, vNode); - } + StaticBlockUtil.createSyntheticStaticBlockInvocation(g, vNode); + } } /** @@ -131,7 +131,7 @@ protected void afterFileInsert(GraphTraversalSource g, Vertex vNode) { // Intentionally left blank } - protected void addProperties(GraphTraversalSource g, JorjeNode node, Vertex vNode) { + protected void addProperties(GraphTraversalSource g, JorjeNode node, Vertex vNode) { GraphTraversal traversal = g.V(vNode.id()); TreeSet previouslyInsertedKeys = CollectionUtil.newTreeSet(); @@ -143,7 +143,8 @@ protected void addProperties(GraphTraversalSource g, JorjeNode node, Vertex vNod } for (Map.Entry entry : getAdditionalProperties(node).entrySet()) { - GremlinVertexUtil.addProperty(previouslyInsertedKeys, traversal, entry.getKey(), entry.getValue()); + GremlinVertexUtil.addProperty( + previouslyInsertedKeys, traversal, entry.getKey(), entry.getValue()); } // Commit the changes. @@ -157,7 +158,6 @@ protected Object adjustPropertyValue(JorjeNode node, String key, Object value) { /** Add additional properties to the node that aren't present in the orginal AST */ protected Map getAdditionalProperties(JorjeNode node) { - return new HashMap<>(); - } - + return new HashMap<>(); + } } diff --git a/sfge/src/main/java/com/salesforce/graph/build/ApexStandardLibraryVertexBuilder.java b/sfge/src/main/java/com/salesforce/graph/build/ApexStandardLibraryVertexBuilder.java index a821e3804..8486cd02f 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/ApexStandardLibraryVertexBuilder.java +++ b/sfge/src/main/java/com/salesforce/graph/build/ApexStandardLibraryVertexBuilder.java @@ -5,7 +5,6 @@ import com.salesforce.exception.UnexpectedException; import com.salesforce.graph.Schema; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.logging.log4j.LogManager; diff --git a/sfge/src/main/java/com/salesforce/graph/build/GremlinUtil.java b/sfge/src/main/java/com/salesforce/graph/build/GremlinUtil.java index d98f77449..a34fc84eb 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/GremlinUtil.java +++ b/sfge/src/main/java/com/salesforce/graph/build/GremlinUtil.java @@ -32,7 +32,11 @@ public static Optional getOnlyChild( if (children.isEmpty()) { return Optional.empty(); } else if (children.size() > 1) { - throw new UnexpectedException("Did not expect more than one child node of type " + childLabel +". Actual count: " + children.size()); + throw new UnexpectedException( + "Did not expect more than one child node of type " + + childLabel + + ". Actual count: " + + children.size()); } else { return Optional.of(children.get(0)); } @@ -56,9 +60,10 @@ public static List getChildren(GraphTraversalSource g, Vertex vertex) { .toList(); } - public static List getChildren(GraphTraversalSource g, Vertex vertex, String childLabel) { - return g.V(vertex).out(Schema.CHILD).hasLabel(childLabel).toList(); - } + public static List getChildren( + GraphTraversalSource g, Vertex vertex, String childLabel) { + return g.V(vertex).out(Schema.CHILD).hasLabel(childLabel).toList(); + } public static Optional getPreviousSibling(GraphTraversalSource g, Vertex vertex) { Iterator it = g.V(vertex).in(Schema.NEXT_SIBLING); @@ -91,14 +96,14 @@ public static Optional getFirstChild(GraphTraversalSource g, Vertex vert } } - public static Optional getParent(GraphTraversalSource g, Vertex vertex) { - Iterator it = g.V(vertex).out(Schema.PARENT); - if (it.hasNext()) { - return Optional.of(it.next()); - } else { - return Optional.empty(); - } - } + public static Optional getParent(GraphTraversalSource g, Vertex vertex) { + Iterator it = g.V(vertex).out(Schema.PARENT); + if (it.hasNext()) { + return Optional.of(it.next()); + } else { + return Optional.empty(); + } + } private GremlinUtil() {} } diff --git a/sfge/src/main/java/com/salesforce/graph/build/GremlinVertexUtil.java b/sfge/src/main/java/com/salesforce/graph/build/GremlinVertexUtil.java index 4de95cd58..56282b904 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/GremlinVertexUtil.java +++ b/sfge/src/main/java/com/salesforce/graph/build/GremlinVertexUtil.java @@ -1,55 +1,51 @@ package com.salesforce.graph.build; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; -import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; -import org.apache.tinkerpop.gremlin.structure.Vertex; import com.salesforce.exception.UnexpectedException; import com.salesforce.graph.Schema; - import java.util.List; import java.util.Optional; import java.util.TreeSet; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.structure.Vertex; -/** - * Handles common operations performed while creating vertices on Graph - */ +/** Handles common operations performed while creating vertices on Graph */ public final class GremlinVertexUtil { - private static final Logger LOGGER = LogManager.getLogger(GremlinVertexUtil.class); - private GremlinVertexUtil(){} + private static final Logger LOGGER = LogManager.getLogger(GremlinVertexUtil.class); - /** - * Create parent-child relationship between vertices on graph - */ - static void addParentChildRelationship(GraphTraversalSource g, Vertex parentVertex, Vertex childVertex) { - // We are currently adding PARENT and CHILD, in theory the same edge could be navigated - // both ways, however - // the code looks messy when that is done. - // TODO: Determine if this causes performance or resource issues. Consider a single edge - g.addE(Schema.PARENT).from(childVertex).to(parentVertex).iterate(); - g.addE(Schema.CHILD).from(parentVertex).to(childVertex).iterate(); - } + private GremlinVertexUtil() {} - /** - * Make a synthetic vertex a sibling of an existing vertex on graph - */ - static void makeSiblings(GraphTraversalSource g, Vertex vertex, Vertex syntheticVertex) { - // Get parent node of vertex - final Optional rootVertex = GremlinUtil.getParent(g, vertex); - if (rootVertex.isEmpty()) { - throw new UnexpectedException("Did not expect vertex to not have a parent vertex. vertex=" + vertex); - } + /** Create parent-child relationship between vertices on graph */ + static void addParentChildRelationship( + GraphTraversalSource g, Vertex parentVertex, Vertex childVertex) { + // We are currently adding PARENT and CHILD, in theory the same edge could be navigated + // both ways, however + // the code looks messy when that is done. + // TODO: Determine if this causes performance or resource issues. Consider a single edge + g.addE(Schema.PARENT).from(childVertex).to(parentVertex).iterate(); + g.addE(Schema.CHILD).from(parentVertex).to(childVertex).iterate(); + } - addParentChildRelationship(g, rootVertex.get(), syntheticVertex); - } + /** Make a synthetic vertex a sibling of an existing vertex on graph */ + static void makeSiblings(GraphTraversalSource g, Vertex vertex, Vertex syntheticVertex) { + // Get parent node of vertex + final Optional rootVertex = GremlinUtil.getParent(g, vertex); + if (rootVertex.isEmpty()) { + throw new UnexpectedException( + "Did not expect vertex to not have a parent vertex. vertex=" + vertex); + } + + addParentChildRelationship(g, rootVertex.get(), syntheticVertex); + } - /** Add a property to the traversal, throwing an exception if any keys are duplicated. */ + /** Add a property to the traversal, throwing an exception if any keys are duplicated. */ protected static void addProperty( - TreeSet previouslyInsertedKeys, - GraphTraversal traversal, - String keyParam, - Object value) { + TreeSet previouslyInsertedKeys, + GraphTraversal traversal, + String keyParam, + Object value) { final String key = keyParam.intern(); if (!previouslyInsertedKeys.add(key)) { diff --git a/sfge/src/main/java/com/salesforce/graph/build/MethodPathBuilderVisitor.java b/sfge/src/main/java/com/salesforce/graph/build/MethodPathBuilderVisitor.java index 33c53ae82..5d7ba6c86 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/MethodPathBuilderVisitor.java +++ b/sfge/src/main/java/com/salesforce/graph/build/MethodPathBuilderVisitor.java @@ -5,8 +5,6 @@ import com.salesforce.exception.TodoException; import com.salesforce.exception.UnexpectedException; import com.salesforce.graph.Schema; -import com.salesforce.graph.ops.CloneUtil; -import com.salesforce.graph.ops.MethodUtil; import com.salesforce.graph.vertex.SFVertexFactory; import java.util.ArrayList; import java.util.Arrays; @@ -24,7 +22,7 @@ /** Visits a method and draws all control flow edges. */ public class MethodPathBuilderVisitor { private static final Logger LOGGER = LogManager.getLogger(MethodPathBuilderVisitor.class); - /** Load the vertices as SFVertices and log more information. Will impact performance. */ + /** Load the vertices as SFVertices and log more information. Will impact performance. */ private static boolean SF_VERTEX_LOGGING = true; /** @@ -98,8 +96,7 @@ public static void apply(GraphTraversalSource g, Vertex methodVertex) { } } - - private void _visit(Vertex vertex, Vertex parent, boolean lastChild) { + private void _visit(Vertex vertex, Vertex parent, boolean lastChild) { try { String label = vertex.label(); diff --git a/sfge/src/main/java/com/salesforce/graph/build/StaticBlockUtil.java b/sfge/src/main/java/com/salesforce/graph/build/StaticBlockUtil.java index 78ccbef2a..d21e97246 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/StaticBlockUtil.java +++ b/sfge/src/main/java/com/salesforce/graph/build/StaticBlockUtil.java @@ -1,322 +1,340 @@ package com.salesforce.graph.build; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; -import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; -import org.apache.tinkerpop.gremlin.structure.Vertex; - import com.salesforce.apex.jorje.ASTConstants; import com.salesforce.apex.jorje.JorjeNode; import com.salesforce.collections.CollectionUtil; import com.salesforce.exception.ProgrammingException; import com.salesforce.graph.Schema; import com.salesforce.graph.ops.MethodUtil; - import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeSet; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.structure.Vertex; /** * Handles creation of synthetic methods and vertices to gracefully invoke static code blocks. * + *

Consider this example: class StaticBlockClass { static { System.debug("inside static block + * 1"); } static { System.debug("inside static block 2"); } } In Jorje's compilation structure, + * static blocks are represented like this: class StaticBlockClass { private static void () { + * { System.debug("inside static block 1"); } { System.debug("inside static block 2"); } } } * - * Consider this example: - * class StaticBlockClass { - * static { - * System.debug("inside static block 1"); - * } - * static { - * System.debug("inside static block 2"); - * } - * } - * In Jorje's compilation structure, static blocks are represented like this: - * class StaticBlockClass { - * private static void () { - * { - * System.debug("inside static block 1"); - * } - * { - * System.debug("inside static block 2"); - * } - * } - * } - * - * Having multiple block statements inside a method breaks SFGE's makes handling code blocks in () - * impossible. As an alternative, we are creating synthetic vertices in the Graph to represent the static - * blocks as individual methods. We also create one top-level synthetic method ("StaticBlockInvoker") that invokes individual static - * block methods. While creating static scope for this class, we should invoke the method call expressions - * inside the top-level synthetic method. - * - * New structure looks like this: - * class StaticBlockClass { - * private static void SyntheticStaticBlock_1() { - * System.debug("inside static block 1"); - * } + *

Having multiple block statements inside a method breaks SFGE's makes handling code blocks in + * () impossible. As an alternative, we are creating synthetic vertices in the Graph to + * represent the static blocks as individual methods. We also create one top-level synthetic method + * ("StaticBlockInvoker") that invokes individual static block methods. While creating static scope + * for this class, we should invoke the method call expressions inside the top-level synthetic + * method. * - * private static void SyntheticStaticBlock_2() { - * System.debug("inside static block 2"); - * } + *

New structure looks like this: class StaticBlockClass { private static void + * SyntheticStaticBlock_1() { System.debug("inside static block 1"); } * - * private static void StaticBlockInvoker() { - * SyntheticStaticBlock_1(); - * SyntheticStaticBlock_2(); - * } - * } + *

private static void SyntheticStaticBlock_2() { System.debug("inside static block 2"); } * + *

private static void StaticBlockInvoker() { SyntheticStaticBlock_1(); SyntheticStaticBlock_2(); + * } } */ public final class StaticBlockUtil { - private static final Logger LOGGER = LogManager.getLogger(StaticBlockUtil.class); - - private static final String SYNTHETIC_STATIC_BLOCK_METHOD_NAME = "SyntheticStaticBlock_%d"; - private static final String STATIC_BLOCK_INVOKER_METHOD = "StaticBlockInvoker"; - - private StaticBlockUtil() {} - - /** - * Creates a synthetic method vertex to represent a static code block - * @param g traversal graph - * @param clintVertex () method's vertex - * @param blockStatementVertex static block originally placed by Jorje under () - * @param staticBlockIndex index to use for name uniqueness - - * TODO: this index is currently just the child count index and does not - * necessarily follow sequence - * @return new synthetic method vertex with name SyntheticStaticBlock_%d, which will be the parent - * for blockStatementVertex, and a sibling of () - */ - public static Vertex createSyntheticStaticBlockMethod(GraphTraversalSource g, Vertex clintVertex, Vertex blockStatementVertex, int staticBlockIndex) { - final Vertex syntheticMethodVertex = g.addV(ASTConstants.NodeType.METHOD).next(); - final String definingType = clintVertex.value(Schema.DEFINING_TYPE); - addSyntheticStaticBlockMethodProperties(g, definingType, syntheticMethodVertex, staticBlockIndex); - - final Vertex modifierNodeVertex = g.addV(ASTConstants.NodeType.MODIFIER_NODE).next(); - addStaticModifierProperties(g, definingType, modifierNodeVertex); - GremlinVertexUtil.addParentChildRelationship(g, syntheticMethodVertex, modifierNodeVertex); - - GremlinVertexUtil.makeSiblings(g, clintVertex, syntheticMethodVertex); - - return syntheticMethodVertex; - } - - /** - * If rootNode contains synthetic static block methods (created through {@link #createSyntheticStaticBlockMethod}), - * adds a top-level synthetic method that invokes each static block method. - */ - public static void createSyntheticStaticBlockInvocation(GraphTraversalSource g, Vertex rootNode) { - // Check if root node contains any static block methods - final List staticBlockMethods = getStaticBlockMethods(g, rootNode); - - // Create static block invocation method - if (!staticBlockMethods.isEmpty()) { - final String definingType = rootNode.value(Schema.DEFINING_TYPE); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Creating synthetic invocation method for {} to invoke {} static block(s)", definingType, staticBlockMethods.size()); - } - - final Vertex invokerMethodVertex = createSyntheticInvocationsMethod(g, rootNode, staticBlockMethods, definingType); - MethodPathBuilderVisitor.apply(g, invokerMethodVertex); - } - } - - static boolean isStaticBlockStatement(JorjeNode node, JorjeNode child) { - return isClintMethod(node) - && containsStaticBlock(node) - && isBlockStatement(child); - } - - private static Vertex createSyntheticInvocationsMethod(GraphTraversalSource g, Vertex rootNode, List staticBlockMethods, String definingType) { - // Create new synthetic method StaticBlockInvoker to invoke each synthetic static block method - final Vertex invokerMethodVertex = g.addV(ASTConstants.NodeType.METHOD).next(); - addStaticBlockInvokerProperties(g, definingType, invokerMethodVertex); - GremlinVertexUtil.addParentChildRelationship(g, rootNode, invokerMethodVertex); - - final Vertex modifierNodeVertex = g.addV(ASTConstants.NodeType.MODIFIER_NODE).next(); - addStaticModifierProperties(g, definingType, modifierNodeVertex); - GremlinVertexUtil.addParentChildRelationship(g, invokerMethodVertex, modifierNodeVertex); - - // Create synthetic BlockStatement inside StaticBlockInvoker to hold the method invocations - final Vertex blockStatementInInvoker = g.addV(ASTConstants.NodeType.BLOCK_STATEMENT).next(); - addBlockStatementProperties(g, definingType, blockStatementInInvoker); - GremlinVertexUtil.addParentChildRelationship(g, invokerMethodVertex, blockStatementInInvoker); - - for (int i = 0; i < staticBlockMethods.size(); i++) { - final Vertex staticBlockMethod = staticBlockMethods.get(i); - boolean isLastMethod = i == staticBlockMethods.size() - 1; - // Create a method call invocation to the synthetic static block method - final Vertex exprStaticBlockMethodCall = createMethodCallExpression(g, definingType, staticBlockMethod, isLastMethod); - - // Add the expression statement containing the method call inside StaticBlockInvoker's block statement - GremlinVertexUtil.addParentChildRelationship(g, blockStatementInInvoker, exprStaticBlockMethodCall); - } - - return invokerMethodVertex; - } - - private static Vertex createMethodCallExpression(GraphTraversalSource g, String definingType, Vertex staticBlockMethod, boolean isLastMethod) { - final Vertex expressionStmt = g.addV(ASTConstants.NodeType.EXPRESSION_STATEMENT).next(); - addExpressionStmtProperties(g, definingType, expressionStmt, isLastMethod); - - // Create new MethodCallExpression for the synthetic static block method - final Vertex staticBlockMethodCall = g.addV(ASTConstants.NodeType.METHOD_CALL_EXPRESSION).next(); - addMethodCallExpressionProperties(g, definingType, staticBlockMethod, staticBlockMethodCall); - GremlinVertexUtil.addParentChildRelationship(g, expressionStmt, staticBlockMethodCall); - - // Create EmptyReferenceExpression inside MethodCallExpression - final Vertex emptyMethodReference = g.addV(ASTConstants.NodeType.EMPTY_REFERENCE_EXPRESSION).next(); - addEmptyMethodReferenceProperties(g, definingType, emptyMethodReference); - GremlinVertexUtil.addParentChildRelationship(g, staticBlockMethodCall, emptyMethodReference); - return expressionStmt; - } - - private static void addExpressionStmtProperties(GraphTraversalSource g, String definingType, Vertex expressionStmt, boolean isLastMethod) { - verifyType(expressionStmt, ASTConstants.NodeType.EXPRESSION_STATEMENT); - - final Map properties = new HashMap<>(); - properties.put(Schema.IS_SYNTHETIC, true); - properties.put(Schema.DEFINING_TYPE, definingType); - properties.put(Schema.FIRST_CHILD, true); - properties.put(Schema.LAST_CHILD, true); - properties.put(Schema.CHILD_INDEX, 0); - - addProperties(g, expressionStmt, properties); - } - - private static void addEmptyMethodReferenceProperties(GraphTraversalSource g, String definingType, Vertex emptyMethodReference) { - verifyType(emptyMethodReference, ASTConstants.NodeType.EMPTY_REFERENCE_EXPRESSION); - - final Map properties = new HashMap<>(); - properties.put(Schema.IS_SYNTHETIC, true); - properties.put(Schema.DEFINING_TYPE, definingType); - properties.put(Schema.FIRST_CHILD, true); - properties.put(Schema.LAST_CHILD, true); - properties.put(Schema.CHILD_INDEX, 0); - - addProperties(g, emptyMethodReference, properties); - } - - private static void addMethodCallExpressionProperties(GraphTraversalSource g, String definingType, Vertex staticBlockMethod, Vertex staticBlockMethodCall) { - verifyType(staticBlockMethodCall, ASTConstants.NodeType.METHOD_CALL_EXPRESSION); - - final Map properties = new HashMap<>(); - properties.put(Schema.IS_SYNTHETIC, true); - properties.put(Schema.DEFINING_TYPE, definingType); - properties.put(Schema.METHOD_NAME, staticBlockMethod.value(Schema.NAME)); - properties.put(Schema.FULL_METHOD_NAME, staticBlockMethod.value(Schema.NAME)); - properties.put(Schema.IS_STATIC_BLOCK_INVOCATION, true); - properties.put(Schema.FIRST_CHILD, true); - properties.put(Schema.LAST_CHILD, true); - properties.put(Schema.CHILD_INDEX, 0); - - addProperties(g, staticBlockMethodCall, properties); - } - - private static void addBlockStatementProperties(GraphTraversalSource g, String definingType, Vertex blockStatementInInvoker) { - verifyType(blockStatementInInvoker, ASTConstants.NodeType.BLOCK_STATEMENT); - - final Map properties = new HashMap<>(); - properties.put(Schema.IS_SYNTHETIC, true); - properties.put(Schema.DEFINING_TYPE, definingType); - properties.put(Schema.CHILD_INDEX, 1); - properties.put(Schema.FIRST_CHILD, false); - properties.put(Schema.LAST_CHILD, true); - - addProperties(g, blockStatementInInvoker, properties); - } - - private static void addStaticModifierProperties(GraphTraversalSource g, String definingType, Vertex modifier) { - verifyType(modifier, ASTConstants.NodeType.MODIFIER_NODE); - - final Map properties = new HashMap<>(); - properties.put(Schema.IS_SYNTHETIC, true); - properties.put(Schema.DEFINING_TYPE, definingType); - properties.put(Schema.STATIC, true); - properties.put(Schema.ABSTRACT, false); - properties.put(Schema.GLOBAL, false); - properties.put(Schema.MODIFIERS, 8); // Apparently, static methods have modifiers 8 - properties.put(Schema.FIRST_CHILD, true); - properties.put(Schema.LAST_CHILD, false); - properties.put(Schema.CHILD_INDEX, 0); - - addProperties(g, modifier, properties); - } - - private static void addStaticBlockInvokerProperties(GraphTraversalSource g, String definingType, Vertex staticBlockInvokerVertex) { - verifyType(staticBlockInvokerVertex, ASTConstants.NodeType.METHOD); - final Map properties = new HashMap<>(); - properties.put(Schema.NAME, STATIC_BLOCK_INVOKER_METHOD); - properties.put(Schema.IS_STATIC_BLOCK_INVOKER_METHOD, true); - addCommonSynthMethodProperties(g, definingType, staticBlockInvokerVertex, properties); - } - - private static void addSyntheticStaticBlockMethodProperties(GraphTraversalSource g, String definingType, Vertex syntheticMethodVertex, int staticBlockIndex) { - verifyType(syntheticMethodVertex, ASTConstants.NodeType.METHOD); - final Map properties = new HashMap<>(); - properties.put(Schema.NAME, String.format(SYNTHETIC_STATIC_BLOCK_METHOD_NAME, staticBlockIndex)); - properties.put(Schema.IS_STATIC_BLOCK_METHOD, true); - - addCommonSynthMethodProperties(g, definingType, syntheticMethodVertex, properties); - } - - private static void addCommonSynthMethodProperties(GraphTraversalSource g, String definingType, Vertex staticBlockInvokerVertex, Map properties) { - properties.put(Schema.ARITY, 0); - properties.put(Schema.CONSTRUCTOR, false); - properties.put(Schema.DEFINING_TYPE, definingType); - properties.put(Schema.IS_SYNTHETIC, true); - properties.put(Schema.RETURN_TYPE, ASTConstants.TYPE_VOID); - - - addProperties(g, staticBlockInvokerVertex, properties); - } - - private static void addProperties(GraphTraversalSource g, Vertex vertex, Map properties) { - final TreeSet previouslyInsertedKeys = CollectionUtil.newTreeSet(); - final GraphTraversal traversal = g.V(vertex.id()); - - for (Map.Entry entry : properties.entrySet()) { - GremlinVertexUtil.addProperty(previouslyInsertedKeys, traversal, entry.getKey(), entry.getValue()); - previouslyInsertedKeys.add(entry.getKey()); - } - - // Commit the changes. - traversal.next(); - } - - /** - * @return true if given node represents () - */ - private static boolean isClintMethod(JorjeNode node) { - return ASTConstants.NodeType.METHOD.equals(node.getLabel()) - && MethodUtil.STATIC_CONSTRUCTOR_CANONICAL_NAME.equals(node.getProperties().get(Schema.NAME)); - } - - /** - * @return true if () node contains any static block definitions - */ - private static boolean containsStaticBlock(JorjeNode node) { - for (JorjeNode childNode : node.getChildren()) { - if (ASTConstants.NodeType.BLOCK_STATEMENT.equals(childNode.getLabel())) { - return true; - } - } - return false; - } - - private static List getStaticBlockMethods(GraphTraversalSource g, Vertex root) { - return g.V(root) - .out(Schema.CHILD) - .hasLabel(ASTConstants.NodeType.METHOD) - .has(Schema.IS_STATIC_BLOCK_METHOD, true) - .toList(); - } - - private static boolean isBlockStatement(JorjeNode child) { - return ASTConstants.NodeType.BLOCK_STATEMENT.equals(child.getLabel()); - } - - private static void verifyType(Vertex vertex, String expectedType) { - if (! expectedType.equals(vertex.label())) { - throw new ProgrammingException("Incorrect vertex type: " + vertex.label()); - } - } + private static final Logger LOGGER = LogManager.getLogger(StaticBlockUtil.class); + + static final String SYNTHETIC_STATIC_BLOCK_METHOD_NAME = "SyntheticStaticBlock_%d"; + static final String STATIC_BLOCK_INVOKER_METHOD = "StaticBlockInvoker"; + + private StaticBlockUtil() {} + + /** + * Creates a synthetic method vertex to represent a static code block + * + * @param g traversal graph + * @param clintVertex () method's vertex + * @param blockStatementVertex static block originally placed by Jorje under () + * @param staticBlockIndex index to use for name uniqueness - TODO: this index is currently just + * the child count index and does not necessarily follow sequence + * @return new synthetic method vertex with name SyntheticStaticBlock_%d, which will be the + * parent for blockStatementVertex, and a sibling of () + */ + public static Vertex createSyntheticStaticBlockMethod( + GraphTraversalSource g, + Vertex clintVertex, + Vertex blockStatementVertex, + int staticBlockIndex) { + final Vertex syntheticMethodVertex = g.addV(ASTConstants.NodeType.METHOD).next(); + final String definingType = clintVertex.value(Schema.DEFINING_TYPE); + addSyntheticStaticBlockMethodProperties( + g, definingType, syntheticMethodVertex, staticBlockIndex); + + final Vertex modifierNodeVertex = g.addV(ASTConstants.NodeType.MODIFIER_NODE).next(); + addStaticModifierProperties(g, definingType, modifierNodeVertex); + GremlinVertexUtil.addParentChildRelationship(g, syntheticMethodVertex, modifierNodeVertex); + + GremlinVertexUtil.makeSiblings(g, clintVertex, syntheticMethodVertex); + + return syntheticMethodVertex; + } + + /** + * If rootNode contains synthetic static block methods (created through {@link + * #createSyntheticStaticBlockMethod}), adds a top-level synthetic method that invokes each + * static block method. + */ + public static void createSyntheticStaticBlockInvocation( + GraphTraversalSource g, Vertex rootNode) { + // Check if root node contains any static block methods + final List staticBlockMethods = getStaticBlockMethods(g, rootNode); + + // Create static block invocation method + if (!staticBlockMethods.isEmpty()) { + final String definingType = rootNode.value(Schema.DEFINING_TYPE); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + "Creating synthetic invocation method for {} to invoke {} static block(s)", + definingType, + staticBlockMethods.size()); + } + + final Vertex invokerMethodVertex = + createSyntheticInvocationsMethod(g, rootNode, staticBlockMethods, definingType); + MethodPathBuilderVisitor.apply(g, invokerMethodVertex); + } + } + + static boolean isStaticBlockStatement(JorjeNode node, JorjeNode child) { + return isClintMethod(node) && containsStaticBlock(node) && isBlockStatement(child); + } + + private static Vertex createSyntheticInvocationsMethod( + GraphTraversalSource g, + Vertex rootNode, + List staticBlockMethods, + String definingType) { + // Create new synthetic method StaticBlockInvoker to invoke each synthetic static block + // method + final Vertex invokerMethodVertex = g.addV(ASTConstants.NodeType.METHOD).next(); + addStaticBlockInvokerProperties(g, definingType, invokerMethodVertex); + GremlinVertexUtil.addParentChildRelationship(g, rootNode, invokerMethodVertex); + + final Vertex modifierNodeVertex = g.addV(ASTConstants.NodeType.MODIFIER_NODE).next(); + addStaticModifierProperties(g, definingType, modifierNodeVertex); + GremlinVertexUtil.addParentChildRelationship(g, invokerMethodVertex, modifierNodeVertex); + + // Create synthetic BlockStatement inside StaticBlockInvoker to hold the method invocations + final Vertex blockStatementInInvoker = g.addV(ASTConstants.NodeType.BLOCK_STATEMENT).next(); + addBlockStatementProperties(g, definingType, blockStatementInInvoker); + GremlinVertexUtil.addParentChildRelationship( + g, invokerMethodVertex, blockStatementInInvoker); + + for (int i = 0; i < staticBlockMethods.size(); i++) { + final Vertex staticBlockMethod = staticBlockMethods.get(i); + boolean isLastMethod = i == staticBlockMethods.size() - 1; + // Create a method call invocation to the synthetic static block method + final Vertex exprStaticBlockMethodCall = + createMethodCallExpression(g, definingType, staticBlockMethod, isLastMethod); + + // Add the expression statement containing the method call inside StaticBlockInvoker's + // block statement + GremlinVertexUtil.addParentChildRelationship( + g, blockStatementInInvoker, exprStaticBlockMethodCall); + } + + return invokerMethodVertex; + } + + private static Vertex createMethodCallExpression( + GraphTraversalSource g, + String definingType, + Vertex staticBlockMethod, + boolean isLastMethod) { + final Vertex expressionStmt = g.addV(ASTConstants.NodeType.EXPRESSION_STATEMENT).next(); + addExpressionStmtProperties(g, definingType, expressionStmt, isLastMethod); + + // Create new MethodCallExpression for the synthetic static block method + final Vertex staticBlockMethodCall = + g.addV(ASTConstants.NodeType.METHOD_CALL_EXPRESSION).next(); + addMethodCallExpressionProperties( + g, definingType, staticBlockMethod, staticBlockMethodCall); + GremlinVertexUtil.addParentChildRelationship(g, expressionStmt, staticBlockMethodCall); + + // Create EmptyReferenceExpression inside MethodCallExpression + final Vertex emptyMethodReference = + g.addV(ASTConstants.NodeType.EMPTY_REFERENCE_EXPRESSION).next(); + addEmptyMethodReferenceProperties(g, definingType, emptyMethodReference); + GremlinVertexUtil.addParentChildRelationship( + g, staticBlockMethodCall, emptyMethodReference); + return expressionStmt; + } + + private static void addExpressionStmtProperties( + GraphTraversalSource g, + String definingType, + Vertex expressionStmt, + boolean isLastMethod) { + verifyType(expressionStmt, ASTConstants.NodeType.EXPRESSION_STATEMENT); + + final Map properties = new HashMap<>(); + properties.put(Schema.IS_SYNTHETIC, true); + properties.put(Schema.DEFINING_TYPE, definingType); + properties.put(Schema.FIRST_CHILD, true); + properties.put(Schema.LAST_CHILD, true); + properties.put(Schema.CHILD_INDEX, 0); + + addProperties(g, expressionStmt, properties); + } + + private static void addEmptyMethodReferenceProperties( + GraphTraversalSource g, String definingType, Vertex emptyMethodReference) { + verifyType(emptyMethodReference, ASTConstants.NodeType.EMPTY_REFERENCE_EXPRESSION); + + final Map properties = new HashMap<>(); + properties.put(Schema.IS_SYNTHETIC, true); + properties.put(Schema.DEFINING_TYPE, definingType); + properties.put(Schema.FIRST_CHILD, true); + properties.put(Schema.LAST_CHILD, true); + properties.put(Schema.CHILD_INDEX, 0); + + addProperties(g, emptyMethodReference, properties); + } + + private static void addMethodCallExpressionProperties( + GraphTraversalSource g, + String definingType, + Vertex staticBlockMethod, + Vertex staticBlockMethodCall) { + verifyType(staticBlockMethodCall, ASTConstants.NodeType.METHOD_CALL_EXPRESSION); + + final Map properties = new HashMap<>(); + properties.put(Schema.IS_SYNTHETIC, true); + properties.put(Schema.DEFINING_TYPE, definingType); + properties.put(Schema.METHOD_NAME, staticBlockMethod.value(Schema.NAME)); + properties.put(Schema.FULL_METHOD_NAME, staticBlockMethod.value(Schema.NAME)); + properties.put(Schema.IS_STATIC_BLOCK_INVOCATION, true); + properties.put(Schema.FIRST_CHILD, true); + properties.put(Schema.LAST_CHILD, true); + properties.put(Schema.CHILD_INDEX, 0); + + addProperties(g, staticBlockMethodCall, properties); + } + + private static void addBlockStatementProperties( + GraphTraversalSource g, String definingType, Vertex blockStatementInInvoker) { + verifyType(blockStatementInInvoker, ASTConstants.NodeType.BLOCK_STATEMENT); + + final Map properties = new HashMap<>(); + properties.put(Schema.IS_SYNTHETIC, true); + properties.put(Schema.DEFINING_TYPE, definingType); + properties.put(Schema.CHILD_INDEX, 1); + properties.put(Schema.FIRST_CHILD, false); + properties.put(Schema.LAST_CHILD, true); + + addProperties(g, blockStatementInInvoker, properties); + } + + private static void addStaticModifierProperties( + GraphTraversalSource g, String definingType, Vertex modifier) { + verifyType(modifier, ASTConstants.NodeType.MODIFIER_NODE); + + final Map properties = new HashMap<>(); + properties.put(Schema.IS_SYNTHETIC, true); + properties.put(Schema.DEFINING_TYPE, definingType); + properties.put(Schema.STATIC, true); + properties.put(Schema.ABSTRACT, false); + properties.put(Schema.GLOBAL, false); + properties.put(Schema.MODIFIERS, 8); // Apparently, static methods have modifiers 8 + properties.put(Schema.FIRST_CHILD, true); + properties.put(Schema.LAST_CHILD, false); + properties.put(Schema.CHILD_INDEX, 0); + + addProperties(g, modifier, properties); + } + + private static void addStaticBlockInvokerProperties( + GraphTraversalSource g, String definingType, Vertex staticBlockInvokerVertex) { + verifyType(staticBlockInvokerVertex, ASTConstants.NodeType.METHOD); + final Map properties = new HashMap<>(); + properties.put(Schema.NAME, STATIC_BLOCK_INVOKER_METHOD); + properties.put(Schema.IS_STATIC_BLOCK_INVOKER_METHOD, true); + addCommonSynthMethodProperties(g, definingType, staticBlockInvokerVertex, properties); + } + + private static void addSyntheticStaticBlockMethodProperties( + GraphTraversalSource g, + String definingType, + Vertex syntheticMethodVertex, + int staticBlockIndex) { + verifyType(syntheticMethodVertex, ASTConstants.NodeType.METHOD); + final Map properties = new HashMap<>(); + properties.put( + Schema.NAME, String.format(SYNTHETIC_STATIC_BLOCK_METHOD_NAME, staticBlockIndex)); + properties.put(Schema.IS_STATIC_BLOCK_METHOD, true); + + addCommonSynthMethodProperties(g, definingType, syntheticMethodVertex, properties); + } + + private static void addCommonSynthMethodProperties( + GraphTraversalSource g, + String definingType, + Vertex staticBlockInvokerVertex, + Map properties) { + properties.put(Schema.ARITY, 0); + properties.put(Schema.CONSTRUCTOR, false); + properties.put(Schema.DEFINING_TYPE, definingType); + properties.put(Schema.IS_SYNTHETIC, true); + properties.put(Schema.RETURN_TYPE, ASTConstants.TYPE_VOID); + + addProperties(g, staticBlockInvokerVertex, properties); + } + + private static void addProperties( + GraphTraversalSource g, Vertex vertex, Map properties) { + final TreeSet previouslyInsertedKeys = CollectionUtil.newTreeSet(); + final GraphTraversal traversal = g.V(vertex.id()); + + for (Map.Entry entry : properties.entrySet()) { + GremlinVertexUtil.addProperty( + previouslyInsertedKeys, traversal, entry.getKey(), entry.getValue()); + previouslyInsertedKeys.add(entry.getKey()); + } + + // Commit the changes. + traversal.next(); + } + + /** @return true if given node represents () */ + private static boolean isClintMethod(JorjeNode node) { + return ASTConstants.NodeType.METHOD.equals(node.getLabel()) + && MethodUtil.STATIC_CONSTRUCTOR_CANONICAL_NAME.equals( + node.getProperties().get(Schema.NAME)); + } + + /** @return true if () node contains any static block definitions */ + private static boolean containsStaticBlock(JorjeNode node) { + for (JorjeNode childNode : node.getChildren()) { + if (ASTConstants.NodeType.BLOCK_STATEMENT.equals(childNode.getLabel())) { + return true; + } + } + return false; + } + + private static List getStaticBlockMethods(GraphTraversalSource g, Vertex root) { + return g.V(root) + .out(Schema.CHILD) + .hasLabel(ASTConstants.NodeType.METHOD) + .has(Schema.IS_STATIC_BLOCK_METHOD, true) + .toList(); + } + + private static boolean isBlockStatement(JorjeNode child) { + return ASTConstants.NodeType.BLOCK_STATEMENT.equals(child.getLabel()); + } + + private static void verifyType(Vertex vertex, String expectedType) { + if (!expectedType.equals(vertex.label())) { + throw new ProgrammingException("Incorrect vertex type: " + vertex.label()); + } + } } diff --git a/sfge/src/main/java/com/salesforce/graph/ops/MethodUtil.java b/sfge/src/main/java/com/salesforce/graph/ops/MethodUtil.java index bcc92ee77..9632bb1d6 100644 --- a/sfge/src/main/java/com/salesforce/graph/ops/MethodUtil.java +++ b/sfge/src/main/java/com/salesforce/graph/ops/MethodUtil.java @@ -84,7 +84,7 @@ public final class MethodUtil { public static final String INSTANCE_CONSTRUCTOR_CANONICAL_NAME = ""; public static final String STATIC_CONSTRUCTOR_CANONICAL_NAME = ""; - public static List getTargetedMethods( + public static List getTargetedMethods( GraphTraversalSource g, List targets) { // The targets passed into this method should exclusively be ones that target specific // methods instead of files. diff --git a/sfge/src/main/java/com/salesforce/graph/ops/expander/ApexPathExpander.java b/sfge/src/main/java/com/salesforce/graph/ops/expander/ApexPathExpander.java index af23a3009..f1dbb9665 100644 --- a/sfge/src/main/java/com/salesforce/graph/ops/expander/ApexPathExpander.java +++ b/sfge/src/main/java/com/salesforce/graph/ops/expander/ApexPathExpander.java @@ -150,6 +150,12 @@ final class ApexPathExpander implements ClassStaticScopeProvider, EngineDirectiv */ private final TreeSet currentlyInitializingStaticClasses; + /** + * Track classes whose static scopes have been initialized. This would help double visiting some + * nodes. + */ + private final TreeSet alreadyInitializedStaticClasses; + /** The symbol provider that corresponds to the {@link #topMostPath} */ private /*finalTODO Add clear method to SymbolProviderVertexVisitor*/ SymbolProviderVertexVisitor symbolProviderVisitor; @@ -195,6 +201,7 @@ final class ApexPathExpander implements ClassStaticScopeProvider, EngineDirectiv this.classStaticScopes = CollectionUtil.newTreeMap(); this.engineDirectiveContext = new EngineDirectiveContext(); this.currentlyInitializingStaticClasses = CollectionUtil.newTreeSet(); + this.alreadyInitializedStaticClasses = CollectionUtil.newTreeSet(); } /** @@ -272,6 +279,8 @@ final class ApexPathExpander implements ClassStaticScopeProvider, EngineDirectiv this.startScope = (SymbolProvider) CloneUtil.clone((DeepCloneable) other.startScope); this.currentlyInitializingStaticClasses = CloneUtil.cloneTreeSet(other.currentlyInitializingStaticClasses); + this.alreadyInitializedStaticClasses = + CloneUtil.cloneTreeSet(other.alreadyInitializedStaticClasses); } /** @@ -370,15 +379,18 @@ void initializeClassStaticScope(String className) apexPath = getTopMostPath().getStaticInitializationPath(className).get(); } - if (!classStaticScope.getState().equals(AbstractClassScope.State.INITIALIZED)) { + if (!classStaticScope.getState().equals(AbstractClassScope.State.INITIALIZED) + && !alreadyInitializedStaticClasses.contains(fullClassName)) { if (apexPath != null && apexPath.getCollectible() != null) { visit(apexPath.getCollectible()); } symbolProviderVisitor.popScope(classStaticScope); classStaticScope.setState(AbstractClassScope.State.INITIALIZED); + alreadyInitializedStaticClasses.add(fullClassName); } } finally { - if (!currentlyInitializingStaticClasses.remove(fullClassName)) { + if (!currentlyInitializingStaticClasses.remove(fullClassName) + && alreadyInitializedStaticClasses.contains(fullClassName)) { throw new ProgrammingException( "Set did not contain class. values=" + currentlyInitializingStaticClasses); diff --git a/sfge/src/main/java/com/salesforce/graph/symbols/ClassStaticScope.java b/sfge/src/main/java/com/salesforce/graph/symbols/ClassStaticScope.java index 036857020..df046f8bf 100644 --- a/sfge/src/main/java/com/salesforce/graph/symbols/ClassStaticScope.java +++ b/sfge/src/main/java/com/salesforce/graph/symbols/ClassStaticScope.java @@ -5,6 +5,7 @@ import com.salesforce.Collectible; import com.salesforce.apex.jorje.ASTConstants; import com.salesforce.collections.CollectionUtil; +import com.salesforce.exception.ProgrammingException; import com.salesforce.exception.UnexpectedException; import com.salesforce.graph.ApexPath; import com.salesforce.graph.DeepCloneable; @@ -131,8 +132,6 @@ private static List getStaticBlocks( .out(Schema.CHILD) .hasLabel(NodeType.METHOD) .has(Schema.IS_STATIC_BLOCK_INVOKER_METHOD, true) - .order(Scope.global) - .by(Schema.CHILD_INDEX, Order.asc) .out(Schema.CHILD) .hasLabel(ASTConstants.NodeType.BLOCK_STATEMENT) .order(Scope.global) @@ -161,6 +160,9 @@ private static List getStaticBlocks( public static Optional getInitializationPath( GraphTraversalSource g, String classname) { ClassStaticScope classStaticScope = ClassStaticScope.get(g, classname); + if (classStaticScope.getState().equals(State.INITIALIZED)) { + throw new ProgrammingException("Initialization path does not need to be invoked on a class that's already initialized: " + classStaticScope.getClassName()); + } List vertices = new ArrayList<>(); vertices.addAll(classStaticScope.getFields()); vertices.addAll(getFieldDeclarations(g, classStaticScope.userClass)); @@ -174,13 +176,4 @@ public static Optional getInitializationPath( } } -// private static MethodCallExpressionVertex createSyntheticInvocation(MethodVertex.StaticBlockVertex methodVertex) { -// final HashMap map = new HashMap<>(); -// map.put(T.id, Long.valueOf(-1)); -// map.put(T.label, NodeType.METHOD_CALL_EXPRESSION); -// map.put(Schema.METHOD_NAME, methodVertex.getName()); -// map.put(Schema.DEFINING_TYPE, methodVertex.getDefiningType()); -// map.put(Schema.STATIC, Boolean.valueOf(true)); -// return new MethodCallExpressionVertex(map); -// } } diff --git a/sfge/src/main/java/com/salesforce/graph/vertex/MethodVertex.java b/sfge/src/main/java/com/salesforce/graph/vertex/MethodVertex.java index 9c203c6d7..958d25b00 100644 --- a/sfge/src/main/java/com/salesforce/graph/vertex/MethodVertex.java +++ b/sfge/src/main/java/com/salesforce/graph/vertex/MethodVertex.java @@ -4,7 +4,6 @@ import com.salesforce.NullCollectible; import com.salesforce.apex.jorje.ASTConstants; import com.salesforce.graph.Schema; -import com.salesforce.graph.ops.MethodUtil; import com.salesforce.graph.symbols.SymbolProvider; import com.salesforce.graph.symbols.SymbolProviderVertexVisitor; import com.salesforce.graph.visitor.PathVertexVisitor; @@ -138,40 +137,41 @@ public ConstructorVertex getCollectible() { } } - public static final class StaticBlockVertex extends MethodVertex implements Collectible { - public static final NullCollectible NULL_VALUE = - new NullCollectible<>(StaticBlockVertex.class); + public static final class StaticBlockVertex extends MethodVertex + implements Collectible { + public static final NullCollectible NULL_VALUE = + new NullCollectible<>(StaticBlockVertex.class); - private StaticBlockVertex(Map properties) { - super(properties); - } + private StaticBlockVertex(Map properties) { + super(properties); + } - @Override - public boolean visit(PathVertexVisitor visitor, SymbolProvider symbols) { - return visitor.visit(this, symbols); - } + @Override + public boolean visit(PathVertexVisitor visitor, SymbolProvider symbols) { + return visitor.visit(this, symbols); + } - @Override - public boolean visit(SymbolProviderVertexVisitor visitor) { - return visitor.visit(this); - } + @Override + public boolean visit(SymbolProviderVertexVisitor visitor) { + return visitor.visit(this); + } - @Override - public void afterVisit(PathVertexVisitor visitor, SymbolProvider symbols) { - visitor.afterVisit(this, symbols); - } + @Override + public void afterVisit(PathVertexVisitor visitor, SymbolProvider symbols) { + visitor.afterVisit(this, symbols); + } - @Override - public void afterVisit(SymbolProviderVertexVisitor visitor) { - visitor.afterVisit(this); - } + @Override + public void afterVisit(SymbolProviderVertexVisitor visitor) { + visitor.afterVisit(this); + } - @Nullable - @Override - public StaticBlockVertex getCollectible() { - return this; - } - } + @Nullable + @Override + public StaticBlockVertex getCollectible() { + return this; + } + } public static final class InstanceMethodVertex extends MethodVertex { private InstanceMethodVertex(Map properties) { @@ -201,14 +201,15 @@ public void afterVisit(SymbolProviderVertexVisitor visitor) { public static final class Builder { public static MethodVertex create(Map vertex) { - final boolean isStaticBlockMethod = BaseSFVertex.toBoolean(vertex.get(Schema.IS_STATIC_BLOCK_METHOD)); + final boolean isStaticBlockMethod = + BaseSFVertex.toBoolean(vertex.get(Schema.IS_STATIC_BLOCK_METHOD)); boolean isConstructor = BaseSFVertex.toBoolean(vertex.get(Schema.CONSTRUCTOR)); - if (isStaticBlockMethod) { - return new StaticBlockVertex(vertex); - } else if (isConstructor) { - return new ConstructorVertex(vertex); - } - return new InstanceMethodVertex(vertex); + if (isStaticBlockMethod) { + return new StaticBlockVertex(vertex); + } else if (isConstructor) { + return new ConstructorVertex(vertex); + } + return new InstanceMethodVertex(vertex); } } } diff --git a/sfge/src/test/java/com/salesforce/graph/build/GraphBuildTestUtil.java b/sfge/src/test/java/com/salesforce/graph/build/GraphBuildTestUtil.java new file mode 100644 index 000000000..07cc97edf --- /dev/null +++ b/sfge/src/test/java/com/salesforce/graph/build/GraphBuildTestUtil.java @@ -0,0 +1,61 @@ +package com.salesforce.graph.build; + +import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.has; + +import com.salesforce.apex.jorje.ASTConstants; +import com.salesforce.apex.jorje.JorjeUtil; +import com.salesforce.graph.ApexPath; +import com.salesforce.graph.Schema; +import com.salesforce.graph.cache.VertexCacheProvider; +import com.salesforce.graph.ops.ApexPathUtil; +import com.salesforce.graph.symbols.DefaultSymbolProviderVertexVisitor; +import com.salesforce.graph.vertex.MethodVertex; +import com.salesforce.graph.vertex.SFVertexFactory; +import com.salesforce.graph.visitor.ApexPathWalker; +import com.salesforce.graph.visitor.DefaultNoOpPathVertexVisitor; +import com.salesforce.graph.visitor.PathVertexVisitor; +import java.util.ArrayList; +import java.util.List; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; + +public class GraphBuildTestUtil { + static void buildGraph(GraphTraversalSource g, String sourceCode) { + buildGraph(g, new String[] {sourceCode}); + } + + static void buildGraph(GraphTraversalSource g, String[] sourceCodes) { + List compilations = new ArrayList(); + for (int i = 0; i < sourceCodes.length; i++) { + compilations.add( + new Util.CompilationDescriptor( + "TestCode" + i, JorjeUtil.compileApexFromString(sourceCodes[i]))); + } + + VertexCacheProvider.get().initialize(g); + CustomerApexVertexBuilder customerApexVertexBuilder = + new CustomerApexVertexBuilder(g, compilations); + + for (GraphBuilder graphBuilder : new GraphBuilder[] {customerApexVertexBuilder}) { + graphBuilder.build(); + } + } + + /** Sanity method to walk all paths. Helps to ensure all of the push/pops are correct */ + static List walkAllPaths(GraphTraversalSource g, String methodName) { + MethodVertex methodVertex = + SFVertexFactory.load( + g, + g.V().hasLabel(ASTConstants.NodeType.METHOD) + .has(Schema.NAME, methodName) + .not(has(Schema.IS_STANDARD, true))); + List paths = ApexPathUtil.getForwardPaths(g, methodVertex); + + for (ApexPath path : paths) { + DefaultSymbolProviderVertexVisitor symbols = new DefaultSymbolProviderVertexVisitor(g); + PathVertexVisitor visitor = new DefaultNoOpPathVertexVisitor(); + ApexPathWalker.walkPath(g, path, visitor, symbols); + } + + return paths; + } +} diff --git a/sfge/src/test/java/com/salesforce/graph/build/MethodPathBuilderTest.java b/sfge/src/test/java/com/salesforce/graph/build/MethodPathBuilderTest.java index 49698f5da..ddef8934d 100644 --- a/sfge/src/test/java/com/salesforce/graph/build/MethodPathBuilderTest.java +++ b/sfge/src/test/java/com/salesforce/graph/build/MethodPathBuilderTest.java @@ -13,12 +13,9 @@ import com.salesforce.TestUtil; import com.salesforce.apex.jorje.ASTConstants.NodeType; -import com.salesforce.apex.jorje.JorjeUtil; import com.salesforce.graph.ApexPath; import com.salesforce.graph.Schema; -import com.salesforce.graph.cache.VertexCacheProvider; import com.salesforce.graph.ops.ApexPathUtil; -import com.salesforce.graph.symbols.DefaultSymbolProviderVertexVisitor; import com.salesforce.graph.vertex.BaseSFVertex; import com.salesforce.graph.vertex.BlockStatementVertex; import com.salesforce.graph.vertex.CatchBlockStatementVertex; @@ -39,9 +36,6 @@ import com.salesforce.graph.vertex.ValueWhenBlockVertex; import com.salesforce.graph.vertex.VariableDeclarationStatementsVertex; import com.salesforce.graph.vertex.VariableExpressionVertex; -import com.salesforce.graph.visitor.ApexPathWalker; -import com.salesforce.graph.visitor.DefaultNoOpPathVertexVisitor; -import com.salesforce.graph.visitor.PathVertexVisitor; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -57,7 +51,7 @@ import org.junit.jupiter.api.Test; public class MethodPathBuilderTest { - private GraphTraversalSource g; + protected GraphTraversalSource g; private static final String[] BLOCK = new String[] {NodeType.BLOCK_STATEMENT}; @@ -129,7 +123,7 @@ public void testMethodWithSingleExpression() { + " }\n" + "}"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); // // @@ -159,7 +153,7 @@ public void testMethodWithSingleExpression() { MatcherAssert.assertThat(getVerticesWithEndScope(), hasSize(equalTo(1))); assertEndScopes(BLOCK, ExpressionStatementVertex.class, 3); - walkAllPaths("doSomething"); + GraphBuildTestUtil.walkAllPaths(g, "doSomething"); } @Test @@ -175,7 +169,7 @@ public void testMethodWithNestedIfs() { + " }\n" + "}"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); // // // // @@ -437,7 +431,7 @@ public void testMethodWithIfStatement() { // Implicit else assertEndScopes(BLOCK_IF_BLOCK, BlockStatementVertex.class, 3, 3); - walkAllPaths("doSomething"); + GraphBuildTestUtil.walkAllPaths(g, "doSomething"); } @Test @@ -453,7 +447,7 @@ public void testMethodWithSingleIfElseStatement() { + " }\n" + "}"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); // // @@ -542,7 +536,7 @@ public void testMethodWithSingleIfElseStatement() { // System.debug('GoodBye'); assertEndScopes(BLOCK_IF_BLOCK, ExpressionStatementVertex.class, 6); - walkAllPaths("doSomething"); + GraphBuildTestUtil.walkAllPaths(g, "doSomething"); } @Test @@ -560,7 +554,7 @@ public void testMethodWithSingleIf_ElseIf_ElseStatement() { + " }\n" + "}"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); // // @@ -692,7 +686,7 @@ public void testMethodWithSingleIf_ElseIf_ElseStatement() { // System.debug('GoodBye'); assertEndScopes(BLOCK_IF_BLOCK, ExpressionStatementVertex.class, 8); - walkAllPaths("doSomething"); + GraphBuildTestUtil.walkAllPaths(g, "doSomething"); } @Test @@ -712,7 +706,7 @@ public void testMethodWithNestedIfElses() { + " }\n" + "}"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); // // @@ -819,7 +813,7 @@ public void testMethodWithNestedIfElses() { // System.debug('Not Logged'); assertEndScopes(BLOCK_IF_BLOCK, ExpressionStatementVertex.class, 10); - walkAllPaths("doSomething"); + GraphBuildTestUtil.walkAllPaths(g, "doSomething"); } @Test @@ -835,7 +829,7 @@ public void testMethodWithExpressionBeforeAndAfterIf() { + " }\n" + "}"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); // // @@ -946,7 +940,7 @@ public void testMethodWithExpressionBeforeAndAfterIf() { // System.debug('After'); assertEndScopes(BLOCK, ExpressionStatementVertex.class, 7); - walkAllPaths("doSomething"); + GraphBuildTestUtil.walkAllPaths(g, "doSomething"); } @Test @@ -965,7 +959,7 @@ public void testMethodWithInnerIfExpressionBeforeAndAfterIf() { + " }\n" + "}"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); // // @@ -1114,7 +1108,7 @@ public void testMethodWithInnerIfExpressionBeforeAndAfterIf() { // System.debug('After'); assertEndScopes(BLOCK, ExpressionStatementVertex.class, 10); - walkAllPaths("doSomething"); + GraphBuildTestUtil.walkAllPaths(g, "doSomething"); } @Test @@ -1133,7 +1127,7 @@ public void testMethodWithForEach() { + " }\n" + "}"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); // (9 edges) BlockStatement->VariableDeclarationStatements->ForEachStatement // @@ -1185,7 +1179,7 @@ public void testMethodWithForEach() { // System.debug('Not Logged'); assertEndScopes(BLOCK_IF_BLOCK_FOREACH_BLOCK, ExpressionStatementVertex.class, 8); - walkAllPaths("doSomething"); + GraphBuildTestUtil.walkAllPaths(g, "doSomething"); } @Test @@ -1205,7 +1199,7 @@ public void testMethodWithForLoop() { + " }\n" + "}"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); // (10 edges) BlockStatement->VariableDeclarationStatements->ForLoopStatement // @@ -1258,7 +1252,7 @@ public void testMethodWithForLoop() { // System.debug('Not Logged'); assertEndScopes(BLOCK_IF_BLOCK_FORLOOP_BLOCK, ExpressionStatementVertex.class, 9); - walkAllPaths("doSomething"); + GraphBuildTestUtil.walkAllPaths(g, "doSomething"); } /** @@ -1283,7 +1277,7 @@ public void testMethodWithForLoopNoInitializerOrStandardConditionOrIncrementer() + " }\n" + "}"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); // (8 edges) BlockStatement->VariableDeclarationStatements->ForLoopStatement // ->BlockStatement @@ -1335,7 +1329,7 @@ public void testMethodWithForLoopNoInitializerOrStandardConditionOrIncrementer() // System.debug('Not Logged'); assertEndScopes(BLOCK_IF_BLOCK_FORLOOP_BLOCK, ExpressionStatementVertex.class, 10); - walkAllPaths("doSomething"); + GraphBuildTestUtil.walkAllPaths(g, "doSomething"); } /** Tests the case where the for loop doesn't contain an initializer. */ @@ -1357,7 +1351,7 @@ public void testMethodWithForLoopNoInitializer() { + " }\n" + "}"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); // (10 edges) // BlockStatement->VariableDeclarationStatements->ForLoopStatement->StandardCondition->PostfixExpression @@ -1410,7 +1404,7 @@ public void testMethodWithForLoopNoInitializer() { // System.debug('Not Logged'); assertEndScopes(BLOCK_IF_BLOCK_FORLOOP_BLOCK, ExpressionStatementVertex.class, 10); - walkAllPaths("doSomething"); + GraphBuildTestUtil.walkAllPaths(g, "doSomething"); } /** @@ -1435,7 +1429,7 @@ public void testMethodWithForLoopNoInitializerOrStandardCondition() { + " }\n" + "}"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); // (9 edges) // BlockStatement->VariableDeclarationStatements->ForLoopStatement->PostfixExpression @@ -1488,7 +1482,7 @@ public void testMethodWithForLoopNoInitializerOrStandardCondition() { // System.debug('Not Logged'); assertEndScopes(BLOCK_IF_BLOCK_FORLOOP_BLOCK, ExpressionStatementVertex.class, 10); - walkAllPaths("doSomething"); + GraphBuildTestUtil.walkAllPaths(g, "doSomething"); } @Test @@ -1509,7 +1503,7 @@ public void testMethodWithForLoopExpressionBeforeAndAfter() { + " }\n" + "}"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); // (10 edges) BlockStatement->VariableDeclarationStatements->ForLoopStatement // @@ -1591,7 +1585,7 @@ public void testMethodWithForLoopExpressionBeforeAndAfter() { // System.debug('After'); assertEndScopes(BLOCK, ExpressionStatementVertex.class, 12); - walkAllPaths("doSomething"); + GraphBuildTestUtil.walkAllPaths(g, "doSomething"); } @Test @@ -1613,7 +1607,7 @@ public void testMethodWithForLoopExpressionBeforeAndAfterEndsForScope() { + " }\n" + "}"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); MatcherAssert.assertThat(getVerticesWithEndScope(), hasSize(equalTo(4))); @@ -1629,7 +1623,7 @@ public void testMethodWithForLoopExpressionBeforeAndAfterEndsForScope() { // System.debug('After'); assertEndScopes(BLOCK, ExpressionStatementVertex.class, 13); - walkAllPaths("doSomething"); + GraphBuildTestUtil.walkAllPaths(g, "doSomething"); } @Test @@ -1644,7 +1638,7 @@ public void testMethodWithEarlyReturn() { + " }\n" + "}"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); // // // @@ -2046,7 +2040,7 @@ public void testMultipleEarlyReturns() { // insert assertEndScopes(BLOCK, DmlInsertStatementVertex.class, 10); - walkAllPaths("doSomething"); + GraphBuildTestUtil.walkAllPaths(g, "doSomething"); } @Test @@ -2066,7 +2060,7 @@ public void testInsertGuardedByExceptionInOtherMethodWithReturn() { + "}\n" }; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); List paths; MethodVertex methodVertex = TestUtil.getVertexOnLine(g, MethodVertex.class, 2); @@ -2092,7 +2086,7 @@ public void testInsertGuardedByExceptionInOtherMethodWithReturn() { // insert assertEndScopes(BLOCK, ThrowStatementVertex.class, 10); - walkAllPaths("doSomething"); + GraphBuildTestUtil.walkAllPaths(g, "doSomething"); } @Test @@ -2108,7 +2102,7 @@ public void testMethodWithSingleTryCatch() { + " }\n" + "}"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); // // @@ -2322,7 +2316,7 @@ public void testMethodWithSingleTryCatchAndExpressionBeforeAndAfter() { // System.debug('After'); assertEndScopes(BLOCK, ExpressionStatementVertex.class, 9); - walkAllPaths("doSomething"); + GraphBuildTestUtil.walkAllPaths(g, "doSomething"); } @Test @@ -2339,7 +2333,7 @@ public void testIfWithMethodInStandardCondition() { + " }\n" + "}\n"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); // doSomething // @@ -2397,7 +2391,7 @@ public void testIfWithMethodInStandardCondition() { List edges = g.V().outE(Schema.CFG_PATH).toList(); MatcherAssert.assertThat(edges, hasSize(7)); - walkAllPaths("doSomething"); + GraphBuildTestUtil.walkAllPaths(g, "doSomething"); } @Test @@ -2413,7 +2407,7 @@ public void testWhileStatement() { + " }\n" + "}\n"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); // (6 edges) // BlockStatement->VariableDeclarationStatements->WhileLoopStatement->StandardCondition->BlockStatement->ExpressionStatement->ExpressionStatement @@ -2424,7 +2418,7 @@ public void testWhileStatement() { ExpressionStatementVertex.class, 6); - List paths = walkAllPaths("doSomething"); + List paths = GraphBuildTestUtil.walkAllPaths(g, "doSomething"); MatcherAssert.assertThat(paths, hasSize(equalTo(1))); } @@ -2448,9 +2442,9 @@ public void testWhileStatementInOtherMethod() { + " }\n" + "}\n"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); - List paths = walkAllPaths("doSomething"); + List paths = GraphBuildTestUtil.walkAllPaths(g, "doSomething"); MatcherAssert.assertThat(paths, hasSize(equalTo(1))); } @@ -2535,7 +2529,7 @@ public void testEnumSwitchStatement() { + " }\n" + "}\n"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); BlockStatementVertex blockStatementLine2 = TestUtil.getVertexOnLine(g, BlockStatementVertex.class, 2); @@ -2611,7 +2605,7 @@ public void testEnumSwitchStatement() { Pair.of(elseWhenBlock, elseWhenBlockBlockStatement), Pair.of(elseWhenBlockBlockStatement, elseWhenBlockExpressionStatement)); - List paths = walkAllPaths("doSomething"); + List paths = GraphBuildTestUtil.walkAllPaths(g, "doSomething"); MatcherAssert.assertThat(paths, hasSize(equalTo(3))); } @@ -2635,7 +2629,7 @@ public void testIntegerSwitchStatement() { + " }\n" + "}\n"; - buildGraph(g, sourceCode); + GraphBuildTestUtil.buildGraph(g, sourceCode); BlockStatementVertex blockStatementLine2 = TestUtil.getVertexOnLine(g, BlockStatementVertex.class, 2); @@ -2709,7 +2703,7 @@ public void testIntegerSwitchStatement() { Pair.of(elseWhenBlock, elseWhenBlockBlockStatement), Pair.of(elseWhenBlockBlockStatement, elseWhenBlockExpressionStatement)); - List paths = walkAllPaths("doSomething"); + List paths = GraphBuildTestUtil.walkAllPaths(g, "doSomething"); MatcherAssert.assertThat(paths, hasSize(equalTo(3))); } @@ -2736,8 +2730,8 @@ public void testMethodSwitchStatement() { + " }\n" + "}\n"; - buildGraph(g, sourceCode); - List paths = walkAllPaths("doSomething"); + GraphBuildTestUtil.buildGraph(g, sourceCode); + List paths = GraphBuildTestUtil.walkAllPaths(g, "doSomething"); // There are 3 paths since #walkPaths does not use any excluders MatcherAssert.assertThat(paths, hasSize(equalTo(3))); } @@ -2809,44 +2803,4 @@ private void assertEndScopes( hasSize(equalTo(endScopes.length))); MatcherAssert.assertThat(vertex.getEndScopes(), contains(endScopes)); } - - /** Sanity method to walk all paths. Helps to ensure all of the push/pops are correct */ - private List walkAllPaths(String methodName) { - MethodVertex methodVertex = - SFVertexFactory.load( - g, - g.V().hasLabel(NodeType.METHOD) - .has(Schema.NAME, methodName) - .not(has(Schema.IS_STANDARD, true))); - List paths = ApexPathUtil.getForwardPaths(g, methodVertex); - - for (ApexPath path : paths) { - DefaultSymbolProviderVertexVisitor symbols = new DefaultSymbolProviderVertexVisitor(g); - PathVertexVisitor visitor = new DefaultNoOpPathVertexVisitor(); - ApexPathWalker.walkPath(g, path, visitor, symbols); - } - - return paths; - } - - private static void buildGraph(GraphTraversalSource g, String sourceCode) { - buildGraph(g, new String[] {sourceCode}); - } - - private static void buildGraph(GraphTraversalSource g, String[] sourceCodes) { - List compilations = new ArrayList<>(); - for (int i = 0; i < sourceCodes.length; i++) { - compilations.add( - new Util.CompilationDescriptor( - "TestCode" + i, JorjeUtil.compileApexFromString(sourceCodes[i]))); - } - - VertexCacheProvider.get().initialize(g); - CustomerApexVertexBuilder customerApexVertexBuilder = - new CustomerApexVertexBuilder(g, compilations); - - for (GraphBuilder graphBuilder : new GraphBuilder[] {customerApexVertexBuilder}) { - graphBuilder.build(); - } - } } diff --git a/sfge/src/test/java/com/salesforce/graph/build/StaticAndAnonymousCodeBlockTest.java b/sfge/src/test/java/com/salesforce/graph/build/StaticAndAnonymousCodeBlockTest.java deleted file mode 100644 index e31367cbf..000000000 --- a/sfge/src/test/java/com/salesforce/graph/build/StaticAndAnonymousCodeBlockTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.salesforce.graph.build; - -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; -import org.junit.jupiter.api.Test; -import com.salesforce.TestRunner; -import com.salesforce.TestUtil; -import com.salesforce.graph.symbols.apex.ApexStringValue; -import com.salesforce.graph.symbols.apex.ApexValue; -import com.salesforce.graph.visitor.SystemDebugAccumulator; -import com.salesforce.matchers.TestRunnerMatcher; - -import java.util.List; -import java.util.Optional; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasSize; - -public class StaticAndAnonymousCodeBlockTest extends MethodPathBuilderTest { - @Test - public void testStaticBlock() { - String[] sourceCode = - { - "public class StaticBlockClass {\n" + - " static {\n" - + " System.debug('static block');\n" - + " }\n" - + " public static String foo() {\n" + - " return 'hello';\n" + - "}\n" - + "}", - "public class MyClass {\n" + - " public void doSomething() {\n" + - " StaticBlockClass sb = new StaticBlockClass();\n" + - " System.debug(sb.foo());\n" + - " }\n" + - "}" - }; - -// TestUtil.buildGraph(g, sourceCode); - - TestRunner.Result result = TestRunner.walkPath(g, - sourceCode); - SystemDebugAccumulator visitor = result.getVisitor(); - final List>> allResults = visitor.getAllResults(); - - assertThat(allResults, hasSize(2)); - - // verify that static block is invoked first - ApexStringValue stringValue = (ApexStringValue) allResults.get(0).get(); - assertThat(stringValue.getValue().get(), equalTo("static block")); - - } -} diff --git a/sfge/src/test/java/com/salesforce/graph/build/StaticBlockUtilTest.java b/sfge/src/test/java/com/salesforce/graph/build/StaticBlockUtilTest.java new file mode 100644 index 000000000..075bb92b5 --- /dev/null +++ b/sfge/src/test/java/com/salesforce/graph/build/StaticBlockUtilTest.java @@ -0,0 +1,117 @@ +package com.salesforce.graph.build; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; + +import com.salesforce.TestUtil; +import com.salesforce.graph.ApexPath; +import com.salesforce.graph.vertex.BaseSFVertex; +import com.salesforce.graph.vertex.BlockStatementVertex; +import com.salesforce.graph.vertex.ExpressionStatementVertex; +import com.salesforce.graph.vertex.LiteralExpressionVertex; +import com.salesforce.graph.vertex.MethodCallExpressionVertex; +import java.util.List; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class StaticBlockUtilTest { + protected GraphTraversalSource g; + private static final String SYNTHETIC_STATIC_BLOCK_METHOD_1 = + String.format(StaticBlockUtil.SYNTHETIC_STATIC_BLOCK_METHOD_NAME, 1); + + @BeforeEach + public void setup() { + this.g = TestUtil.getGraph(); + } + + // does each static block have a corresponding synthetic method block? + @Test + public void testMethodForStaticBlock() { + String[] sourceCode = { + "public class MyClass {\n" + + " static {\n" + + " System.debug('inside static block 1');\n" + + "}\n" + + " static {\n" + + " System.debug('inside static block 2');\n" + + " }\n" + + "void doSomething() {\n" + + " System.debug('inside doSomething');\n" + + "}\n" + + "}" + }; + + GraphBuildTestUtil.buildGraph(g, sourceCode); + + verifyStaticBlockMethod( + String.format(StaticBlockUtil.SYNTHETIC_STATIC_BLOCK_METHOD_NAME, 1), + "inside static block 1"); + verifyStaticBlockMethod( + String.format(StaticBlockUtil.SYNTHETIC_STATIC_BLOCK_METHOD_NAME, 2), + "inside static block 2"); + } + + private void verifyStaticBlockMethod(String methodName, String printedString) { + final List staticBlockPaths = GraphBuildTestUtil.walkAllPaths(g, methodName); + assertThat(staticBlockPaths, hasSize(1)); + final ApexPath path = staticBlockPaths.get(0); + + // Make sure synthetic method is linked to the static block and its contents + final BlockStatementVertex blockStatementVertex = (BlockStatementVertex) path.firstVertex(); + final List blockStmtChildren = blockStatementVertex.getChildren(); + assertThat(blockStmtChildren, hasSize(1)); + + final ExpressionStatementVertex expressionStatementVertex = + (ExpressionStatementVertex) blockStmtChildren.get(0); + final List exprStmtChildren = expressionStatementVertex.getChildren(); + assertThat(exprStmtChildren, hasSize(1)); + + final MethodCallExpressionVertex methodCallExpressionVertex = + (MethodCallExpressionVertex) exprStmtChildren.get(0); + assertThat(methodCallExpressionVertex.getFullMethodName(), equalTo("System.debug")); + final LiteralExpressionVertex literalExpressionVertex = + (LiteralExpressionVertex) methodCallExpressionVertex.getParameters().get(0); + + assertThat(literalExpressionVertex.getLiteralAsString(), equalTo(printedString)); + } + + // does synthetic invoker method exist and contain the necessary parts and relationship? + @Test + public void testMethodInvokerForStaticBlock() { + String[] sourceCode = { + "public class MyClass {\n" + + " static {\n" + + " System.debug('inside static block');\n" + + "}\n" + + "void doSomething() {\n" + + " System.debug('inside doSomething');\n" + + "}\n" + + "}" + }; + + GraphBuildTestUtil.buildGraph(g, sourceCode); + + final List staticBlockPaths = + GraphBuildTestUtil.walkAllPaths(g, StaticBlockUtil.STATIC_BLOCK_INVOKER_METHOD); + assertThat(staticBlockPaths, hasSize(1)); + final ApexPath path = staticBlockPaths.get(0); + + // Make sure synthetic method is linked to the static block and its contents + final BlockStatementVertex blockStatementVertex = (BlockStatementVertex) path.firstVertex(); + final List blockStmtChildren = blockStatementVertex.getChildren(); + assertThat(blockStmtChildren, hasSize(1)); + + final ExpressionStatementVertex expressionStatementVertex = + (ExpressionStatementVertex) blockStmtChildren.get(0); + final List exprStmtChildren = expressionStatementVertex.getChildren(); + assertThat(exprStmtChildren, hasSize(1)); + + final MethodCallExpressionVertex methodCallExpressionVertex = + (MethodCallExpressionVertex) exprStmtChildren.get(0); + assertThat( + methodCallExpressionVertex.getFullMethodName(), + equalTo(SYNTHETIC_STATIC_BLOCK_METHOD_1)); + } +} diff --git a/sfge/src/test/java/com/salesforce/graph/symbols/StaticCodeBlockInvocationTest.java b/sfge/src/test/java/com/salesforce/graph/symbols/StaticCodeBlockInvocationTest.java new file mode 100644 index 000000000..914be6b5b --- /dev/null +++ b/sfge/src/test/java/com/salesforce/graph/symbols/StaticCodeBlockInvocationTest.java @@ -0,0 +1,300 @@ +package com.salesforce.graph.symbols; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.salesforce.TestRunner; +import com.salesforce.TestUtil; +import com.salesforce.graph.symbols.apex.ApexStringValue; +import com.salesforce.graph.symbols.apex.ApexValue; +import com.salesforce.graph.visitor.SystemDebugAccumulator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +public class StaticCodeBlockInvocationTest { + protected GraphTraversalSource g; + + @BeforeEach + public void setup() { + this.g = TestUtil.getGraph(); + } + + @Test + public void testSingleStaticBlockFromStaticMethod() { + String[] sourceCode = { + "public class StaticBlockClass {\n" + + " static {\n" + + " System.debug('static block');\n" + + " }\n" + + " public static String foo() {\n" + + " return 'hello';\n" + + "}\n" + + "}", + "public class MyClass {\n" + + " public void doSomething() {\n" + + " System.debug(StaticBlockClass.foo());\n" + + " }\n" + + "}" + }; + + TestRunner.Result result = TestRunner.walkPath(g, sourceCode); + SystemDebugAccumulator visitor = result.getVisitor(); + final List>> allResults = visitor.getAllResults(); + + assertThat(allResults, hasSize(2)); + + // verify that static block is invoked first + ApexStringValue stringValue = (ApexStringValue) allResults.get(0).get(); + assertThat(stringValue.getValue().get(), equalTo("static block")); + } + + @Test + public void testStaticBlockFromConstructor() { + String[] sourceCode = { + "public class StaticBlockClass {\n" + + " static {\n" + + " System.debug('static block');\n" + + " }\n" + + "}", + "public class MyClass {\n" + + " public void doSomething() {\n" + + " StaticBlockClass sb = new StaticBlockClass();\n" + + " }\n" + + "}" + }; + + TestRunner.Result result = TestRunner.walkPath(g, sourceCode); + SystemDebugAccumulator visitor = result.getVisitor(); + final List>> allResults = visitor.getAllResults(); + + assertThat(allResults, hasSize(1)); + + // verify that static block is invoked first + ApexStringValue stringValue = (ApexStringValue) allResults.get(0).get(); + assertThat(stringValue.getValue().get(), equalTo("static block")); + } + + @Test + public void testNoStaticBlock() { + String[] sourceCode = { + "public class StaticBlockClass {\n" + " public static String foo = 'hello';\n" + "}", + "public class MyClass {\n" + + " public void doSomething() {\n" + + " System.debug(StaticBlockClass.foo);\n" + + " }\n" + + "}" + }; + + TestRunner.Result result = TestRunner.walkPath(g, sourceCode); + SystemDebugAccumulator visitor = result.getVisitor(); + final List>> allResults = visitor.getAllResults(); + + assertThat(allResults, hasSize(1)); + + // verify non-static-block case is handled correctly + ApexStringValue stringValue = (ApexStringValue) allResults.get(0).get(); + assertThat(stringValue.getValue().get(), equalTo("hello")); + } + + @Test + @Disabled // TODO: static field defined along with static block should not be double-invoked + public void testSingleStaticBlockAndField() { + String[] sourceCode = { + "public class StaticBlockClass {\n" + + " static {\n" + + " System.debug('static block');\n" + + " }\n" + + " static String myStr = 'hello';\n" + + " public static String foo() {\n" + + " return 'hello';\n" + + "}\n" + + "}", + "public class MyClass {\n" + + " public void doSomething() {\n" + + " System.debug(StaticBlockClass.foo());\n" + + " }\n" + + "}" + }; + + TestRunner.Result result = TestRunner.walkPath(g, sourceCode); + SystemDebugAccumulator visitor = result.getVisitor(); + final List>> allResults = visitor.getAllResults(); + + assertThat(allResults, hasSize(2)); + + // verify that static block is invoked first + ApexStringValue stringValue = (ApexStringValue) allResults.get(0).get(); + assertThat(stringValue.getValue().get(), equalTo("static block")); + } + + @Test + @Disabled // TODO: static field defined along with static block should not be double-invoked + public void testSingleStaticBlockAndFieldInvokingAMethod() { + String[] sourceCode = { + "public class StaticBlockClass {\n" + + " static {\n" + + " System.debug('static block');\n" + + " }\n" + + " static String myStr = foo();\n" + + " public static String bar() {\n" + + " return 'bar';\n" + + "}\n" + + "}", + "public class MyClass {\n" + + " public void doSomething() {\n" + + " System.debug(StaticBlockClass.bar());\n" + + " }\n" + + "}" + }; + + TestRunner.Result result = TestRunner.walkPath(g, sourceCode); + SystemDebugAccumulator visitor = result.getVisitor(); + final List>> allResults = visitor.getAllResults(); + + assertThat(allResults, hasSize(2)); + + // verify that static block is invoked first + ApexStringValue stringValue = (ApexStringValue) allResults.get(0).get(); + assertThat(stringValue.getValue().get(), equalTo("static block")); + } + + @Test + public void testMultipleStaticBlocks() { + String[] sourceCode = { + "public class StaticBlockClass {\n" + + " static {\n" + + " System.debug('static block 1');\n" + + " }\n" + + " static {\n" + + " System.debug('static block 2');\n" + + " }\n" + + " public static String foo() {\n" + + " return 'hello';\n" + + "}\n" + + "}", + "public class MyClass {\n" + + " public void doSomething() {\n" + + " System.debug(StaticBlockClass.foo());\n" + + " }\n" + + "}" + }; + + TestRunner.Result result = TestRunner.walkPath(g, sourceCode); + SystemDebugAccumulator visitor = result.getVisitor(); + final List>> allResults = visitor.getAllResults(); + + final List resultStrings = getResultStrings(allResults); + assertTrue(resultStrings.contains("static block 1")); + assertTrue(resultStrings.contains("static block 2")); + assertTrue(resultStrings.contains("hello")); + } + + @Test + @Disabled // TODO: fix issue where static block 2 is invoked twice instead of once + public void testEachStaticBlockIsInvokedOnlyOnce() { + String[] sourceCode = { + "public class StaticBlockClass {\n" + + " static {\n" + + " System.debug('static block 1');\n" + + " }\n" + + " static {\n" + + " System.debug('static block 2');\n" + + " }\n" + + " public static String foo() {\n" + + " return 'hello';\n" + + "}\n" + + "}", + "public class MyClass {\n" + + " public void doSomething() {\n" + + " System.debug(StaticBlockClass.foo());\n" + + " }\n" + + "}" + }; + + TestRunner.Result result = TestRunner.walkPath(g, sourceCode); + SystemDebugAccumulator visitor = result.getVisitor(); + final List>> allResults = visitor.getAllResults(); + + assertThat( + allResults, + hasSize(3)); // TODO: this is currently returning 4 where static block 2 gets + // invoked twice + } + + @Test + public void testSuperClassStaticBlocks() { + String[] sourceCode = { + "public class SuperStaticBlockClass {\n" + + " static {\n" + + " System.debug('super static block');\n" + + " }\n" + + "}", + "public class StaticBlockClass extends SuperStaticBlockClass {\n" + + " static {\n" + + " System.debug('static block');\n" + + " }\n" + + " public static String foo() {\n" + + " return 'hello';\n" + + "}\n" + + "}", + "public class MyClass {\n" + + " public void doSomething() {\n" + + " System.debug(StaticBlockClass.foo());\n" + + " }\n" + + "}" + }; + + TestRunner.Result result = TestRunner.walkPath(g, sourceCode); + SystemDebugAccumulator visitor = result.getVisitor(); + final List>> allResults = visitor.getAllResults(); + + final List resultStrings = getResultStrings(allResults); + assertTrue(resultStrings.contains("super static block")); + assertTrue(resultStrings.contains("static block")); + assertTrue(resultStrings.contains("hello")); + } + + @Test + @Disabled // TODO: super class's static block should be invoked only once + public void testSuperClassStaticBlocksInvokedOnceEach() { + String[] sourceCode = { + "public class SuperStaticBlockClass {\n" + + " static {\n" + + " System.debug('super static block');\n" + + " }\n" + + "}", + "public class StaticBlockClass extends SuperStaticBlockClass {\n" + + " static {\n" + + " System.debug('static block');\n" + + " }\n" + + " public static String foo() {\n" + + " return 'hello';\n" + + "}\n" + + "}", + "public class MyClass {\n" + + " public void doSomething() {\n" + + " System.debug(StaticBlockClass.foo());\n" + + " }\n" + + "}" + }; + + TestRunner.Result result = TestRunner.walkPath(g, sourceCode); + SystemDebugAccumulator visitor = result.getVisitor(); + final List>> allResults = visitor.getAllResults(); + + assertThat(allResults, hasSize(3)); + } + + private List getResultStrings(List>> allResults) { + return allResults.stream() + .map(r -> ((ApexStringValue) r.get()).getValue().get()) + .collect(Collectors.toList()); + } +} From ecc2bdd026e25d75e20e17dfac3f88d63471868e Mon Sep 17 00:00:00 2001 From: Roopa Mohan Date: Thu, 30 Jun 2022 12:01:36 -0700 Subject: [PATCH 5/8] Removing intermediary code now unused --- .../salesforce/graph/vertex/MethodVertex.java | 45 +------------------ 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/sfge/src/main/java/com/salesforce/graph/vertex/MethodVertex.java b/sfge/src/main/java/com/salesforce/graph/vertex/MethodVertex.java index 958d25b00..3f0b59fd9 100644 --- a/sfge/src/main/java/com/salesforce/graph/vertex/MethodVertex.java +++ b/sfge/src/main/java/com/salesforce/graph/vertex/MethodVertex.java @@ -137,42 +137,6 @@ public ConstructorVertex getCollectible() { } } - public static final class StaticBlockVertex extends MethodVertex - implements Collectible { - public static final NullCollectible NULL_VALUE = - new NullCollectible<>(StaticBlockVertex.class); - - private StaticBlockVertex(Map properties) { - super(properties); - } - - @Override - public boolean visit(PathVertexVisitor visitor, SymbolProvider symbols) { - return visitor.visit(this, symbols); - } - - @Override - public boolean visit(SymbolProviderVertexVisitor visitor) { - return visitor.visit(this); - } - - @Override - public void afterVisit(PathVertexVisitor visitor, SymbolProvider symbols) { - visitor.afterVisit(this, symbols); - } - - @Override - public void afterVisit(SymbolProviderVertexVisitor visitor) { - visitor.afterVisit(this); - } - - @Nullable - @Override - public StaticBlockVertex getCollectible() { - return this; - } - } - public static final class InstanceMethodVertex extends MethodVertex { private InstanceMethodVertex(Map properties) { super(properties); @@ -201,15 +165,8 @@ public void afterVisit(SymbolProviderVertexVisitor visitor) { public static final class Builder { public static MethodVertex create(Map vertex) { - final boolean isStaticBlockMethod = - BaseSFVertex.toBoolean(vertex.get(Schema.IS_STATIC_BLOCK_METHOD)); boolean isConstructor = BaseSFVertex.toBoolean(vertex.get(Schema.CONSTRUCTOR)); - if (isStaticBlockMethod) { - return new StaticBlockVertex(vertex); - } else if (isConstructor) { - return new ConstructorVertex(vertex); - } - return new InstanceMethodVertex(vertex); + return isConstructor ? new ConstructorVertex(vertex) : new InstanceMethodVertex(vertex); } } } From a9017ba46f5318f1fe133b55b065417cbdbe5cbf Mon Sep 17 00:00:00 2001 From: Roopa Mohan Date: Thu, 30 Jun 2022 15:04:02 -0700 Subject: [PATCH 6/8] Adding child index to synthetic methods --- .../build/AbstractApexVertexBuilder.java | 2 +- .../graph/build/GremlinVertexUtil.java | 9 ++++-- .../graph/build/StaticBlockUtil.java | 29 +++++++++++-------- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java b/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java index bdcef65c6..4b4003e10 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java +++ b/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java @@ -85,7 +85,7 @@ private void buildVertices(JorjeNode node, Vertex vNodeParam, String fileName) { // Handle static block if needed if (StaticBlockUtil.isStaticBlockStatement(node, child)) { final Vertex parentVertexForChild = - StaticBlockUtil.createSyntheticStaticBlockMethod(g, vNode, vChild, i); + StaticBlockUtil.createSyntheticStaticBlockMethod(g, vNode, i); GremlinVertexUtil.addParentChildRelationship(g, parentVertexForChild, vChild); verticesAddressed.add(parentVertexForChild); } else { diff --git a/sfge/src/main/java/com/salesforce/graph/build/GremlinVertexUtil.java b/sfge/src/main/java/com/salesforce/graph/build/GremlinVertexUtil.java index 56282b904..677595747 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/GremlinVertexUtil.java +++ b/sfge/src/main/java/com/salesforce/graph/build/GremlinVertexUtil.java @@ -30,14 +30,19 @@ static void addParentChildRelationship( /** Make a synthetic vertex a sibling of an existing vertex on graph */ static void makeSiblings(GraphTraversalSource g, Vertex vertex, Vertex syntheticVertex) { + final Vertex rootVertex = getParentVertex(g, vertex); + + addParentChildRelationship(g, rootVertex, syntheticVertex); + } + + static Vertex getParentVertex(GraphTraversalSource g, Vertex vertex) { // Get parent node of vertex final Optional rootVertex = GremlinUtil.getParent(g, vertex); if (rootVertex.isEmpty()) { throw new UnexpectedException( "Did not expect vertex to not have a parent vertex. vertex=" + vertex); } - - addParentChildRelationship(g, rootVertex.get(), syntheticVertex); + return rootVertex.get(); } /** Add a property to the traversal, throwing an exception if any keys are duplicated. */ diff --git a/sfge/src/main/java/com/salesforce/graph/build/StaticBlockUtil.java b/sfge/src/main/java/com/salesforce/graph/build/StaticBlockUtil.java index d21e97246..d207146a6 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/StaticBlockUtil.java +++ b/sfge/src/main/java/com/salesforce/graph/build/StaticBlockUtil.java @@ -52,21 +52,22 @@ private StaticBlockUtil() {} * * @param g traversal graph * @param clintVertex () method's vertex - * @param blockStatementVertex static block originally placed by Jorje under () * @param staticBlockIndex index to use for name uniqueness - TODO: this index is currently just * the child count index and does not necessarily follow sequence * @return new synthetic method vertex with name SyntheticStaticBlock_%d, which will be the * parent for blockStatementVertex, and a sibling of () */ public static Vertex createSyntheticStaticBlockMethod( - GraphTraversalSource g, - Vertex clintVertex, - Vertex blockStatementVertex, - int staticBlockIndex) { + GraphTraversalSource g, + Vertex clintVertex, + int staticBlockIndex) { final Vertex syntheticMethodVertex = g.addV(ASTConstants.NodeType.METHOD).next(); final String definingType = clintVertex.value(Schema.DEFINING_TYPE); + final List siblings = GremlinUtil.getChildren(g, GremlinVertexUtil.getParentVertex(g, clintVertex)); + final int nextSiblingIndex = siblings.size(); + addSyntheticStaticBlockMethodProperties( - g, definingType, syntheticMethodVertex, staticBlockIndex); + g, definingType, syntheticMethodVertex, staticBlockIndex, nextSiblingIndex); final Vertex modifierNodeVertex = g.addV(ASTConstants.NodeType.MODIFIER_NODE).next(); addStaticModifierProperties(g, definingType, modifierNodeVertex); @@ -114,8 +115,9 @@ private static Vertex createSyntheticInvocationsMethod( String definingType) { // Create new synthetic method StaticBlockInvoker to invoke each synthetic static block // method + final List siblings = GremlinUtil.getChildren(g, rootNode); final Vertex invokerMethodVertex = g.addV(ASTConstants.NodeType.METHOD).next(); - addStaticBlockInvokerProperties(g, definingType, invokerMethodVertex); + addStaticBlockInvokerProperties(g, definingType, invokerMethodVertex, siblings.size()); GremlinVertexUtil.addParentChildRelationship(g, rootNode, invokerMethodVertex); final Vertex modifierNodeVertex = g.addV(ASTConstants.NodeType.MODIFIER_NODE).next(); @@ -252,24 +254,27 @@ private static void addStaticModifierProperties( } private static void addStaticBlockInvokerProperties( - GraphTraversalSource g, String definingType, Vertex staticBlockInvokerVertex) { + GraphTraversalSource g, String definingType, Vertex staticBlockInvokerVertex, int childIndex) { verifyType(staticBlockInvokerVertex, ASTConstants.NodeType.METHOD); final Map properties = new HashMap<>(); properties.put(Schema.NAME, STATIC_BLOCK_INVOKER_METHOD); properties.put(Schema.IS_STATIC_BLOCK_INVOKER_METHOD, true); + properties.put(Schema.CHILD_INDEX, childIndex); addCommonSynthMethodProperties(g, definingType, staticBlockInvokerVertex, properties); } private static void addSyntheticStaticBlockMethodProperties( - GraphTraversalSource g, - String definingType, - Vertex syntheticMethodVertex, - int staticBlockIndex) { + GraphTraversalSource g, + String definingType, + Vertex syntheticMethodVertex, + int staticBlockIndex, + int childIndex) { verifyType(syntheticMethodVertex, ASTConstants.NodeType.METHOD); final Map properties = new HashMap<>(); properties.put( Schema.NAME, String.format(SYNTHETIC_STATIC_BLOCK_METHOD_NAME, staticBlockIndex)); properties.put(Schema.IS_STATIC_BLOCK_METHOD, true); + properties.put(Schema.CHILD_INDEX, childIndex); addCommonSynthMethodProperties(g, definingType, syntheticMethodVertex, properties); } From 89f7cd6f7507d50570c368c80beb4561b0ddf20c Mon Sep 17 00:00:00 2001 From: Roopa Mohan Date: Tue, 5 Jul 2022 14:39:47 -0700 Subject: [PATCH 7/8] Attempting to remove compilation error in CircleCI --- .../main/java/com/salesforce/graph/build/GremlinVertexUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfge/src/main/java/com/salesforce/graph/build/GremlinVertexUtil.java b/sfge/src/main/java/com/salesforce/graph/build/GremlinVertexUtil.java index 677595747..3efb7f3c1 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/GremlinVertexUtil.java +++ b/sfge/src/main/java/com/salesforce/graph/build/GremlinVertexUtil.java @@ -38,7 +38,7 @@ static void makeSiblings(GraphTraversalSource g, Vertex vertex, Vertex synthetic static Vertex getParentVertex(GraphTraversalSource g, Vertex vertex) { // Get parent node of vertex final Optional rootVertex = GremlinUtil.getParent(g, vertex); - if (rootVertex.isEmpty()) { + if (!rootVertex.isPresent()) { throw new UnexpectedException( "Did not expect vertex to not have a parent vertex. vertex=" + vertex); } From d7fd3604b3c26c3f04113ee36db14481b793bb94 Mon Sep 17 00:00:00 2001 From: Roopa Mohan Date: Tue, 5 Jul 2022 16:19:13 -0700 Subject: [PATCH 8/8] Addressing PR feedback --- .../build/AbstractApexVertexBuilder.java | 12 ++- .../build/CustomerApexVertexBuilder.java | 1 + .../graph/build/StaticBlockUtil.java | 82 +++++++++++++------ 3 files changed, 66 insertions(+), 29 deletions(-) diff --git a/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java b/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java index 4b4003e10..2cd405346 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java +++ b/sfge/src/main/java/com/salesforce/graph/build/AbstractApexVertexBuilder.java @@ -82,7 +82,9 @@ private void buildVertices(JorjeNode node, Vertex vNodeParam, String fileName) { final Vertex vChild = g.addV(child.getLabel()).next(); addProperties(g, child, vChild); - // Handle static block if needed + /** Handle static block if we are looking at a method that has a block statement. + * See {@linkplain StaticBlockUtil} on why this is needed + * and how we handle it. */ if (StaticBlockUtil.isStaticBlockStatement(node, child)) { final Vertex parentVertexForChild = StaticBlockUtil.createSyntheticStaticBlockMethod(g, vNode, i); @@ -116,9 +118,9 @@ private void buildVertices(JorjeNode node, Vertex vNodeParam, String fileName) { */ private final void afterInsert(GraphTraversalSource g, JorjeNode node, Vertex vNode) { if (node.getLabel().equals(ASTConstants.NodeType.METHOD)) { + // If we just added a method, create forward and + // backward code flow for the contents of the method MethodPathBuilderVisitor.apply(g, vNode); - } else if (ROOT_VERTICES.contains(node.getLabel())) { - StaticBlockUtil.createSyntheticStaticBlockInvocation(g, vNode); } } @@ -128,7 +130,9 @@ private final void afterInsert(GraphTraversalSource g, JorjeNode node, Vertex vN * @param vNode root node that corresponds to the file */ protected void afterFileInsert(GraphTraversalSource g, Vertex vNode) { - // Intentionally left blank + // If the root (class/trigger/etc) contained any static blocks, + // create an invoker method to invoke the static blocks + StaticBlockUtil.createSyntheticStaticBlockInvocation(g, vNode); } protected void addProperties(GraphTraversalSource g, JorjeNode node, Vertex vNode) { diff --git a/sfge/src/main/java/com/salesforce/graph/build/CustomerApexVertexBuilder.java b/sfge/src/main/java/com/salesforce/graph/build/CustomerApexVertexBuilder.java index b7419fbcf..073695448 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/CustomerApexVertexBuilder.java +++ b/sfge/src/main/java/com/salesforce/graph/build/CustomerApexVertexBuilder.java @@ -24,6 +24,7 @@ public void build() { @Override protected void afterFileInsert(GraphTraversalSource g, Vertex vNode) { + super.afterFileInsert(g, vNode); ApexPropertyAnnotator.apply(g, vNode); } } diff --git a/sfge/src/main/java/com/salesforce/graph/build/StaticBlockUtil.java b/sfge/src/main/java/com/salesforce/graph/build/StaticBlockUtil.java index d207146a6..3a2a8756f 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/StaticBlockUtil.java +++ b/sfge/src/main/java/com/salesforce/graph/build/StaticBlockUtil.java @@ -19,25 +19,57 @@ /** * Handles creation of synthetic methods and vertices to gracefully invoke static code blocks. * - *

Consider this example: class StaticBlockClass { static { System.debug("inside static block - * 1"); } static { System.debug("inside static block 2"); } } In Jorje's compilation structure, - * static blocks are represented like this: class StaticBlockClass { private static void () { - * { System.debug("inside static block 1"); } { System.debug("inside static block 2"); } } } + *

Consider this example: * - *

Having multiple block statements inside a method breaks SFGE's makes handling code blocks in - * () impossible. As an alternative, we are creating synthetic vertices in the Graph to - * represent the static blocks as individual methods. We also create one top-level synthetic method - * ("StaticBlockInvoker") that invokes individual static block methods. While creating static scope - * for this class, we should invoke the method call expressions inside the top-level synthetic - * method. + * + * class StaticBlockClass { + * static { + * System.debug("inside static block 1"); + * } + * static { + * System.debug("inside static block 2"); + * } + * } + * * - *

New structure looks like this: class StaticBlockClass { private static void - * SyntheticStaticBlock_1() { System.debug("inside static block 1"); } + * In Jorje's compilation structure, static blocks are represented like this: + * + * class StaticBlockClass { + * private static void () { + * { + * System.debug("inside static block 1"); + * } + * { + * System.debug("inside static block 2"); + * } + * } + * } + * * - *

private static void SyntheticStaticBlock_2() { System.debug("inside static block 2"); } + *

Having multiple block statements inside a method breaks SFGE's normal code flow logic. + * This makes handling code blocks in () impossible. + *

As an alternative, we are creating synthetic vertices in the Graph to + * represent the static blocks as individual methods. + *

We also create one top-level synthetic method ("StaticBlockInvoker") that invokes + * individual static block methods. While creating static scope + * for this class, we should invoke the method call expressions inside the top-level synthetic + * method. * - *

private static void StaticBlockInvoker() { SyntheticStaticBlock_1(); SyntheticStaticBlock_2(); - * } } + *

New structure looks like this: + * + * class StaticBlockClass { + * private static void SyntheticStaticBlock_1() { + * System.debug("inside static block 1"); + * } + * private static void SyntheticStaticBlock_2() { + * System.debug("inside static block 2"); + * } + * private static void StaticBlockInvoker() { + * SyntheticStaticBlock_1(); + * SyntheticStaticBlock_2(); + * } + * } + * */ public final class StaticBlockUtil { private static final Logger LOGGER = LogManager.getLogger(StaticBlockUtil.class); @@ -51,19 +83,19 @@ private StaticBlockUtil() {} * Creates a synthetic method vertex to represent a static code block * * @param g traversal graph - * @param clintVertex () method's vertex + * @param clinitVertex () method's vertex * @param staticBlockIndex index to use for name uniqueness - TODO: this index is currently just * the child count index and does not necessarily follow sequence * @return new synthetic method vertex with name SyntheticStaticBlock_%d, which will be the - * parent for blockStatementVertex, and a sibling of () + * parent for blockStatementVertex, and a sibling of () */ public static Vertex createSyntheticStaticBlockMethod( GraphTraversalSource g, - Vertex clintVertex, + Vertex clinitVertex, int staticBlockIndex) { final Vertex syntheticMethodVertex = g.addV(ASTConstants.NodeType.METHOD).next(); - final String definingType = clintVertex.value(Schema.DEFINING_TYPE); - final List siblings = GremlinUtil.getChildren(g, GremlinVertexUtil.getParentVertex(g, clintVertex)); + final String definingType = clinitVertex.value(Schema.DEFINING_TYPE); + final List siblings = GremlinUtil.getChildren(g, GremlinVertexUtil.getParentVertex(g, clinitVertex)); final int nextSiblingIndex = siblings.size(); addSyntheticStaticBlockMethodProperties( @@ -73,7 +105,7 @@ public static Vertex createSyntheticStaticBlockMethod( addStaticModifierProperties(g, definingType, modifierNodeVertex); GremlinVertexUtil.addParentChildRelationship(g, syntheticMethodVertex, modifierNodeVertex); - GremlinVertexUtil.makeSiblings(g, clintVertex, syntheticMethodVertex); + GremlinVertexUtil.makeSiblings(g, clinitVertex, syntheticMethodVertex); return syntheticMethodVertex; } @@ -105,7 +137,7 @@ public static void createSyntheticStaticBlockInvocation( } static boolean isStaticBlockStatement(JorjeNode node, JorjeNode child) { - return isClintMethod(node) && containsStaticBlock(node) && isBlockStatement(child); + return isClinitMethod(node) && containsStaticBlock(node) && isBlockStatement(child); } private static Vertex createSyntheticInvocationsMethod( @@ -308,14 +340,14 @@ private static void addProperties( traversal.next(); } - /** @return true if given node represents () */ - private static boolean isClintMethod(JorjeNode node) { + /** @return true if given node represents () */ + private static boolean isClinitMethod(JorjeNode node) { return ASTConstants.NodeType.METHOD.equals(node.getLabel()) && MethodUtil.STATIC_CONSTRUCTOR_CANONICAL_NAME.equals( node.getProperties().get(Schema.NAME)); } - /** @return true if () node contains any static block definitions */ + /** @return true if () node contains any static block definitions */ private static boolean containsStaticBlock(JorjeNode node) { for (JorjeNode childNode : node.getChildren()) { if (ASTConstants.NodeType.BLOCK_STATEMENT.equals(childNode.getLabel())) {