All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openrewrite.java.migrate.joda.JodaTimeScanner Maven / Gradle / Ivy

There is a newer version: 3.0.1
Show newest version
/*
 * Copyright 2024 the original author or authors.
 * 

* 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 *

* https://www.apache.org/licenses/LICENSE-2.0 *

* 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.joda; import fj.data.Option; import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.openrewrite.Cursor; import org.openrewrite.ExecutionContext; import org.openrewrite.analysis.dataflow.Dataflow; import org.openrewrite.analysis.dataflow.analysis.SinkFlowSummary; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.J.VariableDeclarations.NamedVariable; import org.openrewrite.java.tree.JavaType; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import static org.openrewrite.java.migrate.joda.templates.TimeClassNames.JODA_CLASS_PATTERN; public class JodaTimeScanner extends ScopeAwareVisitor { @Getter private final Set unsafeVars; private final Map> varDependencies = new HashMap<>(); public JodaTimeScanner(Set unsafeVars, LinkedList scopes) { super(scopes); this.unsafeVars = unsafeVars; } public JodaTimeScanner(Set unsafeVars) { this(unsafeVars, new LinkedList<>()); } @Override public J visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { super.visitCompilationUnit(cu, ctx); Set allReachable = new HashSet<>(); for (NamedVariable var : unsafeVars) { dfs(var, allReachable); } unsafeVars.addAll(allReachable); return cu; } @Override public NamedVariable visitVariable(NamedVariable variable, ExecutionContext ctx) { if (!variable.getType().isAssignableFrom(JODA_CLASS_PATTERN)) { return variable; } // TODO: handle class variables && method parameters if (!isLocalVar(variable)) { unsafeVars.add(variable); return variable; } variable = (NamedVariable) super.visitVariable(variable, ctx); if (!variable.getType().isAssignableFrom(JODA_CLASS_PATTERN) || variable.getInitializer() == null) { return variable; } List sinks = findSinks(variable.getInitializer()); Cursor currentScope = getCurrentScope(); J.Block block = currentScope.getValue(); new AddSafeCheckMarker(sinks).visit(block, ctx, currentScope.getParent()); processMarkersOnExpression(sinks, variable); return variable; } @Override public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ctx) { Expression var = assignment.getVariable(); // not joda expr or not local variable if (!isJodaExpr(var) || !(var instanceof J.Identifier)) { return assignment; } J.Identifier ident = (J.Identifier) var; Optional mayBeVar = findVarInScope(ident.getSimpleName()); if (!mayBeVar.isPresent()) { return assignment; } NamedVariable variable = mayBeVar.get(); Cursor varScope = findScope(variable); List sinks = findSinks(assignment.getAssignment()); new AddSafeCheckMarker(sinks).visit(varScope.getValue(), ctx, varScope.getParent()); processMarkersOnExpression(sinks, variable); return assignment; } private void processMarkersOnExpression(List expressions, NamedVariable var) { for (Expression expr : expressions) { Optional mayBeMarker = expr.getMarkers().findFirst(SafeCheckMarker.class); if (!mayBeMarker.isPresent()) { continue; } SafeCheckMarker marker = mayBeMarker.get(); if (!marker.isSafe()) { unsafeVars.add(var); } if (!marker.getReferences().isEmpty()) { varDependencies.compute(var, (k, v) -> v == null ? new HashSet<>() : v).addAll(marker.getReferences()); for (NamedVariable ref : marker.getReferences()) { varDependencies.compute(ref, (k, v) -> v == null ? new HashSet<>() : v).add(var); } } } } private boolean isJodaExpr(Expression expression) { return expression.getType() != null && expression.getType().isAssignableFrom(JODA_CLASS_PATTERN); } private List findSinks(Expression expr) { Cursor cursor = new Cursor(getCursor(), expr); Option mayBeSinks = Dataflow.startingAt(cursor).findSinks(new JodaTimeFlowSpec()); if (mayBeSinks.isNone()) { return Collections.emptyList(); } return mayBeSinks.some().getExpressionSinks(); } private boolean isLocalVar(NamedVariable variable) { if (!(variable.getVariableType().getOwner() instanceof JavaType.Method)) { return false; } J j = getCursor().dropParentUntil(t -> t instanceof J.Block || t instanceof J.MethodDeclaration).getValue(); return j instanceof J.Block; } private void dfs(NamedVariable root, Set visited) { if (visited.contains(root)) { return; } visited.add(root); for (NamedVariable dep : varDependencies.getOrDefault(root, Collections.emptySet())) { dfs(dep, visited); } } @RequiredArgsConstructor private class AddSafeCheckMarker extends JavaIsoVisitor { @NonNull private List expressions; @Override public Expression visitExpression(Expression expression, ExecutionContext ctx) { int index = expressions.indexOf(expression); if (index == -1) { return super.visitExpression(expression, ctx); } Expression withMarker = expression.withMarkers(expression.getMarkers().addIfAbsent(getMarker(expression, ctx))); expressions.set(index, withMarker); return withMarker; } private SafeCheckMarker getMarker(Expression expr, ExecutionContext ctx) { Optional mayBeMarker = expr.getMarkers().findFirst(SafeCheckMarker.class); if (mayBeMarker.isPresent()) { return mayBeMarker.get(); } Cursor boundary = findBoundaryCursorForJodaExpr(); boolean isSafe = true; // TODO: handle return statement if (boundary.getParentTreeCursor().getValue() instanceof J.Return) { isSafe = false; } Expression boundaryExpr = boundary.getValue(); J j = new JodaTimeVisitor(new HashSet<>(), scopes).visit(boundaryExpr, ctx, boundary.getParentTreeCursor()); Set referencedVars = new HashSet<>(); new FindVarReferences().visit(expr, referencedVars, getCursor().getParentTreeCursor()); AtomicBoolean hasJodaType = new AtomicBoolean(); new HasJodaType().visit(j, hasJodaType); isSafe = isSafe && !hasJodaType.get() && !referencedVars.contains(null); referencedVars.remove(null); return new SafeCheckMarker(UUID.randomUUID(), isSafe, referencedVars); } /** * Traverses the cursor to find the first non-Joda expression in the path. * If no non-Joda expression is found, it returns the cursor pointing * to the last Joda expression whose parent is not an Expression. */ private Cursor findBoundaryCursorForJodaExpr() { Cursor cursor = getCursor(); while (cursor.getValue() instanceof Expression && isJodaExpr(cursor.getValue())) { Cursor parent = cursor.getParentTreeCursor(); if (parent.getValue() instanceof J && !(parent.getValue() instanceof Expression)) { return cursor; } cursor = parent; } return cursor; } } private class FindVarReferences extends JavaIsoVisitor> { @Override public J.Identifier visitIdentifier(J.Identifier ident, Set vars) { if (!isJodaExpr(ident) || ident.getFieldType() == null) { return ident; } if (ident.getFieldType().getOwner() instanceof JavaType.Class) { vars.add(null); // class variable not supported yet. } // find variable in the closest scope findVarInScope(ident.getSimpleName()).ifPresent(vars::add); return ident; } } private static class HasJodaType extends JavaIsoVisitor { @Override public Expression visitExpression(Expression expression, AtomicBoolean hasJodaType) { if (hasJodaType.get()) { return expression; } if (expression.getType() != null && expression.getType().isAssignableFrom(JODA_CLASS_PATTERN)) { hasJodaType.set(true); } return super.visitExpression(expression, hasJodaType); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy