Skip to content

Fix LAL segmentId/spanId extractor statements; delete dead DSL code and correct the kernel's docs - #13973

Merged
wu-sheng merged 3 commits into
masterfrom
fix/dsl-dead-code-and-kernel-docs
Aug 11, 2026
Merged

Fix LAL segmentId/spanId extractor statements; delete dead DSL code and correct the kernel's docs#13973
wu-sheng merged 3 commits into
masterfrom
fix/dsl-dead-code-and-kernel-docs

Conversation

@wu-sheng

@wu-sheng wu-sheng commented Aug 10, 2026

Copy link
Copy Markdown
Member

This PR carries two things: a LAL bug fix found while writing missing coverage, and the dead-code
and documentation cleanup that led to it.

Fix LAL segmentId and spanId extractor statements, which never worked

Found by writing the coverage below — the DSL surface existed, was documented by the grammar, and
had never been executed.

LALParser.g4 declares three trace-writing extractor statements:

traceIdStatement   : TRACE_ID   valueAccess typeCast? ;
segmentIdStatement : SEGMENT_ID valueAccess typeCast? ;
spanIdStatement    : SPAN_ID    valueAccess typeCast? ;

LALScriptModel.FieldType has all three (TRACE_ID, SEGMENT_ID, SPAN_ID, TIMESTAMP), and
LALBlockCodegen's output-setter table already carried setSegmentId and setSpanId — which
LogBuilder implements. Only LALScriptParser.visitExtractorStatement was missing two branches,
and its if-chain ended with an unchecked assumption:

// before — ctx.ifStatement() is null for any unhandled alternative
return (ExtractorStatement) visitIfStatement(ctx.ifStatement());

So a rule containing segmentId parsed.seg as String failed at boot with
NullPointerException: Cannot invoke "LALParser$IfStatementContext.condition()" because "ctx" is null — naming a construct the rule does not contain. That fall-through is why two of the three
statements stayed unimplemented: the failure never pointed at them.

Both branches are added, and an unmatched extractor statement now reports its own rule line.

No existing behaviour changes. LogBuilder.init() copies trace id, segment id and span id
directly from the log's metadata, and skips that copy only when a rule already set them:

if (this.segmentId == null) { this.segmentId = tc.getTraceSegmentId(); }

No shipped rule sets them, so every log record already gets these fields — which is exactly why
the gap went unnoticed.

Coverage, none of which existed

METADATA_TRACE_GETTERS (traceId / traceSegmentId / spanId) had no rule and no test
exercising any entry, in either direction. The harness could not express it either — the fixture
builder only understood trace-id, and the execution test had no expectation for these fields.
Both are extended, then used:

case covers
trace-context-read reads all three via log.traceContext.* into tags
trace-write-all-three writes all three from an extractor, overriding metadata
trace-segment-override segmentId alone — the exact shape that threw

trace-segment-override failed with the NPE before the parser fix and passes after, so the tests
demonstrably catch the defect rather than merely accompanying it.


Delete dead DSL code and correct the shared kernel's own documentation

Follow-up to #13972. No behaviour change: deletions of unreferenced code, plus comment and
javadoc corrections. 86 insertions, 506 deletions.

Dead code

Each verified to have no reference in Java, resources, SPI files, codegen string literals or
FreeMarker templates — the last two matter, because a class named only from a .ftl looks dead to
every tool (OALDebug is exactly that case, and is not touched here).

deleted why
DslContentHash byte-identical, zero-caller twin of the live ContentHash, which has 29 call sites and a test. Its javadoc told the reader to consolidate toward the dead copy
oal-rt/.../v2/registry/ (3 classes) a second, divergent answer to what MetricsHolder already does; zero references outside its own package
LogAnalyzerFactory empty class, sole occupant of its package, one occurrence repo-wide
MALCodegenHelper.RUN_VAR, MALExprCodegen.getVarCount(), DslYamlLineIndex.empty() no callers

The kernel was asserting consumers it does not have

This is the part worth reviewing. Three classes in server-core carried javadoc justifying their
placement by naming users that grep disproves:

  • DSLClassLoaderManager"the meter / log compile paths reach for INSTANCE". Neither
    analyzer module references it. MAL and LAL accept a plain ClassLoader and narrow through
    BytecodeClassDefiner, so neither ever names it.
  • LogDataDebugDump"Lives in core so LALOutputBuilder implementations can render their
    cached LogData"
    . Zero core references; its one caller is in log-analyzer.
  • DslContentHash"…can share one implementation" / "scheduled for consolidation in a
    follow-up"
    . The follow-up never happened and nothing shared it.

A false rationale in a shared kernel is not a stale comment, it is a standing instruction to put
more things there. Each now states what the call sites support.

The converse was also missing: OALDebug, OALDebugRecorder and DebugHolderProvider are
OAL-specific and by symmetry belong in oal-rt, where MAL's and LAL's equivalents live. They
cannot move — dsl-debugging/pom.xml declares no oal-rt dependency, so the recorder
implementation would lose sight of them. Nothing said so, leaving it looking like carelessness.
Now recorded on all three.

