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
Possible buggy handling of class / subclass modifier values #791
Comments
Interestingly, the "bad" behavior appears to be invoked twice, in close proximity: Classfile.java lines 486 (initial creation of the ClassInfo, which appears to pass the modifiers of the file) and line 489 (which sets them directly on the resulting ClassInfo object). The correct values are then provided later through ClassInfo.addClassContainment, invoked at line 508.
Looks like it is simply using the values directly from the .class file for the subclass, rather than honoring the override. This might potentially even work fine if "setModifiers" did what "setX" is usually expected to do in a Java API (to wit, unconditionally set the value, as opposed to what it currently does, which is OR the provided value with the current value). |
Hi, thanks for the report, and you're right to be looking in A PR would be greatly appreciated, since I'm not totally following what the issue is! |
The big one is that it is using the access_flags value (0x0021) from DummyClass$DummyProtectedSubclass.class when creating the ClassInfo object. The logic around line 508 which handles class containment entries later calls setModifiers with 0x000C, which is actually the correct value — but this is ORed with the initial 0x0021 to get a net result of 0x002D, rather than overwriting the initial (bad) value. So the obvious possible fixes would be to:
Unfortunately, this particular bit of code is complex enough (by necessity, from what I can see) that it isn't entirely clear to me which of the above is actually likely to be the cleanest. Option #1 (presumably in combination with adding an "addModifiers" method) certainly appeals to the purist in me, but that also seems like a pretty massive and probably gratuitous API break. Basically, you can blame Sun for not doing a particular good job of making the classfile format definition particularly consistent when dealing with inner classes… if compiled into their own classfile, they get an access_flags value (because they have to), but that value is irrelevant because an inner class's access controls are read from an attribute and have a completely different set of valid flag values. |
Another point of interest: the initial access_flags value is loaded in readBasicClassInfo and then used to make decisions about whether to pay attention to the class based on visibility. Based on the behavior of private subclass flags, I think the intended idea of protected subclasses being marked with the public flag at the file level is that they have the potential to be visible to something outside the same class (specifically, if the containing class is exposed and not final, something in another package can extend the containing class and thus see it), and so they can't simply be treated as "ignored outside the package where they appear" (which is basically what the public/non-public flag at the base of a classfile indicates). But that also means that the logic about whether they are considered visible without "ignoreClassVisibility" does not actually act quite according to what the JavaDoc says — although one can argue that the net result might actually be more useful to someone trying to search for "public" classes in terms of "what do I expose as an API?", which is actually the task that I'm working on at the end of the day. My current thinking is that the best answer right now, to avoid breaking a long-established (if poorly named) API method, is to:
That seems like it should fix the biggest problem with subclass modifiers without much risk of blowing up lots of other stuff or introducing "nasty hacks" that would cause problems for future maintenance. Expanding the removeModifiers call to other member types would be simple to do if there ever turns out to be any call for it, and would be consistent with any likely future change to the API (unless that change is so profound that it makes the whole question moot). |
I'm not sure what you're getting at here... |
Whoops, didn't look closely enough to notice that. I'll do a bit of digging and check how widespread the impacts of such a change might be, but won't have time until (at least) tomorrow. Thank you for being responsive, however. |
The general picture is that It is very possible that something is wrong with how I'm parsing or setting the modifier bits... I had to make my best guess with a lot of things when I wrote the classfile parser. Especially for JVM languages other than Java. The reason for OR-ing the modifier bits together is that in Kotlin, and I think Scala too, you can have two classes generated from one class, but they technically represent the same class. So in this case, the modifiers are OR'd together, and all the other |
Ah. That explains the OR-ing, then, and definitely suggests that the right answer is to treat inner classes as a special case. Specifically, the permissions for an inner class are not defined by the access_flags in the classfile, but by the inner_class_access_flags within the inner class array in the InnerClasses attribute and are not additive. From what I can tell, this is calculated handled by the ClassContainment instance and propagated into the ClassInfo object at at ClassInfo.java:537), and the values being set at that point are correct — they're just "contaminated" by being OR-ed with the access_flags values. One big hint that tipped me off here is that table 4.7.6-A (the possible flags for an inner class) does not include any flag with a value of 0x0020, and the spec requires that all bits not otherwise specified are reserved and should be zeroed / ignored. From what I can tell, the cleanest way to map all of this seems to be roughly:
The logic above is also backed up by another subtle hint in the specification: while table 4.7.6-A (inner class permissions) defines ACC_PUBLIC as "Marked or implicitly public in the source", table 4.1-B (class permissions) defines ACC_PUBLIC as "Declared public; may be accessed from outside its package." For top level classes, those two things are synonymous, but for inner classes they are not… this appears to be a "bug" in the spec where it was just never updated to account for the possibility. |
Are you able to submit a PR that implements this, please? The time I can invest into ClassGraph is very limited at this point. |
The current version (4.8.162) appears to have two (probably-related) issues:
Code to replicate:
`
package mypkg;
import io.github.classgraph.*;
public class DummyClass {
public static class DummyPublicSubclass { }
protected static class DummyProtectedSubclass { }
private static class DummyPrivateSubclass { }
}
`
Verbose log attached: classgraph-innermods-mre-log.txt
Expected results:
I'll see what I can do to track down the specific point causing this to happen, and offer a pull request if I can manage to pin it down and the changes to fix it aren't excessively invasive, but I'm not sure what the intent is with regard to ACC_SUPER handling.
The text was updated successfully, but these errors were encountered: