Skip to content

Conversation

@rmohan20
Copy link
Contributor

As a general stop-gap for cryptic errors, this PR catches all UnexpectedExceptions at Main and displays a more friendly message to users while adding the actual stacktrace to logs.

  1. Static blocks are replaced by new synthetic methods that represent them. These synthetic methods are invoked by another top-level synthetic method. More detailed example/explanation here.
  2. While initializing a class and creating its static fields, we now invoke the new top-level method to execute the static blocks.

Even though this PR leaves us in a better position than where we were (throwing illegible errors when static blocks were encountered), there are some open issues to handle in future:

  1. Static fields set in classes along with static blocks throw an exception while setting up class static scope.
  2. When multiple static blocks are present, one of them gets invoked twice. The same problem happens when both class and its super class contain static blocks.

System.err.println(formatError(ex));
return INTERNAL_ERROR;
} catch (UnexpectedException ex) {
LOGGER.error("Unexpected exception while loading graph", ex);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New catch-all for Unexpected exceptions so that the error message is less cryptic.

g.addE(Schema.PARENT).from(vChild).to(vNode).iterate();
g.addE(Schema.CHILD).from(vNode).to(vChild).iterate();

// Handle static block if needed
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a block statement is found in the default <clint>() method, we know it's a static block. This redirection leads to the creation of a new synthetic static block method.

if (node.getLabel().equals(ASTConstants.NodeType.METHOD)) {
MethodPathBuilderVisitor.apply(g, vNode);
} else if (ROOT_VERTICES.contains(node.getLabel())) {
StaticBlockUtil.createSyntheticStaticBlockInvocation(g, vNode);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are at the end of handling a root (class/interface/trigger etc) node, check if any synthetic static block methods were created earlier. If yes, add a synthetic static block invoker method to invoke the static block methods.

}

/** Add a property to the traversal, throwing an exception if any keys are duplicated. */
protected void addProperty(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted to GremlinVertexUtil for reuse.

} else if (children.size() > 1) {
throw new UnexpectedException(children);
throw new UnexpectedException(
"Did not expect more than one child node of type "
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding more information to the exception.

import org.apache.tinkerpop.gremlin.structure.Vertex;

/** Handles common operations performed while creating vertices on Graph */
public final class GremlinVertexUtil {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored existing code into this util for core reuse.

* <p>private static void StaticBlockInvoker() { SyntheticStaticBlock_1(); SyntheticStaticBlock_2();
* } }
*/
public final class StaticBlockUtil {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the new logic is here.

String definingType) {
// Create new synthetic method StaticBlockInvoker to invoke each synthetic static block
// method
final Vertex invokerMethodVertex = g.addV(ASTConstants.NodeType.METHOD).next();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Artificially creating a method and its structure to invoke each static block method.

}
symbolProviderVisitor.popScope(classStaticScope);
classStaticScope.setState(AbstractClassScope.State.INITIALIZED);
alreadyInitializedStaticClasses.add(fullClassName);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now tracking classes that were already initialized.

List<BaseSFVertex> vertices = new ArrayList<>();
vertices.addAll(classStaticScope.getFields());
vertices.addAll(getFieldDeclarations(g, classStaticScope.userClass));
vertices.addAll(getStaticBlocks(g, classStaticScope.userClass));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding calls to static blocks as another item needed to set a class's static scope.

}
}

public static final class StaticBlockVertex extends MethodVertex
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intermediary code that's not needed anymore. Will remove this.

import java.util.List;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;

public class GraphBuildTestUtil {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracting existing methods for reusability

+ "}";

buildGraph(g, sourceCode);
GraphBuildTestUtil.buildGraph(g, sourceCode);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes from extracting methods to GraphBuiltTestUtil

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class StaticBlockUtilTest {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New tests to ascertain structure of synthetic methods.

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

public class StaticCodeBlockInvocationTest {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests to verify static block invocation while setting static scope of class. I've added and disabled test cases that need future work.


// Handle static block if needed
if (StaticBlockUtil.isStaticBlockStatement(node, child)) {
final Vertex parentVertexForChild =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like what's happening is that we detect that the child is a static block, and if so we create our synthetic method block to hold it, make that a child of the current parent, and make the block statement a child of that. Am I reading that right?
I'd love some commenting about what is being done here, and why it was necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added more comments. Let me know if it explains sufficiently or I'll try expanding it some more.

if (node.getLabel().equals(ASTConstants.NodeType.METHOD)) {
MethodPathBuilderVisitor.apply(g, vNode);
} else if (ROOT_VERTICES.contains(node.getLabel())) {
StaticBlockUtil.createSyntheticStaticBlockInvocation(g, vNode);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I'd love a comment explaining what's going on here.

* Creates a synthetic method vertex to represent a static code block
*
* @param g traversal graph
* @param clintVertex <clint>() method's vertex
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's clinit, not clint. It's a little thing, but it's in a bunch of places and it'll bug me if I don't mention it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the catch, I'll fix it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed everywhere

* static blocks are represented like this: class StaticBlockClass { private static void <clint>() {
* { System.debug("inside static block 1"); } { System.debug("inside static block 2"); } } }
*
* <p>Having multiple block statements inside a method breaks SFGE's makes handling code blocks in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sentence seems like it's missing a bit?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like google formatter mauled the documentation. Let me try fixing this.

*
* <p>private static void SyntheticStaticBlock_2() { System.debug("inside static block 2"); }
*
* <p>private static void StaticBlockInvoker() { SyntheticStaticBlock_1(); SyntheticStaticBlock_2();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does now get treated as if it had invoked StaticBlockInvoker?

@jfeingold35 jfeingold35 merged commit ddfed9e into dev-3 Jul 6, 2022
@stephen-carter-at-sf stephen-carter-at-sf deleted the rm/handleStaticBlock branch May 17, 2024 14:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants