Fix bug 603416 - startup crash [@ cal::UTC()]. r=mschroeder
authorPhilipp Kewisch <mozilla@kewis.ch>
Mon, 11 Jul 2011 01:00:00 +0300
changeset 8342 8b8b410f68f7216d45bc3d394584b8b0a4a96f28
parent 8341 e191867446976d0448db06b1068f4d72e9ad2274
child 8343 6bd9fbfeb8c577eeab5ffb2a9182ac1b41f869f2
push idunknown
push userunknown
push dateunknown
reviewersmschroeder
bugs603416
Fix bug 603416 - startup crash [@ cal::UTC()]. r=mschroeder
calendar/base/src/calDateTime.cpp
calendar/base/src/calDateTime.h
calendar/base/src/calICSService.cpp
calendar/base/src/calTimezoneService.js
calendar/base/src/calUtils.h
calendar/test/unit/test_datetime.js
--- a/calendar/base/src/calDateTime.cpp
+++ b/calendar/base/src/calDateTime.cpp
@@ -1,9 +1,9 @@
-/* ***** BEGIN LICENSE BLOCK *****
+/* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
  * Software distributed under the License is distributed on an "AS IS" basis,
@@ -133,17 +133,17 @@ calDateTime::Reset()
     mMonth = 0;
     mDay = 1;
     mHour = 0;
     mMinute = 0;
     mSecond = 0;
     mWeekday = 4;
     mYearday = 1;
     mIsDate = PR_FALSE;
-    mTimezone = cal::UTC();
+    mTimezone = nsnull;
     mNativeTime = 0;
     mIsValid = PR_TRUE;
     return NS_OK;
 }
 
 CAL_VALUETYPE_ATTR(calDateTime, PRInt16, Year)
 CAL_VALUETYPE_ATTR(calDateTime, PRInt16, Month)
 CAL_VALUETYPE_ATTR(calDateTime, PRInt16, Day)
@@ -151,19 +151,30 @@ CAL_VALUETYPE_ATTR(calDateTime, PRInt16,
 CAL_VALUETYPE_ATTR(calDateTime, PRInt16, Minute)
 CAL_VALUETYPE_ATTR(calDateTime, PRInt16, Second)
 CAL_VALUETYPE_ATTR(calDateTime, PRBool, IsDate)
 CAL_VALUETYPE_ATTR_GETTER(calDateTime, PRBool, IsValid)
 CAL_VALUETYPE_ATTR_GETTER(calDateTime, PRTime, NativeTime)
 CAL_VALUETYPE_ATTR_GETTER(calDateTime, PRInt16, Weekday)
 CAL_VALUETYPE_ATTR_GETTER(calDateTime, PRInt16, Yearday)
 
-CAL_ISUPPORTS_ATTR_GETTER(calDateTime, calITimezone, Timezone)
+
+NS_IMETHODIMP
+calDateTime::GetTimezone(calITimezone **aResult)
+{
+    NS_ENSURE_ARG_POINTER(aResult);
+    ensureTimezone();
 
-NS_IMETHODIMP calDateTime::SetTimezone(calITimezone *aValue) {
+    NS_IF_ADDREF(*aResult = mTimezone);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+calDateTime::SetTimezone(calITimezone *aValue)
+{
     NS_ENSURE_FALSE(mImmutable, NS_ERROR_OBJECT_IS_IMMUTABLE);
     NS_ENSURE_ARG_POINTER(aValue);
     mTimezone = aValue;
     CAL_ATTR_SET_POST;
     return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -186,20 +197,21 @@ calDateTime::SetNativeTime(PRTime aNativ
     return NS_OK;
 }
 
 NS_IMETHODIMP
 calDateTime::AddDuration(calIDuration *aDuration)
 {
     NS_ENSURE_FALSE(mImmutable, NS_ERROR_OBJECT_IS_IMMUTABLE);
     NS_ENSURE_ARG_POINTER(aDuration);
+    ensureTimezone();
 
     icaldurationtype idt;
     aDuration->ToIcalDuration(&idt);
-    
+
     icaltimetype itt;
     ToIcalTime(&itt);
 
     icaltimetype const newitt = icaltime_add(itt, idt);
     FromIcalTime(&newitt, mTimezone);
 
     return NS_OK;
 }
@@ -223,18 +235,21 @@ calDateTime::SubtractDate(calIDateTime *
     NS_ADDREF(*aDuration = dur);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 calDateTime::ToString(nsACString & aResult)
 {
     nsCAutoString tzid;
+    char buffer[256];
+
+    ensureTimezone();
     mTimezone->GetTzid(tzid);
-    char buffer[256];
+
     PRUint32 const length = PR_snprintf(
         buffer, sizeof(buffer), "%04hd/%02hd/%02hd %02hd:%02hd:%02hd %s isDate=%01hd",
         mYear, mMonth + 1, mDay, mHour, mMinute, mSecond,
         tzid.get(), static_cast<PRInt16>(mIsDate));
     if (length != static_cast<PRUint32>(-1))
         aResult.Assign(buffer, length);
     return NS_OK;
 }
@@ -287,16 +302,17 @@ calDateTime::GetInTimezone(calITimezone 
         return NS_OK;
     }
 }
 
 NS_IMETHODIMP
 calDateTime::GetStartOfWeek(calIDateTime ** aResult)
 {
     NS_ENSURE_ARG_POINTER(aResult);
+    ensureTimezone();
 
     icaltimetype icalt;
     ToIcalTime(&icalt);
     int day_of_week = icaltime_day_of_week(icalt);
     if (day_of_week > 1)
         icaltime_adjust(&icalt, - (day_of_week - 1), 0, 0, 0);
     icalt.is_date = 1;
 
@@ -305,16 +321,17 @@ calDateTime::GetStartOfWeek(calIDateTime
     NS_ADDREF(*aResult = cdt);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 calDateTime::GetEndOfWeek(calIDateTime ** aResult)
 {
     NS_ENSURE_ARG_POINTER(aResult);
+    ensureTimezone();
 
     icaltimetype icalt;
     ToIcalTime(&icalt);
     int day_of_week = icaltime_day_of_week(icalt);
     if (day_of_week < 7)
         icaltime_adjust(&icalt, 7 - day_of_week, 0, 0, 0);
     icalt.is_date = 1;
 
@@ -323,48 +340,51 @@ calDateTime::GetEndOfWeek(calIDateTime *
     NS_ADDREF(*aResult = cdt);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 calDateTime::GetStartOfMonth(calIDateTime ** aResult)
 {
     NS_ENSURE_ARG_POINTER(aResult);
+    ensureTimezone();
 
     icaltimetype icalt;
     ToIcalTime(&icalt);
     icalt.day = 1;
     icalt.is_date = 1;
 
     calDateTime * const cdt = new calDateTime(&icalt, mTimezone);
     CAL_ENSURE_MEMORY(cdt);
     NS_ADDREF(*aResult = cdt);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 calDateTime::GetEndOfMonth(calIDateTime ** aResult)
 {
     NS_ENSURE_ARG_POINTER(aResult);
+    ensureTimezone();
 
     icaltimetype icalt;
     ToIcalTime(&icalt);
     icalt.day = icaltime_days_in_month(icalt.month, icalt.year);
     icalt.is_date = 1;
 
     calDateTime * const cdt = new calDateTime(&icalt, mTimezone);
     CAL_ENSURE_MEMORY(cdt);
     NS_ADDREF(*aResult = cdt);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 calDateTime::GetStartOfYear(calIDateTime ** aResult)
 {
     NS_ENSURE_ARG_POINTER(aResult);
+    ensureTimezone();
 
     icaltimetype icalt;
     ToIcalTime(&icalt);
     icalt.month = 1;
     icalt.day = 1;
     icalt.is_date = 1;
 
     calDateTime * const cdt = new calDateTime(&icalt, mTimezone);
@@ -372,16 +392,17 @@ calDateTime::GetStartOfYear(calIDateTime
     NS_ADDREF(*aResult = cdt);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 calDateTime::GetEndOfYear(calIDateTime ** aResult)
 {
     NS_ENSURE_ARG_POINTER(aResult);
+    ensureTimezone();
 
     icaltimetype icalt;
     ToIcalTime(&icalt);
     icalt.month = 12;
     icalt.day = 31;
     icalt.is_date = 1;
 
     calDateTime * const cdt = new calDateTime(&icalt, mTimezone);
@@ -419,23 +440,35 @@ calDateTime::SetIcalString(nsACString co
 /**
  ** utility/protected methods
  **/
 
 // internal Normalize():
 void calDateTime::Normalize()
 {
     icaltimetype icalt;
+
+    ensureTimezone();
     ToIcalTime(&icalt);
     FromIcalTime(&icalt, mTimezone);
 }
 
