Detecting Pattern-Match Failures in Haskell...HsColour: Problem 4 zA pattern match without a [] case...
Transcript of Detecting Pattern-Match Failures in Haskell...HsColour: Problem 4 zA pattern match without a [] case...
Detecting Pattern-Match Failures in Haskell
Neil Mitchell andColin RuncimanYork University
www.cs.york.ac.uk/~ndm/catch
Does this code crash?
risers [] = []risers [x] = [[x]]risers (x:y:etc) =
if x ⤠y then (x:s) : ss else [x] : (s:ss)where (s:ss) = risers (y:etc)
> risers [1,2,3,1,2] = [[1,2,3],[1,2]]
Does this code crash?
risers [] = []risers [x] = [[x]]risers (x:y:etc) =
if x ⤠y then (x:s) : ss else [x] : (s:ss)where (s:ss) = risers (y:etc)
> risers [1,2,3,1,2] = [[1,2,3],[1,2]]Potential crash
Does this code crash?
risers [] = []risers [x] = [[x]]risers (x:y:etc) =
if x ⤠y then (x:s) : ss else [x] : (s:ss)where (s:ss) = risers (y:etc)
> risers [1,2,3,1,2] = [[1,2,3],[1,2]]Potential crash
Property:risers (_:_) = (_:_)
Overview
The problem of pattern-matchingA framework to solve patternsConstraint languages for the frameworkThe Catch toolA case study: HsColourConclusions
The problem of Pattern-Matching
head (x:xs) = x
head x_xs = case x_xs ofx:xs â x[] â error âhead []â
Problem: can we detect calls to error
Haskell programs âgo wrongâ
âWell-typed programs never go wrongâBut...â Incorrect result/actions â requires annotationsâ Non-termination â cannot always be fixedâ Call error â not much research done
My Goal
Write a tool for Haskell 98â GHC/Haskell is merely a front-end issue
Check statically that error is not calledâ Conservative, corresponds to a proof
Entirely automaticâ No annotations
= Catch
Preconditions
Each function has a preconditionIf the precondition to a function holds, and none of its arguments crash, it will not crash
pre(head x) = x â {(:) _ _}pre(assert x y) = x â {True}pre(null x) = True pre(error x) = False
Properties
A property states that if a function is called with arguments satisfying a constraint, the result will satisfy a constraint
x â {(:) _ _} â (null x) â {True}x â {(:) [] _} â (head x) â {[]}x â {[]} â (head x) â {True}
Calculation direction
Checking a Program (Overview)
Start by calculating the precondition of mainâ If the precondition is True, then program is safe
Calculate other preconditions and properties as necessary
Preconditions and properties are defined recursively, so take the fixed point
Checking risers
risers r = case r of[] â []x:xs â case xs of
[] â (x:[]) : []y:etc â case risers (y:etc) of
[] â error âpattern matchâs:ssâ case x ⤠y of
True â (x:s) : ssFalse â [x] : (s:ss)
Checking risers
risers r = case r of[] â []x:xs â case xs of
[] â (x:[]) : []y:etc â case risers (y:etc) of
[] â error âpattern matchâs:ssâ case x ⤠y of
True â (x:s) : ssFalse â [x] : (s:ss)
Checking risers
risers r = case r of[] â []x:xs â case xs of
[] â (x:[]) : []y:etc â case risers (y:etc) of
[] â error âpattern matchâs:ssâ case x ⤠y of
True â (x:s) : ssFalse â [x] : (s:ss)
r â {[]} â¨xs â {[]} â¨
risers (y:etc) â {(:) _ _}
Checking risers
risers r = case r of[] â []x:xs â case xs of
[] â (x:[]) : []y:etc â case risers (y:etc) of
[] â error âpattern matchâs:ssâ case x ⤠y of
True â (x:s) : ssFalse â [x] : (s:ss) ... ⨠[x] : (s:ss)
â {(:) _ _}
... ⨠(x:s) : ssâ {(:) _ _}
... ⨠âĽâ {(:) _ _}
... ⨠(x:[]) : []â {(:) _ _}
r â {(:) _ _} â¨[] â {(:) _ _}
Checking risers
risers r = case r of[] â []x:xs â case xs of
[] â (x:[]) : []y:etc â case risers (y:etc) of
[] â error âpattern matchâs:ssâ case x ⤠y of
True â (x:s) : ssFalse â [x] : (s:ss) ... ⨠[x] : (s:ss)
â {(:) _ _}
... ⨠(x:s) : ssâ {(:) _ _}
... ⨠âĽâ {(:) _ _}
... ⨠(x:[]) : []â {(:) _ _}
r â {(:) _ _} â¨[] â {(:) _ _}
Property:r â {(:) _ _} â
risers r â {(:) _ _}
Checking risers
risers r = case r of[] â []x:xs â case xs of
[] â (x:[]) : []y:etc â case risers (y:etc) of
[] â error âpattern matchâs:ssâ case x ⤠y of
True â (x:s) : ssFalse â [x] : (s:ss)
r â {[]} â¨xs â {[]} â¨
risers (y:etc) â {(:) _ _}
Property:r â {(:) _ _} â
risers r â {(:) _ _}
r â {[]} â¨xs â {[]} â¨
y:etc â {(:) _ _}
Checking risers
risers r = case r of[] â []x:xs â case xs of
[] â (x:[]) : []y:etc â case risers (y:etc) of
[] â error âpattern matchâs:ssâ case x ⤠y of
True â (x:s) : ssFalse â [x] : (s:ss)
Property:r â {(:) _ _} â
risers r â {(:) _ _}
Calculating Preconditions
Variables: pre(x) = Trueâ Always True
Constructors: pre(a:b) = pre(a) ⧠pre(b)â Conjunction of the children
Function calls: pre(f x) = x â pre(f) ⧠pre(x)â Conjunction of the childrenâ Plus applying the preconditions of fâ Note: precondition is recursive
Calculating Preconditions (case)
pre(case on of[] â ax:xs â b)
= pre(on) ⧠(on â {[]} ⨠pre(a))⧠(on â {(:) _ _} ⨠pre(b))
An alternative is safe, or is never reached
Extending Constraints (â)
risers r = case r of[] â []x:xs â case xs of
[] â (x:[]) : []y:etc â ...
xs â {(:) _ _} ⨠...r<(:)-2> â {(:) _ _}r â {(:) _ ((:) _ _)}
<(:)-2> â {(:) _ _}{(:) _ ((:) _ _)}
<(:)-1> â {True}{(:) True _}
Splitting Constraints (â)
risers r = case r of[] â []x:xs â case xs of
[] â (x:[]) : []y:etc â ...
(x:[]):[] â {(:) _ _} ⨠...True
((:) 1 2) â {(:) _ _}True
((:) 1 2) â {[]}False
((:) 1 2) â {(:) True []}1 â {True} ⧠2 â {[]}
Summary so far
Rules for PreconditionsHow to manipulate constraintsâ Extend (â) â for locally bound variablesâ Split (â) â for constructor applicationsâ Invoke properties â for function application
Can change a constraint on expressions, to one on function arguments
Algorithm for Preconditions
set all preconditions to Trueset error precondition to Falsewhile any preconditions change
recompute every preconditionend while
Algorithm for properties is very similar
Fixed Point!
Fixed Point
To ensure a fixed point exists demand only a finite number of possible constraintsAt each stage, (â§) with the previous precondition
Ensures termination of the algorithmâ But termination â useable speed!
The Basic Constraints
These are the basic ones I have introduced
Not finite â but can bound the depthâ A little arbitraryâ Canât represent infinite data structures
But a nice simple introduction!
A Constraint System
Finite number of constraintsExtend operator (â)Split operator (â)notin creation, i.e. x â {(:) _ _)}Optional simplification rules in a predicate
Regular Expression Constraints
Based on regular expressionsx â r â câ r is a regular expression of paths, i.e. <(:)-1>â c is a set of constructorsâ True if all r paths lead to a constructor in c
Split operator (â) is regular expression differentiation/quotient
RE-Constraint Examples
head xsâ xs â (1 â {:})
map head xsâ xs â (<(:)-2>* â <(:)-1> â {:})
map head (reverse xs)â xs â (<(:)-2>* â <(:)-1> â {:}) â¨
xs â (<(:)-2>* â {:})
RE-Constraint Problems
They are finite (with certain restrictions)But there are many of them!Some simplification rulesâ Quite a lot (19 so far)â Not complete
In practice, too slow for moderate examples
This fact took 2 years to figure
out!
Multipattern Constraints
Idea: model the recursive and non-recursive components separately
Given a listâ Say something about the first elementâ Say something about all other elementsâ Cannot distinguish between element 3 and 4
MP-Constraint Examples
head xsâ xs â ({(:) _} â {[], (:) _})
Use the typeâs to determine recursive bits
xs must be (:)xs.<(:)-1> must be _
All recursive tailsare unrestricted
More MP-Constraint Examples
map head xsâ {[], (:) ({(:) _} â {[], (:) _})} â
{[], (:) ({(:) _} â {[], (:) _})}
An infinite listâ {(:) _} â {(:) _}
MP-Constraint âsemanticsâ
MP = {set Val}
Val = _ | {set Pat} â {set Pat}
Pat = Constructor [(non-recursive field, MP)]
Element must satisfy at least one pattern
Each recursive part must satisfy at least one pattern
MP-Constraint Split
((:) 1 2) â {(:) _} â {(:) {True}}â An infinite list whose elements (after the first) are
all true1 â _2 â {(:) {True}} â {(:) {True}}
MP-Constraint Simplification
There are 8 rules for simplificationâ Still not complete...
But!â x â a ⨠x â b = x â c union of two setsâ x â a ⧠x â b = x â c cross product of two sets
MP-Constraint Currying
We can merge all MPâs on one variableWe can curry all functions â so each has only one variableMP-constraint Predicate ⥠MP-constraint
(||) a b (||) (a, b)
MP vs RE constraints
Both have different expressive powerâ Neither is a subset/superset
RE-constraints grow too quicklyMP-constraints stay much smaller
Therefore Catch uses MP-constraints
Numbers
data Int = Neg | Zero | One | Pos
Checksâ Is positive? Is natural? Is zero?
Operationsâ (+1), (-1)
Workâs very well in practice
Summary so far
Rules for Preconditions and PropertiesCan manipulate constraints in terms of three operationsMP and RE Constraints introducedHave picked MP-Constraints
Making a Tool (Catch)
Haskell
Core
First-order Core
Curried
Analyse
Yhc
In draft paper,see website
This talk
Testing Catch
The nofib benchmark suite, but
main = do [arg] â getArgsprint $ primes !! (read arg)
Benchmarks have no real usersPrograms without real users crash
Nofib/Imaginary Results (14 tests)
Trivially Safe
Perfect Answer
Good Failures
Bad Failures
Good failure:Did not get perfect answer,
but neither did I!
Bad Failure: Bernouilli
tail (tail x)
Actual condition: list is at least length 2Inferred condition: list must be infinite
drop 2 x
Bad Failure: Paraffins
radical_generator n = f undefinedwhere f unused = big_memory_result
array :: Ix a â (a, a) â [(a, b)] â Array a bâ Each index must be in the given rangeâ Array indexing also problematic
Perfect Answer: Digits of E2
e =(â2.â ++) $tail â concat $map (show â head) $iterate (carryPropagate 2 â map (10*) â tail) $2 : [1,1 ..]
Performance of Catch
012345678
0 200 400 600 800 1000 1200 1400
Source Code
Tim
e (S
econ
ds)
Case Study: HsColour
Takes Haskell source code and prints out a colourised version4 years old, 6 contributors, 12 modules, 800+ lines
Used by GHC nightly runs to generate docsUsed online by http://hpaste.org
HsColour: Bug 1
data Prefs = ... deriving (Read,Show)Uses read/show serialisation to a filereadFile prefs, then read result
Potential crash if the user has modified the fileReal crash when Prefâs structure changed!
FIXED
HsColour: Bug 1 Catch
> Catch HsColour.hsCheck âPrelude.read: no parseâPartial Prelude.read$252Partial Language.Haskell.HsColour
.Colourise.parseColourPrefs...Partial Main.main
Full log is recordedAll preconditionsand properties
HsColour: Bug 2
The latex output mode had:outToken (â\ââ:xs) = â``â ++ init xs ++ ââââ
file.hs: âhscolour âlatex file.hsCrash
FIXED
HsColour: Bug 3
The html anchor output mode had:outToken (â`â:xs) = â<a>â ++ init xs ++ â</a>â
file.hs: (`)hscolour âhtml âanchor file.hsCrash
FIXED
HsColour: Problem 4
A pattern match without a [] caseA nice refactoring, but not a crashProof was complex, distributed and fragileâ Based on the length of comment lexemes!
End result: HsColour cannot crashâ Or could not at the date I checked it...
Required 2.1 seconds, 2.7Mb
CHANGED
Case Study: FiniteMap library
Over 10 years old, was a standard library14 non-exhaustive patterns, 13 are safe
delFromFM (Branch key ..) del_key| del_key > key = ...| del_key < key = ...| del_key ⥠key = ...
Case Study: XMonad
Haskell Window ManagerCentral module (StackSet)Checked by Catch as a library
No bugs, but suggested refactoringsMade explicit some assumptions about Num
Catchâs Failings
Weakest Area: Yhcâ Conversion from Haskell to Core requires Yhcâ Can easily move to using GHC Core (once fixed)
2nd Weakest Area: First-order transformâ Still working on thisâ Could use supercompilation
??-Constraints
Could solve more complex problemsCould retain numeric constraints preciselyIdeally have a single normal form
MP-constraints work well, but there is room for improvement
Alternatives to Catch
Reach, SmallCheck â Matt Naylor, Colin Râ Enumerative testing to some depth
ESC/Haskell - Dana Xuâ Precondition/postcondition checking
Dependent types â Epigram, Cayenneâ Push conditions into the types
Conclusions
Pattern matching is an important area that has been overlookedFramework separate from constraintsâ Can replace constraints for different power
Catch is a good step towards the solutionâ Practical toolâ Has found real bugs
www.cs.york.ac.uk/~ndm/catch