👋This is a demo report on a synthetic project — to show what you'll get. No need to upload your own code. Want to check yours?
🔎1 POU in graphical languages (CFC 1) — line-based checks were NOT applied to them; names are partially covered (occurrences in blocks/expressions). This is a tool limitation, not a sign these POUs are clean.
0
Quality score: 0/100 Save the report to your profile — you'll build a verified quality track. The code is not stored, only the aggregate.
Every rule has a noise? button — click it if the rule gives irrelevant hits on your code. The signal helps us tune the checks: lower the severity, narrow the condition or disable extras. Your code is not sent.
Critical 7 findings
CFC: division by a literal zero1 · cfc-div-zero
In graphical code (CFC/FBD) the divisor of a DIV/MOD block is wired to a literal constant 0 — a runtime exception when the block executes. Checked against the connection graph.
Suggested fixes
Wire a variable/setpoint into the divisor input instead of the literal 0 — usually a debug stub left behind.
If the divisor can go to zero at runtime — pick a safe value before DIV/MOD (a SEL on a compare-with-0).
CfcDiv
— the divisor of the DIV block is wired to a literal 0 → a guaranteed division by zero
Implicit check does not protect1 · broken-implicit-check
An implicit check function (Check…) exists, but its protective logic is gutted: an empty body, or the check (divisor=0 / bounds) removed. The runtime calls it, but it catches nothing — a false sense of safety, more dangerous than its absence.
Suggested fixes
Restore the protective logic: CheckDivReal must test IF divisor = 0 THEN … := 1;
CheckBounds/CheckRange… must clamp the value to lower..upper. Compare with the default CODESYS implementation.
CheckBounds
— no lower/upper bounds comparison — the value is not clamped — the check exists but doesn't work (a false sense of safety)
Division by a literal zero2 · div-by-zero
Division or MOD by a literal zero — a guaranteed runtime exception (division by zero) whenever this line is reached.
Suggested fixes
Remove the division by 0 — probably a typo in the divisor.
If the divisor can be zero dynamically — guard it: IF d <> 0 THEN r := x / d; END_IF
DivZeroVar:1
— division by znever — the variable is 0 (init 0/empty) and never assigned → a guaranteed division by zero
qa := 5 / znever;
Main:2
— division/MOD by a literal zero
d := d / 0;
Global name typo (gvl.*)1 · undeclared-global
A gvl.<name> reference has no declaration in the GVL — almost always a typo; the variable is silently created or the code won't compile.
Suggested fixes
Check the spelling against the GVL declaration (case, underscores, digits).
If the variable is really needed — add it to the GVL with a type and a comment.
Main
— gvl.missing — not in the GVL (likely a typo)
Array index out of bounds1 · array-bounds
A constant index falls outside the declared ARRAY[lo..hi] bounds — reaching this line reads/writes memory that isn't yours. Without active implicit checks the runtime won't intercept it. Checked for one-dimensional arrays with numeric bounds and a literal index.
Suggested fixes
Bring the index into the declared range or widen the array bounds.
Check before access: IF idx >= LO AND idx <= HI THEN arr[idx] … END_IF
Main:1
— arr[5] out of bounds [0..3]
arr[5] := 1;
Infinite loop with no exit1 · infinite-loop
WHILE TRUE / REPEAT … UNTIL FALSE with no EXIT or RETURN inside — the scan never finishes: the task blocks and the watchdog fires (and if the watchdog is off, the controller stalls). In a PLC, long processing is spread across scans, not spun in a loop.
Suggested fixes
Add an exit condition and EXIT, or bound it with an iteration counter.
Split heavy processing across scans — an infinite loop trips the task watchdog.
Main:11
— WHILE TRUE without EXIT/RETURN
Warning 9 findings
Division by a literal zero1 · div-by-zero
Division or MOD by a literal zero — a guaranteed runtime exception (division by zero) whenever this line is reached.
Suggested fixes
Remove the division by 0 — probably a typo in the divisor.
If the divisor can be zero dynamically — guard it: IF d <> 0 THEN r := x / d; END_IF
DivZeroVar:3
— division by zcond — the variable is assigned 0 on some branch and there's no zcond <> 0 guard → a possible division by zero
qb := 5 / zcond;
Integer division (lost fraction?)1 · int-division
Dividing an integer by an integer constant drops the fraction. Use /N.0 or *_TO_REAL if precision matters.
Suggested fixes
Cast to REAL: r := INT_TO_REAL(x) / N.0;
Or divide by a real constant: x / 10.0 instead of x / 10.
Timer with zero delay (PT := T#0)1 · timer-zero-pt
PT is set to zero (T#0S) — the timer fires in the same scan, effectively no delay. Usually a forgotten or wrong time setpoint.
Suggested fixes
Set a non-zero delay: tmr(IN := cond, PT := T#5S);
If no delay is needed — remove the timer and act on the condition directly.
Main:4
— PT := T#0 — the timer fires instantly (no delay)
t1(IN := c, PT := T#0S);
Assignment of a variable to itself1 · self-assignment
A line like `x := x;` — assigning a variable to itself does nothing. Usually a typo on the right-hand side (another variable was meant) or a leftover from deleted logic.
Suggested fixes
The right-hand side probably should be another variable (typo) — check the intent.
If the line is redundant — remove it.
Main:6
— assignment to itself — no effect
x := x;
Empty IF/loop branch1 · empty-branch
An IF/ELSIF/ELSE branch or a loop body is empty (`THEN END_IF`, `DO END_FOR`) — the logic was either never written or cut out during an edit and the construct left behind.
Suggested fixes
Fill the branch with logic or remove the empty IF/loop.
If empty on purpose — leave an explanatory comment.
Main:7
— empty IF … THEN … END_IF
CASE without an ELSE branch (no default)1 · case-without-else
CASE without an ELSE branch: if the selector takes a value not among the labels, nothing runs — a silent skip (a common state-machine bug).
Suggested fixes
Add an ELSE branch with default handling (signal/log/safe state).
If all selector values are truly covered — keep ELSE with an explanatory comment.
Main:8
— CASE without ELSE — unhandled selector values are silently ignored
Check present but not active3 · implicit-check-disabled
A check function exists in the project but is not active: either not registered (no CheckFunction tag — the runtime never calls it), or an entire category (bounds/division/range/pointers) has no active check.
Suggested fixes
Regenerate the POUs for Implicit Checks via the standard CODESYS dialog — that gives the check its CheckFunction registration so the runtime calls it.
Add the missing category (bounds/division/range/pointer) via Add Object → POUs for Implicit Checks.
project
— the runtime doesn't check 'division by zero' — no active check function for this category
project
— the runtime doesn't check 'range overflow' — no active check function for this category
project
— the runtime doesn't check 'pointer dereference' — no active check function for this category
Minor 7 findings
Dead code — POU is never called3 · dead-pou
The POU is never called and never used as a type — dead code or a forgotten name change (check against the spec before deleting).
Suggested fixes
Not needed — delete the POU.
A forgotten version — check the call name against the spec, possibly a typo in the name.
CfcDiv
— CfcDiv — never called and not used as a type
DivZeroVar
— DivZeroVar — never called and not used as a type
Main
— Main — never called and not used as a type
Cryptic variable name4 · naming
Single-character names (other than counters i/j/k/n) are unreadable during maintenance. I/O tags and FB pins are left alone.
Suggested fixes
Give it a meaningful name by purpose (e.g. iRetryCount instead of n).
CheckBounds
— x — single-character name, give it a meaningful one
Main
— d — single-character name, give it a meaningful one
Main
— x — single-character name, give it a meaningful one
Main
— c — single-character name, give it a meaningful one
What the linter does NOT see (needs an eye and a cross-check with the spec): signal polarity, swapped hysteresis (< set+hyst / > set−hyst), copy-paste of someone else's loop. The report is a reason to re-check, not a verdict.
Processing happens in memory — the uploaded code is not stored. The tool advises; code changes are up to the engineer (see what the linter can't see).
· Feedback
· GitHub