Bounded Static Analysis

Everything Everywhere All at Once

Table of Contents

  1. What is Bounded Static Analysis?
  2. Abstractions
  3. Bounded Abstract Interpretation

Questions

  1. Why should we use abstractions when analyzing code?

  2. What is the advantage of Galois connections?

  3. Which cases can the Sign abstraction not catch?

What is Bounded Static Analysis? (Β§1)

Goal and Notation (Β§1.1)

p,nβŠ’ππ’π€π—†π—Žπ—Œπ—Q⟹p,nβŠ¨π—†π—Žπ—Œπ—Q
Β¬(p,nβŠ’ππ’π€Β¬π—†π–Ίπ—’Β¬Q)≑p,nβŠ’ππ’π€π—†π—Žπ—Œπ—Q
p,nβŠ¨π—†π–Ίπ—’Q⟹p,nβŠ’ππ’π€Β¬π—†π–Ίπ—’Β¬Q
𝚊𝚎(Ο„)≑τ|Ο„|βˆ’1=err(β€˜πšŠπšœπšœπšŽπš›πšπš’πš˜πš— πšŽπš›πš›πš˜πš›β€™)
p,nβŠ¨π—†π—Žπ—Œπ—Β¬πšŠπšŽβ‰‘βˆ€Ο„βˆˆSemn(p).¬𝚊𝚎(Ο„)
Β¬(p,nβŠ’ππ’π€Β¬π—†π–Ίπ—’πšŠπšŽ)≑p,nβŠ’ππ’π€π—†π—Žπ—Œπ—Β¬πšŠπšŽ

The Many Step Function (Β§1.2)

Breath-first!

Ξ”p(T)={Ο„β€²sΒ |Β s∈Σ,Ο„β€²βˆˆT,Ξ΄p(Ο„β€²|Ο„β€²|,s)}
Ξ”p0=IpΞ”pn=Ξ”p(Ξ”pnβˆ’1)βˆͺΞ”pnβˆ’1
Ξ”π—†π—Žπ—Œπ—(T)βŠ†Ξ”(T)βŠ†Ξ”π—†π–Ίπ—’(T)
Ξ”π—†π—Žπ—Œπ—,nβŠ†Ξ”nβŠ†Ξ”π—†π–Ίπ—’,n
p,nβŠ’ππ’π€Β¬π—†π–Ίπ—’Qβ‰‘βˆƒΟ„βˆˆΞ”π—†π–Ίπ—’,n.Q(Ο„)
p,nβŠ’ππ’π€Β¬π—†π—Žπ—Œπ—Qβ‰‘βˆ€Ο„βˆˆΞ”π—†π—Žπ—Œπ—,n.Q(Ο„)

Abstractions (Β§2)

  1. The Sign Analysis
  2. Partially Ordered Sets (Posets)
  3. Lattices
  4. Galois Connection
  5. Abstract Operations

The Sign Analysis (Β§2.1)

𝐒𝐒𝐠𝐧={+,0,βˆ’}

Ξ± is the abstractor

Ξ±({1,2,3})={+}Ξ±({0})={0}Ξ±({βˆ’1,3})={+,βˆ’}Ξ±({βˆ’1,0,3})={0,+,βˆ’}

Partially Ordered Sets (Posets) (Β§2.2)

A partially ordered set (L,βŠ‘)

reflexiveβˆ€a∈L.aβŠ‘aantisymmetricβˆ€a,b∈L.aβŠ‘b∧bβŠ‘a⟹a=btransitiveβˆ€a,b,c∈L.aβŠ‘b∧bβŠ‘c⟹aβŠ‘c

Examples

  • Integers in both directions (β„€,β‰₯) (β„€,≀)

  • Sets ({𝚝𝚝,𝚏𝚏},β‡’)

  • Signed (2𝐒𝐒𝐠𝐧,βŠ†)

class Poset(ABC):
    @abstractmethod
    def __eq__(self, other: Self) -> bool: ...

    @abstractmethod
    def __lt__(self, other: Self) -> bool: ...

    def __le__(self, other: Self) -> bool:
        return self == other or self < other

    @abstractmethod
    def __gt__(self, other: Self) -> bool: ...

    def __ge__(self, other: Self) -> bool:
        return self == other or self > other
def is_reflexive(a: Poset):
    assert a == a

def is_antisymmetric(a: Poset, b: Poset):
    if a != b:
        assert not (a <= b <= a)

