Custom Sonar rule to fetch all the statements present in try block in order to scan certain keywords

Hi team,

I am writing custom rules in java language and I want to fetch all the statements present in try block so that I can scan certain keywords like “config.beginConfigTransaction() and config.commitConfigTransaction(configTr)” and these keywords should be part of same try block in a method. Inorder to implement this I have tried many approaches but all went in vain. Below is Rule file where I am trying to fetch try block statements:

@Rule(key = "ConfiguratorTransactionHandlingRule")
public class ConfiguratorTransactionHandlingRule extends BaseTreeVisitor implements JavaFileScanner{
	private final Deque<Kind> treeKindStack = new LinkedList();
	private DefaultJavaFileScannerContext context;
	@Override
	public void visitBlock(BlockTree tree) {
		// TODO Auto-generated method stub
		System.out.println("visitBlock:");
		List tbody = tree.body();
		for(int i=0;i <tbody.size();i++) {
			System.out.println("Inside VisitBlock: "+tbody.get(i));
		}
		super.visitBlock(tree);
	}

	@Override
	public void visitCatch(CatchTree tree) {
		// TODO Auto-generated method stub
		//System.out.println("visitCatch:");
		super.visitCatch(tree);
	}

	 public void visitTryStatement(TryStatementTree tree) {
		
	      this.scan(tree.resourceList());  // what is the benefit of this statement and should it come before traversing try block or after
	      this.scan(tree.block()); // what is the benefit of this statement and should it come before traversing try block or after
	      this.scan(tree.catches()); // what is the benefit of this statement and should it come before traversing try block or after
	      BlockTree tryStatements =  tree.block();
	      
	      //Approach 1: from line no. 53 to 56
	      //-------------------------------------------------------------
	      List<StatementTree> body = tree.block().body();
	      System.out.println("Reso..........");
	      Iterator<StatementTree> v1 = body.stream().iterator();
	      System.out.println(v1);
	   /*   while(v1.hasNext()) {
	    	  ExpressionStatementTreeImpl es = ExpressionStatementTreeImpl.class.cast(v1);
	    	  System.out.println(es.getLine());
	      }*/
	      
	    //Approach 2: from line no. 63 to 66
	      System.out.println("Reso1..........");
	      //-------------------------------------------------------------
	      ResourceListTreeImpl rl = ResourceListTreeImpl.class.cast(tree.resourceList());
	      System.out.println("ResourceListTreeImpl rl = "+rl.getChildren());
	      //------------------------------------------------------------
	      
	    //Approach 3: from line no. 70 to 79
	      Iterator<StatementTree> var5 = tree.block().body().iterator();
	      
	      
	      System.out.println("hasnext = "+var5.hasNext());
	      while(var5.hasNext()) {
              Tree element = (Tree)var5.next();
              System.out.println("element="+element.firstToken().text());
              //System.out.println("tree.resourceList() = "+tree.resourceList().);
             // secondary.add(new Location("Nesting + 1", element));
           }
	      //----------------------------------------------------------------
	    
	      //Approach 4: from line no. 83 to 89
	      if(tryStatements!=null) {
	    	 // tryStatements.body()
	    	  for(int i=0;i <tryStatements.body().size();i++) {
					System.out.println("Inside VisitTryMethod: "+tryStatements.body().get(i).firstToken().text());
				}
	    	  
	      }
	      /*BlockTree finallyBlock = tree.finallyBlock();
	      if (finallyBlock != null) {
	         this.treeKindStack.push(finallyBlock.kind());
	         this.scan(finallyBlock);
	         this.treeKindStack.pop();
	      }*/

	   }

	@Override
	public void visitMethod(MethodTree tree) {
		// TODO Auto-generated method stub
		   this.treeKindStack.push(tree.kind());
		   List tbody = tree.block().body();
			for(int i=0;i <tbody.size();i++) {
				System.out.println("Inside VisitMethod: "+tbody.get(i));
			}
		     super.visitMethod(tree);
		  //   System.out.println("Visit Method:");
		     this.treeKindStack.pop();
	}

	@Override
	 public void scanFile(JavaFileScannerContext context) {
	      this.context = (DefaultJavaFileScannerContext)context;
	      this.treeKindStack.clear();
	      this.scan(context.getTree());
	   }

Hi @Sonar_Sanjeev,

In order to better understand what you are trying to achieve, can you please provide me with Compliant and Noncompliant code examples?

From the code you posted here, I see maybe a bit better approach. You can use SubscriptionVisitor, and by overriding the method nodesToVisit , you can specify which kind of Tree you want to visit.
And, if I understood correctly, you want to list all statements contained in the body of the try statement.
If that is the case, you can do something like:

@Rule(key = "ConfiguratorTransactionHandlingRule")
public class ConfiguratorTransactionHandlingRule extends IssuableSubscriptionVisitor {

  @Override
  public List<Tree.Kind> nodesToVisit() {
    return List.of(Tree.Kind.TRY_STATEMENT);
  }

