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