Skip to content

Commit da2422c

Browse files
author
Denis Levin
committed
Addressed code review comments
1 parent 0b108fa commit da2422c

File tree

14 files changed

+109
-141
lines changed

14 files changed

+109
-141
lines changed

csharp/ql/src/Likely Bugs/LeapYear/UnsafeYearConstruction.qhelp

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,21 @@
33
"qhelp.dtd">
44
<qhelp>
55
<overview>
6-
<include src="LeapYear.qhelp" />
7-
8-
<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>
9-
<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>
10-
6+
<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>
7+
<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>
118
</overview>
129
<recommendation>
13-
<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>
14-
10+
<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>
1511
</recommendation>
1612
<example>
17-
<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>
18-
<sample src="UnsafeYearConstructionBad.cs" />
19-
20-
<p>To fix this bug, we add/substract years to the current date by calling <code>AddYears</code> method on it.</p>
21-
<sample src="UnsafeYearConstructionGood.cs" />
13+
<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>
14+
<sample src="UnsafeYearConstructionBad.cs" />
15+
<p>To fix this bug, we add/substract years to the current date by calling <code>AddYears</code> method on it.</p>
16+
<sample src="UnsafeYearConstructionGood.cs" />
2217
</example>
23-
18+
<references>
19+
<li>
20+
<a href="https://docs.microsoft.com/en-us/dotnet/api/system.datetime?view=netframework-4.8">System.DateTime Struct</a>.
21+
</li>
22+
</references>
2423
</qhelp>
Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,39 @@
11
/**
2-
* @name Unsafe year argument for DateTime constructor
3-
* @description Constructing a DateTime object by setting the year argument by manipulating the year of a different DateTime object
4-
* @kind problem
2+
* @name Unsafe year argument for 'DateTime' constructor
3+
* @description Constructing a 'DateTime' struct by setting the year argument to an increment or decrement of the year of a different 'DateTime' struct
4+
* @kind path-problem
55
* @problem.severity error
6-
* @id cs/leap-year/unsafe-year-contruction
6+
* @id cs/unsafe-year-construction
77
* @precision high
88
* @tags security
9-
* leap-year
9+
* date-time
10+
* reliability
1011
*/
1112

1213
import csharp
14+
import DataFlow::PathGraph
1315
import semmle.code.csharp.dataflow.TaintTracking
1416

