Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add processor fixing sonar rule S4065_ThreadLocalWithInitial #706

Merged

Conversation

MartinWitt
Copy link
Contributor

This code is more or less copied from laughing-train and worked on spoon already, see https://github.com/INRIA/spoon/pull/4540/files. I would be surprised if we find this sonar rule violation anywhere else.

@MartinWitt MartinWitt changed the title Add processor fixing sonar rule S4065_ThreadLocalWithInitial feat: Add processor fixing sonar rule S4065_ThreadLocalWithInitial Jan 27, 2022
if (statement instanceof CtReturn) {
lambda.setExpression(getReturnStatement(statement));
} else {
lambda.setBody(initalValueMethod.getDeclaration().getBody());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here I'm unsure if there is a case where the method initalValue has one statement, and it is not a CtReturn

Copy link
Member

Choose a reason for hiding this comment

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

I don't think it is possible. I think a Supplier needs to return something. At the least, return null should be there.

@algomaster99
Copy link
Member

This rule does not show up on https://rules.sonarsource.com/ so I thought it was deprecated. However, their JIRA dashboard shows that the rule is still active.

@MartinWitt
Copy link
Contributor Author

https://rules.sonarsource.com/java/RSPEC-4065 here is the link

@algomaster99
Copy link
Member

I was prepending the S in the URL, I think. Anyway, thanks for the PR. I can have a look at it soon.

Copy link
Member

@algomaster99 algomaster99 left a comment

Choose a reason for hiding this comment

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

Thanks for the PR, @MartinWitt !

I have posted some general thoughts about this PR as comments. Please have a look.

Comment on lines 24 to 29
if (isThreadLocalType(newClass)) {
CtClass<?> innerClass = newClass.getAnonymousClass();
if (hasNoFields(innerClass) && hasOnlyConstructorAndSingleMethod(innerClass)) {
Optional<CtExecutableReference<?>> initalValueMethod =
findInitalValueMethod(innerClass);
if (initalValueMethod.isPresent()) {
Copy link
Member

Choose a reason for hiding this comment

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

I think you can remove the if-conditions you have here. It is not necessary to check that the type of newClass is ThreadLocal because if it is not, SonarJava would never report a violation and we don't even need to visit this processor in that case.

Following the same logic as above, initialValueMethod must be present for the violation to be reported.

I am not sure what is the behaviour of SonarJava when there are more than one type members. If they report a violation, they have not illustrated how to fix it. So I am unsure if we need to invoke hasNoFields and hasOnlyConstructorAndSingleMethod.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would leave some defensive programming here because it does not harm.

if (statement instanceof CtReturn) {
lambda.setExpression(getReturnStatement(statement));
} else {
lambda.setBody(initalValueMethod.getDeclaration().getBody());
Copy link
Member

Choose a reason for hiding this comment

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

I don't think it is possible. I think a Supplier needs to return something. At the least, return null should be there.

Copy link
Member

@algomaster99 algomaster99 left a comment

Choose a reason for hiding this comment

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

These are my final suggestions. I have commented on defensive programming and why we may opt-out here. Also, have a look at the refactoring comments.

CtLambda<?> lambda = getFactory().createLambda();
if (initalValueMethod.getDeclaration().getBody().getStatements().size() == 1) {
CtStatement statement = initalValueMethod.getDeclaration().getBody().getStatement(0);
if (statement instanceof CtReturn) {
Copy link
Member

Choose a reason for hiding this comment

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

We don't need this check. Supplier will have a return statement.

if (hasNoFields(innerClass) && hasOnlyConstructorAndSingleMethod(innerClass)) {
Optional<CtExecutableReference<?>> initalValueMethod =
findInitalValueMethod(innerClass);
if (initalValueMethod.isPresent()) {
Copy link
Member

Choose a reason for hiding this comment

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

I think we can also remove the .isPresent check because if it would not be present, then it would not be a violation.

Defensive programming is good only when we are unsure about the consequences. For example, we don't know what is supposed to happen when the anonymous class has more than 2 type members. So it is better to add a check for them as you have done above. Moreover, defensive programming reduces coverage as well so we should use it when we really need it.

}
}

private CtInvocation<?> createInitalMethod(CtNewClass<?> threadLocal, CtLambda<?> lambda) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
private CtInvocation<?> createInitalMethod(CtNewClass<?> threadLocal, CtLambda<?> lambda) {
private CtInvocation<?> createInitialMethod(CtNewClass<?> threadLocal, CtLambda<?> lambda) {

There is a typographical error in the spelling of initial. This is just one instance. Please correct them everywhere.

Comment on lines 37 to 46
.createInvocation(
getFactory().createTypeAccess(createThreadLocalRef()),
getFactory()
.Executable()
.createReference(
threadLocal.getType(),
true,
threadLocal.getType(),
"withInitial",
List.of(lambda.getType())));
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
.createInvocation(
getFactory().createTypeAccess(createThreadLocalRef()),
getFactory()
.Executable()
.createReference(
threadLocal.getType(),
true,
threadLocal.getType(),
"withInitial",
List.of(lambda.getType())));
CtTypeAccess<?> target = getFactory().createTypeAccess(createThreadLocalRef());
CtExecutableReference<?> executableReference = getFactory()
.Executable()
.createReference(threadLocal.getType(), true, threadLocal.getType(), "withInitial", List.of(lambda.getType()));
return getFactory().createInvocation(target, executableReference);

Probably a refactoring like this is more readable? You could also create the reference to withInitial in a separate method.

if (initalValueMethod.getDeclaration().getBody().getStatements().size() == 1) {
CtStatement statement = initalValueMethod.getDeclaration().getBody().getStatement(0);
if (statement instanceof CtReturn) {
lambda.setExpression(getReturnStatement(statement));
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
lambda.setExpression(getReturnStatement(statement));
lambda.setExpression(((CtReturn<T>) statement).getReturnedExpression());

In my opinion, this is better as is. No need to create another method for it, but it is up to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need this method for the type system, otherwise we would need to use raw types. Raw type is worse than an unchecked cast for me.

@khesoem
Copy link
Collaborator

khesoem commented Jan 31, 2022

Thanks @MartinWitt a lot.

Per discussion with @algomaster99, we may not be able to fix some of the violations reported by Sonar. For example, if the anonymous class contains more than one (+ an implicit constructor) type members.

To make sure, we have to see if such violations are reported by Sonar. If they are, we can make this an incomplete processor.

@MartinWitt
Copy link
Contributor Author

To make sure, we have to see if such violations are reported by Sonar.

Using sonar lint inside VS Code reports no violation if there exists more than a initialValue method, see screenshots.
Have you some infrastructure to test this against your sonar version?

No Warning

image
image

Warning

image

@khesoem
Copy link
Collaborator

khesoem commented Jan 31, 2022

Thanks @MartinWitt .

@algomaster99 already tested it, it reports no violation. We're good to continue...

Copy link
Member

@algomaster99 algomaster99 left a comment

Choose a reason for hiding this comment

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

I tested for what @khaes-kth said. It reports no violation.

I also suggested a change in the test files. I am sorry to propose these changes after I said the last ones were my final. 😅

@Override
protected void repairInternal(CtNewClass<?> newClass) {
CtClass<?> innerClass = newClass.getAnonymousClass();
if (hasNoFields(innerClass) && hasOnlyConstructorAndSingleMethod(innerClass)) {
Copy link
Member

Choose a reason for hiding this comment

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

Apparently, we don't need these conditions as well. As @khaes-kth suggested, I checked if SonarLint reports a violation if there are more than 2 type members. It does not. For example, the following snippet does not report any sonar violation.

ThreadLocal<List<String>> myThreadLocal = new ThreadLocal<>() {
            private static final String X = "1";
            @Override
            protected List<String> initialValue() {
                System.setProperty(X, "abc");
                return new ArrayList<>();
            }
        };

@@ -0,0 +1,13 @@

public class ThreadLocalInitial {
Copy link
Member

@algomaster99 algomaster99 Jan 31, 2022

Choose a reason for hiding this comment

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

We can rename this, I think.

Suggested change
public class ThreadLocalInitial {
public class ThreadLocalInitialWithExactlyOneReturnStatement {

@@ -0,0 +1,14 @@

public class ThreadLocalInitial2 {
Copy link
Member

Choose a reason for hiding this comment

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

We can rename this also.

Suggested change
public class ThreadLocalInitial2 {
public class ThreadLocalInitialWithMultipleStatements {

@algomaster99
Copy link
Member

algomaster99 commented Jan 31, 2022

@MartinWitt

Have you some infrastructure to test this against your sonar version?

Thanks for testing it! We just used SonarLint with IntelliJ.

Copy link
Member

@algomaster99 algomaster99 left a comment

Choose a reason for hiding this comment

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

Looks good to me!

@algomaster99
Copy link
Member

@khaes-kth you can add more comments or proceed to merge.

@khesoem
Copy link
Collaborator

khesoem commented Jan 31, 2022

Shouldn't we also update the handled rules doc?

@algomaster99
Copy link
Member

algomaster99 commented Jan 31, 2022

Shouldn't we also update the handled rules doc?

It will happen automatically once you merge this PR :) GHA will create a PR for that.

newClass.replace(invocation);
}

private CtInvocation<?> createinitialMethod(CtNewClass<?> threadLocal, CtLambda<?> lambda) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't be createInitialMethod? (typo on first character of initial)

Copy link
Member

Choose a reason for hiding this comment

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

@MartinWitt I think I overlooked this while suggesting the typographical error.

@khesoem
Copy link
Collaborator

khesoem commented Jan 31, 2022

Shouldn't we also update the handled rules doc?

It will happen automatically once you merge this PR :) GHA will create a PR for that.

Cool. Didn't remember it.

Copy link
Collaborator

@khesoem khesoem left a comment

Choose a reason for hiding this comment

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

LGTM!

@khesoem khesoem merged commit c11c1bb into ASSERT-KTH:master Jan 31, 2022
@monperrus
Copy link
Contributor

super cool!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants