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.auto_ref_assignment; 7 8 import dparse.lexer; 9 import dparse.ast; 10 import dscanner.analysis.base; 11 12 /** 13 * Checks for assignment to auto-ref function parameters. 14 */ 15 final class AutoRefAssignmentCheck : BaseAnalyzer 16 { 17 mixin AnalyzerInfo!"auto_ref_assignment_check"; 18 19 /// 20 this(string fileName, bool skipTests = false) 21 { 22 super(fileName, null, skipTests); 23 } 24 25 override void visit(const Module m) 26 { 27 pushScope(); 28 m.accept(this); 29 popScope(); 30 } 31 32 override void visit(const FunctionDeclaration func) 33 { 34 if (func.parameters is null || func.parameters.parameters.length == 0) 35 return; 36 pushScope(); 37 scope (exit) 38 popScope(); 39 func.accept(this); 40 } 41 42 override void visit(const Parameter param) 43 { 44 import std.algorithm.searching : canFind; 45 46 immutable bool isAuto = param.parameterAttributes.canFind!(a => a.idType == cast(ubyte) tok!"auto"); 47 immutable bool isRef = param.parameterAttributes.canFind!(a => a.idType == cast(ubyte) tok!"ref"); 48 if (!isAuto || !isRef) 49 return; 50 addSymbol(param.name.text); 51 } 52 53 override void visit(const AssignExpression assign) 54 { 55 if (assign.operator == tok!"" || scopes.length == 0) 56 return; 57 interest++; 58 assign.ternaryExpression.accept(this); 59 interest--; 60 } 61 62 override void visit(const IdentifierOrTemplateInstance ioti) 63 { 64 import std.algorithm.searching : canFind; 65 66 if (ioti.identifier == tok!"" || interest <= 0) 67 return; 68 if (scopes[$ - 1].canFind(ioti.identifier.text)) 69 addErrorMessage(ioti.identifier.line, ioti.identifier.column, KEY, MESSAGE); 70 } 71 72 override void visit(const IdentifierChain ic) 73 { 74 import std.algorithm.searching : canFind; 75 76 if (ic.identifiers.length == 0 || interest <= 0) 77 return; 78 if (scopes[$ - 1].canFind(ic.identifiers[0].text)) 79 addErrorMessage(ic.identifiers[0].line, ic.identifiers[0].column, KEY, MESSAGE); 80 } 81 82 alias visit = BaseAnalyzer.visit; 83 84 private: 85 86 enum string MESSAGE = "Assignment to auto-ref function parameter."; 87 enum string KEY = "dscanner.suspicious.auto_ref_assignment"; 88 89 invariant 90 { 91 assert(interest >= 0); 92 } 93 94 int interest; 95 96 void addSymbol(string symbolName) 97 { 98 scopes[$ - 1] ~= symbolName; 99 } 100 101 void pushScope() 102 { 103 scopes.length++; 104 } 105 106 void popScope() 107 { 108 scopes = scopes[0 .. $ - 1]; 109 } 110 111 string[][] scopes; 112 } 113 114 unittest 115 { 116 import std.stdio : stderr; 117 import std.format : format; 118 import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; 119 import dscanner.analysis.helpers : assertAnalyzerWarnings; 120 121 StaticAnalysisConfig sac = disabledConfig(); 122 sac.auto_ref_assignment_check = Check.enabled; 123 assertAnalyzerWarnings(q{ 124 int doStuff(T)(auto ref int a) 125 { 126 a = 10; // [warn]: %s 127 } 128 129 int doStuff(T)(ref int a) 130 { 131 a = 10; 132 } 133 }}.format(AutoRefAssignmentCheck.MESSAGE), sac); 134 stderr.writeln("Unittest for AutoRefAssignmentCheck passed."); 135 }