2584 bad programs with known types
-
74 do not fail dynamically
-
2510 do fail dynamically
- 91 fail with unbound variable (possible bug in program extractor)
- of remaining 2419:
- average of 2 tests needed to trigger runtime exception
-
OCaml primitives are listed in
bytecomp/lambda.mli
bytecomp/translcore.ml
external f : t = "prim"
defines primitive wrapper- stdlib implemented with many
external
- should implement actual primitives, then load stdlib from source
[X] ADTs
[ ] extend interpreter with "fail safe"
- when program would go wrong, e.g. int + bool
- just return one, e.g. int
- count number of places where program goes wrong picking one vs other
[ ] from symptom to cause
- simplify counterexample trace
[X] collect sample outputs to webpage (with trace)
[ ] look at physical subtyping
- (similar to soft typing)
- also discriminative union types
[X] dig up extra benchmarks from other "locating type error papers"
- andrew myers
- dan grossman
[X] configurable failure options
- heterogeneous equality vs. homogeneous
- check ADT arguments at application site (IMPORTANT FOR SEMINAL)
- need to carry fully instantiated type around with values (type-carrying operational semantics)
- STILL NEED TO MAKE IT CONFIGURABLE
[ ] add post-hoc generalization a la smartcheck
[ ] go backwards from crash
- where did value
x
come from? - PSE (ESP done backwards) dump checker
[X] for seminal programs that don't crash, WHY??
-
20060305-21:49:26-9fc379ff0ebf471cca400f4c3b144205.seminal.ml
- just need a type for
mapHelp
so we actually generate inputs
- just need a type for
-
20060305-21:56:00-9af22179cf4408715cbf337f5c3a534f.seminal.ml
- used if-then instead of if-then-else, type mismatch between [] and ()
-
20060316-19:34:40-21991f746ce1d63c2b7ecd57ca2fccea.seminal.ml
- partially applied function, so we don't reduce anything, need to supply extra arg
-
20060321-17:38:52-5224914e10b7b5eacd09bb21ab53a674.seminal.ml
- type mismatch in result of match, () vs string
- value is discarded though (with ;) so it can't go wrong
-
USED TO PASS, BUT NO LONGER DO
- 20060313-22:39:42-644d210e9d66cf006f61c64435b0efb3.seminal.ml
20060313-23:03:58-c9d9c96a5c92d44ea888b2bf8287ed92.seminal.ml
- datacon name shadowing
- we should probably forbid shadowing datacons, even though ocaml allows it..
- 20060313-22:39:42-644d210e9d66cf006f61c64435b0efb3.seminal.ml
20060313-23:03:58-c9d9c96a5c92d44ea888b2bf8287ed92.seminal.ml
[ ] a few concrete examples of programs that crash at ML level
[X] add extra language features for seminal benchmarks
- records
- references
- arrays
[ ] keep going after MLExceptions
- ml exceptions are annoying, because they can prevent access to the ill-typed code, e.g.
let interpSmall (movelist : move list) : (float*float) list =
let interpSmallStep movelist x y dir : move list * float * float * float =
match movelist with
[] -> raise Unimplemented
| Home::tl -> raise Unimplemented
| Forward(f)::tl -> raise Unimplemented
| Turn(f)::tl -> raise Unimplemented
| For(i,lst)::tl -> raise Unimplemented
in
let rec loop movelist x y dir acc =
raise Unimplemented
in
List.rev (loop movelist 0.0 0.0 0.0 [(0.0,0.0)])
- `List.rev (loop ...)` is ill-typed, but we can't reach it since
`interpSmallStep` always throws an exception.
- IDEA: throw exceptions *lazily*
- wrap `raise Foo` in a thunk
- when is thunk forced? (maybe when part of primitive application?)
- this won't help the example above. are there actual benchmarks where it would?
- IDEA: replace `raise Foo` with random value of expected type
- ml exceptions are uninteresting, they don't signify a type error
- don't kill test harness on first MLException
- may only be thrown on a particular branch
- keep log of tests that threw MLException
- may indicate that type error is unreachable
- don't kill test harness on first MLException
[X] don't try to specialize datacons with multiple arguments, just let them be tuples...
# type ('a,'b) myoption = Mynone | Mysome of ('a * 'b);;
# let Mysome x = Mysome (1,2);;
val x : int * int = (1, 2)
- turns out OCaml distinguishes between datacons that take multiple args vs a tuple, we just have to mimic the behavior..
[ ] Just evaluate the crash
- Given a program trace, remove everything that is not related to the crash
- RELATED: use lazy-smallcheck style holes for function arguments and exceptions
- iterative refinement of input vs post-hoc generalization from multiple witnesses
[ ] visualizations
- todd millstein interactive d3.js
- plt-redex??
[ ] generate tree of reductions
- given an input term, return all possible sequences of reductions
- find shortest path that goes wrong
- interactive visualization to choose path
fun (f,b) ->
let f = fun b -> (x,y) in
if y = true then wwhile (f,x) else x
==>
fun (f,b) ->
let f = fun b -> (x,y) in
if y = true then wwhile (f,x) else x
wwhile := fun (f,b) ->
let f = fun b -> (x,y) in
if y = true then wwhile (f,x) else x
[ ] compress reduction steps
- "recursive explanation algorithm"
- crash because
1 * true
- where did
true
come from?fac 0
- crash because
- which are the salient bits in an explanation?
[ ] what are the claims to make in the paper?
- expressive? efficient? usable?
- design tool / experiment to support claims
- measure the number choices a human would have to make to reach the "fix" they made
- grab bad program and subsequent fix
- how many steps in tool to reach changed expression
[ ] sketch out exploration for some examples
fac
, let-swap bug, a few others- for each, show a single ideal trace, e.g.
- 5: 1 * true (* pick [1] or [2] *)
- 3: true
------------------------------------------------------------
1 let rec fac n =
2 if n = 0 then
3 true
4 else
5 n * fac (n - 1);;
6
7 fac 1;;
error: 5: could not match 'int' with 'bool' in
1 * true
[1]: 1
[2]: *
[3]: true
> 3
------------------------------------------------------------
1 let rec fac n =
2 if n = 0 then
3 true
4 else
5 n * fac (n - 1);;
6
7 fac 1;;
error: 5: could not match 'int' with 'bool' in
1 * true
fac 0 ==> true
[1]: fac
[2]: 0
> 2
------------------------------------------------------------
1 let rec fac n =
2 if n = 0 then
3 true
4 else
5 n * fac (n - 1);;
6
7 fac 1;;
error: 5: could not match 'int' with 'bool' in
1 * true
5: fac 0 ==> true
5: 1 - 1 ==> 0
[1]: 1
[2]: -
[3]: 1
> 1
------------------------------------------------------------
1 let rec fac n =
2 if n = 0 then
3 true
4 else
5 n * fac (n - 1);;
6
7 fac 1;;
error: could not match 'int' with 'bool' in
5: 1 * true
5: fac 0 ==> true
5: 1 - 1 ==> 0
1: n ==> 1
[1]: n
> 1
------------------------------------------------------------
1 let rec fac n =
2 if n = 0 then
3 true
4 else
5 n * fac (n - 1);;
6
7 fac 1;;
error: could not match 'int' with 'bool' in
5: 1 * true
5: fac 0 ==> true
5: 1 - 1 ==> 0
1: n ==> 1
7: fac 1
[1]: fac
[2]: 1
>
------------------------------------------------------------
1 let (x,y) = ("5", 5);;
2
3 let rec wwhile (f,b) =
4 let f b = (x, y) in
5 if x = true then
6 wwhile (f, x)
7 else x;;
8
9 wwhile (fun x => x, 1);;
error: could not match 'string' with 'bool' in
5: "5" = true
[1]: "5"
[2]: =
[3]: true
> 1
------------------------------------------------------------
1 let (x,y) = ("5", 5);;
2
3 let rec wwhile (f,b) =
4 let f b = (x, y) in
5 if x = true then
6 wwhile (f, x)
7 else x;;
8
9 wwhile (fun x => x, 1);;
error: could not match 'string' with 'bool' in
5: "5" = true
5: x ==> "5"
[1]: x
> 1
------------------------------------------------------------
1 let (x,y) = ("5", 5);;
2
3 let rec wwhile (f,b) =
4 let f b = (x, y) in
5 if x = true then
6 wwhile (f, x)
7 else x;;
8
9 wwhile (fun x => x, 1);;
error: could not match 'string' with 'bool' in
5: "5" = true
5: x ==> "5"
1: let (x,y) = ("5", 5);;
[1]: "5"
[2]: 5
>
------------------------------------------------------------
1 let explode s =
2 let rec go i =
3 if i >= (String.length s)
4 then []
5 else (s.[i]) :: (go (i + 1))
6 in go 0;;
7
8 let listReverse l =
9 let rec helper xs = function
10 | [] -> xs
11 | hd::tl -> helper (hd :: xs) tl
12 in helper [];;
13
14 let palindrome w =
15 if (listReverse (explode w)) = (explode w)
16 then true
17 else false;;
18
19 palindrome "a";;
error: could not match ''a -> 'b' with 'char list' in
15: <fun> = ['a']
[1]: <fun>
[2]: =
[3]: ['a']
> 1
------------------------------------------------------------
1 let explode s =
2 let rec go i =
3 if i >= (String.length s)
4 then []
5 else (s.[i]) :: (go (i + 1))
6 in go 0;;
7
8 let listReverse l =
9 let rec helper xs = function
10 | [] -> xs
11 | hd::tl -> helper (hd :: xs) tl
12 in helper [];;
13
14 let palindrome w =
15 if (listReverse (explode w)) = (explode w)
16 then true
17 else false;;
18
19 palindrome "a";;
error: could not match ''a -> 'b' with 'char list' in
15: <fun> = ['a']
15: listReverse ['a'] ==> <fun>
[1]: listReverse
[2]: ['a']
[3]: single step
> 3
------------------------------------------------------------
1 let explode s =
2 let rec go i =
3 if i >= (String.length s)
4 then []
5 else (s.[i]) :: (go (i + 1))
6 in go 0;;
7
8 let listReverse l =
9 let rec helper xs = function
10 | [] -> xs
11 | hd::tl -> helper (hd :: xs) tl
12 in helper [];;
13
14 let palindrome w =
15 if (listReverse (explode w)) = (explode w)
16 then true
17 else false;;
18
19 palindrome "a";;
error: could not match ''a -> 'b' with 'char list' in
15: <fun> = ['a']
15: listReverse ['a'] ==> <fun>
9: let rec helper xs = ...
in helper []
[1]: single step
> 1
------------------------------------------------------------
1 let explode s =
2 let rec go i =
3 if i >= (String.length s)
4 then []
5 else (s.[i]) :: (go (i + 1))
6 in go 0;;
7
8 let listReverse l =
9 let rec helper xs = function
10 | [] -> xs
11 | hd::tl -> helper (hd :: xs) tl
12 in helper [];;
13
14 let palindrome w =
15 if (listReverse (explode w)) = (explode w)
16 then true
17 else false;;
18
19 palindrome "a";;
error: could not match ''a -> 'b' with 'char list' in
15: <fun> = ['a']
15: listReverse ['a'] ==> <fun>
9: let rec helper xs = ...
in helper []
12: helper []
[1]: helper
[2]: []
[3]: single step
> 3
------------------------------------------------------------
1 let explode s =
2 let rec go i =
3 if i >= (String.length s)
4 then []
5 else (s.[i]) :: (go (i + 1))
6 in go 0;;
7
8 let listReverse l =
9 let rec helper xs = function
10 | [] -> xs
11 | hd::tl -> helper (hd :: xs) tl
12 in helper [];;
13
14 let palindrome w =
15 if (listReverse (explode w)) = (explode w)
16 then true
17 else false;;
18
19 palindrome "a";;
error: could not match ''a -> 'b' with 'char list' in
15: <fun> = ['a']
15: listReverse ['a'] ==> <fun>
9: let rec helper xs = ...
in helper []
12: helper []
9: function
| [] -> []
| hd::tl -> helper (hd :: []) Tl
listReverse (explode w)
==>
(fun l => let rec helper xs = ... in helper []) (explode w)
==>
(fun l => let rec helper xs = ... in helper []) ((fun s => ...) w)
==>
(fun l => let rec helper xs = ... in helper []) ((fun s => ...) "a")
==>
(fun l => let rec helper xs = ... in helper []) ['a']
==>
let rec helper xs = ... in helper []
==>
helper []
==>
(fun xs => function ...) []
==>
(function ...)
-
preliminary results from
Misc.facProg
:- bfs: 30 (19 skipping seen nodes) steps to reach target
true
- dfs: 11 steps
- human: 9 steps (same path as dfs after first backtrack)
- worst-case: 62 nodes in graph
- is this a good example or a pathological case?
- target expression is actually a subterm of the stuck term
- how common will that be?
- bfs: 30 (19 skipping seen nodes) steps to reach target
-
preliminary results from
Misc.badProg
:- bfs: 110 (32 skipping seen nodes) steps to reach target
()
- dfs: 13 steps
- human: 13 steps (same path as dfs)
- worst-case: 100 nodes in graph
- bfs: 110 (32 skipping seen nodes) steps to reach target
-
preliminary results from
Misc.wwhileProg
:- can't even reach target using proposed rules
- worst-case: 52 nodes in graph
-
preliminary results from
Misc.palindromeProg
:- bfs: 12 (11 skipping seen nodes) steps to reach target
helper []
- dfs: 6 steps to reach target
- human: 6 steps (same path as dfs)
- worst-case: 94 nodes in graph
- bfs: 12 (11 skipping seen nodes) steps to reach target
-
will the target expression always be a subterm of the stuck term?
- no, see
Misc.wwhileProg
- type error occurs because
let f b = (x, y)
definesf
instead of callingf
and matching on the result. - target is entire binding, which needs to be reversed
- but the binding of
y
on line 1 is used by the stuck term; so all is not lost, blamingy
on line 1 should highlight the scoping problem.
- type error occurs because
- but for the most part, maybe?
- type errors can appear far removed from their source because of unification variables
- at run-time we always have concrete values, no unification variables
- by extension, perhaps run-time type errors cannot be far removed from their source
- i.e. follow the bad value back to its source
- since ocaml does not have overloading, we always know which value to blame
- not quite true! what if type error is from applying the wrong function/constructor? could have multiple errors
- no, see
-
generate ordered list of interesting paths
- given
prim v1 v2 vn
- paths that end in
vi
that incur type error - path that ends in
prim
(alternatively, path that ends inprim v1 v2 vn
) - paths that end in other
vi
- paths that end in
- given
-
start with source term --> stuck term
-
set of stepping operations
- small step forward
- small step back
- "big" step forward (skip over next function call)
- "big" step back (step back to last function call)
- zoom in on a specific function call
fac 1 ---> 1 * true
==> { big-step back from
1 * true
}fac 1 ---> 1 * fac 0 ---> 1 * true
==> { small-step forward from
1 * fac 0
}fac 1 ---> 1 * fac 0 ---> 1 * if ... then ... else ... ---> 1 * true
==> { click hyper-edge from
fac 0
totrue
}fac 0 ---> true
-
-
catch functions that return different types depending on input
- first run, capture full trace
- if any subsequent run produces a different output type, ERROR
-
demo programs
- fac / fib
- qsort
- recursive sum
- fold
- vanilla-rec vs tail-rec
-
add nonce to ocaml's URLs to identify traces
- can correlate with ocaml-top data
-
list of stuff for UGs to do
- anonymize raw data
- test web ui
- data analysis
- how long does it take students to complete homeworks?
- where do the errors show up?
- which homework?
- which problem?
- what kind of errors do they encounter?
- categories (syntax, monotype, polytype, hof)
- frequency
- how long does it take them to fix the error?
- by category
- by homework (do they get better over time?)
- underlying issue of dealing with large time gaps
- how to decide when they stopped working?
- can use timer events (large gap with no timer events indicates ocaml-top closed)
-
prepare for A/B study
- 5-10 bad programs
- identify source of type error, explain, fix
- A: just ocaml / ocaml-top
- B: ocaml + web ui
- 2 sessions
- 1st after ADT homework
- 2nd?
- willing to pay $20-25/hr
- does this mean new IRB?
- 5-10 bad programs
-
connect the editor, type error, and bad term
- also highlight corresponding source node of selected graph node
-
insert subterm edge to BAD term when the STUCK term contains it as a subterm
-
prepare 20min intro to tool
-
maybe symbolic evaluator to improve coverage
-
test different timeouts
-
debug filter example!!!
let
binder seems to be problematic
-
visualize environment when you click on a node
-
step-into grabs the immediate subterm, not the nested subterm that we want
-
weird criss-crossing of edges between nodes when you do multiple step-intos
-
add step-over, which crunches the next expression down to a value
-
jump forward should jump to the next "return"
- if there are no more beta reductinos?
- or next beta/return whichever comes first?
- ff, env stack (with freshening of shadowed vars), highlight bad subterm
- gather sample problems from logs on goto
let rec wwhile (f,b) =
match f b with
| (b', false) -> b'
| (b', true) -> wwhile (f, b')
let fixpoint (f,b) =
wwhile ((let f' x = ((f x), ((f x) = x)) in f), b)
- dead code issue, specifically,
f'
constraintf : a -> a
butf'
is never used so constraint is vacuous
let rec assoc (d,k,l) =
match l with
| [] -> d
| h::t -> if h = k then
assoc (d, k, t)
else
d
(*
let _ = assoc ((-1), "William", [("william", 23)])
*)
``` # SAFE, but wrong type
UNSAFE EXAMPLES
type expr = | VarX | VarY | Sine of expr | Cosine of expr | Average of expr * expr | Times of expr * expr | Thresh of expr * expr * expr * expr let buildSine e = Sine e let buildX () = VarX let buildY () = VarY let rec build (rand,depth) = match rand (0, depth) with | 0 -> buildX | 1 -> buildY | 2 -> buildSine buildX | _ -> buildX
let removeDuplicates l = let rec helper (seen,rest) = match rest with | [] -> seen | h::t -> let seen' = seen @ h in let rest' = let is_in i = (i mod 2) = 0 in List.filter is_in t in helper (seen', rest') in List.rev (helper ([], l))
let rec digitsOfInt n = if n < 1 then [] else (digitsOfInt (n / 10)) @ [n mod 10]
let digits n = digitsOfInt (abs n)
let rec sumList xs = match xs with | [] -> 0 | h::t -> h + (sumList t)
let rec additivePersistence n = if ((sumList (digitsOfInt (digits n))) ) < 10 then n else additivePersistence (sumList (digitsOfInt (digits n)))
let rec append xs ys = match xs with | [] -> ys | h::t -> let append2 = append t ys in h :: append2
let rec digitsOfInt n = if n < 0 then [] else (match n with | 0 -> [] | _ -> append digitsOfInt (n / 10) [n mod 10])
let listReverse l = let rec reverse xs = match xs with | [] -> xs | h::t -> reverse [h] t in reverse [] l
let rec listReverse l = match l with | [] -> [] | (h::t)::[] -> (listReverse t) @ [h]
(* throws ml exception!!!! *) let _ = listReverse [[1];[2];[3]];;
let rec digitsOfInt n = if n < 1 then [] else (digitsOfInt (n / 10)) @ [n mod 10] let rec sumList xs = match xs with | [] -> 0 | h::t -> h + (sumList t)
let addDigits n = sumList (digitsOfInt n)
let rec digitalRoot n = if n < 10 then n else if (addDigits n) > 9 then digitalRoot (addDigits n)
let rec listReverse l = match l with | [] -> [] | h::t -> (listReverse t) @ [h] let cutTail z = match listReverse z with | [] -> [] | h::t -> t let getHeads x = match listReverse x with | [] -> [] | h::t -> h let explode s = let rec go i = if i >= (String.length s) then [] else (s.[i]) :: (go (i + 1)) in go 0 let rec matchHeads y = match y with | [] -> true | h::t -> if h = (getHeads t) then matchHeads (cutTail t) else false let palindrome w = match explode w with | [] -> true | h::t -> matchHeads (explode w)
let getHead h = match h with | [] -> [] | h::t -> h let getTail t = match t with | [] -> [] | h::t -> t let rec listReverse l = match l with | [] -> [] | h::t -> (listReverse t) @ [h] let explode s = let rec go i = if i >= (String.length s) then [] else (s.[i]) :: (go (i + 1)) in go 0 let rec matchHeads x = match x with | [] -> true | h::t -> if (getHead x) = (getHead (listReverse x)) then matchHeads (getTail (listReverse t)) else false let palindrome w = match explode w with | [] -> true | h::t -> matchHeads (explode w)
let _ = palindrome "bbbb"
``` # WTF, broken.. NOT BROKEN, error is in getHead, which gets type [[a]] -> [a]
, but program still can't go wrong
let rec digitsOfInt n =
if n <= 0 then [] else (digitsOfInt (n / 10)) @ [n mod 10]
let rec sumList xs = match xs with | [] -> 0 | h::t -> h + (sumList t)
let rec additivePersistence n =
let rec recCounter n count =
if n < 10
then count
else recCounter ((sumList (digitsOfInt n)) (count + 1)) in
recCounter n 0
-
SEMFIX
-
analyze by hand random subset of bad programs that we can't crash
- would concolic execution help for them??
-
String.concat
-
prog3754.ml: unbound variable 'n', looks bogus, perhaps buggy guard handling?
-
'assert' is syntax, not unbound