1 // Distributed under the Boost Software License, Version 1.0. 2 // (See accompanying file LICENSE_1_0.txt or copy at 3 // http://www.boost.org/LICENSE_1_0.txt) 4 5 module dscanner.analysis.assert_without_msg; 6 7 import dscanner.analysis.base; 8 import dscanner.utils : safeAccess; 9 import dsymbol.scope_ : Scope; 10 import dparse.lexer; 11 import dparse.ast; 12 13 import std.stdio; 14 import std.algorithm; 15 16 /** 17 * Check that all asserts have an explanatory message. 18 */ 19 final class AssertWithoutMessageCheck : BaseAnalyzer 20 { 21 enum string KEY = "dscanner.style.assert_without_msg"; 22 enum string MESSAGE = "An assert should have an explanatory message"; 23 mixin AnalyzerInfo!"assert_without_msg"; 24 25 /// 26 this(string fileName, const(Scope)* sc, bool skipTests = false) 27 { 28 super(fileName, sc, skipTests); 29 } 30 31 override void visit(const AssertExpression expr) 32 { 33 if (expr.assertArguments && expr.assertArguments.message is null) 34 addErrorMessage(expr.line, expr.column, KEY, MESSAGE); 35 } 36 37 override void visit(const FunctionCallExpression expr) 38 { 39 if (!isStdExceptionImported) 40 return; 41 42 if (const IdentifierOrTemplateInstance iot = safeAccess(expr) 43 .unaryExpression.primaryExpression.identifierOrTemplateInstance) 44 { 45 auto ident = iot.identifier; 46 if (ident.text == "enforce" && expr.arguments !is null && expr.arguments.argumentList !is null && 47 expr.arguments.argumentList.items.length < 2) 48 addErrorMessage(ident.line, ident.column, KEY, MESSAGE); 49 } 50 } 51 52 override void visit(const SingleImport sImport) 53 { 54 static immutable stdException = ["std", "exception"]; 55 if (sImport.identifierChain.identifiers.map!(a => a.text).equal(stdException)) 56 isStdExceptionImported = true; 57 } 58 59 // revert the stack after new scopes 60 override void visit(const Declaration decl) 61 { 62 // be careful - ImportDeclarations don't introduce a new scope 63 if (decl.importDeclaration is null) 64 { 65 bool tmp = isStdExceptionImported; 66 scope(exit) isStdExceptionImported = tmp; 67 decl.accept(this); 68 } 69 else 70 decl.accept(this); 71 } 72 73 mixin ScopedVisit!IfStatement; 74 mixin ScopedVisit!BlockStatement; 75 76 alias visit = BaseAnalyzer.visit; 77 78 private: 79 bool isStdExceptionImported; 80 81 template ScopedVisit(NodeType) 82 { 83 override void visit(const NodeType n) 84 { 85 bool tmp = isStdExceptionImported; 86 scope(exit) isStdExceptionImported = tmp; 87 n.accept(this); 88 } 89 } 90 } 91 92 unittest 93 { 94 import std.stdio : stderr; 95 import std.format : format; 96 import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; 97 import dscanner.analysis.helpers : assertAnalyzerWarnings; 98 99 StaticAnalysisConfig sac = disabledConfig(); 100 sac.assert_without_msg = Check.enabled; 101 102 assertAnalyzerWarnings(q{ 103 unittest { 104 assert(0, "foo bar"); 105 assert(0); // [warn]: %s 106 } 107 }}.format( 108 AssertWithoutMessageCheck.MESSAGE, 109 ), sac); 110 111 assertAnalyzerWarnings(q{ 112 unittest { 113 static assert(0, "foo bar"); 114 static assert(0); // [warn]: %s 115 } 116 }}.format( 117 AssertWithoutMessageCheck.MESSAGE, 118 ), sac); 119 120 // check for std.exception.enforce 121 assertAnalyzerWarnings(q{ 122 unittest { 123 enforce(0); // std.exception not imported yet - could be a user-defined symbol 124 import std.exception; 125 enforce(0, "foo bar"); 126 enforce(0); // [warn]: %s 127 } 128 }}.format( 129 AssertWithoutMessageCheck.MESSAGE, 130 ), sac); 131 132 // check for std.exception.enforce 133 assertAnalyzerWarnings(q{ 134 unittest { 135 import exception; 136 class C { 137 import std.exception; 138 } 139 enforce(0); // std.exception not imported yet - could be a user-defined symbol 140 struct S { 141 import std.exception; 142 } 143 enforce(0); // std.exception not imported yet - could be a user-defined symbol 144 if (false) { 145 import std.exception; 146 } 147 enforce(0); // std.exception not imported yet - could be a user-defined symbol 148 { 149 import std.exception; 150 } 151 enforce(0); // std.exception not imported yet - could be a user-defined symbol 152 } 153 }}, sac); 154 155 stderr.writeln("Unittest for AssertWithoutMessageCheck passed."); 156 }