D-Scanner/src/dscanner/analysis/rundmd.d

408 lines
14 KiB
D

module dscanner.analysis.rundmd;
import std.algorithm : any, canFind, filter, map;
import std.conv : to;
import dmd.astcodegen;
import dmd.dmodule : Module;
import dmd.frontend;
import dscanner.analysis.config : Check, StaticAnalysisConfig;
import dscanner.analysis.base : BaseAnalyzerDmd, MessageSet;
import dscanner.analysis.alias_syntax_check : AliasSyntaxCheck;
import dscanner.analysis.always_curly : AlwaysCurlyCheck;
import dscanner.analysis.asm_style : AsmStyleCheck;
import dscanner.analysis.assert_without_msg : AssertWithoutMessageCheck;
import dscanner.analysis.auto_function : AutoFunctionChecker;
import dscanner.analysis.auto_ref_assignment : AutoRefAssignmentCheck;
import dscanner.analysis.body_on_disabled_funcs : BodyOnDisabledFuncsCheck;
import dscanner.analysis.builtin_property_names : BuiltinPropertyNameCheck;
import dscanner.analysis.constructors : ConstructorCheck;
import dscanner.analysis.cyclomatic_complexity : CyclomaticComplexityCheck;
import dscanner.analysis.del : DeleteCheck;
import dscanner.analysis.enumarrayliteral : EnumArrayVisitor;
import dscanner.analysis.explicitly_annotated_unittests : ExplicitlyAnnotatedUnittestCheck;
import dscanner.analysis.final_attribute : FinalAttributeChecker;
import dscanner.analysis.has_public_example : HasPublicExampleCheck;
import dscanner.analysis.ifelsesame : IfElseSameCheck;
import dscanner.analysis.imports_sortedness : ImportSortednessCheck;
import dscanner.analysis.incorrect_infinite_range : IncorrectInfiniteRangeCheck;
import dscanner.analysis.label_var_same_name_check : LabelVarNameCheck;
import dscanner.analysis.lambda_return_check : LambdaReturnCheck;
import dscanner.analysis.length_subtraction : LengthSubtractionCheck;
import dscanner.analysis.line_length : LineLengthCheck;
import dscanner.analysis.local_imports : LocalImportCheck;
import dscanner.analysis.logic_precedence : LogicPrecedenceCheck;
import dscanner.analysis.numbers : NumberStyleCheck;
import dscanner.analysis.objectconst : ObjectConstCheck;
import dscanner.analysis.opequals_without_tohash : OpEqualsWithoutToHashCheck;
import dscanner.analysis.pokemon : PokemonExceptionCheck;
import dscanner.analysis.properly_documented_public_functions : ProperlyDocumentedPublicFunctions;
import dscanner.analysis.range : BackwardsRangeCheck;
import dscanner.analysis.redundant_attributes : RedundantAttributesCheck;
import dscanner.analysis.redundant_parens : RedundantParenCheck;
import dscanner.analysis.redundant_storage_class : RedundantStorageClassCheck;
import dscanner.analysis.static_if_else : StaticIfElse;
import dscanner.analysis.style : StyleChecker;
import dscanner.analysis.trust_too_much : TrustTooMuchCheck;
import dscanner.analysis.unmodified : UnmodifiedFinder;
import dscanner.analysis.unused_label : UnusedLabelCheck;
import dscanner.analysis.unused_parameter : UnusedParameterCheck;
import dscanner.analysis.unused_result : UnusedResultChecker;
import dscanner.analysis.unused_variable : UnusedVariableCheck;
import dscanner.analysis.useless_assert : UselessAssertCheck;
import dscanner.analysis.useless_initializer : UselessInitializerChecker;
import dscanner.analysis.vcall_in_ctor : VcallCtorChecker;
version (unittest)
enum ut = true;
else
enum ut = false;
Module parseDmdModule(string fileName, string sourceCode)
{
setupDmd();
auto code = sourceCode;
if (code[$ - 1] != '\0')
code ~= '\0';
auto dmdModule = dmd.frontend.parseModule(cast(const(char)[]) fileName, cast(const (char)[]) code);
return dmdModule.module_;
}
private void setupDmd()
{
import std.path : dirName;
import dmd.arraytypes : Strings;
import dmd.globals : global;
auto dmdParentDir = dirName(dirName(dirName(dirName(__FILE_FULL_PATH__))));
auto dmdDirPath = dmdParentDir ~ "/dmd" ~ "\0";
auto druntimeDirPath = dmdParentDir ~ "/dmd/druntime/src" ~ "\0";
global.params.useUnitTests = true;
global.path = Strings();
global.path.push(dmdDirPath.ptr);
global.path.push(druntimeDirPath.ptr);
global.errors = 0;
initDMD();
}
MessageSet analyzeDmd(string fileName, ASTCodegen.Module m, const char[] moduleName, const StaticAnalysisConfig config)
{
MessageSet set = new MessageSet;
BaseAnalyzerDmd[] visitors;
if (moduleName.shouldRunDmd!(ObjectConstCheck!ASTCodegen)(config))
visitors ~= new ObjectConstCheck!ASTCodegen(fileName);
if (moduleName.shouldRunDmd!(EnumArrayVisitor!ASTCodegen)(config))
visitors ~= new EnumArrayVisitor!ASTCodegen(fileName);
if (moduleName.shouldRunDmd!(DeleteCheck!ASTCodegen)(config))
visitors ~= new DeleteCheck!ASTCodegen(fileName);
if (moduleName.shouldRunDmd!(FinalAttributeChecker!ASTCodegen)(config))
visitors ~= new FinalAttributeChecker!ASTCodegen(fileName);
if (moduleName.shouldRunDmd!(ImportSortednessCheck!ASTCodegen)(config))
visitors ~= new ImportSortednessCheck!ASTCodegen(fileName);
if (moduleName.shouldRunDmd!(IncorrectInfiniteRangeCheck!ASTCodegen)(config))
visitors ~= new IncorrectInfiniteRangeCheck!ASTCodegen(fileName);
if (moduleName.shouldRunDmd!(RedundantAttributesCheck!ASTCodegen)(config))
visitors ~= new RedundantAttributesCheck!ASTCodegen(fileName);
if (moduleName.shouldRunDmd!(LengthSubtractionCheck!ASTCodegen)(config))
visitors ~= new LengthSubtractionCheck!ASTCodegen(fileName);
if (moduleName.shouldRunDmd!(AliasSyntaxCheck!ASTCodegen)(config))
visitors ~= new AliasSyntaxCheck!ASTCodegen(fileName);
if (moduleName.shouldRunDmd!(ExplicitlyAnnotatedUnittestCheck!ASTCodegen)(config))
visitors ~= new ExplicitlyAnnotatedUnittestCheck!ASTCodegen(fileName);
if (moduleName.shouldRunDmd!(ConstructorCheck!ASTCodegen)(config))
visitors ~= new ConstructorCheck!ASTCodegen(fileName);
if (moduleName.shouldRunDmd!(AssertWithoutMessageCheck!ASTCodegen)(config))
visitors ~= new AssertWithoutMessageCheck!ASTCodegen(
fileName,
config.assert_without_msg == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(LocalImportCheck!ASTCodegen)(config))
visitors ~= new LocalImportCheck!ASTCodegen(fileName);
if (moduleName.shouldRunDmd!(OpEqualsWithoutToHashCheck!ASTCodegen)(config))
visitors ~= new OpEqualsWithoutToHashCheck!ASTCodegen(
fileName,
config.opequals_tohash_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(TrustTooMuchCheck!ASTCodegen)(config))
visitors ~= new TrustTooMuchCheck!ASTCodegen(
fileName,
config.trust_too_much == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(AutoRefAssignmentCheck!ASTCodegen)(config))
visitors ~= new AutoRefAssignmentCheck!ASTCodegen(fileName);
if (moduleName.shouldRunDmd!(LogicPrecedenceCheck!ASTCodegen)(config))
visitors ~= new LogicPrecedenceCheck!ASTCodegen(
fileName,
config.logical_precedence_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(UnusedLabelCheck!ASTCodegen)(config))
visitors ~= new UnusedLabelCheck!ASTCodegen(
fileName,
config.unused_label_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(BuiltinPropertyNameCheck!ASTCodegen)(config))
visitors ~= new BuiltinPropertyNameCheck!ASTCodegen(fileName);
if (moduleName.shouldRunDmd!(PokemonExceptionCheck!ASTCodegen)(config))
visitors ~= new PokemonExceptionCheck!ASTCodegen(
fileName,
config.exception_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(BackwardsRangeCheck!ASTCodegen)(config))
visitors ~= new BackwardsRangeCheck!ASTCodegen(
fileName,
config.backwards_range_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(ProperlyDocumentedPublicFunctions!ASTCodegen)(config))
visitors ~= new ProperlyDocumentedPublicFunctions!ASTCodegen(
fileName,
config.properly_documented_public_functions == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(RedundantParenCheck!ASTCodegen)(config))
visitors ~= new RedundantParenCheck!ASTCodegen(
fileName,
config.redundant_parens_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(StaticIfElse!ASTCodegen)(config))
visitors ~= new StaticIfElse!ASTCodegen(
fileName,
config.static_if_else_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(UselessAssertCheck!ASTCodegen)(config))
visitors ~= new UselessAssertCheck!ASTCodegen(
fileName,
config.useless_assert_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(AsmStyleCheck!ASTCodegen)(config))
visitors ~= new AsmStyleCheck!ASTCodegen(
fileName,
config.asm_style_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(RedundantStorageClassCheck!ASTCodegen)(config))
visitors ~= new RedundantStorageClassCheck!ASTCodegen(
fileName,
config.redundant_storage_classes == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(NumberStyleCheck!ASTCodegen)(config))
visitors ~= new NumberStyleCheck!ASTCodegen(
fileName,
config.number_style_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(IfElseSameCheck!ASTCodegen)(config))
visitors ~= new IfElseSameCheck!ASTCodegen(
fileName,
config.if_else_same_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(CyclomaticComplexityCheck!ASTCodegen)(config))
visitors ~= new CyclomaticComplexityCheck!ASTCodegen(
fileName,
config.cyclomatic_complexity == Check.skipTests && !ut,
config.max_cyclomatic_complexity.to!int
);
if (moduleName.shouldRunDmd!(LabelVarNameCheck!ASTCodegen)(config))
visitors ~= new LabelVarNameCheck!ASTCodegen(
fileName,
config.label_var_same_name_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(LambdaReturnCheck!ASTCodegen)(config))
visitors ~= new LambdaReturnCheck!ASTCodegen(
fileName,
config.lambda_return_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(AlwaysCurlyCheck!ASTCodegen)(config))
visitors ~= new AlwaysCurlyCheck!ASTCodegen(
fileName,
config.always_curly_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(StyleChecker!ASTCodegen)(config))
visitors ~= new StyleChecker!ASTCodegen(
fileName,
config.style_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(AutoFunctionChecker!ASTCodegen)(config))
visitors ~= new AutoFunctionChecker!ASTCodegen(
fileName,
config.auto_function_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(UnusedParameterCheck!ASTCodegen)(config))
visitors ~= new UnusedParameterCheck!ASTCodegen(
fileName,
config.unused_parameter_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(UnusedVariableCheck!ASTCodegen)(config))
visitors ~= new UnusedVariableCheck!ASTCodegen(
fileName,
config.unused_variable_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(UnmodifiedFinder!ASTCodegen)(config))
visitors ~= new UnmodifiedFinder!ASTCodegen(
fileName,
config.could_be_immutable_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(BodyOnDisabledFuncsCheck!ASTCodegen)(config))
visitors ~= new BodyOnDisabledFuncsCheck!ASTCodegen(
fileName,
config.body_on_disabled_func_check == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(UselessInitializerChecker!ASTCodegen)(config))
visitors ~= new UselessInitializerChecker!ASTCodegen(
fileName,
config.useless_initializer == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(HasPublicExampleCheck!ASTCodegen)(config))
visitors ~= new HasPublicExampleCheck!ASTCodegen(
fileName,
config.has_public_example == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!LineLengthCheck(config))
visitors ~= new LineLengthCheck(
fileName,
config.long_line_check == Check.skipTests && !ut,
config.max_line_length
);
if (moduleName.shouldRunDmd!(UnusedResultChecker!ASTCodegen)(config))
visitors ~= new UnusedResultChecker!ASTCodegen(
fileName,
config.unused_result == Check.skipTests && !ut
);
if (moduleName.shouldRunDmd!(VcallCtorChecker!ASTCodegen)(config))
visitors ~= new VcallCtorChecker!ASTCodegen(
fileName,
config.vcall_in_ctor == Check.skipTests && !ut
);
foreach (visitor; visitors)
{
m.accept(visitor);
foreach (message; visitor.messages)
set.insert(message);
}
return set;
}
/**
* Checks whether a module is part of a user-specified include/exclude list.
*
* The user can specify a comma-separated list of filters, everyone needs to start with
* either a '+' (inclusion) or '-' (exclusion).
*
* If no includes are specified, all modules are included.
*/
private bool shouldRunDmd(check : BaseAnalyzerDmd)(const char[] moduleName, const ref StaticAnalysisConfig config)
{
enum string a = check.name;
if (mixin("config." ~ a) == Check.disabled)
return false;
// By default, run the check
if (!moduleName.length)
return true;
auto filters = mixin("config.filters." ~ a);
// Check if there are filters are defined
// filters starting with a comma are invalid
if (filters.length == 0 || filters[0].length == 0)
return true;
auto includers = filters.filter!(f => f[0] == '+').map!(f => f[1..$]);
auto excluders = filters.filter!(f => f[0] == '-').map!(f => f[1..$]);
// exclusion has preference over inclusion
if (!excluders.empty && excluders.any!(s => moduleName.canFind(s)))
return false;
if (!includers.empty)
return includers.any!(s => moduleName.canFind(s));
// by default: include all modules
return true;
}
///
unittest
{
bool test(string moduleName, string filters)
{
import std.array : split;
StaticAnalysisConfig config;
// it doesn't matter which check we test here
config.asm_style_check = Check.enabled;
// this is done automatically by inifiled
config.filters.asm_style_check = filters.split(",");
return moduleName.shouldRunDmd!(AsmStyleCheck!ASTCodegen)(config);
}
// test inclusion
assert(test("std.foo", "+std."));
// partial matches are ok
assert(test("std.foo", "+bar,+foo"));
// full as well
assert(test("std.foo", "+bar,+std.foo,+foo"));
// mismatch
assert(!test("std.foo", "+bar,+banana"));
// test exclusion
assert(!test("std.foo", "-std."));
assert(!test("std.foo", "-bar,-std.foo"));
assert(!test("std.foo", "-bar,-foo"));
// mismatch
assert(test("std.foo", "-bar,-banana"));
// test combination (exclusion has precedence)
assert(!test("std.foo", "+foo,-foo"));
assert(test("std.foo", "+foo,-bar"));
assert(test("std.bar.foo", "-barr,+bar"));
}