Skip to content

SONARJAVA-6419 Use the Spring context model in S4605 to make it cross-module - #6376

Merged
asya-vorobeva merged 4 commits into
masterfrom
asya/change-s4605-to-cross-module
Oct 21, 2026
Merged

asya-vorobeva merged 4 commits into
masterfrom
asya/change-s4605-to-cross-module

Conversation

@asya-vorobeva

@asya-vorobeva asya-vorobeva commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Also refactored tests to use common helper class for SpringContextModel-based checks.


Summary by Gitar

  • Rewritten check:
    • Moved SpringBeansShouldBeAccessibleCheck onto SpringContextModel, so it detects uncovered beans across modules
    • Registered the check in SpringContextChecks so it runs during analysis
  • Bean kinds:
    • Added BeanDefinitionKind to separate stereotype, configuration, and factory method beans
  • Scan handling:
    • Default-package @SpringBootApplication now collects the empty package, which covers all packages
    • A direct @ComponentScan on a class now shadows the one @SpringBootApplication contributes
  • Test helpers:
    • Shared SpringContextModelTestUtils used by the S4605 and AmbiguousDependency tests
    • Cross-module integration test now expects an issue on OtherComponent in moduleB
  • Serialization:
    • Persisted bean kind in the Spring context model JSON through BeanDefinitionHolderTypeAdapter

This will update automatically on new commits.

…-module

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

SONARJAVA-6419

@datadog-sonarsource

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

Ruling Diff Summary

Detected changes in 1 rule files: 3 issues removed, 0 issues added.

S4605 (java) on mall - 3 issues removed, 0 issues added - deleted ruling file

Removed mall-common/src/main/java/com/macro/mall/common/log/WebLogAspect.java (line 39)

        34 |  * Created by macro on 2018/4/26.
        35 |  */
        36 | @Aspect
        37 | @Component
        38 | @Order(1)
>>>     39 | public class WebLogAspect {
        40 |     private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);
        41 | 
        42 |     @Pointcut("execution(public * com.macro.mall.controller.*.*(..))||execution(public * com.macro.mall.*.controller.*.*(..))")
        43 |     public void webLog() {
        44 |     }

Removed mall-security/src/main/java/com/macro/mall/security/aspect/RedisCacheAspect.java (line 24)

        19 |  * Created by macro on 2020/3/17.
        20 |  */
        21 | @Aspect
        22 | @Component
        23 | @Order(2)