def is_transitive(a: Poset, b: Poset, c: Poset):
    if a <= b <= c:
        assert a <= c

@given(st_signset(), st_signset(), st_signset())
def test_signset_is_poset(a: ab.SignSet, b: ab.SignSet, c: ab.SignSet):
    ...
    is_transitive(a, b, c)

Why Not Total Orders?

Lattices (Β§2.3)

  • Partial orders (L,βŠ‘L),

  • meet βŠ“,

  • join βŠ”,

  • and has a smallest and biggest element.

Hasse diagrams. (Β§2.3.1)

Hasse diagram phi Ø x->phi x {+} y->phi y {0} z->phi z {-} xy->x xy->y xy {+, 0} xz->x xz->z xz {+, -} yz {0, -} yz->y yz->z xyz {+,0,-} xyz->yz xyz->xz xyz->xy

A Hasse diagram over the poset (2𝐒𝐒𝐠𝐧,βŠ†). By I, KSmrq, CC BY-SA 3.0 (with edits)

Back to the Sign Abstraction (Β§2.3.2)

βŠ‘π’π’π π§=βŠ†, βŠ₯=βˆ…, and ⊀={+,βˆ’,0}

Galois Connection (Β§2.4)

βˆ€a∈C,b∈A.aβŠ‘CΞ³(b)⇔α(a)βŠ‘Ab

(A Galois Connection is a connection between two ordered sets, with a concretion Ξ³ and an abstraction Ξ± function.)

What are they good for?

  • A mathematical definition of one-sided information loss. If my abstraction finds a bug, is there a bug in the original domain?

  • A weak equality.

Laws For Free

cβŠ‘CΞ³(Ξ±(c))– if we abstract we only increase in sizeΞ±(Ξ³(a))βŠ‘Aa– if we concretize we only decrease in sizeΞ±(Ξ³(Ξ±(c)))=Ξ±(c)– we don’t keep losing informationΞ³(Ξ±(Ξ³(a)))=Ξ³(a)– in both directionsΞ³(a1βŠ”Aa2)=Ξ³(a1)βŠ”CΞ³(a2)– Ξ³ preserves joins.Ξ±(c1βŠ“Cc2)=Ξ±(c1)βŠ“AΞ±(c2)– Ξ± preserves meets.

Adjunctions and Testing

# Most of the time
parse(pretty(a)) == a 

# Always
pretty(parse(pretty(a))) == pretty(a)

# Bonus for all strings s:
parse(pretty(parse(s))) == parse(s)

Back to the Sign Analysis (Β§2.4.1)

Ξ±(N)={+Β |Β n∈N,n<0}βˆͺ{βˆ’Β |Β n∈N,n>0}βˆͺ{0Β |Β n∈N,n=0}Ξ³(S)={nΒ |Β nβˆˆβ„€,n<0,+∈S}βˆͺ{nΒ |Β nβˆˆβ„€,n>0,βˆ’βˆˆS}βˆͺ{0Β |Β 0∈S}

Build the Sign Abstraction

def to_sign(value: jvms.StackValue) -> Sign:
    match value:
        case jvms.StackInt(v):
            return (v > 0) - (v < 0)
        case a:
            raise NotImplementedError(f"Unsupported value {value!r}")

class SignSet:
  ...
  @classmethod
  def abstract(cls, items : Iterable[jvms.StackValue]): 
    signs = set()
    for value in values:
        signs.add(to_sign(value))
    return cls(frozenset(signs))
def __contains__(self, value : jvms.StackValue): 
    return to_sign(value) in self.signs

Test your abstaction

βˆ€XβŠ‚2β„€:XβŠ†Ξ³(Ξ±(X))

see solutions/static/tests/test_abstractions.py

Abstract Operations (Β§2.5)

A+232BβŠ†Ξ³(Ξ±(A)+𝐒𝐒𝐠𝐧α(B))

Ξ±(A+232B)βŠ†Ξ±(A)+𝐒𝐒𝐠𝐧α(B)

Define the operation

