|
41 | 41 | import com.google.common.collect.Streams; |
42 | 42 | import net.bytebuddy.asm.Advice; |
43 | 43 |
|
| 44 | +import javax.annotation.Nullable; |
44 | 45 | import java.util.ArrayList; |
45 | | -import java.util.Collection; |
46 | 46 | import java.util.Collections; |
47 | 47 | import java.util.List; |
48 | 48 | import java.util.Optional; |
@@ -180,9 +180,50 @@ public boolean transform() { |
180 | 180 | } |
181 | 181 | exitReturnValues.forEach(val -> removeParameterOrVariableIfNeverUsed(exitMethod, val.parameterOrVariable())); |
182 | 182 |
|
| 183 | + ensureParametersAreReadOnly(enterMethod); |
| 184 | + ensureParametersAreReadOnly(exitMethod); |
| 185 | + |
183 | 186 | return true; |
184 | 187 | } |
185 | 188 |
|
| 189 | + /** |
| 190 | + * Ensures that @Advice.Argument, @Advice.FieldValue and @Advice.ReturnValue parameters are never written. |
| 191 | + * This is only required if the advice is inlined, because in that case bytebuddy does not allow writing to the parameters. |
| 192 | + */ |
| 193 | + private void ensureParametersAreReadOnly(@Nullable MethodDeclaration method) { |
| 194 | + if (method == null) { |
| 195 | + return; |
| 196 | + } |
| 197 | + List<Parameter> parameters = new ArrayList<>(method.getParameters()); |
| 198 | + // Iterate in reverse order to keep the local var declarations in the same order as the parameters |
| 199 | + Collections.reverse(parameters); |
| 200 | + for (Parameter parameter : parameters) { |
| 201 | + if ( Utils.hasAnnotation(parameter, ADVICE_ARGUMENT, typeSolver) || |
| 202 | + Utils.hasAnnotation(parameter, ADVICE_FIELDVALUE, typeSolver) || |
| 203 | + Utils.hasAnnotation(parameter, ADVICE_RETURN, typeSolver) |
| 204 | + ) { |
| 205 | + // Look for all assignment expressions with the name on the LHS |
| 206 | + AtomicBoolean isWrittenTo = new AtomicBoolean(false); |
| 207 | + BlockStmt body = method.getBody().get(); |
| 208 | + body.walk(AssignExpr.class, assignment -> { |
| 209 | + Expression target = assignment.getTarget(); |
| 210 | + if (target instanceof NameExpr assignedVar && assignedVar.getNameAsString().equals(parameter.getNameAsString())) { |
| 211 | + // we are conservative: the name might resolve to e.g. a shadowing local variable, but we rename anyways |
| 212 | + isWrittenTo.set(true); |
| 213 | + } |
| 214 | + }); |
| 215 | + if (isWrittenTo.get()) { |
| 216 | + // Introduce a new local variable to be used instead, rename the parameter with "Original" suffix |
| 217 | + String localVarName = parameter.getNameAsString(); |
| 218 | + String parameterName = "original" + Character.toUpperCase(localVarName.charAt(0)) + localVarName.substring(1); |
| 219 | + parameter.setName(parameterName); |
| 220 | + VariableDeclarator localDeclarator = new VariableDeclarator(parameter.getType(), localVarName, new NameExpr(parameterName)); |
| 221 | + body.addAndGetStatement(0, new ExpressionStmt(new VariableDeclarationExpr(localDeclarator))); |
| 222 | + } |
| 223 | + } |
| 224 | + } |
| 225 | + } |
| 226 | + |
186 | 227 | private static Stream<Parameter> findNonReadOnlyParameters(MethodDeclaration method, String annotationFqn, TypeSolver typeSolver) { |
187 | 228 | return method.getParameters().stream() |
188 | 229 | .filter(param -> Utils.getAnnotation(param, annotationFqn, typeSolver) |
|
0 commit comments