ClassCastException on custom java rule when using VariableTreeImpl

my custom rule cannot work at sonar-scanner but can run properly in junit.

exception:

java.lang.ClassCastException: org.sonar.java.model.declaration.VariableTreeImpl cannot be cast to org.sonar.java.model.declaration.VariableTreeImpl
        at name.jacktang.sonar.plugin.checker.LoggerClassChecker.visitVariable(LoggerClassChecker.java:50)
        at org.sonar.java.model.declaration.VariableTreeImpl.accept(VariableTreeImpl.java:185)
        at org.sonar.plugins.java.api.tree.BaseTreeVisitor.scan(BaseTreeVisitor.java:40)
        at org.sonar.plugins.java.api.tree.BaseTreeVisitor.scan(BaseTreeVisitor.java:34)
        at org.sonar.plugins.java.api.tree.BaseTreeVisitor.visitClass(BaseTreeVisitor.java:68)
        at name.jacktang..sonar.plugin.checker.LoggerClassChecker.visitClass(LoggerClassChecker.java:40)
        at org.sonar.java.model.declaration.ClassTreeImpl.accept(ClassTreeImpl.java:202)
        at org.sonar.plugins.java.api.tree.BaseTreeVisitor.scan(BaseTreeVisitor.java:40)
        at org.sonar.plugins.java.api.tree.BaseTreeVisitor.scan(BaseTreeVisitor.java:34)
        at org.sonar.plugins.java.api.tree.BaseTreeVisitor.visitCompilationUnit(BaseTreeVisitor.java:52)
        at org.sonar.java.model.JavaTree$CompilationUnitTreeImpl.accept(JavaTree.java:188)
        at org.sonar.plugins.java.api.tree.BaseTreeVisitor.scan(BaseTreeVisitor.java:40)
        at name.jacktang.sonar.plugin.checker.LoggerClassChecker.scanFile(LoggerClassChecker.java:33)
        at org.sonar.java.model.VisitorsBridge.runScanner(VisitorsBridge.java:135)
        at org.sonar.java.model.VisitorsBridge.lambda$visitFile$0(VisitorsBridge.java:127)
        at java.lang.Iterable.forEach(Iterable.java:75)
        at org.sonar.java.model.VisitorsBridge.visitFile(VisitorsBridge.java:127)
        at org.sonar.java.ast.JavaAstScanner.simpleScan(JavaAstScanner.java:96)
        at org.sonar.java.ast.JavaAstScanner.scan(JavaAstScanner.java:68)
        at org.sonar.java.JavaSquid.scanSources(JavaSquid.java:116)
        at org.sonar.java.JavaSquid.scan(JavaSquid.java:110)
        at org.sonar.plugins.java.JavaSquidSensor.execute(JavaSquidSensor.java:93)
        at org.sonar.scanner.sensor.SensorWrapper.analyse(SensorWrapper.java:53)
        at org.sonar.scanner.phases.SensorsExecutor.executeSensor(SensorsExecutor.java:88)
        at org.sonar.scanner.phases.SensorsExecutor.execute(SensorsExecutor.java:82)
        at org.sonar.scanner.phases.SensorsExecutor.execute(SensorsExecutor.java:68)
        at org.sonar.scanner.phases.AbstractPhaseExecutor.execute(AbstractPhaseExecutor.java:88)
        at org.sonar.scanner.scan.ModuleScanContainer.doAfterStart(ModuleScanContainer.java:182)
        at org.sonar.core.platform.ComponentContainer.startComponents(ComponentContainer.java:136)
        at org.sonar.core.platform.ComponentContainer.execute(ComponentContainer.java:122)
        at org.sonar.scanner.scan.ProjectScanContainer.scan(ProjectScanContainer.java:312)
        at org.sonar.scanner.scan.ProjectScanContainer.scanRecursively(ProjectScanContainer.java:307)
        at org.sonar.scanner.scan.ProjectScanContainer.doAfterStart(ProjectScanContainer.java:281)
        at org.sonar.core.platform.ComponentContainer.startComponents(ComponentContainer.java:136)
        at org.sonar.core.platform.ComponentContainer.execute(ComponentContainer.java:122)
        at org.sonar.scanner.task.ScanTask.execute(ScanTask.java:48)
        at org.sonar.scanner.task.TaskContainer.doAfterStart(TaskContainer.java:81)
        at org.sonar.core.platform.ComponentContainer.startComponents(ComponentContainer.java:136)
        at org.sonar.core.platform.ComponentContainer.execute(ComponentContainer.java:122)
        at org.sonar.scanner.bootstrap.GlobalContainer.executeTask(GlobalContainer.java:132)
        at org.sonar.batch.bootstrapper.Batch.doExecuteTask(Batch.java:116)
        at org.sonar.batch.bootstrapper.Batch.execute(Batch.java:71)
        at org.sonarsource.scanner.api.internal.batch.BatchIsolatedLauncher.execute(BatchIsolatedLauncher.java:46)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.sonarsource.scanner.api.internal.IsolatedLauncherProxy.invoke(IsolatedLauncherProxy.java:60)
        at com.sun.proxy.$Proxy0.execute(Unknown Source)
        at org.sonarsource.scanner.api.EmbeddedScanner.doExecute(EmbeddedScanner.java:171)
        at org.sonarsource.scanner.api.EmbeddedScanner.execute(EmbeddedScanner.java:128)
        at org.sonarsource.scanner.cli.Main.execute(Main.java:111)
        at org.sonarsource.scanner.cli.Main.execute(Main.java:75)
        at org.sonarsource.scanner.cli.Main.main(Main.java:61)

