\documentclass[11pt]{article}
\usepackage{ifthen}
\usepackage{alltt}
% \usepackage{indentfirst}
\usepackage{latexsym}
\usepackage[pdftex]{graphicx}
\usepackage{gdefs}
% \usepackage{logdef}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{setspace}
\usepackage{rotating}
\usepackage{longtable}
\usepackage{hhline}
\usepackage{refs}
\usepackage{graph}
\usepackage{changebar}
\usepackage{array}
\usepackage{wrapfig}
\usepackage[small,compact]{titlesec}
\usepackage{listings}
\usepackage{qtree}
\usepackage{color}
\usepackage{tweaklist}
% this sets the spacings in the itemize environment
\renewcommand{\itemhook}{\setlength{\topsep}{0pt}%
  \setlength{\itemsep}{0pt}%
  \setlength{\parsep}{0ex}%
}

% this sets the spacings in the enumerate environment
\renewcommand{\enumhook}{\setlength{\topsep}{0pt}%
  \setlength{\itemsep}{0pt}%
  \setlength{\parsep}{0ex}%
}

% this sets the spacings in the description environment
\renewcommand{\descripthook}{\setlength{\topsep}{0pt}%
  \setlength{\itemsep}{0pt}%
  \setlength{\parsep}{0ex}%
}

\setlength{\topmargin}{0.0in}
\setlength{\headheight}{0.0in}
\setlength{\headsep}{0.0in}
\setlength{\textwidth}{6.5in}
\setlength{\oddsidemargin}{0.0in}
\setlength{\evensidemargin}{0.0in}
\setlength{\textheight}{9.0in}

\setcounter{topnumber}{5}
\def\topfraction{0.99}
\def\textfraction{0.05}
\def\floatpagefraction{0.9}
\setcounter{bottomnumber}{5}
\def\bottomfraction{0.99}
\setcounter{totalnumber}{10}
\def\dbltopfraction{0.99}
\def\dblfloatpagefraction{0.8}
\setcounter{dbltopnumber}{5}

