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 module analysis.if_statements;
6 
7 import std.d.ast;
8 import std.d.lexer;
9 import std.d.formatter;
10 import analysis.base;
11 
12 class IfStatementCheck : BaseAnalyzer
13 {
14 	alias visit = BaseAnalyzer.visit;
15 	this(string fileName)
16 	{
17 		import std.container.binaryheap : heapify;
18 
19 		super(fileName);
20 	}
21 
22 	override void visit(const IfStatement ifStatement)
23 	{
24 		import std.string : format;
25 		import std.algorithm : sort, countUntil;
26 		import std.array : appender;
27 
28 		++depth;
29 
30 		if (ifStatement.expression.items.length == 1
31 			&& (cast(AndAndExpression) ifStatement.expression.items[0]) is null)
32 		{
33 			redundancyCheck(ifStatement.expression, ifStatement.expression.line,
34 				ifStatement.expression.column);
35 		}
36 		inIfExpresson = true;
37 		ifStatement.expression.accept(this);
38 		inIfExpresson = false;
39 		ifStatement.thenStatement.accept(this);
40 		if (expressions.length)
41 			expressions = expressions[0 .. expressions.countUntil!(a => a.depth + 1 >= depth)];
42 		if (ifStatement.elseStatement)
43 			ifStatement.elseStatement.accept(this);
44 		--depth;
45 	}
46 
47 	override void visit(const AndAndExpression andAndExpression)
48 	{
49 		if (inIfExpresson)
50 		{
51 			redundancyCheck(andAndExpression, andAndExpression.line,
52 				andAndExpression.column);
53 			redundancyCheck(andAndExpression.left, andAndExpression.line,
54 				andAndExpression.column);
55 			redundancyCheck(andAndExpression.right, andAndExpression.line,
56 				andAndExpression.column);
57 		}
58 		andAndExpression.accept(this);
59 	}
60 
61 	override void visit(const OrOrExpression orOrExpression)
62 	{
63 		// intentionally does nothing
64 	}
65 
66 private:
67 	invariant
68 	{
69 		assert(depth >= 0);
70 	}
71 
72 	void redundancyCheck(const ExpressionNode expression, size_t line,
73 		size_t column)
74 	{
75 		import std.string : format;
76 		import std.array : appender;
77 		import std.algorithm : sort;
78 
79 		if (expression is null)
80 			return;
81 		auto app = appender!string();
82 		std.d.formatter.format(app, expression);
83 		immutable size_t prevLocation = alreadyChecked(app.data, line, column);
84 		if (prevLocation != size_t.max)
85 		{
86 			addErrorMessage(line, column, KEY,
87 				"Expression %s is true: already checked on line %d.".format(
88 				expressions[prevLocation].formatted,
89 				expressions[prevLocation].line));
90 		}
91 		else
92 		{
93 			expressions ~= ExpressionInfo(app.data, line, column, depth);
94 			sort(expressions);
95 		}
96 	}
97 
98 	size_t alreadyChecked(string expressionText, size_t line, size_t column)
99 	{
100 		foreach (i, ref info; expressions)
101 		{
102 			if (info.line == line && info.column == column)
103 				continue;
104 			if (info.formatted == expressionText)
105 				return i;
106 		}
107 		return size_t.max;
108 	}
109 
110 	bool inIfExpresson;
111 	int depth;
112 	ExpressionInfo[] expressions;
113 	private enum KEY = "dscanner.if_statement";
114 }
115 
116 private struct ExpressionInfo
117 {
118 	int opCmp(ref const ExpressionInfo other) const nothrow
119 	{
120 		if (line < other.line || (line == other.line && column < other.column))
121 			return 1;
122 		if (line == other.line && column == other.column)
123 			return 0;
124 		return -1;
125 	}
126 
127 	string formatted;
128 	size_t line;
129 	size_t column;
130 	int depth;
131 }