AI-Powered Legacy Code Modernization with IBM Bob
Never “trust the AI” — prove the improvement with the same static analyzer, before and after.
tree-sitter baseline, whole codebase
rank by Likelihood × Impact risk
IBM Bob rewrites, scope-constrained
identical analyzer, no moved goalposts
a hard before/after delta, not a vibe
Unguarded allocation sites eliminated across 7 files, verified by the same analyzer before and after.
Same tree-sitter analyzer, same 7 files, run before IBM Bob touched anything and again after.
Swipe to see all columns →
| Metric | Before | After | Change |
|---|---|---|---|
| LOC | 3299 | 3489 | +5.8% |
| Unguarded allocations | 30 | 0 | −100.0% |
| Unpaired allocations | 1 | 0 | −100.0% |
| Cyclomatic complexity | 788 | 893 | +13.3% |
| Raw pointer count | 1229 | 1290 | +5.0% |
| Risk score (v2) | 26.53 | 27.99 | +5.5% |
| Risk score (v3) | 25.72 | 3.89 | −84.9% |
Risk score (v2) includes cyclomatic complexity, which can increase when defensive NULL-checks are added — so it can rise even as the code gets safer. Risk score (v3) is OWASP-style Likelihood × Impact and excludes complexity; read it together with the unguarded/unpaired allocation metrics above for the safety signal.
Not synthetic examples — both are the actual diffs Bob produced while modernizing these files.
res = http_get(json_url);json = res->data;_debug("status: %d", res->status);if (!res || !res->ok) { goto download;}
res = http_get(json_url);if (!res || !res->ok) { goto download;}json = res->data;_debug("status: %d", res->status);
http_get() returns NULL when the request fails, and the
original code dereferenced it one line before the NULL check it already had.
Now the check runs first.
while (read(fd, buf + l, 1)) { if (buf[l] == '\n' && l >= 3) if (strncmp(...) == 0) break; ++l; /* no upper bound */}buf[l] = 0; /* l reaches 0x10000 */
while (read(fd, buf + l, 1)) { if (buf[l] == '\n' && l >= 3) if (strncmp(...) == 0) break; ++l; if (l >= 0xffff) break; /* bound */}buf[l] = 0; /* always in range */
l to 0x10000, and
buf[l] = 0 writes one byte past the heap allocation. The loop
now stops one byte short.
Adding defensive NULL-checks raises cyclomatic complexity. A blended score would punish the fix.
Shin & Williams (2010) found only a weak correlation (r ≈ 0.30) between cyclomatic complexity and real vulnerabilities — a poor proxy for safety, and a terrible one for measuring a safety fix.