Addressed code review comments

This commit is contained in:
Denis Levin
2019-06-14 18:25:17 -07:00
parent 0b108fab0f
commit da2422cb17
14 changed files with 109 additions and 141 deletions

View File

@@ -3,22 +3,21 @@
"qhelp.dtd">
<qhelp>
<overview>
<include src="LeapYear.qhelp" />
<p>When creating or creating a <code>System.DateTime</code> object by setting the year, month, day in the contructor (other parameters optional) by performing an arithmetic operation on a different <code>DateTime</code> object, there is a risk that the date you are setting is an invalid one.</p>
<p>On a leap year, such code may throw an <code>ArgumentOutOfRangeException</code> with a message of <code>"Year, Month, and Day parameters describe an un-representable DateTime."</code></p>
<p>When creating a <code>System.DateTime</code> object by setting the year, month, and day in the constructor by performing an arithmetic operation on a different <code>DateTime</code> object, there is a risk that the date you are setting is invalid.</p>
<p>On a leap year, such code may throw an <code>ArgumentOutOfRangeException</code> with a message of <code>"Year, Month, and Day parameters describe an un-representable DateTime."</code></p>
</overview>
<recommendation>
<p>Creating a <code>System.DateTime</code> object based on a different <code>System.DateTime</code> object, use the appropriate methods to manipulate the date instead of arithmetic.</p>
<p>Creating a <code>System.DateTime</code> object based on a different <code>System.DateTime</code> object, use the appropriate methods to manipulate the date instead of arithmetic.</p>
</recommendation>
<example>
<p>In this example, we are adding/decreasing 1 year to the current date when creating a new <code>System.DateTime</code> object. This may work most of the time, but on any given February 29th, the resulting value will be invalid.</p>
<sample src="UnsafeYearConstructionBad.cs" />
<p>To fix this bug, we add/substract years to the current date by calling <code>AddYears</code> method on it.</p>
<sample src="UnsafeYearConstructionGood.cs" />
<p>In this example, we are incrementing/decrementing the current date by one year when creating a new <code>System.DateTime</code> object. This may work most of the time, but on any given February 29th, the resulting value will be invalid.</p>
<sample src="UnsafeYearConstructionBad.cs" />
<p>To fix this bug, we add/substract years to the current date by calling <code>AddYears</code> method on it.</p>
<sample src="UnsafeYearConstructionGood.cs" />
</example>
<references>
<li>
<a href="https://docs.microsoft.com/en-us/dotnet/api/system.datetime?view=netframework-4.8">System.DateTime Struct</a>.
</li>
</references>
</qhelp>

View File