Residue from #13972

Fourteen sites. The worst contradicts the code beside it: LALClassGenerator stated "No
LineNumberTable … an absent attribute reports an unknown line, which is honest"
eleven lines
above the call that attaches one, and above a test from the same PR asserting every method carries
one. git show confirms it survived as a two-word substitution inside the very hunk that added
those calls.

Also: a javadoc block orphaned when a method was inserted between it and its owner; a {@link} to
a method that PR deleted; escapeJava / sanitizeName / COMPANION_SAM_LINE_IN_CLASS named in
comments after being removed; a log.debug("MAL YAML line index …") in a class now serving four
DSLs; and a hierarchy javadoc example missing the line number the code actually emits.

Two small corrections

  • MALClassGenerator now uses MALCodegenHelper.GATE_HOLDER_FQCN instead of shadowing it with an
    identical local literal. The constant read as dead only because of the shadow; LAL uses its
    equivalent at six sites.
  • setYamlSource gains the @Deprecated its LAL and Hierarchy counterparts already carry — it was
    the only one of the three giving callers no migration signal while its own delegate target was
    deprecated.

CLAUDE.md: why generated source is fully qualified

Javassist's compiler has no import statement. It resolves a simple class name only against
ClassPool.importedPackages, initialised to java.lang and nothing else; widening it needs
ClassPool.importPackage, which is pool-wide mutable state and is never called here. So generated
source must fully qualify everything, java.util.Map included, and the project's
no-inline-fully-qualified-names rule applies to hand-written Java only. Shortening an FQCN inside a
codegen string or a .ftl compiles as Java and then fails at runtime — worth stating, since the
rule reads as universal.

  • Explain briefly why the bug exists and how to fix it.
  • If this pull request closes/resolves/fixes an existing issue, replace the issue number. Closes #.
  • Update the CHANGES log.

wu-sheng and others added 2 commits August 10, 2026 23:45
Deletions, each verified to have no reference in Java, resources, SPI files,
codegen string literals or FreeMarker templates:
  - DslContentHash, a byte-identical zero-caller twin of the live ContentHash
    whose javadoc instructed the reader to consolidate toward the dead copy
  - oal-rt's metrics-function registry (3 classes), a second divergent answer
    to a problem MetricsHolder already solves
  - LogAnalyzerFactory, sole occupant of its package
  - MALCodegenHelper.RUN_VAR, MALExprCodegen.getVarCount, DslYamlLineIndex.empty

Three kernel classes carried javadoc asserting consumers that do not exist.
DSLClassLoaderManager claimed the MAL and LAL compile paths reach for its
singleton; LogDataDebugDump claimed core LALOutputBuilder implementations
render through it. Neither is referenced by any DSL module. A false rationale
in a shared kernel is a standing instruction to put more things there, so each
now states what the call sites actually support.

OALDebug, OALDebugRecorder and DebugHolderProvider now record why they live in
server-core when MAL's and LAL's equivalents live in their own modules:
dsl-debugging declares no oal-rt dependency, so they cannot move. The
asymmetry is forced by the module graph, not an oversight.

Fourteen comment sites left stale by #13972 are corrected, the worst being a
LALClassGenerator comment stating that no LineNumberTable is attached, eleven
lines above the code that attaches one and a test that asserts it.

MALClassGenerator now uses MALCodegenHelper.GATE_HOLDER_FQCN instead of
shadowing it with an identical local literal, and setYamlSource carries the
@deprecated its LAL and Hierarchy counterparts already had.

CLAUDE.md records why generated source is fully qualified: Javassist's
compiler has no import statement and resolves simple names only against
java.lang, so the no-inline-FQCN rule applies to hand-written Java only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CMkDwMcr7139gZ95XCMQ6i
LALParser.g4 declares traceIdStatement, segmentIdStatement and
spanIdStatement as extractor alternatives, and LALBlockCodegen already
carried setSegmentId / setSpanId in its output-setter table, but
LALScriptParser.visitExtractorStatement implemented a branch for only the
first of the three. The remaining alternatives fell through to a line that
assumed whatever was left had to be an ifStatement, so a rule writing
"segmentId ..." failed at boot with a NullPointerException naming
IfStatementContext -- for a rule line containing no `if`.

Both statements now have their branch, and an extractor statement matching
no branch reports its own rule line instead of throwing an NPE about a
construct the author did not write. That fall-through is what let two of
three trace statements stay unimplemented without anyone noticing.

Existing log records are unaffected: LogBuilder copies trace id, segment id
and span id straight from the log's metadata and skips that copy only when a
rule has already set them -- which no shipped rule did, which is why nothing
surfaced the gap.

