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     }}, sac);
111 
112     assertAnalyzerWarnings(q{
113         int fun() { return 1; }
114         void main()
115         {
116             fun(); // [warn]: %s
117         }
118     }}.format(UnusedResultChecker.MSG), sac);
119 
120     assertAnalyzerWarnings(q{
121         void main()
122         {
123             void fun() {}
124             fun();
125         }
126     }}, 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     }}.format(UnusedResultChecker.MSG), sac);
136 
137     assertAnalyzerWarnings(q{
138         int fun() { return 1; }
139         void main()
140         {
141             cast(void) fun();
142         }
143     }}, sac);
144 
145     assertAnalyzerWarnings(q{
146         void fun() { }
147         alias gun = fun;
148         void main()
149         {
150             gun();
151         }
152     }}, sac);
153 
154     import std.stdio: writeln;
155     writeln("Unittest for UnusedResultChecker passed");
156 }
157