[comp.lang.eiffel] Insecurities in Eiffel

cook@hplabsz.HPL.HP.COM (William Cook) (06/27/89)

*** These opinions are my own and must not be ***
*** taken to represent those of my employer. ***

Since Dr.  Meyer has referred to my analysis of the Eiffel type system
several times, but it is not yet generally available, it seems
appropriate for me to summarize the paper here.  I have been slow to
promote my analysis out of professional courtesy, so the ISE would
have time to prepare a response before the criticisms were publicly
presented.  The analysis will be presented at ECOOP'89, and also
republished this fall in "The Computer Journal" of the British
Computing Society.

Ever since I first pointed out the "insecurities" in the Eiffel type
system, many of which have been discovered independently and posted to
this group, Dr.  Meyer's response has been that these are not language
flaws but deficiencies in the implementation (the compiler misses some
things).  This is imaginable, in that one could attempt to use global
data-flow analysis over complete Eiffel systems to find out whether a
given *potential* violation of the type-system will ever really
happen.  With this point of view no type-system is ever incorrect,
because one could always claim that the surrounding system will patch
up any loop-holes.  (In a recent posting an ISE employee seemed to
confirm that this is going to be their approach.)

Even so, these claims are of little value because this kind of program
analysis is neither theoretically possible nor pragmatically desirable.
It would completely prevent *local* determination of the type-safety
of classes.  If one cannot know whether a given class is type-correct
except by reference to the programs in which it is used, then one can
never be sure that putting in a new assignment somewhere else in a
program will not make introduce type-errors in other classes.  This is
in fact the situation in Eiffel as it currently stands.

A better argument that the insecurities are unimportant is simply that
one never runs into them in real programming.  The experience of the
net seems to contradict this claim, because many problems were found
very quickly.

On the other hand, my overall impression is that the insecurities
could be fixed without seriously effecting the overall
"expressiveness" (as in availability of notation) of Eiffel.  Nobody
wants to use insecure features unless they have to, so if there is a
secure formulation that provides the same effect, why not adopt it?
This question touches on commercial and political problems, so I will
leave it alone.

Dr.  Meyer promised to send out a note on the consistency of the
Eiffel type system, but this has been delayed, as he mentioned in one
of his recent postings.

REDEFINITION

I focus first on the type redefinition rule OOSC page 263:

    An attribute, a function result or a formal routine argument
    declared in a class may be redeclared with a new type in a
    descendant class, provided the new type conforms to the original
    one.

The redefinition of attribute types and restriction of formal routine
argument types are both insecure.  By insecure I mean roughly that it
is easy to write programs that type-check but produce core-dumps when
executed.  Several examples are given (I don't feel like including
them here.  Use your imagination.)  I propose a simple correction to
the redefinition rule that solves these problems, but prohibits direct
attribute redefinition (this feature should be handled by rebinding a
local type variable, as discussed below):

    A function result or a formal routine argument declared in a class
    may be redeclared with a new type in a descendant class, provided
    the new type conforms to the original one if it is a result type,
    and the original one conforms to the new type if it is a formal
    argument type.

DECLARATION BY ASSOCIATION

My intuition is that the redefinition rule is the way it is in order
to support declaration by association, and in particular formal
arguments of type "like current" which necessarily involve
type-restriction.  However, I argue that the redefinition rule is too
crude to support the kind of typing that is implied by "like current"
and other associate types.  In addition, the rules for conformance of
declaration by association are not very carefully defined (and do not
characterize the way the compiler behaves).  The conformance relation
is defined on page 262.

    A type Y is said to conform to a type X if and only if one of the
    following applies:

    1.  X and Y are identical.

    2.  X and Y are class types, X has no generic parameters, and Y
        lists X in its inheritance clause.

    3.  X and Y are class types, X is of the form P[U_1, U_2 ,..., U_n],
        and the inheritance clause of Y lists P[V_1, V_2 ,..., V_n]
        as parent, where every V_i conforms to the corresponding U_i.

    4.  Y is of the form "like anchor", and the type of anchor
        conforms to X.

    5.  There is a type Z such that Y conforms to Z and Z conforms to X.

