Skip to content
Back to the collection

Java and scala's type systems are unsound: the existential crisis of null pointers

Nada Amin, Ross Tate

2016 · Proceedings of the 2016 ACM SIGPLAN International Conference on Object-Oriented Programming, Systems, Languages, and Applications

Read the paper
HN score
1
Mentions
4
Readers
4
Months
4
What’s in a score?

In the conversation

Original HN comments, with labels from simple text rules. A label describes the whole comment and may miss context or irony. Read the discussion to make up your own mind. About the labels

4 selected excerpts · 4 total mentions

TheDong
Recommendation · rule
How were weak and strong defined in the literature you read? Anyway, more on soundness. I can recommend a couple papers: Wadler (inventor of the monad) wrote a paper called "Well-Typed Programs Can’t Be Blamed" [0] about soundness. There was a recent paper that made the rounds about how java's type-system is provably unsound as well that's worth reading [1]. I second the parent comment that weak and strong are ill-defined in my experience, while sound/unsound have well-accepted definitions and are commonly used in academia. Your experience obviously differs for whatever reason. You can find many other references to soundness in the citations of those papers, as well as scattered around the internet. [0]: https://link.springer.com/chapter/10.1007%2F978-3-642-00590-... [1]: https://dl.acm.org/doi/10.1145/2983990.2984004
foldr
Neutral mention · rule
Generics ended up making Java's type system unsound. Not a huge deal in practice, but the following paper sheds some light on just how complex Java's type system really is one you start poking around a bit: https://dl.acm.org/doi/pdf/10.1145/2983990.2984004
Joker_vD
Neutral mention · rule
> The language actually provides quite a strong safety guarantee for generics, as long as we follow the rules: If a program compiles with no unchecked or raw warnings, the synthetic casts inserted by the compiler will never fail. Huh. That was written in 2020, four years after it was shown how to write a very small program that "compiles with no unchecked or raw warnings", and yet "the synthetic casts inserted by the compiler" will fail at run time [0]: class Unsound { static class Constrain<A, B extends A> {} static class Bind<A> { <B extends A> A upcast(Constrain<A, B> constrain, B b) { return b; } } static <T, U> U coerce(T t) { Constrain<U, ?…
Read the full comment
super T> constrain = null; Bind<U> bind = new Bind<U>(); return bind.upcast(constrain, t); } public static void main(String[] args) { String zero = Unsound.<Integer, String>coerce(0); } } [0] N. Amin, R. Tate "Java and Scala's type systems are unsound: the existential crisis of null pointers", https://dl.acm.org/doi/pdf/10.1145/2983990.2984004
cben
Neutral mention · rule
I agree with everything you say, except I have a lower bar of "meaningful". > one possibility is simply that “dynamic type checking” is meaningless. If I take your quotation out of context, this was the assumption of some people who separated languages into simply "strongly" vs "weakly" typed, by which they meant "checked by compiler" vs "not checked". It's incorrect though, and is the reason for separate "static" vs "dynamic" distinction. C is a famous example of static (checked at compile time) but weak (≈ unsound) typing — compiler will happily accept programs that corrupt memory in all kinds of ways. mutating "const" variable, freeing used memory, crashing, remote…
Read the full comment
code execution, and more... I don't think there are any invariants that a C compiler can enforce. Scheme, Python, Lua, Javascript etc. OTOH, have no static (= compile-time) type checking, yet they maintain some invariants! An object that has pointers to it will not be freed; An object's type is known, and will not change (well, some class transmutation is allowed but some not, an int will not turn into an array); Some types are immutable; You can never divide a string / string! etc... Moreover, by maintaining run-time metadata about object's types, they can tell you specifically that an operation raised a TypeError. Thus these languages are strongly typed at run time. IOW, I'm arguing that anything that blows up at run time is checked IFF it tells you it was a type error. This is meaningful compared to C segfaults that tell you nothing :-) --- The Java paper https://dl.acm.org/doi/pdf/10.1145/2983990.2984004 shows an interesting subtlety. "Fortunately, parametric polymorphism was not integrated into the Java Virtual Machine (JVM), so these examples do not demonstrate any unsoundness of the JVM" — yet they present unsound programs that "type-checks according to the Java Language Specification and is compiled by javac, version 1.8.0_25". What gives? If a bad program compiles, how come JVM is still sound? See, Java has two type systems! - JVM is the runtime, intended to be capable of loading even untrusted bytecode yet still maintain some type invariants. It manages memory and type metadata, and for this bad program will correctly identify type violation at run-time: "When executed, a ClassCastExceptionis thrown inside the main method with the message “java.lang.Integer cannot be cast to java.lang.String”" - The compilers uses a distinct more complex static type system. The question of soundness is: can any program that passed the compiler cause JVM run-time type errors? + Java language allows you to write type casts. These are deliberate "trust me" holes in the *static* type system, whose specified semantics is: JVM will check type at run time and raise exception if not as programmer promised. As https://typing-is-hard.ch/#what-about-unsafe-casts says, since these are deliberate, and fall back to meaningful run-time type checking(!), let's ignore them — redefine "soundness" as: can a program with no exclicit casts cause a run-time type error? + Generics only exist in static type system! They are invisible to JVM (aka "type erasure"), they compile into dynamically checked casts. Their soundness goal was that the compiler can prove these implicit casts will never fail — e.g. you can only put candies into ArrayList<Candy>, so arr.get(0).eat() is guaranteed to give you a candy you can eat. That paper demonstrates a simple 17-line program with no explicit casts that compiles yet causes run time type error. --- If you think what type soundness means, ALL statically typed languages have 2 type systems! There are execution semantics — what it means to, say, compute number + number. And there is a static language of talking about types that aims to predict / prove the types that will be involved at run time. Static checking fails if they don't match. Dynamic checking largely fails when you don't do it :-) But also when you erased info you needed to do it.

Keep following the thread.

More in Programming languages

More from the same topics.