Intercepting Object.class toString() method with Byte Buddy - byte-buddy

I am using Byte Buddy to intercept some JDK methods, System.class and Thread.class is well, but is not work on java.lang.Object. I am running my test on JDK8 and it doesn't throw any errors.
final public class AgentBootstrap {
public static void premain(String agentArgs, Instrumentation inst) throws Exception {
try {
new AgentBuilder.Default()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
.with(AgentBuilder.TypeStrategy.Default.REBASE)
.enableNativeMethodPrefix("$$mynative_")
.ignore(ElementMatchers.none())
.with(
new AgentBuilder.Listener.Filtering(
new StringMatcher("java.lang.Object", StringMatcher.Mode.EQUALS_FULLY),
AgentBuilder.Listener.StreamWriting.toSystemOut()))
.type(ElementMatchers.is(Object.class))
.transform(new Transformer() {
#Override
public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
return builder.method(ElementMatchers.named("toString")).intercept(FixedValue.value("HELLO BYTE BUDDY!"));
}
})
.installOn(inst);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
And I try to use Javassist, transform java.lang.Object's method is successful.
Could anyone konw why does it not work on Object.class?

You want to use
disableClassFormatChanges() in order to avoid structural class file changes which would violate retransformation rules,
the advice API in order to insert code into existing methods without adding new ones.
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import java.lang.instrument.Instrumentation;
import static net.bytebuddy.agent.builder.AgentBuilder.RedefinitionStrategy.RETRANSFORMATION;
import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC;
import static net.bytebuddy.matcher.ElementMatchers.*;
class Scratch {
public static class ToStringAdvice {
#Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
public static boolean before() {
// Skip original method execution (false is the default value for boolean)
return false;
}
#Advice.OnMethodExit
public static void after(#Advice.Return(readOnly = false, typing = DYNAMIC) Object returnValue) {
// Set fixed return value
returnValue = "HELLO BYTE BUDDY!";
}
}
public static void premain(String agentArgs, Instrumentation inst) {
new AgentBuilder.Default()
.disableClassFormatChanges()
.with(RETRANSFORMATION)
.with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError())
.with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly())
.with(AgentBuilder.InstallationListener.StreamWriting.toSystemError())
.ignore(none())
.type(is(Object.class))
.transform((builder, typeDescription, classLoader, module) ->
builder.visit(
Advice
.to(ToStringAdvice.class)
.on(named("toString"))
)
)
.installOn(inst);
}
public static void main(String[] args) throws Exception {
Instrumentation instrumentation = ByteBuddyAgent.install();
premain("", instrumentation);
instrumentation.retransformClasses(Object.class);
System.out.println(new Object());
}
}
This works, ...
[Byte Buddy] BEFORE_INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer#3bfdc050 on sun.instrument.InstrumentationImpl#1bce4f0a
[Byte Buddy] REDEFINE BATCH #0 [1 of 1 type(s)]
[Byte Buddy] TRANSFORM java.lang.Object [null, null, loaded=true]
[Byte Buddy] REDEFINE COMPLETE 1 batch(es) containing 1 types [0 failed batch(es)]
[Byte Buddy] INSTALL HELLO BYTE BUDDY! on HELLO BYTE BUDDY!
[Byte Buddy] TRANSFORM java.lang.Object [null, null, loaded=true]
HELLO BYTE BUDDY!
... but I think you should think twice before manipulating JDK core classes. Look at the above log. Do you notice how in log line
[Byte Buddy] INSTALL HELLO BYTE BUDDY! on HELLO BYTE BUDDY!
two objects are being printed which obviously use the method you have just advised? So be careful!

Related

JUnit 5 Parameterized test #ArgumentsSource parameters not loading

