“It is never a compiler error”
blog.plover.com
blog.plover.com
Eventually, someone (Natalie) moved the comment below the assignment, and it worked normally. I tried putting it back above, and it wouldn't compile. Can't remember what happened if I left the moved comment in place and put another above the assignment. I found two like that - one in CodeWarrior, one in Turbo Pascal a few years before that simply returned "error 0: no error."
It wasn't anything complicated in either case, just very basic stuff.
int x = 0;
int y = 2;
DoSomething(); // Didn't run with x before y
I can't recall if I ever reported it or bothered looking into it, but a friend ran into a similar issue around the same time and it was really confusing, but it was resolved by changing some configuration (different compiler version or flags I think).Only other time I bumped into it was in Idris, which is to be expected frankly.
If you ever catch them still there with a "no optimizations" flag, then that's a pretty serious bug!
CW had a pretty nice environment to develop in compared to its contemporary competitors, but it just didn't have a solid compiler.
Removing the offending line or adding a different mix of blanks/instructions would get you back to a working state again. Very frustrating.
Now everybody and their dog write compilers and transpilers for young and/or obscurer languages (I'd include Go and Rust in there), and with the immaturity of these environments compiler bugs are a fact of life.
They're never common, but "it's never the compiler" doesn't apply either. It is, however, a helpful reminder that the most likely source of problems is with the author of the code :)
If you're asking for actual examples - sorry, you're out of luck. The 80's and 90's are a bit too long ago for me to remember details.
If you prefer to believe in the magically flawless compiler fairy, knock yourself out.
Not that hard to stumble upon, but it took us awhile to figure out what the heck was going on.
Turns out, on QNX you can't pass short int into variadic function, which is expected behavior as far as C is concerned (you should only pass int or full size types), but it worked on every other arch so it took me a while.
I don't actually recall if we were using the shipped CRT or not, I'm guessing only minimal parts of it were used, if any, since embedded projects tend to write their own subset of the needed standard library functions.
Honestly, I'm surprised that I haven't run into more of them in the wild.
Compilers are the most extreme case. Anything Turing complete is almost certain to have vast swathes of functionality which aren't used by the developer. If a compiler doesn't already have a large user base, then unless you're using it exactly like someone else has, you're almost guaranteed to find some bugs. Honestly that's true for most software, going back to the maxim "don't be the biggest user of any given software unless you're already capable of writing it yourself."
I'm one of those that routinely find a bug in a library during the very first use of its API. Every "quick weekend project" turns into "let's learn the build system of this library and send a patch/bug report to the maintainer".
Does anybody know how can I turn this annoying superpower of mine into a job that is not basic Q/A?
If you can find bugs in libraries used in IOS/Android or something, that's worth money and gives you the chance to increase security for millions (billions?) of people.
(Might be confirmation bias, but I have been proofreading a lot of theses for my friends while in college, so it might be trained.)
It's likely that your weekend projects are things you find interesting and it seems likely that the things you find interesting are bleeding edge. It's never going to get better. On the other hand, any good dev team would value someone who can not only find the bug, but patch it.
You need to get somewhere where using the latest and greatest is encouraged..
It's a terrible idea to assume that other humans don't make mistakes. When encountering a bug, start at the top, and work your way down when you conclude that the current level is correct. There is no level of development that is immune.
Sometimes you are to blame. Others, it might be a library. It might be the standard library, or the compiler. A faulty optimization, perhaps. Could also be the OS kernel, or even the hardware (CPU, USB controller, network card, ...). It's all chock-full of bugs.
I've hit a handful of miscompilations while writing boring JavaScript web apps, some only when the JIT kicks in for extra fun. I have a handful of outstanding Linux kernel bugs, one of which cause memory corruptions, has existed at least since 2.6 and is dead simple to reproduce. I have a significant GCC performance degradation bug, triggered by calling "pthread_exit(3)" after an infinite loop. I've had what seemed to be software problems turn out to be PCIe problems.
You might think that, as a developer, you're building a card-house on a solid foundation. But no, it's card-houses all the way down.
> When encountering a bug, start at the top, and work your way down when you conclude that the current level is correct.
I agree wholeheartedly. Saying "it is never a compiler error" doesn't preclude this; it encourages this behavior. Even thinking "it could be a compiler error" invites laziness.
I strongly disagree with your point of view: "it is never a compiler error" not only precludes suspicion of the lower levels—voicing your suspicion of problems in lower layers will generally result in ridicule!—but it reducing critical thinking, bars people from looking into "complicated" projects, and makes them overall less efficient and skilled developers. I also disagree that encouraging a bit of wasted time is a problem. What is a few hours, or even days "wasted" learning your lesson once and for all if it greatly improves your debugging skill and efficiency? You do not learn without mistakes.
"It is never a compiler error" is a detrimental mindset to have, in that it suggests a mentality where you shut your eyes for anything you did not author. After all, it is too complex, too well-tested, too well-designed, and implemented by much smarter people than you. Compiler, library, application, hardware, train, bridge, it doesn't matter.
Not only does this mean that you often won't find the bug you hit, as you won't start instrumenting the faulty LargePopularLibrary—after all, it obviously can't be to blame—but it also means that you won't as much as read library|compiler|kernel|etc source code, much less do development on it, as you think it is above you, despite it by no means being some magical black art.
"It could be a compiler error" invites you to consider all possibilities. Immediately blaming the compiler without reasonable evidence would, despite such a mantra, be a result of terrible debugging skills. These debugging skills should be improved through other means than intentionally teaching falsehoods, and if the subject is not an entirely lost cause, it should correct itself in much more valuable ways if a little time is "wasted" on improper debugging.
Teach proper debugging techniques instead of lying to others, or yourself.
Or worse it could invite curiosity and now you are spending time wondering how could you tell if it's a compiler bug and how would you go about debugging it.
If it's really a compiler bug, you'll find out in the process of debugging your code.
The OP simply said that the chances of finding a compiler bug are small. You seem to have found a few minor bugs, and that's cool, but how does that change the fact that chances an average C developer finding a compiler bug are minuscule? There are probably around a million C developers, do we even have one compiler bug per 10 developers?
I'm sorry, how are total miscompilations and memory corruptions "minor bugs"?
I am arguing against the mentality of dismissing compiler bugs, with the idea that "you will never hit one". Hence my examples of when I hit them during something as high-level as web development. I am not arguing that you'll hit such bug every day, or even every month.
I only have myself and those around me as reference. I have been a software developer for less than 10 years, but with the current total, I have been hitting a few "lower layer" (vm, stdlib, compiler, kernel, hardware) bugs a year.
Maybe I'm just "unlucky". If you want to find real numbers, take a look at GCC/LLVM/libstdc++/glibc/linux bug tracker statistics. I'm sure you will be surprised.
A Swift developer?
Just yesterday I found myself compiling a new part of code in increments because all of the new code together wouldn't compile but adding the code and import first, compiling it before adding @UIApplicationMain would compile just fine. Exactly the same code that didn't build before.
I can't imagine what I would have done if I would have trusted that this was my error because the compiler would not make mistakes.
Though nothing compared to Swift 1.x, things tend to work generally and if the compiler does crash it'll crash on weird programming mistakes (like referencing type(of: self) before init() was called)
... is in the eye of the beholder. They exploit every corner of undefined behavior to the benefit of performance. Indeed other compilers might be "safer."
Keep in mind that they also provide you with UBSan to help you detect when you're doing it wrong.
Isn't the "they" in this thread gcc, or did I misread something?
Note that UBSan is (mostly? totally? originally?) a Google project and it's been ported to both clang and gcc.
The compiler writers write the compiler with the expectation that the program being compiled is correct i.e. to the specification.
Now when the program fails to be correct, optimizations can result in "weird" results, which is not really incorrect since the input was incorrect.
If you're able to narrow it down that far why not then fix your bug?
Essentially, it was not worth more of my time to figure out how aggressive optimizations were breaking the parts of a memory bandwidth constrained function that aren't worth optimizing in the first place.
For GCC inline assembly means "take your grubby hands off". For clang it's essentially an invitation to do whatever.
That's not true at all. Clang respects the clobbers entry on inline assembly and does not do whatever it wants. See GCC's documentation on it: https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Clobber...
Based off of your description you probably just needed to add the "memory" clobber to tell the compiler that any registers it might have kept are no longer valid.
I'd rank the likelihood of an error like this :
- My fault
- API bug
- broken environment (ex: corrupted object files)
- hardware problem
- OS bug
- compiler bug
- bug in malloc()/free() ;)
I very politely finished the interview and then ran away as fast as I could and never looked back. I did not need that shit in my life. The world where it's always my fault is a better place.
IBM designs, builds, and sells computers. Once upon a time half the installed computers in the world were built by and designed by IBM.
## IBM in the Vacuum Tube Age[4] (1942-1963):
These computers were produced in such small numbers and ran primitive OS's that did little more than load programs into memory and handled the a few simple devices (card/paper tape reader and printer).
(1952) IBM 701 -- Defense Calculator (they still called computers calculators)
(1953) IBM 702 -- business computer
(1954) IBM 650 -- first mass produced computer, used a magnetic drum for memory, decimal architecture
(1954) IBM 704 -- first mass produced with floating-point
(1954) IBM 705 -- evolution of 702, for business
(1954) IBM NORC -- Navy Ordnance Research Calculator (most powerful computer in the world for 2 years, 9800 tubes)
(1956) IBM 305 -- first commercial computer with a moving head disk drive
(1958) IBM 709 -- FORTRAN first ran on this computer
## IBM in the Transistor Age[5] (1955-1968):
(1957) IBM 608 -- took years to develop, it was first demo'd in 1954
(1959) IBM 7090
(1959) IBM 1401 -- first computer to sell 10,000 units, eventually about half of the worlds computers were IBM 1401, see [1]
(1959) IBM 1620
(1960) IBM 4020
(1960) IBM 7070
(1961) IBM 7030 Stretch -- supercomputer
(1962) IBM 7094
(1963) IBM 7040
(1964) IBM 7094 II
(1965) IBM M44/44X
(1965) IBM 1130 -- IBM's least expensive computer at the time ($32,000 in today's dollars)
This is where programming languages and OS's get interesting. During this period manufacturers began developing OS's, Programming Languages and Compilers for their customers. Although, some customers in the Vacuum Tube age wrote their own simple OS's to help run their machines the computers of the transistor age had more capacity, were faster, and had a wider range of users. This meant that manufacturers had to provide system software with the computers they sold. Almost all system software of this time was written in assembly language. The first operating system code that I studied was in assembler. Some of IBM's Operating Systems from this time period (see [6]) were IBSYS (originally developed by General Motors to run their 701, IBM took over development of this OS) and the FORTRAN Monitor System for the 709, 7090 and 7094, a tape storage based OS.
I never got to program on any of the vacuum tube older systems although I think I wrote some FORTRAN for a 1401 (it's harder to remember because being a junior programmer I rarely got to even touch these expensive systems, you would drop off your drawer of cards at a window for the "operators" to load into the computer and then, perhaps 30 minutes later you would pick up the printed output at the output window). I used the IBM 1130 in school and for a few of my first programming jobs in FORTRAN. This was with an IBM OS and and IBM FORTRAN.
## IBM in the Modern Computer Age
By the 60's IBM completely dominated the industry which was described as "IBM and the seven dwarfs" (Burroughs, UNIVAC, NCR, Control Data Corporation (CDC), and Honeywell, RCA, and General Electric); IBM's market share was larger than the competitors combined. IBM's continued to produce new architectures and manufacture computers in great variety. Perhaps the most important mainframe architecture ever (in history) was the IBM 360. I've programmed this computer in IBM COBOL and IBM assembly language.
The IBM/360 evolved into the IBM/4300, IBM/370, and IBM/390, and eventually to the z/Architecture in use today on large enterprise installations (see [2]). Over the decades the hardware changed drastically and the evolution of operating systems meant that IBM had to produce many advanced operating systems to run these multi-million dollar systems. (In 1969 a typical IBM/360 model 25--at the very low end of the 360 line cost $1,753,000 in today's dollars, see [3]). Back in the early 70's I programmed on the IBM/360 in assembly language, PL/1, COBOL, and FORTRAN.
In the modern era, IBM also produced a number of midrange systems: System/3, System/34, System/32, System/38, AS/400, and the IBM i. These machines ranged from the low end System/3 designed to run IBM's report generation language RPG to the pioneering System/38.
The System/38 was an interesting machine with an IBM OS that contained an integrated relational database management system. The System/38 didn't have files, it had a single-level store where information where the distinction between memory and external storage was treated as a single unit. The System/38 was also one of the only commercial computers using capability-based addressing. This unique hardware/OS combination supported programming in IBM's RPG III, COBOL, BASIC, and PL/1.
Up until around 1984, IBM designed and wrote it own operating systems for all of its important computers. IBM had a number of important research projects going on in the 1970s and IBM's John Cocke, credited with inventing RISC although others were also working with these ideas around that time (e.g. the CDC 6600 designed by Seymour Cray in 1964 -- I still have the book I used to learn assembly language programming for the CDC 6600 a very interesting machine). John Cocke's work resulted in an experimental system known as the IBM 801. This inspired the development of the IBM POWER instruction set processors. Due to internal competition, the POWER/PC chip was turned into a system that supposedly would not threaten the important mainframe business.
The original Power/PC running Unix (IBM's AIX version) ran on the new hardware design and was supposed to fit in between the IBM/PC business and the IBM mainframe business. The hardware was brand new and being developed when I joined IBM. The OS was to be Unix based and despite the pressure to "make it better" most of the architects fought to keep it consistent with ATT and Berkley Unix. Unfortunately, the hardware was new, the only compilers that could take advantage of the RISC instruction set were research compilers running the PL/8 (a system programming language that was supposedly the 80% of PL/1 needed for system programming). Because the hardware was not stable, development of the OS was done in two layers. A low level hardware layer coded in PL/8 and the rest of the kernel coded in C.
It was a huge undertaking (from my perspective as a new OS architect), but it worked. AIX/Unix proved itself within the corporation and even the mainframe groups have adopted Linux and AIX for some of there systems. I left IBM to start my own company, but I learned a lot working during those exciting times.
A computer that wasn't that important to IBM was the IBM PC. It wasn't originally considered to be interesting enough to even cover it's hardware with adequate patent protection leading to a proliferation of hardware compatible competitors and indirectly to the age of personal computers. Even in the IBM PC world IBM developed OS/2 and operating system that briefly competed with Windows.
## IBM Software
All of these machines had their own, often unique, operating systems. These were written a variety of languages. IBM's PL/1 programming language lived on in the early versions of the low level kernel of AIX, I don't know if it is still there.
IBM has, in my humble estimation, written more OS's, more compilers, and built a wider variety of computers than any company in history. However, I'm open to evidence of the contrary.
[1] https://web.archive.org/web/20101105110749/http://www.comput...
[2] http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainfra...
[3] http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainfra...
[4] https://en.wikipedia.org/wiki/List_of_vacuum_tube_computers
[5] https://en.wikipedia.org/wiki/List_of_transistorized_compute...
The less battle-tested a part of the stack is, the more likely it is to contain a bug.
Suppose I'm using a database library written in Elixir to run a PostgreSQL query and I get a weird result. Where is the problem?
PostgreSQL has executed billions of queries in thousands of projects for tens of years and has many contributors.
The Elixir library is much newer and has many fewer contributors and has been used much less.
My brand-new query has been executed a handful of times by me and has only been looked at by me.
The chances are overwhelmingly high that the bug is in my code. If I can't find it there, the next candidate is the Elixir library. Only after exhaustive search in those two should I consider that PostgreSQL may have a bug.
Or to include more of the stack:
my code > Elixir lib > Elixir > Erlang > PostgreSQL
The author's own anecdote with finding a bug in JavaScript's `sort()` illustrates this principle: it was not in (say) the Firefox or Chrome JS interpreter, but in a much less-used one: https://github.com/code-dot-org/JS-Interpreter/pull/23Array.prototype.sort does not sort an array of numbers as expected. In JavaScript, [2, 10].sort() results in the array [10, 2].
As MDN points out, "The default sort order is according to string Unicode code points... If [compareFn is] omitted, the array is sorted according to each character's Unicode code point value, according to the string conversion of each element." [0] It has an example showing off the unintuitive behavior right at the top of the page.
This behavior is intended per the original ECMAScript specification[1, pg. 68]:
When the SortCompare operator is called with two arguments
x and y, the following steps are taken:
1. If x and y are both undefined, return +0.
2. If x is undefined, return 1.
3. If y is undefined, return −1.
4. If the argument comparefn was not provided in the call
to sort, go to step 7.
5. Call comparefn with arguments x and y.
6. Return Result(5).
7. Call ToString(x).
8. Call ToString(y).
9. If Result(7) < Result(8), return −1.
10. If Result(7) > Result(8), return 1.
11. Return +0.
The key points are items 4, 7 and 8.[0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
[1] http://www.ecma-international.org/publications/files/ECMA-ST...
Definitely going to add this to my contempt for javascript list.
Naturally things are only weird for untyped arrays - calling sort() on e.g. an Int32Array does what you'd expect.
scala> 1+2
res0: Int = 12
Here is a blog post I wrote about it at the time: https://illuminatedcomputing.com/posts/2010/11/no-luck-tryin...I was running under Cygwin and the issue was something about the Java-based readline library.
I filed a ticket (linked in my blog post but the link is broken now), but the maintainers' response was something like "Yeah right. Closed."
You'd think compiler bugs would be more obscure than 1+2. :-)
Apparently the problem is that the '+' character was being thrown out, so Scala only saw '12'.
<feep> .neatex string-of 2+2
<feepbot> 23
That took a little figuring! Turns out, I'd forgotten my own precedence rules, and it'd interpreted it as (string-of 2) + 2; string-of, being a compile-time operator, yielded a string literal "2", which then implicitly cast to a string pointer, which then got incremented by two right past the end and into the next string, which happened to be "23".That sounds pretty horrifying! But reading the issue (linked from the github issues linked by sibling), the actual response seems pretty reasonable. He looked into it and determined it was an issue with your setup, and asked for more information (if he couldn't reproduce it, what else could he do?). He also made a guess at what was wrong. You provided more information and a workaround (nice!) and a few weeks later he closed it saying he believes its now fixed. Seems ok to me.
I wasn't personally developing the core simulation software itself at the time, just building it from source and writing extensions. I don't know if these frequent breakages were actually outright compiler bugs or just relying on conventions that weren't guaranteed by language specs. But I do know that switching to one particular blessed compiler revision was often the fastest way to pass validation and move on to your actual research problems.
The program had a memory leak. It was about the same whether it was deployed through Cordova on iOS or node-webkit on OSX or Windows.
There was a _lot_ of code in this thing. A lot of code within the content (which was written by another vendor) and a whole bunch of code in our wrapper that was there to emulate legacy behaviour of the old ObjC app it replaced.
We assumed the leak was somewhere in our code or the other vendor's code. We spent _months_ trying to figure this thing out. I spent countless hours looking at heap dumps. Going over every bit of dumb code to try and figure out what was wrong. This was a big undertaking with a lot on the line from our client.
Then one day we upgraded the version of node-webkit. It happened to include a newer build of webkit. The bug _vanished_ on the OSX and Windows builds. That was our big break. All of a sudden the possibility of a compiler bug was on the table.
We ended up tracking it to a bug in webkit's implementation of (iirc) the html5 filesystem API. Nobody used it much and as far as I can tell, we were the only ones using it as heavily as we were.
Happy days for OSX and Windows, but the iOS build was still horrible because iOS was using a webkit build from before the patch.
We begged and pleaded with Apple to give us some indication of whether or when the build would be updated. wkwebview came and it was still busted.
The project ended up being closed down, in no small part due to the iOS bugs. I moved on. I have no idea to this day whether or not the iOS build has been updated or not.
wkwebview quite possibly brought a major version update, but it sure didn't include the patch we needed. Whether that was because Apple works from a fork that didn't include that patch or the major version was still older than the version we needed, I don't know. There was just no way to get that kind of information out of them.
iirc there was some way to figure out what version of WebKit a given iOS device was using and it was definitely pre patch.
Surely they must sync up, but when? Unless you work inside Apple there's just no way to know. Pretty frustrating as a developer trying to build for their platform.
BTW, what you're talking about isn't a compiler error.
It was pretty out there to use the HTML5 FS API the way we were. It allowed us to share a _massive_ amount of our codebase between iOS, OSX, Windows and later Android though.
The development efficiencies we gained from doing so were pretty incredible. Given the budgetary constraints on the project if we hand't pulled off the code sharing piece it would have been shut down a lot sooner.
Neither was the blog post:
> [ Addendum 20171113: Yes, yes, I know sort() is in the library, not in the compiler. I am using "compiler error" as a synecdoche for "system software error". ]
Backwards compatibility and uses that are niche but vital. Programmers insist on using parts of languages long after they're discovered to be bad ideas, or when they're included in the language for very specific use cases that aren't relevant to 99% of programmers. Those parts of languages are catnip for "real" programmers.
"You'd be foolish to use this technique, and you'd have to be stupid to need it for this application."
"That's a matter of opinion, but it's fact that I'd have to be clever to make it work."
Can't argue with someone who thinks like that.
Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?
Brian Kernighan
Have someone sit down with you and explain how slow computers were in the 1970's. C won because it could be efficiently implemented on everything, produce better code than pretty much anything else, and still produce effective software quickly. And everything after that is a network effect because of all the inherited C.
Your complaint has been repeated ad nauseum by literally generations of programmers who simply can't imagine why everyone did things this way, and who then go on to productive careers writing or integrating or just living with software still written in C.
If you want to change the world, you need to stop pontificating and roll up your sleeves. There's a whole lot of code to write in rust, or whatever your particular brand of zealotry demands.
I'll say it again, C won because:
1. Compilers could be (and were!) written for any byte-addressible architecture by one hacker in a basement...
2. ...that produced output code quality comparable to what you could get anywhere else in the industry
3. ...and take advantage of the huge community of C hackers.
Different languages running on supercomputers and non-360 mainframes wouldn't have changed any of that.
I'm astonished that "principle of least surprise" would be written "POLA".
> If the language can surprise you easily and lets you write weird code that is going to work a way different from what one will probably assume intuitively - that's a language made wrong.
I agree that much of what is unhelpful about C could be changed without compromising its principles. Implicit conversions, for instance, and implicit fall-through.
Fortunately there are compile-time warnings and run-time checks (such as Clang's 'ubsan') to take the edge off.
Not a "compiler bug" but a "gosh I wish some people who are working on a project voluntarily that I'm not paying for would do a crap-load of work" bucket: for a while GCC could either compile a program with threads or with exceptions. If you had both threads AND exception code (even if you never threw an exception), the emitted code would eventually crash.
A few days in, I discovered that they compiled all their code with all compiler optimizations disabled. They had done so ever since they encountered a bug in the compiler/optimizer where the compiler would somehow think that a boolean expression involving floating point numbers in the head of an if-statement would always evaluate to true or false and thus optimize away either the if-part leaving only the body, or remove the whole if-statement.
I did a little bit of research and found what I think was that bug in the release notes to an earlier version, but even so the other programmers were adamant in keeping optimizations disabled.
(FWIW, I once did a build with all reasonable optimizations enabled, and the improvement was miniscule. Either our code sucked so hard the optimizer could not do much about it, or it was so good there was nothing left for the compiler to optimize. I doubt it was the latter, though. ;-) )
Watcom used to have a reputation for generating fast code, but that was in the 1980s, early 1990s. The impression I got was that by the time I started working at that company, OpenWatcom was mostly around because it was very good for compiling code for DOS.
It supported all of those weird memory models from the days of segmented memory, it had its own 32-bit protected mode which I assume many programmers back then welcomed enthusiastically.
Plus, I read somewhere that OpenWatcom is practically the only compiler that can create DOS executables to be stored in ROM (for embedded / industrial applications, I guess). Or something like that.
If you declared something like :
int a = 1 + 2 + 3;
The result would simply be "a=3". Turned out the compiler threw out any argument besides the first two.Another error would be the compiler failing to increment the segment pointer (16 bit platform), and the offset pointer simply wrapped around. This didn't show itself until we added a new 120x90 pixel four color display, meaning our display shadow buffer grew to a staggering 20 KiB, and depending on the build, the display buffer would become corrupted.
Certification for GSM compilers is expensive, so we just learned to work around it, i.e. we reimplemented our stdlib memcpy, memmove, strcpy, etc functions in assembler.
the offset pointer problem however was the least troublesome. The problem with disappearing arguments took a long time, and a lauterbach hardware debugger to figure out :)
Here's your problem
Hence why GCC ate most of the compiler market
Hardware companies building software usually goes bad unless they're really really good
As of this moment I am begrudgingly creating a support ticket with AWS for an issue which I am 99.99999999% confident is in our VM and has nothing to do with AWS itself.
For some reason, "AWS issues" is one of the first things raised, even though we are unlikely to be hitting AWS edge cases, not being Netflix-scale.
Honorable mention: "Kernel issue"
That's aside from the more common issues of "this VM is just going to bite the dust", which is something I feel is just inevitable.
The kernel is more reliable, IMO (except when it comes to the Thinkpad TrackPoint drivers).
That's exactly documented behavior: http://docs.aws.amazon.com/AmazonS3/latest/dev/Introduction....
That's a hugely unuseful caveat in practice though. I'd really love to have consistent reads.
The compiler actually crashes rather than returning anything? Well, that's more likely to happen when your code has lots of errors in it. "Don't look at me like that. Go fix your code and then come and complain..."
1. libdivide found a gcc 4.8 vectorizer bug in high multiplies. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61108
2. Circa 2001, I decided to learn Java. My first-ever Java program performed a right shift by -1 (for some reason I can't recall but was probably really stupid). The spec requires this to be equivalent to >>31, but the compiler wrongly optimized it to >>0. My confused post can probably still be found in comp.lang.java.help.
3. I wrote a Python script that generated a large set of test cases for an FFI (NSInvocation). The test crashed inexplicably on PowerPC64 with a nonsense backtrace. Turns out the PPC64 jump instruction has a 24-bit literal offset, and my functions were so large they overflowed it. That got fixed (also in gcc).
I had a project that lost $150,000+ because of a bug in perl's sprintf (but not printf, mind you).
https://rt.perl.org/Public/Bug/Display.html?id=82418
Yep, that ticket was open for well over a year.
* OS - I've never found an OS bug that wasn't widely known
* Compiler - I've found 2 compiler bugs in my career to date (both of which crashed the compiler, making this very clear cut)
* Standard libraries - I've found a moderate handful of bugs in standard libraries, somewhat proportionate to the code surface.
* Third party libraries - I rarely use a third party library without exposing a bug.
* Applications - I find bugs in almost all of the applications that I use.
* My own code - Most of my professional life is spent debugging my own code.
There are some valid use cases for Bubble Sort for small and almost sorted arrays (although Array.prototype.sort sounds like a very general solution, which I suppose is not a good idea). I've seen this in JavaScriptCore where they use Cocktail Sort (a variant of Bubble Sort) for sorting in some situations. They also added some performance numbers and explanation in [1] that show that it's faster than e.g. std::stable_sort. I actually tried to write an Insertion Set (which is supposed to be better than Bubble Sort), but couldn't get it to perform better than their Cocktail Sort implementation.
[1] https://github.com/WebKit/webkit/blob/master/Source/WTF/wtf/...
One of the other teams found that their robot kept freezing after a few minutes of moving around the arena, I assumed this was due to exhausting available file handles or some other bug in their code. But after some time they realised that the bug only happened when the metal castor they had used on back of their robot touched the metallic tape.
Sure enough, when they replaced the castor with a bit of lego their robot continued to run perfectly for the duration.
The lego construction of the robot must have been building up some static charge which was then discharged when running over the metallic tape and taking out the ARM computer when it did so.
I'd have never believed that one unless I'd seen it with my own eyes.
I've hit dozens or hundreds of these, across many languages for decades.
It's not the first thing to check, but once you verify the code is logically correct, that can be the only conclusion.
Compilers aren't excessively complex code, but all code has bugs.
To be fair, sometimes that's because the compiler became more strict and your code had an error that wasn't being caught. Sometimes it's the compiler taking a more liberal stance on what "undefined behavior" is for performance reasons. In both cases, that would still a programmer error (and I think those are likely the more common case than an actual compiler error).
We ended up staying late in order to try to find a minimal reproducible example. We finally managed and found that the cause was a basic trigonometric function in the standard library giving the wrong result.
At first, we couldn't believe it. Then we googled the issue and found a bug report about the intrinsic version of that function not working properly. With Microsoft confirming that it didn't and that they weren't looking to fix it. We disabled intrinsics in that section of the code and the original code worked as intended.
1) Borland Turbo Pascal, where code would not execute correctly in the presence of a comment near the top of a loop. Deleting the comment caused the correct loop execution.
2) Texas Instruments DSP C-compiler sometimes failed to unroll loops correctly. Certain loop forms needed to be avoided as a result.
3) Recently I've been working on a C-compiler written in Golang, and the grammar definition was raw enough that it definitely had bugs. The bugs were fortunately obvious in that it would refuse to compile valid C code.
Of course, 99.98% bugs were mine.
If the answer is you just don't care about the perfromance and can say why it isn't important (at least yet) as long as you avoid the pathological then that's a good answer. It's a really bad answer to assume it when you can't. IMHO.
Others have pointed out that bubble sort is great when your data is already mostly sorted. The textbook example is check stub ids, which are sequential - probably an outdated example nowadays. The benefit is greater than a O() analysis would have you believe because running sequentially through contiguous memory is something computers are just great at doing due to memory caching and prefetching. Minimising cache misses /may/ dominate the number of passes of the data in performance analysis. If you don't care about performance then an O(N^2) algorithm may be also something you don't care about.
And if you believe sequential access is gonna save you from O()... you're so wrong its not even funny. Of course the curves will cross latter. But they will cross. Especially on a sort.
http://warp.povusers.org/grrr/bubblesort_eng.html
http://warp.povusers.org/grrr/bubblesort_misconceptions.html (Bubble sort is not great when your data is almost sorted. Insertion sort is, though.)
"Bubble sort always performs n-1 passes through the data (where n is the amount of elements), and it always performs (n-1)+(n-2)+...+1 comparisons regardless of how the data is organized to begin with."
That is just false. 1 pass and n-1 comparisons then stops for already sorted data is what you get. An implementation that doesn't show that characteristic has been either deliberately or accidentally pessimized to behave poorly. You can gimp any algo to make it look bad. Quicksort without randomization on a pathological case is O(n^2) for example and nobody cares for the overwhelming majority of practical purposes. If your definition of bubble sort is that it must be O(n^2) always then we're not using "bubble sort" to mean the same thing and it's pointless to continue the discussion.
Bubble sort is usually operates on data in place. I don't know how to do an efficent in-place selection sort for mostly sorted data, maybe it's possible? You pay for additional copies in both space and cpu cycles. 2xn space for selection usually - but maybe this can be avoided without paying a very high time overhead, (eg find the spot, ripple all data below the spot down to the hole where you selected to open a slot for insert or similar).
Count the memory operations, ie number of memory reads and number of memory writes for a given set of data comparing the two algorithms.
Suggestions: Write 50 odd lines of C code to see the actual effect in cpu cycles. Craft data to make each algorithm dominate the other, it's really interesting to see.
I'm disinclined to say "Never uses algo X, always use algo Y!" Because it's really hard for that to be sensible given all the various shapes and sizes input data takes. Know your data is always really, really good advice.
What about insertion sort? Which always performs fewer comparisons and swaps than bubble sort for any array, by the way.
What is it with this thread and "nyer, you're wrong" responses? It's not helpful, not clever, not useful and not pleasant to read for anybody. Finding out you're wrong is great, I learn stuff.
Have you actually looked at the insertion sort algorithm? It doesn't allocate any additional memory. Neither does bubble sort or selection sort.
Knuth does some analysis of Bubble Sort in Volume 3 of the Art of Computer Programming, which proves bubble sort performs more comparisons and more swap operations than insertion sort. To quote the conclusion: "[A]lthough the techniques used in the calculations [to analyze the bubble sort] are instructive, the results are disappointing since they tell us that the bubble sort isn't really very good at all. Compared to straight insertion […], bubble sorting requires a more complicated program and takes about twice as long!"
6 comparisons, 1 swap with bubble and it stops. I make it 7 memory reads and 2 memory writes.
Insertion and selection sorts are going to run in half that time? I'd really like to see that! I think Knuth might be talking about a general case of any data rather than the topic being discussed here, but no, I haven't read Knuth on bubble sort so I can't be sure. I may do so now so thank you for the pointer. Arguments from authority really can be informative and useful despite their other shortcomings.
To avoid confusion, here's the bubble sort algorithm:
procedure bubbleSort( A : list of sortable items )
n = length(A)
repeat
swapped = false
for i = 1 to n-1 inclusive do
if A[i-1] > A[i] then
swap(A[i-1], A[i])
swapped = true
end if
end for
n = n - 1
until not swapped
end procedure
As you can see, after performing a swap during the first pass through the array it has to go through again (though it can skip the last element because we know the last element must contain the largest element in the array)Insertion sort will perform 7 comparisons, 1 swap, 7 memory reads and 2 writes.
Perhaps harry8 was thinking of the bubble sort implementation in the OP, which would indeed stop after one pass in these cases.
Here is insertion sort
void insertion_sort(int s[], int n) {
int i, j;
for (i = 1; i < n; i++) {
for (j = i; j > 0 && s[j] < s[j-1]; j--) {
swap(&s[j], &s[j-1]);
}
}
}
You can even improve this further (some improvements have new names), however its base form is not in the class of bad algorithms to never use. Besides Bubble sort, Bogosort comes to mind. And there are of course nice algorithms to use if your data works for them, Radix sort comes to mind. Knowing your data is good advice, knowing never to use bubble sort is also good.It really isn't, compared to insertion sort. https://users.cs.duke.edu/~ola/papers/bubble.pdf shows insertion sort performs 5x better. I also like Knuth's Quote in TAOCP: "[A]lthough the techniques used in the calculations [to analyze the bubble sort] are instructive, the results are disappointing since they tell us that the bubble sort isn't really very good at all. Compared to straight insertion […], bubble sorting requires a more complicated program and takes about twice as long!"
> running sequentially through contiguous memory is something computers are just great at doing due to memory caching and prefetching
Name one sorting algorithm that doesn't involve purely running sequentially through contiguous memory.
It'd make me question the stability of the rest of their product too.
We checked, and this particular bug was not caught by the test262 suite - the broken sort worked for enough cases to slip past the Array.prototype.sort tests there. We've added a regression test for this issue in the layer that consumes the interpreter, but I've thought about proposing more aggressive validation of sort behavior to the test262 project.
It was a great reminder to me that while tests may be necessary for quality, they are not always sufficient!
Brad, Code.org Engineering
I later did find bugs in GCC (version ~ 1.37), but since it's pretty robust these days, notably the regression suite is a lot better than it used to be.
"In Code Complete, Steve McConnell cited two studies that proved it:
"A pair of studies performed [in 1973 and 1984] found that, of total errors reported, roughly 95% are caused by programmers, 2% by systems software (the compiler and the operating system), 2% by some other software, and 1% by the hardware. Systems software and development tools are used by many more people today than they were in the 1970s and 1980s, and so my best guess is that, today, an even higher percentage of errors are the programmers' fault."
I'd really like to learn how to track down hardware bugs, or mess with things like Christopher Domas does.
[0] https://blog.codinghorror.com/the-first-rule-of-programming-...
[0] A. R. Brown and W. A. Sampson, Program debugging: the prevention and cure of program errors. Computer Monographs, 18. 1973
[1] Thomas J. Ostrand and Elaine J. Weyuker, Collecting and categorizing software error data in an industrial environment. Journal of Systems and Software, Volume 4, Issue 4. 1984
I couldn't actually get a hold of [0]; however, [1] was easy to find, and it goes into great detail about it's categorization of "faults". In fact, the abstract explicitly mentions that it catogorizes and analyzes 173 faults. On page 7 (page 295 of journal), we finally get to your question:
"Of these 171, 15 were attributed to clerical mistakes, such as incorrect typing or misunderstood handwriting."
So 15/173, or about 8.7%, of the faults they found were of the kind you mention. Interestingly, from earlier on the same page, "... 12 represented compiler or operating system faults," so only 20% less frequent than "clerical errors".
This paper also compares with a lot of other studies, so it's worth reading if you're interested.
Thanks for asking a good question!
Errors in the run-time library and errors that crash the compiler are much more common. While many eyes cover a lot of search space. The space they have to cover has grown immensely.
If you step off the trodden path you can find problems much more often. Just recently I encountered a function in a run-time library that freed the amount of memory the alloc asked for instead of the amount of memory the alloc function actually returned (Sometimes it returned slightly more if the remaining space was not enough to hold a free-memory structure).
It would not surprise me if the population using that particular compiler/platform combination numbered less than a thousand.
If you are beginner dev (I would say over 90% of all devs) or experienced but never try to stress out your stack, it is not very likely that you are going to encounter and properly recognize an error in your compiler. Even if you encounter one, you will most likely change something until it starts to work again and then you will shrug it off.
On the other hand if you are very experienced, you are routinely writing code that stresses out your compiler (think algorithmic trading, etc.) and you are in a habit of trying to get to the root cause of every failure, you are very likely to find errors.
No non-trivial software is without bugs and compilers are some of the most complex tools used in our job. Because they are very complex and heavily used, all low-hanging bugs are already explored very well so what's left is typically very complex and requiring expertise to recognize and debug.
That said, I think most people - me included - don't reach for "compiler error" until we've exhausted every other possibility.
The original source code did nothing special - it was simple code and due to specific optimisations that the compiler performed, inexplicable seemingly random errors would arise. One would have to constrain the optimisations the compiler would do to get the code to execute correctly.
No compiler is bug free. The bugs can manifest in strange ways and sometimes they will manifest only on specific kinds of data (edge case data).
Certainly there are many programs that are written incorrectly and give rise to apparently inexplicable errors that are due to misunderstood language features or specified compiler limits.
However, there are also many programs that do not have such source code errors that end up being rewritten in some way to avoid compiler based errors.
The worst I have seen is when dummy statements have to be put in to the source code in order to get the program to work correctly. I have seen compilers change the precision of numbers from one version to the next and you have to add additional code to restrict those numbers back to the specified range. I have seen compilers allocate multiple i/o buffers as an optimisation that causes a program to fail when a single i/o buffer allocation works fine - one has to turn off the optimisation - no documentation available as to how the compiler generated code uses those buffers.
All I can say is that it takes a fair amount of investigation to determine what kind of bug it is when it is subtle and randomly occurring. It can be everything from errors in the source to errors in the libraries to errors in code generation to errors in the o/s (including driver errors or memory errors).
Anyone who blanket says the compiler is not to blame is living in a rose coloured glasses world. Compilers are complex programs and the source code for them can be just as wrong as for any other program. The code generated for compilers can be just as bug ridden as any other program.
gcc's __attribute__(aligned(N))
Whenever you see it if you assume it doesn't do what you think it does from reading the docs that's probably a sound strategy.[1][2]
I'd love it if I'm wrong and there is a way of understanding what it is meant to do and successfully predicting behavior.
[1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82914 [2] https://gcc.gnu.org/ml/gcc/2014-06/msg00308.html
When declaring variables, `__attribute__((aligned(N)))` indicates you want the variable in whatever format it was defined in aligned to that value. If it is less then the default alignment, it will be allowed to be aligned to that instead. For `struct`s, this would mean that the `struct` is kept in the exact same format (with the same padding, in particular) as the declaration would require, but the entire entity itself is instead aligned to whatever alignment you give.
When declaring a `struct` type, however, the individual members all still have their own alignment constraints, which is where `struct` padding comes in. So when you just apply `__attribute__((aligned(N)))` to a `struct` type declaration, it's not clear how you want to achieve that alignment. If that alignment is a multiple of the required alignment for the `struct` then there is no problem, because you implicitly meet the alignment requirements already.
If it is less, however, then it is not clear when you want alignment requirements met and when you do not (IE. Where should the compiler insert the padding bytes in the `struct`? Should it unalign the first member and then correctly align all the rest? Or pack it as though everything is correctly aligned, and then allow the entire thing and all the members to be unaligned?).
There is already an attribute to solve this problem, `__attribute__((packed))`. All `packed` does is force the alignment of all the members in a`struct` to be 1, which both tells the compiler no padding bytes are necessary, and also means that `__attribute__((aligned(N)))` always works, because every `N` will always be some multiple of 1.
So with that in mind, in my opinion the bug largely on EMACS' side (Assuming I'm reading those messages and code correctly). In my opinion, they shouldn't be using the `aligned` attribute on the variable declaration at all, and instead they should just ensure the `struct thread_state` type itself already has the proper alignment. The fact that they are using `__attribute__((aligned(8)))` seems to indicate to me that somewhere align the line a `struct thread_state` type was defined with the incorrect alignment, and it was fixed by adding the alignment to the variable declaration. But if you just apply the attribute to the declaration of the `struct thread_state` type instead, then everything would be fine, and architectures with a `struct thread_state` with a higher-alignment (like 32, in this example) wouldn't be affected by the alignment requirements of architectures with a lower alignment.
That said, there is something I found very interesting in those posts, though I don't believe it is a `gcc` bug. But it is this syntax:
struct __attribute__((aligned(N))) s var;
I didn't actually know you could even put the attribute in-between the `struct` and the structure name. That said, it does make some sense when you realize what it is doing, even though the syntax is kinda screwy - it is for applying `struct` attributes after-the-fact. So while this does work for `aligned(N)` (which has variable and `struct` variants) it would also probably work for `packed`. This would likely make most sense to use in a `typedef`, which would allow you to declare the 'normal' version of a `struct`, and then `typedef` a `packed` version or `aligned` version as well. I've never really had a need to do that, but it is interesting to see. It does technically fix the problem here, but it is the same as attaching an `aligned` attribute to the `struct` type declaration after-the-fact, and I really do think if possible they should be looking at putting it on the declaration of `struct thread_state` instead.All that said, I wouldn't consider myself an expert, so if you think I'm wrong please let me know. That said I believe it all fits into what the documentation says about this attribute. I hope the above wasn't too confusing, I attempted to make it clear when I was talking about a "variable declaration" vs. a "struct type declaration", but I'm not sure I was always successful.
Over two years and no forward progress! Admittedly, I haven't re-tested this with a newer compiler. But apparently neither has anybody else.
#include <stdio.h>
#include <stddef.h>
struct s {
char b;
int k;
};
int main()
{
printf("offsetof packed k: %zd\n", offsetof(struct __attribute__((packed)) s, k));
printf("offsetof regular k: %zd\n", offsetof(struct s, k));
printf("alignof inline: %zd\n", __alignof__(struct __attribute__((aligned(32))) s));
printf("alignof before: %zd\n", __alignof__(__attribute__((aligned(32))) struct s));
printf("alignof regular: %zd\n", __alignof__(struct s));
return 0;
}
The outputs of this program (Just the numbers) are 4, 4, 4, 32, 4. If the 'inline' attributes were actually respected, the results would be 2, 4, 32, 32, 4. Meaning, the attributes in test's 1 and 3 are completely ignored but do not produce warnings. Also interesting, test 4 actually produces a warning about the `aligned` attribute being ignored even though it actually respects it (as it should). I think this is a just weird issue edge-case with the `alignof` operator specific to `gcc`, since `alignof` takes a 'type' as input, but it's definitely a bug. If you declare a variable with that type there is no warning.Besides the warnings this doesn't surprise me too much, AFAIK it is not legal to put anything between the `struct` and the structure name in usages of a `struct` type. But from the response gotten over at the `gcc` Bugzilla I would wager the 'inline' versions are in the uncharted waters of "Nobody really knows what that should do anymore, don't do that". Reading the `gcc` bug report again, it seems the last response does actually indicate the error I'm pointing out above, where the attribute is silently ignored, I missed that when I skimmed it the first time.
With that said, the fix EMACS implemented was the same as just removing the attribute completely. If the EMACS code really does need that alignment forced for one of its architectures, it should probably take a look at that fix again and apply the `aligned` attribute on the `struct thread_state` for the architecture(s) that need the 8-byte alignment (If they actually exist), and then remove the attribute completely from the variable declaration.
Walter was quick with a fix. Sometimes it is the compiler, but usually it's not.
In 10 years, I've seen 3 compiler bugs. That's just enough to leave the door cracked open for "y'know, it could be a compiler bug..."
As somebody who learned C in a way that over time made me aware of the whole "compiler bug" vs. "the standard said so" thing, I'm still not over the nightmare that was "Admitting Defeat On K&R in LCTHW"... Beneath is the link, be warned that this read is probably not for the light hearted.
https://zedshaw.com/2015/01/04/admitting-defeat-on-kr-in-lct...
For example: https://stackoverflow.com/questions/45424272/gnu-gcc-bug-whe...
C++ was another hotspot of brokenness for early gcc 4.x.
We had this weird behavior some years ago in an embedded functional safety-related project where a case tool generated the code for the compiler.
When the generated code looked like this:
i++;
i++;
i++;
i++;
i++;
if (i > something) {
} else {
}
Then the compiler was miscalculating the jump by 2 bytes in the else path.But if the generated code was this (spot the difference):
i++;
i++;
i++;
i++;
if (i > something) {
} else {
}
The compiler correctly calculated the jumps. If certain optimization option was enabled, the linker afterwards corrected the miscalculated jump.The compiler vendor could reproduce the behavior aka bug, but could not say with confidence the circumstaalnce what the cause for this bug was. As it was for a function safety-related product we where required to identify all jumps caused by if clauses and had to review the assembly, if the jumps in the assembly were correct.
http://envisage-project.eu/proving-android-java-and-python-s...
Don't think I've seen one since, though I have found a nasty footgun in Scala where if you do a fold of a parallel collection, you can give the fold a starting value, which will be supplied to all the executing threads. The result therefore depends on how many cores you have running. Sadly I suspect that one's in spec.
Browsers are incredibly complex pieces of software which are constantly evolving. Go look at any browser's issue tracker and you'll find a huge list of bugs.
“We now use the phrase "select is broken" as a gentle reminder whenever one of us starts blaming the system for a fault that is likely to be our own.”
That quote appears right above «Tip 26: “select” isn’t broken.»
Of course, that was pre-1.0 software..
https://github.com/micropython/micropython/issues/610
In the end it was a buggy implementation of the & operator on long integers, which was fixed very quickly.
Obviously things like forward and backward compatibility are much more important in the programming context, so you can't just "fix" things like weird side effects or not throwing an error on assignment in an if statement.
The other bugs were all mine.
I still always blame my own code first though
which only gets printed if the shell it's running in egregiously mishandles &&...
It was fixed in Java 1.0.3.
Parent comment makes sense to me as a general comment about getting good at JS. Especially given JS has so many new and exciting libraries that get used in production. Ultimately you must fix your code, JS & libraries aren't likely to change in a timely fashion. And of course what I call a bug in the spec must have been considered a feature at one time, some may still consider it so. That's a question of taste and you can always consider yourself 100% correct on any matter of taste simultaneously with those who disagree. :-)
if(foo) { <- js cannot insert semicolon there
Edit: add this comment you have provided approximately nothing in support of your assertion, you can and should do better. I note this thread is JS and a whole lot of this kind of thing going on here I don't usually see elsewhere on HN. It's a shame, discussion is useful to learn things I don't already know. "Nyer, your wrong" not so useful to me, you or anyone else.
I’ve written a ridiculously detailed blog post about automatic semicolon insertion if you want to know all the specifics.
return {
};
and return
{
};The K&R brace style is not about putting an object literal on the same line with a return statement or on another line, but about how control structures are formatted.
The reason I said that K&R brace style has nothing to do with semicolon insertion is that it’s possible to write all your control structures with K&R braces and still be bitten by the places in the grammar where line breaks are not allowed, like after a return statement, if you’re unaware of it.
It’s also possible to use Allman braces everywhere and never have that problem with return statements.
So yeah, it's sometimes a compiler error.
console.log lazily evaluates complex values at first-expand-time, rather than eagerly at print-time, and there is no built-in facility to automatically expand them at print-time. To see this being weird, try logging a changing array inside of a loop, then expand the first log after the loop completes. It will show the array state at click-time, rather than at the point in time the log statement executed. Subsequent changes to the array will not be reflected.
People have been complaining for years about this, but it seems to be widely accepted as just-the-way-it-is[2-4], with the most common workaround being JSON.stringifying logged objects.
I'm mostly shocked that I only noticed I was being bit by it a little while ago.
1 - (2010, shows 2012 fix) https://bugs.webkit.org/show_bug.cgi?id=35801
2 - (2010, w/ edits to note 2012 fix) https://stackoverflow.com/questions/4057440/is-chromes-javas...
3 - (2014) https://stackoverflow.com/questions/23392111/console-log-asy...
4 - (2016) https://taoalpha.github.io/blog/2016/04/21/tech-lazy-evaluat...
Pascal functions use assignment to function name as the way to set the return value, as it happens, Turbo Pascal would allow declaring a local variable with the same name and then return garbage as the function's result.
was sorting the numbers
into
the
wrong
order.> [5, 4, 5, 1].sort().join('')
> "1455"
See the pull request here https://github.com/code-dot-org/JS-Interpreter/pull/23
// but it should have been:
if (changes == 0) break;
While this fixes the problem, it no longer handles the case where count is negative. Yes, in this function it's likely that "this never happens", but it's generally good practice assume as little as possible. // still stops on negative values
if (changes < 1) break;
or if negative values could indicate some kind of serious problem: // first trap problems
if (changes < 0) raise_error();
if (changes == 0) break;Of course. As I said, I know this is defending as case that shouldn't happen.
> the programmer doesn’t need to defend against it
My point is that this is a dangerous attitude that can create bugs. A less-than comparison should cost the same as testing for quality, so only testing for 0 isn't faster.
Why are negative values nonsensical? Is is because the variable is named changes? The algorithm in the preceding code? Comments in the code near that variable? All of these things can change as code evolves. Variables are repurposed, algorithms change (sometimes radically), and updates to code can desynchronize from comments.
You could consider it an extension of the "0" condition. However...
> terminate execution, because the program is incorrect.
...this is probably better than simply terminating the loop.
i.e. into the read-only portion of the binary.
The result, of course, was that writing to the variables caused a core dump.
So yes, 99% of the time, stupid programmer errors are stupid programmer errors. But compilers do have bugs.
e.g.
I hope Mark Dominus has learned how not to use sarcasm and condescension in his coaching style since then. Toxic environments kill people.
https://gcc.gnu.org/bugzilla/buglist.cgi?bug_status=__open__...
https://bugs.llvm.org/buglist.cgi?bug_status=__open__&no_red...
I fight compiler bugs all the time. Poor optimizations, poor comparisons, bad allocations, incorrect warnings and errors, long compile times due to bugs, don't even get me started on GCC intrinsics and SIMD.