@@ -1,35 +1,39 @@
/**
* @name Unsafe year argument for DateTime constructor
* @description Constructing a DateTime object by setting the year argument by manipulating the year of a different DateTime object
* @kind problem
* @name Unsafe year argument for 'DateTime' constructor
* @description Constructing a 'DateTime' struct by setting the year argument to an increment or decrement of the year of a different 'DateTime' struct
* @kind path-problem
* @problem.severity error
* @id cs/leap-year/unsafe-year-contruction
* @id cs/unsafe-year-construction
* @precision high
* @tags security
* leap-year
* date-time
* reliability
*/
import csharp
import DataFlow::PathGraph
import semmle.code.csharp.dataflow.TaintTracking
class UnsafeYearCreationFromArithmeticConfiguration extends TaintTracking::Configuration {
UnsafeYearCreationFromArithmeticConfiguration() { this = "UnsafeYearCreationFromArithmeticConfiguration" }
class UnsafeYearCreationFromArithmeticConfiguration extends TaintTracking::Configuration {
UnsafeYearCreationFromArithmeticConfiguration() {
this = "UnsafeYearCreationFromArithmeticConfiguration"
}
override predicate isSource(DataFlow::Node source) {
exists( ArithmeticOperation ao, PropertyAccess pa |
ao = source.asExpr() |
pa = ao.getAChild*()
and pa.getProperty().getQualifiedName().matches("%DateTime.Year")
override predicate isSource(DataFlow::Node source) {
exists(ArithmeticOperation ao, PropertyAccess pa | ao = source.asExpr() |
pa = ao.getAChild*() and
pa.getProperty().getQualifiedName().matches("System.DateTime.Year")
)
}
override predicate isSink(DataFlow::Node sink) {
exists( ObjectCreation oc |
sink.asExpr() = oc.getArgumentForName("year")
and oc.getObjectType().getABaseType*().hasQualifiedName("System.DateTime"))
exists(ObjectCreation oc |
sink.asExpr() = oc.getArgumentForName("year") and
oc.getObjectType().getABaseType*().hasQualifiedName("System.DateTime")
)
}
}
from UnsafeYearCreationFromArithmeticConfiguration config, Expr sink, Expr source
where config.hasFlow(DataFlow::exprNode(source), DataFlow::exprNode(sink))
select sink, "This $@ based on a System.DateTime.Year property is used in a construction of a new System.DateTime object, flowing to the 'year' argument.", source, "arithmetic operation"
from UnsafeYearCreationFromArithmeticConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink, source, sink, "This $@ based on a 'System.DateTime.Year' property is used in a construction of a new 'System.DateTime' object, flowing to the 'year' argument.", source, "arithmetic operation"

View File

@@ -1,5 +1,4 @@
using System;
public class UnsafeYearConstructionBad
{
public UnsafeYearConstructionBad()
@@ -7,7 +6,6 @@ public class UnsafeYearConstructionBad
DateTime Start;
DateTime End;
var now = DateTime.UtcNow;
// the base-date +/1 n years may not be a valid date.
Start = new DateTime(now.Year - 1, now.Month, now.Day, 0, 0, 0, DateTimeKind.Utc);
End = new DateTime(now.Year + 1, now.Month, now.Day, 0, 0, 1, DateTimeKind.Utc);

View File

@@ -1,5 +1,4 @@
using System;
public class UnsafeYearConstructionBad
{
public UnsafeYearConstructionBad()
@@ -7,7 +6,6 @@ public class UnsafeYearConstructionBad
DateTime Start;
DateTime End;
var now = DateTime.UtcNow;
Start = now.AddYears(-1).Date;
End = now.AddYears(-1).Date.AddSeconds(1);
}

View File

@@ -1,39 +1,21 @@
using System;
using System.Globalization;
public class Example
{
public static void Main()
{
var cal = new JapaneseCalendar();
// constructing date using current era
var dat = cal.ToDateTime(2, 1, 2, 0, 0, 0, 0);
Console.WriteLine($"{dat:s}");
// constructing date using current era
dat = new DateTime(2, 1, 2, cal);
Console.WriteLine($"{dat:s}");
}
}
// Output with the Heisei era current:
// 1990-01-02T00:00:00
// 1990-01-02T00:00:00
// Output with the new era current:
// 2020-01-02T00:00:00
// 2020-01-02T00:00:00

View File

@@ -24,10 +24,13 @@
<a href="https://devblogs.microsoft.com/dotnet/handling-a-new-era-in-the-japanese-calendar-in-net/">Handling a new era in the Japanese calendar in .NET</a>.
</li>
<li>
<a href="https://blogs.msdn.microsoft.com/shawnste/2018/04/12/the-japanese-calendars-y2k-moment/">The Japanese Calendar<EFBFBD>s Y2K Moment</a>.
<a href="https://blogs.msdn.microsoft.com/shawnste/2018/04/12/the-japanese-calendars-y2k-moment/">The Japanese Calendar's Y2K Moment</a>.
</li>
<li>
<a href="https://docs.microsoft.com/en-us/windows/desktop/Intl/era-handling-for-the-japanese-calendar/">Era Handling for the Japanese Calendar</a>.
</li>
<li>
<a href="https://simple.wikipedia.org/wiki/List_of_Japanese_eras">List of Japanese Eras (Wikipedia)</a>
</li>
</references>
</qhelp>

View File

@@ -1,60 +1,72 @@
/**
* @name MishandlingJapaneseEraStartDate
* @description Japanese Era should be handled with built-in JapaneseCalendar class. Aviod hard-coding Japanese era start dates and names.
* @name Mishandling the Japanese era start date
* @description Japanese era should be handled with the built-in 'JapaneseCalendar' class. Avoid hard-coding Japanese era start dates and names.
* @id cs/mishandling-japanese-era
* @kind problem
* @problem.severity warning
* @precision medium
* @tags reliability
* japanese-era
* date-time
*/
import csharp
/**
* Holds if `year`, `month`, and `day` specify the start of a new era
* (see https://simple.wikipedia.org/wiki/List_of_Japanese_eras).
*/
predicate isEraStart(int year, int month, int day) {
year = 1989 and month = 1 and day = 8
or
year = 2019 and month = 5 and day = 1
}
predicate isExactEraStartDateCreation(ObjectCreation cr) {
(cr.getType().hasQualifiedName("System.DateTime") or cr.getType().hasQualifiedName("System.DateTimeOffset"))
and
cr.getArgument(0).getValue() = "1989" and
cr.getArgument(1).getValue() = "1" and
cr.getArgument(2).getValue() = "8"
(
cr.getType().hasQualifiedName("System.DateTime") or
cr.getType().hasQualifiedName("System.DateTimeOffset")
) and
isEraStart(cr.getArgument(0).getValue().toInt(), cr.getArgument(1).getValue().toInt(), cr.getArgument(2).getValue().toInt())
}
predicate isDateFromJapaneseCalendarToDateTime(MethodCall mc) {
(mc.getQualifier().getType().hasQualifiedName("System.Globalization.JapaneseCalendar") or mc.getQualifier().getType().hasQualifiedName("System.Globalization.JapaneseLunisolarCalendar"))
and
mc.getTarget().hasName("ToDateTime") and
mc.getArgument(0).getValue() != "" and
(mc.getNumberOfArguments() = 7 or // implicitly current era
mc.getNumberOfArguments() = 8 and
mc.getArgument(7).getValue() = "0") // explicitly current era
(
mc.getQualifier().getType().hasQualifiedName("System.Globalization.JapaneseCalendar") or
mc.getQualifier().getType().hasQualifiedName("System.Globalization.JapaneseLunisolarCalendar")
) and
mc.getTarget().hasName("ToDateTime") and
mc.getArgument(0).hasValue() and
(
mc.getNumberOfArguments() = 7 // implicitly current era
or
mc.getNumberOfArguments() = 8 and
mc.getArgument(7).getValue() = "0"
) // explicitly current era
}
predicate isDateFromJapaneseCalendarCreation(ObjectCreation cr) {
(cr.getType().hasQualifiedName("System.DateTime") or cr.getType().hasQualifiedName("System.DateTimeOffset")) and
(cr.getArgumentForName("calendar").getType().hasQualifiedName("System.Globalization.JapaneseCalendar") or cr.getArgumentForName("calendar").getType().hasQualifiedName("System.Globalization.JapaneseLunisolarCalendar"))
and
cr.getArgumentForName("year").getValue() != ""
}
predicate inEraArrayCreation(ArrayInitializer ai) {
ai.getElement(0).getValue() = "1867" and
ai.getElement(1).getValue() = "1911" and
ai.getElement(2).getValue() = "1925" and
ai.getElement(3).getValue() = "1988"
}
predicate isEraCollectionCreation(CollectionInitializer cs) {
cs.getElementInitializer(0).getValue() = "1867" and
cs.getElementInitializer(1).getValue() = "1911" and
cs.getElementInitializer(2).getValue() = "1925" and
cs.getElementInitializer(3).getValue() = "1988"
(
cr.getType().hasQualifiedName("System.DateTime") or
cr.getType().hasQualifiedName("System.DateTimeOffset")
) and
(
cr
.getArgumentForName("calendar")
.getType()
.hasQualifiedName("System.Globalization.JapaneseCalendar") or
cr
.getArgumentForName("calendar")
.getType()
.hasQualifiedName("System.Globalization.JapaneseLunisolarCalendar")
) and
cr.getArgumentForName("year").hasValue()
}
from Expr expr, string message
where
isDateFromJapaneseCalendarToDateTime(expr) and message = "DateTime created from Japanese calendar with explicit or current era and hard-coded year" or
isDateFromJapaneseCalendarCreation(expr) and message = "DateTime constructed from Japanese calendar with explicit or current era and hard-coded year" or
isEraCollectionCreation(expr) and message = "Hard-coded collection with Japanese era years" or
inEraArrayCreation (expr) and message = "Hard-coded array with Japanese era years" or
isExactEraStartDateCreation(expr) and message = "Hard-coded the beginning of the Japanese Heisei era"
where
isDateFromJapaneseCalendarToDateTime(expr) and message = "'DateTime' created from Japanese calendar with explicit or current era and hard-coded year."
or
isDateFromJapaneseCalendarCreation(expr) and message = "'DateTime' constructed from Japanese calendar with explicit or current era and hard-coded year."
or
isExactEraStartDateCreation(expr) and message = "Hard-coded the beginning of the Japanese Heisei era."
select expr, message

View File

@@ -1,3 +0,0 @@
| Program.cs:13:39:13:50 | ... - ... | This $@ based on a System.DateTime.Year property is used in a construction of a new System.DateTime object, flowing to the 'year' argument. | Program.cs:13:39:13:50 | ... - ... | arithmetic operation |
| Program.cs:17:37:17:43 | access to local variable endYear | This $@ based on a System.DateTime.Year property is used in a construction of a new System.DateTime object, flowing to the 'year' argument. | Program.cs:15:27:15:38 | ... + ... | arithmetic operation |
| Program.cs:26:39:26:42 | access to parameter year | This $@ based on a System.DateTime.Year property is used in a construction of a new System.DateTime object, flowing to the 'year' argument. | Program.cs:33:18:33:29 | ... - ... | arithmetic operation |

View File

@@ -9,20 +9,20 @@ namespace LeapYear
public PipelineProperties()
{
var now = DateTime.UtcNow;
// BUG
// BAD
this.Start = new DateTime(now.Year - 1, now.Month, now.Day, 0, 0, 0, DateTimeKind.Utc);
var endYear = now.Year + 1;
// BUG
// BAD
this.End = new DateTime(endYear, now.Month, now.Day, 0, 0, 1, DateTimeKind.Utc);
// OK
// GOOD
this.Start = now.AddYears(-1).Date;
}
private void Test(int year, int month, int day)
{
// BUG (arithmetic operation from StartTest)
// BAD (arithmetic operation from StartTest)
this.Start = new DateTime(year, month, day);
}

View File

@@ -0,0 +1,9 @@
edges
| Program.cs:13:39:13:50 | ... - ... | Program.cs:13:39:13:50 | ... - ... |
| Program.cs:15:27:15:38 | ... + ... | Program.cs:17:37:17:43 | access to local variable endYear |
| Program.cs:23:31:23:34 | year | Program.cs:26:39:26:42 | access to parameter year |
| Program.cs:33:18:33:29 | ... - ... | Program.cs:23:31:23:34 | year |
#select
| Program.cs:13:39:13:50 | ... - ... | Program.cs:13:39:13:50 | ... - ... | Program.cs:13:39:13:50 | ... - ... | This $@ based on a 'System.DateTime.Year' property is used in a construction of a new 'System.DateTime' object, flowing to the 'year' argument. | Program.cs:13:39:13:50 | ... - ... | arithmetic operation |
| Program.cs:17:37:17:43 | access to local variable endYear | Program.cs:15:27:15:38 | ... + ... | Program.cs:17:37:17:43 | access to local variable endYear | This $@ based on a 'System.DateTime.Year' property is used in a construction of a new 'System.DateTime' object, flowing to the 'year' argument. | Program.cs:15:27:15:38 | ... + ... | arithmetic operation |
| Program.cs:26:39:26:42 | access to parameter year | Program.cs:33:18:33:29 | ... - ... | Program.cs:26:39:26:42 | access to parameter year | This $@ based on a 'System.DateTime.Year' property is used in a construction of a new 'System.DateTime' object, flowing to the 'year' argument. | Program.cs:33:18:33:29 | ... - ... | arithmetic operation |

View File

@@ -1,8 +1,6 @@
| Program.cs:12:31:12:54 | object creation of type DateTime | Hard-coded the beginning of the Japanese Heisei era |
| Program.cs:15:25:15:50 | { ..., ... } | Hard-coded array with Japanese era years |
| Program.cs:18:59:18:84 | { ..., ... } | Hard-coded array with Japanese era years |
| Program.cs:21:66:21:89 | object creation of type DateTime | Hard-coded the beginning of the Japanese Heisei era |
| Program.cs:28:42:28:98 | object creation of type DateTimeOffset | Hard-coded the beginning of the Japanese Heisei era |
| Program.cs:35:37:35:71 | call to method ToDateTime | DateTime created from Japanese calendar with explicit or current era and hard-coded year |
| Program.cs:39:27:39:64 | call to method ToDateTime | DateTime created from Japanese calendar with explicit or current era and hard-coded year |
| Program.cs:58:28:58:73 | object creation of type DateTime | DateTime constructed from Japanese calendar with explicit or current era and hard-coded year |
| Program.cs:12:31:12:54 | object creation of type DateTime | Hard-coded the beginning of the Japanese Heisei era. |
| Program.cs:15:66:15:89 | object creation of type DateTime | Hard-coded the beginning of the Japanese Heisei era. |
| Program.cs:22:42:22:98 | object creation of type DateTimeOffset | Hard-coded the beginning of the Japanese Heisei era. |
| Program.cs:29:37:29:71 | call to method ToDateTime | 'DateTime' created from Japanese calendar with explicit or current era and hard-coded year. |
| Program.cs:33:27:33:64 | call to method ToDateTime | 'DateTime' created from Japanese calendar with explicit or current era and hard-coded year. |
| Program.cs:49:28:49:73 | object creation of type DateTime | 'DateTime' constructed from Japanese calendar with explicit or current era and hard-coded year. |

View File

@@ -11,12 +11,6 @@ namespace JapaneseDates
// BAD: hard-coded era start date
var henseiStart = new DateTime(1989, 1, 8);
// BAD: hard-coded era start years, array initialization
int[] era = { 1867, 1911, 1925, 1988 };
// BAD: hard-coded era start years, collection initialization
List<int> listOfEra = new List<int> (new int[]{ 1867, 1911, 1925, 1988 });
// BAD: hard-coded era start dates, list
List<DateTime> listOfEraStart = new List<DateTime> { new DateTime(1989, 1, 8) };
@@ -51,9 +45,6 @@ namespace JapaneseDates
int realYear = 1988 + jk.GetYear(datejk);
Console.WriteLine("Which converts to year {0}", realYear);
int convertedYear = dateThisEra.Year + jk.GetYear(datejk);
RoundTripDate();
// BAD: creating DateTime using specified Japanese era date. This may yield a different date when era changes
DateTime val = new DateTime(32, 2, 1, new JapaneseCalendar());
Console.WriteLine("DateTime from constructor {0}", val);
@@ -62,27 +53,5 @@ namespace JapaneseDates
DateTime val1 = new DateTime(jk.GetYear(datejk), 2, 1, new JapaneseCalendar());
Console.WriteLine("DateTime from constructor {0}", val);
}
public static void RoundTripDate()
{
var ciJapanese = new CultureInfo("ja-JP")
{
DateTimeFormat = { Calendar = new JapaneseCalendar() }
};
// Original user input date string.
string formattedString = "平成 32年2月1日 0:00:00";
// Parse the string to a DateTime object.
DateTime dt = DateTime.Parse(formattedString, ciJapanese);
// Get the era name of the parsed DateTime object.
string roundTrippedString = dt.ToString("gg", ciJapanese);
// Check whether the date is formatted using a different era than the original formatted string.
if (roundTrippedString.IndexOf("平成") < 0)
{
Console.WriteLine("Detected failure in round tripping ");
}
}
}
}
}