-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
static method hiding produces false positives when original method is forbidden #237
Comments
Practically speaking: This came up in the context of apache/solr#947 (comment) |
Hi @hossman,
So basically, I will add an if statement to the method resolver to not check parent classes if the method is static. |
Hi, correction: Static methods are inherited by subclasses, so the behaviour is correct. The problem is the viewpoint of forbiddenapis. In contrast to some code calling a method, forbiddenapis has to make sure that also hits are found when you call a virtual method in a subclass and this one calls For static methods, we may stop going up to superclasses, but I have to check this one more time: I am not sure if you can use super() to call superclass's static method. It makes no sense but it is unclear if the java language allows this. If you cannot call a superclass static methods with super, I agree to remove the superclass check. |
I have to read https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-6.html#jvms-6.5.invokestatic and compare with https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-6.html#jvms-6.5.invokevirtual May take some time. |
Static methods are not virtual, Uwe. They can be shadowed but the invokestatic call always uses an explicit class reference (because of that you can call SuperClass.staticMethod from Subclass.staticMethod... in fact, you can call SuperClass.staticMethod and Subclass.staticMethod from anywhere you like). |
Here is the way how methods are looked up: https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-5.html#jvms-5.4.3 It makes no difference between static and non-static. The difference is only implemented in the compiler. Basically you can refer to a subclass' static method, if it does not exist it find the superclass' method. As forbiddenapis does not know if another subclass hides this method, it has to go to a superclass (because it needs to ensure there are no other possibilities how to call this method). Forbiddenapis problem also exists for virtual methods: As it doe snot know it the virtual method calls @dweiss : I explicitely said they are not virtual, I just compared the spec. When you have a virtual call you have the argument with the class on stack, but it then does exactly the same: it looks up the method as described above. In short: actually in JVM runtime theres no difference with method lookups. The difference is only with the argument on stack which is the class for the virtual call. But lookup of method is identical, it just does not use the class on stack but the one in bytecode. So basically that's all forbiddenapis can do. It has its limitations. We can add a specialcase for static methods to only look at the exact descriptor and not dive into superclasses, but this would have the problem of undetected calls. |
Ok. I think I can remove the static call. The problem with virtual calls is different. On bytecode you do not know which method is actually called, so you have to check all possible superclasses (from perspective of forbiddenapis, as first argument is unknown). In case of static methods you can stop in forbiddenapis when method was found. The type is given on call site and you just have to look until you found one in the ancestors-or-self. I will provide a PR. |
I'm not sure I agree with you here (with regard to virtual vs. static method calls)... but anyway. I think considering odd cases like swapping out classes without proper recompilation (which would emit different bytecode) is not worth it. 99.9% of the cases are legitimate valid code and javac would see the difference and link it up properly. |
See my response. |
Sorry for the confusion. The problem of forbiddenapis is that it needs to look somehow without runtime types. For virtual methods it is hard, because you have no information which class actually receives the call. For static methods you can just follow the spec and stop on first found method. |
Btw., the same apples for fields. |
I did see it. But I still think maybe forbiddenapis shouldn't try to play JVM here - if a static method doesn't exist at its target class, something is wrong - classes are stale and this should perhaps produce a warning (javac should never produce such bytecode because it always resolves static call to a concrete class). |
Yes, if you scan your own compiled classes the output of javac should be "uptodate". Elasticserach uses forbiddenapis also to scan JAR files on unknown source which might be very outdated. Anyways, I think the simplest / only thing to change is: If method call is static ( |
BTW: I fixed the code to assume static also for |
Here is a PR: #238 I still need to add a test. |
I rewrote the PR to now also look into superclasses until a match was found, It was doing this for fields already, but it was not correct according to static or virtual. Now both methods and fields are handled in the same way. #238 I still need a test. I will also run a check on Lucene, Solr, Opensearch as integration test. ... and sleep a night over it. |
Field handling was correct. For methods it now differentiates only between virtual/interface invocations (collect all superclasses) and static/special (only until found). Although for "special" it should not go to superclasses at all (used for constructors and super calls). |
FWIW: I was hoping to provide a (commitable) test case when i created this issue, but even as someone who has a lot of experience with java, and using forbidden-apis: I couldn't' make heads or tails of how to write a new test case for this type of situation based on the existing tests i found. It would be great if there was a "HelloWorldIntegreationTest.java" that showed some basic rule checking of code compiled as part of the test class that could serve as an example to folks who want to submit future issues/PRs demonstrating problems they encountered. |
The test needs to be done like the many antunit tests. The actual tests need to be example class files and then you can assert on the results with antunit. I will take your example code above. |
FYI, to setup a test like this: Add a class with |
I added a few more tests to check static and virtual methods:
I think it's ready. |
ahhhh, ok. yeah. test looks good. The part i wasn't understanding originally is that you keep all the "integration tests" in the Out of curiosity: why do you commit all the "test input" class files? I understand some of them are because they have to be compiled using old JDKs, or using oracle specific JDKs, but couldn't most test just be re-compiled on every run and pulled from the test-classpath? |
This was easiest at beginning. There is still an issue open to have the Checker API return a list of structured "violation" instances. By this is would be easier to write "standard tests". Antunit was quite nice to configure input files/signature files/pass signatures. The only thing that is harder is to check is the returned violations. But with some logic you can grep on the log output and catch the cases you like.
That's also historical: At beginning forbiddenapis was java5+, so files for Java 7 and Java 8 and Java 9 needed to be compiled separately. In addition some tests were checking for undocumented features (the ones we would like to prevent, like But yes, I am planning to switch to Java 8 as minimum requirement, so the classes can be compiled out of box (for all checks). That's something for a lonely weekend... |
There are more generation tasks in forbiddenapis: The deprecated signatures files can be generated, too, but you need to run with exactly that JDK... |
hi @hossman, |
no no, this is a nice to have. |
Forbiddenapis v3.7 was released a minute ago. |
Consider the following code (contrived example, but simplest I could find in a hurry that anyone can try using only JDK methods w/o needing third-party libs) ...
Let's see what happens when we tell forbidden-apis we don't want our code to use
Bitset.valueOf(...)
...It correctly identified that line 6 is "bad" for it's "Forbidden method invocation", but the errors reported for Lines 6 & 21 are false positives -- those lines do not invoke
Bitset.valueOf(...)
, they invokeY.valueOf(...)
, which hides the static method with the same name provided by Y's ancestor class...The text was updated successfully, but these errors were encountered: