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.specifiedFunctionBody && fb.specifiedFunctionBody.blockStatement !is null)
46 			visit(fb.specifiedFunctionBody.blockStatement);
47 	}
48 
49 	override void visit(const BlockStatement bs)
50 	{
51 		if (bs.declarationsAndStatements is null)
52 			return;
53 		if (bs.declarationsAndStatements.declarationsAndStatements is null)
54 			return;
55 		if (bs.declarationsAndStatements.declarationsAndStatements.length != 1)
56 			return;
57 		visit(bs.declarationsAndStatements);
58 	}
59 
60 	override void visit(const ReturnStatement rs)
61 	{
62 		if (inStruct == 0 || line == size_t.max) // not within a struct yet
63 			return;
64 		if (!rs.expression || rs.expression.items.length != 1)
65 			return;
66 		UnaryExpression unary = cast(UnaryExpression) rs.expression.items[0];
67 		if (unary is null)
68 			return;
69 		if (unary.primaryExpression is null)
70 			return;
71 		if (unary.primaryExpression.primary != tok!"false")
72 			return;
73 		addErrorMessage(line, column, KEY, MESSAGE);
74 	}
75 
76 	override void visit(const Unittest u)
77 	{
78 	}
79 
80 private:
81 	uint inStruct;
82 	enum string KEY = "dscanner.suspicious.incorrect_infinite_range";
83 	enum string MESSAGE = "Use `enum bool empty = false;` to define an infinite range.";
84 	size_t line = size_t.max;
85 	size_t column;
86 }
87 
88 unittest
89 {
90 	import std.stdio : stderr;
91 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
92 	import std.format : format;
93 
94 	StaticAnalysisConfig sac = disabledConfig();
95 	sac.incorrect_infinite_range_check = Check.enabled;
96 	assertAnalyzerWarnings(q{struct InfiniteRange
97 {
98 	bool empty() // [warn]: %1$s
99 	{
100 		return false;
101 	}
102 
103 	bool stuff()
104 	{
105 		return false;
106 	}
107 
108 	unittest
109 	{
110 		return false;
111 	}
112 
113 	// https://issues.dlang.org/show_bug.cgi?id=18409
114 	struct Foo
115 	{
116 		~this() nothrow @nogc;
117 	}
118 }
119 
120 bool empty() { return false; }
121 class C { bool empty() { return false; } } // [warn]: %1$s
122 
123 }}
124 			.format(IncorrectInfiniteRangeCheck.MESSAGE), sac);
125 }
126 
127 // test for https://github.com/dlang-community/D-Scanner/issues/656
128 // unittests are skipped but D-Scanner sources are self checked
129 version(none) struct Foo
130 {
131 	void empty()
132 	{
133 		return;
134 	}
135 }
136 
137 unittest
138 {
139 	import std.stdio : stderr;
140 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
141 	import std.format : format;
142 
143 	StaticAnalysisConfig sac = disabledConfig();
144 	sac.incorrect_infinite_range_check = Check.enabled;
145 	assertAnalyzerWarnings(q{
146 		enum isAllZeroBits = ()
147 		{
148 			if (true)
149 				return true;
150 			else
151 				return false;
152 		}();
153 	}, sac);
154 	stderr.writeln("Unittest for IncorrectInfiniteRangeCheck passed.");
155 }