  @Override
  public void visitNode(Tree tree) {
    TryStatementTree tryStatementTree = (TryStatementTree) tree;
    List<StatementTree> statements = tryStatementTree.block().body();
    // do something with statements
  }
}

I will be able to help you more once I understand your goal.

All the best,

Irina

Hi Irina, please check below the compliant & Noncompliant code:

//Compliant Code - where both beginConfigTransaction & commitConfigTransaction are both part of the same try block
try {
ct = config.beginConfigTransaction();
opt1.setState(IState.TRUE);
config.commitConfigTransaction(ct);
} catch (Exception e) {

Noncompliant Code - where both beginConfigTransaction & commitConfigTransaction are not part of the same try block
try {

opt1.setState(IState.TRUE);
opt1.setState(IState.TRUE);

} catch (Exception e) {

Also I have tried your code as well let me paste the same below:

@Rule(key = "ConfiguratorTransactionHandlingRule")
public class ConfiguratorTransactionHandlingRule extends IssuableSubscriptionVisitor{

	@Override
	  public List<Tree.Kind> nodesToVisit() {
	  //  return List.of(Tree.Kind.TRY_STATEMENT);
	    return  Collections.singletonList(Tree.Kind.TRY_STATEMENT);
	  }
	
	public void visitNode(Tree tree) {
		TryStatementTree tryStatementTree  = (TryStatementTree) tree;
		List<StatementTree> statements = tryStatementTree.block().body();
		
		for(int i = 0;i< statements.size();i++) {
			System.out.println(statements.get(i));
			
				
				
			
		}

When I executed the above code I found logs like below:

org.sonar.java.model.statement.ExpressionStatementTreeImpl@18d910b3
org.sonar.java.model.statement.ExpressionStatementTreeImpl@1e7ab390
org.sonar.java.model.statement.ExpressionStatementTreeImpl@5679e96b
org.sonar.java.model.statement.IfStatementTreeImpl@79b84841
org.sonar.java.model.statement.ExpressionStatementTreeImpl@7ceb4478
org.sonar.java.model.statement.ExpressionStatementTreeImpl@7fdab70c
org.sonar.java.model.statement.ExpressionStatementTreeImpl@25ad4f71
org.sonar.java.model.statement.ExpressionStatementTreeImpl@49faf066
org.sonar.java.model.statement.IfStatementTreeImpl@6f94a5a5
org.sonar.java.model.declaration.VariableTreeImpl@52d97ab6

So Now the question is how can I traverse/print the exact statement/text written in try block line by line, so that I can compare. From the above logs I think we are getting the statement type like whether it is a normal expression or logical expression etc.

Please help here if we can print the exact text line by line present in try block?

Hi @Sonar_Sanjeev,

Based on the code examples you provided, I can conclude limited things.
Can you please verify if this is intended:

If a setState method exists in the try block, both beginConfigTransaction and commitConfigTransaction must be present.
A rule should raise an issue if there is only one or none of them.

If the assumption above is correct, I would create three method matches that represent: setState, beginConfigTransaction, and commitConfigTransaction.
I would iterate through the body of the try block, and if any statement matches setState, then there must be present beginConfigTransaction and commitConfigTransaction statements. If not, report.

The MethodMatchers interface is used to identify a method by giving a type, name, and parameters.

Hope this helps.
All the best,

Irina

Hi @irina.batinic , Yes you are right, I have created below three method mathces that represent: setState,beginConfigTransaction, and commitConfigTransaction.. as per your suggestions. Now I am looking for iterating through the body of the try block, could you guide me on this.

Below is Rule file:

@Rule(key = "ConfiguratorTransactionHandlingRule")
public class ConfiguratorTransactionHandlingRule extends IssuableSubscriptionVisitor{

	 private static final MethodMatchers set_State = MethodMatchers.or(new MethodMatchers[]{MethodMatchers.create().ofTypes(new String[]{"oracle.apps.cz.cio.BooleanFeature"}).names(new String[]{"setState"}).withAnyParameters().build()});
	 private static final MethodMatchers begin_Config_Transaction = MethodMatchers.or(new MethodMatchers[]{MethodMatchers.create().ofTypes(new String[]{"oracle.apps.cz.cio.Configuration"}).names(new String[]{"beginConfigTransaction"}).withAnyParameters().build()});
	 private static final MethodMatchers commit_Config_Transaction = MethodMatchers.or(new MethodMatchers[]{MethodMatchers.create().ofTypes(new String[]{"oracle.apps.cz.cio.Configuration"}).names(new String[]{"commitConfigTransaction"}).withAnyParameters().build()});
	 
	 
	@Override
	  public List<Tree.Kind> nodesToVisit() {
	  //  return List.of(Tree.Kind.TRY_STATEMENT);
	    return  Collections.singletonList(Tree.Kind.TRY_STATEMENT);
	  }
	
	public void visitNode(Tree tree) {
		TryStatementTree tryStatementTree  = (TryStatementTree) tree;
		List<StatementTree> statements = tryStatementTree.block().body();
		
		for(int i = 0;i< statements.size();i++) {
			System.out.println(statements.get(i));
			if (tree.is(new Kind[]{Kind.EXPRESSION_STATEMENT})) {
				ExpressionStatementTree expressionStatementTree= (ExpressionStatementTree) tree;
				
				
			
		}
		
	}
	
}

Hi @Sonar_Sanjeev,

Here, you can find documentation on how to use MethodMatchers and on many places in sonar-java project you can find its usages.

Lead by the statement in the comment above, the code could look like something like this:

  @Override
  public void visitNode(Tree tree) {
    var tryStatementTree = (TryStatementTree) tree;
    List<StatementTree> statements = tryStatementTree.block().body();

    List<MethodInvocationTree> methodInvocations = statements.stream()
      .filter(mi -> mi.is(Tree.Kind.METHOD_INVOCATION))
      .map(MethodInvocationTree.class::cast)
      .toList();

    boolean anyMatchSetState = methodInvocations.stream()
      .anyMatch(methodInvocation -> SET_STATE.matches(methodInvocation));

    if (anyMatchSetState) {
      // report if you didn't find any method invocations that match begin or commit MethodMatchers
    } 

For future reference, please format the code and comment text appropriately. Thank you!

All the best,

Irina

HI @irina.batinic ,

In Method visitNode the below statements is returning null:

List methodInvocations = statements.stream()
.filter(mi → mi.is(Tree.Kind.METHOD_INVOCATION))
.map(MethodInvocationTree.class::cast).collect(Collectors.toList());

the “Tree.Kind.METHOD_INVOCATION” is not working here, I tried with others like expressionstatement etc, those are working. but Method related Tree.kind are not working. Could you please help.

Below is the Rule file for reference:

@Rule(key = “ConfiguratorTransactionHandlingRule”, description = “Sanjeev235”, priority = Priority.BLOCKER, tags = {“bug”})
public class ConfiguratorTransactionHandlingRule extends IssuableSubscriptionVisitor{

private static final String BooleanFeature_CLASS = BooleanFeature.class.getCanonicalName().toString();
  
  private static final MethodMatchers beginConfigTransaction_MATCHER = MethodMatchers.create().ofAnyType().names(new String[]{"beginConfigTransaction"}).addWithoutParametersMatcher().build();
  
  private static final MethodMatchers commitConfigTransaction_MATCHER = MethodMatchers.create().ofAnyType().names(new String[]{"commitConfigTransaction"}).withAnyParameters().build();
  
   

  private static final MethodMatchers beginConfigTransaction_MATCHER1 = MethodMatchers.create().ofSubTypes(new String[]{"oracle.apps.cz.cio.Configuration"}).names(new String[]{"beginConfigTransaction"}).addWithoutParametersMatcher().build();
   private static final MethodMatchers setState_MATCHER = MethodMatchers.create().ofAnyType().names(new String[]{"setState"}).withAnyParameters().build();
 	 List<Tree.Kind> modifiable = new ArrayList<>();
 
 
@Override
  public List<Tree.Kind> nodesToVisit() {
 
    return  Collections.singletonList(Tree.Kind.TRY_STATEMENT);
    
  }

public void visitNode(Tree tree) {
	
	TryStatementTree tryStatementTree  = (TryStatementTree) tree;
	List<StatementTree> statements = tryStatementTree.block().body();
	
	System.out.println("stream count="+statements.stream().map(MethodInvocationTree.class::cast).count());
	
	  List<MethodInvocationTree> methodInvocations = statements.stream()
		      .filter(mi -> mi.is(Tree.Kind.METHOD_INVOCATION))
		      .map(MethodInvocationTree.class::cast).collect(Collectors.toList());
		      //.toList();

	  
		 //   boolean anyMatchSetState = methodInvocations.stream()
		   //   .anyMatch(methodInvocation -> setState_MATCHER.matches(methodInvocation));
		    
	  System.out.println("methodInvocations size="+methodInvocations.size()+"statements.size()="+statements.size());
	
		    boolean anyMatchBeginTransaction = methodInvocations.stream()
				      .anyMatch(methodInvocation -> beginConfigTransaction_MATCHER1.matches(methodInvocation));
				    
		    boolean anyMatchCommitTransaction = methodInvocations.stream()
				      .anyMatch(methodInvocation -> commitConfigTransaction_MATCHER.matches(methodInvocation));
				    
		 //   System.out.println(anyMatchSetState+" "+anyMatchBeginTransaction+" "+anyMatchCommitTransaction);
		//    System.out.println(anyMatchBeginTransaction+" AND "+anyMatchCommitTransaction);
		   
		   
		    if (anyMatchBeginTransaction && !anyMatchCommitTransaction) {
		      // report if you didn't find any method invocations that match begin or commit MethodMatchers
		    	System.out.println("sanjeev2");
				reportIssue(tree, "Sanjeev235");
		    } 
		    
		    if(!anyMatchBeginTransaction && anyMatchCommitTransaction) {
		    	System.out.println("sanjeev23");
				reportIssue(tree, "Sanjeev235");
		    }

}
}

You really helped me a lot on this, really appreciated.

Thank you!

Hi @irina.batinic , Could you please help me resolving the above mentioned issue. Appreciated!

Hi @Sonar_Sanjeev,

Can you send me a file on which you are running this check?
Best,

Irina