Ali published a popular Java development manual. pdf a long time ago, which defines many development specifications, of which the most basic specifications must be observed. Ali has issued a set of Sonar plug-ins to help scan
The essence of the development plug-in is to use the entry left by the source designer to get the source event and then implement it. Finally, your implementation will be scheduled and executed at a time and place you don't know. This is the charm of the design mode, which ensures the function expansion under the opening and closing principle
So what entrances did the designers of Sonar leave for us?
At this time, we often meet Baidu and Google. After an hour, you will find that you still don't understand anything. Because there are too few and scattered materials, you need this blog~~~
To get back to business, there are quite a lot of extensions left by Sonar. Please don't worry. Let's first understand what Sonar generates after parsing our java files
AST syntax tree
AST (Abstract Syntax Tree) is also called Abstract Syntax Tree. Sonar will parse our java files into AST Syntax Tree. So what is AST Syntax Tree? In fact, back-end developers may not know much about it, but they will be familiar with the front-end
public class TimeCase2 { public void test(){ String s = ""; Long start = System.currentTimeMillis(); } }
Such a piece of Java code will generate the following AST tree structure
The whole AST syntax tree is too huge to draw one by one. The main core parts are drawn. In fact, the role of each branch node in the corresponding implementation class in Sonar can be roughly understood through the diagram. In fact, each node of the AST tree has no restricted type, and different tree nodes are mainly made according to the expression and behavior of the code: description type, behavior type Tree nodes such as expression type, return value type, etc
class | effect |
Tree | The root node of the entire Class and the metadata information of the Class |
ModifierKeywordTreeImpl | Class description, annotation, name, etc |
InternalSyntaxToken | Record the start symbol and end symbol of the class, and adapt to various grammars |
MethodTreeImpl | Method implementation Record the name, description, annotation, input parameter, output parameter, etc. of the method |
BlockTreeImpl | Method internal code block implementation Match internal line code records |
In fact, there are many implementation types. Their top-level interfaces are Tree interfaces. This top interface defines the following interface methods:
//Judge the type of the current concrete implementation class boolean is(Tree.Kind... var1); //Function execution void accept(TreeVisitor var1); //Parent node of the current node @Nullable Tree parent(); //Start symbol of current node @Nullable SyntaxToken firstToken(); //End symbol of current node @Nullable SyntaxToken lastToken(); //Current node type Tree.Kind kind();
How many subclasses of the top interface of Tree inherit? Very many, each subclass interface extends new functions again:
COMPILATION_UNIT(CompilationUnitTree.class), CLASS(ClassTree.class), ENUM(ClassTree.class), INTERFACE(ClassTree.class), ANNOTATION_TYPE(ClassTree.class), ENUM_CONSTANT(EnumConstantTree.class), INITIALIZER(BlockTree.class), STATIC_INITIALIZER(StaticInitializerTree.class), CONSTRUCTOR(MethodTree.class), METHOD(MethodTree.class), BLOCK(BlockTree.class), EMPTY_STATEMENT(EmptyStatementTree.class), LABELED_STATEMENT(LabeledStatementTree.class), EXPRESSION_STATEMENT(ExpressionStatementTree.class), IF_STATEMENT(IfStatementTree.class), ASSERT_STATEMENT(AssertStatementTree.class), SWITCH_STATEMENT(SwitchStatementTree.class), CASE_GROUP(CaseGroupTree.class), CASE_LABEL(CaseLabelTree.class), WHILE_STATEMENT(WhileStatementTree.class), DO_STATEMENT(DoWhileStatementTree.class), FOR_STATEMENT(ForStatementTree.class), FOR_EACH_STATEMENT(ForEachStatement.class), BREAK_STATEMENT(BreakStatementTree.class), CONTINUE_STATEMENT(ContinueStatementTree.class), RETURN_STATEMENT(ReturnStatementTree.class), THROW_STATEMENT(ThrowStatementTree.class), SYNCHRONIZED_STATEMENT(SynchronizedStatementTree.class), TRY_STATEMENT(TryStatementTree.class), CATCH(CatchTree.class), POSTFIX_INCREMENT(UnaryExpressionTree.class), POSTFIX_DECREMENT(UnaryExpressionTree.class), PREFIX_INCREMENT(UnaryExpressionTree.class), PREFIX_DECREMENT(UnaryExpressionTree.class), UNARY_PLUS(UnaryExpressionTree.class), UNARY_MINUS(UnaryExpressionTree.class), BITWISE_COMPLEMENT(UnaryExpressionTree.class), LOGICAL_COMPLEMENT(UnaryExpressionTree.class), MULTIPLY(BinaryExpressionTree.class), DIVIDE(BinaryExpressionTree.class), REMAINDER(BinaryExpressionTree.class), PLUS(BinaryExpressionTree.class), MINUS(BinaryExpressionTree.class), LEFT_SHIFT(BinaryExpressionTree.class), RIGHT_SHIFT(BinaryExpressionTree.class), UNSIGNED_RIGHT_SHIFT(BinaryExpressionTree.class), LESS_THAN(BinaryExpressionTree.class), GREATER_THAN(BinaryExpressionTree.class), LESS_THAN_OR_EQUAL_TO(BinaryExpressionTree.class), GREATER_THAN_OR_EQUAL_TO(BinaryExpressionTree.class), EQUAL_TO(BinaryExpressionTree.class), NOT_EQUAL_TO(BinaryExpressionTree.class), AND(BinaryExpressionTree.class), XOR(BinaryExpressionTree.class), OR(BinaryExpressionTree.class), CONDITIONAL_AND(BinaryExpressionTree.class), CONDITIONAL_OR(BinaryExpressionTree.class), CONDITIONAL_EXPRESSION(ConditionalExpressionTree.class), ARRAY_ACCESS_EXPRESSION(ArrayAccessExpressionTree.class), MEMBER_SELECT(MemberSelectExpressionTree.class), NEW_CLASS(NewClassTree.class), NEW_ARRAY(NewArrayTree.class), METHOD_INVOCATION(MethodInvocationTree.class), TYPE_CAST(TypeCastTree.class), INSTANCE_OF(InstanceOfTree.class), PARENTHESIZED_EXPRESSION(ParenthesizedTree.class), ASSIGNMENT(AssignmentExpressionTree.class), MULTIPLY_ASSIGNMENT(AssignmentExpressionTree.class), DIVIDE_ASSIGNMENT(AssignmentExpressionTree.class), REMAINDER_ASSIGNMENT(AssignmentExpressionTree.class), PLUS_ASSIGNMENT(AssignmentExpressionTree.class), MINUS_ASSIGNMENT(AssignmentExpressionTree.class), LEFT_SHIFT_ASSIGNMENT(AssignmentExpressionTree.class), RIGHT_SHIFT_ASSIGNMENT(AssignmentExpressionTree.class), UNSIGNED_RIGHT_SHIFT_ASSIGNMENT(AssignmentExpressionTree.class), AND_ASSIGNMENT(AssignmentExpressionTree.class), XOR_ASSIGNMENT(AssignmentExpressionTree.class), OR_ASSIGNMENT(AssignmentExpressionTree.class), INT_LITERAL(LiteralTree.class), LONG_LITERAL(LiteralTree.class), FLOAT_LITERAL(LiteralTree.class), DOUBLE_LITERAL(LiteralTree.class), BOOLEAN_LITERAL(LiteralTree.class), CHAR_LITERAL(LiteralTree.class), STRING_LITERAL(LiteralTree.class), NULL_LITERAL(LiteralTree.class), IDENTIFIER(IdentifierTree.class), VARIABLE(VariableTree.class), ARRAY_TYPE(ArrayTypeTree.class), PARAMETERIZED_TYPE(ParameterizedTypeTree.class), UNION_TYPE(UnionTypeTree.class), UNBOUNDED_WILDCARD(WildcardTree.class), EXTENDS_WILDCARD(WildcardTree.class), SUPER_WILDCARD(WildcardTree.class), ANNOTATION(AnnotationTree.class), MODIFIERS(ModifiersTree.class), LAMBDA_EXPRESSION(LambdaExpressionTree.class), PRIMITIVE_TYPE(PrimitiveTypeTree.class), TYPE_PARAMETER(TypeParameterTree.class), IMPORT(ImportTree.class), PACKAGE(PackageDeclarationTree.class), ARRAY_DIMENSION(ArrayDimensionTree.class), OTHER(Tree.class), TOKEN(SyntaxToken.class), TRIVIA(SyntaxTrivia.class), INFERED_TYPE(InferedTypeTree.class), TYPE_ARGUMENTS(TypeArguments.class), METHOD_REFERENCE(MethodReferenceTree.class), TYPE_PARAMETERS(TypeParameters.class), ARGUMENTS(Arguments.class), LIST(ListTree.class);
This enumeration class lists the subclass interface definitions of all Tree interfaces. Different subclass interfaces correspond to different expressions, descriptions and other implementations of the code. Therefore, when we extend the sonar plug-in later, we can directly obtain the information we want from the super many subclass implementations above by scanning the code of the rule types we need to extend, We don't need to get it from the top Tree one by one. Please continue to see the analysis below~~
Sonar storage core table structure
Sonar uses PGSQL by default
Table name | describe |
projects | The table holds the basic information of all items analyzed by sonar |
metrics | This table stores test indicators, such as test coverage, code complexity, and so on |
rules_profiles | All test indicators are stored in the metrics table. rules_profiles is a subset of these metrics, which can be regarded as a set of customized test standards. Each project will have corresponding rules_ Corresponding to project |
snapshots | With projects and rules_profiles. According to some rules_profile a sonar analysis of a project will generate some snapshots. But there is no real index value stored here. Instead, it is stored in the project_measures in this table. Snapshots and projects_ Measures are associated by foreign keys |
Develop Sonar plug-ins
Create a new maven project and modify POM
<?xml version="1.0" encoding="UTF-8"?> <project> ... <build> <plugins> <plugin> <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId> <artifactId>sonar-packaging-maven-plugin</artifactId> <extensions>true</extensions> <configuration> <pluginClass>org.sonarqube.plugins.example.ExamplePlugin</pluginClass> </configuration> </plugin> </plugins> </build> </project>
Implement the Plugin interface of Sonar
public class MyJavaRulesPlugin implements Plugin { @Override public void define(Context context) { // server extensions -> objects are instantiated during server startup context.addExtension(MyJavaRulesDefinition.class); // batch extensions -> objects are instantiated during code analysis context.addExtension(MyJavaFileCheckRegistrar.class); } }
- context.addExtension is to add an extension execution class. MyJavaRulesDefinition implements RulesDefinition to describe some rule metadata information, mainly the description of your own custom rules
public class MyJavaRulesDefinition implements RulesDefinition { public static final String REPOSITORY_KEY = "finger-java-custom-rules"; private final Gson gson = new Gson(); @Override public void define(Context context) { NewRepository repository = context .createRepository( REPOSITORY_KEY, Java.KEY ) .setName( "Finger Java Custom Rules" ); new AnnotationBasedRulesDefinition( repository, Java.KEY ) .addRuleClasses(/* don't fail if no SQALE annotations */ false, RulesList.getChecks() ); for (NewRule rule : repository.rules()) { String metadataKey = rule.key(); System.out.println( metadataKey ); // Setting internal key is essential for rule templates (see SONAR-6162), and it is not done by AnnotationBasedRulesDefinition from // sslr-squid-bridge version 2.5.1: rule.setInternalKey( metadataKey ); rule.setHtmlDescription( readRuleDefinitionResource( metadataKey + ".html" ) ); addMetadata( rule, metadataKey ); } repository.done(); } @Nullable private static String readRuleDefinitionResource(String fileName) { URL resource = MyJavaRulesDefinition.class.getResource( "/org/sonar/l10n/java/rules/squid/" + fileName ); if (resource == null) { return null; } try { return Resources.toString( resource, Charsets.UTF_8 ); } catch (IOException e) { throw new IllegalStateException( "Failed to read: " + resource, e ); } } private void addMetadata(NewRule rule, String metadataKey) { String json = readRuleDefinitionResource( metadataKey + ".json" ); if (json != null) { RuleMetadata metadata = gson.fromJson( json, RuleMetadata.class ); rule.setSeverity( metadata.defaultSeverity.toUpperCase( Locale.US ) ); rule.setName( metadata.title ); rule.setTags( metadata.tags ); rule.setStatus( RuleStatus.valueOf( metadata.status.toUpperCase( Locale.US ) ) ); if (metadata.remediation != null) { // metadata.remediation is null for template rules rule.setDebtRemediationFunction( metadata.remediation.remediationFunction( rule.debtRemediationFunctions() ) ); rule.setGapDescription( metadata.remediation.linearDesc ); } } } private static class RuleMetadata { String title; String status; @Nullable Remediation remediation; String[] tags; String defaultSeverity; } private static class Remediation { String func; String constantCost; String linearDesc; String linearOffset; String linearFactor; private DebtRemediationFunction remediationFunction(DebtRemediationFunctions drf) { if (func.startsWith( "Constant" )) { return drf.constantPerIssue( constantCost.replace( "mn", "min" ) ); } if ("Linear".equals( func )) { return drf.linear( linearFactor.replace( "mn", "min" ) ); } return drf.linearWithOffset( linearFactor.replace( "mn", "min" ), linearOffset.replace( "mn", "min" ) ); } } }
- MyJavaFileCheckRegistrar is the main extension rule class, which is similar to the responsibility class mode. You can add the extension class to be executed
@SonarLintSide public class MyJavaFileCheckRegistrar implements CheckRegistrar { /** * Register the classes that will be used to instantiate checks during analysis. */ @Override public void register(RegistrarContext registrarContext) { // Call to registerClassesForRepository to associate the classes with the correct repository key registrarContext.registerClassesForRepository(MyJavaRulesDefinition.REPOSITORY_KEY, Arrays.asList(checkClasses()), Arrays.asList(testCheckClasses())); } /** * Lists all the checks provided by the plugin */ public static Class<? extends JavaCheck>[] checkClasses() { return new Class[] { xx.class, //Just fill in your extended rule class xx1.class }; } /** * Lists all the test checks provided by the plugin */ public static Class<? extends JavaCheck>[] testCheckClasses() { return new Class[] {}; } }
Specific rule extension class development
The above has correctly accessed the Sonar extension, and the next step is to realize the specific extension
/** * <p>Title:Thread pool specification verification</p> * <p>Description:</p> * * @author QIQI * @params * @return * @throws * @date 2021/06/10 17:26 */ @Rule(key = "ThreadPoolCheck") public class ThreadPoolCheck extends BaseTreeVisitor implements JavaFileScanner { private static final Logger LOGGER = LoggerFactory.getLogger( ThreadPoolCheck.class ); private JavaFileScannerContext context; @Override public void scanFile(JavaFileScannerContext context) { this.context = context; scan( context.getTree() ); } @Override public void visitBlock(BlockTree tree) { List<StatementTree> statementTrees = tree.body(); if (statementTrees != null && statementTrees.size() > 0) { LOGGER.info( "ThreadPoolCheck start ...." ); for (StatementTree tree1 : statementTrees) { if (tree1 instanceof VariableTreeImpl) { CompletableFuture.runAsync( () -> System.out.println(1) ); executorServiceCheck( (VariableTreeImpl) tree1 ); } else if (tree1 instanceof ExpressionStatementTreeImpl) { completableFutureCheck( (ExpressionStatementTreeImpl) tree1 ); } } } super.visitBlock( tree ); } @Override public void visitAnnotation(AnnotationTree annotationTree) { if(annotationTree.annotationType().toString().equals( "Async" )){ if(annotationTree.arguments().size() == 0){ context.reportIssue( this, annotationTree, "Unified thread pool is used except for special requirement thread pool" ); } } super.visitAnnotation( annotationTree ); } private void executorServiceCheck(VariableTreeImpl variableTree) { if (variableTree.type().toString().equals( "ExecutorService" )) { List<Tree> treeList = variableTree.getChildren(); if (treeList != null && treeList.size() > 0) { for (Tree tree2 : treeList) { if (tree2 instanceof MethodInvocationTreeImpl) { MethodInvocationTreeImpl methodInvocationTree = (MethodInvocationTreeImpl) tree2; if (methodInvocationTree.symbol().name().equals( "newFixedThreadPool" )) { context.reportIssue( this, methodInvocationTree, "Unified thread pool is used except for special requirement thread pool" ); } } } } } } private void completableFutureCheck(ExpressionStatementTreeImpl expressionStatementTree) { MethodInvocationTreeImpl methodInvocationTree = (MethodInvocationTreeImpl) expressionStatementTree.expression(); if(methodInvocationTree != null && methodInvocationTree.arguments().size() < 2){ context.reportIssue( this, expressionStatementTree, "Unified thread pool is used except for special requirement thread pool" ); } } }
- Fixed inheritance of BaseTreeVisitor and implementation of JavaFileScanner
- Next, it depends on the @ Override method you need. The entire parent class has a lot of @ Override. Different @ Override corresponds to different rule verification scenarios. Sonar will throw different Tree subclass implementations into different @ Override implementation classes and pass in the parameters
- I need to verify the @ block subclass in the tree interface, so I need to verify the @ block subclass
- Now I need to verify the method annotation, so I @ Override visitAnnotation again, and Sonar passes in the subclass interface of AnnotationTree
- There can be @ Override multiple in the same extension class, which can be developed according to the needs of the scenario
Dear students, it's not over yet~~~~
Now we have just developed the rules and have not done the case display ~. After we are scanned for bugs, there are cases to tell you how to modify, so we also need to define cases
Careful students have found that the above code has an annotation @ Rule(key = "ThreadPoolCheck"), which must correspond to the name of the following case file
- ThreadPoolCheck.html
<p>MachineTimeCheck Check</p> <h2>Noncompliant Code Example</h2> <pre> Executors.newFixedThreadPool(); CompletableFuture.runAsync( () -> System.out.println(1) ); @Async; </pre> <h2>Compliant Solution</h2> <pre> ThreadFactory.getThreadExecutor( APOLLO-CONFIG ); CompletableFuture.runAsync( () -> System.out.println(1), ThreadFactory.getThreadExecutor( APOLLO-CONFIG ) ); (https://duapp.yuque.com/team_tech/confluence-data-iwskfg/ul9gg1) </pre>
- ThreadPoolCheck.json (this file represents the location of the problem after scanning: odor? BUG? Vulnerability?)
{ "title": "Unified thread pool is used except for special requirement thread pool", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "bug", "pitfall" ], "defaultSeverity": "CRITICAL" }
- ThreadPoolCheck_java.json
{ "title": "Unified thread pool is used except for special requirement thread pool", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "bug", "pitfall" ], "defaultSeverity": "CRITICAL" }
Finally, how to write the test case of the plug-in?
Create a new files folder under test and a threadpoolcase Java file, which writes a counter example that does not conform to the rules
/** * <p>Title:</p> * <p>Description:</p> * @author QIQI * @params * @return * @throws * @date 2021/06/10 18:11 */ public class ThreadPoolCase { public void test(){ ExecutorService executorService = Executors.newFixedThreadPool( 1 ); // Noncompliant } }
Then create a new test case
@Test public void test() { JavaCheckVerifier.verify("src/test/files/ThreadPoolCase.java", new ThreadPoolCheck()); }