diff --git a/src/analysis/auto_ref_assignment.d b/src/analysis/auto_ref_assignment.d new file mode 100644 index 0000000..7ff6e8b --- /dev/null +++ b/src/analysis/auto_ref_assignment.d @@ -0,0 +1,128 @@ +// Copyright Brian Schott (Hackerpilot) 2015. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +module analysis.auto_ref_assignment; + +import dparse.lexer; +import dparse.ast; +import analysis.base; + +/** + * Checks for assignment to auto-ref function parameters. + */ +class AutoRefAssignmentCheck : BaseAnalyzer +{ + /// + this(string fileName) + { + super(fileName, null); + } + + override void visit(const FunctionDeclaration func) + { + if (func.parameters is null || func.parameters.parameters.length == 0) + return; + pushScope(); + scope (exit) + popScope(); + func.accept(this); + } + + override void visit(const Parameter param) + { + import std.algorithm.searching : canFind; + + immutable bool isAuto = param.parameterAttributes.canFind(cast(ubyte) tok!"auto"); + immutable bool isRef = param.parameterAttributes.canFind(cast(ubyte) tok!"ref"); + if (!isAuto || !isRef) + return; + addSymbol(param.name.text); + } + + override void visit(const AssignExpression assign) + { + if (assign.operator == tok!"" || scopes.length == 0) + return; + interest++; + assign.ternaryExpression.accept(this); + interest--; + } + + override void visit(const IdentifierOrTemplateInstance ioti) + { + import std.algorithm.searching : canFind; + + if (ioti.identifier == tok!"" || interest <= 0) + return; + if (scopes[$ - 1].canFind(ioti.identifier.text)) + addErrorMessage(ioti.identifier.line, ioti.identifier.column, KEY, + "Assignment to auto-ref function parameter"); + } + + override void visit(const IdentifierChain ic) + { + import std.algorithm.searching : canFind; + + if (ic.identifiers.length == 0 || interest <= 0) + return; + if (scopes[$ - 1].canFind(ic.identifiers[0].text)) + addErrorMessage(ic.identifiers[0].line, ic.identifiers[0].column, KEY, + MESSAGE); + } + + alias visit = BaseAnalyzer.visit; + +private: + + enum string MESSAGE = "Assignment to auto-ref function parameter"; + enum string KEY = "dscanner.suspicious.auto_ref_assignment"; + + invariant + { + assert(interest >= 0); + } + + int interest; + + void addSymbol(string symbolName) + { + scopes[$ - 1] ~= symbolName; + } + + void pushScope() + { + scopes.length++; + } + + void popScope() + { + scopes = scopes[0 .. $ - 1]; + } + + string[][] scopes; +} + +unittest +{ + import std.stdio : stderr; + import std.format : format; + import analysis.config : StaticAnalysisConfig; + import analysis.helpers : assertAnalyzerWarnings; + + StaticAnalysisConfig sac; + sac.auto_ref_assignment_check = true; + assertAnalyzerWarnings(q{ + int doStuff(T)(auto ref int a) + { + a = 10; // [warn]: %s + } + + int doStuff(T)(ref int a) + { + a = 10; + } + }c.format(AutoRefAssignmentCheck.MESSAGE), sac); + stderr.writeln("Unittest for AutoRefAssignmentCheck passed."); +} diff --git a/src/analysis/config.d b/src/analysis/config.d index 48068f0..01fd1ae 100644 --- a/src/analysis/config.d +++ b/src/analysis/config.d @@ -101,4 +101,7 @@ struct StaticAnalysisConfig @INI("Checks for lines longer than 120 characters") bool long_line_check; + + @INI("Checks for assignment to auto-ref function parameters") + bool auto_ref_assignment_check; } diff --git a/src/analysis/run.d b/src/analysis/run.d index bc69b3a..79c7ce5 100644 --- a/src/analysis/run.d +++ b/src/analysis/run.d @@ -52,6 +52,7 @@ import analysis.redundant_parens; import analysis.mismatched_args; import analysis.label_var_same_name_check; import analysis.line_length; +import analysis.auto_ref_assignment; import dsymbol.string_interning : internString; import dsymbol.scope_; @@ -260,6 +261,8 @@ MessageSet analyze(string fileName, const Module m, checks ~= new UnusedVariableCheck(fileName, moduleScope); if (analysisConfig.long_line_check) checks ~= new LineLengthCheck(fileName, tokens); + if (analysisConfig.auto_ref_assignment_check) + checks ~= new AutoRefAssignmentCheck(fileName); version (none) if (analysisConfig.redundant_if_check) checks ~= new IfStatementCheck(fileName, moduleScope);