I am trying to log field writes with bytebuddy. After reading some earlier posts, I started using MemberSubstitution and got something going using the following code:
private static Method FIELD_INTERCEPTOR = // reflective reference to interceptFieldWrite
AsmVisitorWrapper VISITOR = MemberSubstitution.relaxed()
.field(ElementMatchers.any())
.onWrite()
.replaceWith(FIELD_INTERCEPTOR)
.on(ElementMatchers.isMethod());
..
public static void interceptFieldWrite(Object object,Object value) {
System.out.println("intercepted field write in object " + object + " , attempt to set value to " + value);
}
..
The part I am struggling with is how to pass a reference to the field for which the access is intercepted to interceptFieldWrite (as string or instance of Field). If possible I would of course like to avoid reflection completely. I don't actually want to completely substitute field access, but just want to add a method with some checks just before it takes place. Is there a feature in bytebuddy to do this, or do I have to use something more low-level than ASM to achieve this ?
Byte Buddy offers this but you will have to compose your own StackManipulations to do so. The mechanism in MemberSubstitution is called replaceWithChain. Here you specify Steps where each step can do what you intend:
invoke a method via MethodInvocation.
write a field via FieldAccessor.
You will have to load the arguments to the method call and the field access prior to using the above stack manipulations via the MethodVariableAccess where the targeted element's offsets are represented by offsets.
In your case, this would require to read the target instance via
MethodVaribaleAccess.of(parameters.get(0)).loadFrom(offsets.get(0));
MethodVaribaleAccess.of(parameters.get(1)).loadFrom(offsets.get(1));
and the to execute the method or field write in question. The targeted field will be passed as target, you can cast it to FieldDescription if you only ever intercept fields.
Make sure you only intercept non-static fields where the this instance will not be passed.
Related
I've found the InvokeDynamic class and have made it work with a static method handle acquired via MethodHandles.Lookup.findStatic().
Now I am trying to do the same thing, but with a virtual method handle acquired via MethodHandles.Lookup.findVirtual().
I can cause my bootstrap method to run, and I make sure in my bootstrap method that I'm returning a ConstantCallSite(mh), where mh is the result of calling MethodHandles.Lookup.findVirtual(). (This part all works fine, i.e. I understand how "indy" works.)
However, when I use the resulting Implementation as the argument to an intercept() call, I cannot pass the actual object on which the method represented by the method handle is to be invoked. This is due to the withArgument() method being used for two contradictory purposes.
Here is my recipe:
Implementation impl =
InvokeDynamic.bootstrap(myBootstrapDescription, someOtherConstantArgumentsHere)
.invoke(theMethodName, theMethodReturnType)
// 0 is the object on which I want to invoke my virtual-method-represented-by-a-method-handle;
// 1 is the sole argument that the method actually takes.
.withArgument(0, 1);
There are some problems here.
Specifically, it seems that withArgument() is used by ByteBuddy for two things, not just one:
Specifying the parameter types that will be used to build a MethodType that will be supplied to the bootstrap method. Let's say my virtual method takes one argument.
Specifying how the instrumented method's arguments are passed to the actual method handle execution.
If I have supplied only one argument, the receiver type is left unbound and execution of the resulting MethodHandle cannot happen, because I haven't passed an argument that will be used for the receiver type "slot". If I accordingly supply two arguments to (1) above (as I do in my recipe), then the method handle is not found by my bootstrap method, because the supplied MethodType indicates that the method I am searching for requires two arguments, and my actual method that I'm finding only takes one.
Finally, I can work around this (and validate my hypothesis) by doing some fairly ugly stuff in my bootstrap method:
First, I deliberately continue to pass two arguments, not one, even though my method only takes two arguments: withArgument(0, 1)
In my bootstrap method, I now know that the MethodType it will receive will be "incorrect" (it will have two parameter types, not one, where the first parameter type will represent the receiver type). I drop the first parameter using MethodType#dropParameterTypes(int, int).
I call findVirtual() with the new MethodType. It returns a MethodType with two parameter types: the receiver type that it adds automatically, and the existing non-dropped parameter type.
(More simply I can just pass a MethodType as a constant to my bootstrap method via, for example, JavaConstant.MethodType.of(myMethodDescription) or built however I like, and ignore the one that ByteBuddy synthesizes. It would still be nice if there were instead a way to control the MethodType that ByteBuddy supplies (is obligated to supply) to the bootstrap method.)
When I do things like this in my bootstrap method, my recipe works. I'd prefer not to tailor my bootstrap method to ByteBudddy, but will here if I have to.
Is it a bug that ByteBuddy does not seem to allow InvokeDynamic to specify the ingredients for a MethodType directly, without also specifying the receiver?
What you described, is entirely independent of Byte-Buddy. It’s just the way how invokedynamic works.
JVMS, §5.4.3.6
5.4.3.6. Dynamically-Computed Constant and Call Site Resolution
To resolve an unresolved symbolic reference R to a dynamically-computed constant or call site, there are three tasks. First, R is examined to determine which code will serve as its bootstrap method, and which arguments will be passed to that code. Second, the arguments are packaged into an array and the bootstrap method is invoked. Third, the result of the bootstrap method is validated, and used as the result of resolution.
…
The second task, to invoke the bootstrap method handle, involves the following steps:
An array is allocated with component type Object and length n+3, where n is the number of static arguments given by R (n ≥ 0).
The zeroth component of the array is set to a reference to an instance of java.lang.invoke.MethodHandles.Lookup for the class in which R occurs, produced as if by invocation of the lookup method of java.lang.invoke.MethodHandles.
The first component of the array is set to a reference to an instance of String that denotes N, the unqualified name given by R.
The second component of the array is set to the reference to an instance of Class or java.lang.invoke.MethodType that was obtained earlier for the field descriptor or method descriptor given by R.
Subsequent components of the array are set to the references that were obtained earlier from resolving R's static arguments, if any. The references appear in the array in the same order as the corresponding static arguments are given by R.
A Java Virtual Machine implementation may be able to skip allocation of the array and, without any change in observable behavior, pass the arguments directly to the bootstrap method.
So the first three arguments to the bootstrap method are provided by the JVM according to the rules cited above. Only the other arguments are under the full control of the programmer.
The method type provided as 3rd argument always matches the type of the invokedynamic instruction describing the element types to pop from the stack and the type to push afterwards, if not void. Since this happens automatically, there’s not even a possibility to create contradicting, invalid bytecode in that regard; there is just a single method type stored in the class file.
If you want to bind the invokedynamic instruction to an invokevirtual operation using a receiver from the operand stack, you have exactly the choices already mentioned in your question. You may derive the method from other bootstrap arguments or drop the first parameter type of the instruction’s type. You can also use that first parameter type to determine the target of the method lookup. There’s nothing ugly in this approach; it’s the purpose of bootstrap methods to perform adaptations.
How can i retrieve the is_sfparam-content, either using query or function module.
then will be passed as a parameter to cl_recp_data_cn_general=>get_contract.
do you have any idea? Where can I get that is_sfparam-content?
Thanks
CALL METHOD cl_recp_data_cn_general=>get_contract
EXPORTING
id_guid = is_sfparam-content
IMPORTING
es_contract = contract
CHANGING
cf_error = lf_error.
This guid paramater brings no sense as it is generated in runtime. You can check this yourself by putting breakpoint at the first line (e.g. line 102) of method cl_recp_data_cn_general=>get_contract and checking id_guid var in debugger. It will be different with each run of preview in RECN tcode.
Check also line 11 of CONSTRUCTOR method of CL_RECP_SF_DOC class, it is executed during each form generation.
The real contract object lays in variable go_busobj of program SAPLRECA_BDT_APPL_TOOL which brings RECN functionality and it is global i.e. it is loaded into memory constantly while RECN is running, just passing doc object into called methods thru the call stack, which you can follow in debugger.
Where really form generation takes place is CL_RECP_SF_JOB class, for preview it is _FP_PREVIEW method, for print it is _FP_PRINT. For example, in preview method form FM is called in that place
What you need to do if you want to call arbitrary contract print-form, which is what I assume you want to do by asking this question:
Find out smartform FM name by putting breakpoint in the above line
Build parameters for this FM calling. The only obligatory parameter is LS_SFPARAM-CONTENT which is the GUID.
You can generate this GUID manually by creating CL_RECP_SF_DOC object, let it be io_doc var. The constructor of this object has input parameter is_doc which should be filled according to the attributes of contract which you want to print, and which are stored in VIBDRO and VICNCN tables.
After creating this object pass its attribute io_doc->md_guid to ls_sfparam-content, generate other parameters with CL_RECP_SF_JOB->_sf_get_form_param and
Run the smartform FM /1BCDWB/SF000000XX found at step 1 with these parameters
I didn't check all these steps by myself but I believe it should work.
I want to control read access to an Itcl public variable. I can do this for write access using something such as:
package require Itcl
itcl::class base_model_lib {
public variable filename ""
}
itcl::configbody base_model_lib::filename {
puts "in filename write"
dict set d_model filename $filename
}
The configbody defines what happens when config is called: $obj configure -filename foo.txt. But how do I control what happens during the read? Imagine that I want to do more than just look up a value during the read.
I would like to stay using the standard Itcl pattern of using cget/configure to expose these to the user.
So that is my question. However, let me describe what I really want to do and you tell me if I should do something completely different :)
I like python classes. I like that I can create a variable and read/write to it from outside the instance. Later, when I want to get fancy, I'll create methods (using #property and #property.setter) to customize the read/write without the user seeing an API change. I'm trying to do the same thing here.
My sample code also suggests something else I want to do. Actually, the filename is stored internally in a dictionary. i don't want to expose that entire dictionary to the user, but I do want them to be able to change values inside that dict. So, really 'filename' is just a stub. I don't want a public variable called that. I instead want to use cget and configure to read and write a "thing", which I may chose to make a simple public variable or may wish to define a procedure for looking it up.
PS: I'm sure I could create a method which took either one or two arguments. If one, its a read and two its a write. I assumed that wasn't the way to go as I don't think you could use the cget/configure method.
All Itcl variables are mapped to Tcl variables in a namespace whose name is difficult to guess. This means that you can get a callback whenever you read a variable (it happens immediately before the variable is actually read) via Tcl's standard tracing mechanism; all you need to do is to create the trace in the constructor. This requires the use of itcl::scope and is best done with itcl::code $this so that we can make the callback be a private method:
package require Itcl
itcl::class base_model_lib {
public variable filename ""
constructor {} {
trace add variable [itcl::scope filename] read [itcl::code $this readcallback]
}
private method readcallback {args} { # You can ignore the arguments here
puts "about to read the -filename"
set filename "abc.[expr rand()]"
}
}
All itcl::configbody does is effectively the equivalent for variable write traces, which are a bit more common, though we'd usually prefer you to set the trace directly these days as that's a more general mechanism. Demonstrating after running the above script:
% base_model_lib foo
foo
% foo configure
about to read the -filename
{-filename {} abc.0.8870089169996832}
% foo configure -filename
about to read the -filename
-filename {} abc.0.9588680136757288
% foo cget -filename
about to read the -filename
abc.0.694705847974264
As you can see, we're controlling exactly what is read via the standard mechanism (in this case, some randomly varying gibberish, but you can do better than that).
I've a simple class like this:
Public Class CalculationParameter{
public Long TariffId{get;set;}
}
In a workflow activity, I've an Assign like this:
(From tariffDetail In db.Context.TariffDetails
Where tariffDetial.TariffId = calculationParameter.TariffId).FirstOrDefault()
Dto is passed to Activity as an Input Argument.
It raise following error and I'm wondering how to assign Id. Any Idea?
LINQ to Entities does not recognize the method 'Int64
GetValue[Int64](System.Activities.LocationReference)' method, and this
method cannot be translated into a store expression.
How can I assign the calculationParameter.TariffId to tariffDetial.TariffId?!
UPDATE:
Screen shot attached shows that how I'm trying to assign calculationParameter.TariffId to tariffDetail.TariffId (car.Id = Dto.Id) and the query result should assign to CurrentTrafficDetail object.
Here's your problem. I don't know if there is a solution to it.
As you said in a (now deleted, unfortunately necessitating that I answer) comment, the exception you're getting is
LINQ to Entities does not recognize the method Int64 GetValue[Int64](System.Activities.LocationReference) method, and this method cannot be translated into a store expression.
in your Linq query, calculationParameter is a Variable defined on the workflow. That Variable is actually an instance that extends the type System.Activities.LocationReference and NOT CalculationParameter.
Normally, when the workflow executes, the LocationReference holds all the information it needs to find the value which is assigned to it. That value isn't retrieved until the last possible moment. At runtime, the process of retrieval (getting the executing context, getting the value, converting it to the expected type) is managed by the workflow.
However, when you introduce Linq into the mix, we have the issue you are experiencing. As you may or may not know, your expression gets compiled into the extension method version of the same.
(From tariffDetail In db.Context.TariffDetails
Where tariffDetial.TariffId = calculationParameter.TariffId)
.FirstOrDefault()
is compiled to
db.Context.TariffDetails
.Where(x => x.TariffId = calculationParameter.TariffId)
.FirstOrDefault();
When this executes, L2E doesn't actually execute this code. It gets interpreted and converted into a SQL query which is executed against the database.
As the interpreter isn't omniscient, there are a well defined set of limitations on what methods you can use in a L2S query.
Unfortunately for you, getting the current value of a LocationReference is not one of them.
TL:DR You cannot do this.
As for workarounds, the only thing I think you can do is create a series of extension methods on your data context type or add methods to your CalculationParameter class that you can call from within the Expression Editor. You can create your Linq to Entities queries within these methods, as all types will already have been dereferenced by the workflow runtime, which means you won't have to worry about the L2E interpreter choking on LocationReferences.
*Edit: A workaround can be found here (thanks to Slauma who mentioned this in a comment on the question)
I am writing an Eclipse plug-in that uses JDT AST's ASTParser to parse a method. I am looking within that method for the creation of a particular type of object.
When I find a ClassInstanceCreation, I call getType() on it to see what type is being instantiated. I want to be sure that the fully-resolved type being dealt with there is the one I think it is, so I tell the resultant Type object to resolveBinding(). I get null back even though there are no compilation errors and even though I called setResolveBindings(true) on my ASTParser. I gave my ASTParser (via setSource()) the ICompilationUnit that contains my method, so the parser has access to the entire workspace context.
final IMethod method = ...;
final ASTParser parser = ASTParser.newParser(AST.JLS3);
parser.setResolveBindings(true);
parser.setSource(method.getCompilationUnit());
parser.setSourceRange(method.getSourceRange().getOffset(), method.getSourceRange().getLength());
parser.setKind(ASTParser.K_CLASS_BODY_DECLARATIONS);
final TypeDeclaration astRoot = (TypeDeclaration) parser.createAST(null);
final ClassInstanceCreation classInstanceCreation = walkAstAndFindMyExpression(astRoot);
final Type instantiatedType = classInstanceCreation.getType();
System.out.println("BINDING: " + instantiatedType.resolveBinding());
Why does resolveBinding() return null? How can I get the binding information?
Tucked away at the bottom of the overview of ASTParser.setKind(), carefully hidden from people troubleshooting resolveBinding() and setResolveBindings(), is the statement
Binding information is only computed when kind is K_COMPILATION_UNIT.
(from the online Javadoc)
I don't understand offhand why this would be the case, but it does seem to point pretty clearly at what needs to be different!