Type Safety and Exception Handling
Total Page:16
File Type:pdf, Size:1020Kb
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 operational semantics value & type environments parametric polymorphism/ generics type safety control stacks error/exception handling partial application/function closures semantic features functional (algebraic) data types static & dynamic static & dynamic scoping typing language concepts explicit & implicit procedural/imperative typing Types in programming languages statically vs dynamically typed type safe vs non type safe strongly vs weakly typed Static and Dynamic Semantics • MinHs 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’ e1 : τ ↦ e2 : τ ↦ e3 : τ ↦ … • Together is means that a program will either evaluate to a value of the promised type, or run forever 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? Γ ⊢ t1 : Int Γ ⊢ t2 : Int Γ ⊢ Div t1 t2 : Int 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 Run-time Errors and Safety • Application of a partial function can yield Error Div v (Num 0) ↦ Error • An Error interrupts any computation 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 in C# • Language constructs to handle exceptions: - try: execute a code block which may trigger an exception, has to be followed by at least one - catch block, which contains the exception handling code, which may be followed by a code block preceded by - finally, which is executed whether or not - throw, which triggers an exception, was called in the try block • Predefined exception classes contain: - Message: human readable text that describes the exception - StackTrace: lists all the called methods and line numbers • Exception class can be extended by user defined exceptions Exceptions in C# try { // code block which may trigger // an exception through … throw … } catch (ExceptionName e1 ) { // error handling code } catch (ExceptionName e2 ) { // error handling code } catch (ExceptionName eN ) { // error handling code } finally { // code block which will always be // executed } Exceptions in C# namespace SaveDivision { class Division { int result; Division() { result = 0; } public void div(int num1, int num2) { try { result = num1 / num2; } catch (DivideByZeroException e) { Console.WriteLine("Exception caught: {0}", e.Message); Console.WriteLine(“Stack trace: {0}", e.StackTrace); } finally { Console.WriteLine("Result: {0}", result); } } … } } 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, Exception in C# Exceptions - Static Semantics • Typing rules Γ ⊢ e : τexc Γ ⊢ Raise e: ∀ τ. τ Γ ∪ {x : τexc} ⊢ e2 :τ Γ ⊢ e1: τ Γ ⊢ Try e1, (x.e2): τ Exceptions - Dynamic Semantics • We introduce a new machines state s ⪻ (Raise v) the machine raises an exception with the exception value v • First approach: on s ⪻ (Raise v) ‣ propagate exception upwards in the control stack s ‣ use first handler encountered Exceptions - Dynamic Semantics • Entering a try block s ≻ Try e1(x.e2) ↦C (Try ☐ (x.e2) ▷ s)≻ e1 • Returning to a try block (Try ☐ (x.e2) ▷ s ≻ v1 ↦C s ≺ v1 • Evaluating a raise expression s ≻ Raise e ↦C (Raise ☐) ▷ s ≻ e • Raising an exception (Raise ☐) ▷s ≻ v ↦C s ⪻ Raise v • Catching an exception Try ☐ x.e2 ▷ s ⪻ Raise v ↦C s ≻ e2 [x:=v] • Propagating an exception f ▷ 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 (h, k) ≻ Try e1 (x.e2) ↦C (Handle k (x.e2) ▷ h,(Try ☐ )▷ k) ≻ e1 • Returning to a try block (Handle k (x.e2) ▷ h,(Try ☐) ▷ k) ≺ v1 ↦C (h, k) ≺ v1 • Evaluating a raise expression (h, k) ≻(Raise e) ↦C ( h, (Raise ☐) ▷ k) ≻ e • Raising an exception ( h, (Raise ☐) ▷ k) ≻ v ↦C (h, k) ⪻ (Raise v) • Catching an exception (handle k’ (x.e2))▷ h, k) ⪻ (Raise v) ↦C (h, k’) ≻ e2 [x:=v].