Pointers Gone Wild
goto.ucsd.edu
goto.ucsd.edu
https://github.com/tomprimozic/type-systems/tree/master/refi...
Edit: I submitted it as a separate story here: https://news.ycombinator.com/item?id=7826175
I've never found that answer very satisfying...
I imagine it depends on the difficulty of the problem being outsourced.
Which, thanks to pipeline stalls and out-of-order execution is no walk in the park these days, but it's not TOO bad.
Smarter is your opinion. Is a library writer smarter than an app developer? Is a compiler writer smarter than a library writer? Perhaps, though I don't see how that is a given. I look at it more like; compilers will be used by more people than a single library, so it pays off to spend more time on developing, testing, debugging, etc. it than a library. A library might be used by more clients than a single app, so it pays off to use more time on it and optimize it, which might include unsafe features.
If people that work "further down the stack" are necessarily smarter than those in the upper layers, we programmers must be pretty stupid... :)
Things didn't quite work out that way, but it's a good principle for language designers to live by.
If you don't know how to implement the languages you use, then either you or the language is at fault, IMO.
(Forth might be one of the ultimate expressions of the principle of a fully understandable compiler - Chuck Moore even hesitates to call it a compiler.)
On the other hand, you can get very far without really losing anything if you just know the semantics of the language, and leave the nitty-gritty to the compiler. C is supposed to be simple, but given how big of a deal optimizing compilers for C seems to be, it's not straightforward to make a proper, industrial standard compiler for the language (perhaps both with regards to competition and effort). And yet C is supposedly just a thinly-veiled abstraction over assembly ("supposedly" since I'm not a C programmer).
I for one welcome giant, too-big-to-understand-for-one-person compilers, as long as they are awesome at what they do. Simple programming language? Then you seem to get complicated design patterns. Simple semantics ala "everything is X" (immutable, object, ...)? Then you get complicated work-around in the places where you can't/don't want immutability/objects... . Simple compiler? Lot's of responsibility and mental overhead for each individual programmer, in a sense duplicating a lot of the effort that could be handled by a team of compiler writers, once.
That said the code here is more about the implementation of the vector library internals at a very low level that deals with machine types. If you're a Python user this is effectively equivalent to reading the NumPy internals which I can tell you first hand are an ungodly mess of preprocessor macros and code generation gone horribly wrong and far worse than this.
I always thought Python to be one of the most approachable for example. Then maybe C. I also like Erlang.
More in depth:
Python is closest to pseudocode. As in, if I sat down and wrote an algorithm, in some pseudocode language, it would probably look a lot like Python.
I like C and Erlang because they are relatively small and self-consistent. Think of it as chess, there are not too many rules of the game, but the game can still be very interesting. Erlang is a good example of a practical functional language. I really like pattern matching in it.
I don't like C++ for example because the syntax is too complex. Too many exceptions, rules and corner cases.
I don't have an opinion on Lisp/Scheme/Clojure/Haskell just because I haven't tried using them in depth.
And the solution for those seems to be the equivalent of "Don't use 'array[i]', always 'if (i < len) ? array[i] : BOOM'". Or am I misinterpreting we have passed the burden of proof onto users of charAt? I guess the difference is that the check only needs to be done once for each 'i' at the time it is assigned/created? But then how does one guarantee that a given AValidI is used with array for which is intended, and not for a shorter one?
http://goto.ucsd.edu:8090/index.html#?demo=permalink%2F14014...
that uses Lists instead of pointers, for a short example.
Of course, you can write your own safe wrapper which checks deference, but one could just as easily (in fact, I think more easily) write such a thing in C...
That does not mean that it isn't good there are people trying to give us safer languages, but there is something disconcerting about the ease with which presumably bullet-proof Haskell can be made to do unsafe things like referring to out-of-bounds data, even if the reason is 'performance'. The whole idea was (or at least, that's what I thought it was) that you can get there at reasonable speed without giving up the safety net. Like this you might as well go the whole hog and program in C after all... It's a bit like making a vault that has the front door hinges and the lock screwed on from the outside.
Compare with like Linux on the desktop not having too many viruses and such. The viruses aren't there because nobody is motivated enough to write them given the small distribution footprint of the OS. The money isn't there for the virus writers. Not because Linux is somehow magically resistant to viruses (though it does a few things better than say Windows).
The number of people trying to bust down the barriers is roughly proportional to the size of the deployments, and I suspect that Haskell when put to the test will eventually turn out to have it's own (different) class of problems that have nothing to do with the class of problems that plagues the C like languages.
Just like interpreted languages have a unique set of problems and virtual machines have their own. Silver bullets are rare.
Linux is exploited via remote execution or by passing payload data to be processed by the user using known vulnerable programs. The latter is harder to pull off.
Getting access to Linux systems is not usually a problem of Linux itself. It happens a lot more than you think.
Nor is checking the source code to whatever module or tool you need to compile and run.
The main difference I find between the two is that I have never needed to reach for an unsafe construct in Haskell myself - including some networking / parsing code that lives at roughly the same level of the stack as you'd find the heartbleed bug. The 'unsafe' functionality I use is encapsulated in libraries which expose a safer, higher-level interface. (Which is roughly the situation in Ruby with C bindings or Rust / Go with `unsafe` blocks.) I do feel there's a significant difference between a language where you need to be more-or-less constantly vigilant and one where you can isolate the memory management to a small, well-vetted library, but it's true that the situation still isn't ideal. (I think Haskell's story is much better as you move up the stack, but that's another story...)
But if you're interested in a stronger level of safety in Haskell, you may be interested in Safe Haskell[0] -- it disables many of the unsafe features (or requires uses of them to be explicitly 'trusted') and thus provides much stronger safety guarantees.
[0] http://www.haskell.org/ghc/docs/7.4.1/html/users_guide/safe-...
… Now go read rest of the post
> Of course, you can write your own safe wrapper which checks deference
You don't generally. unsafe* functions practically are only used in libraries where you really need C-level speed. The next level of abstraction is their public API.
Certainly Haskell does have advantages over C (better type system, garbage collection), but I feel this article does a bad job explaining them an introduces a sausage to fortune by talking about Heartbleed.
I would not expect any unsafe calls in a crypto library. It would defeat almost the whole purpose of implementing a crypto library in Haskell. It would be like implementing a web application in Ruby, and then using static type annotations everywhere using some eDSL.
BTW, I don't think this article is meant to argue the advantages of Haskell, it is meant to describe a solution to a problem in Haskell.
I think everyone who had ever looked at the code for OpenSSL (including myself) knew it was an unsafe time bomb waiting to go off, it's just no-one could be bothered to replace it, or do the basic complete re-engineer the BSD folks are now doing.
That sounds like a fun experiment, actually. Someone should go and make that.
That's quite easy. In 100 lines of OpenSSL you have 6 nested #ifdefs and the code is unreadable formatted.
The point of this blog post is how to enforce memory safety at compile time even when using unsafe.
Safe indexing is constant time too, unless you're out of bounds and you have to process the exception, in which case your program is wrong anyway.
After listening to Bob Beck's talk[1], I think people did know how unsafe that code was but no one wanted to take a shot at fixing it until it all went bad. At 2:12 into the video (the Jon Snow slide), he explains why nobody looked (or admitted they looked)[2]. Code can be so bad in any language to discourage people from even attempting to figure out how it works.
1) https://www.youtube.com/watch?v=GnBbhXBDmwU
2) quotes "Why did WE not look" & "It is your parents talking about how you got made horrible"
I agree. Who could ever think in using unsafe* functions?
In my opinion, if you are writing code that is performing array indexing, and are even the slightest bit uncertain about the size of the array, then you should not keep going, but stop and think a bit more about what it's doing. Language safety is not a replacement for true understanding.
userbinator makes a very valid and legitimate point: regardless of what safety features a programming language may or may not offer, it's still critical for the programmer to understand exactly what's actually happening.
You can't have true safety through tools alone. Operator knowledge is just as critical as whatever safety features the tool may offer.
Realistically, you get "better safety". Operator knowledge gets you better safety. Better languages get you better safety. Better libraries get you better safety. Better testing gets you better safety. Often, each of these has diminishing returns, but that just means that the best outcome is one where don't over-focus one at the expense of others.
Of course humans will make more mistakes, when the immense complexity of the business logic models they deal with surpasses what even the most advanced computer can process today.
Plus, computers also make mistakes. They fail in arbitrary ways, and may produce an arbitrarily wrong result when you least expect it (say, if they overheat).
Computers are great at executing algorithms, and never learning from their experiences. Humans are great at learning from their experiences, but pretty hit-or-miss when it comes to following algorithms. It is probably exactly that learn-ability that trips us humans up: we can learn new and beneficial things, but the flip side is that we can learn the wrong things. Clearly, we need both of these traits (predictability, learn-ability), and should probably delegate the tasks according to each others strengths.
Of course, there is nothing fundamentally different between computers and humans (modulo some philosophical assumptions); in principle, we could implement very complex artificial neural networks, to the point that computers being deterministic might be as much of a useless distinction as considering sociological phenomena as deterministic - fine in a deterministic universe, but a pretty useless perspective for all practical purposes given the immense set of variables. We then gain learn-ability, or AI, but we also lose predictability. So we would probably still delegate the task of compiling programs to a very deterministic program over an incredibly advanced AI that, while sometimes having some good and creative insights when it comes to generating output code, sometimes also learns the wrong stuff and gives us just flat-out wrong output.
That's why formal methods ( type systems, model checkers, ... ) are extremely important, the only to reason about programs in the large is to prove properties of the system that are machine-checkable and remove the need for manual proofs that admit error.
Considering that developers have been making this error pretty much every single day for the past 40 years, I'm gonna go with 'yes'.
For one given block, maybe not. The problem is that in a language like C, the fallible human must get every single one correct, otherwise it's a bug waiting to promote to a security flaw. Not only must you get array indexing right, but also pointer dereferencing right, avoid dangling pointers, be mindful of the interaction of aliasing and mutation, etc. This is a lot for a person to never mess up. This is also the reason why projects like Rust exist: they start with the premise that humans will fail and provide tools that prevent the common mistakes.
> In my opinion, if you are writing code that is performing array indexing, and are even the slightest bit uncertain about the size of the array, then you should not keep going, but stop and think a bit more about what it's doing. Language safety is not a replacement for true understanding.
What if you are very sure that your code is correct, but five months later someone modifies code elsewhere in the program that aliases to this array and reallocs() it?
All those things are just part of the logic of the program, which must be correct in any case. Even in safer languages, you still have to write correct code, and if you believe that humans can't reliably get simple things like array sizes correct, how would they do any better with the more subtle kinds of logic errors?
What if you are very sure that your code is correct, but five months later someone modifies code elsewhere in the program that aliases to this array and reallocs() it?
It depends on the specific case but I would tend to believe that someone is not modifying the code correctly (e.g. failing to update the associated size), because they have not understood it.
The mentality of "bugs are normal, and we should restrict languages as much as possible because people might do the wrong things otherwise" is what irks me the most, because proliferating the assumption that perfect code can never be written leads to a self-fulfilling prophecy.
Heck, bugs in general are usually code that works in some cases, but not every case. Having a type system to catch all that throws out a ton of classes of bugs.