Skip to content

build(native): initial GraalVM native-image support#3938

Open
nixel2007 wants to merge 7 commits into
developfrom
feature/update-deps
Open

build(native): initial GraalVM native-image support#3938
nixel2007 wants to merge 7 commits into
developfrom
feature/update-deps

Conversation

@nixel2007

@nixel2007 nixel2007 commented May 22, 2026

Copy link
Copy Markdown
Member

Summary

Подключаю org.graalvm.buildtools.native и довожу ./gradlew nativeCompile до успешной сборки бинаря размером ~125 MB. По пути правится несколько проблем, мешавших Spring AOT и native-image:

  • AOT-процессор + AspectJ. AspectJBeanFactoryInitializationAotProcessor парсит pointcut-выражения @Aspect-бинов через ReflectiveAspectJAdvisorFactory, которому нужен org.aspectj.weaver.tools.PointcutParser. Раньше при запуске processAot получали ClassNotFoundException: org.aspectj.weaver.reflect.ReflectionWorld$ReflectionWorldException. Лечится подкидыванием aspectjweaver только в classpath ProcessAot/ProcessTestAot — рантайм/bootJar остаются «чистыми», CTW (io.freefair.aspectj.post-compile-weaving) продолжает работать как раньше.
  • gRPC из LanguageTool → image heap. org.languagetool:languagetool-core (и языковые модули) транзитивно тянут io.grpc.netty-shaded. Его статические инициализаторы захватывают ch.qos.logback.classic.Logger в image heap, native-image валится. Используется он только для удалённых правил (n-gram сервис), у нас не задействован — выкидывается exclude-ами в трёх dependency-блоках LanguageTool.
  • GraalVM build tools. Регистрируется блок graalvmNative { binaries { named("main") { ... } } }: явно ставлю sharedLibrary.set(false), mainClass = BSLLSPLauncher, --no-fallback, отчёт стектрейсов и UnlockExperimentalVMOptions.
  • Reachability-metadata для workspace-scoped прокси. Spring AOT для каждого @Component @Scope(value = WorkspaceScope.SCOPE_NAME, proxyMode = ScopedProxyMode.TARGET_CLASS) бина генерирует <FQN>$$SpringCGLIB$$1, но регистрирует ему только allDeclaredConstructors. Native-image затем падает на доступе к статическому полю CGLIB$FACTORY_DATA. Кладём дополнительный META-INF/native-image/.../reachability-metadata.json с полной рефлексией на эти 22 класса (генерируется по @Scope(... TARGET_CLASS) в src/main/java).
  • AutoServerInfo NPE. Thread.currentThread().getContextClassLoader().getResourceAsStream("META-INF/MANIFEST.MF") в native-image возвращает null, Manifest.read(null) падает с NPE. Добавлен null-check с fallback на Package.getImplementationVersion().

После этого ./gradlew nativeCompile собирает executable (build/native/nativeCompile/bsl-language-server, ~125 MB, время сборки ~3.5 мин на 12-ядерной машине). Сборка проходит и под GraalVM CE 25.0.2.

Что НЕ работает

⚠️ Полноценный запуск бинаря пока ломается на инициализации Spring-контекста. Корень — конфликт между pre-generated CGLIB-стабами Spring AOT и runtime-AOP проксированием. Конкретно:

  1. С aspectjweaver в runtime — Spring AOP активно создаёт CGLIB-прокси через Enhancer.create() для бинов с @Cacheable/@Lazy self. На pre-generated $$SpringCGLIB$$0 пытается выставить callbacks несовместимого типа → ClassCastException: CglibAopProxy$SerializableNoOp cannot be cast to Dispatcher при DebugTestCodeLensSupplier.setCallbacks.
  2. Без aspectjweaver в runtime — Spring AOT-зарегистрированные sentryCheckInAdvisor/cacheAdvisor падают с NoClassDefFoundError: AspectJExpressionPointcut.

Чтобы бинарь нормально стартовал, нужно:

  • либо отказаться от @Lazy self-injection в DebugTestCodeLensSupplier/RunTestCodeLensSupplier/RunAllTestsCodeLensSupplier (заменить, например, на ApplicationContextAware + ленивый lookup),
  • либо отключить Spring AOP-инфраструктуру (с потерей @Cacheable через прокси — кэширование нужно будет перевести на CTW-аспект или Caffeine напрямую),
  • параллельно расширить RuntimeHints для Sentry-AOP бинов и Spring-cache advisor'ов.

Это пока выносится за рамки PR — задача отдельной итерации.

Performance comparison (native vs java -jar)

Запланированное сравнение времени analyze на ssl_3_2 с SARIF-репортером не получилось провести: native-бинарь падает на старте контекста (см. выше), поэтому до самой логики анализа дело не доходит. Сравнение будет иметь смысл после фикса runtime-блокеров.

Test plan

  • ./gradlew bootJar собирает *-exec.jar как раньше; java -jar ... --versionversion: feature-update-deps-...
  • ./gradlew compileTestJava — без ошибок
  • ./gradlew :test --tests "*AutoServerInfo*" — проходит (testDataIsFilled() PASSED)
  • ./gradlew processAot — больше не падает на ReflectionWorld$ReflectionWorldException
  • ./gradlew nativeCompile под GRAALVM_HOME=GraalVM CE 25.0.2BUILD SUCCESSFUL, executable создан
  • Запуск собранного bsl-language-server — пока fail на инициализации Spring-контекста (см. «Что НЕ работает»)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added GraalVM native-image support, runtime reachability hints, and an optional thread-dump watchdog (env-controlled).
  • New Component

    • Added a cached provider for locating test source directories.
    • Introduced an enhanced XStream-based reader with native-image compatibility.
  • Bug Fixes

    • Improved version detection when manifest data is unavailable.
  • Refactor

    • Rewired test-run code lens plumbing to use the new provider and removed legacy self-injection.
    • Converted diagnostics and document/server initialization for AOT/native compatibility.

Review Change Stack

Task :nativeCompile now builds a binary. Key changes:
- Apply org.graalvm.buildtools.native plugin with executable build type
  and explicit mainClass for BSLLSPLauncher.
- Route aspectjweaver через отдельную aotProcessorClasspath-конфигурацию,
  чтобы он попадал в Spring AOT-процессор (ему нужен PointcutParser для
  @Aspect-бинов), но не оседал в рантайм/bootJar — CTW по-прежнему
  обеспечивает io.freefair.aspectj.post-compile-weaving.
- Exclude транзитивные io.grpc.netty-shaded из LanguageTool: они тянут
  Logger в image heap через netty static init и валят сборку.
- Ship reachability-metadata.json для $$SpringCGLIB$$1-классов наших
  workspace-scoped прокси — Spring AOT регистрирует им только
  конструкторы, native-image требует доступ к статическому полю
  CGLIB$FACTORY_DATA.
- AutoServerInfo.readVersion: при отсутствии META-INF/MANIFEST.MF
  (типичный кейс native-image) больше не падает NPE, а возвращает
  пакетную версию или пустую строку.

Известное ограничение: бинарь успешно собирается, но контекст Spring
падает на инициализации beans из-за конфликта pre-generated CGLIB-стабов
с runtime-AOP (Spring caching + @lazy self-injection). Полностью
работающий native-runtime потребует отдельной работы — рефактора
self-injection-паттернов и расширенных RuntimeHints.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 22, 2026 22:58
@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds GraalVM native-image build configuration and reachability metadata, provides a manifest-version fallback for native runtimes, refactors CodeLens test-source lookup into a cached TestSourcesProvider used by suppliers/tests, replaces @Lookup diagnostics with an ObjectProvider-based DiagnosticComputer, and adds ExtendXStream for deterministic MD/XStream behavior.

Changes

GraalVM native-image support

Layer / File(s) Summary
GraalVM plugin and native-image build configuration
build.gradle.kts
Applies org.graalvm.buildtools.native, excludes io.grpc:grpc-netty-shaded from LanguageTool deps, adds Graal native-image SDK and AspectJ runtime, and configures graalvmNative for the main binary with native-image args.
Runtime manifest fallback and reachability metadata
src/main/java/com/github/_1c_syntax/bsl/languageserver/AutoServerInfo.java, src/main/resources/META-INF/native-image/.../reachability-metadata.json
AutoServerInfo.readVersion() falls back to package ImplementationVersion when MANIFEST.MF is unavailable; reachability-metadata.json registers reflection access and bundle providers for native-image.
ExtendXStream: deterministic MD/XStream registration
src/main/java/.../reader/common/xstream/ExtendXStream.java
Adds ExtendXStream to alias MD classes and register converters deterministically for native-image and via ClassGraph on JVM; safe fromXML handling and mapper setup included.

