-
Notifications
You must be signed in to change notification settings - Fork 27
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
Changes from 1 commit
c3d38b1
8bf69d2
63cccee
688eab9
3a15259
79eea1b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,95 @@ | ||||||||||||||||||||||||||||||||
package sorald.processor; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
import java.util.List; | ||||||||||||||||||||||||||||||||
import java.util.Optional; | ||||||||||||||||||||||||||||||||
import java.util.function.Supplier; | ||||||||||||||||||||||||||||||||
import sorald.annotations.ProcessorAnnotation; | ||||||||||||||||||||||||||||||||
import spoon.reflect.code.CtExpression; | ||||||||||||||||||||||||||||||||
import spoon.reflect.code.CtInvocation; | ||||||||||||||||||||||||||||||||
import spoon.reflect.code.CtLambda; | ||||||||||||||||||||||||||||||||
import spoon.reflect.code.CtNewClass; | ||||||||||||||||||||||||||||||||
import spoon.reflect.code.CtReturn; | ||||||||||||||||||||||||||||||||
import spoon.reflect.code.CtStatement; | ||||||||||||||||||||||||||||||||
import spoon.reflect.declaration.CtClass; | ||||||||||||||||||||||||||||||||
import spoon.reflect.reference.CtExecutableReference; | ||||||||||||||||||||||||||||||||
import spoon.reflect.reference.CtTypeReference; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
@ProcessorAnnotation(key = "S4065", description = "\"ThreadLocal.withInitial\" should be preferred") | ||||||||||||||||||||||||||||||||
public class ThreadLocalWithInitial extends SoraldAbstractProcessor<CtNewClass<?>> { | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private static final String THREADLOCAL_FQN = "java.lang.ThreadLocal"; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
@Override | ||||||||||||||||||||||||||||||||
protected void repairInternal(CtNewClass<?> newClass) { | ||||||||||||||||||||||||||||||||
if (isThreadLocalType(newClass)) { | ||||||||||||||||||||||||||||||||
CtClass<?> innerClass = newClass.getAnonymousClass(); | ||||||||||||||||||||||||||||||||
if (hasNoFields(innerClass) && hasOnlyConstructorAndSingleMethod(innerClass)) { | ||||||||||||||||||||||||||||||||
Optional<CtExecutableReference<?>> initalValueMethod = | ||||||||||||||||||||||||||||||||
findInitalValueMethod(innerClass); | ||||||||||||||||||||||||||||||||
if (initalValueMethod.isPresent()) { | ||||||||||||||||||||||||||||||||
CtLambda<?> lambda = createSupplier(initalValueMethod.get()); | ||||||||||||||||||||||||||||||||
CtInvocation<?> invocation = createInitalMethod(newClass, lambda); | ||||||||||||||||||||||||||||||||
invocation.setArguments(List.of(lambda)); | ||||||||||||||||||||||||||||||||
newClass.replace(invocation); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private boolean isThreadLocalType(CtNewClass<?> newClass) { | ||||||||||||||||||||||||||||||||
return newClass.getType() != null | ||||||||||||||||||||||||||||||||
&& newClass.getType().getQualifiedName().equals(THREADLOCAL_FQN); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private CtInvocation<?> createInitalMethod(CtNewClass<?> threadLocal, CtLambda<?> lambda) { | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There is a typographical error in the spelling of |
||||||||||||||||||||||||||||||||
return getFactory() | ||||||||||||||||||||||||||||||||
.createInvocation( | ||||||||||||||||||||||||||||||||
getFactory().createTypeAccess(createThreadLocalRef()), | ||||||||||||||||||||||||||||||||
getFactory() | ||||||||||||||||||||||||||||||||
.Executable() | ||||||||||||||||||||||||||||||||
.createReference( | ||||||||||||||||||||||||||||||||
threadLocal.getType(), | ||||||||||||||||||||||||||||||||
true, | ||||||||||||||||||||||||||||||||
threadLocal.getType(), | ||||||||||||||||||||||||||||||||
"withInitial", | ||||||||||||||||||||||||||||||||
List.of(lambda.getType()))); | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Probably a refactoring like this is more readable? You could also create the reference to |
||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private CtTypeReference<Object> createThreadLocalRef() { | ||||||||||||||||||||||||||||||||
return getFactory().createCtTypeReference(ThreadLocal.class); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private CtLambda<?> createSupplier(CtExecutableReference<?> initalValueMethod) { | ||||||||||||||||||||||||||||||||
CtLambda<?> lambda = getFactory().createLambda(); | ||||||||||||||||||||||||||||||||
if (initalValueMethod.getDeclaration().getBody().getStatements().size() == 1) { | ||||||||||||||||||||||||||||||||
CtStatement statement = initalValueMethod.getDeclaration().getBody().getStatement(0); | ||||||||||||||||||||||||||||||||
if (statement instanceof CtReturn) { | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need this check. |
||||||||||||||||||||||||||||||||
lambda.setExpression(getReturnStatement(statement)); | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
In my opinion, this is better as is. No need to create another method for it, but it is up to you. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||
lambda.setBody(initalValueMethod.getDeclaration().getBody()); | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here I'm unsure if there is a case where the method There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it is possible. I think a |
||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||
lambda.setBody(initalValueMethod.getDeclaration().getBody()); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
lambda.setType(getFactory().createCtTypeReference(Supplier.class)); | ||||||||||||||||||||||||||||||||
return lambda; | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private <T> CtExpression<T> getReturnStatement(CtStatement statement) { | ||||||||||||||||||||||||||||||||
return ((CtReturn<T>) statement).getReturnedExpression(); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private Optional<CtExecutableReference<?>> findInitalValueMethod(CtClass<?> innerClass) { | ||||||||||||||||||||||||||||||||
return innerClass.getDeclaredExecutables().stream() | ||||||||||||||||||||||||||||||||
.filter(v -> v.getSimpleName().equals("initialValue")) | ||||||||||||||||||||||||||||||||
.findFirst(); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private boolean hasOnlyConstructorAndSingleMethod(CtClass<?> innerClass) { | ||||||||||||||||||||||||||||||||
return innerClass.getDeclaredExecutables().size() == 2; | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private boolean hasNoFields(CtClass<?> innerClass) { | ||||||||||||||||||||||||||||||||
return innerClass.getDeclaredFields().isEmpty(); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
Sorald fixes the violations of this rule by replacing an anonymous `ThreadLocal` class overriding `initalValue` with an invocation of `ThreadLocal.withInitial(Supplier)`. | ||
Example: | ||
```diff | ||
- ThreadLocal<String> myThreadLocal = new ThreadLocal<String>() { | ||
- @Override | ||
- protected String initialValue() { | ||
- return "Hello"; | ||
- } | ||
+ Threadlocal<String> myThreadLocal = ThreadLocal.withInitial(() -> "Hello"); | ||
``` |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,13 @@ | ||||||
|
||||||
public class ThreadLocalInitial { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can rename this, I think.
Suggested change
|
||||||
|
||||||
public void bar() { | ||||||
ThreadLocal<String> threadLocal = new ThreadLocal<String>() { // Noncompliant | ||||||
@Override | ||||||
protected String initialValue() { | ||||||
return "hello"; | ||||||
} | ||||||
}; | ||||||
threadLocal.set("42"); | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,14 @@ | ||||||
|
||||||
public class ThreadLocalInitial2 { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can rename this also.
Suggested change
|
||||||
|
||||||
public void bar() { | ||||||
ThreadLocal<String> threadLocal = new ThreadLocal<String>() { // Noncompliant | ||||||
@Override | ||||||
protected String initialValue() { | ||||||
String s = "hello"; | ||||||
return s+"42"; | ||||||
} | ||||||
}; | ||||||
threadLocal.set("42"); | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
|
||
public class ThreadLocalInitial2 { | ||
|
||
public void bar() { | ||
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> {String s = "hello"; return s+"42";}); | ||
threadLocal.set("42"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
|
||
public class ThreadLocalInitial { | ||
|
||
public void bar() { | ||
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "hello"); | ||
threadLocal.set("42"); | ||
} | ||
} |
There was a problem hiding this comment.
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-condition
s you have here. It is not necessary to check that the type ofnewClass
isThreadLocal
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
andhasOnlyConstructorAndSingleMethod
.There was a problem hiding this comment.
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.