Type safety and exception handling Gabriele Keller Ron - - PowerPoint PPT Presentation
Type safety and exception handling Gabriele Keller Ron - - PowerPoint PPT Presentation
Concepts of Program Design Type safety and exception handling Gabriele Keller Ron Vanderfeesten Overview higher & first-order syntax inference rules, induction tools to talk about languages abstract machines big step and small step
Overview
semantic features tools to talk about languages
static & dynamic scoping static & dynamic typing explicit & implicit typing
language concepts
functional procedural/imperative higher & first-order syntax big step and small step operational semantics abstract machines λ-calculus and de Bruijn indices inference rules, induction (algebraic) data types partial application/function closures value & type environments control stacks parametric polymorphism/ generics explicit & implicit typing
type safety
error/exception handling
Types in programming languages
statically vs dynamically typed strongly vs weakly typed type safe vs non type safe
Static and Dynamic Semantics
- MinHs (as discussed in the lecture) is a type-safe (or strongly typed) language
- What exactly do we mean by this?
- these terms are used by different authors to mean different things
- in general, it refers to guarantees about the run-time behaviour derived from
static properties of the program
- Robin Milner: “Well typed programs never go wrong”
- do not exhibit undefined behaviour
- we define type safety to be the following two properties:
- preservation
- progress
- we look at both preservation and progress in turn
Preservation and Progress
- Preservation:
- Idea: evaluation does not change the type of an expression
- Formally: If ⊢ e : τ and e ↦ e’ , then ⊢ e’ : τ
- Progress:
- Idea: a well-defined program can not get stuck
- Formally: If e is well typed, then either e is a final state, or there exists e’, with
e ↦ e’
- Together is means that a program will either evaluate to a value of the
promised type, or run forever
e1 : τ ↦ e2 : τ ↦ e3 : τ ↦ …
Type Safety
- For any language to be type safe, the progress and preservation properties need
to hold!
- Strictly speaking, the term type safety only makes sense in the context of a
formal static and dynamic semantics
- This is one reason why formal methods in programming languages are essential
- The more expressive a type system is, the more information and assertions the
type checker can derive at compile type
- type systems usually should be decidable
- but there are exceptions
- MinHs is type safe
- we can show that progress and preservation hold!
- but what if the language contains partial operations, like division?
Run-time Errors and Safety
- Stuck states: in a type safe language language, stuck states correspond to ill-
defined programs, e.g.,
- use (+) on values of function type, for example
- treat an integer value as a pointer
- use an integer as function
let x = 1 in x 5
- Unsafe languages/operations do not get stuck
- something happens, but its not predictable and/or portable:
void boom () { void (f*)(void) = 0xdeadbeef; f (); }
Type safe languages
- Which of these languages are type safe?
- C
- C++
- C#
- Haskell
- Python
- Rust
Run-time Errors and Safety
- How can we deal with partial functions, for example division by zero?
Problem: the expression 5/0 is well-typed, but does not evaluate to a value.
- There are two alternatives:
(1) Change static semantics: can we enhance the type system to check for division by zero?
- in general, such a type system would not be decidable
- there exist systems that approximate this
(2) Change dynamic semantics: can we modify the semantics such that the devision by zero does not lead to a stuck state
- this approach is widely used for type safe languages
Γ ⊢ t1 : Int Γ ⊢ t2 : Int Γ ⊢ Div t1 t2 : Int
Run-time Errors and Safety
- Application of a partial function can yield Error
- An Error interrupts any computation
Div v (Num 0) ↦ Error Plus Error e ↦ Error Plus e Error ↦ Error If Error e1 e2 ↦ Error
and so on.....
Run-time errors and Safety
- Typing the Error value:
- a run-time error can have any type
Γ ⊢ Error : ∀ τ. τ
- What type of situations lead to checked run-time errors in Haskell?
Exceptions
- Error handling so far:
- The Error expression to handle run-time errors deterministically aborts the
whole program
- For many applications, this is not the appropriate behaviour
- Exceptions permit a more fine grained response to run-time errors
- Error:
- result of a programming error (e.g., preconditions of a function not met), can be
fixed by fixing the program
- Exception:
- result of expected, but irregular occurrence, can possibly be handled in the
program
Exceptions
- Exceptions in MinHs:
(1) raising (or throwing) an exception: raise e
- e : information about handling of exception
(2) catching an exception: try e1 handle x => e2
- catch expression raised in e1
- exception handler is e2
- access to information about handling exception via x
Exceptions
- Abstract Syntax
- raise e Raise e
- try e1 handle x => e2 Try e1 (x.e2)
- Informal evaluation rules: on try e1 handle x => e2
- evaluate e1, and
- if (Raise v) is encountered during e1, bind x to v and then evaluate e2
Exceptions
- Example:
try
if (y <= 0) then raise -1 else x/y handle err => ....
- try expressions can be nested
- innermost try expression catches
- handler may re-raise an exception
Exceptions
- Observations:
- type of exception values (second argument of raise)
- in many programming languages, this is a fixed type τexc
- may simply be a string or integer (exception code)
- e.g., subclass Throwable in Java
Exceptions - Static Semantics
- Typing rules
Γ ⊢ Raise e: Γ ⊢ e1: τ Γ∪ {x : τexc} ⊢ e2 :τ Γ ⊢ Try e1, (x.e2):
∀ τ. τ
Γ ⊢ e : τexc τ
Exceptions - Dynamic Semantics
- We introduce a new machines state
s ⪻ (Raise v) the machine raises an exception with the exception value v
- First approach:
- n s ⪻ (Raise v)
- propagate exception upwards in the control stack s
- use first handler encountered
Exceptions - Dynamic Semantics
- Entering a try block
- Returning to a try block
- Evaluating a raise expression
- Raising an exception
- Catching an exception
- Propagating an exception
f ▷ s ⪻ Raise v s ≻ Try e1(x.e2) (Raise ☐) ▷s ≻ v s ≻ Raise e (Try ☐ (x.e2) ▷ s ≻ v1 ↦C (Try ☐ (x.e2) ▷ s)≻ e1
↦C (Raise ☐) ▷ s ≻ e
↦C s ≺ v1 ↦C s ≻ e2 [x:=v] Try ☐ x.e2 ▷ s ⪻ Raise v ↦C s ⪻ Raise v ↦C s ⪻ Raise v
Exceptions - Dynamic Semantics
- What is the problem here?
- efficiency: the frames are popped one by one when an exception is raised
- Second approach
- how can we jump directly to the appropriate handler?
- we use an extra handler stack h
- a handler frame contains
- a copy of the control stack
- the handler expression
Exceptions - Dynamic Semantics
- Entering a try block
- Returning to a try block
- Evaluating a raise expression
- Raising an exception
- Catching an exception
(h, k) ≻ Try e1 (x.e2) ↦C (h, k) ≺ v1 (h, k) ≻(Raise e) ↦C
(h, k) ⪻ (Raise v)
(h, k’) ≻ e2 [x:=v] (handle k’ (x.e2))▷ h, k) ⪻ (Raise v) ↦C (Handle k (x.e2) ▷ h,(Try ☐ )▷ k) ≻ e1 (Handle k (x.e2) ▷ h,(Try ☐) ▷ k) ≺ v1 ↦C
( h, (Raise ☐) ▷ k) ≻ e ( h, (Raise ☐) ▷ k) ≻ v ↦C