+void
+calDateTime::ensureTimezone()
+{
+    if (mTimezone == nsnull) {
+        mTimezone = cal::UTC();
+    }
+}
+
 NS_IMETHODIMP_(void)
 calDateTime::ToIcalTime(struct icaltimetype * icalt)
 {
+    ensureTimezone();
+
     icalt->year = mYear;
     icalt->month = mMonth + 1;
     icalt->day = mDay;
     icalt->hour = mHour;
     icalt->minute = mMinute;
     icalt->second = mSecond;
 
     icalt->is_date = mIsDate ? 1 : 0;
@@ -550,17 +583,17 @@ void calDateTime::PRTimeToIcaltime(PRTim
 {
     PRExplodedTime et;
     PR_ExplodeTime(time, PR_GMTParameters, &et);
 
     icalt->year   = et.tm_year;
     icalt->month  = et.tm_month + 1;
     icalt->day    = et.tm_mday;
 
-    if (isdate) { 
+    if (isdate) {
         icalt->hour    = 0;
         icalt->minute  = 0;
         icalt->second  = 0;
         icalt->is_date = 1;
     } else {
         icalt->hour   = et.tm_hour;
         icalt->minute = et.tm_min;
         icalt->second = et.tm_sec;
@@ -642,23 +675,24 @@ calDateTime::GetProperty(nsIXPConnectWra
     NS_ENSURE_ARG_POINTER(_retval);
 
     if (JSID_IS_STRING(id)) {
         size_t length;
         JSString *idString = JSID_TO_STRING(id);
         const jschar *str = JS_GetStringCharsAndLength(cx, idString, &length);
 
         nsDependentString const val(reinterpret_cast<PRUnichar const*>(str), length);
- 
+
         if (val.EqualsLiteral("jsDate")) {
             PRTime tmp, thousand;
             jsdouble msec;
             LL_I2L(thousand, 1000);
             LL_DIV(tmp, mNativeTime, thousand);
             LL_L2D(msec, tmp);
+            ensureTimezone();
 
             JSObject *obj;
             PRBool b;
             if (NS_SUCCEEDED(mTimezone->GetIsFloating(&b)) && b) {
                 obj = js_NewDateObject(cx, mYear, mMonth, mDay, mHour, mMinute, mSecond);
             } else {
                 obj = js_NewDateObjectMsec(cx, msec);
             }
--- a/calendar/base/src/calDateTime.h
+++ b/calendar/base/src/calDateTime.h
@@ -73,15 +73,16 @@ protected:
     PRInt16 mWeekday;
     PRInt16 mYearday;
 
     PRTime mNativeTime;
     nsCOMPtr<calITimezone> mTimezone;
 
     void Normalize();
     void FromIcalTime(icaltimetype const* icalt, calITimezone *tz);
+    void ensureTimezone();
 
     static PRTime IcaltimeToPRTime(icaltimetype const* icalt, icaltimezone const* tz);
     static void PRTimeToIcaltime(PRTime time, PRBool isdate,
                                  icaltimezone const* tz, icaltimetype *icalt);
 };
 
 #endif // INCLUDED_CALDATETIME_H
--- a/calendar/base/src/calICSService.cpp
+++ b/calendar/base/src/calICSService.cpp
@@ -40,16 +40,17 @@
 #include "nsStringStream.h"
 #include "nsComponentManagerUtils.h"
 
 #include "calICSService.h"
 #include "calTimezone.h"
 #include "calDateTime.h"
 #include "calDuration.h"
 #include "calIErrors.h"
+#include "calUtils.h"
 
 extern "C" {
 #include "ical.h"
 }
 
 calIcalProperty::~calIcalProperty()
 {
     if (!mParent) {
@@ -433,19 +434,19 @@ nsresult calIcalProperty::getDatetime_(c
                     NS_ASSERTION(tz, tzid_);
                 }
             }
             if (!tz) {
                 // look up tz in tz service.
                 // this hides errors from incorrect ics files, which could state
                 // a TZID that is not present in the ics file.
                 // The other way round, it makes this product more error tolerant.
-                cal::getTimezoneService()->GetTimezone(tzid, getter_AddRefs(tz));
+                nsresult rv = cal::getTimezoneService()->GetTimezone(tzid, getter_AddRefs(tz));
 
-                if (!tz) {
+                if (NS_FAILED(rv) || !tz) {
                     icaltimezone const* zone = itt.zone;
                     if (!zone && comp) {
                         // look up parent VCALENDAR for VTIMEZONE:
                         zone = icalcomponent_get_timezone(comp->mComponent, tzid_);
                         NS_ASSERTION(zone, tzid_);
                     }
                     if (zone) {
                         // We need to decouple this (inner) VTIMEZONE from the parent VCALENDAR to avoid
--- a/calendar/base/src/calTimezoneService.js
+++ b/calendar/base/src/calTimezoneService.js
@@ -101,36 +101,26 @@ calStringEnumerator.prototype = {
         }
         return this.mStringArray[this.mIndex++];
     }
 };
 
 function calTimezoneService() {
     this.wrappedJSObject = this;
 
-    // floating
-    this.floating = new calIntrinsicTimezone("floating", null, "", "");
-    this.floating.isFloating = true;
-    // UTC
-    this.UTC = new calIntrinsicTimezone("UTC", null, "", "");
-    this.UTC.isUTC = true;
-
     this.mTimezoneCache = {};
     this.mBlacklist = {};
-    this.mTimezoneCache.floating = this.floating;
-    this.mTimezoneCache.UTC = this.UTC;
-    this.mTimezoneCache.utc = this.UTC;
 }
 calTimezoneService.prototype = {
     mTimezoneCache: null,
     mBlacklist: null,
     mDefaultTimezone: null,
     mHasSetupObservers: false,
-    floating: null,
-    UTC: null,
+    mFloating: null,
+    mUTC: null,
     mDb: null,
 
 
     // nsIClassInfo:
     getInterfaces: function calTimezoneService_getInterfaces(count) {
         const ifaces = [Components.interfaces.calITimezoneService,
                         Components.interfaces.calITimezoneProvider,
                         Components.interfaces.calIStartupService,
@@ -173,16 +163,37 @@ calTimezoneService.prototype = {
             this.mDb = null;
         } catch (e) {
             cal.ERROR("Error closing timezone database: " + e);
         }
 
         aCompleteListener.onResult(null, Components.results.NS_OK);
     },
 
+    get UTC() {
+        if (!this.mUTC) {
+            this.mUTC = new calIntrinsicTimezone("UTC", null, "", "");
+            this.mUTC.isUTC = true;
+            this.mTimezoneCache.UTC = this.mUTC;
+            this.mTimezoneCache.utc = this.mUTC;
+        }
+
+        return this.mUTC;
+    },
+
+    get floating() {
+        if (!this.mFloating) {
+            this.mFloating = new calIntrinsicTimezone("floating", null, "", "");
+            this.mFloating.isFloating = true;
+            this.mTimezoneCache.floating = this.mFloating;
+        }
+
+        return this.mFloating;
+    },
+
     ensureInitialized: function(aCompleteListener) {
         if (!this.mSelectByTzid) {
             this.initialize(aCompleteListener);
         }
     },
 
     _initDB: function _initDB(sqlTzFile) {
         try {
@@ -265,16 +276,20 @@ calTimezoneService.prototype = {
             g_stringBundle = cal.calGetStringBundle(bundleURL);
         } else {
             // Otherwise, we have to give up. Show an error and fail hard!
             let msg = cal.calGetString("calendar", "missingCalendarTimezonesError");
             cal.ERROR(msg);
             showError(msg);
         }
 
+        // Make sure UTC and floating are cached by calling their getters
+        this.UTC;
+        this.floating;
+
         if (aCompleteListener) {
             aCompleteListener.onResult(null, Components.results.NS_OK);
         }
     },
 
     // calITimezoneProvider:
     getTimezone: function calTimezoneService_getTimezone(tzid) {
         this.ensureInitialized();
--- a/calendar/base/src/calUtils.h
+++ b/calendar/base/src/calUtils.h
@@ -81,17 +81,24 @@ inline nsCOMPtr<nsIConsoleService> getCo
 inline nsCOMPtr<calIICSService> getICSService() {
     return do_GetService(CAL_ICSSERVICE_CONTRACTID);
 }
 
 /**
  * Gets the global timezone service.
  */
 inline nsCOMPtr<calITimezoneService> getTimezoneService() {
-    return do_GetService(CAL_TIMEZONESERVICE_CONTRACTID);
+    nsresult rv;
+    nsCOMPtr<calITimezoneService> tzs;
+
+    tzs = do_GetService(CAL_TIMEZONESERVICE_CONTRACTID, &rv);
+    if (NS_FAILED(rv)) {
+        NS_RUNTIMEABORT("Could not load timezone service, brace yourself and prepare for crash");
+    }
+    return tzs;
 }
 
 /**
  * Logs an error.
  */
 nsresult logError(PRUnichar const* msg);
 inline nsresult logError(char const* msg) {
     return logError(NS_ConvertASCIItoUTF16(msg).get());
@@ -123,27 +130,39 @@ inline nsresult log(nsACString const& ms
 }
 
 // some timezone helpers
 
 /**
  * Gets the "UTC" timezone.
  */
 inline nsCOMPtr<calITimezone> UTC() {
+    nsresult rv;
     nsCOMPtr<calITimezone> tz;
-    getTimezoneService()->GetUTC(getter_AddRefs(tz));
+
+    rv = getTimezoneService()->GetUTC(getter_AddRefs(tz));
+    if (NS_FAILED(rv)) {
+        NS_RUNTIMEABORT("Could not load UTC timezone, brace yourself and prepare for crash");
+    }
+
     return tz;
 }
 
 /**
  * Gets the "floating" timezone
  */
 inline nsCOMPtr<calITimezone> floating() {
+    nsresult rv;
     nsCOMPtr<calITimezone> tz;
-    getTimezoneService()->GetFloating(getter_AddRefs(tz));
+
+    rv = getTimezoneService()->GetFloating(getter_AddRefs(tz));
+    if (NS_FAILED(rv)) {
+        NS_RUNTIMEABORT("Could not load floating timezone, brace yourself and prepare for crash");
+    }
+
     return tz;
 }
 
 /**
  * Returns the libical VTIMEZONE component, null if floating.
  * 
  * @attention
  * Every timezone provider needs to use calICSService for
--- a/calendar/test/unit/test_datetime.js
+++ b/calendar/test/unit/test_datetime.js
@@ -104,9 +104,15 @@ function run_test() {
 
     do_check_eq(b.timezone.displayName, "America/New York");
     do_check_eq(b.timezone.latitude, "+0404251");
     do_check_eq(b.timezone.longitude, "-0740023");
 
     // check aliases
     do_check_eq(getMozTimezone("/mozilla.org/xyz/Pacific/Yap").tzid, "Pacific/Truk");
     do_check_eq(getMozTimezone("Pacific/Yap").tzid, "Pacific/Truk");    
+
+    // A newly created date should be in UTC, as should its clone
+    let utc = cal.createDateTime();
+    do_check_eq(utc.timezone.tzid, "UTC");
+    do_check_eq(utc.clone().timezone.tzid, "UTC");
+    do_check_eq(utc.timezoneOffset, 0);
 }