>>>     24 | public class RedisCacheAspect {
        25 |     private static final Logger LOGGER = LoggerFactory.getLogger(RedisCacheAspect.class);
        26 | 
        27 |     @Pointcut("execution(public * com.macro.mall.portal.service.*CacheService.*(..)) || execution(public * com.macro.mall.service.*CacheService.*(..))")
        28 |     public void cacheAspect() {
        29 |     }

Removed mall-security/src/main/java/com/macro/mall/security/util/SpringUtil.java (line 13)

         8 | /**
         9 |  * Spring工具类
        10 |  * Created by macro on 2020/3/3.
        11 |  */
        12 | @Component
>>>     13 | public class SpringUtil implements ApplicationContextAware {
        14 | 
        15 |     private static ApplicationContext applicationContext;
        16 | 
        17 |     // 获取applicationContext
        18 |     public static ApplicationContext getApplicationContext() {

…ation

A @componentscan declared directly on a class shadows the one
@SpringBootApplication contributes as a meta-annotation, so Spring
honours only the former. The gatherer collected both, widening the
scanned package set with scanBasePackages that Spring ignores.

Gate @SpringBootApplication on the whole annotation instead of only on
the own-package fallback. The decision is now taken per class rather
than from the packages collected so far in the file, so it no longer
depends on the order in which classes of a file are visited.
@asya-vorobeva
asya-vorobeva force-pushed the asya/change-s4605-to-cross-module branch from d8af427 to 88e4f37 Compare October 20, 2026 18:15
@gitar-bot

gitar-bot Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Code Review ✅ Approved 2 closed / 2 findings

🟡 Medium risk · Cross-module S4605 scan aggregation and bean-kind filtering could misreport reachable beans or miss genuinely unreachable ones.

Moves SpringBeansShouldBeAccessibleCheck onto SpringContextModel so uncovered beans are detected across modules, with a new BeanDefinitionKind to separate stereotype, configuration, and factory method beans. A direct @ComponentScan without base packages now overrides the one @SpringBootApplication contributes, addressing that resolved finding. The cache entry shape changed without a CACHE_FORMAT_VERSION bump; this was closed without a fix since the mechanism is not yet in production.

✅ 2 closed
✅ Edge Case: Direct @componentscan without base packages doesn't shadow @SpringBootApp

📄 java-frontend/src/main/java/org/sonar/java/model/springcontext/ComponentScanPackageGatherer.java:49-50 📄 java-frontend/src/main/java/org/sonar/java/model/springcontext/ComponentScanPackageGatherer.java:115-118 📄 java-frontend/src/main/java/org/sonar/java/model/springcontext/ComponentScanPackageGatherer.java:135-145
The new Javadoc says a directly declared @ComponentScan shadows the one that @SpringBootApplication contributes. But handledByComponentScan returns true only when a basePackages/basePackageClasses/value attribute is present. That gives wrong results in two cases:

  1. @SpringBootApplication(scanBasePackages = "x") @ComponentScan(excludeFilters = ...): the method returns false, so the shadowed scanBasePackages value x is collected. Spring would scan the declaring class's own package.
  2. @SpringBootApplication @ComponentScan(basePackages = {}): the method returns true and nothing is added. Before this commit, the packagesCollectedAtFileLevel.isEmpty() fallback still added the class's own package. Spring also uses the declaring class's package when no base packages resolve, so this case is a regression.

Suggested fix: treat any direct @ComponentScan as governing the scan. If its base attributes resolve to no packages, fall back to the class's own package.

Closed: Quality: Cache entry shape changed without bumping CACHE_FORMAT_VERSION

📄 java-frontend/src/main/java/org/sonar/java/serialization/BeanDefinitionHolderTypeAdapter.java:111 📄 java-frontend/src/main/java/org/sonar/java/serialization/BeanDefinitionHolderTypeAdapter.java:132
This PR adds a required kind property to every cached bean entry. BeanDefinitionHolderTypeAdapter.read now calls required(kind, KIND), but SpringContextCacheHelper.CACHE_FORMAT_VERSION stays at 1. That class's Javadoc sets the rule that the version must be bumped "whenever any entry's shape changes", so that older entries get invalidated on purpose. Without the bump, a version-1 entry written before this change still passes the version check. It then fails on the missing kind, and FileCachingCheck catches that failure, logs it at trace level, and treats it as a cache miss. Analysis results stay correct, but the format version no longer tells the two layouts apart. Old entries get rejected for looking corrupted instead of being invalidated by version. Fix: bump CACHE_FORMAT_VERSION to 2 and update the "version":1 fixtures in SpringContextCacheHelperTest.

Verification Rules 2/2 met Knowledge ✅ 1 Repos 6 inspected · PRs 2 inspected

Functional validation No results

Rules 2 passed 1 not applicable

✅ Internal artifact registries
✅ Repository license compliance

Knowledge ✅ 1 expectation met

Expectation met Empty ComponentScan package settings expectation met

Broader impact Repos 6 inspected PRs 2 inspected
Cross-repo 6 repositories inspected 0 open impacts

SonarSource/sonarlint-core Inspected
SonarSource/sonarlint-language-server Inspected
SonarSource/sonarlint-vscode Inspected
SonarSource/sonarlint-intellij Inspected
SonarSource/sonar-java-symbolic-execution Inspected

1 more repository

Cross-PR 2 PRs inspected 0 open impacts

PR 6308: SONARJAVA-7137 Optionally rely on source paths to resolve types during analyses Inspected
PR 6366: SONARJAVA-7197 Log when no Spring context model file is found at the start of an analysis Inspected

Auto-approval Not enabled

Set up

For more verification details, comment gitar display:verbose.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing compact verification results.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqube-next

Copy link
Copy Markdown
Contributor

@aurelien-coet-sonarsource aurelien-coet-sonarsource left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just a small suggestion, but LGTM ! 👍


// @ComponentScan without explicit packages shadows @SpringBootApplication's ones.
@SpringBootApplication(scanBasePackages = "x")
@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.example.excluded"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We don't do anything with the filter on the component scan annotation here, right ? Maybe we should remove it to avoid confusion ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, here no, but we have a ticket to fix this (I've created it some time ago, even before working on Spring Context Model). I'd prefer to deliberately leave it here as a mention.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ok, makes sense then. Thanks for the link !

@asya-vorobeva
asya-vorobeva merged commit d9e3721 into master Oct 21, 2026
20 checks passed
@asya-vorobeva
asya-vorobeva deleted the asya/change-s4605-to-cross-module branch October 21, 2026 07:30
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.

2 participants