Josh Stone
Josh Stone

Reputation: 4448

Intercepting field access using Javassist or ASM

I'm familiar with various ways of intercepting method invocations using proxies, but I'm wondering if there's a way to detect field access / dereferences on some proxy using a library like Javassist or ASM? For example:

void detectFieldName(Function<Foo, Supplier<String>> f) {
  Foo fooProxy = createFooProxy();
  f.apply(fooProxy);
}

detectFieldName((Foo foo) -> foo.bar);

Ideally from this I'd like to know that a field named bar was dereferenced.

Upvotes: 0

Views: 2138

Answers (2)

Rafael Winterhalter
Rafael Winterhalter

Reputation: 44007

Without major hacks, this is not possible. Field access in Java is not bound dynamically. This means, any reading or writing to a field is hardcoded into all using classes. With a method proxy, one makes use of the fact you can override a method to determine behavior. For intercepting field access, one would need to intercept the class that is using a field. Some libraries emulate this behavior by replacing field access by synthetic accessor methods. This requires however some build time redefinition of all concerned classes throughout the entire project.

As for your example, you could in theory use a tool like ASM to extract the required information from the lambda expression. However, note that the lambda expression's code will be extracted into a method of the class of the method that uses the lambda expression. You might have trouble finding out which method it actually is that contains your lambda but the byte code for invoking the expression will merely look something like this:

InvokeDynamic #0:accept:(LFoo;)Ljava/util/function/Function;

As you can see, the byte code will only contain a possibly ambiguous signature. Otherwise, you could of course copy the lambda expression's logic into a new class where you changed the logic of a field access. Since lambdas are by definition interfaces, the creation of such a new class would actually be comparably easy. But the problem with the method detection remains.

Upvotes: 1

Jeffrey Bosboom
Jeffrey Bosboom

Reputation: 13653

Looking at your updated use case: lambdas are desugared to synthetic (compiler-generated) methods, with a function object that forwards interface calls through the generated method (I haven't looked into exactly how this is implemented, but I think Brian Goetz has talked about it). You can just look in that method's bytecode (loaded from the class file; some of the ASM sample code does this) and read off the field access. Instrumentation is not required.

Note that you can't create a proxy to see field access; the field access is performed in the lambda method (or more generally, where the field is loaded) without executing any code in Foo. In fact, you don't even need to call the lambda if all you want is to get the field name, and if all you're using the Foo proxy for is the call, you don't need a proxy.


I'm not aware of any way to intercept field accesses as easily as java.lang.reflect.Proxy makes intercepting method calls.

The getfield and putfield bytecodes use symbolic descriptors that encode the class and field name, so you could use a Java agent to add method calls before or after each load and store passing the field name, object and value being loaded/stored. (This works best if you're only interested in a subset of fields, say all fields of a particular class.) Depending on your needs, you may also have to recognize reflective accesses to your fields by instrumenting use of java.lang.reflect.Field, the handle returned by MethodHandles.Lookup.findGetter/Setter etc. (which may involve interprocedural analysis or reasoning about string operations used to build the field name, etc.). You could also try instrumenting "just before" the library calls into some JVM-specific native functionality, but that ties you to one JVM implementation and your instrumentation may be skipped if the JVM intrinsifies (special-cases codegen for) reflective calls.

If you're willing to write C code, you can use the JVM Tool Interface watched field functions. This seems the easiest way to get information, but it's harder to do interesting Java-level things with (though you can call back into your Java support library from the JVMTI).

Upvotes: 2

Related Questions