Skip to content
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

Created a new detector for rule MET04-J #1994

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

sajtizsolt
Copy link

@sajtizsolt sajtizsolt commented Mar 22, 2022

I've created a new detector for SEI CERT rule MET04-J.

This detector should prevent the happening of increasing accessibility of methods from the super class. This can occur in three ways:

  • increasing the accessibility of a package private method to protected
  • increasing the accessibility of a package private method to public
  • increasing the accessibility of a protected method to public

A noteable exception is the Cloneable interface: when a class implements the clone method, it should increase it's accessiblity from protected to public.

My changes:

  • Updated CHANGELOG.md
  • Created a new detector called FindIncreasedAccessibilityOfMethods in spotbugs module
  • Created a test for the new detector called FindIncreasedAccessibilityOfMethodsCheckTest in spotbugs-tests module
  • Created test cases under spotbugs/spotbugsTestCases/src/java/increasedAccessibilityOfMethods
  • Added the required descriptions to findbugs.xml and messages.xml

My tests:

  • I've ran my detector on the codebase of Jenkins to make sure it works properly.
  • I've ran the ./gradlew spotlessApply build smoketest command locally

ThrawnCA
ThrawnCA previously approved these changes Mar 31, 2022
@sajtizsolt
Copy link
Author

Thanks for the review @ThrawnCA ! Could you please give me a workflow approval as well? 🙏

Copy link
Member

@KengoTODA KengoTODA left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added several comments, please check them.

Also, could you consider adding one more test, to verify that we can detect cases that publish methods provided not by direct superclass but by other ancestors, such as a parent of the superclass, or the default method defined in an interface?

@sajtizsolt
Copy link
Author

Hey @KengoTODA ! Thanks for you review, I've made the requested changes and also added some test cases (I've made small adjustments on the detector as well).

@ThrawnCA
Copy link
Contributor

ThrawnCA commented Apr 7, 2022

Are malicious subclasses really a serious concern? There are reasons that the Security Manager is going away...

KengoTODA
KengoTODA previously approved these changes Apr 8, 2022
Copy link
Member

@KengoTODA KengoTODA left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for your contribution!

@sajtizsolt
Copy link
Author

I had to resolve conflicts to be able to merge. Please re-approve my PR and the workflow, thanks!

@sajtizsolt
Copy link
Author

@ThrawnCA @KengoTODA Please check and approve my PR and workflow once again (and hopefully for the last time 😄). Thank you in advance!

@sajtizsolt
Copy link
Author

@ThrawnCA @KengoTODA Hi! I don't want to urge you, I'm just interested - is there any chance that my PR will be included in a future release?

KengoTODA

This comment was marked as resolved.

Copy link
Contributor

@ThrawnCA ThrawnCA left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be reverting a bunch of typo fixes.

spotbugs/etc/messages.xml Outdated Show resolved Hide resolved
spotbugs/etc/messages.xml Outdated Show resolved Hide resolved
spotbugs/etc/messages.xml Outdated Show resolved Hide resolved
spotbugs/etc/messages.xml Outdated Show resolved Hide resolved
spotbugs/etc/messages.xml Outdated Show resolved Hide resolved
@sajtizsolt
Copy link
Author

@ThrawnCA
Hey! I resolved the conflicts with the master branch and reviewed my changes. If I'm right, I resolved all previous merge issues and unnecessary changes, so I would be grateful if you would check my PR once more and approve it if you think it's right. Should I resolve the previous conversations or it is the task of the starter? Also, could you please give me a workflow approval as well?

@sajtizsolt
Copy link
Author

@KengoTODA @ThrawnCA I've rebased to the latest master, squashed my previous commits into one, amended my commit message to one which follows the conventional commit rules. Please check this PR one last time, give me a workflow approval and merge if you think that my changes are right too. Thanks in advance and thank you for your work!

CHANGELOG.md Outdated Show resolved Hide resolved
CHANGELOG.md Outdated Show resolved Hide resolved
spotbugs/etc/messages.xml Outdated Show resolved Hide resolved
@sajtizsolt sajtizsolt force-pushed the MET04-J branch 4 times, most recently from 7337de4 to c9827e5 Compare July 8, 2023 08:22
@sajtizsolt sajtizsolt force-pushed the MET04-J branch 2 times, most recently from 841b50e to 109f07b Compare July 20, 2023 18:29
@JuditKnoll
Copy link
Collaborator