\setlength{\marginparwidth}{.95in}
\let\oldmarginpar\marginpar
\renewcommand\marginpar[1]{\-\oldmarginpar[\raggedleft\footnotesize #1]%
{\raggedright\footnotesize #1}}

\let\oldthebibliography=\thebibliography
  \let\endoldthebibliography=\endthebibliography
  \renewenvironment{thebibliography}[1]{%
    \begin{oldthebibliography}{#1}%
      \setlength{\parskip}{0ex}%
      \setlength{\itemsep}{0ex}%
  }%
  {%
    \end{oldthebibliography}%
  }

\newcommand{\pp}{\textit{pp}}
\newcommand{\store}{\textit{store}}

\title{Static Program Analysis \\
       CS701 \\
}

\author{Thomas Reps \\ \small[Based on notes taken by Aditya Venkataraman on Oct 6th, 2015]}
\date{}

\begin{document}           % End of preamble and beginning of text.

\maketitle
\begin{abstract}
This lecture introduces the area of static program analysis.
We introduce the topics to be reviewed in this unit, and
say a bit about abstract interpretation.
\end{abstract}

\section{Introduction to Static Program Analysis}
Static program analysis is known by various terms, including
static analysis, dataflow analysis, abstract interpretation,
state-space exploration, model checking, and static bug finding.
All of these terms are rough synonyms.

\subsection{Brief Background}
Static analysis tries to answer questions about a program's
behavior without running the program on specific inputs.
Many questions can be of interest, including
\begin{itemize}
  \item
    Can variable $x$ equal value $v$ at label $L$?
  \item
    Must variable $x$ equal value $v$ at label $L$?
  \item
    What are the upper bounds on the value of variable $x$ at label $L$?
  \item
    Is $x$ initialized before it is first used?
  \item
    What is the sign of $x$ at $L$?
  \item
    What is the parity of $x$ at $L$?
  \item
    Can there be any overflows in the computation performed at $L$?
  \item
    Can the value of $x$ be read before it is overwritten (during
    execution actions performed after reaching $L$)?
  \item
    Does $x = y$ hold at $L$?
  \item
    The questions answered by BTA/taint-analysis
  \item
    Can pointer $p$ be NULL at label $L$?
  \item
    What variable(s)/heap-blocks can pointer $p$ point to at $L$?
  \item
    What program points could have assigned to $x$ the value read from $x$ at $L$?
  \item
    Do pointers $p$ and $q$ point to disjoint heap-allocated data structures at $L$?
\end{itemize}

Traditionally static analysis was done to optimize program
performance.
However, with improving hardware predictors, static
analysis yielded diminishing returns for increasing performance.
More recently, say after 1998, static analysis has pivoted toward
software engineering, program verification, and security where it
can provide information about possible bugs and security vulnerabilities.

\subsection{A Difficulty: Undecidability of Static Program Analysis}

Unfortunately, all static-program-analysis problems of interest
are undecidable.
We can often prove such results by a reduction from the halting
problem.
For example, imagine a program analyzer \textit{DecideConstant}
that can decide the problem of testing the following property:
\begin{quote}
  Given program $P$, is it the case that on all runs of $P$, variable
  $x$ has the same value at the end of execution?
\end{quote}
We will depict \textit{DecideConstant} as follows:

\begin{center}
  \includegraphics[width=3.5cm]{figures/decide-constant.pdf}
\end{center}

\noindent
We now show that if \textit{DecideConstant} exists, we can use it
to create an algorithm that decides whether a given Turing machine
halts when run on blank tape---which is known to be impossible.

Consider what happens if $P$ is the following program, where
we would like to know whether Turing machine $M$ halts:
\begin{lstlisting}
	x = 0;
	r = rand;
	run M on blank tape for r steps
	if (M reached its final state) x = 1;
\end{lstlisting}
(Without loss of generality, we are assuming that $M$'s initial state
is not a final state.)

As depicted below, if we feed this program $P$ into \textit{DecideConstant}
and get back ``Y,'' then we know that no matter what the value of $r$ is,
when $P$ finishes $x$ has the value $0$.
In other words, no matter how many steps $M$ runs, it can never reach
its final state, and hence it never halts on blank tape.
Thus, the algorithm can return ``N,'' meaning ``$M$ never halts on blank tape.''
If \textit{DecideConstant} returns ``N,'' then sometimes (the constructed)
$P$ finishes with $x = 0$ and sometimes with $x = 1$, and hence after
some number of steps $M$ halts on blank tape, so the algorithm can return ``Y,''
meaning ``$M$ halts on blank tape.''
Such an algorithm is depicted below:
\begin{center}
  \includegraphics[width=13cm]{figures/decide-halting.pdf}
\end{center}
This construction contradicts the known fact that the question of
whether a given Turing-machine halts on blank tape is undecidable,
which means that our putative analyzer \textit{DecideConstant} cannot
exist.

To argue the undecidability of other static-analysis problems, one
either uses a similar argument (i.e., reduction from the halting
problem), or in some cases, reduction from Post's Correspondence
Problem.
For examples of the latter, see
\cite{TOPLAS:Ramalingam94,TOPLAS:Reps00,TOPLAS:Ramalingam00}.

\subsection{Sidestepping Undecidability: One-Sided Answers}

One might think that the undecidability issue would put the kibosh
on the whole idea of static program analysis.
However, the field of static program analysis has an interesting
approach to sidestepping the undecidability issue, namely, to
only build analyzers that return ``one-sided'' answers.

At first glance, the one-sided nature of the answers may be
concealed, because typically an analyzer returns a Boolean result,
TRUE or FALSE, which doesn't sound ``one-sided.''
There are really two kinds of such analyzers:
\begin{enumerate}
  \item
    One kind behaves as follows: whenever TRUE is returned, the property is true;
    however, when FALSE is returned, the property can be either false or true.
  \item
    Another kind behaves as follows: whenever FALSE is returned, the property is false;
    however, when TRUE is returned the property can be either false or true.
\end{enumerate}
The description of the analyzer should make it clear which of TRUE
or FALSE is a definite answer (and which is the indefinite answer.

Alternatively---and much less common---an analyzer can return one of
three values: TRUE, FALSE, or UNKNOWN \cite{TOPLAS:SRW02}.
\begin{itemize}
  \item
    Whenever TRUE is returned, the property is true.
  \item
    Whenever FALSE is returned, the property is false.
  \item
    Whenever UNKNOWN is returned, the property can be either true or false.
\end{itemize}

\subsection{State-Space Exploration}

\begin{figure*}
\centering
\begin{tabular}{cc}
  \includegraphics[width=7.5cm]{figures/separability-goal.pdf}
  &
  \includegraphics[width=7.5cm]{figures/analysis-success.pdf}
  \\
  (a) & (b)
  \\
  \\
  \includegraphics[width=7.5cm]{figures/false-positive.pdf}
  &
  \includegraphics[width=7.5cm]{figures/true-positive.pdf}
  \\
  (c) & (d)
\end{tabular}
\caption{{\small Abstract depiction of how a static program analyzer carries
  out state-space exploration.
  (a) For a correct program, there is a separation between the set of
      reachable states and the set of bad states.
  (b) A correct program, with an analysis that is capable of demonstrating
      a separation between the set of \emph{apparently} reachable states
      and the set of bad states.
  (c) The analysis is too imprecise; an overlap between the set of
      apparently reachable states and the set of bad states would be reported,
      which is actually a false positive because there is no overlap between
      the set of reachable states and the set of bad states.
  (d) Another case in which a overlap is reported. In this case, there is
      an overlap between the reachable states and the set of bad states.
  Note that a client of the analysis would not know whether an
  overlap report falls into case (c) or case (d).
}}
\label{Fi:StateSpaceExploration}
\end{figure*}

The computational state of a program can be represented as a pair
$(\pp, \store)$, where $\pp$ is a program point (i.e., label) and
$store$ is a map from variables to their values.
The universe of states for a given program is the Cartesian product,
$\{ \pp \} \times \{ \store \}$.
A subset of this universe is the reachable states of the program.
(Note that, in general, this subset cannot be identified exactly,
otherwise we could solve the Halting Problem.)

Static analysis does not try to find the exact reachable states of the
program.
Instead, it tries to find an \emph{over-approximation} (superset) of the
reachable states and, we hope, prove that there exists a separation between
this over-approximation and a given set of bad states.
However, if the analysis over-approximates too much, the analyzer
loses too much accuracy and we may not be able to prove the separation.

\subsection {Topics to be covered}

\begin{itemize}
  \item
    Abstract Interpretation
  \item
    Meanings of descriptors
  \item
    Over-approximations
  \item
    Dataflow analysis
  \item
    Intra-procedural Analysis
  \item
    Inter-procedural Analysis
  \item
    Call-strings
    \begin{itemize}
      \item
        approximations of stack configurations, using strings of bounded length
    \end{itemize}
  \item
    Function summaries
    \begin{itemize}
      \item
        no approximation with respect to the stack
      \item
        in effect, call-strings with unbounded-length strings (``call-strings $\infty$'') 
    \end{itemize}
  \item
    Graph reachability
    \begin{itemize}
      \item
         related to function summaries
    \end{itemize}
  \item
    Pushdown Systems
    \begin{itemize}
      \item
        related to both call strings and function summaries
      \item
        no approximation with respect to the stack
    \end{itemize}
  \item
    Weighted Pushdown Systems
\end{itemize}

\section{Abstract Interpretation}

\begin{itemize}
  \item
    Abstraction $\Rightarrow$ Replace values with descriptors of sets of values
  \item
    Interpretation $\Rightarrow$ Run the program over these descriptors.
\end{itemize}
The descriptors depend on the problem of interest, but a useful example is
the domain of ``environments of intervals.''
Each variable's value is represented by an interval $[a,b]$, and an
environment of intervals is a map from variables to intervals, e.g.,
\begin{eqnarray*}
  x & \mapsto & [0,3] \\
  y & \mapsto & [5,10]
\end{eqnarray*}
Operations in the concrete domain---in which the original program is
executed---have over-approximating analogs in the abstract domain.
\begin{equation}
  \label{Eq:AddOneAction}
  (x = x + 1) \Rightarrow (X = X~\A{+}~\A{1})
\end{equation}
For instance, if the pre-states to statement (\ref{Eq:AddOneAction})
are described by the interval environment given above, the post-states
are described by the following interval environment:
\begin{eqnarray*}
  x & \mapsto & [1,4] \\
  y & \mapsto & [5,10]
\end{eqnarray*}


\subsection{Concretization Function $\gamma$}
Each family of descriptors has an operation $\gamma$, which takes a
descriptor and returns the set of states
in the concrete domain that the descriptor describes.
For example,
\[
  \gamma((x \mapsto [0,3], y \mapsto [5,10])) =  \{(0,5), (1,5) .. (3,5), (0,6), (1,6) ... (3,10) \}
\]

\subsection{Abstraction Function $\alpha$}

Suppose that at some program point, the only states possible are
$(x \mapsto 0, y \mapsto 5)$ and $(x \mapsto 3, y \mapsto 10)$.
By abstracting into the interval-environments domain, there is
an unavoidable loss of precision.
In this case, the most precise descriptor that includes both
$(x \mapsto 0, y \mapsto 5)$ and $(x \mapsto 3, y \mapsto 10)$
is $(x \mapsto [0,3], y \mapsto [5,10])$;
however, $\gamma((x \mapsto [0,3], y \mapsto [5,10]))$ includes far
more than just the two states $(x \mapsto 0, y \mapsto 5)$ and
$(x \mapsto 3, y \mapsto 10)$.

If $\alpha$ represents the abstraction function and $C$
represents a set of states in the concrete domain,
the loss of precision can be stated precisely in either
of the following two ways:
\begin{itemize}
  \item
    $\gamma(\alpha(C)) \supseteq C $, or
  \item
    $(\gamma \circ \alpha)(C) \supseteq C.$
\end{itemize}
This inequalities express the over-approximation inherent in abstract
interpretation.

\subsection{Correctness (``Soundness'') of Abstract Interpretation} 

The essence of the correctness (``soundness'') argument that one uses
in abstract interpretation can be called \emph{lock-step
over-approximation}.
That is, each operation in the abstract domain is in lock-step
with an operation in the concrete domain.
\[
  \textrm{concrete function}~f \Longrightarrow \textrm{abstract domain function}~\A{f}
\]
Moreover, we want each $\A{f}$ to over-approximate $f$ in some fashion.
To make this idea precise, in the concrete domain, we need to lift the
function $f$ to operate on a set of values (instead of singleton values)
to form $\tilde{f}$. \\

The key idea for a soundness argument is expressed by the equation:
\begin{equation}
  \label{Eq:SoundnessWRTConcrete}
  \tilde{f}(\gamma(a_1), \gamma(a_2)... \gamma(a_k)) \subseteq \gamma( \A{f} (a_1, a_2 .. a_k))
\end{equation}
In words, applying $\tilde{f}$ to a set of concretized values $\gamma(a)$ will
always be a subset of the concretization of applying $\A{f}(a)$ in the
abstract domain.

\subsection{Abstract Containment}

Let us define an ordering relation in the abstract domain, denoted by $\sqsubseteq$.
For example,
\[
  (x \mapsto [0,3], y \mapsto [5,10]) \sqsubseteq ( x \mapsto [-2,3], y \mapsto [4,20])
\]
Both intervals on the right-hand side are larger---and thus represent supersets of---the
intervals on the left-hand side.
Soundness of an abstract interpretation can also be expressed as:
\begin{equation}
  \label{Eq:SoundnessWRTAbstract}
  \alpha ( \tilde{f}(c_1, c_2 .. c_k) ) \sqsubseteq \A{f} (\alpha(c_1), \alpha(c_2) ... \alpha(c_k)).
\end{equation}
(\eqrefs{SoundnessWRTConcrete}{SoundnessWRTAbstract}
are really two ways of saying the same thing.)

\subsection{Creating a Sound Abstract Interpreter}

To create a sound abstract interpreter that uses a given abstract
domain, we need to have some way to create the abstract analogs of
\begin{enumerate}
  \item
    \label{It:Constants}
    each constant that can be denoted in the programming language
  \item
    \label{It:PrimitiveOperations}
    each primitive operation in the programming language
  \item
    \label{It:UserDefinedFunctions}
    each user-defined function in every program to be analyzed.
\end{enumerate}
Task (\ref{It:Constants}) is related to defining the $\alpha$
function;
to create the abstract analog of a constant $k$, apply $\alpha$:
$\A{k} = \alpha(k)$.
By  an ``abstract analog'' of an operation/function,
we mean an abstract operation/function that satisfies
\eqrefs{SoundnessWRTConcrete}{SoundnessWRTAbstract}.

The effort that has to go into task (\ref{It:PrimitiveOperations}) is
bounded---the language has a fixed number of primitive
operations---and task (\ref{It:PrimitiveOperations})
only has to be done once for a given abstract domain.
However, unless we have some automatic approach, the effort that
would have to go into task (\ref{It:UserDefinedFunctions})
depends on the size of a user's program, which is not of {\it a priori}
bounded size.

\subsubsection{Syntax-Directed Replacement}
\label{Se:SyntaxDirectedReplacement}

Fortunately, \eqref{SoundnessWRTAbstract} enables a simple,
compositional approach to be used---namely, syntax-directed
replacement of the concrete constants and concrete primitive
operations by their abstract analogs.
For instance, consider the following function:
\[
  f(x, y) = x * y + 1
\]
To see why \eqref{SoundnessWRTAbstract} enables a compositional approach
to be applied, first hoist $f$ to $\tilde{f}$, i.e.,
\[
  \tilde{f}(X, Y) = X~\tilde{*}~Y~\tilde{+}~\{1\}.
\]
Then, by \eqref{SoundnessWRTAbstract}, we have
\begin{eqnarray*}
  \alpha ( \tilde{f} ( X, Y) )
      & =           & \alpha ( X~\tilde{*}~Y~\tilde{+}~\{1\} ) \\
      & \sqsubseteq & \alpha ( X~\tilde{*}~Y )~\A{+}~\A{\{1\}} \\
      & \sqsubseteq & \alpha ( X )~\A{*}~\alpha ( Y )~\A{+}~\A{\{1\}}
\end{eqnarray*}
Consequently, one way to ensure that we have a sound $\A{f}$
is to define $\A{f}(A,B)$ by
\[
  \A{f}(A,B) \eqdef A~\A{*}~B~\A{+}~\A{\{1\}}.
\]

\subsubsection{Challenges for Abstract Interpretation of Functional Languages} 

The runtime environment for an abstract interpreter must be different
from that of a concrete interpreter.
Note that the two interpreters are doing different things:
the concrete interpreter works with \emph{singleton values};
the abstract interpreter works with (descriptors of) \emph{sets of values}.

Consider some of the issues that come up, even if we are dealing with
a pure functional language.
\begin{itemize}
  \item
    \emph{The need to execute ``both ways'' at a branch}:
    The expression in a conditional expression will be evaluated in
    the abstract domain to produce a value in the domain of
    $\textit{AbstractBoolean}$s, which is often $3$-valued:
    $\{ \textrm{TRUE}, \textrm{FALSE}, \textrm{UNKNOWN} \}$.
    In the case of $\textrm{UNKNOWN}$, we will have to consider both
    branches of the conditional (and take the join of the two results).
  \item
    How does the abstract interpreter detect termination of the
    analysis process? (We could be analyzing a recursively defined function.)
\end{itemize}

\subsubsection{Example}
Consider the following concrete arithmetic operations:
\[
\begin{array}{l@{\hspace{6ex}}l}
  \begin{array}{r|rrrrr}
    + & 0 & 1 & 2 & 3 & \ldots \\
    \hline
    0 & 0 & 1 & 2 & 3 & \ldots \\
    1 & 1 & 2 & 3 & 4 & \ldots \\
    2 & 2 & 3 & 4 & 5 & \ldots \\
    3 & 3 & 4 & 5 & 6 & \ldots \\
    \vdots & \vdots & \vdots & \vdots & \vdots & \ddots
  \end{array}
  &
  \begin{array}{r|rrrrr}
    \times & 0 & 1 & 2 & 3 & \ldots \\
    \hline
    0 & 0 & 0 & 0 & 0 & \ldots \\
    1 & 0 & 1 & 2 & 3 & \ldots \\
    2 & 0 & 2 & 4 & 6 & \ldots \\
    3 & 0 & 3 & 6 & 9 & \ldots \\
    \vdots & \vdots & \vdots & \vdots & \vdots & \ddots
  \end{array}
\end{array}
\]
Now consider the abstraction of sets of numbers to the
\emph{parity abstract domain}.
There are three values: $E$, $O$, and $?$,
standing for ``even,'' ``odd,'' and ``unknown.''
The abstract operations $\A{+}$ and $\A{\times}$
are defined as follows:
\[
\begin{array}{l@{\hspace{6ex}}l}
  \begin{array}{r|rrr}
    \A{+} & ? & E & O \\
    \hline
    ? & ? & ? & ? \\
    E & ? & E & O \\
    O & ? & O & E
  \end{array}
  &
  \begin{array}{r|rrr}
    \A{\times} & ? & E & O \\
    \hline
    ? & ? & E & ? \\
    E & E & E & E \\
    O & ? & E & O
  \end{array}
\end{array}
\]

Now consider the following function: $g(x,y) = (16*y + 3)*(2*x+1)$.
The abstract syntax tree (AST) for the defining right-hand-side expression
is as follows:

\noindent
\Tree [.* [.+ [.* 16 y ]
              3
          ]
          [.+ [.* 2 x ]
              1
          ]
      ]

\noindent
Consider an evaluation over the AST using the parity abstract domain,
where abstract values at leaves and internal nodes are shown as
{\color{red} Red} annotations:

\noindent
\Tree [.{\color{red} O }$\A{*}$
          [.{\color{red} O }$\A{+}$ [.{\color{red} E }$\A{*}$ {\color{red} E}
                                                              {\color{red} ?}
                                    ]
                                    {\color{red} O}
          ]
          [.{\color{red} O }$\A{+}$ [.{\color{red} E }$\A{*}$ {\color{red} E}
                                                               {\color{red} ?}
                                    ]
                                    {\color{red} O}
          ]
      ]

\subsubsection{Drawbacks of Syntax-Directed Replacement}

Although the syntax-directed-replacement approach is simple and compositional,
it can be quite myopic as it focuses solely on what happens at a single
production in the abstract syntax tree.
The approach can lead to a loss of precision by not accounting for
correlations between operations an far-apart positions in the abstract
syntax tree.

To illustrate the issue, consider the function
\[
  h(x) \eqdef x + (-x).
\]
Obviously, $h(x)$ always returns $0$.
Suppose that we apply the syntax-directed method described in
\sectref{SyntaxDirectedReplacement}.
\begin{eqnarray}
  \tilde{h}(X) & = & X~\tilde{+}~(\tilde{-}X) \nonumber \\
  \A{h}(X) & \eqdef & X~\A{+}~(\A{-}X)  \label{Eq:hSharp}
\end{eqnarray}
Now suppose that we evaluate over the \emph{sign abstract domain},
which consists of six values: $\{ <0, 0, >0, \le 0, \ge 0, ? \}$.
In particular, the abstract unary-minus operation is defined as follows:
\[
\begin{array}{r|l}
  a & \A{-}a \\
  \hline
      ? & ? \\
  \ge 0 & \le 0 \\
  \le 0 & \ge 0 \\
     >0 & <0 \\
      0 & 0 \\
     <0 & >0  
\end{array}
\]

Consider evaluating $\A{h}(a)$ with the abstract value $>0$ for
the value of $a$.
(Again, abstract values at leaves and internal nodes of the AST of the
right-hand-side expression are shown as {\color{red} Red} annotations.)

\noindent
\Tree [.{\color{red} ? }$\A{+}$
          {\color{red} $>0$ }$a$
          [.{\color{red} $<0$ }$\A{-}$ {\color{red} $>0$ }$a$ ]
      ]

\noindent
In other words, we get no information from the abstract interpreter,
whereas the concrete value is always $0$, and therefore the most-precise
abstract answer would have been $0$ (because $\alpha(\{ 0 \}) = 0$).

{\small
  \bibliography{cs701}
  \bibliographystyle{plain}
}

\end{document}
