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