Skip to content

Commit 588aa6c

Browse files
authored
Merge pull request #819 from CyberShadow/unused_result
Add unused_result check
2 parents 2a1f96f + e61ce45 commit 588aa6c

File tree

4 files changed

+167
-0
lines changed

4 files changed

+167
-0
lines changed

.dscanner.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,5 @@ useless_initializer="disabled"
8989
allman_braces_check="disabled"
9090
; Check for redundant attributes
9191
redundant_attributes_check="enabled"
92+
; Check for unused function return values
93+
unused_result="enabled"

src/dscanner/analysis/config.d

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ struct StaticAnalysisConfig
203203
@INI("Check for redundant storage classes on variable declarations")
204204
string redundant_storage_classes = Check.enabled;
205205

206+
@INI("Check for unused function return values")
207+
string unused_result = Check.enabled;
208+
206209
@INI("Module-specific filters")
207210
ModuleFilters filters;
208211
}

src/dscanner/analysis/run.d

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ import dscanner.analysis.assert_without_msg;
7979
import dscanner.analysis.if_constraints_indent;
8080
import dscanner.analysis.trust_too_much;
8181
import dscanner.analysis.redundant_storage_class;
82+
import dscanner.analysis.unused_result;
8283

8384
import dsymbol.string_interning : internString;
8485
import dsymbol.scope_;
@@ -583,6 +584,10 @@ MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig a
583584
checks ~= new RedundantStorageClassCheck(fileName,
584585
analysisConfig.redundant_storage_classes == Check.skipTests && !ut);
585586

587+
if (moduleName.shouldRun!UnusedResultChecker(analysisConfig))
588+
checks ~= new UnusedResultChecker(fileName, moduleScope,
589+
analysisConfig.unused_result == Check.skipTests && !ut);
590+
586591
version (none)
587592
if (moduleName.shouldRun!IfStatementCheck(analysisConfig))
588593
checks ~= new IfStatementCheck(fileName, moduleScope,
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// Copyright Vladimir Panteleev 2020
2+
// Distributed under the Boost Software License, Version 1.0.
3+
// (See accompanying file LICENSE_1_0.txt or copy at
4+
// http://www.boost.org/LICENSE_1_0.txt)
5+
module dscanner.analysis.unused_result;
6+
7+
import dscanner.analysis.base;
8+
import dscanner.analysis.mismatched_args : resolveSymbol, IdentVisitor;
9+
import dscanner.utils;
10+
import dsymbol.scope_;
11+
import dsymbol.symbol;
12+
import dparse.ast, dparse.lexer;
13+
import std.algorithm.searching : canFind;
14+
import std.range: retro;
15+
16+
/**
17+
* Checks for function call statements which call non-void functions.
18+
*
19+
* In case the function returns a value indicating success/failure,
20+
* ignoring this return value and continuing execution can lead to
21+
* undesired results.
22+
*
23+
* When the return value is intentionally discarded, `cast(void)` can
24+
* be prepended to silence the check.
25+
*/
26+
final class UnusedResultChecker : BaseAnalyzer
27+
{
28+
alias visit = BaseAnalyzer.visit;
29+
30+
mixin AnalyzerInfo!"unused_result";
31+
32+
private:
33+
34+
enum string KEY = "dscanner.unused_result";
35+
enum string MSG = "Function return value is discarded";
36+
37+
public:
38+
39+
const(DSymbol)* void_;
40+
41+
///
42+
this(string fileName, const(Scope)* sc, bool skipTests = false)
43+
{
44+
super(fileName, sc, skipTests);
45+
void_ = sc.getSymbolsByName(internString("void"))[0];
46+
}
47+
48+
override void visit(const(ExpressionStatement) decl)
49+
{
50+
import std.typecons : scoped;
51+
52+
super.visit(decl);
53+
if (!decl.expression)
54+
return;
55+
if (decl.expression.items.length != 1)
56+
return;
57+
auto ue = cast(UnaryExpression) decl.expression.items[0];
58+
if (!ue)
59+
return;
60+
auto fce = ue.functionCallExpression;
61+
if (!fce)
62+
return;
63+
64+
auto identVisitor = scoped!IdentVisitor;
65+
if (fce.unaryExpression !is null)
66+
identVisitor.visit(fce.unaryExpression);
67+
else if (fce.type !is null)
68+
identVisitor.visit(fce.type);
69+
70+
if (!identVisitor.names.length)
71+
return;
72+
73+
const(DSymbol)*[] symbols = resolveSymbol(sc, identVisitor.names);
74+
75+
if (!symbols.length)
76+
return;
77+
78+
foreach (sym; symbols)
79+
{
80+
if (!sym)
81+
return;
82+
if (!sym.type)
83+
return;
84+
if (sym.kind != CompletionKind.functionName)
85+
return;
86+
if (sym.type is void_)
87+
return;
88+
}
89+
90+
addErrorMessage(decl.expression.line, decl.expression.column, KEY, MSG);
91+
}
92+
}
93+
94+
unittest
95+
{
96+
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
97+
import dscanner.analysis.helpers : assertAnalyzerWarnings;
98+
import std.stdio : stderr;
99+
import std.format : format;
100+
101+
StaticAnalysisConfig sac = disabledConfig();
102+
sac.unused_result = Check.enabled;
103+
104+
assertAnalyzerWarnings(q{
105+
void fun() {}
106+
void main()
107+
{
108+
fun();
109+
}
110+
}c, sac);
111+
112+
assertAnalyzerWarnings(q{
113+
int fun() { return 1; }
114+
void main()
115+
{
116+
fun(); // [warn]: %s
117+
}
118+
}c.format(UnusedResultChecker.MSG), sac);
119+
120+
assertAnalyzerWarnings(q{
121+
void main()
122+
{
123+
void fun() {}
124+
fun();
125+
}
126+
}c, sac);
127+
128+
version (none) // TODO: local functions
129+
assertAnalyzerWarnings(q{
130+
void main()
131+
{
132+
int fun() { return 1; }
133+
fun(); // [warn]: %s
134+
}
135+
}c.format(UnusedResultChecker.MSG), sac);
136+
137+
assertAnalyzerWarnings(q{
138+
int fun() { return 1; }
139+
void main()
140+
{
141+
cast(void) fun();
142+
}
143+
}c, sac);
144+
145+
assertAnalyzerWarnings(q{
146+
void fun() { }
147+
alias gun = fun;
148+
void main()
149+
{
150+
gun();
151+
}
152+
}c, sac);
153+
154+
import std.stdio: writeln;
155+
writeln("Unittest for UnusedResultChecker passed");
156+
}
157+

0 commit comments

Comments
 (0)