Bad version number checks

Date:February 13, 2004 / year-entry #61
Tags:history
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20040213-00/?p=40633
Comments:    33
Summary:Version numbers. Very important. And so many people check them wrong. This is why Windows 95's GetVersion function returned 3.95 instead of 4.0. A lot of code checked the version number like this: UINT Ver = GetVersion(); UINT MajorVersion = LOBYTE(uVer); UINT MinorVersion = HIBYTE(uVer); if (MajorVersion < 3 || MinorVersion < 10) { Error("This...

Version numbers. Very important. And so many people check them wrong.

This is why Windows 95's GetVersion function returned 3.95 instead of 4.0. A lot of code checked the version number like this:

  UINT Ver = GetVersion();
  UINT MajorVersion = LOBYTE(uVer);
  UINT MinorVersion = HIBYTE(uVer);
  if (MajorVersion < 3 || MinorVersion < 10) {
   Error("This program requires Windows 3.1");
  }

Now consider what happens when the version number is reported as 4.0. The major version check passes, but the minor version check fails since 0 is less than 10.

This bug was so rife that we gave up shimming every app that had the problem and just decided, "Fine. If anybody asks, say that the Windows version is 3.95."

I suspect this is also why DirectX always reports its version as 4.x.


Comments (33)
  1. Wayne says:

    The best version number API I’ve seen returns the version numbers as a string AND provides a strcmp-like function for comparing version numbers. That way you can perform calculations like so:

    if (Version_Cmp(GetVersionStr(), "3.11") >= 0) {

    Do something

    }

  2. Raymond Chen says:

    Returning the version as a string is no good either. People would do

    if (strcmp(GetVersionStr(), "3.11") >= 0) …

    and then they would fail once the OS reached version 10.

    The function VerifyVersionInfo is essentially the Version_Cmp function above.

  3. In light of these mentioned problems, I would return the value as a 16.16 fixed point number. So, 3.11 would be 3 << 16 + 11 (which is 3 * 65536 + 11 or 0x0003 000B). Then it is a simple compare of two integers.

    Does anyone see any problems with this?

  4. Jack Mathews says:

    It just sucks how much you have to pander to the stupidest common denominator. It also is bad that you can’t pull an Apple and just start off fresh every so often.

  5. Raymond Chen says:

    Jason: You can certainly use the MAKELONG macro yourself to pack the versions into a single integer in order to simplify the comparison. But sometimes people have need to check the sub-minor versions, too, and then you start to run out of space in a 32-bit integer. (For example, Windows XP Service Pack 1 is version 5.1.2600.1106.)

  6. Strong Sad says:

    I’ve seen the very bug that Raymond points out on plenty of Microsoft’s own source code, internally, although in every case I saw, the problem got scrubbed out long before release (usually within days of the first checked-in, bogus, version comparison).

  7. Cooney says:

    <p>Raymond: For example, Windows XP Service Pack 1 is version 5.1.2600.1106.</p>

    <p>In this case, why not just compare the build number (2600)?</p>

  8. Raymond Chen says:

    That’s still not good enough, because Windows XP RTM is 5.1.2600.0 (same build number).

  9. Dylan Greene says:

    Why not 4.2 instead of 3.95?

  10. Matt says:

    It’s absolutely stunning how much poor quality software is out there. Just stunning.

    What constantly frustrates me are applications that won’t run on newer operating systems… yet drivers that WILL allow themselves to be installed on newer operating systems.

    Maybe the industry could come up with a highly visible "hall of shame" for these instances. (Hey, I can dream.)

  11. BuckChuckNorris to the 23rd power says:

    So you quit shimming every program that had bugs, and stuck a shim between the API and the app.

    =)

    sorry, couldn’t help my self…

  12. scott lewis says:

    I’ve always liked Apple’s implementation of versioning: they return a 16-bit Binary Coded Decimal representation of the verson number. The fist two digit are the major number, the last two the minor and subminor, respectively. Thus if you want to require System 7.5.2 for example you write:

    if (version < 0x0752) {

    fail();

    }

    Which still works with 10.3.2 (0x1032). Although, it will break when they get past Mac OS 99.9.9. ;-)

  13. Dylan Greene says:

    > I’ve always liked Apple’s implementation

    > 10.3.2 (0x1032).

    What if minor or subminor version happen to be greater than 9?

    10.10.3.4 > 10.1.3.4

    10.1034 < 10.134

  14. scott lewis says:

    Obviously, under the Apple scheme they can’t be. :)

    However, the general concept can be extened out to any number of BCD digits, and the invariant (old version < new version) will hold as long as the format of the BCD encoded number remains the same. That is, the number of digits allocated to each version, subversion, etc. doesn’t change.

  15. Jordan Russell says:

    Why does Windows XP return 1 as its minor version number instead of 10? Someone made a mistake, I take it?

  16. Jack says:

    "It just sucks how much you have to pander to the stupidest common denominator. It also is bad that you can’t pull an Apple and just start off fresh every so often. "

    You can, its called .NET.

    [Note: This entry has been edited by the blog maintainer.]

  17. Adrian Oney says:

    Things get even more exciting when you include service packs into the mix.

    XP can be said to be a superset of Win2K. Therefore we could say VerXP > Ver2K. Likewise, XP-SP1 can be said to be a superset of XP-Gold, so VerXP_SP1 > VerXP.

    But now the test: is WinXP-SP1 a superset of Win2K-SP4? In other words, is VerXP_SP1 > Ver2K_SP4? And the answer is… not necessarily.

    Win2K-SP4 might contain some bug fixes and backported features (hardware support, etc) that will only arrive in the XP line as of XP-SP2. Therefore, WinXP-SP1 couldn’t be said to be a superset of Win2K-SP4. Whether VerXP_SP1 > Ver2K_SP4 is true depends entirely on the reason you are asking (bug fix, etc).

    As such, version numbers are best thought of as describing *code lineages*. There is a main lineage where Win2K, WinXP, and Win2003 lie. But each major OS forms the base of a fork containing their service packs. Thus versioning could be modeled using trees. You can ask how far down a given branch you are, but that doesn’t tell you anything about the branches that aren’t between you and the root.

  18. Centaur says:

    The guides on Javascript available in the Internet say, “Don’t check for versions; check for objects.” Which is essentially checking for capabilities. Which means

    try {

    LoadLibrary foo("foo.dll"); GetProcAddress<BarProc> barProc(foo, "BarProc"); baz = barProc(quux);

    }

    catch (LoadLibrary::Exception)

    {

    // something

    }

    catch (GetProcAddress::Exception)

    {

    // something else

    }

    I’m imagining some C++ wrappers around LoadLibrary and GetProcAddress here, but you get the idea.

  19. Peter Torr says:

    I’ve always wondered why we don’t just reverse the version checking pattern: instead of the app asking the OS for its version number and (incorrectly) deciding if it is good enough, why doesn’t the app give the expected minimum version to the OS and it does the work to figure out if the current OS is good enough? Eg:

    if (IsWindowsVersionAtLeast(3, 1) != TRUE)

    {

    printf("You need to upgrade");

    exit(1);

    }

    Then version checking — rather than version reporting — becomes a base function of the OS.

  20. Sven says:

    VerifyVersionInfo

    Client: Requires Windows XP or Windows 2000 Professional.

    Server: Requires Windows Server 2003 or Windows 2000 Server.

  21. Centaur says:

    > if (IsWindowsVersionAtLeast(3, 1) != TRUE)

    Is it moral to compare for equality against TRUE? :) I mean, the general convention for BOOL is that 0 is FALSE, and everything else is TRUE, and the TRUE define or constant is usually defined as some arbitrary non-FALSE value, like 1 or -1. 2 is as TRUE as 1, but will fail the comparison against 1.

  22. Raymond Chen says:

    Centaur’s right. Checking for equality against TRUE is asking for trouble.

  23. Shane King says:

    This bug is pretty understandable. It’s a code path that can’t be tested. When a program was released under windows 3.1, there was no windows version greater than 3, so the code path never would have run, and hence never noticed.

    Everyone makes mistakes, so I don’t really blame the devs of such a bug. It’s just one of those things.

    Although you can probably avoid it by pushing your code into a function IsVersionOK(currentVersion), and unit testing that function. However, lack of unit testing usually comes from impossible deadlines and such imposed from above, and hence the managers, not the programmers, are most likely the reason why such code reaches the wild.

  24. Karan Dhillon says:

    Shane, Application Verifier has a test called HighVersionLie to test for this sort of thing.

    http://www.microsoft.com/windows/appcompatibility/appverifier.mspx

  25. "Ivana" says:

    Why not have an enum for known versions.

    public enum WindowsVersions

    {

    2000,

    XP,

    Server

    }

    BLAH, its not going to change at runtime. Saves all the parsing errors and counting.

    [Note: Edited by blog maintainer. This is getting really boring really fast. Will "Jack"/"Ivana" please stop misrepresenting yourself as a Microsoft employee. This is your last warning.]

  26. Michael Grier [MSFT] says:

    Re: capabilities:

    Nobody likes these. Some Highly Placed Folks working on the Big Reset Thingie mentioned simply rejected the idea and said that they would only consider a version numbering based approach.

    Oh well, maybe the Next Really Big Thing after this current Really Big Thing will get it right

  27. Anonymous says:

    I got thinking about how easy we have it sometimes in Linux. In a similar situation, we seem to favour fixing the app rather than the kernel. Then I thought some more, and remembered the painful disaster that is the version number checking in agpgart.

  28. KH says:

    A version number is actually a list of integers, which we happen to write with dots between them, like 5.1.2600.1106. On one level, comparing version numbers by comparing strings like "5.1.2600.1106" seems as silly as comparing floating point numbers by comparing their string representations.

    What you really want is a data structure (call it, oh, Version) that consists of a list of integers. Operations you’d want include converting to and from strings, comparing versions (by finding the leftmost numbers that differ), and asking what the version of the OS/library/whatever is.

    Can you tell I’m up late fixing bugs in C++ code? :-) This sounds like a CS 101 assignment to me.

  29. Lennie says:

    That’s why in the (D)HTML-world we atleast _try_ to do capabilities-checking, instead of browser-strings and scrary things like that.

    Well, we try to do the right thing anyway, there are still cornercases that need extra checking.

  30. What a boring title.

  31. Normally, I’m talking about how to fix applications here, but I want to digress and instead talk about

Comments are closed.


*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index