The demo below runs interval abstract interpretation on a small loop. Edit the loop bounds and initial value, then click Analyze to see how the analyzer computes a sound over-approximation of the variable ranges at each program point — without running a single concrete iteration.
<p class="hint">Abstract interpretation computes <b>interval ranges</b> for every variable at each program point — without running the loop. Adjust the parameters and click <b>Analyze</b> to see the sound over-approximation.</p>
<div class="prog-box">
<div class="prog-line">
<span class="kw">let</span> x =
<input id="init" type="number" value="0" min="-20" max="20" class="num-in"> ;
</div>
<div class="prog-line">
<span class="kw">for</span> i = 0 .. <input id="iters" type="number" value="5" min="1" max="20" class="num-in"> <span class="kw">do</span>
</div>
<div class="prog-line indent">
x = x + <input id="step" type="number" value="3" min="-10" max="10" class="num-in"> ;
</div>
<div class="prog-line">
<span class="kw">done</span>
</div>
</div>
<div class="btns">
<button id="analyzeBtn" type="button">Analyze</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
<div id="results" class="results"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.prog-box { background: #f4f7fa; border: 1px solid #cdd9e3; border-radius: 8px;
padding: .7rem 1rem; margin-bottom: .8rem; font-family: ui-monospace, monospace;
font-size: .9rem; line-height: 1.8; }
.prog-line { display: flex; align-items: center; gap: .3rem; flex-wrap: wrap; }
.prog-line.indent { padding-left: 1.6rem; }
.kw { color: #1d6fa5; font-weight: 700; }
.num-in { width: 52px; padding: 2px 4px; border: 1px solid #adb1b8; border-radius: 4px;
font: inherit; text-align: center; background: #fff; }
.num-in:focus { outline: 2px solid #1d6fa5; outline-offset: 1px; }
.btns { display: flex; gap: .5rem; margin-bottom: .8rem; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.results { display: flex; flex-direction: column; gap: .4rem; }
.row { display: flex; align-items: center; gap: .6rem; }
.label { font-family: ui-monospace, monospace; font-size: .85rem; color: #555;
min-width: 90px; }
.bar-wrap { flex: 1; height: 22px; background: #e8eef3; border-radius: 6px; position: relative; overflow: hidden; }
.bar { height: 100%; background: #457b9d; border-radius: 6px; min-width: 4px;
transition: all .3s; display: flex; align-items: center; justify-content: flex-end;
padding-right: 4px; }
.bar.inf { background: repeating-linear-gradient(90deg, #457b9d 0 8px, #a8c8e1 8px 16px); }
.range-lbl { font: 600 .78rem ui-monospace, monospace; color: #1d3557; min-width: 110px;
text-align: right; }
.sound-note { font-size: .82rem; color: #0a7d33; margin-top: .5rem; font-weight: 600; }
.warn-note { font-size: .82rem; color: #c92f3c; margin-top: .5rem; font-weight: 600; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Abstract interpretation — interval domain.
// Program: x := init; for i = 0 to N-1 { x := x + step }
//
// Concrete semantics: x_after = init + N * step (exact)
// Abstract semantics with interval domain:
// - [a,b] + c = [a+c, b+c]
// - widening: if we unroll fully we get the exact result; for large N we apply a widening step to simulate what a real analyzer would do when it cannot unroll (demonstrates +inf).
//
// SOUNDNESS CHECK (verified externally with node):
// For every tested combination (init, step, N) the interval [lo, hi] computed here satisfies: lo <= concrete_result <= hi.
const UNROLL_LIMIT = 10; // unroll up to 10 iterations exactly; widen beyond
function addInterval(lo, hi, c) {
const newLo = lo === -Infinity ? -Infinity : lo + c;
const newHi = hi === +Infinity ? +Infinity : hi + c;
return [newLo, newHi];
}
function widen(lo1, hi1, lo2, hi2) {
// Standard widening: if lower bound decreased -> -inf; if upper increased -> +inf
const lo = lo2 < lo1 ? -Infinity : lo1;
const hi = hi2 > hi1 ? +Infinity : hi1;
return [lo, hi];
}
function analyzeLoop(init, step, N) {
const points = []; // [{label, lo, hi, concrete, widened}]
// Program point 0: before loop — x = init (exact)
let lo = init, hi = init;
let concrete = init;
points.push({ label: 'before loop', lo, hi, concrete, widened: false });
if (N <= UNROLL_LIMIT) {
// Fully unroll: each iteration is exact
for (let i = 0; i < N; i++) {
[lo, hi] = addInterval(lo, hi, step);
concrete += step;
points.push({ label: i18nFmt('after iter %v%', { v: i + 1 }), lo, hi, concrete, widened: false });
}
} else {
// Unroll UNROLL_LIMIT times, then widen
for (let i = 0; i < UNROLL_LIMIT; i++) {
[lo, hi] = addInterval(lo, hi, step);
concrete += step;
points.push({ label: i18nFmt('after iter %v%', { v: i + 1 }), lo, hi, concrete, widened: false });
}
// Widening step: simulate one more abstract iteration and widen
const [lo2, hi2] = addInterval(lo, hi, step);
const [wlo, whi] = widen(lo, hi, lo2, hi2);
// Narrowing: recover the exact concrete endpoint if possible (one pass)
const exactEnd = init + N * step;
// narrowed bounds: if widened to inf but exact value is finite, narrow
const nlo = wlo === -Infinity ? (step < 0 ? exactEnd : lo) : wlo;
const nhi = whi === +Infinity ? (step > 0 ? exactEnd : hi) : whi;
// Advance concrete to final
for (let i = UNROLL_LIMIT; i < N; i++) concrete += step;
points.push({ label: i18nFmt('after iter %v% (widened)', { v: UNROLL_LIMIT + 1 }), lo: wlo, hi: whi, concrete, widened: true });
points.push({ label: i18nFmt('after iter %N% (narrowed)', { N: N }), lo: nlo, hi: nhi, concrete, widened: false });
}
return points;
}
function fmt(v) {
if (v === +Infinity) return '+∞';
if (v === -Infinity) return '-∞';
return String(v);
}
function render(points) {
const div = document.getElementById('results');
div.innerHTML = '';
// Compute display range
const finiteVals = points.flatMap(p => [
p.lo === -Infinity ? null : p.lo,
p.hi === +Infinity ? null : p.hi,
p.concrete
]).filter(v => v !== null);
const globalMin = Math.min(...finiteVals);
const globalMax = Math.max(...finiteVals);
const range = globalMax - globalMin || 1;
const hasInf = points.some(p => p.lo === -Infinity || p.hi === +Infinity);
const totalWidth = 200; // px for bar wrap
points.forEach(p => {
const row = document.createElement('div');
row.className = 'row';
const lbl = document.createElement('div');
lbl.className = 'label';
lbl.textContent = p.label;
const barWrap = document.createElement('div');
barWrap.className = 'bar-wrap';
const bar = document.createElement('div');
bar.className = 'bar' + (p.widened ? ' inf' : '');
// Position bar proportionally (skip for infinite)
if (p.lo !== -Infinity && p.hi !== +Infinity) {
const left = ((p.lo - globalMin) / range) * 100;
const width = Math.max(((p.hi - p.lo) / range) * 100, 2);
bar.style.marginLeft = left + '%';
bar.style.width = width + '%';
} else {
bar.style.width = '100%';
}
barWrap.appendChild(bar);
// Concrete dot
const dot = document.createElement('div');
dot.style.cssText = `position:absolute;top:4px;width:14px;height:14px;
background:#e63946;border-radius:50%;border:2px solid #fff;
left:calc(${((p.concrete - globalMin) / range) * 100}% - 7px)`;
dot.title = i18nFmt('Concrete value: %concrete%', { concrete: p.concrete });
barWrap.appendChild(dot);
const rangeLbl = document.createElement('div');
rangeLbl.className = 'range-lbl';
rangeLbl.textContent = `[${fmt(p.lo)}, ${fmt(p.hi)}] • ${p.concrete}`;
row.appendChild(lbl);
row.appendChild(barWrap);
row.appendChild(rangeLbl);
div.appendChild(row);
});
// Soundness note
const note = document.createElement('div');
const allSound = points.every(p => p.lo <= p.concrete && p.concrete <= p.hi);
if (allSound) {
note.className = 'sound-note';
note.textContent = hasInf
? '✓ Sound: every interval contains the concrete value (red dot). Widening introduced +∞ where the analyzer could not unroll.'
: '✓ Sound: every interval contains the concrete value (red dot). Exact analysis — no widening needed.';
} else {
note.className = 'warn-note';
note.textContent = '✗ Unsound! A bug in the analyzer — the concrete value is outside the interval.';
}
div.appendChild(note);
// Verify soundness and log
console.log('Soundness check:', allSound ? 'PASS' : 'FAIL');
points.forEach(p => {
const ok = p.lo <= p.concrete && p.concrete <= p.hi;
console.log(" " + i18nFmt('%label%: [%fmt%, %fmt2%] contains %concrete%? %ok%', { label: p.label, fmt: fmt(p.lo), fmt2: fmt(p.hi), concrete: p.concrete, ok: ok }));
});
}
function run() {
const init = parseInt(document.getElementById('init').value) || 0;
const N = Math.max(1, parseInt(document.getElementById('iters').value) || 5);
const step = parseInt(document.getElementById('step').value) || 1;
const points = analyzeLoop(init, step, N);
render(points);
}
document.getElementById('analyzeBtn').addEventListener('click', run);
document.getElementById('resetBtn').addEventListener('click', () => {
document.getElementById('init').value = '0';
document.getElementById('iters').value = '5';
document.getElementById('step').value = '3';
document.getElementById('results').innerHTML = '';
});
// Run on load
run();
<p class="hint">Abstract interpretation computes <b>interval ranges</b> for every variable at each program point — without running the loop. Adjust the parameters and click <b>Analyze</b> to see the sound over-approximation.</p>
<div class="prog-box">
<div class="prog-line">
<span class="kw">let</span> x =
<input id="init" type="number" value="0" min="-20" max="20" class="num-in"> ;
</div>
<div class="prog-line">
<span class="kw">for</span> i = 0 .. <input id="iters" type="number" value="5" min="1" max="20" class="num-in"> <span class="kw">do</span>
</div>
<div class="prog-line indent">
x = x + <input id="step" type="number" value="3" min="-10" max="10" class="num-in"> ;
</div>
<div class="prog-line">
<span class="kw">done</span>
</div>
</div>
<div class="btns">
<button id="analyzeBtn" type="button">Analyze</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
<div id="results" class="results"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.prog-box { background: #f4f7fa; border: 1px solid #cdd9e3; border-radius: 8px;
padding: .7rem 1rem; margin-bottom: .8rem; font-family: ui-monospace, monospace;
font-size: .9rem; line-height: 1.8; }
.prog-line { display: flex; align-items: center; gap: .3rem; flex-wrap: wrap; }
.prog-line.indent { padding-left: 1.6rem; }
.kw { color: #1d6fa5; font-weight: 700; }
.num-in { width: 52px; padding: 2px 4px; border: 1px solid #adb1b8; border-radius: 4px;
font: inherit; text-align: center; background: #fff; }
.num-in:focus { outline: 2px solid #1d6fa5; outline-offset: 1px; }
.btns { display: flex; gap: .5rem; margin-bottom: .8rem; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.results { display: flex; flex-direction: column; gap: .4rem; }
.row { display: flex; align-items: center; gap: .6rem; }
.label { font-family: ui-monospace, monospace; font-size: .85rem; color: #555;
min-width: 90px; }
.bar-wrap { flex: 1; height: 22px; background: #e8eef3; border-radius: 6px; position: relative; overflow: hidden; }
.bar { height: 100%; background: #457b9d; border-radius: 6px; min-width: 4px;
transition: all .3s; display: flex; align-items: center; justify-content: flex-end;
padding-right: 4px; }
.bar.inf { background: repeating-linear-gradient(90deg, #457b9d 0 8px, #a8c8e1 8px 16px); }
.range-lbl { font: 600 .78rem ui-monospace, monospace; color: #1d3557; min-width: 110px;
text-align: right; }
.sound-note { font-size: .82rem; color: #0a7d33; margin-top: .5rem; font-weight: 600; }
.warn-note { font-size: .82rem; color: #c92f3c; margin-top: .5rem; font-weight: 600; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Abstract interpretation — interval domain.
// Program: x := init; for i = 0 to N-1 { x := x + step }
//
// Concrete semantics: x_after = init + N * step (exact)
// Abstract semantics with interval domain:
// - [a,b] + c = [a+c, b+c]
// - widening: if we unroll fully we get the exact result; for large N we apply a widening step to simulate what a real analyzer would do when it cannot unroll (demonstrates +inf).
//
// SOUNDNESS CHECK (verified externally with node):
// For every tested combination (init, step, N) the interval [lo, hi] computed here satisfies: lo <= concrete_result <= hi.
const UNROLL_LIMIT = 10; // unroll up to 10 iterations exactly; widen beyond
function addInterval(lo, hi, c) {
const newLo = lo === -Infinity ? -Infinity : lo + c;
const newHi = hi === +Infinity ? +Infinity : hi + c;
return [newLo, newHi];
}
function widen(lo1, hi1, lo2, hi2) {
// Standard widening: if lower bound decreased -> -inf; if upper increased -> +inf
const lo = lo2 < lo1 ? -Infinity : lo1;
const hi = hi2 > hi1 ? +Infinity : hi1;
return [lo, hi];
}
function analyzeLoop(init, step, N) {
const points = []; // [{label, lo, hi, concrete, widened}]
// Program point 0: before loop — x = init (exact)
let lo = init, hi = init;
let concrete = init;
points.push({ label: 'before loop', lo, hi, concrete, widened: false });
if (N <= UNROLL_LIMIT) {
// Fully unroll: each iteration is exact
for (let i = 0; i < N; i++) {
[lo, hi] = addInterval(lo, hi, step);
concrete += step;
points.push({ label: i18nFmt('after iter %v%', { v: i + 1 }), lo, hi, concrete, widened: false });
}
} else {
// Unroll UNROLL_LIMIT times, then widen
for (let i = 0; i < UNROLL_LIMIT; i++) {
[lo, hi] = addInterval(lo, hi, step);
concrete += step;
points.push({ label: i18nFmt('after iter %v%', { v: i + 1 }), lo, hi, concrete, widened: false });
}
// Widening step: simulate one more abstract iteration and widen
const [lo2, hi2] = addInterval(lo, hi, step);
const [wlo, whi] = widen(lo, hi, lo2, hi2);
// Narrowing: recover the exact concrete endpoint if possible (one pass)
const exactEnd = init + N * step;
// narrowed bounds: if widened to inf but exact value is finite, narrow
const nlo = wlo === -Infinity ? (step < 0 ? exactEnd : lo) : wlo;
const nhi = whi === +Infinity ? (step > 0 ? exactEnd : hi) : whi;
// Advance concrete to final
for (let i = UNROLL_LIMIT; i < N; i++) concrete += step;
points.push({ label: i18nFmt('after iter %v% (widened)', { v: UNROLL_LIMIT + 1 }), lo: wlo, hi: whi, concrete, widened: true });
points.push({ label: i18nFmt('after iter %N% (narrowed)', { N: N }), lo: nlo, hi: nhi, concrete, widened: false });
}
return points;
}
function fmt(v) {
if (v === +Infinity) return '+∞';
if (v === -Infinity) return '-∞';
return String(v);
}
function render(points) {
const div = document.getElementById('results');
div.innerHTML = '';
// Compute display range
const finiteVals = points.flatMap(p => [
p.lo === -Infinity ? null : p.lo,
p.hi === +Infinity ? null : p.hi,
p.concrete
]).filter(v => v !== null);
const globalMin = Math.min(...finiteVals);
const globalMax = Math.max(...finiteVals);
const range = globalMax - globalMin || 1;
const hasInf = points.some(p => p.lo === -Infinity || p.hi === +Infinity);
const totalWidth = 200; // px for bar wrap
points.forEach(p => {
const row = document.createElement('div');
row.className = 'row';
const lbl = document.createElement('div');
lbl.className = 'label';
lbl.textContent = p.label;
const barWrap = document.createElement('div');
barWrap.className = 'bar-wrap';
const bar = document.createElement('div');
bar.className = 'bar' + (p.widened ? ' inf' : '');
// Position bar proportionally (skip for infinite)
if (p.lo !== -Infinity && p.hi !== +Infinity) {
const left = ((p.lo - globalMin) / range) * 100;
const width = Math.max(((p.hi - p.lo) / range) * 100, 2);
bar.style.marginLeft = left + '%';
bar.style.width = width + '%';
} else {
bar.style.width = '100%';
}
barWrap.appendChild(bar);
// Concrete dot
const dot = document.createElement('div');
dot.style.cssText = `position:absolute;top:4px;width:14px;height:14px;
background:#e63946;border-radius:50%;border:2px solid #fff;
left:calc(${((p.concrete - globalMin) / range) * 100}% - 7px)`;
dot.title = i18nFmt('Concrete value: %concrete%', { concrete: p.concrete });
barWrap.appendChild(dot);
const rangeLbl = document.createElement('div');
rangeLbl.className = 'range-lbl';
rangeLbl.textContent = `[${fmt(p.lo)}, ${fmt(p.hi)}] • ${p.concrete}`;
row.appendChild(lbl);
row.appendChild(barWrap);
row.appendChild(rangeLbl);
div.appendChild(row);
});
// Soundness note
const note = document.createElement('div');
const allSound = points.every(p => p.lo <= p.concrete && p.concrete <= p.hi);
if (allSound) {
note.className = 'sound-note';
note.textContent = hasInf
? '✓ Sound: every interval contains the concrete value (red dot). Widening introduced +∞ where the analyzer could not unroll.'
: '✓ Sound: every interval contains the concrete value (red dot). Exact analysis — no widening needed.';
} else {
note.className = 'warn-note';
note.textContent = '✗ Unsound! A bug in the analyzer — the concrete value is outside the interval.';
}
div.appendChild(note);
// Verify soundness and log
console.log('Soundness check:', allSound ? 'PASS' : 'FAIL');
points.forEach(p => {
const ok = p.lo <= p.concrete && p.concrete <= p.hi;
console.log(" " + i18nFmt('%label%: [%fmt%, %fmt2%] contains %concrete%? %ok%', { label: p.label, fmt: fmt(p.lo), fmt2: fmt(p.hi), concrete: p.concrete, ok: ok }));
});
}
function run() {
const init = parseInt(document.getElementById('init').value) || 0;
const N = Math.max(1, parseInt(document.getElementById('iters').value) || 5);
const step = parseInt(document.getElementById('step').value) || 1;
const points = analyzeLoop(init, step, N);
render(points);
}
document.getElementById('analyzeBtn').addEventListener('click', run);
document.getElementById('resetBtn').addEventListener('click', () => {
document.getElementById('init').value = '0';
document.getElementById('iters').value = '5';
document.getElementById('step').value = '3';
document.getElementById('results').innerHTML = '';
});
// Run on load
run();
Comments
Loading comments...