It is clear no type ever conforms to "like x" so that, for example,
the following assignment should be invalid:

    x : T;
    y : like x;
  do
    y := x;

Note that "like x" conforms to "T" because the type of "x", namely
"T", conforms to "T".  Thus the assignment "x := y" is valid according
to the rules.  (As Meyer points out in his book, rule 4 is carefully
designed to avoid a more subtle problem involving redefinition, but
the rule messes up the simple case shown here.)

Rather than try to patch up this confusing situation, I propose that
most cases of declaration by association (all but "like Current", see
below) be eliminated from the language, and replaced by explicit type
variables.  Using this idea, the code above would become

    t = T;
    x : t;
    y : t;
  do
    y := x;
    x := y;

These type-variables can be "rebound" during inheritance.  This
proposal eliminates the "asymmetry" that is the source of the problems
in defining correct type-rules, and also may eliminate the need for
"artificial anchors".

This reformulation of declaration brings out its clear connection to
genericity.  We can rewrite the above as:

    class proto[t <= T] feature
      x : t;
      y : t;
    main is do
      y := x;
      x := y;
      end

The rebinding of the type variable during inheritance is achieved by
simple generic application:

    class actual inherit proto[T'] end

Note that Meyer's paper on "Genericity versus Inheritance" in
OOPSLA'86 should be reviewed in this light.  Under my proposal this
the title would read "Genericity versus (Inheritance + Genericity)"
since his original definition of inheritance includes declaration by
association, which I claim is just genericity in disguise.

GENERICITY

The conformance rules for generic types are also very insecure.  It is
assumed that a type G[T] conforms to G[S] if T conforms to S.  This
can easily lead to type-violations.  To achieve security, it is
necessary to examine the "polarity" of the occurrences of generic
parameters.  If they are always used only to type attributes and
return-values (values of the generic type can only be "read" from the
objects), then the condition given above holds.  If they are used only
as arguments (the object is "write-only" for that parameter) then the
opposite rule holds:  G[T] conforms to G[S] if S conforms to T.  If
the parameter is used both ways, then *no conformance is secure*.

For example, the cell class from the library does not allow any
conformance:

    class Cell[T] feature
       info : T;

       change_info (x : T) is
          do info := x end;
    end

That is, Cell[A] should *never* conform to Cell[B], no matter how A
and B are related.  One can split this type up, however, and get any
safe conformance that is needed:

    class WriteCell[T] feature
       change_info (x : T) is
          do info := x end;
    end

    class ReadCell[T] feature
       info : T;
    end

    class Cell[T] inherit WriteCell[T], ReadCell[T] end

Then one has, given A<=B (where <= means conforms to),

    Cell[A] <= WriteCell[A]
    Cell[A] <= ReadCell[A] <= ReadCell[B]

    Cell[B] <= WriteCell[B] <= WriteCell[A]
    Cell[B] <= ReadCell[B]

These relations seem rich enough for most situations.  (oops, I am
glossing over the potential difficulty of writing "WriteCell" because
it doesn't know about "info".  This example is off the top of my head,
so I am not sure what to do about it right now.  Probably a hidden
variable and an explicit exported get operation would suffice.)

LIKE CURRENT

The form "like Current" cannot be handled by "type variables", and is
a truly novel and important feature of Eiffel.  I note in passing that
conformance of classes using "like Current" must be determined in very
much the same way as classes with generic parameters (and in some
cases in a class A, the type A should not conform to "like Current").

SUMMARY

Replace declaration by association (except the special type "like
Current") with explicit type attributes, which act like bounded
generic parameters with default values.

Eliminate attribute type redefinition and invert the method argument
redefinition rule.

Use the variance ("polarity") of generic parameters, type attributes
and "like Current" to determine what conformance relations arise from
inheritance.

Eiffel is advertised as type-safe.  It is not.  It has some good
ideas, and brings together features that have not previously been
supported all at once.  It has some novel contributions.  But I think
the design is immature, and that experience will demonstrate that
these problems will only get worse if not addressed soon.

Comments are welcome.  This note was extracted on line, so there may
be errors not found in the paper.

-william cook@hplabs.hp.com