Suicidal Code
July 13th, 2007Some code is not meant to live forever. In particular, during development phases of projects, it can be very reassuring to know that a particular release of an application will have a short life-cycle, because you’ll be (relatively) assured that users aren’t accidentally running crufty old (development!) versions of the app.
The basic means of accomplishing this is to put a date check in your code that determines if a certain time has come and, if it has, refuses to run any longer. Anecdotally, I can tell you that lots of developers do something like this. But I can also tell you that lots of developers do it the error-prone, labor-intensive way. I did too, until I picked up a great tip from Brian Cooke a while back, that completely automates the process.
The laborious and error-prone method involves manually typing into your code a particular date in the future, and using that date as the test for whether or not to keep running. Brian’s brilliant observation was that the future date is almost always some number of days from “now,” where “now” is the date the code is being compiled. Using a gcc predefined macro, __DATE__, the code can know for itself when it was compiled, and build in an expiration date based on that value.
// Two-week expiration #define EXPIREAFTERDAYS 14 #if EXPIREAFTERDAYS // Idea from Brian Cooke. NSString* nowString = [NSString stringWithUTF8String:__DATE__]; NSCalendarDate* nowDate = [NSCalendarDate dateWithNaturalLanguageString:nowString]; NSCalendarDate* expireDate = [nowDate addTimeInterval:(60*60*24* EXPIREAFTERDAYS)]; if ([expireDate earlierDate:[NSDate date]] == expireDate) { // Run an alert or whatever // Quit! [NSApp terminate:self]; } #endif
By putting code like this in all of your projects, you have at your disposal a simple, error-proof way of releasing a build that will stop working N days from now. When you’re ready to release a non-suicidal version, just change the EXPIREAFTERDAYS preprocessor macro to 0. Nifty, eh?
PS: Thanks to Jon Trainer for inspiring this entry by thinking the idea was cool when I told him about it.
Update: Rosyna points out that dateWithNaturalLanguageString might be a dangerous method to use here, because it will implicitly use a different locale to parse the string, depending on the user’s language settings. Furthermore, the documentation for that method strongly discourages its use. Ironically though, the reason its use is discouraged, because it’s got a limited vocabulary and favors English, is probably just about right for parsing the date from GCC.
The GCC documentation describes the contents of __DATE__ as “eleven characters and looks like ‘Feb 12 1996’.” I’m not really sure how many locales this will or will not work correctly for, so I’m thinking the safe bet is to use a more explicit string -> date conversion format. I’m busy with something else right now but if anybody has a good capsule solution for this, please share it in the comments! Rosyna suggests offline that perhaps some POSIX date string formatter will do the trick.
July 13th, 2007 at 10:32 am
The code is quite cool, but the __DATE__ constant is entered in plain text into the executable. It would be very easy for a determined user to change the date.
To be fair, this solution was probably not designed to keep these users out. Even if the date was unchangeable, someone would find a way to redirect the terminate method to a nil object. But it should be noted that if you do not want anyone at all to be able to use the application after a certain date this solution is not the best.
July 13th, 2007 at 10:34 am
ACoolie: indeed – the example is meant to be as simple as possible, but the issue you raise should be noted by anybody who is trying to make “determinedly suicidal” code :)
July 13th, 2007 at 10:38 am
Nice!
I could’ve used this, I’ve been sending out expiring betas for the last 5 months or so but finally releasing 1.0.
Thanks.
July 13th, 2007 at 10:46 am
It’s redshirt code — code that you write to get something going, but you know is going to die very soon. So named after the ensigns who would transport down to the planet with Kirk and [Spock|McCoy|Scotty] and then die once the main characters walked off screen and the (red shirted) ensigns were not necessary to the plot.
July 13th, 2007 at 11:01 am
Red shirt… red sweater… ;)
Neat.
July 13th, 2007 at 11:31 am
I’ve used this trick in beta versions of Mental Case. One thing I did was create a separate ‘Beta Release’ target, and set that EXPIREAFTERDAYS to 0 in the Release build, and 14 in the Beta Release build, just using macros
#ifdef BETA
#define EXPIREAFTERDAYS 14
#else
#define EXPIREAFTERDAYS 0
#endif
That way you don’t even have to change the EXPIREAFTERDAYS macro each time. Simply change the target. (Make sure you do a clean build before any beta release, or the __DATE__ will not be updated in the source.)
Drew
July 13th, 2007 at 11:39 am
The big problem I’ve seen is forgetting to remove your suicide code!
July 13th, 2007 at 1:47 pm
Nice idea, thanks for sharing!
July 13th, 2007 at 4:30 pm
I likey. But s/[NSApplication sharedApplication]/NSApp/ is prettier.
July 13th, 2007 at 4:58 pm
To avoid leaving the code in, print out a release checklist and step through it methodically. Once your project is sizable and you have support files to build and add, temp files to remove, server data (e.g. RSS feeds) to upload, scripts to run, etc. then you don’t really want to have to remember crucial stuff. Especially on release day (aka the busiest day of your year).
I find it helps to put all version-specific stuff like this in one header, so that as I go through the release checklist I can make several changes in one place.
One other thing: you may want to check the user’s clock. It’s far from uncommon for a user to report problems with beta time-outs or license validation due to their clock having been set back (either accidentally or otherwise). Get the build date and compare against the current date as returned from the OS. If there’s more than a few days difference (remember your friends in future time zones!) then show a notice to the effect “check your clock” and exit.
Note: if you do this, don’t be tempted to bypass __DATE__ by checking date stamps on arbitrary system or preference files. (They are not at all reliable.)
Note 2: be careful with __DATE__. It represents the time of compilation of that source file only. Thus, if you put your check in some source file, compile it, and never recompile it before release, then __DATE__ will not reflect the overall build date of your project.
July 13th, 2007 at 5:00 pm
rentzsch: Agreed and amended.
July 13th, 2007 at 8:49 pm
Nice!
I’ll add my 1 cent to Dds Dunham & Wareing — in your startup, #if EXPIREAFTERDAYS print “this application will expire soon”, to the console, and the splash screen.
You’ll remember pretty quick about the suicide code then. Or someone else will, before the 14 days are up. Transparency is next to godliness.
July 14th, 2007 at 2:01 pm
I thought that was a pretty cool idea since you mentioned in the other night. Thanks for posting the code!
July 15th, 2007 at 1:47 pm
dateWithNaturalLanguageString: is completely dependent on the user’s locale and settings. This means it may “mysteriously” fail in “really bad ways” depending on the user’s settings.
From the documentation:
“This method supports only a limited set of colloquial phrases, primarily in English. It may give unexpected results, and its use is strongly discouraged.”
July 16th, 2007 at 12:13 pm
__DATE__ is actually defined by the ANSI C standard and is not just a gcc extension. I don’t think there is any locale support.
The date of translation of the preprocessing translation unit: a character
string literal of the form”Mmm dd yyyy”, where the names of the
months are the same as those generated by the asctime function, and the
first character of dd is a space character if the value is less than 10. If the
date of translation is not available, an implementation-defined valid date
shall be supplied.
July 16th, 2007 at 12:18 pm
E. Wing: Good to know! That should mean the technique is highly portable, even to other platforms. Since the format is so predictable maybe the best thing is just to parse it myself, but of course I bet some standard Posix call will do it given the right template.
July 16th, 2007 at 3:42 pm
I think the ‘safe’ Cocoa way to do it is:
nowDate = [NSCalendarDate dateWithString: nowString calendarFormat: @”%b %e %Y” locale: nil];
I still think that dateWithString:… is somewhat underspecified, it’s certainly not as clear as the POSIX strptime(3). But it seems to Do What I Mean in all the reasonable cases, so I guess that’s good enough for me.
Just watch out for that Year Ten-Thousand bug! :-)
July 17th, 2007 at 7:18 pm
Definitely bookmarking this idea
July 18th, 2007 at 4:52 am
I’ve released a class called ESSTimeTrialClass for this purpose. You can get it over at http://codebeach.org/code/show/36
Long time ago, maybe brian even took this as inspiration :)
Take care!