1 //          Copyright Brian Schott (Hackerpilot) 2015.
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 
6 module dscanner.analysis.incorrect_infinite_range;
7 
8 import dscanner.analysis.base;
9 import dscanner.analysis.helpers;
10 import dparse.ast;
11 import dparse.lexer;
12 
13 /**
14  * Checks for incorrect infinite range definitions
15  */
16 final class IncorrectInfiniteRangeCheck : BaseAnalyzer
17 {
18 	alias visit = BaseAnalyzer.visit;
19 
20 	///
21 	this(string fileName, bool skipTests = false)
22 	{
23 		super(fileName, null, skipTests);
24 	}
25 
26 	override void visit(const StructBody structBody)
27 	{
28 		inStruct++;
29 		structBody.accept(this);
30 		inStruct--;
31 	}
32 
33 	override void visit(const FunctionDeclaration fd)
34 	{
35 		if (inStruct > 0 && fd.name.text == "empty")
36 		{
37 			line = fd.name.line;
38 			column = fd.name.column;
39 			fd.accept(this);
40 		}
41 	}
42 
43 	override void visit(const FunctionBody fb)
44 	{
45 		if (fb.bodyStatement !is null)
46 			visit(fb.bodyStatement.blockStatement);
47 		else
48 			if (fb.blockStatement !is null)
49 				visit(fb.blockStatement);
50 	}
51 
52 	override void visit(const BlockStatement bs)
53 	{
54 		if (bs.declarationsAndStatements is null)
55 			return;
56 		if (bs.declarationsAndStatements.declarationsAndStatements is null)
57 			return;
58 		if (bs.declarationsAndStatements.declarationsAndStatements.length != 1)
59 			return;
60 		visit(bs.declarationsAndStatements);
61 	}
62 
63 	override void visit(const ReturnStatement rs)
64 	{
65 		if (inStruct == 0 || line == size_t.max) // not within a struct yet
66 			return;
67 		if (!rs.expression || rs.expression.items.length != 1)
68 			return;
69 		UnaryExpression unary = cast(UnaryExpression) rs.expression.items[0];
70 		if (unary is null)
71 			return;
72 		if (unary.primaryExpression is null)
73 			return;
74 		if (unary.primaryExpression.primary != tok!"false")
75 			return;
76 		addErrorMessage(line, column, KEY, MESSAGE);
77 	}
78 
79 	override void visit(const Unittest u)
80 	{
81 	}
82 
83 private:
84 	uint inStruct;
85 	enum string KEY = "dscanner.suspicious.incorrect_infinite_range";
86 	enum string MESSAGE = "Use `enum bool empty = false;` to define an infinite range.";
87 	size_t line = size_t.max;
88 	size_t column;
89 }
90 
91 unittest
92 {
93 	import std.stdio : stderr;
94 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
95 	import std.format : format;
96 
97 	StaticAnalysisConfig sac = disabledConfig();
98 	sac.incorrect_infinite_range_check = Check.enabled;
99 	assertAnalyzerWarnings(q{struct InfiniteRange
100 {
101 	bool empty() // [warn]: %1$s
102 	{
103 		return false;
104 	}
105 
106 	bool stuff()
107 	{
108 		return false;
109 	}
110 
111 	unittest
112 	{
113 		return false;
114 	}
115 
116 	// https://issues.dlang.org/show_bug.cgi?id=18409
117 	struct Foo
118 	{
119 		~this() nothrow @nogc;
120 	}
121 }
122 
123 bool empty() { return false; }
124 class C { bool empty() { return false; } } // [warn]: %1$s
125 
126 }}
127 			.format(IncorrectInfiniteRangeCheck.MESSAGE), sac);
128 }
129 
130 // test for https://github.com/dlang-community/D-Scanner/issues/656
131 // unittests are skipped but D-Scanner sources are self checked
132 version(none) struct Foo
133 {
134 	void empty()
135 	{
136 		return;
137 	}
138 }
139 
140 unittest
141 {
142 	import std.stdio : stderr;
143 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
144 	import std.format : format;
145 
146 	StaticAnalysisConfig sac = disabledConfig();
147 	sac.incorrect_infinite_range_check = Check.enabled;
148 	assertAnalyzerWarnings(q{
149 		enum isAllZeroBits = ()
150 		{
151 			if (true)
152 				return true;
153 			else
154 				return false;
155 		}();
156 	}, sac);
157 	stderr.writeln("Unittest for IncorrectInfiniteRangeCheck passed.");
158 }