the tracking method:

	@Override
	public void visitVariable(VariableTree tree) {
		super.visitVariable(tree);

		if (isLoggerType(tree.type())) {
			System.out.println(Arrays.toString(tree.getClass().getMethods()));
			System.out.println(Arrays.toString(tree.getClass().getDeclaredMethods()));
			for (Tree child : ((VariableTreeImpl) tree).getChildren()) {
				if (child instanceof MethodInvocationTree) {
					ExpressionTree expressionTree = ((MethodInvocationTreeImpl) child).arguments().get(0);
					Tree args = ((MethodInvocationTreeImpl) expressionTree).getChildren().get(0);
					Tree className = ((MemberSelectExpressionTreeImpl) args).getChildren().get(0);
					if (!className.equals(((MemberSelectExpressionTreeImpl) className).expression().toString())) {
						context.reportIssue(this, expressionTree, "Logger class is wrong.");
					}
				}
			}
		}
	}

Hello,

You can not rely on any of the implementation classes from the SonarJava syntax tree when writing custom rules. You should solely rely on classes from the API, because that’s going to be the only classes available in your classloader at runtime for your the custom plugin.

In your case, you are using VariableTreeImpl, MethodInvocationTreeImpl and MemberSelectExpressionTreeImpl, which won’t be part of the classloader. Hence the exception your are getting. The CheckVerifier is however not verifying this, and consequently accept such calls.

Now, you are not supposed to use the children() method for your custom rules, which is an implementation mechanism. I have no idea what your rule is currently trying to do, but it seems to me that you should be able to do the same as what you are trying to do by using an IssuableSubscriptionVisitor, or simply visiting MemberInvocationTrees, and calls of the parent() method (available from any Tree).

It seems that you are also trying to do some validation of MemberSelectTree, which is probably way more efficient using the semantic API.

Please look at implementation of other rules of the SonarJava plugin for examples, I’m pretty sure there will be some rules achieving something not so far from what you want. If you need some clarification on how to write custom rules, it may worth having another look at the tutorial.

Hope this helps,
Michael

Hi Michael,

Firstly, thanks for your explanation.

For this kind of special class loader management, I should use the classes under org.sonar.plugins.java instead of org.sonar.java, right?

My checker is to check whether the class used to init logger is the current class. And I modified it to:

	@Override
	public void visitVariable(VariableTree tree) {
		super.visitVariable(tree);
		if (isLoggerType(tree.type())) {
			ExpressionTree expressionTree = tree.initializer();
			if (expressionTree instanceof MethodInvocationTree) {
				ExpressionTree arg = ((MethodInvocationTree) expressionTree).arguments().get(0);
				if (arg.firstToken() != null && !className.equals(arg.firstToken().text())) {
					context.reportIssue(this, expressionTree, "Logger class is wrong.");
				}
			}
		}
	}

it can work properly without this kind of exception now. And have you got any more advice?

Hey,

In fact, for custom rule writing, you should focus on using only classes which are exposed in package org.sonar.plugins.java.api.*. Then, indeed, you will most certainly have some trouble when using classes from org.sonar.java. As a general rule of thumb, you can also usually assume that as soon as the class name ends with Impl, it’s a NO-GO.

Nothing much to add, except maybe that usually you never have to use instanceof instruction. You can rely on Tree.Kind enum to test the kind of a tree. Knowing that, you could replace the following:

if (expressionTree instanceof MethodInvocationTree) {

by

if (expressionTree.is(Tree.Kind.METHOD_INVOCATION)) {

The javadoc of the enum constant will then give you the corresponding class in which to cast the tree.

Happy rule coding,
Michael

Many thanks for your advice. I can custom my rules now.

You may add some kind of more complicated examples to tutorial or examples. Then it will help more developers to custom sonar rules. :grinning:

Well the examples I gave you are all the 500+ rules from the SonarJava analyzer, so I can not really give more. :slight_smile:

Glad it helped you!
Michael