Dedicated coverage, none of which existed: reading all three fields through
log.traceContext.*, writing all three from an extractor, and writing
segmentId alone (the shape that threw). The fixture builder gained
trace-segment-id and span-id inputs, and the execution test gained traceId /
segmentId / spanId expectations -- the harness could not express any of this
before, which is the reason METADATA_TRACE_GETTERS had never been executed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CMkDwMcr7139gZ95XCMQ6i
@wu-sheng wu-sheng changed the title Delete dead DSL code and correct the shared kernel's own documentation Fix LAL segmentId/spanId extractor statements; delete dead DSL code and correct the kernel's docs Aug 10, 2026
LALCodegenHelper.METADATA_GETTER_ALIASES was Map.of() with a single reader
in LALValueCodegen, so get() always returned null and the alias branch could
never execute. Its comment named a traceSegmentId -> segmentId alias as the
motivating case; that mismatch no longer exists, because LogMetadata's nested
TraceContext names the field traceSegmentId and Lombok generates
getTraceSegmentId() to match. The map was empty because the problem was
solved, not because work was left unfinished.

Removing the branch makes getterName final; it was the only reassignment.
A missing getter now fails the same way it already did for every other
field, with an IllegalArgumentException naming the type and the getter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CMkDwMcr7139gZ95XCMQ6i
@wu-sheng wu-sheng added the bug Something isn't working and you are sure it's a bug! label Aug 11, 2026
@wu-sheng wu-sheng added this to the 11.0.0 milestone Aug 11, 2026
@wu-sheng
wu-sheng merged commit f5a5063 into master Aug 11, 2026
452 of 457 checks passed
@wu-sheng
wu-sheng deleted the fix/dsl-dead-code-and-kernel-docs branch August 11, 2026 01:55
wu-sheng added a commit that referenced this pull request Aug 11, 2026
The #13973 entry states that dsl-debugging now declares its server-core
dependency directly rather than inheriting it transitively. It does not:
that pom change was proposed, rejected on review as unnecessary -- server-core
is foundational and never leaves the dependency tree -- and reverted, but the
changelog sentence written before the revert was not removed with it.

dsl-debugging/pom.xml contains no server-core declaration. Removing the
sentence rather than adding the dependency, because the review's conclusion
stands.
wu-sheng added a commit that referenced this pull request Aug 11, 2026
… and name the classify branches

Three pieces of DSL-internals cleanup. No behaviour change.

core/classloader held only DSL types, two of which say so in their names, and
nothing outside the DSL and rule world imported it. It is now
core/dsl/classloader. Catalog moves to core/dsl instead: a rule-file taxonomy is
not a class-loading concern, and it is the package's most-used type, mostly as a
RuleKey component in dsl-debugging. Nothing names this package in a string
literal, a FreeMarker template or an SPI file, so the compiler catches every
reference; ClassLoaderGc builds its unload-probe resource path from
UnloadProbePayload.class.getName() rather than a constant, so it follows the move.

Three copies of "define this generated class into the right loader" -- MAL's and
LAL's generators and MeterSystem -- collapse into a static
BytecodeClassDefiner.define. It lives on the interface rather than in a class of
its own because the whole decision is whether the loader implements that
interface; a second similarly-named type in the same package would be one concept
with two names. The JDK 17 --add-opens rationale, previously restated in four
places, now has one home and the other three point at it. MeterSystem's
multi-catch drops | IOException, which define() wraps into CannotCompileException
-- already an arm of the same catch, so the rollback and UnexpectedException wrap
are unchanged. That arm was unreachable regardless: CtClass.toBytecode() writes to
a ByteArrayOutputStream whose close() is a documented no-op, so the checked
exception is a declaration artefact.

Three of the four copies of the SW_DYNAMIC_CLASS_ENGINE_DEBUG dump-directory
lookup become DslGeneratedFileWriter.resolveClassDumpDir. OAL keeps its own: its
openEngineDebug flag is settable independently of the environment variable and two
tests call setOpenEngineDebug(true), which the shared resolver cannot express
without a second variant invented for one caller.

/addOrUpdate chose its delta classifier with a MAL/LAL ternary, written when those
were the only two engines, so its else meant LAL and would hand a third engine's
rule to the LAL classifier. The decision is now three-valued and named
(engineKindFor). An unknown catalog cannot reach it -- validate() rejects that
earlier with invalid_catalog, using the same registry lookup -- so the third case
means a registered engine with no delta classifier, which should fail loudly
rather than parse as LAL. DSLScriptKey.isMalCatalog and isLalCatalog go with it:
one caller between them. RuntimeRuleServiceEngineKindTest covers the dispatch,
which had no test because it lived inside a private handler needing a DSLManager,
storage and locks.

DslSourceRef records a decision not to add a shared producer interface for rule
coordinates. Two of the four DSLs could not implement one -- OAL's dispatcher
deliberately wants a lineless ref, and Hierarchy has no per-rule object -- so the
narrow entrance, a file and a line, stays the contract and each DSL keeps what it
knows about its own rules.

Also drops a changelog sentence merged with #13973 claiming dsl-debugging declares
its server-core dependency directly. It does not; that pom change was proposed,
rejected on review and reverted, but the sentence was not removed with it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working and you are sure it's a bug!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants