275 lines
6.8 KiB
D
275 lines
6.8 KiB
D
// Copyright Basile Burg 2016.
|
|
// 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 dscanner.analysis.auto_function;
|
|
|
|
import dscanner.analysis.base;
|
|
import dscanner.analysis.helpers;
|
|
import dparse.ast;
|
|
import dparse.lexer;
|
|
|
|
import std.stdio;
|
|
import std.algorithm : map, filter;
|
|
|
|
/**
|
|
* Checks for auto functions without return statement.
|
|
*
|
|
* Auto function without return statement can be an omission and are not
|
|
* detected by the compiler. However sometimes they can be used as a trick
|
|
* to infer attributes.
|
|
*/
|
|
final class AutoFunctionChecker : BaseAnalyzer
|
|
{
|
|
|
|
private:
|
|
|
|
enum string KEY = "dscanner.suspicious.missing_return";
|
|
enum string MESSAGE = "Auto function without return statement, prefer replacing auto with void";
|
|
enum string MESSAGE_INSERT = "Auto function without return statement, prefer inserting void to be explicit";
|
|
|
|
bool[] _returns;
|
|
size_t _mixinDepth;
|
|
string[] _literalWithReturn;
|
|
|
|
public:
|
|
|
|
alias visit = BaseAnalyzer.visit;
|
|
|
|
mixin AnalyzerInfo!"auto_function_check";
|
|
|
|
///
|
|
this(string fileName, bool skipTests = false)
|
|
{
|
|
super(fileName, null, skipTests);
|
|
}
|
|
|
|
package static const(Token)[] findAutoReturnType(const(FunctionDeclaration) decl)
|
|
{
|
|
auto autoFunTokens = decl.storageClasses
|
|
.map!(a => a.token.type == tok!"auto"
|
|
? [a.token]
|
|
: a.atAttribute
|
|
? a.atAttribute.tokens
|
|
: null)
|
|
.filter!(a => a.length > 0);
|
|
return autoFunTokens.empty ? null : autoFunTokens.front;
|
|
}
|
|
|
|
override void visit(const(FunctionDeclaration) decl)
|
|
{
|
|
_returns.length += 1;
|
|
scope(exit) _returns.length -= 1;
|
|
_returns[$-1] = false;
|
|
|
|
auto autoTokens = findAutoReturnType(decl);
|
|
bool isAtAttribute = autoTokens.length > 1;
|
|
|
|
decl.accept(this);
|
|
|
|
if (decl.functionBody.specifiedFunctionBody && autoTokens.length && !_returns[$-1])
|
|
{
|
|
if (isAtAttribute)
|
|
{
|
|
// highlight on the whitespace between attribute and function name
|
|
auto tok = autoTokens[$ - 1];
|
|
auto whitespace = tok.column + (tok.text.length ? tok.text.length : str(tok.type).length);
|
|
auto whitespaceIndex = tok.index + (tok.text.length ? tok.text.length : str(tok.type).length);
|
|
addErrorMessage([whitespaceIndex, whitespaceIndex + 1], tok.line, [whitespace, whitespace + 1], KEY, MESSAGE_INSERT,
|
|
[AutoFix.insertionAt(whitespaceIndex + 1, "void ")]);
|
|
}
|
|
else
|
|
addErrorMessage(autoTokens, KEY, MESSAGE,
|
|
[AutoFix.replacement(autoTokens[0], "void")]);
|
|
}
|
|
}
|
|
|
|
override void visit(const(ReturnStatement) rst)
|
|
{
|
|
if (_returns.length)
|
|
_returns[$-1] = true;
|
|
rst.accept(this);
|
|
}
|
|
|
|
override void visit(const(AssertArguments) exp)
|
|
{
|
|
exp.accept(this);
|
|
if (_returns.length)
|
|
{
|
|
const UnaryExpression u = cast(UnaryExpression) exp.assertion;
|
|
if (!u)
|
|
return;
|
|
const PrimaryExpression p = u.primaryExpression;
|
|
if (!p)
|
|
return;
|
|
|
|
immutable token = p.primary;
|
|
if (token.type == tok!"false")
|
|
_returns[$-1] = true;
|
|
else if (token.text == "0")
|
|
_returns[$-1] = true;
|
|
}
|
|
}
|
|
|
|
override void visit(const(MixinExpression) mix)
|
|
{
|
|
++_mixinDepth;
|
|
mix.accept(this);
|
|
--_mixinDepth;
|
|
}
|
|
|
|
override void visit(const(PrimaryExpression) exp)
|
|
{
|
|
exp.accept(this);
|
|
|
|
import std.algorithm.searching : canFind;
|
|
|
|
if (_returns.length && _mixinDepth)
|
|
{
|
|
if (findReturnInLiteral(exp.primary.text))
|
|
_returns[$-1] = true;
|
|
else if (exp.identifierOrTemplateInstance &&
|
|
_literalWithReturn.canFind(exp.identifierOrTemplateInstance.identifier.text))
|
|
_returns[$-1] = true;
|
|
}
|
|
}
|
|
|
|
private bool findReturnInLiteral(const(string) value)
|
|
{
|
|
import std.algorithm.searching : find;
|
|
import std.range : empty;
|
|
|
|
return value == "return" || !value.find("return ").empty;
|
|
}
|
|
|
|
private bool stringliteralHasReturn(const(NonVoidInitializer) nvi)
|
|
{
|
|
bool result;
|
|
if (!nvi.assignExpression || (cast(UnaryExpression) nvi.assignExpression) is null)
|
|
return result;
|
|
|
|
const(UnaryExpression) u = cast(UnaryExpression) nvi.assignExpression;
|
|
if (u.primaryExpression &&
|
|
u.primaryExpression.primary.type.isStringLiteral &&
|
|
findReturnInLiteral(u.primaryExpression.primary.text))
|
|
result = true;
|
|
|
|
return result;
|
|
}
|
|
|
|
override void visit(const(AutoDeclaration) decl)
|
|
{
|
|
decl.accept(this);
|
|
|
|
foreach(const(AutoDeclarationPart) p; decl.parts)
|
|
if (p.initializer &&
|
|
p.initializer.nonVoidInitializer &&
|
|
stringliteralHasReturn(p.initializer.nonVoidInitializer))
|
|
_literalWithReturn ~= p.identifier.text.idup;
|
|
}
|
|
|
|
override void visit(const(VariableDeclaration) decl)
|
|
{
|
|
decl.accept(this);
|
|
|
|
foreach(const(Declarator) d; decl.declarators)
|
|
if (d.initializer &&
|
|
d.initializer.nonVoidInitializer &&
|
|
stringliteralHasReturn(d.initializer.nonVoidInitializer))
|
|
_literalWithReturn ~= d.name.text.idup;
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.stdio : stderr;
|
|
import std.format : format;
|
|
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
|
|
import dscanner.analysis.helpers : assertAnalyzerWarnings;
|
|
|
|
StaticAnalysisConfig sac = disabledConfig();
|
|
sac.auto_function_check = Check.enabled;
|
|
assertAnalyzerWarnings(q{
|
|
auto ref doStuff(){} /+
|
|
^^^^ [warn]: %s +/
|
|
auto doStuff(){} /+
|
|
^^^^ [warn]: %s +/
|
|
int doStuff(){auto doStuff(){}} /+
|
|
^^^^ [warn]: %s +/
|
|
auto doStuff(){return 0;}
|
|
int doStuff(){/*error but not the aim*/}
|
|
}c.format(
|
|
AutoFunctionChecker.MESSAGE,
|
|
AutoFunctionChecker.MESSAGE,
|
|
AutoFunctionChecker.MESSAGE,
|
|
), sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
auto doStuff(){assert(true);} /+
|
|
^^^^ [warn]: %s +/
|
|
auto doStuff(){assert(false);}
|
|
}c.format(
|
|
AutoFunctionChecker.MESSAGE,
|
|
), sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
auto doStuff(){assert(1);} /+
|
|
^^^^ [warn]: %s +/
|
|
auto doStuff(){assert(0);}
|
|
}c.format(
|
|
AutoFunctionChecker.MESSAGE,
|
|
), sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
auto doStuff(){mixin("0+0");} /+
|
|
^^^^ [warn]: %s +/
|
|
auto doStuff(){mixin("return 0;");}
|
|
}c.format(
|
|
AutoFunctionChecker.MESSAGE,
|
|
), sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
auto doStuff(){mixin("0+0");} /+
|
|
^^^^ [warn]: %s +/
|
|
auto doStuff(){mixin("static if (true)" ~ " return " ~ 0.stringof ~ ";");}
|
|
}c.format(
|
|
AutoFunctionChecker.MESSAGE,
|
|
), sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
auto doStuff(){} /+
|
|
^^^^ [warn]: %s +/
|
|
extern(C) auto doStuff();
|
|
}c.format(
|
|
AutoFunctionChecker.MESSAGE,
|
|
), sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
auto doStuff(){} /+
|
|
^^^^ [warn]: %s +/
|
|
@disable auto doStuff();
|
|
}c.format(
|
|
AutoFunctionChecker.MESSAGE,
|
|
), sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
@property doStuff(){} /+
|
|
^ [warn]: %s +/
|
|
@safe doStuff(){} /+
|
|
^ [warn]: %s +/
|
|
@disable doStuff();
|
|
@safe void doStuff();
|
|
}c.format(
|
|
AutoFunctionChecker.MESSAGE_INSERT,
|
|
AutoFunctionChecker.MESSAGE_INSERT,
|
|
), sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
enum _genSave = "return true;";
|
|
auto doStuff(){ mixin(_genSave);}
|
|
}, sac);
|
|
|
|
stderr.writeln("Unittest for AutoFunctionChecker passed.");
|
|
}
|