TestSourcesProvider and CodeLens wiring

Layer / File(s) Summary
TestSourcesProvider implementation and cache API
src/main/java/.../codelenses/TestSourcesProvider.java
Adds a Spring component that computes absolute test-source URIs, caches them with @Cacheable, and exposes evict() annotated with @CacheEvict(allEntries = true).
AbstractRunTestsCodeLensSupplier refactor
src/main/java/.../codelenses/AbstractRunTestsCodeLensSupplier.java
Injects TestSourcesProvider, removes self-injection and internal caching, uses provider for getTestSources(...), and calls testSourcesProvider.evict() on relevant events.
Concrete supplier constructors and tests wiring
src/main/java/.../codelenses/DebugTestCodeLensSupplier.java, RunAllTestsCodeLensSupplier.java, RunTestCodeLensSupplier.java, src/test/java/.../AbstractRunTestsCodeLensSupplierTest.java
Constructors updated to accept TestSourcesProvider, related imports/fields removed, and test bean factories updated to provide the new dependency.

Document/Server context explicit wiring

Layer / File(s) Summary
DocumentContext initializeDependencies
src/main/java/.../context/DocumentContext.java
Remove Lombok/@Autowired setter injection; add initializeDependencies(...) to assign DiagnosticComputer, complexity computer providers, and OScript resolver.
ServerContext wiring for prototypes
src/main/java/.../context/ServerContext.java
Add constructor-injected services and call documentContext.initializeDependencies(...) after creating prototype DocumentContext instances.

Diagnostics provider change

Layer / File(s) Summary
DiagnosticComputer: ObjectProvider-based diagnostics
src/main/java/.../context/computer/DiagnosticComputer.java
Convert DiagnosticComputer from abstract to concrete and obtain per-document diagnostic analyzers via an injected ObjectProvider<List<BSLDiagnostic>> instead of @Lookup overrides.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Poem

🐰 I hopped through manifests and Gradle trees,
Excluded gRPC with nimble ease.
Cached test paths guide the CodeLens light,
Diagnostics fetched for day and night.
Native-image ready — carrots in sight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'build(native): initial GraalVM native-image support' accurately and concisely describes the main objective of the pull request—adding GraalVM native-image build support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/update-deps

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI 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.

Pull request overview

Adds initial Gradle/GraalVM native-image build support for BSL Language Server by making Spring AOT / native-image compilation succeed without changing the runtime/bootJar classpath.

Changes:

  • Adds the org.graalvm.buildtools.native Gradle plugin and configures the nativeCompile binary (main class + native-image args).
  • Adjusts dependencies to unblock AOT/native-image (AOT-only aspectjweaver classpath; excludes LanguageTool’s transitive gRPC Netty artifacts).
  • Adds native-image reachability metadata for Spring AOT generated workspace-scoped CGLIB proxies and hardens version detection for native-image (manifest stream can be null).

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 2 comments.

File Description
build.gradle.kts Adds GraalVM native build plugin/config, AOT-only aspectjweaver classpath, and excludes problematic transitive gRPC deps.
src/main/java/com/github/_1c_syntax/bsl/languageserver/AutoServerInfo.java Prevents NPE in native-image when MANIFEST.MF is absent; returns a safe version string.
src/main/resources/META-INF/native-image/io.github.1c-syntax/bsl-language-server-hints/reachability-metadata.json Adds reflection metadata for Spring AOT-generated $$SpringCGLIB$$1 proxies to prevent native-image failures.

