I am using MethodCall.setsField() to try to set an instance field on another instance.
My generated class that is doing the field-setting, GC, is trying to set the value of an instance field in an instance of something it has created (CI). So the field's declaring type is CI; my field-setting code resides in GC (which is in the same package as CI but otherwise unrelated to it).
The ByteBuddy checks seem to indicate that although GC and CI are in the same package, GC must be assignable to CI in order to set this field! That greatly surprised me, but I am not a bytecode expert, and I might very well be overlooking something obvious. Could someone kindly explain why this check is necessary?
The method call sets the field implicitly on the this instance on which the method is invoked. For this to be possible, a non-static field must be declared by a super type of the type on which the method is invoked.
If you think this is too strict, please file an issue with an example of the code you are trying to generate, including the code to generate it which is currently failing. Maybe I am not thinking straight about this and if there's a restriction to be lifted, I would surely do it.
Related
I am generating a class in ByteBuddy.
As part of one method implementation, I would like to set a (let's just say) public instance field in another object to the return value of a MethodCall invocation. (Keeping the example public means that access checks etc. are irrelevant.)
I thought I could use MethodCall#setsField(FieldDescription) to do this.
But from my prior question related to this I learned that MethodCall#setsField(FieldDescription) is intended to work only on fields of the instrumented type, and, looking at it now, I'm not entirely sure why or how I thought it was ever going to work.
So: is there a way for a ByteBuddy-generated method implementation to set an instance field of another object to the return value of a method invocation?
If it matters, the "instrumented method" (in ByteBuddy's terminology) accepts the object whose field I want to set as an argument. Naïvely I'd expect to be able to do something like:
MethodCall.invoke(someMethod).setsField(somePublicField).onArgument(2);
There may be problems here that I am not seeing but I was slightly surprised not to see this DSL option. (It may not exist for perfectly good reasons; I just don't know what they would be.)
This is not possible as of Byte Buddy 1.10.18, the mechanism was originally created to support getters/setters when defining beans, for example. That said, it would not be difficult to add; I think it would even be easiest to allow any custom byte code to be dispatched as a consumer of the method call.
I will look into how this can be done, but as a new feature, this will take some time before I find the empty space to do so. The change is tracked on GitHub.
Intellij, of course, highlights my unused code. If I write something like this in the test:
String something= "testValue";
something will be grayed out.
But , if I have some instantiation like:
DccData dccData = new DccData();
even with
dccData.setAmount(12);
If dccData is never assigned to some other object, my FindBugs will eventually find that dccData is useless since it is not assigned or used as a return (thenReturn), etc. and this will crash my build, because that is how my build is set. I need to keep it that way because of the company's policy.
My question is:
Is there a way for Intellij to notice and gray-out this?
Unfortunately you can't set "Unused declaration" inspection to trigger dccData variable from your sample as unused. It will be difficult to prove that dccData is not used anywhere else, i.e. in setter method.
I'd like to have a project-wide variable which I can change during looking at that project. In other words, I'd like to get it affected whenever opening a file. Yes, I know .dir-locals.el exist in Emacs world. But I think it would be reset to the value set in .dir-locals.el whenever opening a file under that project.
Could I get some hints from you, please?
For this kind of thing you might want to use a function instead of a variable (directly). Specifically, use a getter and setter function.
All of your project code can invoke the getter function to get the value (which can be cached in a variable). And all of your code can invoke the setter function to change the value (which, again, can be cached in a variable).
These functions can be visible globally to your project. The cache variable would be accessed only by the getter and setter functions.
But as for code everywhere in your project being informed when the value gets updated and do what's appropriate with the new value whenever that happens, see #Phil's comment about the use of a variable - the same considerations apply.
You can have a hook in the setter function (or advise it), so that it does something additional (e.g. informs interested/subscribed code) whenever it updates the value.
For a variable you can do something similar using, as #Phils said in a comment, using add-variable-watcher.
For a user-option variable you can do something similar using :set and :get functions in the defcustom. (But those take effect only if changes are made using appropriate Customize functions or the Customize UI.)
You can eval in the dir-locals.el So, if you have a variable my-var that you want to be able to change with setq you could do something like
((nil . ((eval . (or (boundp 'my-var) (setq my-var 'default))))))
There are warnings about using eval in a dir-local though, since any code could be run there.
As you may know, it is pretty easy to have active code of a class containing syntax errors (someone activated the code ignoring syntax warnings or someone changed the signature of a method the class calls, for instance).
This means that also dynamic instantiation of such a class via
CREATE OBJECT my_object TYPE (class_name).
will fail with an apparently uncatchable SYNTAX_ERROR exception. The goal is to write code that does not terminate when this occurs.
Known solutions:
Wrap the CREATE OBJECT statement inside an RFC function module, call the module with destination NONE, then catch the (classic) exception SYSTEM_FAILURE from the RFC call. If the RFC succeeds, actually create the object (you can't pass the created object out of the RFC because RFC function modules can't pass references, and objects cannot be passed other than by reference as far as I know).
This solution is not only inelegant, but impacts performance rather harshly since an entirely new LUW is spawned by the RFC call. Additionally, you're not actually preventing the SYNTAX_ERROR dump, just letting it dump in a thread you don't care about. It will still, annoyingly, show up in ST22.
Before attempting to instantiate the class, call
cl_abap_typedescr=>describe_by_name( class_name )
and catch the class-based exception CX_SY_RTTI_SYNTAX_ERROR it throws when the code it attempts to describe has syntax errors.
This performs much better than the RFC variant, but still seems to add unnecessary overhead - usually, I don't want the type information that describe_by_name returns, I'm solely calling it to get a catchable exception, and when it succeeds, its result is thrown away.
Is there a way to prevent the SYNTAX_ERROR dump without adding such overhead?
Most efficient way we could come up with:
METHODS has_correct_syntax
IMPORTING
class_name TYPE seoclsname
RETURNING
VALUE(result) TYPE abap_bool.
METHOD has_correct_syntax.
DATA(include_name) = cl_oo_classname_service=>get_cs_name( class_name ).
READ REPORT include_name INTO DATA(source_code).
SYNTAX-CHECK FOR source_code MESSAGE DATA(message) LINE DATA(line) WORD DATA(word).
result = xsdbool( sy-subrc = 0 ).
ENDMETHOD.
Still a lot of overhead for loading the program and syntax-checking it. However, at least none additional for compiling descriptors you are not interested in.
We investigated when we produced a dependency manager that wires classes together upon startup and should exclude syntactically wrong candidates.
CS includes don't always exist, so get_cs_name might come back empty. Seems to depend on the NetWeaver version and the editor the developer used.
If you are certain that the syntax errors are caused by the classes’ own code, you might want to consider buffering the results of the syntax checks and only revalidate when the class changed after it was last checked. This does not work if you expect syntax errors to be caused by something outside those classes.
We are in the progress of introducing PostSharp in one of our projects. It's been working great so far! There is one thing though that we haven't managed to solve: how to fire an advice conditionally.
Details:
- we have an attribute StopWatchAttribute which makes it possible to record the time needed to run methods
- this attribute accepts an enumeration "LoggingLevel" which is set in the config file with values like 0, 1, 2 etc
- this parameter is read in a base class called BaseService during runtime: new BaseService().CurrentLoggingSettings
- we tried to set up the attribute constructor like StopWatchAttribute(new BaseService().CurrentLoggingLevel) but we get a compile error: an attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type.
--> summary: we would like the advice to be called conditionally and the condition depends on the parameter in the constructor of the attribute.
Is this possible to do?
Thanks for your help,
Andras
You cannot give variables to attributes, PostSharp or not. Since you're already reading the values from the config, just set your aspect to do the same on the Initialize() method. Override it in the aspect class and then save the value to a local field. You can use that field throughout the aspect. This compiles the value into the aspect essentially hard coding it.
Or, you can pull the value from the config from your advice method (OnMethodStart, etc) so that you can change it in the config at runtime. This is a more 'flexible' way to do it as it doesn't hard code anything.
Remember, your variables are being set at Runtime. PostSharp is a post-compile framework which means it does it's work long before your variables are even known to JIT.