Repository navigation
Conversation
|
Thanks for opening this — it has been seen, and it is queued. This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence. Current review status: working through a backlog. What that means for this PR, concretely:
Things that will genuinely speed it up whenever review does happen:
If this fixes a bug, a reproduction we can run is worth more than a description of the symptom. Thanks for contributing, and sorry in advance for the wait. |
Adds first-class indexing for PLC codebases written in IEC 61131-3 Structured Text, as shipped by TwinCAT 3 and CODESYS. Closes DeusData#1805. Three new languages: - CBM_LANG_IEC_ST — plain .st/.iecst Structured Text, parsed with a vendored tree-sitter grammar. FUNCTION/FUNCTION_BLOCK/PROGRAM become Class-family nodes, METHOD/PROPERTY become Methods, VAR blocks become Fields, global_var_declaration_block members become Variables, and EXTENDS/IMPLEMENTS carry inheritance edges. - CBM_LANG_TWINCAT — transform-only container for the XML files TwinCAT actually stores on disk (.TcPOU/.TcDUT/.TcGVL/.TcIO). The CDATA declaration/implementation sections are transcoded into one ST buffer with the original line numbers preserved, so every node's line points at the real file. - CBM_LANG_PLCOPEN_XML — transform-only container for CODESYS/PLCopen TC6 XML exports, where the interface is structured XML elements rather than CDATA. Recognised by a content sniff on .xml so ordinary XML files keep their XML classification. Project files (.plcproj/.tsproj/.xti) are walked by a new pass_tcproj to map PLC projects to Package nodes; the definition passes skip them so MSBuild markup never mints Class nodes. The vendored grammar is a self-maintained fork (tools/tree-sitter-iec-st): eleven dialect rules, each derived from a construct that upstream's grammar could not parse in a real 130-file TwinCAT solution. Partial parses on that solution drop from 49 files to 8. Docs: README gains the language entry, the file-type/graph mapping and its limitations; CONTRIBUTING gains "Transform-Only Container Languages" and "Self-Maintained Grammar Forks"; the vendored MANIFEST records the iec_st custom-handling row and its fork status. Verified: 1071 tests pass across extraction, pipeline, language, discover, grammar_labels, grammar_regression and the four repro suites; `make -f Makefile.cbm cbm` links; clang-format, the NOLINT whitelist, security-audit, security-vendored and the license-provenance audit are all clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Yusuf S. Demirci <ysf-samet@outlook.com>
864e7d9 to
d86fe58
Compare
|
Thank you for this — it is a serious piece of work, and validating against a real 130-file / 66-POU TwinCAT solution rather than only fixtures is exactly the evidence that makes a language contribution credible. Several details show you read how this codebase already does things: mirroring The maintainer decision is that this needs to be split: one PR per language, each reviewed on its own merit. I want to be straight with you about what that does and does not mean: splitting is not a commitment to merge any of them. Each will be judged separately, and any one could still be declined. I would rather say that now than have you do the work on an implied promise. The split we are asking forThe dependency order makes itself:
The practical reason is reviewability. At +282,044 lines over 61 files, no reviewer can give the extraction logic, two XML transforms, a new pipeline pass and a 1,253-line grammar the attention each deserves in one pass — and a self-maintained grammar fork is a long-term maintenance commitment that should be decided on its own, not as a by-product of accepting TwinCAT support. Things to fix in whichever PR vendors the grammar
On the security reviewNothing to worry about there, so you do not need to pre-empt it: the grammar is MIT, the new I know splitting a landed piece of work is unrewarding, and I am sorry to ask for it without a guarantee at the end. But per-language review is the only way this gets a fair hearing rather than a nervous one. |
Maintainer notice: please disregard comments from @adfjadfj16-a11y on this thread@adfjadfj16-a11y is not a maintainer of this project and does not speak for it. That account has posted replies on 17 threads here written in the project's voice — promising merges, announcing that a case has been "escalated to the development team", asking to close issues, and in some threads replying as though it were the author of someone else's pull request. None of those were maintainer decisions, and none of them carried any weight. @DeusData is the only account that gives a maintainer response on this repository. If a comment about the fate of your issue or pull request did not come from @DeusData, it is not a decision, however official it reads. If you were waiting on something because of one of those comments — a promised merge, a review "immediately", a request to close your ticket — I am sorry. That was noise you had no way to identify as noise, and it should not have been on your thread. Your issue or PR is judged on its own merits, and I will answer it here myself. Nothing in this notice reflects on your contribution. Thank you for your patience, and thank you for the work. |
Dismissed by the maintainer: this account is not a member of the maintainer team and its reviews carry no weight here. Only reviews from DeusData are maintainer decisions.
|
Heads-up, with thanks: one small defect fix bundled in this PR is too useful to wait for the language review, so it is split out as #2083 with you credited as co-author: the semantic-edges pass called the sort with a null array whenever a project has no functions or methods, which is undefined behaviour that the Linux sanitizer lane reports today. When you next rebase this branch that hunk will drop out on its own; the language addition itself stays under review here as before. |
phase1_scan_functions() in pass_semantic_edges.c sorts the collected Function/Method node pointers so the func order is deterministic. When the graph has no Function or Method node at all -- a struct-only or config-only project -- node_ptrs is never allocated and the call becomes qsort(NULL, 0, ...). The func_count <= 0 early-out lives in phase 1b, which runs only after phase 1 has returned, so it never guards this call. A NULL qsort base is undefined behaviour. glibc declares the parameter nonnull, so UBSan on the Linux leg reports "null pointer passed as argument 1, which is declared to never be null" at the call. The macOS SDK carries no such attribute, so neither the default sanitizer build nor the Homebrew LLVM diag lane can flag it there. The existing pipeline_docstring_go_class test already reaches the call on Linux; the harness never gated on recoverable UBSan reports, so it went unnoticed. Guard the sort on func_count > 0. The new test pipeline_semantic_edges_no_functions indexes a Go struct-only fixture through the full pipeline and pins the fixture to what it claims (Struct >= 1, Function = Method = 0). On the Linux leg with UBSAN_OPTIONS=halt_on_error=1 the pipeline suite aborts at the qsort call before the fix and completes with it. Distilled from DeusData#1810 with co-author credit. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com> Co-authored-by: Yusuf S. Demirci <ysf-samet@outlook.com>
|
Status note: the empty-graph function-sort guard that surfaced while reviewing this PR (a qsort over zero functions) landed on main as 8ec859b (#2083) and ships in v0.10.9, so it is off this PR's plate. The language support itself is still under review on its own merits. Thank you for the patience on this one. |
|
I've been working on the same topic independently, on Where the two approaches differ
What the fork adds that I couldn't find in #1810's diff (each measured against an XML ground
All of it is covered by tests in What I'd propose Once the IEC_ST foundation from #1810's split is settled, I could port items 1–4 onto it as small, |
|
Thank you for the grammar and normalization follow-up. There are now competing foundation choices for the IEC ST work, and that direction needs a maintainer decision before more porting effort. We need more time to compare them; please do not interpret this as a request to rework the stack against an unchosen foundation. |
|
I’m completely fine with moving forward with @SarletteD ’s approach if that’s the direction the maintainers would prefer for the IEC ST foundation. I’ve been dealing with a short-term increase in workload recently, so unfortunately I haven’t been able to dedicate enough time to continue development on this PR. If you’re willing, @SarletteD, I’d really appreciate it if you could split your implementation into the smaller, focused PRs suggested by @DeusData and submit them separately. I’m happy for the work to continue in that direction, and I can contribute again once my availability improves. Thanks for picking this up and for the additional work on the TwinCAT/ST support. |
|
Thanks a lot, @ysfsmet — for the go-ahead and for the groundwork in this PR, much of which @DeusData, understood from your note on the 19th: I won't open anything until you have chosen
PLCopen XML and If a draft of step 1 would help you compare, I can put one up; otherwise I'll wait for your call. |
|
Thank you both. @ysfsmet, for the groundwork here and for handing it on so generously. @SarletteD, for building on it and for laying out the split so clearly. We are going with your single-language Structured Text approach as the foundation, @SarletteD, in the six PRs you listed and in that order. Please start with PR 1 (
We'll keep #1810 open as the reference until PR 1 is up, then close it with credit to @ysfsmet for the groundwork. Thank you again for keeping this moving. |
phase1_scan_functions() in pass_semantic_edges.c sorts the collected Function/Method node pointers so the func order is deterministic. When the graph has no Function or Method node at all -- a struct-only or config-only project -- node_ptrs is never allocated and the call becomes qsort(NULL, 0, ...). The func_count <= 0 early-out lives in phase 1b, which runs only after phase 1 has returned, so it never guards this call. A NULL qsort base is undefined behaviour. glibc declares the parameter nonnull, so UBSan on the Linux leg reports "null pointer passed as argument 1, which is declared to never be null" at the call. The macOS SDK carries no such attribute, so neither the default sanitizer build nor the Homebrew LLVM diag lane can flag it there. The existing pipeline_docstring_go_class test already reaches the call on Linux; the harness never gated on recoverable UBSan reports, so it went unnoticed. Guard the sort on func_count > 0. The new test pipeline_semantic_edges_no_functions indexes a Go struct-only fixture through the full pipeline and pins the fixture to what it claims (Struct >= 1, Function = Method = 0). On the Linux leg with UBSAN_OPTIONS=halt_on_error=1 the pipeline suite aborts at the qsort call before the fix and completes with it. Distilled from DeusData#1810 with co-author credit. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com> Co-authored-by: Yusuf S. Demirci <ysf-samet@outlook.com>
|
@ysfsmet, thank you for this, and for how generously you handed it on. This PR is where Structured Text support in this project really started: it showed that TwinCAT and CODESYS repositories were invisible to the indexer, and it worked out how to change that on a real 130-file solution rather than only on fixtures. As announced on 2026-09-25, the Structured Text foundation is going ahead as the single-language series by @SarletteD, and its first PR is now up as #2397. As we said then, that is the point at which we close this one, with credit to you for the groundwork. Several things from here carry forward or are already on main:
Two parts of this PR are not in the six-PR plan: the PLCopen XML container and the .plcproj / .tsproj project nodes. If you find the time once the Structured Text foundation has landed, those would be very welcome as small, separate PRs on top of it, each reviewed on its own merit as before. #1805 stays open to track the language support until the series lands. Thank you again for the work, and for keeping the door open for the next step. |
Closes #1805.
Industrial-automation repositories built with Beckhoff TwinCAT 3 (and CODESYS-family IDEs) were entirely invisible to the indexer: PLC sources (
.TcPOU,.TcDUT,.TcGVL,.TcIO) and project files (.plcproj,.tsproj) matched nothing in the extension table and were silently skipped. This adds first-class IEC 61131-3 Structured Text support, following the patterns the codebase already sanctions.Everything below was validated against a real 130-file / 66-POU TwinCAT solution, not only fixtures — that is where most of the interesting defects came from.
What lands
Three new languages.
CBM_LANG_IEC_ST— the grammar language, for plain.st/.iecstsources.CBM_LANG_TWINCAT— transform-only container for.TcPOU/.TcDUT/.TcGVL/.TcIO, mirroringCBM_LANG_OBJECTSCRIPT_EXPORT:internal/cbm/twincat_xml.crecomposes the<Declaration>/<Implementation><ST>CDATA into textual ST, newline-padded so definition lines still point into the physical file, and the generated units are re-extracted asCBM_LANG_IEC_ST.CBM_LANG_PLCOPEN_XML— transform-only container for CODESYS/PLCopen TC6 exports, detected by a content sniff on theplcopen.org/xmlnamespace. PLCopen declares variables as structured XML rather than CDATA, sointernal/cbm/plcopen_xml.csynthesizes the declaration text from<inputVars>/<outputVars>/<variable>.A dedicated project-file pass (
src/pipeline/pass_tcproj.c): each.plcprojbecomes aPackagenode withDEPENDS_ONedges to its library references (Tc2_Standard,Tc3_Module, …) andCONTAINS_FILEedges to its members; a.tsprojgetsDEPENDS_ONto the PLC projects it references andCONFIGURESto their.xtidevice descriptions. Both are registered as incremental control files.Graph mapping.
FUNCTION_BLOCK/PROGRAM→Class;FUNCTION→Function;METHOD/PROPERTY→MethodwithDEFINES_METHOD;INTERFACE→Interface; DUTTYPE→Type; GVL entries →Variable;EXTENDS→INHERITS;IMPLEMENTS→IMPLEMENTS.The grammar is a fork, and that was necessary
The upstream grammar (HeytalePazguato/tree-sitter-iec61131-3-st, MIT, ABI 15) is standard-only, and standard-only does not parse production TwinCAT: 49 of 130 files parsed partially, and every partial parse cost symbols — whole function blocks reached the graph without their methods.
It is forked into
tools/tree-sitter-iec-st/(grammar.js plus generated parser, same layout astools/tree-sitter-form). Each rule was derived from a real failing construct and verified against the same corpus: modifiers after the POU keyword (FUNCTION_BLOCK PUBLIC FINAL FB_X), wildcard located addresses (AT %I*, spaced or glued, on VAR entries and DUT fields), bit-in-word access (wError.0 := TRUE), a base type after an enumerator list ((Idle, Run) UINT;),REFERENCE TObesideREF_TO, function-block instance argument lists (fb : FB_T(),ByteBuffer(ADR(x), SIZEOF(x))), the terminator TwinCAT omits after a block-shapedTYPE, attribute pragmas above members,__TRY/__CATCH/__FINALLY/__ENDTRY,;-terminated interface prototypes, and tolerance for a stray;.Result on the same solution: 49 partially-parsed files → 8, and 98 more methods reach the graph. ABI stays 15; generation is reproducible (
tree-sitter generate, CLI 0.26.x).Defects found and fixed along the way
Three of these are worth calling out because they were silent — no error, no failing test, just wrong or missing data:
PUBLIC. The grammar bound the access modifier to the declaration'snamefield and dropped the real identifier into an ERROR node: 13 function blocks namedPUBLICon the real solution. Recovered structurally (header line only, so a body-level parse error can never rename its POU), which serves all three mis-bound POU kinds without needing the source buffer.Classper XML element. Mapping.plcproj/.tsprojtoCBM_LANG_XMLso they would get File nodes also ran the XML grammar over their markup: 120 nodes namedProject,PropertyGroup,Name,BitSizeburied the 64 genuine POU classes. The definition passes now skip grammar extraction for them — in both the sequential and parallel dispatch.grammar_*.cis a one-line wrapper that#includes its vendored parser, and make cannot see through an include. Harmless for grammars that never change; for a fork it meant the rebuilt binary kept the old grammar while the source tree showed the new one.Makefile.cbmnow names the vendored parser as an explicit prerequisite (prod/test/tsan).Also fixed:
pass_semantic_edgespassed NULL toqsortwhen a project hasClass/Methodnodes but noFunctionnode — formal UB that UBSan flags, and a TwinCAT solution is exactly that shape.Testing
Every registry gate is updated: the capability ledger and its partition counts, the call-argument matrix, the call-node manifest, label goldens,
vendored-checksums.txt, and the MANIFEST provenance record (fork noted, so a future re-vendor re-applies the dialect rules instead of reverting them).New tests cover the transcoders (multi-POU, declaration synthesis, graphical-body tolerance, BOM, UTF-16 rejection, entity decoding, prefix near-misses), the access-modifier recovery, the project-markup exclusion, and an end-to-end
pipeline_twincat_project_graphasserting the Package/DEPENDS_ON/CONTAINS_FILE shape plus a transcoded POU'sDEFINES_METHODchain.All suites pass;
make -f Makefile.cbm cbmlinks.Two notes for reviewers:
tests/test_venue_parity_contract.shhangs on any machine with ImageMagick installed — it probesscripts/ci/generate-sbom.pywithbash, soimport datetimeruns ImageMagick's screen grabber. That is scripts/test.sh hangs indefinitely at the venue-parity contract when ImageMagick is installed (bash runs generate-sbom.py, 'import' becomes ImageMagick) #1384, not this branch; the suites here were run withDISPLAYunset to get past it.tests/test_cli.c:1748(cli_uninstall_quiesces_active_cohort_before_removing_binary_and_index) fails identically on pristinemain— verified in a worktree at the merge base. Not introduced here.Known limitations (documented in README)
Cross-file GVL/DUT references resolve by name rather than declared type; a function-block instance call resolves on the instance identifier rather than the block's type; inside a TwinCAT container the POU's own body line numbers can land after its methods (declaration lines are exact); PLCopen array/pointer-typed variables are dropped rather than approximated, and resource-level
<globalVars>outside a POU interface are not transcoded. Eight of the 130 files still parse partially — the remaining constructs are listed in the fork's notes.🤖 Generated with Claude Code
https://claude.ai/code/session_011BDg22SxMEYCXxGQicWgAc