Skip to content

Commit c2e7627

Browse files
authored
Merge pull request #351 from nystrom/master
Approved by pavgust
2 parents 306b711 + 4227cdb commit c2e7627

File tree

7 files changed

+312
-0
lines changed

7 files changed

+312
-0
lines changed

change-notes/1.19/analysis-java.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
| **Query** | **Tags** | **Purpose** |
88
|-----------------------------|-----------|--------------------------------------------------------------------|
9+
| Missing catch of NumberFormatException (`java/uncaught-number-format-exception`) | reliability, external/cwe/cwe-248 | Finds calls to `Integer.parseInt` and similar string-to-number conversions that might raise a `NumberFormatException` without a corresponding `catch`-clause. |
910

1011
## Changes to existing queries
1112

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
String s = ...;
2+
int n;
3+
4+
n = Integer.parseInt(s); // BAD: NumberFormatException is not caught.
5+
6+
try {
7+
n = Integer.parseInt(s);
8+
} catch (NumberFormatException e) { // GOOD: The exception is caught.
9+
// Handle the exception
10+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
7+
<overview>
8+
<p>Methods such as <code>Integer.parseInt</code> that parse strings into numbers
9+
throw
10+
<code>NumberFormatException</code> if their arguments cannot be parsed.
11+
This exception should be caught so that any parse errors can be handled.
12+
</p>
13+
14+
</overview>
15+
<recommendation>
16+
17+
<p>It is usually best to handle <code>NumberFormatException</code> in a <code>catch</code> clause
18+
surrounding the call to the parsing method.</p>
19+
20+
</recommendation>
21+
<example>
22+
23+
<p>In the following example, the first call to <code>Integer.parseInt</code> does not catch the exception.
24+
The second call does.
25+
</p>
26+
27+
<sample src="NumberFormatException.java" />
28+
29+
</example>
30+
<references>
31+
32+
33+
<li>
34+
Java Platform, Standard Edition 8, API Specification:
35+
<a href="https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#valueOf-java.lang.String-">Integer.valueOf</a>,
36+
<a href="https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#parseInt-java.lang.String-">Integer.parseInt</a>,
37+
<a href="https://docs.oracle.com/javase/8/docs/api/java/lang/Long.html#parseLong-java.lang.String-">Long.parseLong</a>,
38+
<a href="https://docs.oracle.com/javase/8/docs/api/java/lang/NumberFormatException.html">NumberFormatException</a>.
39+
</li>
40+
41+
42+
</references>
43+
</qhelp>
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* @name Missing catch of NumberFormatException
3+
* @description Calling a string to number conversion method without handling
4+
* 'NumberFormatException' may cause unexpected runtime exceptions.
5+
* @kind problem
6+
* @problem.severity recommendation
7+
* @precision high
8+
* @id java/uncaught-number-format-exception
9+
* @tags reliability
10+
* external/cwe/cwe-248
11+
*/
12+
13+
import java
14+
15+
private class SpecialMethodAccess extends MethodAccess {
16+
predicate isValueOfMethod(string klass) {
17+
this.getMethod().getName() = "valueOf" and
18+
this.getQualifier().getType().(RefType).hasQualifiedName("java.lang", klass) and
19+
this.getAnArgument().getType().(RefType).hasQualifiedName("java.lang", "String")
20+
}
21+
22+
predicate isParseMethod(string klass, string name) {
23+
this.getMethod().getName() = name and
24+
this.getQualifier().getType().(RefType).hasQualifiedName("java.lang", klass)
25+
}
26+
27+
predicate throwsNFE() {
28+
this.isParseMethod("Byte", "parseByte") or
29+
this.isParseMethod("Short", "parseShort") or
30+
this.isParseMethod("Integer", "parseInt") or
31+
this.isParseMethod("Long", "parseLong") or
32+
this.isParseMethod("Float", "parseFloat") or
33+
this.isParseMethod("Double", "parseDouble") or
34+
this.isParseMethod("Byte", "decode") or
35+
this.isParseMethod("Short", "decode") or
36+
this.isParseMethod("Integer", "decode") or
37+
this.isParseMethod("Long", "decode") or
38+
this.isValueOfMethod("Byte") or
39+
this.isValueOfMethod("Short") or
40+
this.isValueOfMethod("Integer") or
41+
this.isValueOfMethod("Long") or
42+
this.isValueOfMethod("Float") or
43+
this.isValueOfMethod("Double")
44+
}
45+
}
46+
47+
private class SpecialClassInstanceExpr extends ClassInstanceExpr {
48+
predicate isStringConstructor(string klass) {
49+
this.getType().(RefType).hasQualifiedName("java.lang", klass) and
50+
this.getAnArgument().getType().(RefType).hasQualifiedName("java.lang", "String") and
51+
this.getNumArgument() = 1
52+
}
53+
54+
predicate throwsNFE() {
55+
this.isStringConstructor("Byte") or
56+
this.isStringConstructor("Short") or
57+
this.isStringConstructor("Integer") or
58+
this.isStringConstructor("Long") or
59+
this.isStringConstructor("Float") or
60+
this.isStringConstructor("Double")
61+
}
62+
}
63+
64+
class NumberFormatException extends RefType {
65+
NumberFormatException() { this.hasQualifiedName("java.lang", "NumberFormatException") }
66+
}
67+
68+
private predicate catchesNFE(TryStmt t) {
69+
exists(CatchClause cc, LocalVariableDeclExpr v |
70+
t.getACatchClause() = cc and
71+
cc.getVariable() = v and
72+
v.getType().(RefType).getASubtype*() instanceof NumberFormatException
73+
)
74+
}
75+
76+
private predicate throwsNFE(Expr e) {
77+
e.(SpecialClassInstanceExpr).throwsNFE() or e.(SpecialMethodAccess).throwsNFE()
78+
}
79+
80+
from Expr e
81+
where
82+
throwsNFE(e) and
83+
not exists(TryStmt t |
84+
t.getBlock() = e.getEnclosingStmt().getParent*() and
85+
catchesNFE(t)
86+
) and
87+
not exists(Callable c |
88+
e.getEnclosingCallable() = c and
89+
c.getAThrownExceptionType().getASubtype*() instanceof NumberFormatException
90+
)
91+
select e, "Potential uncaught 'java.lang.NumberFormatException'."
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
| Test.java:11:9:11:29 | parseByte(...) | Potential uncaught 'java.lang.NumberFormatException'. |
2+
| Test.java:12:9:12:26 | decode(...) | Potential uncaught 'java.lang.NumberFormatException'. |
3+
| Test.java:13:9:13:27 | valueOf(...) | Potential uncaught 'java.lang.NumberFormatException'. |
4+
| Test.java:14:9:14:31 | valueOf(...) | Potential uncaught 'java.lang.NumberFormatException'. |
5+
| Test.java:15:9:15:30 | valueOf(...) | Potential uncaught 'java.lang.NumberFormatException'. |
6+
| Test.java:16:9:16:23 | new Byte(...) | Potential uncaught 'java.lang.NumberFormatException'. |
7+
| Test.java:19:9:19:31 | parseShort(...) | Potential uncaught 'java.lang.NumberFormatException'. |
8+
| Test.java:20:9:20:27 | decode(...) | Potential uncaught 'java.lang.NumberFormatException'. |
9+
| Test.java:21:9:21:28 | valueOf(...) | Potential uncaught 'java.lang.NumberFormatException'. |
10+
| Test.java:22:9:22:32 | valueOf(...) | Potential uncaught 'java.lang.NumberFormatException'. |
11+
| Test.java:23:9:23:33 | valueOf(...) | Potential uncaught 'java.lang.NumberFormatException'. |
12+
| Test.java:24:9:24:24 | new Short(...) | Potential uncaught 'java.lang.NumberFormatException'. |
13+
| Test.java:27:9:27:31 | parseInt(...) | Potential uncaught 'java.lang.NumberFormatException'. |
14+
| Test.java:28:9:28:29 | decode(...) | Potential uncaught 'java.lang.NumberFormatException'. |
15+
| Test.java:29:9:29:30 | valueOf(...) | Potential uncaught 'java.lang.NumberFormatException'. |
16+
| Test.java:30:9:30:34 | valueOf(...) | Potential uncaught 'java.lang.NumberFormatException'. |
17+
| Test.java:31:9:31:39 | valueOf(...) | Potential uncaught 'java.lang.NumberFormatException'. |
18+
| Test.java:32:9:32:26 | new Integer(...) | Potential uncaught 'java.lang.NumberFormatException'. |
19+
| Test.java:35:9:35:29 | parseLong(...) | Potential uncaught 'java.lang.NumberFormatException'. |
20+
| Test.java:36:9:36:26 | decode(...) | Potential uncaught 'java.lang.NumberFormatException'. |
21+
| Test.java:37:9:37:27 | valueOf(...) | Potential uncaught 'java.lang.NumberFormatException'. |
22+
| Test.java:38:9:38:31 | valueOf(...) | Potential uncaught 'java.lang.NumberFormatException'. |
23+
| Test.java:39:9:39:36 | valueOf(...) | Potential uncaught 'java.lang.NumberFormatException'. |
24+
| Test.java:40:9:40:23 | new Long(...) | Potential uncaught 'java.lang.NumberFormatException'. |
25+
| Test.java:43:9:43:40 | parseFloat(...) | Potential uncaught 'java.lang.NumberFormatException'. |
26+
| Test.java:44:9:44:37 | valueOf(...) | Potential uncaught 'java.lang.NumberFormatException'. |
27+
| Test.java:45:9:45:33 | new Float(...) | Potential uncaught 'java.lang.NumberFormatException'. |
28+
| Test.java:48:9:48:42 | parseDouble(...) | Potential uncaught 'java.lang.NumberFormatException'. |
29+
| Test.java:49:9:49:38 | valueOf(...) | Potential uncaught 'java.lang.NumberFormatException'. |
30+
| Test.java:50:9:50:34 | new Double(...) | Potential uncaught 'java.lang.NumberFormatException'. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Violations of Best Practice/Exception Handling/NumberFormatException.ql
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import java.io.*;
2+
3+
public class Test {
4+
public static void main(String[] args) {
5+
test1();
6+
test2();
7+
test3();
8+
}
9+
10+
static void test1() {
11+
Byte.parseByte("123");
12+
Byte.decode("123");
13+
Byte.valueOf("123");
14+
Byte.valueOf("123", 10);
15+
Byte.valueOf("7f", 16);
16+
new Byte("123");
17+
new Byte((byte) 123); // don't flag: wrong constructor
18+
19+
Short.parseShort("123");
20+
Short.decode("123");
21+
Short.valueOf("123");
22+
Short.valueOf("123", 10);
23+
Short.valueOf("7abc", 16);
24+
new Short("123");
25+
new Short((short) 123); // don't flag: wrong constructor
26+
27+
Integer.parseInt("123");
28+
Integer.decode("123");
29+
Integer.valueOf("123");
30+
Integer.valueOf("123", 10);
31+
Integer.valueOf("1234beef", 16);
32+
new Integer("123");
33+
new Integer(123); // don't flag: wrong constructor
34+
35+
Long.parseLong("123");
36+
Long.decode("123");
37+
Long.valueOf("123");
38+
Long.valueOf("123", 10);
39+
Long.valueOf("deadbeef", 16);
40+
new Long("123");
41+
new Long(123l); // don't flag: wrong constructor
42+
43+
Float.parseFloat("2.7818281828");
44+
Float.valueOf("2.7818281828");
45+
new Float("2.7818281828");
46+
new Float(2.7818281828f); // don't flag: wrong constructor
47+
48+
Double.parseDouble("2.7818281828");
49+
Double.valueOf("2.7818281828");
50+
new Double("2.7818281828");
51+
new Double(2.7818281828); // don't flag: wrong constructor
52+
}
53+
54+
static void test2() {
55+
// Don't flag any of these. The exception is caught.
56+
try {
57+
Byte.parseByte("123");
58+
Byte.decode("123");
59+
Byte.valueOf("123");
60+
Byte.valueOf("123", 10);
61+
Byte.valueOf("7f", 16);
62+
new Byte("123");
63+
64+
Short.parseShort("123");
65+
Short.decode("123");
66+
Short.valueOf("123");
67+
Short.valueOf("123", 10);
68+
Short.valueOf("7abc", 16);
69+
new Short("123");
70+
71+
Integer.parseInt("123");
72+
Integer.decode("123");
73+
Integer.valueOf("123");
74+
Integer.valueOf("123", 10);
75+
Integer.valueOf("1234beef", 16);
76+
new Integer("123");
77+
78+
Long.parseLong("123");
79+
Long.decode("123");
80+
Long.valueOf("123");
81+
Long.valueOf("123", 10);
82+
Long.valueOf("deadbeef", 16);
83+
new Long("123");
84+
85+
Float.parseFloat("2.7818281828");
86+
Float.valueOf("2.7818281828");
87+
new Float("2.7818281828");
88+
89+
Double.parseDouble("2.7818281828");
90+
Double.valueOf("2.7818281828");
91+
new Double("2.7818281828");
92+
}
93+
catch (NumberFormatException e) {
94+
// parse error
95+
}
96+
}
97+
98+
static void test3() throws NumberFormatException {
99+
// Don't flag any of these: the exception is explcitly declared
100+
Byte.parseByte("123");
101+
Byte.decode("123");
102+
Byte.valueOf("123");
103+
Byte.valueOf("123", 10);
104+
Byte.valueOf("7f", 16);
105+
new Byte("123");
106+
107+
Short.parseShort("123");
108+
Short.decode("123");
109+
Short.valueOf("123");
110+
Short.valueOf("123", 10);
111+
Short.valueOf("7abc", 16);
112+
new Short("123");
113+
114+
Integer.parseInt("123");
115+
Integer.decode("123");
116+
Integer.valueOf("123");
117+
Integer.valueOf("123", 10);
118+
Integer.valueOf("1234beef", 16);
119+
new Integer("123");
120+
121+
Long.parseLong("123");
122+
Long.decode("123");
123+
Long.valueOf("123");
124+
Long.valueOf("123", 10);
125+
Long.valueOf("deadbeef", 16);
126+
new Long("123");
127+
128+
Float.parseFloat("2.7818281828");
129+
Float.valueOf("2.7818281828");
130+
new Float("2.7818281828");
131+
132+
Double.parseDouble("2.7818281828");
133+
Double.valueOf("2.7818281828");
134+
new Double("2.7818281828");
135+
}
136+
}

0 commit comments

Comments
 (0)