hugomg
hugomg

Reputation: 69934

Can Common Lisp type annotations result in unsound behavior?

I know that if the safety setting is low, Common Lisp can use type annotations as optimization aids and are not checked. For example, this program runs and prints both the number and the string without any type errors. (I only get a type error in SBCL, when safety >= 1)

(declaim (optimize
           (speed 3)
           (safety 0)))

(defun f (x)
  (declare (type fixnum x))
  x)

(format t "1 ~A~%" (f 17))
(format t "2 ~A~%" (f "asd"))

I now wonder if its possible to create a program that does nasty things if safety is set to zero and type annotations are not respected. Things like casting from one type to another (like C type casts) and other kinds of undefined behavior.

So far, I haven't managed to find out an example that does that. I tried variations of this example that uses typed arrays but none of them resulted in typecasting behavior.

(declaim (optimize
           (speed 3)
           (safety 0)
           ))

(defun f ()
    (let ((arr (make-array '(5)
      :element-type 'fixnum
      :initial-contents (list 1 2 3 4 5))))
      (declare (type (vector fixnum 5) arr))
      (setf (aref arr 0) "hello")
      (aref arr 0)))

(format t "a1 ~A~%" (f))

In CLISP, this program prints "hello", without doing a typecast to int and in SBCL the program aborts with a SIMPLE-TYPE-ERROR error.

Is there a way to create a Common Lisp program that results in demons coming out of my nose if I don't respect my type declarations?

Upvotes: 2

Views: 1152

Answers (3)

m-n
m-n

Reputation: 1446

Here are a couple of examples with safety 0 in sbcl:

CL-USER> (proclaim '(optimize (safety 0)))
; No value

Out Of Bounds Read

CL-USER> (loop for i from 0 to 20 
              do (print (aref #(0) i)))

0 
0 
#(0) 
(I) 
I 
NIL 
(AREF #(0) I) 
NIL 
(PRINT (AREF #(0) I)) 
NIL 
#<unknown immediate object, lowtag=#b1001, widetag=#x59 {D59}> 
#<SB-KERNEL:LAYOUT for SB-KERNEL:LEXENV {10005F3C33}> 
NIL 
NIL 
NIL 
NIL 
NIL 
NIL 
NIL 
NIL 
NIL 
NIL

Undefined Typecasting

CL-USER> (defun f (x y)
           (declare (fixnum x y))
           (+ x y))
F
CL-USER> (f t ())
537919538

In SBCL if you smell nasal demons you can restart the image and input (sb-ext:restrict-compiler-policy 'safety 3) (and maybe again with 'debug, too) before loading the problematic code. With a little luck that will get you a nice condition instead of undefined behavior.

Upvotes: 2

Rainer Joswig
Rainer Joswig

Reputation: 139251

The Common Lisp standard says that there are type declarations. It does not really say what they do or what an implementation would do with them.

Purposes:

  • generate type specific code
  • run-time and/or compile-time type checking. This is mostly only been done by CMUCL, SBCL and SCL.

Let's say we have the operation:

(+ i 100)

By default it would be a generic +, which can handle all numeric types.

If we tell that i is a fixnum then

  • the + operation can be fixnum specific

If additionally the return type is declared as a fixnum:

  • it may not overflow into a bignum

If we additional tell the compiler that we want low safety, then the compiler will not generate runtime type checks.

What capabilities the compiler provides is not standardized. It can even fully ignore the type declaration.

If you have a compiler which supports type specific code (and there are many such compilers), safety is low and one provides an object of the wrong type at runtime, then it can have unwanted consequences. Including crashing the Lisp due to heap memory corruption.

Thus it is best to use low safety only on very small code regions and not on whole files or systems. Using declarations with locally helps.

SBCL (also SCL and CMUCL) is special, because it also treats type declarations as assertions for type checking.

Upvotes: 4

sds
sds

Reputation: 60004

Not sure about the demons, but you can get a segfault, as I did 15+ years ago with CMUCL and something similar to this code:

(declaim (optimize (speed 3) (safety 0)))
(defun f (v)
  (declare (type (vector double-float) v))
  (loop for x in v sum x))
(f #(1 2 3))

Note that I promised the I will only pass (vector double-float) to f and then I gave it a simple-vector with fixnums.

Upvotes: 2

Related Questions