August, 2012

  • The Old New Thing

    Adventures in undefined behavior: The premature downcast

    • 45 Comments

    A customer encountered the following problem:

    class Shape
    {
    public:
        virtual bool Is2D() { return false; }
    };
    
    class Shape2D : public Shape
    {
    public:
        virtual bool Is2D() { return true; }
    };
    
    Shape *FindShape(Cookie cookie);
    
    void BuyPaint(Cookie cookie)
    {
        Shape2D *shape = static_cast<Shape2D *>(FindShape(cookie));
        if (shape->Is2D()) {
           .. do all sorts of stuff ...
        }
    }
    

    The Buy­Paint function converts the cookie back to a Shape object, and then checks if the object is a Shape2D object by calling Is­2D. If so, then it does some more stuff to figure out what type of paint to buy.

    (Note to nitpickers: The actual scenario was not like this, but I presented it this way to illustrate the point. If you say "You should've used RTTI" or "You should've had a BuyPaint method on the Shape class", then you're missing the point.)

    The programmers figured they'd save some typing by casting the result of Find­Shape to a Shape2D right away, because after all, since Is­2D is a virtual method, it will call the right version of the function, either Shape::Is­2D or Shape2D::Is­2D, depending on the actual type of the underlying object.

    But when compiler optimizations were turned on, they discovered that the call to Is­2D was optimized away, and the Buy­Paint function merely assumed that it was always operating on a Shape2D object. It then ended up trying to buy paint even for one-dimensional objects like points and lines.

    Compiler optimization bug? Nope. Code bug due to reliance on undefined behavior.

    The C++ language says (9.3.1) "If a nonstatic member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined." In other words, if you are invoking a method on an object of type X, then you are promising that it really is of type X, or a class derived from it.

    The code above violates this rule, because it is invoking the Is­2D method on a Shape2D*, which therefore comes with the promise "This really is a Shape2D object (or something derived from it)." But this is a promise the code cannot deliver on, because the object returned by Find­Shape might be a simple Shape.

    The compiler ran with the (false) promise and said, "Well, since you are guaranteeing that the object is at least a Shape2D, and since I have studied your code and determined that no classes which further derive from Shape2D override the Is­2D method, I have therefore proved that the final overrider is Shape2D::Is­2D and can therefore inline that method."

    Result: The compiler inlines Shape2D::Is­2D, which returns true, so the "if" test can be optimized out. The compiler can assume that Buy­Paint is always called with cookies that represent two-dimensional objects.

    The fix is to do the annoying typing that the original authors were trying to avoid:

    void BuyPaint(Cookie cookie)
    {
        Shape *shape = FindShape(cookie);
        if (shape->Is2D()) {
          Shape2D *shape2d = static_cast<Shape2D *>(shape);
           .. do all sorts of stuff (with shape2d) ...
        }
    }
    
  • The Old New Thing

    What happened to the Windows 2000 "Language settings for the system" control panel?

    • 41 Comments

    In 2011, a customer had a question about migrating from Windows 2000 to Windows XP. (That's right, this customer was still using Windows 2000 in the year 2011.) Specifically, they noted that in Windows 2000, they can select multiple languages in the "Language settings for the system" portion of the Regional Options control panel, and they couldn't find the corresponding control panel setting in Windows XP.

    Regional Options
    General
    Numbers
    Currency
    Time
    Date
    Input Locales
     
    Settings for the current user
    Many programs support international settings for numbers, currencies, times, and dates. Set the locale in order to use the standard settings.
    Your locale (location):
    English (United States)
    Language settings for the system
    Your system is configured to read and write documents in multiple languages.
    Arabic
    Armenian
    Baltic
    Central Europe
    Cyrillic

    In Windows 2000, "Language settings for the system" provides the option to install support (such as code pages, keyboard layouts, and fonts) for various language groups. In Windows XP, the big list of language groups was reduced to three categories:

    • Basic (Baltic, Central Europe, Cyrillic, Greek, Turkish, Western Europe)
    • Complex (Arabic, Armenian, Georgian, Hebrew, Indic, Vietnamese, Thai)
    • East Asia (Chinese Simplified, Chinese Traditional, Japanese, Korean)

    The Basic category is always installed. To install the Complex or East Asia categories, use the "Supplemental language support" section of the Regional and Language Options control panel.

    Windows XP Regional and Language Options property sheet, with a section titled "Supplemental language support" with options "Install files for complex script and right-to-left languages (including Thai)" and "Install files for East Asian languages

    Someday, that customer might upgrade to Windows Vista, so I may as well answer the question right now. In Windows Vista and onward, things were simplified even more: All language groups are installed at all times. The dialog box went away completely since there were no options remaining.

    As it turns out, the customer's problem had nothing to do with language support. Of course, they didn't come out and describe the problem they were having; rather, they reduced the problem into multiple pieces, and then asked for help on one specific piece. They tried out a solution based on this new information, but it didn't solve the problem, because as it turns out, the Language settings for the system control panel was a red herring. If they had told us what their original problem was, we could have told them "But this setting will do nothing to solve your problem. What you really need is over there."

    Tomorrow, we'll look at the customer's actual problem. (So please don't try to guess or you'll ruin the surprise. I can't believe I had to write that.)

  • The Old New Thing

    If you're going to throw a badminton match, you have to be less obvious about it

    • 37 Comments

    It may be possible based on your location to view what NBC euphemistically calls "highlights" from the women's badminton doubles match between China's Yu Yang/Wang Xiaoli and South Korea's Jung Kyung Eun/Kim Ha Na. The serves go laughably into the net, there is barely any attempt to chase down shots, and returns go far out of bounds. If this is top-level badminton, I think I could be a world champion.

    Both sides had secured their advance into the next round, and Wired explained why both teams decided that a strategic loss would be advantageous. Julie VanDusky-Allen provides a game-theoretical analysis of the situation. (Even if you randomize the seeds in the knockout portion of the tournament, you can still get into a situation where a team in the round-robin portion of the tournament may decide that it is advantageous to lose a match on purpose.¹)

    Partway into the match, an official warns both teams that if they do not make an effort to win, the teams will both be disqualified. That served to improve the quality of play only marginally.

    Okay, they need to study soccer or American professional basketball, where intentionally losing is a long-standing tradition: You need to make it look like you're trying, or people are going to figure you out. For example, play normally most of the time, but then have a mental lapse and "accidentally" make an error that concedes a point.

    At least fake an injury. That'll let you start playing badly with plausibility.

    (Although these techniques for throwing a match subtly probably don't work if your opponent is also trying to lose.)

    Since the attempt to get both sides to play to win didn't seem to help, perhaps the officials should have announced, "We have decided to assist in motivating the two sides by declaring that the loser of the match will be disqualified from the tournament."

    Now they have something to play for.

    ¹ Consider a four-team group with teams A, B, C, and D. In round 1, A defeats B 5–1 and C defeats D 2–1. In round 2, A defeats D 5–1 and B defeats C 2–1. At this point, A is guaranteed advancement as long as it doesn't lose to C by more than 8 points. If A defeats C, then B will advance. But A may decide that it prefers to play against C in the knockout portion of the tournament. In that case, it can intentionally lose to C in the third round by 4 points (leaving a comfortable margin of error), and as long as B doesn't win by more than 7 points, A will get its wish: C will advance.

  • The Old New Thing

    How long does it take for a notification icon to stop appearing in the Notification Area Icons control panel?

    • 31 Comments

    A customer reported that even after uninstalling their application, the notification icon entry remains in the Notification Area Icons control panel.

    Yup, that's right. Explorer remembers the icon, even after the underlying program has been uninstalled, because you might have uninstalled it with the intention of immediately reinstalling it, so Explorer remembers the icon in case it comes back. But after one week, Explorer gives up and finally forgets about the icon. "It's been a week, and the user hasn't reinstalled the application. I'm going to give up waiting for it."

    The customer wanted to know how they could remove their icon immediately upon uninstall. They reported that having the icon remain in the Notification Area Icons control panel made it appear that the uninstall was unsuccessful.

    There is no documented mechanism for removing the icon (and the undocumented mechanisms destroy all the icon history, not just the icon history for your icon, so don't do that either). You'll just have to wait seven days for the icon to go away.

    (One possibility that was considered was to have the Notification Area Icons control panel check if the application is still installed before showing it on the list. This runs into problems, though, if the application resides on the network or removable media. It means that opening the Notification Area Icons control panel can stall on network I/O, generate security audits if you lost access to the network share, and spin up external media. Remember how much people hated it when Windows 95 spun up your CD-ROM drive the first time you clicked on the address bar?)

  • The Old New Thing

    The shifting sands of "Run as different user"

    • 31 Comments

    A customer liaison asked the following question on behalf of his customer.

    When I do a shift-right-click on a shortcut to a program, I see the following:

    • On Windows Server 2008, shift-right-click does not offer the option Run as different user.
    • On Windows 7, shift-right-click does offer the option Run as different user.

      On Windows Server 2008 R2 (the server counterpart to Windows 7), shift-right-click does offer the option Run as different user.

    The option to run a program as another user (other than Administrator) was present in Windows XP, but it was lost in Windows Vista. It appears that we responded to those complaints by restoring the functionality in Windows 7.

    Is that right?

    The odd thing is that my customer has the Run as different user option available on their Windows 7 machines, but not on their Windows Server 2008 R2 machines. Does whether you have access to Run as different user depend on how you installed Windows Server 2008 R2? (If it matters, my customer installed it via the Microsoft Deployment Toolkit.)

    I found this question interesting for a variety of reasons.

    First of all, it comes dangerously close to being one of those Please tell me I'm not hallucinating type of requests. These support requests take the following peculiar form:

    We did X, then Y, then Z, and then Q happened. Is that right?

    "Um, if you say so. I wasn't there."

    But in this case, it started out looking like it was going to turn into a Please tell me I'm not hallucinating request, then veered into "Is X the reason the feature was added back?"

    This is a rather peculiar question, because knowing the answer one way or the other doesn't actually take you any closer to a solution to the problem. (And I don't know the answer, but fortunately, it wasn't relevant to solving the customer's problem.)

    Another interesting thing about the customer's question is that he never actually comes out and asks the question. He sort of says a few related things, and asks a tangential question, but never comes right out and asks, "How do I get the Run as different user option to work on my Windows Server 2008 R2 machine?"

    It's like a kid who pointedly hangs around a candy bowl, hoping that an adult will say, "Here, have a piece of candy."

    You're a grown-up now. You don't have to linger around the candy bowl hoping somebody will figure out that you want some candy. You should just ask, "May I have a piece of candy?"

    My psychic powers tell me that they have set the Require trusted path for credential entry policy. The Run as different user feature is disabled if you set this policy.

    The customer liaison replied, "It appears that the option for Require trusted path for credential entry is not enabled. The customer is going to do a clean install and test on a non-domain-joined machine to avoid any GPOs."

    Some time passed, and the customer liaison reported back with a resolution.

    The culprit was indeed the Require trusted path for credential entry policy. It didn't show up in a GPO search because they were setting the policy via a script rather than deploying a group policy object.

    It was very nice of the customer liaison to reply with confirmation that the problem was solved. This is, unfortunately, a notable event. Most of the time, people never report back if your suggestion solved their program; they only come back if your suggestion didn't help. Which means you're not sure if your suggestion solved the problem, or if it didn't solve the problem but they decided to continue the investigation somewhere else.

    Bonus chatter: This shows yet another reason why you should use Group Policy Objects to manage group policies rather than custom scripts that whack registry keys. In addition to the fact that registry key whacking may not work, there are tools for processing Group Policy Objects that make this sort of investigation much easier.

  • The Old New Thing

    Well at least nobody's parking there any more

    • 31 Comments

    There is a field next to the Microsoft building I used to work in, and for a time, people parked their cars out on the field, presumably because finding a proper parking space in the garage became difficult due to overcrowding. To prevent people from parking in the field, Security placed a large log across the access to the field. The technique worked: Nobody parked in the field any more.

    Some months later, our building had a fire drill, and everybody dutifully filed out of the building and waited until the all-clear signal was given to return. Normally, people would wait in the field, because that is the designated assembly area for the building, but oh wait, Security blocked off access to the field. Instead, people waited in the fire lane.

    Good thing this was just a drill, because they would have gotten run over by a fire truck.

    I pointed out to the Life Safety Security representative who was running the fire drill that Parking Security had created a life safety hazard. My observations were duly noted, and it looks like somebody actually paid attention, because a few weeks later, the log was removed. Now if there's a real fire, we can actually reach our designated assembly area.

    I just found it ironic that the Security department created a safety hazard.

  • The Old New Thing

    Sorry we got cut off, my phone just auto-rebooted

    • 29 Comments

    Some time ago, I was on the phone when the connection suddenly cut out. I looked at the my phone display and it said, Auto-restart in 7 seconds. The seconds counted down, and then the phone rebooted, and after about ten more seconds, it was back in business, acting like nothing was wrong. (Except that it just terminated a phone call without warning, but hey, who's keeping track?)

    Sometimes I think phones are getting too smart for their own good.

    Historical note: This entry was written over four years ago, and the model of IP phone in question has long since been replaced.

  • The Old New Thing

    How does the taskbar decide whether to show Recent or Frequent documents on the jump list?

    • 29 Comments

    Some jump lists show Recent documents, and others show Frequent documents.

    (Images stolen from Sasha Goldshtein.)

    Our friend xpclient wanted to know how the taskbar decides which one to use, because it seemed random. (Ironically, xpclient is asking a question about the Window 7 client.)

    The default is to show Recent documents. But an application can customize its jump list, and in that case, the application can select which category to show based on the guidance in the documentation. (Or the application might choose to ignore the guidance in the documentation and show both.)

    Okay, but what about Explorer itself? You can find that Explorer will sometimes show Frequent locations (e.g., on My Computer) and sometimes Recent locations (e.g., on Control Panel). How is this possible if the decision is made on per-application basis?

    Because the taskbar uses the shell application user model to associate windows with applications. We've taken advantage of this model a number of times before, like when we prevented a program from being pinned, or made one application look like two or many. Explorer sets its jump lists to show Frequent, but if an Explorer window sets a custom application ID, then that window is treated by the taskbar as a separate program and therefore the Explorer default does not apply (and it gets Recent instead).

    To make things even more complicated, xpclient was running a third-party taskbar hacking application which manipulated application ID of windows which belong to other applications. And just like in real life, if you mess with stuff that isn't yours, you can cause trouble for the real owner. In this case, manipulating the application IDs broke Explorer's jump list customizations.

    This is another case of somebody doing some weird customization and then complaining that their system acts all strange (which often leads to the accusation that Microsoft developers are a bunch of idiots) when the strange behavior was caused by their customization. I mean, if you're running a taskbar hacking application, then you really shouldn't be surprised if the taskbar occasionally behaves erratically.

    Bonus chatter: The Recent and Frequent categories are variable-length, which you can customize by setting the Number of recent items to display in Jump Lists.

  • The Old New Thing

    Wait, you never said that I had to initialize the object before I used it!

    • 29 Comments

    A customer reported that they were having trouble creating slim reader/writer locks at runtime. They simplified the issue to a short program:

    #include <windows.h>
    #include <iostream>
    
    using namespace std; // this is just a quick test app
    
    int a = 10;
    
    // This version works
    int working_version()
    {
     SRWLOCK lock;
     AcquireSRWLockExclusive(&lock);
     cout<<"Acquired exclusively"<<endl;
     a++;
     ReleaseSRWLockExclusive(&lock);
    }
    
    // This one doesn't
    int broken_version_1()
    {
     SRWLOCK *lock = new SRWLOCK;
     AcquireSRWLockExclusive(lock);
     cout<<"Acquired exclusively"<<endl;
     a++;
     ReleaseSRWLockExclusive(lock);
     // ignore the memory leak - this is just a quick test app
    }
    
    // This one doesn't either
    int broken_version_2()
    {
     SRWLOCK *lock = new SRWLOCK[2];
     AcquireSRWLockExclusive(&lock[0]);
     cout<<"Acquired exclusively"<<endl;
     a++;
     ReleaseSRWLockExclusive(&lock[0]);
     // ignore the memory leak - this is just a quick test app
    }
    
    int main(int argc, char **argv)
    {
     switch (argv[1][0]) {
     case '0': working_version(); break;
     case '1': broken_version_1(); break;
     case '2': broken_version_2(); break;
     }
    
     cout<<"a="<<a<<endl;
    
     return 0;
    }
    

    "What is the correct way of creating an SRWLOCK via the new operator?"

    It wasn't long before somebody noted that nowhere in the code is the function Initialize­SRW­Lock called.

    "Oh, yeah, thanks for catching that. It looks like one needs to initialize SRW locks which are created via the new operator. Otherwise it's not required."

    No, the function is always required. It's just that you got lucky in the local variable case and the initial stack garbage looks enough like an initialized SRW lock that you don't notice the problem.

    MSDN doesn't say "You must initialize an SRW lock before using it" because the statement was believed to be so obvious that it never occurred to anybody that somebody would think the opposite was true. I mean, what's the point of having an Initialize­SRW­Lock function if initialization is not required? Think of it as one of the ground rules for programming: If an object has an initialization method, you must initialize the object before using it.

    But just to be sure, I've submitted a documentation change request to add the requirement.

    Bonus chatter: A common coding pattern is to wrap the low-level C-style object inside a C++style RAII-style object.

    Bonus chatter 2: If you're creating a highly-concurrent system, then you should probably put each lock on its own cache line.

  • The Old New Thing

    How do I find the most recently created file in a directory from a batch file?

    • 29 Comments

    We've reached Hump Day of Batch File Week. Remember, nobody actually likes batch programming. You merely tolerate it.

    Today, we'll find the most recently-created item in a directory. (For example, we have a server that holds our daily builds, and you might want to write a batch file that automatically installs the latest build.)

    There may be better ways, but what I do is ask for a list sorted oldest-to-newest, and then choose the last one.

    for /f %%i in ('dir /b/a-d/od/t:c') do set LAST=%%i
    echo The most recently created file is %LAST%
    

    This trick works by asking the dir command to list just the names (/b) of just the files /a-d, sorted by date (/od), based on the creation time (/t:c).

    Each time a new file is reported, its name is stored in the LAST variable, overwriting the previous one. When the loop finishes, the LAST variable contains the name of the newest file, since that's the one that didn't get overwritten.

    You can tweak the command line to perform other queries. For example, if you want the newest file, then just ask for a reverse sort (/o-d). If you want the file sorted by modified time rather than creation time, then use /t:w. You get the idea.

    Limitations: The implementation above assumes that no files contain spaces in their name. Removing this limitation is left as an exercise.

Page 1 of 3 (29 items) 123