I have created below JUnit5 parameterized test with ArgumentsSource for loading arguments for the test:
public class DemoModelValidationTest {
public ParamsProvider paramsProvider;
public DemoModelValidationTest () {
try {
paramsProvider = new ParamsProvider();
}
catch (Exception iaex) {
}
}
#ParameterizedTest
#ArgumentsSource(ParamsProvider.class)
void testAllConfigurations(int configIndex, String a) throws Exception {
paramsProvider.executeSimulation(configIndex);
}
}
and the ParamsProvider class looks like below:
public class ParamsProvider implements ArgumentsProvider {
public static final String modelPath = System.getProperty("user.dir") + File.separator + "demoModels";
YAMLDeserializer deserializedYAML;
MetaModelToValidationModel converter;
ValidationRunner runner;
List<Configuration> configurationList;
List<Arguments> listOfArguments;
public ParamsProvider() throws Exception {
configurationList = new ArrayList<>();
listOfArguments = new LinkedList<>();
deserializedYAML = new YAMLDeserializer(modelPath);
deserializedYAML.load();
converter = new MetaModelToValidationModel(deserializedYAML);
runner = converter.convert();
configurationList = runner.getConfigurations();
for (int i = 0; i < configurationList.size(); i++) {
listOfArguments.add(Arguments.of(i, configurationList.get(i).getName()));
}
}
public void executeSimulation(int configListIndex) throws Exception {
final Configuration config = runner.getConfigurations().get(configListIndex);
runner.run(config);
runner.getReporter().consolePrintReport();
}
#Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return listOfArguments.stream().map(Arguments::of);
// return Stream.of(Arguments.of(0, "Actuator Power"), Arguments.of(1, "Error Logging"));
}}
In the provideArguments() method, the commented out code is working fine, but the first line of code
listOfArguments.stream().map(Arguments::of)
is returning the following error:
org.junit.platform.commons.PreconditionViolationException: Configuration error: You must configure at least one set of arguments for this #ParameterizedTest
I am not sure whether I am having a casting problem for the stream in provideArguments() method, but I guess it somehow cannot map the elements of listOfArguments to the stream, which can finally take the form like below:
Stream.of(Arguments.of(0, "Actuator Power"), Arguments.of(1, "Error Logging"))
Am I missing a proper stream mapping of listOfArguments?
provideArguments(…) is called before your test is invoked.
Your ParamsProvider class is instantiated by JUnit. Whatever you’re doing in desiralizeAndCreateValidationRunnerInstance should be done in the ParamsProvider constructor.
Also you’re already wrapping the values fro deserialised configurations to Arguments and you’re double wrapping them in providesArguments.
Do this:
#Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return listOfArguments.stream();
}}

How to use ByteBuddy in a java project

I made a simple project of java to test ByteBuddy . I typed exactly the same code in a tutorial made by Rafael Winterhalter but it showing some errors
1) ByteBuddyAgent cannot be resolved.
2) type cannot be resolved to a variable.
3) builder cannot be resolved.
4) method cannot be resolved to a variable.
I added the byte-buddy-1.7.1.jar as a referenced library.
public class LogAspect {
public static void main(String[] args){
premain("", ByteBuddyAgent.installOnOpenJDK());
Calculator calculator = new Calculator();
int sum = calculator.sum(10, 15, 20);
System.out.println("Sum is "+ sum);
}
public static void premain(String arg, Instrumentation inst){
new AgentBuilder.Default()
.rebase(type -> type.getSimpleName().equals("Calculator"))
.transform((builder, typeDescription) -> builder
.method(method -> method.getDeclaredAnnotations().isAnnotationPresent(Log.class))
.intercept(MethodDelegation.to(LogAspect.class).andThen(SuperMethodCall.INSTANCE)))
.installOn(inst);
}
public static void intercept(#Origin Method method){
System.out.println(method.getName()+" is called.");
}
}
#interface Log{
}
class Calculator {
#Log
public int sum(int... values) {
return Arrays.stream(values).sum();
}
}
It looks like you are using a very outdated tutorial of the 0.* ara. Use an IDE to check your compile-time errors and check the webpage for more recent tutorials.

Customized parameter logging when using aspect oriented programing

All the examples I've seen that use aspect oriented programming for logging either log just class, method name and duration, and if they log parameters and return values they simply use ToString(). I need to have more control over what is logged. For example I want to skip passwords, or in some cases log all properties of an object but in other cases just the id property.
Any suggestions? I looked at AspectJ in Java and Unity interception in C# and could not find a solution.
You could try introducing parameter annotations to augment your parameters with some attributes. One of those attributes could signal to skip logging the parameter, another one could be used to specify a converter class for the string representation.
With the following annotations:
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Log {
}
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface SkipLogging {
}
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface ToStringWith {
Class<? extends Function<?, String>> value();
}
the aspect could look like this:
import java.lang.reflect.Parameter;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public aspect LoggingAspect {
private final static Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
pointcut loggableMethod(): execution(#Log * *..*.*(..));
before(): loggableMethod() {
MethodSignature signature = (MethodSignature) thisJoinPoint.getSignature();
Parameter[] parameters = signature.getMethod()
.getParameters();
String message = IntStream.range(0, parameters.length)
.filter(i -> this.isLoggable(parameters[i]))
.<String>mapToObj(i -> toString(parameters[i], thisJoinPoint.getArgs()[i]))
.collect(Collectors.joining(", ",
"method execution " + signature.getName() + "(", ")"));
Logger methodLogger = LoggerFactory.getLogger(
thisJoinPointStaticPart.getSignature().getDeclaringType());
methodLogger.debug(message);
}
private boolean isLoggable(Parameter parameter) {
return parameter.getAnnotation(SkipLogging.class) == null;
}
private String toString(Parameter parameter, Object value) {
ToStringWith toStringWith = parameter.getAnnotation(ToStringWith.class);
if (toStringWith != null) {
Class<? extends Function<?, String>> converterClass =
toStringWith.value();
try {
#SuppressWarnings("unchecked")
Function<Object, String> converter = (Function<Object, String>)
converterClass.newInstance();
String str = converter.apply(value);
return String.format("%s='%s'", parameter.getName(), str);
} catch (Exception e) {
logger.error("Couldn't instantiate toString converter for logging "
+ converterClass.getName(), e);
return String.format("%s=<error converting to string>",
parameter.getName());
}
} else {
return String.format("%s='%s'", parameter.getName(), String.valueOf(value));
}
}
}
Test code:
public static class SomethingToStringConverter implements Function<Something, String> {
#Override
public String apply(Something something) {
return "Something nice";
}
}
#Log
public void test(
#ToStringWith(SomethingToStringConverter.class) Something something,
String string,
#SkipLogging Class<?> cls,
Object object) {
}
public static void main(String[] args) {
// execution of this method should log the following message:
// method execution test(something='Something nice', string='some string', object='null')
test(new Something(), "some string", Object.class, null);
}
I used Java 8 Streams API in my answer for it's compactness, you could convert the code to normal Java code if you don't use Java 8 features or need better efficiency. It's just to give you an idea.

AspectJ, Intertype Definition

I am receiving this error when I compile
The type XXX must implement the inherited abstract method
I have three files
A default implementation [com.SafeReaderIMPL.java]
public class SafeReaderIMPL implements ISafeReader {
private boolean successfulRead;
public SafeReaderIMPL() {
successfulRead = true;
}
protected void fail() {
successfulRead = false;
}
#Override
public boolean isSuccessfulRead() {
return successfulRead;
}
}
An interface file [com.ISafeReader.java]
public interface ISafeReader {
public boolean isSuccessfulRead();
}
An apsect (using annotations) [com.SafeReaderAspect.java]
#Aspect
public class SafeReaderAspect {
#DeclareParents(value = "com.BadReader", defaultImpl = SafeReaderIMPL.class)
public ISafeReader implementedInterface;
#AfterThrowing(pointcut = "execution(* *.*(..)) && this(m)", throwing = "e")
public void handleBadRead(JoinPoint joinPoint, ISafeReader m, Throwable e) {
((SafeReaderIMPL)m).fail();
}
}
And a Test Class [com.BadReader]
public class BadReader {
public void fail() throws Throwable {
throw new Throwable();
}
}
I compile the first three files in a separate jar using
ajc -source 1.8 -sourceroots . -outjar aspectLib.jar
I then compile the second file using the aspectLib like so
ajc -source 1.8 -sourceroots . -aspectpath ./aspectLib.jar -outjar common.jar
When I go to compile the second jar I get the error. I am using the latest stable version of AspectJ 1.8.3
BadReader.java:10 [error] The type BadReader must implement the
inherited abstract method ISafeReader.isSuccessfulRead() public class
BadReader {
^^^^^^^^
The problem is not two-step compilation as such, but the fact that #DeclareParents in #AspectJ syntax in not 100% compatible with declare parents in native syntax. Actually, #DeclareParents for introducing default interface implementations is superseded by #DeclareMixin (see this bug ticket), but the downside of the mixin approach is that you do not have a real A implements B scenario there, i.e. you cannot cast as you wish in your after-throwing advice, so this is also not a good option in your case.
So what do you do if you want to keep the two-step compilation approach? Just use native syntax:
Interface:
package com;
public interface ISafeReader {
boolean isSuccessfulRead();
}
Default implementation:
package com;
public class SafeReaderIMPL implements ISafeReader {
private boolean successfulRead;
public SafeReaderIMPL() { successfulRead = true; }
public void fail() { successfulRead = false; }
#Override public boolean isSuccessfulRead() { return successfulRead; }
}
ITD aspect:
package com;
public aspect SafeReaderAspect {
declare parents : com.BadReader implements SafeReaderIMPL;
after(ISafeReader safeReader) throwing : execution(* *(..)) && this(safeReader) {
System.out.println(thisJoinPoint + " - calling 'fail()' before rethrowing error");
((SafeReaderIMPL) safeReader).fail();
}
}
ITD target class with sample main method:
package com;
public class BadReader {
public void doSomething() {
throw new RuntimeException("my error");
}
public static void main(String[] args) {
BadReader badReader = new BadReader();
System.out.println("badReader.isSuccessfulRead() = " + badReader.isSuccessfulRead());
try { badReader.doSomething(); }
catch(Throwable t) { System.out.println(t); }
System.out.println("badReader.isSuccessfulRead() = " + badReader.isSuccessfulRead());
}
}
Now you can use the two-stage compilation approach.
Console output:
badReader.isSuccessfulRead() = true
execution(void com.BadReader.doSomething()) - calling 'fail()' before rethrowing error
java.lang.RuntimeException: my error
badReader.isSuccessfulRead() = false
The problem is due to the two-step compilation. During the second step, ajc needs the source code of SafeReaderIMPL to be able to weave BadReader, but it cannot find it into aspectLib.jar
In fact, if you try compiling in a single step (I did), it compiles and runs.
Unfortunately I don't know a way to fix this without providing the source code during the second compile step, which I suppose would render the whole two-step approach a bit pointless.

jmap -permstat takes long time and hangs

We started seeing 'java.lang.OutOfMemoryError: PermGen space'. In order to findout what is held in perm space, tried running
'/usr/j2sdk1.6.0_13/bin/jmap -permstat 20476 -J-mx1280m > /tmp/permstats20476.txt &'
This command is taking long time ..... in between it gave below exception:
finding class loader instances ..252829 intern Strings occupying 30781792 bytes.
Finding object size using Printezis bits and skipping over...
Finding object size using Printezis bits and skipping over...
Finding object size using Printezis bits and skipping over...
Finding object size using Printezis bits and skipping over...
done.
computing per loader stat ..done.
please wait.. computing liveness...................Exception in thread "Thread-1" java.lang.OutOfMemoryError: GC overhead limit exceeded
at sun.jvm.hotspot.debugger.linux.LinuxDebuggerLocal.readBytesFromProcess0(Native Method)
at sun.jvm.hotspot.debugger.linux.LinuxDebuggerLocal.access$1000(LinuxDebuggerLocal.java:51)
at sun.jvm.hotspot.debugger.linux.LinuxDebuggerLocal$1ReadBytesFromProcessTask.doit(LinuxDebuggerLocal.java:558)
at sun.jvm.hotspot.debugger.linux.LinuxDebuggerLocal$LinuxDebuggerLocalWorkerThread.run(LinuxDebuggerLocal.java:127)
but it is not completing ...
[svcmig2#app430 ~]$ uname -a
Linux app430... 2.6.18-194.el5 #1 SMP Tue Mar 16 21:52:39 EDT 2010 x86_64 x86_64 x86_64 GNU/Linux
is there any other alternates to jmap ? so that i get the perm stats fast
is there any other alternates to jmap ? so that i get the perm stats fast
You can make one yourself! This is really easy with the aid of Serviceability Agent.
Here is an example for your case:
import sun.jvm.hotspot.memory.*;
import sun.jvm.hotspot.oops.*;
import sun.jvm.hotspot.runtime.*;
import sun.jvm.hotspot.tools.Tool;
public class PermTool extends Tool {
#Override
public void run() {
// Show PermGen object histogram
ObjectHistogram histo = new ObjectHistogram();
VM.getVM().getObjectHeap().iteratePerm(histo);
histo.print();
// List all classes in PermGen with their ClassLoaders
VM.getVM().getObjectHeap().iteratePerm(new DefaultHeapVisitor() {
#Override
public boolean doObj(Oop obj) {
if (obj instanceof InstanceKlass) {
obj.printValue();
Oop loader = ((InstanceKlass) obj).getClassLoader();
if (loader == null) {
System.out.println(" -- loaded by Bootstrap ClassLoader");
} else {
System.out.print(" -- loaded by ");
loader.printValue();
System.out.println();
}
}
return false;
}
});
}
public static void main(String[] args) {
new PermTool().start(args);
}
}
Just compile and run it using the same JDK as your target process.
Make sure jdk/lib/sa-jdi.jar is on the CLASSPATH.
You can set the JVM args: -XX:+HeapDumpOnOutOfMemoryError to get .hprof file when the oom arise, then use mat to analysis the duplicate loaded classes.
I have write a tool class with SA for your question. My JDK version is 1.8.0_151.
Here is source code:
import sun.jvm.hotspot.oops.*;
import sun.jvm.hotspot.runtime.*;
import sun.jvm.hotspot.tools.Tool;
/**
* #author duqi
* #createTime 2018/9/1 下午11:55
**/
public class PermTool extends Tool {
#Override
public void run() {
// Show PermGen object histogram
ObjectHistogram histo = new ObjectHistogram();
VM.getVM().getObjectHeap().iterate(histo);
histo.print();
// List all classes in PermGen with their ClassLoaders
VM.getVM().getObjectHeap().iterate(new DefaultHeapVisitor() {
#Override
public boolean doObj(Oop obj) {
if (obj.getKlass() instanceof InstanceKlass) {
obj.printValue();
Oop loader = ((InstanceKlass) obj.getKlass()).getClassLoader();
if (loader == null) {
System.out.println(" -- loaded by Bootstrap ClassLoader");
} else {
System.out.print(" -- loaded by ");
loader.printValue();
System.out.println();
}
}
return false;
}
});
}
public static void main(String[] args) {
new PermTool().execute(args);
}
}