Skip to content

Bump OpenCC ABI version to 1.4 to prevent librime loading ABI-incompatible v1.3.2 builds - #1377

Merged
frankslin merged 1 commit into
BYVoid:masterfrom
loafer-19:fix-rime-simplifier-crash
Jul 1, 2026
Merged

frankslin merged 1 commit into
BYVoid:masterfrom
loafer-19:fix-rime-simplifier-crash

Conversation

@loafer-19

Copy link
Copy Markdown
Contributor

This crash was caused by #1359

Root Cause

Recent OpenCC configs can include a normalization pre-pass. When normalization is present, Config wraps the normal converter stages in a PipelineConverter.

PipelineConverter::GetConversionChain() previously returned nullptr because a pipeline does not have a single conversion chain. However, librime's Simplifier code inspects OpenCC through converter_->GetConversionChain()->GetConversions() and assumes the returned chain is non-null. With a pipeline-backed converter, that assumption becomes a null pointer dereference, causing the input method to crash when switching to Simplified Chinese.

There is also a separate ABI risk from the recent C++ API/interface changes: downstream C++ consumers built against the old libopencc.so.1.3 ABI can still load the new library because the SONAME did not change.

Fix

  • Make PipelineConverter::GetConversionChain() return the last stage's conversion chain, matching the existing best-effort behavior of GetSegmentation().
  • Update API comments to document this compatibility behavior.
  • Add regression coverage for:
    • PipelineConverter::GetConversionChain()
    • configs with normalization exposing a non-null main conversion chain
  • Set OPENCC_ABI_VERSION to 1.3.2 for CMake SOVERSION, so ABI-incompatible downstream C++ consumers are forced to relink instead of silently loading the wrong library.

Verification

  • git diff --check
  • g++ -std=c++17 -fsyntax-only ... src/ConversionChainTest.cpp
  • g++ -std=c++17 -fsyntax-only ... src/ConfigTest.cpp
  • Manual smoke test confirmed a converter with normalization now returns a non-null conversion chain.

Full CMake/Bazel test runs were not available in this environment because cmake and bazel are not installed.

@frankslin

Copy link
Copy Markdown
Collaborator

We should probably change the minor ABI version to 1.4 as there are several breaking changes introduced by the 1.3.2 release.

@loafer-19
loafer-19 force-pushed the fix-rime-simplifier-crash branch from 3b1b814 to 4dbc8ae Compare July 1, 2026 07:01
@loafer-19

Copy link
Copy Markdown
Contributor Author

We should probably change the minor ABI version to 1.4 as there are several breaking changes introduced by the 1.3.2 release.

Updated:)

@frankslin

Copy link
Copy Markdown
Collaborator

Please correct me if I’m wrong: the simplest fix is to deprecate 1.3.2 and release 1.4.0 without API changes.

The ABI has already changed, so the clean fix is to expose that as a new ABI/API version. I don’t think we need to retrofit the new API to preserve legacy behavior, especially if that would give PipelineConverter::GetConversionChain() a best-effort meaning that is not semantically precise.

@loafer-19
loafer-19 force-pushed the fix-rime-simplifier-crash branch from 4dbc8ae to 72d5765 Compare July 1, 2026 07:36
@loafer-19
loafer-19 force-pushed the fix-rime-simplifier-crash branch from 72d5765 to c02a622 Compare July 1, 2026 07:37
@loafer-19

Copy link
Copy Markdown
Contributor Author

Please correct me if I’m wrong: the simplest fix is to deprecate 1.3.2 and release 1.4.0 without API changes.

The ABI has already changed, so the clean fix is to expose that as a new ABI/API version. I don’t think we need to retrofit the new API to preserve legacy behavior, especially if that would give PipelineConverter::GetConversionChain() a best-effort meaning that is not semantically precise.

that makes sense. I’ve updated this PR to only expose the ABI break by bumping the CMake SOVERSION to 1.4.

One related note: librime still seems to have a separate source-level assumption that GetConversionChain() is always non-null. With the new normalization pipeline, OpenCC may return a PipelineConverter, whose GetConversionChain() can legitimately be nullptr.

So the ABI bump should prevent old librime binaries from silently loading an incompatible OpenCC, but librime may still need a small follow-up fix to handle a null GetConversionChain() when rebuilt against the new OpenCC API.

@frankslin

Copy link
Copy Markdown
Collaborator

Yeah, that’s a real issue.

I think a cleaner approach may be to keep PipelineConverter semantics unchanged and introduce one more abstraction layer e.g. ConfigBasedConverter.

The idea is that PipelineConverter itself still does not have a single well-defined conversion chain, so PipelineConverter::GetConversionChain() should not need to pretend otherwise.

However, in practice, external users have been treating the converter object as a way to introspect the config. For that use case, a config-backed converter could preserve the user-facing behavior: internally it executes a two-stage pipeline, as normalization followed by the traditional conversion flow. In ConfigBasedConverter, GetConversionChain() would return the main conversion chain from the config, not an arbitrary stage of a generic Pipeline Converter.

Separately, I think we can avoid hardcoding OPENCC_ABI_VERSION as 1.4. If we release this as 1.4.0, the existing SOVERSION ${OPENCC_VERSION_MAJOR}.${OPENCC_VERSION_MINOR} logic will naturally produce 1.4.

@frankslin frankslin changed the title Fix a crash triggered by switching to Simplified Chinese Bump OpenCC ABI version to 1.4 to prevent librime loading ABI-incompatible v1.3.2 builds Jul 1, 2026
@frankslin
frankslin merged commit 172f3b1 into BYVoid:master Jul 1, 2026
36 of 37 checks passed
frankslin added a commit that referenced this pull request Jul 1, 2026
…ion is configured (#1380)

Introduce ConfigBasedConverter to correctly expose the main conversion chain when a normalization pre-pass is present, resolving a null-pointer crash in downstream consumers such as librime. Also bumps the CMake/Bazel fallback version to 1.4.0 and tightens the installed public header set.

Note: dev builds derived from untagged commits may still report package version 1.3.2.dev…; the SOVERSION is explicitly pinned to 1.4 by the preceding commit (#1377) and is unaffected by the git-tag-derived package version.

Detailed Changes:
- **ConfigBasedConverter** (new private class):
  - Wraps normConverter + mainConverter; delegates GetConversionChain() to mainConverter so callers always receive a valid non-null ConversionChain.
  - Replaces PipelineConverter in Config.cpp when a normalization field is present.
- **Public header cleanup**:
  - DictConverter.hpp, PhraseExtract.hpp, UTF8StringSlice.hpp moved from LIBOPENCC_HEADERS to LIBOPENCC_PRIVATE_HEADERS (CLI-tool internals, not library API).
  - BinaryDict.hpp, DartsDict.hpp likewise moved to private (were incorrectly appended to the public list).
- **Version fallback**: cmake/GitVersion.cmake minor 3→4, revision 2→0; MODULE.bazel version 1.3.2→1.4.0.
- **Regression test**: NormalizationGetConversionChainIsNonNull verifies GetConversionChain() is non-null and delegates to the main chain, not the normalization chain.
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