I tested your detector on some bigger projects:

I think I found an FP type in MatSim-Libs (e.g. this one): IMO in case of constructors this bug should not be reported.

@sajtizsolt
Copy link
Author

Good point, thanks for trying it! As the SEI CERT rule definition doesn't mention anything specific about constructors, I think you are right, I removed constructors from the analysis - also added some tests to validate if there are no false positive cases of them. If it is okay now, could you please give me a workflow approval?

Copy link
Collaborator

@JuditKnoll JuditKnoll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rerun the detector on bigger projects, the links are the same as before, overwrote the earlier results.

}

@Override
public void visitClassContext(ClassContext classContext) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the current implementation there is a hit type, which may cause problems for the users, so we may need filter it out: we have three classes: SuperClass, Class, and SubClass, SuperClass is parent of Class, which is parent of SubClass. We have a method, which is declared in SuperClass as protected, overriden in both Class and SubClass as public. There will be a hit in Class, which is great, but there will be also a hit in SubClass, about which I'm not so sure. If the user have no write access to SuperClass and Class, then can't really do anything about the problem.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, if this can occur in real life. Do SpotBugs analyze classes to which the user has no write access? For example in dependencies? I see your point, can you maybe show me an example of it where I can reproduce the hit?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user provided the necessary compiled files for the analysis, even if doesn't have write access to them, cause e.g. they are inside a jar file. Checked every hit of the tests on the bigger projects, but didn't find any test like the one, I mentioned. (Otherwise I would have referred it.) You can create a test by simply creating a class, which descends from one of the hits and overrides the appropriate function, or reusing your already existing test classes, using SuperClassOfSuperClass as SuperClass in my example, SuperClass as Class, and SubClassFromSamePackage as SubClass. If you simply add an override of superPackagePrivateMethodToPublic() to SuperClass, then you have the test:

@Override
public String superPackagePrivateMethodToPublic() {
    return "SuperClass.superPackagePrivateMethodToPublic";
}

IMO in this case, the error should be only reported in SuperClass, and not in SubClassFromSamePackage.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sajtizsolt Can you please create a testcase like this and modify the code to not to bury our users in hits they can't do anything about?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will, I'm just a littly busy nowadays. I will tag you if it happens!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuditKnoll Now I thought about this problem again. To achieve the desired behaviour, we should be able to filter out potential bugs based on the bugs already found while we analyzed the super classes of the current class. I don't know, if there is some logic implemented in SpotBugs which enables us to query this list of bugs based on a class, but if there is not, then we should analyze every single super class again. I don't know, I feel that this would increase the complexity of the detector by a huge scale. Do you feel, that the added value of this solution complies with the amount of extra computational demand of it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, I've added the requested changes in another commit - take a look at them. Not terribly slow if there are few super classes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think having a more precise detector is worth the increased complexity. Usually there is less than 10 super classes, so it's not as much of an overhead.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, IMO there is a solution which only needs to check every super class only once, if we take advantage of the order of visiting, and maintain a collection of the correct methods of the examined class. If we phrase the problem like this, that may help: an IAOM bug should be reported, when there is an increase of accessibility of a method from the closest parent declaring/overriding the given method.
When the bug is reported, there is an accessibility increase, then we can remove the current buggy method from the methods of the examined class (subClass) collection. This way, we don't need to check at every parent whether the same bug got reported or not, also, the number of methods to check against decreases.
Removing a method won't cause a problem, since a method can only have one IAOM bug in one class, and the closer parent will be visited first, since JavaClass.getSuperClasses() contains the list of super classes in ascending order.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answering your question about filtering out bugs based on other bugs, there is a solution using BugAccumulator, but it's a bit different to what you need. As far as I know, that one only works inside one class, not across classes.

@sajtizsolt sajtizsolt force-pushed the MET04-J branch 2 times, most recently from f7e75ce to b50003f Compare July 30, 2023 09:05
@sajtizsolt
Copy link
Author

@JuditKnoll Can I run CodeChecker with the demo account? I really can't find any way to do it.

