Skip to content

Commit a3aacfb

Browse files
authored
Merge pull request #20190 from Napalys/java/jvm-exit-query-promotion
Java: Enhance `java/jvm-exit` query and add to quality
2 parents f232335 + b3f90bb commit a3aacfb

File tree

11 files changed

+246
-33
lines changed

11 files changed

+246
-33
lines changed

java/ql/integration-tests/java/query-suite/java-code-quality-extended.qls.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ ql/java/ql/src/Violations of Best Practice/Records/IgnoredSerializationMembersOf
8282
ql/java/ql/src/Violations of Best Practice/SpecialCharactersInLiterals/NonExplicitControlAndWhitespaceCharsInLiterals.ql
8383
ql/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.ql
8484
ql/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.ql
85+
ql/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.ql
8586
ql/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.ql
8687
ql/java/ql/src/Violations of Best Practice/Undesirable Calls/DoNotCallFinalize.ql
8788
ql/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.ql

java/ql/integration-tests/java/query-suite/not_included_in_qls.expected

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,6 @@ ql/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConsta
186186
ql/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.ql
187187
ql/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.ql
188188
ql/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsField.ql
189-
ql/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.ql
190189
ql/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.ql
191190
ql/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.ql
192191
ql/java/ql/src/Violations of Best Practice/legacy/FinallyMayNotComplete.ql

java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ boolean write(String[] s) {
44
try {
55
output.write(s.getBytes());
66
} catch (IOException e) {
7-
System.exit(1);
7+
System.exit(1); // BAD: Should handle or propagate error instead of exiting
88
}
99
return true;
1010
}
@@ -16,9 +16,30 @@ public void run() {
1616
// ...
1717
// Perform tasks ...
1818
// ...
19-
System.exit(0);
19+
System.exit(0); // BAD: Should return status or throw exception
2020
}
2121
public static void main(String[] args) {
22-
new Action(args).run();
22+
new Action().run();
2323
}
24-
}
24+
}
25+
26+
// Good example: Proper error handling
27+
class BetterAction {
28+
public int run() throws Exception {
29+
// ...
30+
// Perform tasks ...
31+
// ...
32+
return 0; // Return status instead of calling System.exit
33+
}
34+
35+
public static void main(String[] args) {
36+
try {
37+
BetterAction action = new BetterAction();
38+
int exitCode = action.run();
39+
System.exit(exitCode); // GOOD: Exit from main method
40+
} catch (Exception e) {
41+
System.err.println("Error: " + e.getMessage());
42+
System.exit(1); // GOOD: Exit from main method on error
43+
}
44+
}
45+
}

java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.qhelp

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,20 @@ program state from being written to disk consistently.</p>
1313

1414
<p>It is sometimes considered acceptable to call <code>System.exit</code>
1515
from a program's <code>main</code> method in order to indicate the overall exit status
16-
of the program. Such calls are an exception to this rule.</p>
16+
of the program. The <code>main</code> method should be the primary place
17+
where exit conditions are handled, as it represents the natural termination point
18+
of the application. Such calls are an exception to this rule.</p>
1719

1820
</overview>
1921
<recommendation>
2022

21-
<p>It is usually preferable to use a different mechanism for reporting
22-
failure conditions. Consider returning a special value (perhaps
23-
<code>null</code>) that users of the current method check for and
24-
recover from appropriately. Alternatively, throw a suitable exception, which
25-
unwinds the stack and allows properly written code to clean up after itself,
26-
while leaving other threads undisturbed.</p>
23+
<p>Instead of calling <code>System.exit</code> from non-main methods, prefer to propagate
24+
errors upward to the <code>main</code> method where they can be handled appropriately.
25+
Consider returning a special value (perhaps <code>null</code>) that users of the current
26+
method check for and recover from appropriately. Alternatively, throw a suitable exception,
27+
which unwinds the stack and allows properly written code to clean up after itself,
28+
while leaving other threads undisturbed. The <code>main</code> method can then catch
29+
these exceptions and decide whether to exit the program and with what exit code.</p>
2730