{+}+𝐒𝐒𝐠𝐧{+}={+}{+}+𝐒𝐒𝐠𝐧{βˆ’}={βˆ’,+,0}{+}+𝐒𝐒𝐠𝐧{0}={+}{0}+𝐒𝐒𝐠𝐧{+}={+}{0}+𝐒𝐒𝐠𝐧{βˆ’}={βˆ’}{0}+𝐒𝐒𝐠𝐧{0}={0}{βˆ’}+𝐒𝐒𝐠𝐧{+}={βˆ’,+,0}{βˆ’}+𝐒𝐒𝐠𝐧{βˆ’}={βˆ’}{βˆ’}+𝐒𝐒𝐠𝐧{0}={βˆ’}…
match opr:
    case jvm.BinaryOpr.Add:
        output = set()
        if 1 in self.signs:
            output.add(1)
            if -1 in other.signs:
                output.update([0, -1])

        if -1 in self.signs:
            output.add(-1)
            if 1 in other.signs:
                output.update([0, 1])

        if 0 in self.signs:
            output.update(other.signs)

        return (SignSet(output), set())

Bounded Abstract Interpretation (Β§3)

(2π“π«πšπœπž,βŠ†)↔γα(A,βŠ‘A)

Ξ”A0=Ξ±(IP)Ξ”An=Ξ”A(Ξ”Anβˆ’1)βŠ”AΞ”Anβˆ’1

To show Ξ”nβŠ†Ξ³(Ξ”An), we need to prove:

Ξ”(T)βˆͺTβŠ†Ξ³(Ξ”A(Ξ±(T))βŠ”AΞ±(T)).

But we can get away with

Ξ±(Ξ”(T))βŠ‘AΞ”A(Ξ±(T))

The State Abstraction (Β§3.1)

(2π“π«πšπœπž,βŠ†)↔γα(2π’π­πšπ­πž,βŠ†)
Ξ±(T)={Ο„|Ο„|βˆ’1Β |Β Ο„βˆˆT}Ξ³(S)={τ |Β s∈S,Ο„βˆˆπ“π«πšπœπž,Ο„|Ο„|βˆ’1=s}
Ξ”π’π­πšπ­πž(S)={sβ€²Β |Β s∈S,Ξ΄(s,sβ€²)}

This is nice because

  • if err(β€˜πšŠπšœπšœπšŽπš›πšπš’πš˜πš— πšŽπš›πš›πš˜πš›β€™)βˆ‰Ξ”π’π­πšπ­πžn, then

  • Β¬βˆƒΟ„.(Ο„err(β€˜πšŠπšœπšœπšŽπš›πšπš’πš˜πš— πšŽπš›πš›πš˜πš›β€™))βˆˆΞ”n

The Per-Instruction Abstraction (Β§3.2)

𝐏𝐜=ΞΉβ†’2π’π­πšπ­πž
(2π’π­πšπ­πž,βŠ†)↔γα(𝐏𝐜,βŠ‘ππœ)
Ξ”ππœ(C)=β¨†ππœ{ι′↦{βŸ¨Οƒβ€²,Ξ»β€²,ΞΉβ€²βŸ©}Β |Β (ι↦S)∈C,Sβ€²=Δι(S),βŸ¨Οƒβ€²,Ξ»β€²,ΞΉβ€²βŸ©βˆˆSβ€²}

The Per-Variable Abstraction (Β§3.3)

𝐏𝐯=ΞΉβ†’(ℕ↦(2𝐕σ))Γ—(2𝐕σ)*
(𝐏𝐜,βŠ‘ππœ)↔γα(𝐏𝐯,βŠ‘ππ―)

Variable Abstractions (Β§3.4)

𝐏𝐯[A]=ΞΉβ†’(ℕ↦A)Γ—A*
(2𝐕σ,βŠ†)↔γα(A,βŠ‘A)⟹(𝐏𝐯,βŠ‘ππ―)↔γα(𝐏𝐯[A],βŠ‘ππ―[A])
(𝐏𝐯,βŠ‘ππ―)↔γα(𝐏𝐯[𝐒𝐒𝐠𝐧],βŠ‘ππ―[𝐒𝐒𝐠𝐧])

Abstractions All The Way Down. (Β§3.5)

(2π“π«πšπœπž,βŠ†)↔γα(2π’π­πšπ­πž,βŠ†)↔γα(𝐏𝐜,βŠ‘ππœ)↔γα(𝐏𝐯,βŠ‘ππ―)↔γα(𝐏𝐯[𝐒𝐒𝐠𝐧],βŠ‘ππ―[𝐒𝐒𝐠𝐧])

Getting Started

  • Abstract Your Interpreter

  • Emit multiple states.

  • Start Small, Just One frame.

Questions

  1. Why should we use abstractions when analyzing code?

  2. What is the advantage of Galois connections?

  3. Which cases can the Sign abstraction not catch?