-
Notifications
You must be signed in to change notification settings - Fork 110
Recipes to find ThreadLocals and determine their mutation
#875
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
Closed
Closed
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
d062ebf
basic recipe to find thread local variables that are never mutated
MBoegers 9120a12
Acknowledge that we only check file internal ATM
MBoegers aae2849
Acknowledge that we only check file internal ATM
MBoegers 92368e8
enable scanning capabilities to detect ThreadLocals that are mutated …
MBoegers 347870f
add preconditions
MBoegers a3bd2a9
replace recipe with 3 independent recipes
MBoegers 01f2e9f
First round of review
MBoegers 7563cb6
apply best practices
MBoegers 88d6365
remove old artifacts
MBoegers 9d2a798
apply code review
MBoegers f770cbc
Merge main into PR branch
MBoegers e6b2aff
Move ThreadLocalTable to table package and add data table tests
MBoegers 21e1049
Add ThreadLocal recipes to recipe catalog
MBoegers File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
408 changes: 408 additions & 0 deletions
408
src/main/java/org/openrewrite/java/migrate/search/threadlocal/AbstractFindThreadLocals.java
Large diffs are not rendered by default.
Oops, something went wrong.
61 changes: 61 additions & 0 deletions
61
...in/java/org/openrewrite/java/migrate/search/threadlocal/FindNeverMutatedThreadLocals.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| /* | ||
| * Copyright 2024 the original author or authors. | ||
| * <p> | ||
| * Licensed under the Moderne Source Available License (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * <p> | ||
| * https://docs.moderne.io/licensing/moderne-source-available-license | ||
| * <p> | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package org.openrewrite.java.migrate.search.threadlocal; | ||
|
|
||
| import lombok.EqualsAndHashCode; | ||
| import lombok.Value; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.HashSet; | ||
| import java.util.Set; | ||
|
|
||
| @Value | ||
| @EqualsAndHashCode(callSuper = false) | ||
| public class FindNeverMutatedThreadLocals extends AbstractFindThreadLocals { | ||
|
|
||
| @Override | ||
| public String getDisplayName() { | ||
| return "Find ThreadLocal variables that are never mutated"; | ||
| } | ||
|
|
||
| @Override | ||
| public String getDescription() { | ||
| return "Find `ThreadLocal` variables that are never mutated after initialization. " + | ||
| "These are prime candidates for migration to `ScopedValue` in Java 25+ as they are effectively immutable. " + | ||
| "The recipe identifies `ThreadLocal` variables that are only initialized but never reassigned or modified through `set()` or `remove()` methods."; | ||
| } | ||
|
|
||
| @Override | ||
| public Set<String> getTags() { | ||
| return new HashSet<>(Arrays.asList("java25", "threadlocal", "scopedvalue", "migration")); | ||
| } | ||
|
|
||
| @Override | ||
| protected boolean shouldMarkThreadLocal(ThreadLocalInfo info) { | ||
| // Mark ThreadLocals that have no mutations at all | ||
| return info.hasNoMutation(); | ||
| } | ||
|
|
||
| @Override | ||
| protected String getMessage(ThreadLocalInfo info) { | ||
| return "ThreadLocal is never mutated and could be replaced with ScopedValue"; | ||
| } | ||
|
|
||
| @Override | ||
| protected String getMutationType(ThreadLocalInfo info) { | ||
| return "Never mutated"; | ||
| } | ||
| } | ||
74 changes: 74 additions & 0 deletions
74
...a/org/openrewrite/java/migrate/search/threadlocal/FindThreadLocalsMutableFromOutside.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| /* | ||
| * Copyright 2024 the original author or authors. | ||
| * <p> | ||
| * Licensed under the Moderne Source Available License (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * <p> | ||
| * https://docs.moderne.io/licensing/moderne-source-available-license | ||
| * <p> | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package org.openrewrite.java.migrate.search.threadlocal; | ||
|
|
||
| import lombok.EqualsAndHashCode; | ||
| import lombok.Value; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.HashSet; | ||
| import java.util.Set; | ||
|
|
||
| @Value | ||
| @EqualsAndHashCode(callSuper = false) | ||
| public class FindThreadLocalsMutableFromOutside extends AbstractFindThreadLocals { | ||
|
|
||
| @Override | ||
| public String getDisplayName() { | ||
| return "Find ThreadLocal variables mutable from outside their defining class"; | ||
| } | ||
|
|
||
| @Override | ||
| public String getDescription() { | ||
| //language=markdown | ||
| return "Find `ThreadLocal` variables that can be mutated from outside their defining class. " + | ||
MBoegers marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "These ThreadLocals have the highest risk as they can be modified by any code with access to them. " + | ||
| "This includes non-private ThreadLocals or those mutated from other classes in the codebase."; | ||
| } | ||
|
|
||
| @Override | ||
| public Set<String> getTags() { | ||
| return new HashSet<>(Arrays.asList("java25", "threadlocal", "scopedvalue", "migration", "security")); | ||
| } | ||
|
|
||
| @Override | ||
| protected boolean shouldMarkThreadLocal(ThreadLocalInfo info) { | ||
| // Mark ThreadLocals that are either: | ||
| // 1. Actually mutated from outside their defining class | ||
| // 2. Non-private (and thus potentially mutable from outside) | ||
| return info.hasExternalMutations() || !info.isPrivate(); | ||
| } | ||
|
|
||
| @Override | ||
| protected String getMessage(ThreadLocalInfo info) { | ||
| if (info.hasExternalMutations()) { | ||
| return "ThreadLocal is mutated from outside its defining class"; | ||
| } | ||
|
|
||
| // Non-private but not currently mutated externally | ||
| String access = info.isStatic() ? "static " : ""; | ||
| return "ThreadLocal is " + access + "non-private and can potentially be mutated from outside"; | ||
| } | ||
|
|
||
| @Override | ||
| protected String getMutationType(ThreadLocalInfo info) { | ||
| if (info.hasExternalMutations()) { | ||
| return "Mutated externally"; | ||
| } | ||
|
|
||
| return "Potentially mutable"; | ||
| } | ||
| } | ||
75 changes: 75 additions & 0 deletions
75
...enrewrite/java/migrate/search/threadlocal/FindThreadLocalsMutatedOnlyInDefiningScope.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| /* | ||
| * Copyright 2024 the original author or authors. | ||
| * <p> | ||
| * Licensed under the Moderne Source Available License (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * <p> | ||
| * https://docs.moderne.io/licensing/moderne-source-available-license | ||
| * <p> | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package org.openrewrite.java.migrate.search.threadlocal; | ||
|
|
||
| import lombok.EqualsAndHashCode; | ||
| import lombok.Value; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.HashSet; | ||
| import java.util.Set; | ||
|
|
||
| @Value | ||
| @EqualsAndHashCode(callSuper = false) | ||
| public class FindThreadLocalsMutatedOnlyInDefiningScope extends AbstractFindThreadLocals { | ||
|
|
||
| @Override | ||
| public String getDisplayName() { | ||
| return "Find ThreadLocal variables mutated only in their defining scope"; | ||
| } | ||
|
|
||
| @Override | ||
| public String getDescription() { | ||
| //language=markdown | ||
| return "Find `ThreadLocal` variables that are only mutated within their defining class or initialization context (constructor/static initializer). " + | ||
MBoegers marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "These may be candidates for refactoring as they have limited mutation scope. " + | ||
| "The recipe identifies `ThreadLocal` variables that are only modified during initialization or within their declaring class."; | ||
| } | ||
|
|
||
| @Override | ||
| public Set<String> getTags() { | ||
| return new HashSet<>(Arrays.asList("java25", "threadlocal", "scopedvalue", "migration")); | ||
| } | ||
|
|
||
| @Override | ||
| protected boolean shouldMarkThreadLocal(ThreadLocalInfo info) { | ||
| if (info.hasNoMutation()) { | ||
| return false; | ||
| } | ||
| if (!info.isPrivate()) { | ||
| return false; | ||
| } | ||
| return info.isOnlyLocallyMutated(); | ||
| } | ||
|
|
||
| @Override | ||
| protected String getMessage(ThreadLocalInfo info) { | ||
| if (info.hasOnlyInitMutations()) { | ||
| return "ThreadLocal is only mutated during initialization (constructor/static initializer)"; | ||
| } | ||
|
|
||
| return "ThreadLocal is only mutated within its defining class"; | ||
| } | ||
|
|
||
| @Override | ||
| protected String getMutationType(ThreadLocalInfo info) { | ||
| if (info.hasOnlyInitMutations()) { | ||
| return "Mutated only in initialization"; | ||
| } | ||
|
|
||
| return "Mutated in defining class"; | ||
| } | ||
| } | ||
21 changes: 21 additions & 0 deletions
21
src/main/java/org/openrewrite/java/migrate/search/threadlocal/package-info.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| /* | ||
| * Copyright 2024 the original author or authors. | ||
| * <p> | ||
| * Licensed under the Moderne Source Available License (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * <p> | ||
| * https://docs.moderne.io/licensing/moderne-source-available-license | ||
| * <p> | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| @NullMarked | ||
| @NonNullFields | ||
| package org.openrewrite.java.migrate.search.threadlocal; | ||
|
|
||
| import org.jspecify.annotations.NullMarked; | ||
| import org.openrewrite.internal.lang.NonNullFields; |
61 changes: 61 additions & 0 deletions
61
src/main/java/org/openrewrite/java/migrate/table/ThreadLocalTable.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| /* | ||
| * Copyright 2024 the original author or authors. | ||
| * <p> | ||
| * Licensed under the Moderne Source Available License (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * <p> | ||
| * https://docs.moderne.io/licensing/moderne-source-available-license | ||
| * <p> | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package org.openrewrite.java.migrate.table; | ||
|
|
||
| import lombok.Value; | ||
| import org.openrewrite.Column; | ||
| import org.openrewrite.DataTable; | ||
| import org.openrewrite.Recipe; | ||
|
|
||
| public class ThreadLocalTable extends DataTable<ThreadLocalTable.Row> { | ||
|
|
||
| public ThreadLocalTable(Recipe recipe) { | ||
| super(recipe, | ||
| "ThreadLocal usage", | ||
| "ThreadLocal variables and their mutation patterns."); | ||
| } | ||
|
|
||
| @Value | ||
| public static class Row { | ||
| @Column(displayName = "Source file", | ||
| description = "The source file containing the ThreadLocal declaration.") | ||
| String sourceFile; | ||
|
|
||
| @Column(displayName = "Class name", | ||
| description = "The fully qualified class name where the ThreadLocal is declared.") | ||
| String className; | ||
|
|
||
| @Column(displayName = "Field name", | ||
| description = "The name of the ThreadLocal field.") | ||
| String fieldName; | ||
|
|
||
| @Column(displayName = "Access modifier", | ||
| description = "The access modifier of the ThreadLocal field (private, protected, public, package-private).") | ||
| String accessModifier; | ||
|
|
||
| @Column(displayName = "Field modifiers", | ||
| description = "Additional modifiers like static, final.") | ||
| String modifiers; | ||
|
|
||
| @Column(displayName = "Mutation type", | ||
| description = "Type of mutation detected (Never mutated, Mutated only in initialization, Mutated in defining class, Mutated externally, Potentially mutable).") | ||
| String mutationType; | ||
|
|
||
| @Column(displayName = "Message", | ||
| description = "Detailed message about the ThreadLocal's usage pattern.") | ||
| String message; | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.