Configuring and Using LiSA
This website describes LiSA’s architecture and provides guides on how to use and extend it. It is intended to be valid for the latest stable release of LiSA, but should be compatible with versions 0.2 and later. Signatures or packages might differ in older versions, but the overall architecture and design principles should remain the same.
Using and configuring LiSA is straightforward: first, a Program (or more
programs, one for each programming language to analyze) must be obtained,
then a LiSAConfiguration object must be created and customized, and finally
a LiSA instance must be created with the configuration and run on the
program(s). For example:
Program program = ... // use a frontend to parse the code, or build the program programmatically
LiSAConfiguration config = new LiSAConfiguration();
// set configuration options (see below)
LiSA lisa = new LiSA(config);
LiSAReport report = lisa.run(program);
// use the report
Each configuration option can be set individually by changing the value of a
field of the LiSAConfiguration object passed to the LiSA constructor. In the
following sections, when an example usage is provided for a configuration option,
it is assumed that the LiSAConfiguration object is stored in a variable named conf.
Available Options
Setting the Abstract Domain
analysis
AbstractDomain<?>
null
conf.analysis = new SimpleAbstractDomain(new PointBasedHeap(), new Interval(), new InferredTypes())
The Abstract Domain
to execute during the analysis can be selected through the analysis option.
The value of this option decides what analyysis is being run, and what shape
will the computed states have. If no value is set for this option, no semantic
analysis will be executed.
Several abstract domains are available in LiSA:
-
it.unive.lisa.analysis.HistoryDomain
: An abstract domain that tracks the history of fixpoint iterations as a
HistoryState. - it.unive.lisa.analysis.Reachability : An abstract domain that tracks the reachability of program points, exploiting an underlying abstract domain to (i) compute approximations of the program state, and (ii) deducing which branches are taken after traversing a guard.
-
it.unive.lisa.analysis.SimpleAbstractDomain
: An abstract domain that combines a heap, a value, and a type domain into a single abstract domain of type
SimpleAbstractState. The interaction between heap and value/type domains follows the one defined in this paper. -
it.unive.lisa.analysis.traces.TracePartitioning
: The trace partitioning abstract domain that splits execution traces to increase precision of the analysis. Individual traces are identified by
ExecutionTraces composed of tokens representing the conditions traversed during the analysis. Note that allTraceTokens represent intraprocedural control-flow constructs, as calls are abstracted away before reaching this domain. Traces are never merged: instead, we limit the size of the traces we can track, and we leave the choice of when and where to compact traces to other analysis components. Specifically, anExecutionTracewill contain at mostmax_conditionsBranchingtokens, and will track at mostmax_loop_iterationsiterations for each loop (throughLoopIterationtokens) before summarizing the next ones with aLoopSummarytoken. Both values are editable and customizable before the analysis starts.
If you adopt the Simple Abstract Domain framework to build your own abstract domain, LiSA also provides alternatives for each component. The out-of-the-box implementations for the heap domain are:
-
it.unive.lisa.analysis.NoOpHeap
: A no-op heap domain that uses
SingleHeapLatticeas lattice structure. This is useful in analyses where heap information is not relevant or when a placeholder is needed. Note that this domain never produces substitutions, and rewrite operations will always return the input expression wrapped in anExpressionSet. - it.unive.lisa.analysis.heap.MonolithicHeap : A monolithic heap implementation that abstracts all heap locations to a unique identifier.
- it.unive.lisa.analysis.heap.TypeBasedHeap : A type-based heap implementation that abstracts heap locations depending on their types, i.e., all the heap locations with the same type are abstracted into a single unique identifier.
-
it.unive.lisa.analysis.heap.pointbased.FieldSensitivePointBasedHeap
: A field-insensitive program point-based
AllocationSiteBasedAnalysis. The implementation follows X. Rival and K. Yi, "Introduction to Static Analysis An Abstract Interpretation Perspective", Section 8.3.4 -
it.unive.lisa.analysis.heap.pointbased.PointBasedHeap
: A field-insensitive program point-based
AllocationSiteBasedAnalysis. The implementation follows X. Rival and K. Yi, "Introduction to Static Analysis An Abstract Interpretation Perspective", Section 8.3.4
Instead, the bundled alternatives for the value domain are:
-
it.unive.lisa.analysis.ConstantValuePropagation
: A non-relational value domain tracking
ConstantValues of variables for numeric, character, string and boolean values. -
it.unive.lisa.analysis.NoOpValues
: A no-op value domain that uses
SingleValueLatticeas lattice structure. This is useful in analyses where value information is not relevant or when a placeholder is needed. - it.unive.lisa.analysis.combination.constraints.WholeValueAnalysis : The constraint-based whole-value analysis between a non-relational Boolean abstract domain, a non-relational numeric abstract domain, and a non-relational string abstract domain. This domains tracks environments of whole-value elements, which are values of one of the types produced by the client domains.
-
it.unive.lisa.analysis.combination.smash.SmashedSum
: The smashed-sum abstract domain between
BooleanPowerset, a non-relational numeric abstract domain, and a non-relational string abstract domain. This domains tracks environments of smashed values, which are values of one of the types produced by the client domains. - it.unive.lisa.analysis.dataflow.AvailableExpressions : An implementation of the available expressions dataflow analysis, that focuses only on the expressions that are stored into some variable.
- it.unive.lisa.analysis.dataflow.ConstantPropagation : An implementation of the overflow-insensitive constant propagation dataflow analysis, that focuses only on integers.
- it.unive.lisa.analysis.dataflow.Liveness : An implementation of the liveness dataflow analysis, that determines which values might be used later on in the program.
- it.unive.lisa.analysis.dataflow.ReachingDefinitions : An implementation of the reaching definition dataflow analysis.
-
it.unive.lisa.analysis.informationFlow.NonInterference
: Implementation of the non interference analysis, using annotations to detect low confidentiality variables/fields/functions (
LOW_CONF_ANNOTATION) and high integrity variables/fields/functions (HIGH_INT_ANNOTATION). -
it.unive.lisa.analysis.informationFlow.ThreeLevelsTaint
: A
BaseTaintimplementation with three level of taintedness: clean, tainted and top. As such, this class distinguishes values that are always clean, always tainted, or tainted in at least one execution path. -
it.unive.lisa.analysis.informationFlow.TwoLevelsTaint
: A
BaseTaintimplementation with only two level of taintedness: clean and tainted. As such, this class distinguishes values that are always clean from values that are tainted in at least one execution path. -
it.unive.lisa.analysis.nonRedundantPowerset.NonRedundantPowerset
: A
ValueDomainthat computesNonRedundantSetDomainLatticeelements as the powerset of the elements of a given underlying lattice. -
it.unive.lisa.analysis.nonRedundantPowerset.NonRelationalNonRedundantPowerset
: A
NonRelationalValueDomainthat computesNonRedundantSetLatticeelements as the powerset of the elements of a given underlying lattice. -
it.unive.lisa.analysis.nonrelational.value.BooleanPowerset
: A
NonRelationalValueDomainthat tracks sets of boolean values in the environments it produces. Sets are are represented asSatisfiabilityvalues. -
it.unive.lisa.analysis.numeric.IntegerConstantPropagation
: The overflow-insensitive basic integer constant propagation analysis, tracking if a certain integer value has constant value or not, implemented as a
BaseNonRelationalValueDomain. The lattice structure used by this domain isIntegerConstant. -
it.unive.lisa.analysis.numeric.Interval
: The overflow-insensitive interval abstract domain, approximating integer values as the minimum integer interval containing them. It is implemented as a
BaseNonRelationalValueDomain. The lattice structure of this domain isIntInterval. -
it.unive.lisa.analysis.numeric.NonRedundantIntervals
: An analysis computing finite non redundant powersets of
IntIntervals, approximating integer values as a non redundant set of intervals. -
it.unive.lisa.analysis.numeric.Parity
: The overflow-insensitive Parity abstract domain, tracking if a numeric value is even or odd, implemented as a
BaseNonRelationalValueDomain. - it.unive.lisa.analysis.numeric.Pentagon : Implementation of the pentagons analysis of this paper.
-
it.unive.lisa.analysis.numeric.Sign
: The basic overflow-insensitive Sign abstract domain, tracking zero, strictly positive and strictly negative integer values, implemented as a
BaseNonRelationalValueDomain. -
it.unive.lisa.analysis.numeric.Stability
: Implementation of the stability abstract domain ( Stability paper). This domain computes per-variable numerical trends to infer stability, covariance and contravariance relations on program variables, exploiting an auxiliary domain of choice. This is implemented as an open product where the stability domain gathers information from the auxiliary one through boolean queries.
Implementation-wise, this class is built as a product between a given
ValueDomainauxand aValueEnvironmenttrendsofTrendinstances, representing per-variable trends. Queries are carried over by theSemanticDomain.satisfies(DomainLattice, SymbolicExpression, ProgramPoint)operator invoked onaux. - it.unive.lisa.analysis.numeric.UpperBounds : Relational implementation of the upper bounds analysis of this paper.
-
it.unive.lisa.analysis.string.BoundedStringSet
: A domain computing bounded set of strings, where the maximum number of elements is defined by
max_size. If the number of elements exceeds this limit, the set is considered to be top. The domain is defined in this paper. - it.unive.lisa.analysis.string.Bricks : The bricks string abstract domain.
- it.unive.lisa.analysis.string.CharInclusion : The character inclusion abstract domain.
- it.unive.lisa.analysis.string.Prefix : The prefix string abstract domain.
-
it.unive.lisa.analysis.string.StringConstantPropagation
: The string constant propagation abstract domain, tracking if a certain string value has constant value or not. Top and bottom cases for least upper bounds, widening and less or equals operations are handled by
BaseLatticeinBaseLattice.lub(L),BaseLattice.widening(L)andBaseLattice.lessOrEqual(L), respectively. - it.unive.lisa.analysis.string.SubstringDomain : The substring relational abstract domain, tracking relation between string expressions. This domain follows the one defined in this paper.
-
it.unive.lisa.analysis.string.SubstringDomainWithConstants
: The substring relational abstract domain (see
SubstringDomain) enriched with string constant propagation. This domain tracks the Cartesian product betweenSubstringsandStringConstant. This domain follows the one defined in this paper. - it.unive.lisa.analysis.string.Suffix : The suffix string abstract domain.
-
it.unive.lisa.analysis.string.fsa.FSA
: A class that represent the Finite State Automaton domain for strings, exploiting a
SimpleAutomaton. Caution: the FSA domain is buggy and requires lots of resources, to the point where it might be hard to debug also on relatively small samples. Use with caution. -
it.unive.lisa.analysis.string.tarsis.Tarsis
: A class that represent the Tarsis domain for strings, exploiting a
RegexAutomaton.
Finally, the bundled alternatives for the type domain are:
-
it.unive.lisa.analysis.NoOpTypes
: A no-op type domain that uses
SingleTypeLatticeas lattice structure. This is useful in analyses where type information is not relevant or when a placeholder is needed. Note that this domain cannot produce typing information:getRuntimeTypesOf(SingleTypeLattice, SymbolicExpression, ProgramPoint, SemanticOracle)always returns all possible types, andgetDynamicTypeOf(SingleTypeLattice, SymbolicExpression, ProgramPoint, SemanticOracle)always returnsUntyped.INSTANCE. -
it.unive.lisa.analysis.types.InferredTypes
: A
NonRelationalTypeDomainholding a set ofTypes, representing the inferred runtime types of anExpression. -
it.unive.lisa.analysis.types.StaticTypes
: A
NonRelationalTypeDomainthat tracks the static type of variables, and that computes expression types using their static type. Typing information is thus deemed to be the set of all subtypes of the tracked type.
Interprocedural Analysis and Call Graph
interproceduralAnalysis
InterproceduralAnalysis<?, ?>
null
conf.interproceduralAnalysis = new ModularWorstCaseAnalysis<>()
callGraph
CallGraph
null
conf.callGraph = new CHACallGraph()
openCallPolicy
OpenCallPolicy
TopExecutionPolicy.INSTANCE
conf.openCallPolicy = new CustomPolicy()
The Interprocedural Analysis
and the Call Graph
regulate how the program-wide analysis is executed, and how calls are resolved
and computed. The value of the interproceduralAnalysis option determines the
interprocedural analysis to use, while the value of the callGraph option
determines the call graph.
If no value is set for interproceduralAnalysis, no
semantic analysis will be executed. Instead, the value of callGraph is
effectively ignored if the selected does not require
a call graph, as determined by that analysis’ needsCallGraph method.
If instead the selected interprocedural analysis requires a call graph and no value is
set for callGraph, an error will be raised at startup.
The Open Call Policy is used to determine the results of calls that have no targets in the program to analyze.
Several interprocedural analyses are available in LiSA:
- it.unive.lisa.interprocedural.BackwardModularWorstCaseAnalysis : A worst case modular analysis were all cfg calls are treated as open calls.
- it.unive.lisa.interprocedural.ModularWorstCaseAnalysis : A worst case modular analysis were all cfg calls are treated as open calls.
-
it.unive.lisa.interprocedural.context.ContextBasedAnalysis
: A context sensitive interprocedural analysis. The context sensitivity is tuned by the number of calls that tail the call stack to keep track of. This happens concretely in
KDepthToken. Recursions are approximated applying the iterates of the recursion starting from bottom and using the same widening threshold of cfg fixpoints. - it.unive.lisa.interprocedural.context.recursion.BaseCasesFinder : A recursion solver that applies a single iteration of the recursion starting from bottom and using top as entry state for the recursion. This is useful for understanding what is the return value in the base cases of the recursion: as the call that closes the recursion loop returns bottom, only the returns from the base cases will affect the result.
-
it.unive.lisa.interprocedural.context.recursion.RecursionSolver
: A recursion solver that applies the iterates of the recursion starting from bottom. This solver operates by restarting the recursion from
Recursion.getInvocation()a number of times, until the results of all the members stabilize. - it.unive.lisa.interprocedural.inlining.InliningAnalysis : An inlining-based interprocedural analysis. This means that each call receives its own result, with no "compacting" based on context or other technique: each call receives its own result that is uniquely determined by the call's entry strate. Recursions are not supported: either they converge to a result, or the analysis (i) diverges if no maximum call stack depth is set through the constructor, or (ii) terminates with an exception when the maximum call stack depth has been reached.
Orthogonally, call graphs implementations are also bundled with LiSA:
- it.unive.lisa.interprocedural.callgraph.CHACallGraph : A call graph constructed following the Class Hierarchy Analysis as defined in: Frank Tip and Jens Palsberg. 2000. Scalable propagation-based call graph construction algorithms. In Proceedings of the 15th ACM SIGPLAN conference on Object-oriented programming, systems, languages, and applications (OOPSLA '00). Association for Computing Machinery, New York, NY, USA, 281–293. DOI:https://doi.org/10.1145/353171.353190
- it.unive.lisa.interprocedural.callgraph.RTACallGraph : A call graph constructed following the Rapid Type Analysis as defined in: Frank Tip and Jens Palsberg. 2000. Scalable propagation-based call graph construction algorithms. In Proceedings of the 15th ACM SIGPLAN conference on Object-oriented programming, systems, languages, and applications (OOPSLA '00). Association for Computing Machinery, New York, NY, USA, 281–293. DOI:https://doi.org/10.1145/353171.353190
For open call policies, LiSA provides:
-
it.unive.lisa.interprocedural.ReturnTopPolicy
: An
OpenCallPolicy, where the post state is exactly the entry state, with the only difference of having a the call's meta variable assigned to top only if the call returns a value. This variable, that is also stored as computed expression, represent the unknown result of the call, if any. -
it.unive.lisa.interprocedural.TopExecutionPolicy
: An
OpenCallPolicywhere the whole execution state becomes top and all information is lost. The return value, if any, is stored in the call's meta variable. No errors are assumed to be thrown. -
it.unive.lisa.interprocedural.WorstCasePolicy
: A worst-case
OpenCallPolicy, where the whole analysis state becomes top and all information is lost. The return value, if any, is stored in the call's meta variable. All possible errors are assumed to be thrown.
Adding Syntactic and Semantic Checks
syntacticChecks
Collection<SyntacticCheck>
new HashSet<>()
conf.syntacticChecks.add(new VariableNamesCheck())
semanticChecks
Collection<SemanticCheck<?, ?>>
new HashSet<>()
conf.semanticChecks.add(new NullDereferenceCheck())
Syntactic Checks and
Semantic Checks are visitors of
the program under analysis. Syntactic checks only require syntactic information,
and thus can be executed before the analysis, while semantic checks require
semantic information, and thus can only be executed after the analysis.
The collections of syntactic and semantic checks to execute can be customized
through the syntacticChecks and semanticChecks options, respectively.
The checks in these collections will be executed on the program under analysis, and
the results of the checks will be included in the final report. Note that
the order of execution of the checks is not guaranteed, and should not be relied upon.
The collection of syntactic checks to execute should only be added to, and not
replaced with other (possibly immutable) collections, as LiSA might add new
checks depending on the values of other options. The same applies to the
collection of semantic checks.
As of today, LiSA does not include any syntactic or semantic check implementations, as they are highly situational.
Thresholds for Widenings and GLBs
wideningThreshold
int
5
conf.wideningThreshold = 10
recursionWideningThreshold
int
5
conf.recursionWideningThreshold = 10
glbThreshold
int
5
conf.glbThreshold = 10
Fixpoint algorithms that use widenings and greatest lower
bounds (glbs) can be customized by setting the thresholds for the application of
the respective operators. wideningThreshold determines after how many iterations
of the fixpoint algorithm on a given node the widening operator should be applied
instead of the least upper bound operator (lub). recursionWideningThreshold
determines after how many iterations of the fixpoint algorithm on a recursive call
chain the widening operator should be applied instead of the least upper bound
operator (lub). glbThreshold determines how many descending iterations of the
fixpoint algorithm can be performed a given node using the greatest lower bound
operator (glb) before the descending iteration is stopped.
Setting these thresholds to 0 or a negative number causes the respective
operator to be always applied (for widenings) or to never be applied (for glbs).
Selecting the Fixpoint Algorithms
forwardFixpoint
ForwardCFGFixpoint<?, ?>
new ForwardAscendingFixpoint<>()
conf.forwardFixpoint = new CustomForwardFixpoint<>()
forwardDescendingFixpoint
ForwardCFGFixpoint<?, ?>
null
conf.forwardDescendingFixpoint = new CustomForwardDescendingFixpoint<>()
backwardFixpoint
BackwardCFGFixpoint<?, ?>
new BackwardAscendingFixpoint<>()
conf.backwardFixpoint = new CustomBackwardFixpoint<>()
backwardDescendingFixpoint
BackwardCFGFixpoint<?, ?>
null
conf.backwardDescendingFixpoint = new CustomBackwardDescendingFixpoint<>()
fixpointWorkingSet
WorkingSet<Statement>
new OrderBasedWorkingSet()
conf.fixpointWorkingSet = new CustomWorkingSet<>()
All fixpoint algorithms that LiSA executes over
control flow graphs
can be customized.
forwardFixpoint determines the fixpoint to compute forward fixpoints over
CFGs, while backwardFixpoint determines the fixpoint to compute backward
fixpoints over CFGs. The interprocedural analysis selected through the
interproceduralAnalysis option
determines whether forward and/or backward fixpoints are required, and thus
whether the values of these options are relevant.
Optionally, descending fixpoints can be computed after the ascending ones,
to refine the results. forwardDescendingFixpoint determines the fixpoint to
compute descending forward fixpoints over CFGs, while backwardDescendingFixpoint
determines the fixpoint to compute descending backward fixpoints over CFGs. If
no value is set for forwardDescendingFixpoint or backwardDescendingFixpoint,
no descending phase will be executed.
The order in which the nodes of the CFG are visited during fixpoint iterations
is determined by the WorkingSet passed to the fixpointWorkingSet option.
In all options above, the instances passed to the fields are used as factories to create new fixpoint instances or new working sets.
LiSA provides standard forward fixpoint algorithms, alongside their optimized variants:
-
it.unive.lisa.program.cfg.fixpoints.forward.ForwardAscendingFixpoint
: A
ForwardCFGFixpointthat traverses ascending chains using lubs and widenings. -
it.unive.lisa.program.cfg.fixpoints.forward.ForwardDescendingGLBFixpoint
: A
ForwardCFGFixpointthat traverses descending chains using glbs up to threshold. -
it.unive.lisa.program.cfg.fixpoints.forward.ForwardDescendingNarrowingFixpoint
: A
ForwardCFGFixpointthat traverses descending chains using narrowings. -
it.unive.lisa.program.cfg.fixpoints.optforward.OptimizedForwardAscendingFixpoint
: An
OptimizedForwardFixpointthat traverses ascending chains using lubs and widenings. -
it.unive.lisa.program.cfg.fixpoints.optforward.OptimizedForwardDescendingGLBFixpoint
: An
OptimizedForwardFixpointthat traverses descending chains using glbs up to threshold. -
it.unive.lisa.program.cfg.fixpoints.optforward.OptimizedForwardDescendingNarrowingFixpoint
: An
OptimizedForwardFixpointthat traverses descending chains using narrowings.
These are paired with their backward variants:
-
it.unive.lisa.program.cfg.fixpoints.backward.BackwardAscendingFixpoint
: A
BackwardCFGFixpointthat traverses ascending chains using lubs and widenings. -
it.unive.lisa.program.cfg.fixpoints.backward.BackwardDescendingGLBFixpoint
: A
BackwardCFGFixpointthat traverses descending chains using glbs up to threshold. -
it.unive.lisa.program.cfg.fixpoints.backward.BackwardDescendingNarrowingFixpoint
: A
BackwardCFGFixpointthat traverses descending chains using narrowings. -
it.unive.lisa.program.cfg.fixpoints.optbackward.OptimizedBackwardAscendingFixpoint
: A
BackwardCFGFixpointthat traverses ascending chains using lubs and widenings. -
it.unive.lisa.program.cfg.fixpoints.optbackward.OptimizedBackwardDescendingGLBFixpoint
: An
OptimizedBackwardFixpointthat traverses descending chains using glbs up to threshold. -
it.unive.lisa.program.cfg.fixpoints.optbackward.OptimizedBackwardDescendingNarrowingFixpoint
: An
OptimizedBackwardFixpointthat traverses descending chains using narrowings.
Each fixpoint implementation can be customized by the following bundled working sets:
- it.unive.lisa.util.collections.workset.ConcurrentFIFOWorkingSet : A first-in, first-out working set. This implementation is thread-safe.
- it.unive.lisa.util.collections.workset.ConcurrentLIFOWorkingSet : A last-in, first-out working set. This implementation is thread-safe.
- it.unive.lisa.util.collections.workset.DuplicateFreeFIFOWorkingSet : A LIFO working set that guarantees that, at any time, the same element cannot appear more than once in it. It works by pushing elements only if they are not already part of the working set. This implementation is not thread-safe.
- it.unive.lisa.util.collections.workset.DuplicateFreeLIFOWorkingSet : A LIFO working set that guarantees that, at any time, the same element cannot appear more than once in it. It works by pushing elements only if they are not already part of the working set. This implementation is not thread-safe.
- it.unive.lisa.util.collections.workset.FIFOWorkingSet : A first-in, first-out working set. This implementation is not thread-safe.
- it.unive.lisa.util.collections.workset.LIFOWorkingSet : A last-in, first-out working set. This implementation is not thread-safe.
-
it.unive.lisa.util.collections.workset.OrderBasedWorkingSet
: A
WorkingSetforStatements that sorts its contents according to their natural order. This is specifically designed for fixpoint algorithms of CFGs: since the natural order of Statements discriminates for theirCodeLocationfirst, this allows instructions that are exit points of control-flow structures to be analyzed only when all branches of the preceding structure has been fully analyzed. This holds since, unless several GOTO-like instructions are present, contents of ifs and loops always appear earlier in the code w.r.t. the exit points. Note that this working set is backed by a set: it is thus impossible to have duplicates in it. - it.unive.lisa.util.collections.workset.VisitOnceFIFOWorkingSet : A FIFO working set that guarantees that each element will be added to this working set no more than once. It works by pushing elements only if they were not already added before (even if they have already been popped out). This implementation is not thread-safe.
- it.unive.lisa.util.collections.workset.VisitOnceLIFOWorkingSet : A LIFO working set that guarantees that each element will be added to this working set no more than once. It works by pushing elements only if they were not already added before (even if they have already been popped out). This implementation is not thread-safe.
Optimization-related Options
useWideningPoints
boolean
true
conf.useWideningPoints = false
hotspots
Predicate<Statement>
null
conf.hotspots = stmt -> stmt instanceof Assignment
dumpForcesUnwinding
boolean
false
conf.dumpForcesUnwinding = true
LiSA can be optimized in several ways. A simple optimization is to use
widenings and narrowings only on widening points (i.e., loop conditions),
and to use lubs and glbs on all other nodes regardless of the threshold.
This is typically more efficient, as widening and narrowing are more expensive
than lub and glb, and the results of lubs and glbs are often more precise
than those of widenings and narrowings. This behavior can be enabled by
setting the useWideningPoints option to true. Note that widening points
correspond to the conditions of loops, as identified by CFG.getCycleEntries().
A second optimization is to use
optimized fixpoint algorithms
(i.e., algorithms for which invocations of
AnalysisFixpoint.isOptimized() on forwardFixpoint,
forwardDescendingFixpoint,
backwardFixpoint, or
backwardDescendingFixpoint
yields true — these correspond to the ones having Optimized in their name).
Such algorithms exploit basic blocks, and store the fixpoint results only for
widening points (i.e., loop conditions), return statements, and calls. This is
doable since results for any other instruction can be recontsructed by executing
a single fixpoint iteration local to the CFG that contains the instruction,
that will stabilize in one iteration since the results of widening points is
already a post-fixpoint. This reconstruction is called unwinding in LiSA.
When such algorithms are used, the hotspots predicate can be set to determine
additional statements for which the fixpoint results must be kept to avoid
excessive unwinding. null is a special value corresponding to the predicate t
-> false. Instead, dumpForcesUnwinding can be set to true to
force unwinding of all non-hotspot statements when dumping results to output
files, to ensure that results are available for all program instructions.
Hiding Error and Exceptions
shouldSmashError
Predicate<Type>
null
conf.shouldSmashError = type -> type.getName().equals("java.lang.NullPointerException")
Some error typems might pollute the analysis results, since they might not be
relevant for the properties to prove or they are caused by an excessive
imprecision of the analysis. While it is not possible to completely remove them
(as the modifications they cause to the control flow must be taken into
account), it is possible to smash them, that is, to not have a separate entry
for each of their occurrences in the
AnalysisState errors.
Instead, all occurrences of smashed errors will share a unique ProgramState.
The choice over what error types to smash is determined by the shouldSmashError
predicate, that returns true for the types of errors to smash. null is a
special value corresponding to the predicate t -> false.
Event Listeners
synchronousListeners
List<EventListener>
new LinkedList<>()
conf.synchronousListeners.add(new LoggingListener())
asynchronousListeners
List<EventListener>
new LinkedList<>()
conf.asynchronousListeners.add(new TracingListener())
EventListeners can be
registered to process events emitted during the analysis. Listeners can be
either synchronous or asynchronous, and are registered through the
synchronousListeners and asynchronousListeners options, respectively.
Synchronous listeners will be executed in the same thread as the analysis
itself, and thus will block the analysis until they complete. Asynchronous
listeners will be executed in a separate thread, and thus will not block the
analysis. Synchronous listeners are executed before asynchronous ones, and
the order of execution of the listeners preservers the insertion order into
the respective collection.
The lists of listeners should only be added to, and not
replaced with other (possibly immutable) lists, as LiSA might add new
listeners depending on the values of other options.
LiSA bundles the following event listeners, that can be used both synchronously and asynchronously:
- it.unive.lisa.listeners.BottomTopListener : An event listener that traces bottom and top elements generated during the analysis starting from states that are not bottom or top.
- it.unive.lisa.listeners.CallResolutionListener : An event listener that issues notices on call resolution events.
-
it.unive.lisa.listeners.FlameGraphListener
: An event listener that traces
StartEvents andEndEvents to build a flame graph-style html page. -
it.unive.lisa.listeners.TracingListener
: An event listener that traces
StartEvents andEndEvents to a trace file, constructing a timeline of how the analysis performed.
When implementinc custom listeners, the following events are issued automatically by the analysis:
- it.unive.lisa.analysis.events.AnalysisAssignEnd : An event signaling the end of an assignment of a value to a symbolic expression during the analysis.
- it.unive.lisa.analysis.events.AnalysisAssignStart : An event signaling the start of an assignment of a value to a symbolic expression during the analysis.
- it.unive.lisa.analysis.events.AnalysisAssumeEnd : An event signaling the end of an assumption of a symbolic expression during the analysis.
- it.unive.lisa.analysis.events.AnalysisAssumeStart : An event signaling the start of an assumption of a symbolic expression during the analysis.
- it.unive.lisa.analysis.events.AnalysisErrorsToExecEnd : An event signaling the end of the transfer of error states to the execution state during the analysis.
- it.unive.lisa.analysis.events.AnalysisErrorsToExecStart : An event signaling the start of the transfer of error states to the execution state during the analysis.
- it.unive.lisa.analysis.events.AnalysisExecToErrorEnd : An event signaling the end of the transfer of the execution state to an error state during the analysis.
- it.unive.lisa.analysis.events.AnalysisExecToErrorStart : An event signaling the start of the transfer of the execution state to an error state during the analysis.
- it.unive.lisa.analysis.events.AnalysisExecToHaltEnd : An event signaling the end of the transfer of the execution state to the halting state during the analysis.
- it.unive.lisa.analysis.events.AnalysisExecToHaltStart : An event signaling the start of the transfer of the execution state to the halting state during the analysis.
- it.unive.lisa.analysis.events.AnalysisOnCallReturnEnd : An event signaling the end of the context transfer from a callee back to the caller during the analysis.
- it.unive.lisa.analysis.events.AnalysisOnCallReturnStart : An event signaling the start of the context transfer from a callee back to the caller during the analysis.
- it.unive.lisa.analysis.events.AnalysisRemoveCaughtEnd : An event signaling the end of the removal of caught errors during the analysis.
- it.unive.lisa.analysis.events.AnalysisRemoveCaughtStart : An event signaling the start of the removal of caught errors during the analysis.
- it.unive.lisa.analysis.events.AnalysisSatisfiesEnd : An event signaling the end of a satisfiability test of a symbolic expression during the analysis.
- it.unive.lisa.analysis.events.AnalysisSatisfiesStart : An event signaling the start of a satisfiability test of a symbolic expression during the analysis.
- it.unive.lisa.analysis.events.AnalysisSmallStepEnd : An event signaling the end of a semantics computation of a symbolic expression during the analysis.
- it.unive.lisa.analysis.events.AnalysisSmallStepStart : An event signaling the start of a semantics computation of a symbolic expression during the analysis.
- it.unive.lisa.analysis.events.AnalysisTransferThrowersEnd : An event signaling the end of the transfer of throwers during the analysis.
- it.unive.lisa.analysis.events.AnalysisTransferThrowersStart : An event signaling the start of the transfer of throwers during the analysis.
- it.unive.lisa.analysis.events.DomainAssignEnd : An event signaling the end of an assignment of a value to a symbolic expression by a domain taking part in the analysis.
- it.unive.lisa.analysis.events.DomainAssignStart : An event signaling the start of an assignment of a value to a symbolic expression by a domain taking part in the analysis.
- it.unive.lisa.analysis.events.DomainAssumeEnd : An event signaling the end of an assumption of a symbolic expression by a domain taking part in the analysis.
- it.unive.lisa.analysis.events.DomainAssumeStart : An event signaling the start of an assumption of a symbolic expression by a domain taking part in the analysis.
- it.unive.lisa.analysis.events.DomainSatisfiesEnd : An event signaling the end of a satisfiability test of a symbolic expression by a domain taking part in the analysis.
- it.unive.lisa.analysis.events.DomainSatisfiesStart : An event signaling the start of a satisfiability test of a symbolic expression by a domain taking part in the analysis.
- it.unive.lisa.analysis.events.DomainSmallStepEnd : An event signaling the end of a semantics computation of a symbolic expression by a domain taking part in the analysis.
- it.unive.lisa.analysis.events.DomainSmallStepStart : An event signaling the start of a semantics computation of a symbolic expression by a domain taking part in the analysis.
- it.unive.lisa.analysis.events.HeapRewriteEnd : An event signaling the end of the rewrite of a symbolic expression by the heap domain.
- it.unive.lisa.analysis.events.HeapRewriteStart : An event signaling the start of the rewrite of a symbolic expression by the heap domain.
-
it.unive.lisa.analysis.events.SADSubsEnd
: An event signaling the end of the substitutions application by a
SimpleAbstractDomain. -
it.unive.lisa.analysis.events.SADSubsStart
: An event signaling the start of the substitutions application by a
SimpleAbstractDomain. - it.unive.lisa.analysis.nonrelational.events.NRDEvalEnd : An event signaling the end of the evaluation of a symbolic expression by a non-relational domain taking part in the analysis.
- it.unive.lisa.analysis.nonrelational.events.NRDEvalStart : An event signaling the start of the evaluation of a symbolic expression by a non-relational domain taking part in the analysis.
- it.unive.lisa.interprocedural.callgraph.events.CallResolved : An event signaling that a call has been resolved.
- it.unive.lisa.interprocedural.events.CFGFixpointEnd : An event signaling the end of the fixpoint computation for a given CFG in the interprocedural analysis.
- it.unive.lisa.interprocedural.events.CFGFixpointStart : An event signaling the start of the fixpoint computation for a given CFG in the interprocedural analysis.
- it.unive.lisa.interprocedural.events.CFGFixpointStored : An event signaling that the result of a cfg fixpoint has been stored in the results cache. The stored version might be different from the actual result, since some lattice operations might have been applied with some pre-existing result.
- it.unive.lisa.interprocedural.events.ComputedCallResult : An event signaling that the analysis computed the given result for a given call.
- it.unive.lisa.interprocedural.events.ComputedCallState : An event signaling that the analysis has computed the state for a given call.
- it.unive.lisa.interprocedural.events.FixpointEnd : An event signaling the end of program-wide fixpoint of the interprocedural analysis.
- it.unive.lisa.interprocedural.events.FixpointIterationEnd : An event signaling the end of a fixpoint iteration during an interprocedural analysis.
- it.unive.lisa.interprocedural.events.FixpointIterationStart : An event signaling the start of a fixpoint iteration during an interprocedural analysis.
- it.unive.lisa.interprocedural.events.FixpointStart : An event signaling the start of program-wide fixpoint of the interprocedural analysis.
- it.unive.lisa.interprocedural.events.PrecomputedCallResult : An event signaling that the analysis found an existing compatible result for a given call.
- it.unive.lisa.interprocedural.events.RecursionEnd : An event signaling the end of the recursion solving during an interprocedural analysis.
- it.unive.lisa.interprocedural.events.RecursionStart : An event signaling the start of the recursion solving during an interprocedural analysis.
- it.unive.lisa.program.cfg.fixpoints.events.EdgeTraverseEnd : An event signaling the end of the traversal for a given Edge during a fixpoint computation.
- it.unive.lisa.program.cfg.fixpoints.events.EdgeTraverseStart : An event signaling the start of the traversal for a given Edge during a fixpoint computation.
- it.unive.lisa.program.cfg.fixpoints.events.JoinPerformed : An event signaling that the join operation between subsequent abstractions for the same Statement has been performed during a fixpoint computation.
- it.unive.lisa.program.cfg.fixpoints.events.LeqPerformed : An event signaling that the comparison operation between subsequent abstractions for the same Statement has been performed during a fixpoint computation.
- it.unive.lisa.program.cfg.fixpoints.events.PreStateComputed : An event signaling that the pre-state for a statement has been computed during a fixpoint computation. Depending on the analysis direction, the pre-state might actually be the post-state: we do not distinguish between the two in terms of events for ease of handling.
- it.unive.lisa.program.cfg.fixpoints.events.StatementSemanticsEnd : An event signaling the end of the semantics computation for a given Statement during a fixpoint computation.
- it.unive.lisa.program.cfg.fixpoints.events.StatementSemanticsStart : An event signaling the start of the semantics computation for a given Statement during a fixpoint computation.
Producing Outputs
workdir
String
Paths.get(".").toAbsolutePath().normalize().toString()
conf.workdir = "/tmp/lisa-analysis"
outputs
Collection<LiSAOutput>
new HashSet<>()
conf.outputs.add(new JsonReport())
All Outputs produced by LiSA
are generated inside the working directory specified by the workdir option.
By default, the working directory is the directory where the JVM executing LiSA
was launched, but it can be customized by setting the workdir option to a
different path. To generate a new output, it is sufficient to add it to the
collection of outputs specified by the outputs option. Note that the order of
generation of the outputs is not guaranteed, and should not be relied upon.
The collection of outputs to produce should only be added to, and not replaced with
other (possibly immutable) collections, as LiSA might add new outputs depending on
on the values of other options.
LiSA includes the following output generators:
-
it.unive.lisa.outputs.DotCallGraph
: An output that dumps the
CallGraphproduced by the analysis, if any, in dot format. - it.unive.lisa.outputs.DotInputs : An output that dumps each input cfg as a dot file, with no information on the analysis results.
- it.unive.lisa.outputs.DotResults : An output that dumps each input cfg as a dot file, including the results produced by the analysis.
-
it.unive.lisa.outputs.HtmlCallGraph
: An output that dumps the
CallGraphproduced by the analysis, if any, in html format. - it.unive.lisa.outputs.HtmlInputs : An output that dumps each input cfg as an html file, optionally including subnodes, with no information on the analysis results.
- it.unive.lisa.outputs.HtmlResults : An output that dumps each input cfg as an html file, optionally includiong subnodes, including the results produced by the analysis.
-
it.unive.lisa.outputs.JSONCallGraph
: An output that dumps the
CallGraphproduced by the analysis, if any, in json format. - it.unive.lisa.outputs.JSONInputs : An output that dumps each input cfg as a json file, with no information on the analysis results.
- it.unive.lisa.outputs.JSONReportDumper : An output that dumps the analysis report in JSON format to "report.json".
- it.unive.lisa.outputs.JSONResults : An output that dumps each input cfg as a json file, including the results produced by the analysis.
Logging
Logging is not configured through the LiSAConfiguration object. LiSA produces
all logging through log4j2,
and will thus follow the framework’s own configuration. There are a number of
ways to configure log4j2, but the simplest one is to create a log4j2.xml file
in the working directory, with the desired configuration. For example, the
following configuration will log all messages of level DEBUG or higher to a file
the console:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" name="DefaultLoggingConf">
<Appenders>
<Console name="console">
<PatternLayout pattern="%d [%5level] %m %ex%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="it.unive.lisa" level="DEBUG" />
<Logger name="org.reflections" level="WARN" />
<Root level="DEBUG">
<AppenderRef ref="console" level="DEBUG"/>
</Root>
</Loggers>
</Configuration>
Please check log4j2’s documentation for more details on how to configure logging.
If no logging is configured, LiSA will set up a default configuration that logs to the console only.
Default and Test Configuration
LiSA also offers a class named DefaultConfiguration, that provides a default
value for interprocedural analysis and call graph. It also offers utility
methods for building an abstract domain following the
Simple Abstract Domain framework.
A common use case when developing static analyzers is to have end-to-end tests
that starts from an input file and the necessary configuration, execute a full
analysis as a black box, and compare the results obtained with some pre-existing
results. LiSA provides a unique infrastructure for this use case to simplify
testing. A TestConfiguration is a LiSAConfiguration extended with the
following fields:
testDirdefines the relative path to the root folder where test files are located;testSubDirdefines an optional path relative totestDirto use as workdir for the analysis, useful to keep output files separated for similar tests;programFileholds the name of the source file to analyze, relative totestDir;forceUpdatespecifies that, should any difference be found between the results of the analysis and the pre-existing results, the pre-existing results should be updated with the new results instead of raising an error;compareWithOptimizationspecifies that, should no difference be found between the results of the analysis and the pre-existing results, the analysis should be executed again with optimizations enabled, and the results of the optimized analysis should be compared with the pre-existing results as well, to check that optimizations do not change the results of the analysis;resultComparerholds a reference to theResultComparerinstance to use to compare the results of the analysis with the pre-existing results, that can be customized to ignore irrelevant differences between the results or additional analyzer-specific settings.
A TestConfiguration can be used with an instance of AnalysisTestExecutor, an
abstract class defined in LiSA to provide the standard workflow for executing
end-to-end tests. The class has a constructor that takes the path to the
expected results folder, where the pre-existing results are located, and a path
to the actual results folder, where test files for produced by the analysis will
be generated. The analysis is started by invoking one of the perform
overloads, each accepting a TestConfiguration and optionally an already parsed
Program. If the program is not provided, the abstract readProgram method
will be invoked to parse the file located at expected-dir/testDir/programFile.
Then, the execution proceeds as follows (in the following, if testSubDir is
null, testDir/testSubDir should be read as testDir):
- the folder
actual-dir/testDir/testSubDiris cleared of all files, and it is set as workdir for the analysis; - an instance of
JSONReportDumperis added to the outputs to produce; - a
LiSAinstance is created with theTestConfigurationas configuration, and it is run on the parsed program; - if no
expected-dir/testDir/testSubDir/report.jsonfile exists andforceUpdateis not set, the execution terminates; - if no
expected-dir/testDir/testSubDir/report.jsonfile exists andforceUpdateis set, the contents of the workdir will be copied toexpected-dir/testDir/testSubDir, and the execution terminates; - if
expected-dir/testDir/testSubDir/report.jsonexists andforceUpdateis not set, theresultCompareris used to compare it withactual-dir/testDir/testSubDir/report.json, raising an exception if there are any differences; - if
expected-dir/testDir/testSubDir/report.jsonexists andforceUpdateis set, theresultCompareris used to compare it withactual-dir/testDir/testSubDir/report.json, and all files where at least one difference is found are copied from the workdir toexpected-dir/testDir/testSubDir, replacing the pre-existing files; - if
compareWithOptimizationis not set, or if it is set but the fixpoints used were already optimized, the execution terminates; - if
compareWithOptimizationis set and the fixpoints used were not already optimized, the whole process is repeated with the same configuration but with optimized fixpoints, and the results of the optimized analysis are compared with the pre-existing results as well, raising an exception if there are any differences (in this run,forceUpdateis ignored).
Alternatively to forceUpdate, the lisa.cron.update system property can be set
to true to achieve the same effect.
Frontends
Frontends for several languages have been developed over the years. Recall that frontends are not part of LiSA, but rather they are fully-fledged static analysers that use LiSA as a library to execute the analysis. They can be used as-is, can be extended, or can be used as examples to build new frontends for other languages. For more details on how to build a frontend, please check the frontend documentation.
GoLiSA
GoLiSA is a frontend for a subset of the Go programming language. It has been developed with the objective of performing analyses targeting security properties of blockchain software and smart contracts, focusing on the frameworks Hyperledger Fabric, Cosmos SDK, Ethereum Client, and Tendermint Core. The properties targeted include harmful usage of non-deterministic APIs and constructs, dangerous cross-contract invocations, and read-write inconsistencies.
For more information on features and usage, refer to the GitHub repository.
JLiSA
JLiSA is a frontend for a subset of the Java programming language, developed primarely for the SV-COMP competition. It is able to identify failing assertions and uncaught runtime exceptions, and models a subset of the Java APIs.
For more information on features and usage, refer to the GitHub repository.
PyLiSA
PyLiSA is a highly-experimental frontend for a subset of the Python programming language. Specifically, it does not yet support any dynamic features related to changes to the program structure (e.g., dynamic code loading, dynamic attribute access, etc.), and it only supports a subset of the Python APIs. PyLiSA has been developed for two distinct scenarios: the analysis of Data Science scripts using Pandas, and the reconstruction of architectural diagrams for ROS2 robotic networks.
For more information on features and usage, refer to the GitHub repository.
EVMLiSA
EVMLiSA is a frontend for EVM bytecode aimed at soundly reconstructing a full and sound control flow graph from the bytecode itself, that can be used in later analyses. The reconstruction can optionally be made aware of the current global storage to improve the accuracy of the generated control flow graph.
For more information on features and usage, refer to the GitHub repository.
MichelsonLiSA
MichelsonLiSA is a frontend for Michelson bytecode obtained by the compilation of smart contracts for Tezos blockchains. It is based on an SSA translation of the bytecode, and it is aimed at detecting cross-contracts invocations.
For more information on features and usage, refer to the GitHub repository.