Skip to content
Commits on Source (10)
lombok-patcher (0.32-1) unstable; urgency=medium
* New upstream version 0.32.
* Declare compliance with Debian Policy 4.4.0.
* Use canonical VCS-URI.
* Remove get-orig-source target
-- Markus Koschany <apo@debian.org> Fri, 02 Aug 2019 13:58:21 +0200
lombok-patcher (0.30-1) unstable; urgency=medium
* New upstream version 0.30.
......
......@@ -15,10 +15,10 @@ Build-Depends:
libjna-java,
libjsch-java,
liblombok-java
Standards-Version: 4.2.1
Standards-Version: 4.4.0
Homepage: https://projectlombok.org
Vcs-Git: https://anonscm.debian.org/git/pkg-java/lombok-patcher.git
Vcs-Browser: https://anonscm.debian.org/cgit/pkg-java/lombok-patcher.git
Vcs-Git: https://salsa.debian.org/java-team/lombok-patcher.git
Vcs-Browser: https://salsa.debian.org/java-team/lombok-patcher
Package: liblombok-patcher-java
Architecture: all
......
......@@ -7,7 +7,7 @@ Copyright: 2009-2015 The Project Lombok Authors.
License: Expat
Files: debian/*
Copyright: 2016-2018, Markus Koschany <apo@debian.org>
Copyright: 2016-2019, Markus Koschany <apo@debian.org>
License: Expat
......
dist/lombok.patcher.jar
dist/lombok.injector-0.30.jar
dist/lombok.injector-0.32.jar
......@@ -12,6 +12,3 @@ override_dh_auto_clean:
override_dh_auto_build:
mkdir -p build/packInjector
ant dist
get-orig-source:
uscan --download-current-version --force-download
/*
* Copyright (C) 2009-2018 The Project Lombok Authors.
* Copyright (C) 2009-2019 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -26,7 +26,7 @@ package lombok.patcher;
*/
public class Version {
// ** CAREFUL ** - this class must always compile with 0 dependencies (it must not refer to any other sources or libraries).
private static final String VERSION = "0.30";
private static final String VERSION = "0.32";
private Version() {
//Prevent instantiation
......
/*
* Copyright (C) 2009 The Project Lombok Authors.
* Copyright (C) 2009-2019 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -32,6 +32,35 @@ import lombok.patcher.TargetMatcher;
import org.objectweb.asm.Opcodes;
/**
* Defines scripts to modify bytecode directly.
*
* Some definitions:
*
* <h2>transplant</h2>
*
* Transplant means that the code you are trying to inject into another class (generally called the {@code hook}) is copied
* into the target class. Specifically, you identify a method (the hook) that contains the code that must run as part of the
* method targeted for manipulation (the target). Transplantation means that the entire hook method, signature and all, is copied
* straight into the target class. You should definitely make the hook static unless you really know what you are doing.<br />
* <ul>
* <li>Advantage: Your hook is loaded by the same classloader as the target, which helps a lot with classloader issues</li>
* <li>Advantage: Whilst you'd have to write it reflectively otherwise your hook can't be compiled, you still get free, unfettered access even to private members of the target</li>
* <li>Disadvantage: Because your hook method is moved, you <em>cannot</em> reference <em>anything</em> else from the surroundings of where your hook method lives. Don't make helper methods!
* </ul>
*
* <h2>insert</h2>
*
* Insert is like transplant but even more aggressive: The actual bytecode is dropped straight into the relevant place inside the target
* method. insertion only works for extremely simple methods, because no effort is made to ensure that the target location's local space
* and/or framesizes are sufficient, and in any case any usage of local slots by your injected code would simply overwrite. Generally,
* for one-liners, especially if the one-liner is to replace something with a constant or wrap it with a single method call, you can use it.
*
* <h2>cast</h2>
*
* This lets you have the method that contains the code you wish to inject simply return {@code java.lang.Object}; this value will be
* casted to the required type in the targeted method.
*/
public class ScriptBuilder {
private ScriptBuilder() throws NoSuchMethodException {
throw new NoSuchMethodException("ScriptBuilder cannot be instantiated - just use the static methods.");
......@@ -39,12 +68,12 @@ public class ScriptBuilder {
private static void checkTypeSyntaxSlash(String spec) {
if (spec.indexOf('.') > -1) throw new IllegalArgumentException(
"Your type specification includes a dot, but this method wants a slash-separated type specification");
"Your type specification includes a dot, but this method wants a slash-separated type specification");
}
private static void checkTypeSyntaxDot(String spec) {
if (spec.indexOf('/') > -1) throw new IllegalArgumentException(
"Your type specification includes a slash, but this method wants a dot-separated type specification");
"Your type specification includes a slash, but this method wants a dot-separated type specification");
}
public static class AddFieldBuilder {
......@@ -76,6 +105,10 @@ public class ScriptBuilder {
return this;
}
/**
* @param value The value the field should be initialized to. Has to be a constant value of the appropriate type. Optional;
* if skipped, you get the default 0/false/null.
*/
public AddFieldBuilder value(Object value) {
this.value = value;
return this;
......@@ -151,37 +184,59 @@ public class ScriptBuilder {
return new ExitFromMethodEarlyScript(matchers, decisionMethod, valueMethod, transplant, insert, requests);
}
/**
* The method that you want to modify.
*/
public ExitEarlyBuilder target(TargetMatcher matcher) {
this.matchers.add(matcher);
return this;
}
/**
* The code to be used to decide whether or not a given call should return immediately. If omitted, it's as if you hook
* in the code: {@code return true;}. {@code hook} must be a static method with a {@code boolean} return type.
*/
public ExitEarlyBuilder decisionMethod(Hook hook) {
this.decisionMethod = hook;
return this;
}
/**
* The code to be used to decide the return value. Cannot be specified if {@code target} is a method that returns {@code void},
* otherwise, mandatory. {@code hook} must be a static method with the same return value as {@code target}.
*/
public ExitEarlyBuilder valueMethod(Hook hook) {
this.valueMethod = hook;
return this;
}
/**
* See {@link ScriptBuilder} javadoc for details.
* Applies to both the {@code valueMethod} and the {@code decisionMethod}.
*/
public ExitEarlyBuilder transplant() {
this.transplant = true;
this.insert = false;
return this;
}
/**
* See {@link ScriptBuilder} javadoc for details.
* Applies to both the {@code valueMethod} and the {@code decisionMethod}.
*/
public ExitEarlyBuilder insert() {
this.transplant = false;
this.insert = true;
return this;
}
/**
* Defines the parameter(s) of your decision and value methods.
*/
public ExitEarlyBuilder request(StackRequest... requests) {
for (StackRequest r : requests) {
if (r == StackRequest.RETURN_VALUE) throw new IllegalArgumentException(
"You cannot ask for the tentative return value in ExitFromMethodEarlyScript.");
"You cannot ask for the tentative return value in ExitFromMethodEarlyScript");
this.requests.add(r);
}
......@@ -204,27 +259,53 @@ public class ScriptBuilder {
return new ReplaceMethodCallScript(matchers, methodToReplace, replacementMethod, transplant, insert, extraRequests);
}
/**
* The method that you want to modify.
*/
public ReplaceMethodCallBuilder target(TargetMatcher matcher) {
this.matchers.add(matcher);
return this;
}
/**
* The method you want to modify is scanned for any calls to {@code methodToReplace}; such calls are then replaced to be
* calls to this method instead. The {@code hook} should be static; its first argument should be the type of receiver
* and then all parameters of the {@code methodToReplace}, and then any further arguments you ask for via {@code requestExtra}.
* <br><br><br>
* Example:<br>
* You target instance method {@code bar} in class {@code Foo}.<br>
* Your methodToReplace is: java.lang.String charAt(int).<br>
* You added {@code StackRequest.THIS} via requestExtra.<br>
* Your replacement method signature should be: {@code public static char replacementCharAt(String receiver, int param1, Foo caller)}.
*/
public ReplaceMethodCallBuilder replacementMethod(Hook hook) {
this.replacementMethod = hook;
return this;
}
/**
* The method you want to modify is scanned for any calls to this specific method; such calls are then replaced to be
* calls to {@code replacementMethod}.
*/
public ReplaceMethodCallBuilder methodToReplace(Hook hook) {
this.methodToReplace = hook;
return this;
}
/**
* See {@link ScriptBuilder} javadoc for details.
* Applies to {@code replacementMethod}.
*/
public ReplaceMethodCallBuilder transplant() {
this.transplant = true;
this.insert = false;
return this;
}
/**
* See {@link ScriptBuilder} javadoc for details.
* Applies to {@code replacementMethod}.
*/
public ReplaceMethodCallBuilder insert() {
this.transplant = false;
this.insert = true;
......@@ -234,7 +315,7 @@ public class ScriptBuilder {
public ReplaceMethodCallBuilder requestExtra(StackRequest... requests) {
for (StackRequest r : requests) {
if (r == StackRequest.RETURN_VALUE) throw new IllegalArgumentException(
"You cannot ask for the tentative return value in ReplaceMethodCallScript.");
"You cannot ask for the tentative return value in ReplaceMethodCallScript");
this.extraRequests.add(r);
}
......@@ -242,6 +323,10 @@ public class ScriptBuilder {
}
}
/**
* This script scans a target method (The <em>target</em>) for calls to a certain method (The <code>methodToWrap</code>) and
* adds a call to a third method (The <code>wrapMethod</code>), which can inspect and even modify the value.
*/
public static class WrapMethodCallBuilder {
private List<TargetMatcher> matchers = new ArrayList<TargetMatcher>();
private Hook wrapMethod;
......@@ -257,27 +342,55 @@ public class ScriptBuilder {
return new WrapMethodCallScript(matchers, methodToWrap, wrapMethod, transplant, insert, extraRequests);
}
/**
* The method that you want to modify.
*/
public WrapMethodCallBuilder target(TargetMatcher matcher) {
this.matchers.add(matcher);
return this;
}
/**
* The method you want to modify is scanned for any calls to {@code methodToWrap}; a call to {@code wrapMethod}
* is injected immediately after every call to {@code methodToWrap}.
* The {@code hook} should be static; the returntype of the method you are wrapping should be replicated both as
* the hook's return type and as the type of its first argument. The hook should then have any further arguments
* you ask for via {@code requestExtra}.
* <br><br><br>
* Example:<br>
* You target instance method {@code bar} in class {@code Foo}.<br>
* Your methodToWrap is: java.lang.String charAt(int).<br>
* You added {@code StackRequest.THIS} via requestExtra.<br>
* Your wrap method signature should be: {@code public static char wrapCharAt(char result, Foo caller)}.
*/
public WrapMethodCallBuilder wrapMethod(Hook hook) {
this.wrapMethod = hook;
return this;
}
/**
* The method you want to modify is scanned for any calls to this specific method; calls to {@code wrapMethod}
* are injected immediately following any calls to this method.
*/
public WrapMethodCallBuilder methodToWrap(Hook hook) {
this.methodToWrap = hook;
return this;
}
/**
* See {@link ScriptBuilder} javadoc for details.
* Applies to {@code wrapMethod}.
*/
public WrapMethodCallBuilder transplant() {
this.transplant = true;
this.insert = false;
return this;
}
/**
* See {@link ScriptBuilder} javadoc for details.
* Applies to {@code wrapMethod}.
*/
public WrapMethodCallBuilder insert() {
this.transplant = false;
this.insert = true;
......@@ -287,7 +400,7 @@ public class ScriptBuilder {
public WrapMethodCallBuilder requestExtra(StackRequest... requests) {
for (StackRequest r : requests) {
if (r == StackRequest.RETURN_VALUE) throw new IllegalArgumentException(
"You cannot ask for the tentative return value in WrapMethodCallBuilder.");
"You cannot ask for the tentative return value in WrapMethodCallBuilder");
this.extraRequests.add(r);
}
......@@ -295,41 +408,69 @@ public class ScriptBuilder {
}
}
/**
* This script lets you inspect (and replace) the returned value for any target method.
*/
public static class WrapReturnValueBuilder {
private List<TargetMatcher> matchers = new ArrayList<TargetMatcher>();
private Hook wrapMethod;
private Set<StackRequest> requests = new HashSet<StackRequest>();
private boolean transplant, insert;
private boolean transplant, insert, cast;
public WrapReturnValuesScript build() {
if (matchers.isEmpty()) throw new IllegalStateException("You have to set a target method matcher");
if (wrapMethod == null) throw new IllegalStateException("You have to set a method you'd like to wrap the return values with");
return new WrapReturnValuesScript(matchers, wrapMethod, transplant, insert, requests);
return new WrapReturnValuesScript(matchers, wrapMethod, transplant, insert, cast, requests);
}
/**
* The method that you want to modify.
*/
public WrapReturnValueBuilder target(TargetMatcher matcher) {
this.matchers.add(matcher);
return this;
}
/**
* All attempts to return in the {@code target} will be preceded by a call to this {@code hook} immediately prior to the return.
*/
public WrapReturnValueBuilder wrapMethod(Hook hook) {
this.wrapMethod = hook;
return this;
}
/**
* See {@link ScriptBuilder} javadoc for details.
* Applies to {@code wrapMethod}.
*/
public WrapReturnValueBuilder transplant() {
this.transplant = true;
this.insert = false;
return this;
}
/**
* See {@link ScriptBuilder} javadoc for details.
* Applies to {@code wrapMethod}.
*/
public WrapReturnValueBuilder insert() {
if (this.cast) throw new IllegalArgumentException("cast and insert are mutually exlusive");
this.transplant = false;
this.insert = true;
return this;
}
/**
* See {@link ScriptBuilder} javadoc for details.
* Applies to {@code wrapMethod}.
*/
public WrapReturnValueBuilder cast() {
if (this.insert) throw new IllegalArgumentException("insert and cast are mutually exlusive");
this.cast = true;
return this;
}
public WrapReturnValueBuilder request(StackRequest... requests) {
for (StackRequest r : requests) this.requests.add(r);
return this;
......@@ -379,21 +520,27 @@ public class ScriptBuilder {
}
/**
* Allows you patch any method so that you get called first, and you can choose to take over entirely if you want.
* This script lets you modify a target method to return immediately upon being invoked; this can also be used to replace
* entirely what it does.
* Your can control:<br />
* (optional) Have a 'decider' method which decides whether or not a call to the target should return immediately.<br />
* (mandatory) The value to be returned. For void methods this is irrelevant, of course.
*/
public static ExitEarlyBuilder exitEarly() {
return new ExitEarlyBuilder();
}
/**
* Allows you to replace all calls to a given method in a given method with calls to a method of your choosing.
* This script scans a target method (The <em>target</em>) for calls to a certain method (The <code>methodToReplace</code>) and
* replaces these calls with a different call; usually the replacement is your own creation.
*/
public static ReplaceMethodCallBuilder replaceMethodCall() {
return new ReplaceMethodCallBuilder();
}
/**
* Allows you to inspect and optionally replace the result of calls to a given method in a given method.
* This script scans a target method (The <em>target</em>) for calls to a certain method (The <code>methodToWrap</code>) and
* adds a call to a third method (The <code>wrapMethod</code>), which can inspect and even modify the value.
*/
public static WrapMethodCallBuilder wrapMethodCall() {
return new WrapMethodCallBuilder();
......
/*
* Copyright (C) 2009-2017 The Project Lombok Authors.
* Copyright (C) 2009-2019 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -41,7 +41,7 @@ public final class WrapReturnValuesScript extends MethodLevelPatchScript {
private final Hook wrapper;
private final Set<StackRequest> requests;
private final boolean hijackReturnValue;
private final boolean transplant, insert;
private final boolean transplant, insert, cast;
/**
* @param targetMethod The target method to patch.
......@@ -50,7 +50,7 @@ public final class WrapReturnValuesScript extends MethodLevelPatchScript {
* helper methods if you use this!
* @param requests The kinds of parameters you want your hook method to receive.
*/
WrapReturnValuesScript(List<TargetMatcher> matchers, Hook wrapper, boolean transplant, boolean insert, Set<StackRequest> requests) {
WrapReturnValuesScript(List<TargetMatcher> matchers, Hook wrapper, boolean transplant, boolean insert, boolean cast, Set<StackRequest> requests) {
super(matchers);
if (wrapper == null) throw new NullPointerException("wrapper");
this.wrapper = wrapper;
......@@ -58,13 +58,15 @@ public final class WrapReturnValuesScript extends MethodLevelPatchScript {
this.requests = requests;
this.transplant = transplant;
this.insert = insert;
this.cast = cast && hijackReturnValue;
assert !(insert && transplant);
assert !(cast && insert);
}
@Override protected MethodPatcher createPatcher(ClassWriter writer, final String classSpec, TransplantMapper transplantMapper) {
final MethodPatcher patcher = new MethodPatcher(writer, transplantMapper, new MethodPatcherFactory() {
public MethodVisitor createMethodVisitor(String name, String desc, MethodVisitor parent, MethodLogistics logistics) {
return new WrapReturnValues(parent, logistics, classSpec);
return new WrapReturnValues(parent, logistics, classSpec, desc);
}
});
......@@ -73,14 +75,25 @@ public final class WrapReturnValuesScript extends MethodLevelPatchScript {
return patcher;
}
private static String extractReturnValueFromDesc(String desc) {
int lastIdx = desc == null ? -1 : desc.lastIndexOf(')');
if (lastIdx == -1) return null;
String rd = desc.substring(lastIdx + 1);
if (rd.startsWith("L") && rd.endsWith(";")) return rd.substring(1, rd.length() - 1);
return rd;
}
private class WrapReturnValues extends MethodVisitor {
private final MethodLogistics logistics;
private final String ownClassSpec;
private final String returnValueDesc;
public WrapReturnValues(MethodVisitor mv, MethodLogistics logistics, String ownClassSpec) {
public WrapReturnValues(MethodVisitor mv, MethodLogistics logistics, String ownClassSpec, String desc) {
super(Opcodes.ASM7, mv);
this.logistics = logistics;
this.ownClassSpec = ownClassSpec;
this.returnValueDesc = extractReturnValueFromDesc(desc);
}
@Override public void visitInsn(int opcode) {
......@@ -108,9 +121,14 @@ public final class WrapReturnValuesScript extends MethodLevelPatchScript {
logistics.generateLoadOpcodeForParam(param.getParamPos(), mv);
}
if (insert) insertMethod(wrapper, mv);
else super.visitMethodInsn(Opcodes.INVOKESTATIC, transplant ? ownClassSpec : wrapper.getClassSpec(), wrapper.getMethodName(),
if (insert) {
insertMethod(wrapper, mv);
} else {
super.visitMethodInsn(Opcodes.INVOKESTATIC, transplant ? ownClassSpec : wrapper.getClassSpec(), wrapper.getMethodName(),
wrapper.getMethodDescriptor(), false);
}
if (cast) super.visitTypeInsn(Opcodes.CHECKCAST, returnValueDesc);
super.visitInsn(opcode);
}
}
......
......@@ -22,7 +22,7 @@
package lombok.patcher.scripts;
import static lombok.patcher.scripts.ScriptTestUtils.*;
import static junit.framework.Assert.*;
import static org.junit.Assert.*;
import java.io.InputStream;
import java.lang.reflect.Constructor;
......
/*
* Copyright (C) 2009 The Project Lombok Authors.
* Copyright (C) 2009-2019 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -57,6 +57,26 @@ public class TestWrapReturnValuesScript {
assertEquals("patched return value", 20, (int)(Integer)fooMethod.invoke(ex1Constructor.newInstance(), 5, null));
}
@Test
public void testWrapReturnWithCast() throws Exception {
InputStream raw = TestWrapReturnValuesScript.class.getResourceAsStream("/lombok/patcher/scripts/TestWrapReturnValuesScriptEx1.class");
byte[] pretransform = readFromStream(raw);
byte[] posttransform = ScriptBuilder.wrapReturnValue()
.target(new MethodTarget("lombok.patcher.scripts.TestWrapReturnValuesScriptEx1", "bar",
"java.lang.String[]"))
.wrapMethod(new Hook("lombok.patcher.scripts.TestWrapReturnValuesScript$TestWrapReturnValuesScriptEx2",
"hook2", "java.lang.Object", "java.lang.Object"))
.cast().request(StackRequest.RETURN_VALUE)
.build().patch("lombok/patcher/scripts/TestWrapReturnValuesScriptEx1", pretransform, TransplantMapper.IDENTITY_MAPPER);
Class<?> ex1 = loadRaw("lombok.patcher.scripts.TestWrapReturnValuesScriptEx1", posttransform);
Method barMethod = ex1.getMethod("bar");
Constructor<?> ex1Constructor = ex1.getDeclaredConstructor();
barMethod.setAccessible(true);
ex1Constructor.setAccessible(true);
assertArrayEquals("patched return value", new String[] {"B"}, (String[]) barMethod.invoke(ex1Constructor.newInstance()));
}
public static class TestWrapReturnValuesScriptEx2 {
public static int hook1(int supposedReturnValue, Object thisRef, int param1, String[] param2) {
assertEquals("supposedReturnValue", param1 < 10 ? 10 : 80, supposedReturnValue);
......@@ -69,6 +89,13 @@ public class TestWrapReturnValuesScript {
return supposedReturnValue *2;
}
public static Object hook2(Object supposedReturnValue) {
assertEquals("supposedReturnValue-type", String[].class, supposedReturnValue.getClass());
String[] srv = (String[]) supposedReturnValue;
assertArrayEquals("supposedReturnValue", new String[] {"A"}, srv);
return new String[] {"B"};
}
}
}
......@@ -78,4 +105,8 @@ class TestWrapReturnValuesScriptEx1 {
if (x < 10) return 10;
return 80;
}
public String[] bar() {
return new String[] {"A"};
}
}