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

Analyse escaping for local vars #4658

Draft
wants to merge 71 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
7f55c3a
Visitor that can be used to find local variables that are leaking the…
MBoegers Nov 8, 2024
5a03ded
add licenses
MBoegers Nov 8, 2024
1088ccc
add support for ternary operators to FindVariablesEscapeLocation
MBoegers Nov 8, 2024
a38d727
Add visitor to easily determine, if a named variable will escape its …
MBoegers Nov 8, 2024
ea3bf6c
Update rewrite-java/src/test/java/org/openrewrite/java/search/FindVar…
timtebeek Nov 11, 2024
018669f
Merge branch 'main' into escape-analysis
timtebeek Nov 11, 2024
fd1a30c
FindEscapingVariablesTe
MBoegers Nov 12, 2024
9966a1b
Keep multiple whitelines and same comments from the gitignore file (#…
Jenson3210 Nov 11, 2024
cb0b4a7
Format JavaTemplateTest.addAnnotation to minimize review bot spam
timtebeek Nov 11, 2024
885dc51
Feature - YAML search - find properties with a specific scalar value …
aamotharald Nov 12, 2024
d01b739
Merge remote-tracking branch 'origin/main'
knutwannheden Apr 12, 2023
fd170d7
Update cursor-droping logic in RemoveMethodInvocationsVisitor
kunli2 Apr 12, 2023
749063f
update RemoveMethodInvocationsVisitor to work on java files only
kunli2 Apr 12, 2023
d560871
Update for rewrite 8.0 (#345)
knutwannheden May 31, 2023
f4967ef
Remove `private` modifiers from marker subtypes
knutwannheden Feb 20, 2024
43c2e02
Fix possible ClassCastException and cleanup warnings in NoRequestMapp…
sambsnyd Mar 14, 2024
2969c43
refactor: Move `@Nullable` method annotations to the return type
jkschneider Jul 16, 2024
6e86ee6
Migrate to JSpecify from OpenRewrite JSR-305 meta-annotations (#576)
jkschneider Aug 15, 2024
9119ea7
Merge remote-tracking branch 'origin/main'
knutwannheden Apr 12, 2023
5fd28c3
Perform recipe SelectRecipeExamples on rewrite-spring to select recip…
kunli2 Apr 26, 2023
e467242
Integrated with rewrite 7.40.3, update @DocumentExample package name
kunli2 Apr 26, 2023
8b9d397
Update for rewrite 8.0 (#345)
knutwannheden May 31, 2023
0ff762d
Fix compilation error
sambsnyd Nov 3, 2023
1c13f8b
refactor: Remove `public` visibility of JUnit 5 tests
timtebeek Feb 5, 2024
e3e2a19
relocating RemoveMethodInvocationsVisitor / Test to appropriate dir a…
nmck257 Nov 1, 2024
db497c5
adjusting RemoveMethodInvocationsVisitor super visit order to fix sup…
nmck257 Nov 1, 2024
a42419c
polishing nullability, documenting unsupported operation with test
nmck257 Nov 1, 2024
5efb642
quieting unused-method warnings
nmck257 Nov 1, 2024
0b4fdcc
simplifying boolean condition per intellisense
nmck257 Nov 1, 2024
e9b09cb
Apply suggestions from code review
nmck257 Nov 1, 2024
84bb741
fixing changes from robo code review
nmck257 Nov 1, 2024
1593b9e
Feature/remove unused properties (#4636)
nmck257 Nov 12, 2024
c621f80
wrapping RemoveMethodInvocationsVisitor into a recipe with streamline…
nmck257 Nov 12, 2024
36d76a5
Fixing the quoteEscaping test case for proper quote character escapin…
mccartney Nov 12, 2024
549521a
Fix SemanticallyEqual, and by extension SimplifyBooleanExpression, co…
sambsnyd Nov 13, 2024
a7b1d5a
Account for a second common resource filtering pattern (#4666)
timtebeek Nov 13, 2024
64845de
Update `ChangeType` and `ChangePackage` to work with `SourceFileWithR…
Laurens-W Nov 13, 2024
1923939
Update model for interfaces extending interfaces (#4663)
rlsanders4 Nov 13, 2024
14c9306
Fix condition where Attribute#getValue() throws an unsupported operat…
jkschneider Nov 13, 2024
2099691
Fix Javadoc for AnnotationService#getAllAnnotations(Cursor)
jkschneider Nov 14, 2024
3b6066f
Add new test and fix (#4672)
nielsdebruin Nov 14, 2024
5b1d46c
ChangeType does not work on J.ClassDeclaration (#4670)
rlsanders4 Nov 14, 2024
74f45a7
Refactor `ChangeType` tests to adhere to testing framework (#4673)
Laurens-W Nov 14, 2024
ae16af8
Add JavaDoc to ListUtils class
jevanlingen Nov 14, 2024
1247d42
Move project matcher to MavenVisitor (#4675)
ammachado Nov 14, 2024
618d2cf
Fix FindPlugins.find() throwing a class-cast exception if GString int…
sambsnyd Nov 15, 2024
aae6492
Fix Gradle AddDependency not doing anything when no onlyIfUsing param…
sambsnyd Nov 15, 2024
7052680
Remove redundant space for DeleteMethodArgument when argumentIndex=0 …
ckcd Nov 15, 2024
b174dcd
Also report `DeserializationError`s in `FindParseFailures` recipe
knutwannheden Nov 15, 2024
dde7882
Trim `RecipeDescriptor`s
knutwannheden Nov 15, 2024
64086ef
Slightly improve performance of `JsonPathMatcher`s
knutwannheden Nov 17, 2024
5201917
Improve performance of YAML `JsonPathMatcher`
knutwannheden Nov 17, 2024
cf3f27b
Improve performance of `JsonPathMatcher`s by only parsing once
knutwannheden Nov 17, 2024
37d24b3
`JsonPatchMatcher`: Replace Stream API with for-loops
knutwannheden Nov 17, 2024
221ea31
`JsonPatchMatcher`: Short-circuit matching when possible
knutwannheden Nov 17, 2024
95e9712
Fix accidental regression
knutwannheden Nov 17, 2024
9623d4a
Polish YAML `JsonPatchMatcher`
knutwannheden Nov 17, 2024
0b20b3b
Yet another YAML `JsonPatchMatcher` performance tweak
knutwannheden Nov 18, 2024
5e5ae55
Yet another YAML `JsonPatchMatcher` performance tweak
knutwannheden Nov 18, 2024
ec692b3
Allow YAML `JsonPatchMatcher` to match document-level sequences (#4680)
knutwannheden Nov 18, 2024
1a5ce2c
Append items to annotation attribute array (#4667)
nielsdebruin Nov 19, 2024
e30edf7
Polish `UpdateGradleWrapper`
knutwannheden Nov 20, 2024
8627e4c
Call isTrue on Reference asserts in XmlParserTest (#4689)
nielsdebruin Nov 20, 2024
88678ca
Fix NPE in SpringReference (#4691)
Laurens-W Nov 20, 2024
6836e29
Make `FindMissingTypes` stricter for method invocations (#4688)
knutwannheden Nov 20, 2024
832c12c
Make UpdateGradleWrapper.GradleWrapperState directly configurable
sambsnyd Nov 20, 2024
a29d6d5
Find Refaster style recipes as well (#4693)
timtebeek Nov 20, 2024
8c15566
refactor: Update Gradle wrapper
shanman190 Nov 20, 2024
5821fbf
Find Yaml recipes as well (#4695)
timtebeek Nov 20, 2024
0bf6212
Add find source files negation test.
sambsnyd Nov 21, 2024
dfc69df
`TypeUtils#isAssignable()` improvements (#4696)
knutwannheden Nov 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
* <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.search;

import org.openrewrite.ExecutionContext;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.tree.J;
import org.openrewrite.marker.SearchResult;

import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

/**
* Identify local variables that escape there defining scope.
*
* @see FindVariableEscapeLocations
*/
public class FindEscapingVariables extends JavaIsoVisitor<ExecutionContext> {
/**
* Finds named local variables from the given subtree that will escape their defining scope
*/
public static Set<J.VariableDeclarations.NamedVariable> find(J subtree) {
return TreeVisitor.collect(new FindEscapingVariables(), subtree, new HashSet<>())
.stream()
.filter(J.VariableDeclarations.NamedVariable.class::isInstance)
.map(J.VariableDeclarations.NamedVariable.class::cast)
.collect(Collectors.toSet());
}

/**
* Determine if a local named variable from the given subtree will escape its defining scope
*/
public static boolean willLeakFrom(J.VariableDeclarations.NamedVariable variable, J subtree) {
return find(subtree).contains(variable);
}

@Override
public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations.NamedVariable variable, ExecutionContext executionContext) {
variable = super.visitVariable(variable, executionContext);

if (variable.isField(getCursor())) {
return variable;
}

J.Block definingScope = getCursor().firstEnclosing(J.Block.class);
if (definingScope != null && variable.getName().getFieldType() != null) {
boolean willLeak = FindVariableEscapeLocations.findLeakingVars(definingScope).stream()
.map(J.Identifier::getFieldType)
.anyMatch(variable.getName().getFieldType()::equals);
if (willLeak) {
variable = SearchResult.found(variable);
}
}

return variable;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
* <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.search;

import org.openrewrite.ExecutionContext;
import org.openrewrite.Tree;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.Statement;
import org.openrewrite.marker.SearchResult;

import javax.annotation.Nullable;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
* Find escaping points of local variables.
* An escaping point is the statement where a reference to a local variable leafs it scope an is now accessible from outside the method.
* In some situation such escaping is wanted, think of getters, but everytime its importent to rethink.
* Is mutability, synchronization or information hiding a problem here?
*/
public class FindVariableEscapeLocations extends JavaIsoVisitor<ExecutionContext> {

@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) {
method = super.visitMethodInvocation(method, executionContext);

boolean noLeaks = findLocalArguments(method.getArguments()).isEmpty();

return noLeaks ? method : SearchResult.found(method);
}

@Override
public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext executionContext) {
assignment = super.visitAssignment(assignment, executionContext);

J.Identifier assigmentTarget = (J.Identifier) assignment.getVariable();
if (isPrimitive(assigmentTarget)) {
return assignment;
}

boolean targetIsOutsider = isNotLocalVar(assigmentTarget);
boolean assignedByLocalVar = extractIdentifiers(assignment.getAssignment()).stream()
.map(FindVariableEscapeLocations::isLocalVar)
.reduce(false, Boolean::logicalOr);
boolean leaksVariable = targetIsOutsider && assignedByLocalVar;

return leaksVariable ? SearchResult.found(assignment) : assignment;
}

@Override
public J.Return visitReturn(J.Return _return, ExecutionContext executionContext) {
_return = super.visitReturn(_return, executionContext);

if (_return.getExpression() == null) {
return _return;
}

boolean returnsLocalVar = extractIdentifiers(_return.getExpression()).stream()
.map(FindVariableEscapeLocations::extractIdentifiers)
.flatMap(Collection::stream)
.filter(i -> !isPrimitive(i))
.map(FindVariableEscapeLocations::isLocalVar)
.reduce(false, Boolean::logicalOr);

return returnsLocalVar ? SearchResult.found(_return) : _return;
}

@Override
public J.NewClass visitNewClass(J.NewClass newClass, ExecutionContext executionContext) {
newClass = super.visitNewClass(newClass, executionContext);

boolean noLeaks = findLocalArguments(newClass.getArguments()).isEmpty();

return noLeaks ? newClass : SearchResult.found(newClass);
}

/**
* Finds statements that enable escaping of local variables from the given subtree
*/
public static Set<J> find(J subtree) {
return TreeVisitor.collect(new FindVariableEscapeLocations(), subtree, new HashSet<>())
.stream()
.filter(Statement.class::isInstance)
.map(Statement.class::cast)
.collect(Collectors.toSet());
}

/**
* Finds identifier of local variables that escape from the given subtree
*/
public static Set<J.Identifier> findLeakingVars(J subtree) {
Function<? super Tree, Set<J.Identifier>> extractIdentifiers = t -> {
Set<J.Identifier> identifiers = new HashSet<>();
if (t instanceof J.Return) {
identifiers.addAll(extractIdentifiers(((J.Return) t).getExpression()));
} else if (t instanceof J.Assignment) {
identifiers.addAll(extractIdentifiers(((J.Assignment) t).getAssignment()));
} else if (t instanceof J.NewClass) {
identifiers.addAll(findLocalArguments(((J.NewClass) t).getArguments()));
} else if(t instanceof J.MethodInvocation) {
identifiers.addAll(findLocalArguments(((J.MethodInvocation) t).getArguments()));
}

return identifiers;
};

return TreeVisitor.collect(new FindVariableEscapeLocations(), subtree, new HashSet<>())
.stream()
.map(extractIdentifiers)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
}

/**
* Extracts Set of Identifiers from J.Identifiers and Ternary operators.
* These two potentially participate in very case but will not lead to escaping themselves.
*/
private static Set<J.Identifier> extractIdentifiers(@Nullable Expression assignment) {
Set<J.Identifier> identifiers = new HashSet<>();
// fast return if already an J.Identifier
if (assignment instanceof J.Identifier) {
identifiers.add((J.Identifier) assignment);
}
if (assignment instanceof J.Ternary) {
// if ternary we only need to care about direct assigment.
// the other possibilities (J.MethodInvocation, J.NewClass) are handled by the visitor itself.
if (((J.Ternary) assignment).getFalsePart() instanceof J.Identifier) {
identifiers.add((J.Identifier) ((J.Ternary) assignment).getFalsePart());
}
if (((J.Ternary) assignment).getTruePart() instanceof J.Identifier) {
identifiers.add((J.Identifier) ((J.Ternary) assignment).getTruePart());
}
}

return identifiers;
}

private static boolean isLocalVar(J.Identifier identifier) {
JavaType.Variable fieldType = identifier.getFieldType();

if (fieldType != null) {
JavaType owner = fieldType.getOwner();

boolean isOwnedByMethod = owner instanceof JavaType.Method;
if (isOwnedByMethod) {
boolean isMethodParameter = ((JavaType.Method) owner).getParameterNames().contains(identifier.getSimpleName());
return !isMethodParameter;
}
}

return false;
}

private static boolean isNotLocalVar(J.Identifier identifier) {
return !isLocalVar(identifier);
}

private static boolean isPrimitive(J.Identifier identifier) {
JavaType.Variable fieldType = identifier.getFieldType();
return fieldType != null && fieldType.getType() instanceof JavaType.Primitive;
}

private static boolean isNotPrimitive(J.Identifier identifier) {
return !isPrimitive(identifier);
}

private static List<J.Identifier> findLocalArguments(List<Expression> arguments) {
return arguments.stream()
.map(FindVariableEscapeLocations::extractIdentifiers)
.flatMap(Collection::stream)
.filter(FindVariableEscapeLocations::isNotPrimitive)
.filter(FindVariableEscapeLocations::isLocalVar)
.collect(Collectors.toList());
}
}
Loading