Skip to content

Commit bfb54af

Browse files
committed
Ensure parameters are read-only to support inlining
1 parent e1f0b49 commit bfb54af

File tree

1 file changed

+42
-1
lines changed

1 file changed

+42
-1
lines changed

src/main/java/co/elastic/indytransformer/AdviceTransformationPlan.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@
4141
import com.google.common.collect.Streams;
4242
import net.bytebuddy.asm.Advice;
4343

44+
import javax.annotation.Nullable;
4445
import java.util.ArrayList;
45-
import java.util.Collection;
4646
import java.util.Collections;
4747
import java.util.List;
4848
import java.util.Optional;
@@ -180,9 +180,50 @@ public boolean transform() {
180180
}
181181
exitReturnValues.forEach(val -> removeParameterOrVariableIfNeverUsed(exitMethod, val.parameterOrVariable()));
182182

183+
ensureParametersAreReadOnly(enterMethod);
184+
ensureParametersAreReadOnly(exitMethod);
185+
183186
return true;
184187
}
185188

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+
186227
private static Stream<Parameter> findNonReadOnlyParameters(MethodDeclaration method, String annotationFqn, TypeSolver typeSolver) {
187228
return method.getParameters().stream()
188229
.filter(param -> Utils.getAnnotation(param, annotationFqn, typeSolver)

0 commit comments

Comments
 (0)