15-
class UnsafeYearCreationFromArithmeticConfiguration extends TaintTracking::Configuration {
16-
UnsafeYearCreationFromArithmeticConfiguration() { this = "UnsafeYearCreationFromArithmeticConfiguration" }
17+
class UnsafeYearCreationFromArithmeticConfiguration extends TaintTracking::Configuration {
18+
UnsafeYearCreationFromArithmeticConfiguration() {
19+
this = "UnsafeYearCreationFromArithmeticConfiguration"
20+
}
1721

18-
override predicate isSource(DataFlow::Node source) {
19-
exists( ArithmeticOperation ao, PropertyAccess pa |
20-
ao = source.asExpr() |
21-
pa = ao.getAChild*()
22-
and pa.getProperty().getQualifiedName().matches("%DateTime.Year")
22+
override predicate isSource(DataFlow::Node source) {
23+
exists(ArithmeticOperation ao, PropertyAccess pa | ao = source.asExpr() |
24+
pa = ao.getAChild*() and
25+
pa.getProperty().getQualifiedName().matches("System.DateTime.Year")
2326
)
2427
}
2528

2629
override predicate isSink(DataFlow::Node sink) {
27-
exists( ObjectCreation oc |
28-
sink.asExpr() = oc.getArgumentForName("year")
29-
and oc.getObjectType().getABaseType*().hasQualifiedName("System.DateTime"))
30+
exists(ObjectCreation oc |
31+
sink.asExpr() = oc.getArgumentForName("year") and
32+
oc.getObjectType().getABaseType*().hasQualifiedName("System.DateTime")
33+
)
3034
}
3135
}
3236

33-
from UnsafeYearCreationFromArithmeticConfiguration config, Expr sink, Expr source
34-
where config.hasFlow(DataFlow::exprNode(source), DataFlow::exprNode(sink))
35-
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"
37+
from UnsafeYearCreationFromArithmeticConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
38+
where config.hasFlowPath(source, sink)
39+
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"

csharp/ql/src/Likely Bugs/LeapYear/UnsafeYearConstructionBad.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
using System;
2-
32
public class UnsafeYearConstructionBad
43
{
54
public UnsafeYearConstructionBad()
65
{
76
DateTime Start;
87
DateTime End;
98
var now = DateTime.UtcNow;
10-
119
// the base-date +/1 n years may not be a valid date.
1210
Start = new DateTime(now.Year - 1, now.Month, now.Day, 0, 0, 0, DateTimeKind.Utc);
1311
End = new DateTime(now.Year + 1, now.Month, now.Day, 0, 0, 1, DateTimeKind.Utc);

csharp/ql/src/Likely Bugs/LeapYear/UnsafeYearConstructionGood.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
using System;
2-
32
public class UnsafeYearConstructionBad
43
{
54
public UnsafeYearConstructionBad()
65
{
76
DateTime Start;
87
DateTime End;
98
var now = DateTime.UtcNow;
10-
119
Start = now.AddYears(-1).Date;
1210
End = now.AddYears(-1).Date.AddSeconds(1);
1311
}
Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,21 @@
11
using System;
2-
32
using System.Globalization;
4-
53
public class Example
6-
74
{
8-
95
public static void Main()
10-
116
{
12-
137
var cal = new JapaneseCalendar();
14-
158
// constructing date using current era
169
var dat = cal.ToDateTime(2, 1, 2, 0, 0, 0, 0);
17-
1810
Console.WriteLine($"{dat:s}");
19-
2011
// constructing date using current era
2112
dat = new DateTime(2, 1, 2, cal);
22-
2313
Console.WriteLine($"{dat:s}");
24-
2514
}
26-
2715
}
28-
2916
// Output with the Heisei era current:
30-
3117
// 1990-01-02T00:00:00
32-
3318
// 1990-01-02T00:00:00
34-
3519
// Output with the new era current:
36-
3720
// 2020-01-02T00:00:00
38-
3921
// 2020-01-02T00:00:00

csharp/ql/src/Likely Bugs/MishandlingJapaneseEra.qhelp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@
2424
<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>.
2525
</li>
2626
<li>
27-
<a href="https://blogs.msdn.microsoft.com/shawnste/2018/04/12/the-japanese-calendars-y2k-moment/">The Japanese Calendars Y2K Moment</a>.
27+
<a href="https://blogs.msdn.microsoft.com/shawnste/2018/04/12/the-japanese-calendars-y2k-moment/">The Japanese Calendar's Y2K Moment</a>.
2828
</li>
2929
<li>
3030
<a href="https://docs.microsoft.com/en-us/windows/desktop/Intl/era-handling-for-the-japanese-calendar/">Era Handling for the Japanese Calendar</a>.
3131
</li>
32+
<li>
33+
<a href="https://simple.wikipedia.org/wiki/List_of_Japanese_eras">List of Japanese Eras (Wikipedia)</a>
34+
</li>
3235
</references>
3336
</qhelp>
Lines changed: 51 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,72 @@
11
/**
2-
* @name MishandlingJapaneseEraStartDate
3-
* @description Japanese Era should be handled with built-in JapaneseCalendar class. Aviod hard-coding Japanese era start dates and names.
2+
* @name Mishandling the Japanese era start date
3+
* @description Japanese era should be handled with the built-in 'JapaneseCalendar' class. Avoid hard-coding Japanese era start dates and names.
44
* @id cs/mishandling-japanese-era
55
* @kind problem
66
* @problem.severity warning
77
* @precision medium
88
* @tags reliability
9-
* japanese-era
9+
* date-time
1010
*/
1111

1212
import csharp
1313

14+
/**
15+
* Holds if `year`, `month`, and `day` specify the start of a new era
16+
* (see https://simple.wikipedia.org/wiki/List_of_Japanese_eras).
17+
*/
18+
predicate isEraStart(int year, int month, int day) {
19+
year = 1989 and month = 1 and day = 8
20+
or
21+
year = 2019 and month = 5 and day = 1
22+
}
23+
1424
predicate isExactEraStartDateCreation(ObjectCreation cr) {
15-
(cr.getType().hasQualifiedName("System.DateTime") or cr.getType().hasQualifiedName("System.DateTimeOffset"))
16-
and
17-
cr.getArgument(0).getValue() = "1989" and
18-
cr.getArgument(1).getValue() = "1" and
19-
cr.getArgument(2).getValue() = "8"
25+
(
26+
cr.getType().hasQualifiedName("System.DateTime") or
27+
cr.getType().hasQualifiedName("System.DateTimeOffset")
28+
) and
29+
isEraStart(cr.getArgument(0).getValue().toInt(), cr.getArgument(1).getValue().toInt(), cr.getArgument(2).getValue().toInt())
2030
}
2131

2232
predicate isDateFromJapaneseCalendarToDateTime(MethodCall mc) {
23-
(mc.getQualifier().getType().hasQualifiedName("System.Globalization.JapaneseCalendar") or mc.getQualifier().getType().hasQualifiedName("System.Globalization.JapaneseLunisolarCalendar"))
24-
and
25-
mc.getTarget().hasName("ToDateTime") and
26-
mc.getArgument(0).getValue() != "" and
27-
(mc.getNumberOfArguments() = 7 or // implicitly current era
28-
mc.getNumberOfArguments() = 8 and
29-
mc.getArgument(7).getValue() = "0") // explicitly current era
33+
(
34+
mc.getQualifier().getType().hasQualifiedName("System.Globalization.JapaneseCalendar") or
35+
mc.getQualifier().getType().hasQualifiedName("System.Globalization.JapaneseLunisolarCalendar")
36+
) and
37+
mc.getTarget().hasName("ToDateTime") and
38+
mc.getArgument(0).hasValue() and
39+
(
40+
mc.getNumberOfArguments() = 7 // implicitly current era
41+
or
42+
mc.getNumberOfArguments() = 8 and
43+
mc.getArgument(7).getValue() = "0"
44+
) // explicitly current era
3045
}
3146

3247
predicate isDateFromJapaneseCalendarCreation(ObjectCreation cr) {
33-
(cr.getType().hasQualifiedName("System.DateTime") or cr.getType().hasQualifiedName("System.DateTimeOffset")) and
34-
(cr.getArgumentForName("calendar").getType().hasQualifiedName("System.Globalization.JapaneseCalendar") or cr.getArgumentForName("calendar").getType().hasQualifiedName("System.Globalization.JapaneseLunisolarCalendar"))
35-
and
36-
cr.getArgumentForName("year").getValue() != ""
37-
}
38-
39-
predicate inEraArrayCreation(ArrayInitializer ai) {
40-
ai.getElement(0).getValue() = "1867" and
41-
ai.getElement(1).getValue() = "1911" and
42-
ai.getElement(2).getValue() = "1925" and
43-
ai.getElement(3).getValue() = "1988"
44-
}
45-
46-
predicate isEraCollectionCreation(CollectionInitializer cs) {
47-
cs.getElementInitializer(0).getValue() = "1867" and
48-
cs.getElementInitializer(1).getValue() = "1911" and
49-
cs.getElementInitializer(2).getValue() = "1925" and
50-
cs.getElementInitializer(3).getValue() = "1988"
48+
(
49+
cr.getType().hasQualifiedName("System.DateTime") or
50+
cr.getType().hasQualifiedName("System.DateTimeOffset")
51+
) and
52+
(
53+
cr
54+
.getArgumentForName("calendar")
55+
.getType()
56+
.hasQualifiedName("System.Globalization.JapaneseCalendar") or
57+
cr
58+
.getArgumentForName("calendar")
59+
.getType()
60+
.hasQualifiedName("System.Globalization.JapaneseLunisolarCalendar")
61+
) and
62+
cr.getArgumentForName("year").hasValue()
5163
}
5264

5365
from Expr expr, string message
54-
where
55-
isDateFromJapaneseCalendarToDateTime(expr) and message = "DateTime created from Japanese calendar with explicit or current era and hard-coded year" or
56-
isDateFromJapaneseCalendarCreation(expr) and message = "DateTime constructed from Japanese calendar with explicit or current era and hard-coded year" or
57-
isEraCollectionCreation(expr) and message = "Hard-coded collection with Japanese era years" or
58-
inEraArrayCreation (expr) and message = "Hard-coded array with Japanese era years" or
59-
isExactEraStartDateCreation(expr) and message = "Hard-coded the beginning of the Japanese Heisei era"
66+
where
67+
isDateFromJapaneseCalendarToDateTime(expr) and message = "'DateTime' created from Japanese calendar with explicit or current era and hard-coded year."
68+
or
69+
isDateFromJapaneseCalendarCreation(expr) and message = "'DateTime' constructed from Japanese calendar with explicit or current era and hard-coded year."
70+
or
71+
isExactEraStartDateCreation(expr) and message = "Hard-coded the beginning of the Japanese Heisei era."
6072
select expr, message

csharp/ql/test/query-tests/Likely Bugs/LeapYear/AntiPattern1/MishandlingJapaneseEra.expected

Lines changed: 0 additions & 3 deletions
This file was deleted.

csharp/ql/test/query-tests/Likely Bugs/LeapYear/AntiPattern1/options

Lines changed: 0 additions & 1 deletion
This file was deleted.

csharp/ql/test/query-tests/Likely Bugs/LeapYear/AntiPattern1/Program.cs renamed to csharp/ql/test/query-tests/Likely Bugs/LeapYear/UnsafeYearConstruction/Program.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@ public class PipelineProperties
99
public PipelineProperties()
1010
{
1111
var now = DateTime.UtcNow;
12-
// BUG
12+
// BAD
1313
this.Start = new DateTime(now.Year - 1, now.Month, now.Day, 0, 0, 0, DateTimeKind.Utc);
1414

1515
var endYear = now.Year + 1;
16-
// BUG
16+
// BAD
1717
this.End = new DateTime(endYear, now.Month, now.Day, 0, 0, 1, DateTimeKind.Utc);
1818

19-
// OK
19+
// GOOD
2020
this.Start = now.AddYears(-1).Date;
2121
}
2222

2323
private void Test(int year, int month, int day)
2424
{
25-
// BUG (arithmetic operation from StartTest)
25+
// BAD (arithmetic operation from StartTest)
2626
this.Start = new DateTime(year, month, day);
2727
}
2828

0 commit comments

Comments
 (0)