2831
</recommendation>
2932
<example>
@@ -38,12 +41,14 @@ upwards and be handled by a method that knows how to recover.</p>
3841
<p>Problem 2 is more subtle. In this example, there is just one entry point to
3942
the program (the <code>main</code> method), which constructs an
4043
<code>Action</code> and performs it. <code>Action.run</code> calls
41-
<code>System.exit</code> to indicate successful completion. Consider,
42-
however, how this code might be integrated in an application server that
43-
constructs <code>Action</code> instances and calls
44+
<code>System.exit</code> to indicate successful completion. Instead, the
45+
<code>run</code> method should return a status code or throw an exception
46+
on failure, allowing the <code>main</code> method to decide whether to exit
47+
and with what exit code. Consider how this code might be integrated in an
48+
application server that constructs <code>Action</code> instances and calls
4449
<code>run</code> on them without going through <code>main</code>.
4550
The fact that <code>run</code> terminates the JVM instead of returning its
46-
exit code as an integer makes that use-case impossible.</p>
51+
exit code makes that use-case impossible.</p>
4752

4853
<sample src="CallsToSystemExit.java" />
4954

java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.ql

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,80 @@
44
* reuse and prevent important cleanup steps from running.
55
* @kind problem
66
* @problem.severity warning
7-
* @precision low
7+
* @precision medium
88
* @id java/jvm-exit
9-
* @tags reliability
10-
* maintainability
9+
* @previous-id java/jvm-exit-prevents-cleanup-and-reuse
10+
* @tags quality
11+
* reliability
12+
* correctness
1113
* external/cwe/cwe-382
1214
*/
1315

1416
import java
1517

16-
from Method m, MethodCall sysexitCall, Method sysexit, Class system
17-
where
18-
sysexitCall = m.getACallSite(sysexit) and
19-
(sysexit.hasName("exit") or sysexit.hasName("halt")) and
20-
sysexit.getDeclaringType() = system and
21-
(
22-
system.hasQualifiedName("java.lang", "System") or
23-
system.hasQualifiedName("java.lang", "Runtime")
24-
) and
25-
m.fromSource() and
26-
not m instanceof MainMethod
27-
select sysexitCall,
28-
"Avoid calls to " + sysexit.getDeclaringType().getName() + "." + sysexit.getName() +
29-
"() as this makes code harder to reuse."
18+
/**
19+
* A `Method` which, when called, causes the JVM to exit or halt.
20+
*
21+
* Explicitly includes these methods from the java standard library:
22+
* - `java.lang.System.exit`
23+
* - `java.lang.Runtime.halt`
24+
* - `java.lang.Runtime.exit`
25+
*/
26+
class ExitOrHaltMethod extends Method {
27+
ExitOrHaltMethod() {
28+
exists(Class system | this.getDeclaringType() = system |
29+
this.hasName("exit") and
30+
system.hasQualifiedName("java.lang", ["System", "Runtime"])
31+
or
32+
this.hasName("halt") and
33+
system.hasQualifiedName("java.lang", "Runtime")
34+
)
35+
}
36+
}
37+
38+
/** A `MethodCall` to an `ExitOrHaltMethod`, which causes the JVM to exit abruptly. */
39+
class ExitOrHaltMethodCall extends MethodCall {
40+
ExitOrHaltMethodCall() {
41+
exists(ExitOrHaltMethod exitMethod | this.getMethod() = exitMethod |
42+
exists(SourceMethodNotMainOrTest srcMethod | this = srcMethod.getACallSite(exitMethod))
43+
)
44+
}
45+
}
46+
47+
/**
48+
* An intentional `MethodCall` to a system or runtime "exit" method, such as for
49+
* functions which exist for the purpose of exiting the program. Assumes that an exit method
50+
* call within a method is intentional if the exit code is passed from a parameter of the
51+
* enclosing method.
52+
*/
53+
class IntentionalExitMethodCall extends ExitOrHaltMethodCall {
54+
IntentionalExitMethodCall() {
55+
this.getMethod().hasName("exit") and
56+
this.getAnArgument() = this.getEnclosingCallable().getAParameter().getAnAccess()
57+
}
58+
}
59+
60+
/**
61+
* A `Method` that is defined in source code and is not a `MainMethod` or a `LikelyTestMethod`.
62+
*/
63+
class SourceMethodNotMainOrTest extends Method {
64+
SourceMethodNotMainOrTest() {
65+
this.fromSource() and
66+
not this instanceof MainMethod and
67+
not (
68+
this.getEnclosingCallable*() instanceof LikelyTestMethod
69+
or
70+
this.getDeclaringType()
71+
.getEnclosingType*()
72+
.(LocalClassOrInterface)
73+
.getLocalTypeDeclStmt()
74+
.getEnclosingCallable() instanceof LikelyTestMethod
75+
)
76+
}
77+
}
78+
79+
from ExitOrHaltMethodCall mc
80+
where not mc instanceof IntentionalExitMethodCall
81+
select mc,
82+
"Avoid calls to " + mc.getMethod().getDeclaringType().getName() + "." + mc.getMethod().getName() +
83+
"() as this prevents runtime cleanup and makes code harder to reuse."
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
| ExampleRuntimeExit.java:22:17:22:44 | exit(...) | Avoid calls to Runtime.exit() as this prevents runtime cleanup and makes code harder to reuse. |
2+
| ExampleRuntimeExit.java:25:17:25:44 | exit(...) | Avoid calls to Runtime.exit() as this prevents runtime cleanup and makes code harder to reuse. |
3+
| ExampleRuntimeHalt.java:18:17:18:44 | halt(...) | Avoid calls to Runtime.halt() as this prevents runtime cleanup and makes code harder to reuse. |
4+
| ExampleRuntimeHalt.java:21:17:21:44 | halt(...) | Avoid calls to Runtime.halt() as this prevents runtime cleanup and makes code harder to reuse. |
5+
| ExampleSystemExit.java:22:17:22:30 | exit(...) | Avoid calls to System.exit() as this prevents runtime cleanup and makes code harder to reuse. |
6+
| ExampleSystemExit.java:25:17:25:30 | exit(...) | Avoid calls to System.exit() as this prevents runtime cleanup and makes code harder to reuse. |
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
query: Violations of Best Practice/Undesirable Calls/CallsToSystemExit.ql
2+
postprocess: utils/test/InlineExpectationsTestQuery.ql
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import java.io.FileOutputStream;
2+
import java.io.IOException;
3+
4+
public class ExampleRuntimeExit {
5+
6+
public static void main(String[] args) {
7+
Action action = new Action();
8+
try {
9+
action.run();
10+
} catch (Exception e) {
11+
printUsageAndExit(e.getMessage(), 1);
12+
}
13+
Runtime.getRuntime().exit(0); // COMPLIANT
14+
}
15+
16+
static class Action {
17+
public void run() {
18+
try {
19+
FileOutputStream fos = new FileOutputStream("output.txt");
20+
fos.write("Hello, World!".getBytes());
21+
fos.close();
22+
Runtime.getRuntime().exit(0); // $ Alert
23+
} catch (IOException e) {
24+
e.printStackTrace();
25+
Runtime.getRuntime().exit(1); // $ Alert
26+
} catch (Exception e) {
27+
// re-throw the exception
28+
throw e;
29+
}
30+
}
31+
}
32+
33+
protected static void printUsageAndExit(final String message, final int exitCode) {
34+
System.err.println("Usage: <example_cmd> <example_args> : " + message);
35+
Runtime.getRuntime().exit(exitCode); // COMPLIANT
36+
}
37+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import java.io.FileOutputStream;
2+
import java.io.IOException;
3+
4+
public class ExampleRuntimeHalt {
5+
6+
public static void main(String[] args) {
7+
Action action = new Action();
8+
action.run();
9+
Runtime.getRuntime().halt(0); // COMPLIANT
10+
}
11+
12+
static class Action {
13+
public void run() {
14+
try {
15+
FileOutputStream fos = new FileOutputStream("output.txt");
16+
fos.write("Hello, World!".getBytes());
17+
fos.close();
18+
Runtime.getRuntime().halt(0); // $ Alert
19+
} catch (IOException e) {
20+
e.printStackTrace();
21+
Runtime.getRuntime().halt(1); // $ Alert
22+
}
23+
}
24+
}
25+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import java.io.FileOutputStream;
2+
import java.io.IOException;
3+
4+
public class ExampleSystemExit {
5+
6+
public static void main(String[] args) {
7+
Action action = new Action();
8+
try {
9+
action.run();
10+
} catch (Exception e) {
11+
printUsageAndExit(e.getMessage(), 1);
12+
}
13+
System.exit(0); // COMPLIANT
14+
}
15+
16+
static class Action {
17+
public void run() {
18+
try {
19+
FileOutputStream fos = new FileOutputStream("output.txt");
20+
fos.write("Hello, World!".getBytes());
21+
fos.close();
22+
System.exit(0); // $ Alert
23+
} catch (IOException e) {
24+
e.printStackTrace();
25+
System.exit(1); // $ Alert
26+
} catch (Exception e) {
27+
// re-throw the exception
28+
throw e;
29+
}
30+
}
31+
}
32+
33+
protected static void printUsageAndExit(final String message, final int exitCode) {
34+
System.err.println("Usage: <example_cmd> <example_args> : " + message);
35+
System.exit(exitCode); // COMPLIANT
36+
}
37+
}

0 commit comments

Comments
 (0)