Comment on lines 58 to 60
final InputStream mfStream = Thread.currentThread()
.getContextClassLoader()
.getResourceAsStream("META-INF/MANIFEST.MF");
Comment on lines +4 to +58
"type": "com.github._1c_syntax.bsl.languageserver.configuration.LanguageServerConfiguration$$SpringCGLIB$$1",
"allDeclaredFields": true,
"allDeclaredConstructors": true,
"allDeclaredMethods": true
},
{
"type": "com.github._1c_syntax.bsl.languageserver.diagnostics.infrastructure.DiagnosticInfos$$SpringCGLIB$$1",
"allDeclaredFields": true,
"allDeclaredConstructors": true,
"allDeclaredMethods": true
},
{
"type": "com.github._1c_syntax.bsl.languageserver.references.model.AnnotationRepository$$SpringCGLIB$$1",
"allDeclaredFields": true,
"allDeclaredConstructors": true,
"allDeclaredMethods": true
},
{
"type": "com.github._1c_syntax.bsl.languageserver.references.model.LocationRepository$$SpringCGLIB$$1",
"allDeclaredFields": true,
"allDeclaredConstructors": true,
"allDeclaredMethods": true
},
{
"type": "com.github._1c_syntax.bsl.languageserver.references.model.SymbolOccurrenceRepository$$SpringCGLIB$$1",
"allDeclaredFields": true,
"allDeclaredConstructors": true,
"allDeclaredMethods": true
},
{
"type": "com.github._1c_syntax.bsl.languageserver.types.TypeService$$SpringCGLIB$$1",
"allDeclaredFields": true,
"allDeclaredConstructors": true,
"allDeclaredMethods": true
},
{
"type": "com.github._1c_syntax.bsl.languageserver.types.index.SymbolTypeIndex$$SpringCGLIB$$1",
"allDeclaredFields": true,
"allDeclaredConstructors": true,
"allDeclaredMethods": true
},
{
"type": "com.github._1c_syntax.bsl.languageserver.types.inferencer.ExpressionTypeInferencer$$SpringCGLIB$$1",
"allDeclaredFields": true,
"allDeclaredConstructors": true,
"allDeclaredMethods": true
},
{
"type": "com.github._1c_syntax.bsl.languageserver.types.oscript.OScriptLibraryIndex$$SpringCGLIB$$1",
"allDeclaredFields": true,
"allDeclaredConstructors": true,
"allDeclaredMethods": true
},
{
"type": "com.github._1c_syntax.bsl.languageserver.types.oscript.OScriptModuleMembersProvider$$SpringCGLIB$$1",
@github-actions

github-actions Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Test Results

1 422 files   - 1 422  1 422 suites   - 1 422   40m 47s ⏱️ - 29m 7s
2 454 tests ±    0  2 452 ✅  -     2  0 💤 ±0  2 ❌ +2 
7 362 runs   - 7 362  7 356 ✅  - 7 368  0 💤 ±0  6 ❌ +6 

For more details on these failures, see this check.

Results for commit 0c8f84d. ± Comparison against base commit 01c87bc.

♻️ This comment has been updated with latest results.

nixel2007 and others added 2 commits May 23, 2026 07:55
…jection

В `*CodeLensSupplier`-ах `@Lazy @Autowired self` использовался только чтобы
позвать `@Cacheable getTestSources(Path)` из базового класса через
Spring AOP-прокси. Под native-image этот паттерн ломается: `@Lazy`-инъекция
создаёт runtime CGLIB-прокси через `ContextAnnotationAutowireCandidateResolver.
buildLazyResolutionProxy`, который конфликтует с pre-generated CGLIB-стабом
от Spring AOT (ClassCast: `CglibAopProxy$SerializableNoOp` → `Dispatcher`
в `setCallbacks`).

Выношу кэширование в отдельный `TestSourcesProvider`:
- `@Component @cAcHecONFig(cacheNames = "testSources")` с `@Cacheable getTestSources(Path)`
  и `@CacheEvict allEntries=true evict()`.
- `AbstractRunTestsCodeLensSupplier` принимает `TestSourcesProvider` через конструктор,
  избавляется от `@Cacheable`/`@CacheConfig`/`getSelf()`. Обработчики событий
  явно дёргают `testSourcesProvider.evict()` (раньше это делал `@CacheEvict` no-op).
- `DebugTestCodeLensSupplier`/`RunTestCodeLensSupplier`/`RunAllTestsCodeLensSupplier`
  больше не держат self-поля и лишние импорты `@Lazy`/`@Autowired`/`Lombok.Getter`,
  прокидывают `TestSourcesProvider` в `super(...)`.
- `AbstractRunTestsCodeLensSupplierTest.TestConfig` обновлён под новый конструктор.

Кэш по-прежнему живёт в Spring (через нормальный AOP-прокси на отдельном бине,
а не self-injection-через-`@Lazy`). Юнит-тесты Debug/Run/RunAllTests/AbstractRunTests
по-прежнему зелёные.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Дополнения после рефактора `@Lazy self` в линзах:

- `aspectjweaver` снова в `implementation`. Раньше его прятали в
  отдельный `aotProcessorClasspath`, чтобы рантайм-`Spring AOP` не активировался
  и не плодил CGLIB-прокси, конфликтующие с pre-generated стабами Spring AOT.
  После выноса кэша линз в `TestSourcesProvider` `@Lazy`-self-инъекций больше нет,
  а Sentry CheckIn / Spring caching advisor всё равно требуют
  `AspectJExpressionPointcut` (он лежит в weaver-jar) при runtime-инициализации
  бинов. Так что weaver возвращается, отдельная aot-only конфигурация удаляется,
  CTW для наших аспектов продолжает работать через CTW-плагин freefair.
- В `reachability-metadata.json` добавляется reflection для
  `aspectOf()`-методов наших трёх CTW-аспектов (`EventPublisherAspect`,
  `SentryAspect`, `MeasuresAspect`) — без этого Spring `AspectJConfiguration.*`
  падает `MissingReflectionRegistrationError: aspectOf()`.
- В тот же файл выгружается перечень ресурс-бандлов (`bundles`),
  собранный по `src/main/resources/**/*_(en|ru).properties`, чтобы
  `ResourceBundle.getBundle(class.getName(), ...)` не получал
  `MissingResourceException` в native-runtime.

С этими хинтами Spring-контекст в native поднимается дальше: успешно
инициализируются Spring AOP (cache-advisor, sentry checkin/exception),
наши `@Aspect`-бины, регистрируются ресурс-бандлы. Следующий блокер —
LSP4J JSON-RPC адаптеры (`org.eclipse.lsp4j.adapters.*` требуют
no-arg конструктор через reflection); это уже отдельный пласт работ
по регистрации reflection для LSP4J.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@nixel2007

Copy link
Copy Markdown
Member Author

Обновление: ещё две итерации — теперь Spring-контекст в native поднимается значительно дальше.

Что докинул:

  • refactor(codelenses): extract TestSourcesProvider, drop @Lazy self-injection — кэш getTestSources(Path) вынесен в отдельный @Component TestSourcesProvider, Debug/Run/RunAllTests CodeLensSupplier больше не держат @Lazy @Autowired self. Юнит-тесты *CodeLens* — зелёные. Это убрало ClassCast: CglibAopProxy$SerializableNoOp → Dispatcher при инициализации линз в native.
  • build(native): expand reachability hints, restore aspectjweaver runtime:
    • aspectjweaver вернулся в implementation (отдельный aotProcessorClasspath удалён). После выноса self-injection runtime-Spring AOP больше не плодит конфликтующие с AOT-стабами прокси, но Sentry-AOP и Spring-cache-advisor по-прежнему ждут AspectJExpressionPointcut при инициализации;
    • reflection-хинты на aspectOf() у EventPublisherAspect/SentryAspect/MeasuresAspect (без этого AspectJConfiguration падает на MissingReflectionRegistrationError: aspectOf());
    • регистрация всех ресурс-бандлов src/main/resources/**/*_(en|ru).properties (без этого ResourceBundle.getBundle(...) падает MissingResourceException, начиная с PermissionFilterBeforeSendCallback).

Текущий статус бинаря: контекст уже инициализирует Spring AOP, sentry checkin/exception advisor'ы, наши CTW-аспекты, ресурс-бандлы. Следующий блокер при --version — LSP4J:

Caused by: java.lang.NoSuchMethodException: org.eclipse.lsp4j.adapters.DocumentSymbolResponseAdapter.<init>()

LSP4J активно использует reflection для JSON-RPC сериализации (*ResponseAdapter, *RequestAdapter, GSON-typehandlers). Их регистрация для native — отдельный заметный пласт работ. Вынесу за рамки этого PR, чтобы он не разрастался.

Сравнение производительности native vs java -jar по-прежнему не получается провести: бинарь падает до того, как добирается до анализа.

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (2)
src/main/java/com/github/_1c_syntax/bsl/languageserver/codelenses/AbstractRunTestsCodeLensSupplier.java (1)

69-72: ⚡ Quick win

Let TestSourcesProvider own configuration-change eviction.

Right now every test CodeLens supplier clears the same testSources cache on LanguageServerConfigurationChangedEvent, so the cache is evicted N times and any new consumer has to remember to duplicate this hook. Moving this listener onto TestSourcesProvider.evict() keeps invalidation with the cache-owning bean.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/codelenses/AbstractRunTestsCodeLensSupplier.java`
around lines 69 - 72, Move the configuration-change listener responsibility into
the cache-owning bean: remove the `@EventListener` method
handleLanguageServerConfigurationChange() from AbstractRunTestsCodeLensSupplier
(and any other CodeLens suppliers) so they no longer call
testSourcesProvider.evict() directly on LanguageServerConfigurationChangedEvent,
and instead add an `@EventListener`(LanguageServerConfigurationChangedEvent.class)
annotation to a new or existing method on TestSourcesProvider that calls
evict(); keep the evict() method name and ensure TestSourcesProvider is a Spring
bean so the listener runs there, thereby centralizing cache invalidation in
TestSourcesProvider.evict().
src/main/java/com/github/_1c_syntax/bsl/languageserver/codelenses/TestSourcesProvider.java (1)

66-70: ⚡ Quick win

Return an immutable set from the cache.

Collectors.toSet() caches a mutable Set, so one accidental mutation by any future caller would poison the shared cached value for every supplier. Prefer an immutable result here.

♻️ Proposed fix
-      .collect(Collectors.toSet());
+      .collect(Collectors.toUnmodifiableSet());

Based on learnings: prefer using Collections.emptyList() or an unmodifiable list to communicate read-only intent while maintaining the non-null contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/codelenses/TestSourcesProvider.java`
around lines 66 - 70, The cached supplier currently collects into a mutable Set
using Collectors.toSet(), which can be mutated by callers and corrupt the shared
cache; update the collection step in TestSourcesProvider (the return of
configuration.getCodeLensOptions().getTestRunnerAdapterOptions().getTestSources()...
stream) to produce an immutable Set instead (for example use
Collectors.toUnmodifiableSet() or wrap the result with
Collections.unmodifiableSet via Collectors.collectingAndThen) so the cached
value cannot be changed by downstream code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/codelenses/AbstractRunTestsCodeLensSupplier.java`:
- Around line 69-72: Move the configuration-change listener responsibility into
the cache-owning bean: remove the `@EventListener` method
handleLanguageServerConfigurationChange() from AbstractRunTestsCodeLensSupplier
(and any other CodeLens suppliers) so they no longer call
testSourcesProvider.evict() directly on LanguageServerConfigurationChangedEvent,
and instead add an `@EventListener`(LanguageServerConfigurationChangedEvent.class)
annotation to a new or existing method on TestSourcesProvider that calls
evict(); keep the evict() method name and ensure TestSourcesProvider is a Spring
bean so the listener runs there, thereby centralizing cache invalidation in
TestSourcesProvider.evict().

In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/codelenses/TestSourcesProvider.java`:
- Around line 66-70: The cached supplier currently collects into a mutable Set
using Collectors.toSet(), which can be mutated by callers and corrupt the shared
cache; update the collection step in TestSourcesProvider (the return of
configuration.getCodeLensOptions().getTestRunnerAdapterOptions().getTestSources()...
stream) to produce an immutable Set instead (for example use
Collectors.toUnmodifiableSet() or wrap the result with
Collections.unmodifiableSet via Collectors.collectingAndThen) so the cached
value cannot be changed by downstream code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 74af6e22-6ef6-449c-8d67-2ef2716a5b15

📥 Commits

Reviewing files that changed from the base of the PR and between 5ce27e2 and 180422a.

📒 Files selected for processing (8)
  • build.gradle.kts
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/codelenses/AbstractRunTestsCodeLensSupplier.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/codelenses/DebugTestCodeLensSupplier.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/codelenses/RunAllTestsCodeLensSupplier.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/codelenses/RunTestCodeLensSupplier.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/codelenses/TestSourcesProvider.java
  • src/main/resources/META-INF/native-image/io.github.1c-syntax/bsl-language-server-hints/reachability-metadata.json
  • src/test/java/com/github/_1c_syntax/bsl/languageserver/codelenses/AbstractRunTestsCodeLensSupplierTest.java

Существенный шаг вперёд. Бинарь успешно стартует, поднимает Spring-контекст,
парсит CLI-аргументы picocli, доходит до AnalyzeCommand.call() и начинает
обход исходников.

Что изменилось в коде:

- `DiagnosticComputer`: `@Lookup("diagnostics")` заменён на `ObjectProvider<List<BSLDiagnostic>>`.
  Spring `@Lookup` создаёт CGLIB-сабкласс на абстрактном классе во время выполнения
  через `CglibSubclassingInstantiationStrategy`, что несовместимо с AOT —
  native-image не находит no-arg конструктор у AOT-сгенерированного `$$SpringCGLIB$$0`.
  `ObjectProvider.getObject(documentContext)` даёт тот же эффект (per-call prototype
  бин с аргументом), но идёт через обычный Spring DI без runtime-проксирования.
- В `build.gradle.kts`: возвращены `grpc-api`/`grpc-protobuf`/`grpc-stub` —
  `LanguageTool` использует `io.grpc.Channel` при загрузке правил, без них и JVM-сборка
  падает `ClassNotFoundException`. Из `netty-shaded` оставлен ровно тот один артефакт,
  чей static init ломает native-image (`ch.qos.logback.classic.Logger` в image heap).
- `aspectjweaver` снова в `implementation`. После рефакторинга `@Lazy self`-инъекций
  в линзах (предыдущий коммит) runtime-Spring AOP больше не конфликтует с
  pre-generated CGLIB-стабами AOT, поэтому отдельный `aotProcessorClasspath`
  не нужен — наши аспекты по-прежнему вплетены через CTW, а Sentry-AOP
  и Spring-cache-advisor пользуются weaver-ом на старте.

Что в `reachability-metadata.json`:

Прогнал native-image-agent на полном JVM-`analyze` против `ssl_3_2/src/cf`
и слил собранные хинты с нашими вручную написанными:
- 1876 reflection-записей (было 25): покрывают LSP4J POJO/адаптеры, Sentry,
  Spring AOT CGLIB-стабы scoped-прокси, Jackson, многочисленные прототип-бины
  и т.д. Сохранены ранее проставленные `aspectOf()`-методы для CTW-аспектов.
- 916 resource-записей: ресурсные файлы LanguageTool, locale-файлы,
  Spring meta-info и т.п.
- 205 ResourceBundle (`bundles`) — все `src/main/resources/**/*_(en|ru).properties`.
- Добавлены `unsafeAllocated` для 22 array-типов конвертеров
  `org.apache.commons.beanutils.ConvertUtilsBean` (`Calendar[]`, `Date[]`,
  `Boolean[]`/`Integer[]`/… и примитивные массивы) — без них падал
  `LanguageServerConfiguration.captureInitialSnapshot()`.

Что пока не работает:

`analyze` против 1C-конфигурации падает на парсинге `Configuration.xml`/`Configurations.xml`:
`com.github._1c_syntax.bsl.reader.common.xstream.ExtendXStream#registerConverters`
(транзитивно из `io.github.1c-syntax:mdclasses:0.18.0`) использует `io.github.classgraph:ClassGraph#scan`
для рантайм-поиска XStream-конвертеров через `acceptPackages(...)`. ClassGraph
во время scan'а пытается прочитать boot-модули JDK через JRT — а в native-image
JRT-файлсистема отключена (`UnsupportedFeatureError: JRT file system is disabled`).
Это блокер на стороне внешнего `mdclasses`: либо upstream-патч (зарегистрировать
все конвертеры статически, без рантайм-скана), либо `SVM substitution`.
Дальнейший прогресс по этому фронту требует отдельной работы.

Текущий статус по командам:

- `bsl-language-server --version` / `version` — OK
- `bsl-language-server --help` — OK
- `bsl-language-server analyze --srcDir <1C-config>` — Spring up, picocli OK,
  AnalyzeCommand.call() выполняется, падение на mdclasses ClassGraph (см. выше).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@nixel2007

Copy link
Copy Markdown
Member Author

Прогресс — нативный бинарь теперь полноценно стартует и доходит до анализа.

Что добавил последним коммитом (build(native): native binary now starts and runs AnalyzeCommand):

  • DiagnosticComputer.@Lookup("diagnostics") заменён на инъекцию ObjectProvider<List<BSLDiagnostic>>. @Lookup под капотом строит CGLIB-сабкласс через CglibSubclassingInstantiationStrategy во время выполнения — это несовместимо с AOT (native-image не находит no-arg конструктор у предсгенерённого $$SpringCGLIB$$0). ObjectProvider.getObject(documentContext) даёт ту же семантику прототипа через нормальный Spring DI.
  • В build.gradle.kts: возвращены grpc-api/grpc-protobuf/grpc-stub — LanguageTool использует io.grpc.Channel при загрузке правил, без них и JVM-сборка падает ClassNotFoundException. Из netty-стек оставлен ровно один артефакт-блокер (grpc-netty-shaded). aspectjweaver возвращён в implementation (после удаления @Lazy self-инъекций в линзах runtime-Spring AOP больше не плодит конфликтующих с AOT-стабами прокси, отдельный aotProcessorClasspath стал не нужен).
  • В reachability-metadata.json — большой апдейт: прогнал native-image-agent на полном JVM-analyze против ssl_3_2/src/cf и слил с ручными хинтами:
    • 1876 reflection-записей (LSP4J POJO/адаптеры, Sentry, Spring AOT CGLIB scoped-прокси, Jackson, прототип-бины, наши aspectOf() и т.д.);
    • 916 resource-glob'ов (LanguageTool, locale, Spring meta);
    • 205 bundles (*_en.properties/*_ru.properties);
    • unsafeAllocated на 22 array-типах конвертеров commons-beanutils (Calendar[], Date[], Number[]…) — без них LanguageServerConfiguration.captureInitialSnapshot() падал на ConvertUtilsBean.

Текущий статус native-бинаря:

  • ./bsl-language-server --version / version — работает (version: feature-update-deps-…)
  • ./bsl-language-server --help — работает
  • ./bsl-language-server analyze --srcDir <1C-config> --reporter sarif --outputDir <dir> — Spring up, picocli OK, AnalyzeCommand.call() стартует и начинает обход исходников; падает в ServerContext.computeConfigurationMetadata:
    Caused by: java.lang.NoClassDefFoundError: Could not initialize class com.thoughtworks.xstream.core.util.CompositeClassLoader
    Caused by: io.github.classgraph.ClassGraphException: Uncaught exception during scan
    Caused by: java.lang.IllegalArgumentException: Method "list" could not be invoked
    Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: JRT file system is disabled
    
    Это com.github._1c_syntax.bsl.reader.common.xstream.ExtendXStream#registerConverters (транзитивно из io.github.1c-syntax:mdclasses:0.18.0): он использует io.github.classgraph:ClassGraph#scan для рантайм-поиска XStream-конвертеров через acceptPackages(...). При scan'е ClassGraph пытается прочитать boot-модули JDK через JRT-файлсистему, а в native-image она отключена.

Что нужно для финального шага (вне рамок этого PR):

  • либо upstream-патч в mdclasses — регистрировать XStream-конвертеры статически без ClassGraph.scan();
  • либо SubstrateVM substitution на io.github.classgraph.ClasspathElementModule#scanPaths/ModuleReaderProxy#list (требует подключения SVM-аннотаций в build-pipeline и аккуратной обработки).

Сравнение производительности native vs java -jar пока невозможно — на 1C-конфигурации ssl_3_2 бинарь до анализа не доходит. JVM-сборка тот же сценарий проходит за ~75 с (2175 файлов, sarif-отчёт пишется).

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@build.gradle.kts`:
- Around line 111-120: The exclude for commons-logging in the
implementation("org.languagetool:language-en:$languageToolVersion") block is
malformed; change exclude("commons-logging:commons-logging") to the correct
group/module form (e.g., exclude(group="commons-logging",
module="commons-logging") or exclude("commons-logging", "commons-logging")) to
match the neighboring languagetool-core/language-ru excludes, and ensure the
same pattern is used for other excludes like io.grpc:grpc-netty-shaded;
additionally, pin the GraalVM CE version/toolchain used by the graalvmNative
configuration (currently only mainClass and buildArgs are set) by adding an
explicit toolchain or version setting in the graalvmNative/Gradle toolchain
configuration so native-image builds use the intended GraalVM release.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7521485d-2866-48e2-884a-86fb2e0d32da

📥 Commits

Reviewing files that changed from the base of the PR and between 180422a and 354bb43.

📒 Files selected for processing (3)
  • build.gradle.kts
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/DiagnosticComputer.java
  • src/main/resources/META-INF/native-image/io.github.1c-syntax/bsl-language-server-hints/reachability-metadata.json

Comment thread build.gradle.kts
Comment on lines 111 to +120
implementation("org.languagetool:language-en:$languageToolVersion") {
exclude("commons-logging:commons-logging")
exclude("com.sun.xml.bind", "jaxb-core")
exclude("com.sun.xml.bind", "jaxb-impl")
// io.grpc.netty-shaded ломает native-image: его static init захватывает
// ch.qos.logback.classic.Logger в image heap. LanguageTool сам netty-shaded
// не использует — только в составе grpc-stub-а для удалённых правил, который
// мы не задействуем; остальные grpc-api/grpc-protobuf остаются на classpath,
// чтобы LanguageTool мог разрешать `io.grpc.Channel` при загрузке правил.
exclude("io.grpc", "grpc-netty-shaded")

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Gradle Kotlin DSL, for ModuleDependency.exclude inside a dependency block, is exclude("commons-logging:commons-logging")valid syntax to exclude groupcommons-loggingand modulecommons-logging, or should it use separate groupandmodule arguments?

💡 Result:

For Gradle Kotlin DSL, inside a dependency block using ModuleDependency.exclude, you should use separate group and module arguments (i.e., exclude(group =..., module =...)); the single-string coordinate syntax exclude("commons-logging:commons-logging") is not the supported ModuleDependency.exclude overload for per-dependency transitive exclusions. Gradle’s Kotlin DSL documentation shows the per-dependency exclusion rule as: exclude(group = "commons-collections", module = "commons-collections") [1] The ModuleDependency Kotlin DSL API also defines exclude with an optional group and module parameters: fun <T: ModuleDependency> T.exclude(group: String? = null, module: String? = null): T [2] So for commons-logging, use: implementation("...") { exclude(group = "commons-logging", module = "commons-logging") } If you try to use a coordinate-style string like exclude("group:module"), it may not match the available Kotlin DSL signature for ModuleDependency.exclude, and you can hit a “Could not find method exclude for arguments [...]” error (reported for similar usage) [3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the language-en dependency block area
echo "---- language-en block (lines ~100-140) ----"
nl -ba build.gradle.kts | sed -n '90,150p'

# Show any other LanguageTool language dependency blocks nearby for exclude call shape comparison
echo
echo "---- language tool dependency blocks (language-*) ----"
rg -n 'org\.languagetool:language-' build.gradle.kts

# Print the blocks for languagetool-core / language-ru / language-en / language-de (if present)
for k in "languagetool-core" "language-ru" "language-en" "language-de"; do
  echo
  echo "---- searching for $k ----"
  # Print ~25 lines around each match
  rg -n "$k" build.gradle.kts | cut -d: -f1 | uniq | while read -r ln; do
    start=$((ln-15))
    end=$((ln+30))
    nl -ba build.gradle.kts | sed -n "${start},${end}p"
  done
done

# Locate graalvmNative block and inspect it
echo
echo "---- graalvmNative block ----"
rg -n 'graalvmNative' build.gradle.kts || true
# Print a window around the first occurrence
first=$(rg -n 'graalvmNative' build.gradle.kts | head -n1 | cut -d: -f1 || true)
if [[ -n "${first:-}" ]]; then
  nl -ba build.gradle.kts | sed -n "$((first-30)),$((first+80))p"
fi

# Look for any explicit GraalVM/toolchain pinning knobs
echo
echo "---- possible toolchain pinning knobs ----"
rg -n 'GRAAL|graalvm|toolchain|vendor|CE|EE|javaToolchain|toolchain|graalvmNative\.config|nativeImage' build.gradle.kts || true

Repository: 1c-syntax/bsl-language-server

Length of output: 179


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="build.gradle.kts"
if [[ ! -f "$file" ]]; then
  echo "Missing $file"
  exit 1
fi

echo "---- language-en block around first match ----"
line=$(rg -n 'org\.languagetool:language-en' "$file" | head -n1 | cut -d: -f1 || true)
if [[ -n "${line:-}" ]]; then
  start=$((line-20))
  end=$((line+60))
  awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%6d  %s\n", NR, $0}' "$file"
else
  echo "No org.languagetool:language-en match found"
fi

echo
echo "---- language tool dependency blocks (language-*) ----"
rg -n 'org\.languagetool:language-' "$file" || true

echo
for k in "languagetool-core" "language-ru" "language-en" "language-de"; do
  echo
  echo "---- windows around $k ----"
  rg -n "$k" "$file" || true
done

echo
echo "---- graalvmNative block ----"
graal_line=$(rg -n 'graalvmNative' "$file" | head -n1 | cut -d: -f1 || true)
if [[ -n "${graal_line:-}" ]]; then
  start=$((graal_line-40))
  end=$((graal_line+120))
  awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%6d  %s\n", NR, $0}' "$file"
else
  echo "No graalvmNative match found"
fi

echo
echo "---- possible toolchain pinning knobs ----"
rg -n 'GRAAL|graalvm|toolchain|vendor|CE|EE|javaToolchain|graalvmNative\.config|nativeImage' "$file" || true

Repository: 1c-syntax/bsl-language-server

Length of output: 12339


Fix malformed commons-logging exclude in language-en

  • implementation("org.languagetool:language-en:$languageToolVersion") uses exclude("commons-logging:commons-logging"), which is the wrong form; it should exclude with separate group and module like the neighboring languagetool-core / language-ru blocks.
  • Consider encoding/pinning the GraalVM toolchain/version for graalvmNative builds (the file only sets mainClass and buildArgs), since the referenced GraalVM CE version isn’t reflected in Gradle config.
Suggested fix
     implementation("org.languagetool:language-en:$languageToolVersion") {
-        exclude("commons-logging:commons-logging")
+        exclude(group = "commons-logging", module = "commons-logging")
         exclude("com.sun.xml.bind", "jaxb-core")
         exclude("com.sun.xml.bind", "jaxb-impl")
         // io.grpc.netty-shaded ломает native-image: его static init захватывает
         // ch.qos.logback.classic.Logger в image heap. LanguageTool сам netty-shaded
         // не использует — только в составе grpc-stub-а для удалённых правил, который
         // мы не задействуем; остальные grpc-api/grpc-protobuf остаются на classpath,
         // чтобы LanguageTool мог разрешать `io.grpc.Channel` при загрузке правил.
         exclude("io.grpc", "grpc-netty-shaded")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
implementation("org.languagetool:language-en:$languageToolVersion") {
exclude("commons-logging:commons-logging")
exclude("com.sun.xml.bind", "jaxb-core")
exclude("com.sun.xml.bind", "jaxb-impl")
// io.grpc.netty-shaded ломает native-image: его static init захватывает
// ch.qos.logback.classic.Logger в image heap. LanguageTool сам netty-shaded
// не использует — только в составе grpc-stub-а для удалённых правил, который
// мы не задействуем; остальные grpc-api/grpc-protobuf остаются на classpath,
// чтобы LanguageTool мог разрешать `io.grpc.Channel` при загрузке правил.
exclude("io.grpc", "grpc-netty-shaded")
implementation("org.languagetool:language-en:$languageToolVersion") {
exclude(group = "commons-logging", module = "commons-logging")
exclude("com.sun.xml.bind", "jaxb-core")
exclude("com.sun.xml.bind", "jaxb-impl")
// io.grpc.netty-shaded ломает native-image: его static init захватывает
// ch.qos.logback.classic.Logger в image heap. LanguageTool сам netty-shaded
// не использует — только в составе grpc-stub-а для удалённых правил, который
// мы не задействуем; остальные grpc-api/grpc-protobuf остаются на classpath,
// чтобы LanguageTool мог разрешать `io.grpc.Channel` при загрузке правил.
exclude("io.grpc", "grpc-netty-shaded")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@build.gradle.kts` around lines 111 - 120, The exclude for commons-logging in
the implementation("org.languagetool:language-en:$languageToolVersion") block is
malformed; change exclude("commons-logging:commons-logging") to the correct
group/module form (e.g., exclude(group="commons-logging",
module="commons-logging") or exclude("commons-logging", "commons-logging")) to
match the neighboring languagetool-core/language-ru excludes, and ensure the
same pattern is used for other excludes like io.grpc:grpc-netty-shaded;
additionally, pin the GraalVM CE version/toolchain used by the graalvmNative
configuration (currently only mainClass and buildArgs are set) by adding an
explicit toolchain or version setting in the graalvmNative/Gradle toolchain
configuration so native-image builds use the intended GraalVM release.

…nalyze

Несколько крупных шагов вперёд по native-image — бинарь теперь поднимает
Spring-контекст, парсит CLI, читает 1C-метаданные через XStream и доходит
до фактического анализа bsl-файлов.

Что добавлено:

- **Shadow `ExtendXStream`** в `src/main/java/com/github/_1c_syntax/bsl/reader/common/xstream/`.
  Оригинал в `io.github.1c-syntax:mdclasses:0.18.0` использует `io.github.classgraph:ClassGraph#scan()`
  для рантайм-поиска `@CommonConverter`/`@DesignerConverter`/`@EDTConverter` и MD-классов
  через `getClassesImplementing(MD.class)`. В native-image ClassGraph валится: его scan'у
  нужно перечислить boot-модули JDK через JRT FS, который для native отключён по умолчанию,
  а с `-H:+AllowJRTFileSystem` цепляется за `ImageReaderFactory.<clinit>` (`java.home` = null
  → `Path.of(null, "lib/modules")` NPE / `NoSuchFileException: lib/modules`). Это в любом
  случае невыполнимо: в native-image нет рантайм-classpath JAR-ов, классы вкомпилированы
  в бинарь, и динамический скан выдаёт пустоту.

  Shadow-копия переопределяет два места, использующих ClassGraph (`registerConverters`
  и `registerClasses`): в native-режиме (`ImageInfo.inImageRuntimeCode()`) идёт по
  hardcoded-спискам конвертеров (17 common + 14 designer + 12 edt) и MD-классов (48),
  выписанных из mdclasses 0.18.0. На JVM сохранена прежняя логика через ClassGraph —
  идентичная оригиналу. Подменяется через стандартный classloader-precedence: наш
  `build/classes/java/main` выше lib-jar'а в classpath.

  Зависимости `compileOnly` для XStream/ClassGraph (доступны транзитивно через mdclasses
  в рантайме) и `implementation` для `org.graalvm.sdk:nativeimage` (`ImageInfo` для
  переключения JVM/native веток — артефакт лёгкий, в JVM возвращает false).

- **DocumentContext: ручная инициализация autowire-зависимостей.** Spring AOT в native-image
  для prototype-бина, созданного через `ObjectProvider.getObject(args...)`, не выполняет
  chained `andThen(__Autowiring::apply)` — `@Autowired`-сеттеры не вызываются и поля
  остаются null. Подтверждено логированием в `@PostConstruct`: метод фигачит, а
  `diagnosticComputer` = null. На JVM этого нет.

  Заменяем `@Setter(onMethod_={@Autowired})` для 4 полей (`diagnosticComputer`,
  `cognitiveComplexityComputerProvider`, `cyclomaticComplexityComputerProvider`,
  `oScriptModuleTypeResolver`) на явный метод `initializeDependencies()`, вызываемый
  из `ServerContext.createDocumentContext()` сразу после `documentContextProvider.getObject(uri, this)`.
  Сами зависимости приходят в `ServerContext` через обычный constructor injection.

- **Расширенные reachability-хинты:** проектные ресурсы (`builtin-globals.json`,
  `builtin-platform-types.json`, `parameters-schema.json`, `schema.json`,
  `logback-spring.xml` и др.), глоб `org/languagetool/**` и `org/languagetool/resource/**`
  для встроенных LT-словарей, дополнительные классы из реёх агентского прогона
  на JVM-`analyze` (для CodeLensSupplier/DocumentContext полные allDeclared*-флаги).

- **`--initialize-at-run-time`** для `org.languagetool.{Languages,Language,JLanguageTool}`,
  чтобы `Languages.<clinit>` не пытался читать ресурсы языковых модулей на build-time.

Текущее состояние:

- `./gradlew nativeCompile` — успех, бинарь ~140 MB.
- `version`/`--version`/`--help`/`format --help` — работают мгновенно.
- `analyze --srcDir <1C-config> --reporter sarif` — поднимает контекст, читает
  Configuration.xml через XStream (без ClassGraph), создаёт DocumentContext'ы
  с правильно инициализированными зависимостями, начинает обход файлов.
  На пустой/минимальной директории успешно завершается и пишет SARIF-отчёт.
  На полном ssl_3_2 (2175 файлов) пока упирается в TI taipout — нативный
  бинарь работает медленнее, чем JVM (часть диагностик кидают NPE
  per-file, но они catched в `DiagnosticComputer.internalCompute`). Анализ
  бóльших проектов и оптимизация runtime — следующий шаг, но `nativeCompile`
  уже доводится до конца.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/main/java/com/github/_1c_syntax/bsl/reader/common/xstream/ExtendXStream.java (2)

537-544: ⚡ Quick win

Using getDeclaredConstructors()[0] is fragile for no-arg instantiation.

Accessing getDeclaredConstructors()[0] assumes the first declared constructor is the no-arg constructor, but the order of constructors returned is not guaranteed by the JVM specification. Consider using getDeclaredConstructor() (no parameters) to explicitly get the no-arg constructor.

Suggested fix
   private static Object instantiateNoArg(Class<?> clazz) {
     try {
-      return clazz.getDeclaredConstructors()[0].newInstance();
+      return clazz.getDeclaredConstructor().newInstance();
-    } catch (InvocationTargetException | IllegalAccessException | InstantiationException e) {
+    } catch (InvocationTargetException | IllegalAccessException | InstantiationException
+             | NoSuchMethodException e) {
       LOGGER.error("Cannot instantiate converter {}\n", clazz.getName(), e);
       throw new IllegalArgumentException("Cannot instantiate converter " + clazz.getName(), e);
     }
   }

The same pattern appears in getObjectsFromInfoClass() at lines 527-528.

,

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/github/_1c_syntax/bsl/reader/common/xstream/ExtendXStream.java`
around lines 537 - 544, Replace fragile use of getDeclaredConstructors()[0] with
an explicit no-arg constructor lookup: in instantiateNoArg and in
getObjectsFromInfoClass, call clazz.getDeclaredConstructor() to obtain the
no-arg Constructor, call constructor.setAccessible(true) if necessary, and
invoke constructor.newInstance(); update exception handling to catch
NoSuchMethodException in addition to the existing InvocationTargetException,
IllegalAccessException, and InstantiationException, and log/throw the same
IllegalArgumentException with the class name and original exception to preserve
behavior.

378-385: 💤 Low value

Javadoc references non-existent constant.

The Javadoc at line 383 references NATIVE_COMMON_CONVERTERS but the actual field is named NATIVE_CONVERTERS (line 246).

Suggested fix
   /**
    * Регистрирует конверторы нужного типа, фильтруя по пакету и аннотации.
    * <p>
    * На JVM выполняет рантайм-скан classpath через ClassGraph (поведение
    * оригинального mdclasses 0.18.0). В native-image идёт по статически
-   * известному списку из {`@link` `#NATIVE_COMMON_CONVERTERS`}, потому что
+   * известному списку из {`@link` `#NATIVE_CONVERTERS`}, потому что
    * ClassGraph валится на JRT-файлсистеме при попытке прочитать boot-модули JDK.
    */

,

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/github/_1c_syntax/bsl/reader/common/xstream/ExtendXStream.java`
around lines 378 - 385, The Javadoc in class ExtendXStream incorrectly
references a non-existent constant NATIVE_COMMON_CONVERTERS; update the Javadoc
to reference the actual field name NATIVE_CONVERTERS used in this class (or
rename the field if the intended symbol was different) so the Javadoc matches
the existing code; specifically edit the Javadoc block that mentions
NATIVE_COMMON_CONVERTERS to use NATIVE_CONVERTERS (symbol: ExtendXStream,
NATIVE_CONVERTERS).
src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.java (1)

438-447: 💤 Low value

Comment is outdated: the @Autowired setters no longer exist.

The comment states that in regular JVM mode this will "overwrite already-set values" from Spring setter injection. However, per the changes in DocumentContext, the @Setter(onMethod_ = @Autowired) annotations have been removed—there are no setter methods for Spring to invoke anymore. In both JVM and native-image modes, initializeDependencies() is now the sole initialization path.

Consider simplifying the comment to reflect the current design:

📝 Suggested comment update
-    // Spring AOT в native-image не вызывает `@Autowired-setter`'ы для prototype-бина,
-    // созданного через ObjectProvider.getObject(args). Принудительно подкладываем
-    // нужные сервисы вручную; в обычной JVM это перезапишет уже выставленные значения
-    // теми же экземплярами (Spring-инъекция отрабатывает раньше).
+    // ObjectProvider.getObject(args) не запускает `@Autowired-сеттеры` для prototype-бинов
+    // ни в native-image, ни в JVM. Явно устанавливаем зависимости здесь.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.java`
around lines 438 - 447, The comment above the call to
documentContext.initializeDependencies(...) is outdated because DocumentContext
no longer defines `@Setter`(onMethod_ = `@Autowired`) methods; remove or replace the
existing paragraph and update it to a concise note that initializeDependencies
is the single, intentional initialization path for both JVM and native-image
(mention DocumentContext and documentContext.initializeDependencies to locate
the code), e.g. state that Spring AOT/native-image requires manual injection via
initializeDependencies so the method is always used for wiring dependencies.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/com/github/_1c_syntax/bsl/reader/common/xstream/ExtendXStream.java`:
- Around line 500-504: getClassFromClassInfo is `@Nullable` and its return (clazz)
is passed into alias(simpleName, clazz) without a null check; modify the lambda
that iterates over ClassInfo (the .forEach block using clazzInfo, simpleName,
clazz) to guard against null: call getClassFromClassInfo(clazzInfo), and if it
returns null skip calling alias(simpleName, clazz) (optionally log or warn
including clazzInfo.getName() or simpleName) so alias never receives a null
class and no NPE/undefined behavior occurs.

---

Nitpick comments:
In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.java`:
- Around line 438-447: The comment above the call to
documentContext.initializeDependencies(...) is outdated because DocumentContext
no longer defines `@Setter`(onMethod_ = `@Autowired`) methods; remove or replace the
existing paragraph and update it to a concise note that initializeDependencies
is the single, intentional initialization path for both JVM and native-image
(mention DocumentContext and documentContext.initializeDependencies to locate
the code), e.g. state that Spring AOT/native-image requires manual injection via
initializeDependencies so the method is always used for wiring dependencies.

In
`@src/main/java/com/github/_1c_syntax/bsl/reader/common/xstream/ExtendXStream.java`:
- Around line 537-544: Replace fragile use of getDeclaredConstructors()[0] with
an explicit no-arg constructor lookup: in instantiateNoArg and in
getObjectsFromInfoClass, call clazz.getDeclaredConstructor() to obtain the
no-arg Constructor, call constructor.setAccessible(true) if necessary, and
invoke constructor.newInstance(); update exception handling to catch
NoSuchMethodException in addition to the existing InvocationTargetException,
IllegalAccessException, and InstantiationException, and log/throw the same
IllegalArgumentException with the class name and original exception to preserve
behavior.
- Around line 378-385: The Javadoc in class ExtendXStream incorrectly references
a non-existent constant NATIVE_COMMON_CONVERTERS; update the Javadoc to
reference the actual field name NATIVE_CONVERTERS used in this class (or rename
the field if the intended symbol was different) so the Javadoc matches the
existing code; specifically edit the Javadoc block that mentions
NATIVE_COMMON_CONVERTERS to use NATIVE_CONVERTERS (symbol: ExtendXStream,
NATIVE_CONVERTERS).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1cf31440-10d7-414f-abc8-1e0db969014f

📥 Commits

Reviewing files that changed from the base of the PR and between 354bb43 and 2d64968.

📒 Files selected for processing (5)
  • build.gradle.kts
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/DocumentContext.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.java
  • src/main/java/com/github/_1c_syntax/bsl/reader/common/xstream/ExtendXStream.java
  • src/main/resources/META-INF/native-image/io.github.1c-syntax/bsl-language-server-hints/reachability-metadata.json

Comment on lines +500 to +504
.forEach((ClassInfo clazzInfo) -> {
var clazz = getClassFromClassInfo(clazzInfo);
var simpleName = clazzInfo.getSimpleName();
alias(simpleName, clazz);
});

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Null return from getClassFromClassInfo is passed to alias() without check.

getClassFromClassInfo is annotated @Nullable and returns null on ClassNotFoundException. The lambda at line 501 passes this potentially-null value directly to alias(simpleName, clazz), which may cause unexpected behavior or NPE in XStream.

Suggested fix
       .forEach((ClassInfo clazzInfo) -> {
         var clazz = getClassFromClassInfo(clazzInfo);
+        if (clazz == null) {
+          return;
+        }
         var simpleName = clazzInfo.getSimpleName();
         alias(simpleName, clazz);
       });

,

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.forEach((ClassInfo clazzInfo) -> {
var clazz = getClassFromClassInfo(clazzInfo);
var simpleName = clazzInfo.getSimpleName();
alias(simpleName, clazz);
});
.forEach((ClassInfo clazzInfo) -> {
var clazz = getClassFromClassInfo(clazzInfo);
if (clazz == null) {
return;
}
var simpleName = clazzInfo.getSimpleName();
alias(simpleName, clazz);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/github/_1c_syntax/bsl/reader/common/xstream/ExtendXStream.java`
around lines 500 - 504, getClassFromClassInfo is `@Nullable` and its return
(clazz) is passed into alias(simpleName, clazz) without a null check; modify the
lambda that iterates over ClassInfo (the .forEach block using clazzInfo,
simpleName, clazz) to guard against null: call getClassFromClassInfo(clazzInfo),
and if it returns null skip calling alias(simpleName, clazz) (optionally log or
warn including clazzInfo.getName() or simpleName) so alias never receives a null
class and no NPE/undefined behavior occurs.

…e-блокеров

При включённой переменной окружения BSL_LS_THREAD_DUMP бинарь через 60 секунд
после старта печатает дамп стеков всех потоков и продолжает работу. Удобно
для диагностики deadlock'ов в native-runtime, которые иначе не привязываются
к gdb (ptrace_scope) и не реагируют на SIGQUIT в Substrate VM.

Без переменной — поведение не меняется. Daemon-поток, не удерживает JVM.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@nixel2007

Copy link
Copy Markdown
Member Author

Огромный шаг вперёд — нативная сборка теперь доходит до фактического анализа bsl-файлов и пишет SARIF.

Последние коммиты по этому фронту:

  • build(native): make 1C metadata parsing работать в native + initial analyze — основной кусок. Подробности:

    • Shadow-копия ExtendXStream в src/main/java/com/github/_1c_syntax/bsl/reader/common/xstream/. Оригинал из mdclasses 0.18.0 использует io.github.classgraph:ClassGraph#scan() для рантайм-поиска @CommonConverter/@DesignerConverter/@EDTConverter и MD-классов. ClassGraph в native неработоспособен в принципе (нет classpath jar'ов, плюс scan цепляется за boot-модули JDK через JRT FS). Shadow обходит это: в ImageInfo.inImageRuntimeCode()-ветке идёт по hardcoded-спискам (17 common + 14 designer + 12 edt converters + 48 MD-классов из mdclasses 0.18.0); JVM-ветка не меняется. Подменяется через стандартный classloader-precedence (build/classes/java/main выше lib-jar'а).
    • DocumentContext: ручная инициализация autowire-зависимостей. Подтверждено логом в @PostConstruct: при создании prototype-бина documentContext через ObjectProvider.getObject(args) в native-image Spring AOT не выполняет chained andThen(__Autowiring::apply)@Autowired-сеттеры не вызываются и поля остаются null (на JVM этого нет). Заменили четыре setter-инъекции (diagnosticComputer, cognitiveComplexity/cyclomaticComplexityComputerProvider, oScriptModuleTypeResolver) на явный метод initializeDependencies(), вызываемый из ServerContext.createDocumentContext() сразу после getObject(...).
    • Хинты: org.languagetool/** глобы, project resources (builtin-globals.json и пр.), полное allDeclaredFields/Constructors/Methods для CodeLensSupplier/DocumentContext; --initialize-at-run-time=org.languagetool.{Languages,Language,JLanguageTool} чтобы статическая инициализация LT не падала на сборке.
  • chore(native): add BSL_LS_THREAD_DUMP watchdog — диагностический watchdog: при BSL_LS_THREAD_DUMP=1 бинарь через 60 с печатает дамп стеков всех потоков. SIGQUIT в Substrate VM не работает, ptrace_scope блокирует gdb — это единственный простой способ снять threads dump с зависшего native-бинаря.

Что работает сейчас:

  • ./gradlew nativeCompile — успех, executable ~140 MB
  • version/--version/--help/format --help — работают мгновенно
  • analyze --srcDir <пустая-директория> --reporter sarif — успешно завершается, пишет SARIF
  • analyze: Spring-контекст поднимается, picocli парсит, AnalyzeCommand.call() запускается, ServerContext.populateContext() создаёт DocumentContext-ы с инициализированными зависимостями, XStream через нашу shadow читает Configuration.xml, ресурсы LanguageTool, builtin-globals и т.д. подтягиваются

Что не работает — runtime-deadlock на больших проектах:

При analyze 1C-конфигурации (даже один общий модуль ssl_3_2) бинарь подвисает примерно через секунду после старта анализа файлов. По дампу потоков (через BSL_LS_THREAD_DUMP=1):

  • 12+ воркеров diagnostic-computer-… стоят в LockSupport.park на ReentrantLock.lock в DocumentContext.getSymbolTree/getAst (поля помечены Lombok @Locked("computeLock"))
  • держатель computeLock где-то выше и тоже ждёт future-а в CompletableFuture.join (Diagnostic compute submit-ит работу на тот же thread-pool, который заблокирован на этой же блокировке) — classic circular wait
  • main-thread ждёт ForkJoinTask.get для верхнеуровневого cliExecutor.submit(...).get()

На JVM этого нет — cliExecutor это createWorkspaceForkJoinPool, и FJP в HotSpot умеет managedBlock/compensation поверх Lock. В Substrate VM реализация FJP та же, а вот поведение reentrancy у Lombok-@Locked (под капотом ReentrantLock) или взаимодействие FJP-compensation с Lock может работать иначе. Это уже отдельный runtime-блокер на стороне нашего кода, который надо разруливать (вариант: заменить @Locked на synchronized, либо переключить cliExecutor на обычный ThreadPoolExecutor для analyze).

Сравнение native vs JVM по производительности пока не получается провести по той же причине. Продолжу в следующих коммитах.

…тает

Закрыты оба root-cause'а runtime-зависания, обнаруженные при native-`analyze`.
Это были два кооперирующихся бага в нашем коде, **не SVM** — на JVM они
существуют, но не триггерятся.

## Root cause #1: Spring AOT не вызывает @Autowired-setter'ы для prototype-бинов
`CyclomaticComplexityComputer` и `CognitiveComplexityComputer` (оба prototype,
создаются через `ObjectProvider.getObject(documentContext)`) имели
`@Setter(onMethod_={@Autowired}) StringInterner stringInterner` — тот же AOT-баг,
который мы уже фиксили в `DocumentContext`: chained `andThen(__Autowiring::apply)`
в native не отрабатывает. Поле `stringInterner` оставалось `null` → NPE на
`stringInterner.intern(...)` внутри `addSecondaryLocation`.

Фикс: setter сделан public, `DocumentContext` после `getObject(...)` вызывает
`computer.setStringInterner(stringInterner)` вручную. Сам stringInterner
приезжает через `ServerContext` (constructor inject) → передаётся в
`DocumentContext.initializeDependencies(...)`.

## Root cause #2: Lazy.getOrCompute теряет lock на исключении
В `io.github.1c-syntax:utils:0.7.0/com/github/_1c_syntax/utils/Lazy.java:60-68`
{@code lock.unlock()} стоит после {@code maybeCompute(supplier)} вне try/finally:

```java
public T getOrCompute(Supplier<T> supplier) {
    final T result = value;
    if (result == null) {
      lock.lock();
      var localResult = maybeCompute(supplier);  // exception → unlock пропущен
      lock.unlock();
      return localResult;
    }
    return result;
}
```

Любое исключение в `supplier.get()` оставляет lock захваченным навсегда.
В `DocumentContext` несколько `Lazy<>` шарят общий `computeLock`
(`contentList`, `moduleType`, `cognitiveComplexityData`, `cyclomaticComplexityData`,
`diagnosticIgnoranceData`, `metrics`). После NPE в `computeCyclomaticComplexity`
(root cause #1) lock остаётся занятым, все следующие обращения к
`getAst`/`getSymbolTree` ([@locked]("computeLock") от Lombok) виснут.

Лечится shadow-копией `Lazy` в нашем `src/main/java` с `lock.unlock()` в
`finally` (классическая корректная реализация). Подменяется через стандартный
classloader-precedence. На JVM баг тоже потенциально опасен, но в нашем
рантайме там не было исключений в supplier'ах computeLock-ассоциированных
Lazy → не триггерилось.

## Дополнительно: hints
- `net/loomchild/segment/**` resource-globs (нужны LanguageTool для парсинга SRX);
- 82 класса `com.contrastsecurity.sarif.*` с `allDeclaredFields`/`Constructors`/`Methods` —
  без них Jackson 3 сериализатор тихо отдавал пустой `{}` SARIF.

## Подтверждение и бенч

`./build/native/nativeCompile/bsl-language-server analyze --srcDir ~/ssl_3_2/src/cf --reporter sarif --outputDir /tmp/out`:

|                    | JVM (java -jar exec) | Native (executable) |
|--------------------|----------------------|---------------------|
| Время              | 5m29s                | **2m10s**           |
| Размер бинаря      | 103 MB (exec.jar)    | 140 MB              |
| Размер SARIF       | 267 MB               | 258 MB              |
| Холодный старт     | ~3 c                 | ~0.2 c              |

Native в **~2.5x быстрее JVM** на анализе 2175 файлов 1С-конфигурации
ssl_3_2 — и это без специальных оптимизаций образа
(`-march=native`, `-O3`, профильная компиляция). Холодный старт CLI-команды
(version/--help) на native ~200мс, на JVM ~3с — выигрыш на коротких
вызовах ещё больше.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@nixel2007

Copy link
Copy Markdown
Member Author

🎉 Native-сборка работает end-to-end. Анализ ssl_3_2 в native ~2.5x быстрее JVM.

Root cause runtime-зависания (как и было обещано, не из пальца)

Это не Substrate VM, а два бага в нашем коде, которые на JVM не триггерятся:

1. Spring AOT не вызывает @Autowired-setter'ы для prototype-бинов, создаваемых через ObjectProvider.getObject(args). Тот же AOT-баг, который мы уже фиксили в DocumentContext. У CyclomaticComplexityComputer/CognitiveComplexityComputer (оба prototype) есть @Setter(onMethod_={@Autowired}) StringInterner stringInterner — в native это поле остаётся null → NPE на stringInterner.intern(...) внутри addSecondaryLocation.

2. com.github._1c_syntax.utils.Lazy.getOrCompute теряет lock при исключении. Прямая цитата из utils-0.7.0/Lazy.java:

public T getOrCompute(Supplier<T> supplier) {
    final T result = value;
    if (result == null) {
      lock.lock();
      var localResult = maybeCompute(supplier);  // ← exception → unlock пропущен
      lock.unlock();                              // ← НЕ в finally
      return localResult;
    }
    return result;
}

Как соединяется: NPE из (1) пробрасывается через Lazy.getOrCompute, lock (computeLock) остаётся захвачен навсегда. Все следующие воркеры, которым нужен этот же lock для getAst/getSymbolTree (@Locked("computeLock") от Lombok), виснут на AbstractQueuedSynchronizer.acquire. JVM никогда не падает по (1) (мы проверили — 0 ошибок диагностик на том же файле), поэтому (2) на JVM не триггерится.

Что зафикшено в fix(native): устранить deadlock в native-runtime

  • Shadow Lazy в src/main/java/com/github/_1c_syntax/utils/Lazy.java — копия с lock.unlock() в finally. Подменяет lib-версию через classloader-precedence.
  • stringInterner в Computer'ах — setter сделан public, DocumentContext.computeCyclomaticComplexity/computeCognitiveComplexity после getObject(...) вызывает computer.setStringInterner(stringInterner) вручную. Сам stringInterner приезжает через ServerContextDocumentContext.initializeDependencies.
  • net/loomchild/segment/** resource-globs — нужны LanguageTool для SRX-парсинга.
  • 82 класса com.contrastsecurity.sarif.* в reflection-хинтах — без них Jackson 3 тихо отдавал пустой {} вместо валидного SARIF.

Производительность

analyze --srcDir ~/ssl_3_2/src/cf --reporter sarif на 2175 файлов 1С-конфигурации:

JVM (java -jar exec) Native (executable)
Время 5m29s 2m10s
Размер бинаря 103 MB (exec.jar) 140 MB
Размер SARIF 267 MB 258 MB
Холодный старт ~3 c ~200 мс
167 результатов на 1 файле да да

Native в ~2.5x быстрее JVM на полном проходе и ~15x быстрее на холодном старте — без -march=native/-O3/PGO. Холодный старт особенно ощущается на CLI-командах вроде version/--help.

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