@JuditKnoll
Copy link
Collaborator

@JuditKnoll Can I run CodeChecker with the demo account? I really can't find any way to do it.

The demo account doesn't have rights to store the results. If you would like to run it yourself, you need to set up a separate codechecker server and configure the runs, which is not so straightforward.
I rerun the tests, the links are the same as earlier.

@sajtizsolt
Copy link
Author

@JuditKnoll Can I run CodeChecker with the demo account? I really can't find any way to do it.

The demo account doesn't have rights to store the results. If you would like to run it yourself, you need to set up a separate codechecker server and configure the runs, which is not so straightforward. I rerun the tests, the links are the same as earlier.

Thank you! I examined the results and they seem to be valid positive cases to me. What do you think? Can this detector be considered as complete or are there any more edge cases which comes into mind?

@JuditKnoll
Copy link
Collaborator

@JuditKnoll Can I run CodeChecker with the demo account? I really can't find any way to do it.

The demo account doesn't have rights to store the results. If you would like to run it yourself, you need to set up a separate codechecker server and configure the runs, which is not so straightforward. I rerun the tests, the links are the same as earlier.

Thank you! I examined the results and they seem to be valid positive cases to me. What do you think? Can this detector be considered as complete or are there any more edge cases which comes into mind?

Only the one I mentioned at the detector's visitClassContext() function.

}

@Override
public void visitClassContext(ClassContext classContext) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think having a more precise detector is worth the increased complexity. Usually there is less than 10 super classes, so it's not as much of an overhead.

}

@Override
public void visitClassContext(ClassContext classContext) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, IMO there is a solution which only needs to check every super class only once, if we take advantage of the order of visiting, and maintain a collection of the correct methods of the examined class. If we phrase the problem like this, that may help: an IAOM bug should be reported, when there is an increase of accessibility of a method from the closest parent declaring/overriding the given method.
When the bug is reported, there is an accessibility increase, then we can remove the current buggy method from the methods of the examined class (subClass) collection. This way, we don't need to check at every parent whether the same bug got reported or not, also, the number of methods to check against decreases.
Removing a method won't cause a problem, since a method can only have one IAOM bug in one class, and the closer parent will be visited first, since JavaClass.getSuperClasses() contains the list of super classes in ascending order.

}

boolean isSameMethodAndAccessibility(IAOMBug other) {
return Objects.equals(method.getSignature(), other.method.getSignature())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure, that checking the signatures is enough to check two methods are the same?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Method class itself has an equals, which checks both the name of the method and the signature. Is there any reason, why you don't use that equals?


List<IAOMBug> superClassBugs = new ArrayList<>();
for (JavaClass superClass : superClasses) {
superClassBugs.addAll(findIAOMBugs(superClass));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does work, but it could be a bit more efficient, see my comment at the visitClassContext() method.

}

@Override
public void visitClassContext(ClassContext classContext) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answering your question about filtering out bugs based on other bugs, there is a solution using BugAccumulator, but it's a bit different to what you need. As far as I know, that one only works inside one class, not across classes.

@JuditKnoll
Copy link
Collaborator

Seems like the build is failing, can you please run ./gradlew :spotbugs:spotlessApply command to solve the spotless problems?

Copy link
Collaborator

@JuditKnoll JuditKnoll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please rebase to the current master and resolve the merge conflicts?
Since your last rebase the project got migrated from Junit to Junit Jupiter (see #2605), which will cause build fail after the rebase, but I commented at the relevant lines, which needs to be modified to work properly.

@sajtizsolt sajtizsolt force-pushed the MET04-J branch 2 times, most recently from 7b506b7 to eacaa5c Compare April 13, 2024 09:32
private List<Method> getMethodsExceptConstructors(JavaClass javaClass) {
ArrayList<Method> methods = new ArrayList<>();
for (Method method : javaClass.getMethods()) {
if (!Const.CONSTRUCTOR_NAME.equals(method.getName())) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can also filter out the static initializer.

}

boolean isSameMethodAndAccessibility(IAOMBug other) {
return Objects.equals(method.getSignature(), other.method.getSignature())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Method class itself has an equals, which checks both the name of the method and the signature. Is there any reason, why you don't use that equals?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants