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