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 }