diff --git a/build.bat b/build.bat index 3b23f31..999c861 100644 --- a/build.bat +++ b/build.bat @@ -18,8 +18,8 @@ if %githashsize% == 0 ( move /y bin\githash_.txt bin\githash.txt ) -set DFLAGS=-O -release -version=StdLoggerDisableWarning -version=CallbackAPI -version=DMDLIB -Jbin -Jdmd %MFLAGS% -set TESTFLAGS=-g -w -version=StdLoggerDisableWarning -version=CallbackAPI -version=DMDLIB -Jbin -Jdmd +set DFLAGS=-O -release -version=StdLoggerDisableWarning -version=CallbackAPI -version=DMDLIB -version=MARS -Jbin -Jdmd %MFLAGS% +set TESTFLAGS=-g -w -version=StdLoggerDisableWarning -version=CallbackAPI -version=DMDLIB -version=MARS -Jbin -Jdmd set CORE= set LIBDPARSE= set STD= diff --git a/dub.selections.json b/dub.selections.json index 5e58ee4..dd3c592 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -2,6 +2,7 @@ "fileVersion": 1, "versions": { "dcd": "0.16.0-beta.2", + "dmd": "~master", "dsymbol": "0.13.0", "emsi_containers": "0.9.0", "inifiled": "1.3.3", diff --git a/makefile b/makefile index c09542f..e35818b 100644 --- a/makefile +++ b/makefile @@ -71,11 +71,11 @@ INCLUDE_PATHS = \ -Ilibddoc/common/source \ -Idmd/src -DMD_VERSIONS = -version=StdLoggerDisableWarning -version=CallbackAPI -version=DMDLIB +DMD_VERSIONS = -version=StdLoggerDisableWarning -version=CallbackAPI -version=DMDLIB -version=MARS DMD_DEBUG_VERSIONS = -version=dparse_verbose -LDC_VERSIONS = -d-version=StdLoggerDisableWarning -d-version=CallbackAPI -d-version=DMDLIB +LDC_VERSIONS = -d-version=StdLoggerDisableWarning -d-version=CallbackAPI -d-version=DMDLIB -version=MARS LDC_DEBUG_VERSIONS = -d-version=dparse_verbose -GDC_VERSIONS = -fversion=StdLoggerDisableWarning -fversion=CallbackAPI -fversion=DMDLIB +GDC_VERSIONS = -fversion=StdLoggerDisableWarning -fversion=CallbackAPI -fversion=DMDLIB -version=MARS GDC_DEBUG_VERSIONS = -fversion=dparse_verbose DC_FLAGS += -Jbin -Jdmd diff --git a/src/dscanner/analysis/base.d b/src/dscanner/analysis/base.d index a9baca0..b4a8664 100644 --- a/src/dscanner/analysis/base.d +++ b/src/dscanner/analysis/base.d @@ -9,6 +9,9 @@ import std.container; import std.meta : AliasSeq; import std.string; import std.sumtype; +import dmd.transitivevisitor; +import core.stdc.string; +import std.conv : to; /// struct AutoFix @@ -361,11 +364,15 @@ enum comparitor = q{ a.startLine < b.startLine || (a.startLine == b.startLine && alias MessageSet = RedBlackTree!(Message, comparitor, true); +/** + * Should be present in all visitors to specify the name of the check + * done by a patricular visitor + */ mixin template AnalyzerInfo(string checkName) { enum string name = checkName; - override protected string getName() + extern(D) override protected string getName() { return name; } @@ -897,3 +904,47 @@ unittest auto isOldScope = void; }); } + +/** + * Visitor that implements the AST traversal logic. + * Supports collecting error messages + */ +extern(C++) class BaseAnalyzerDmd(AST) : ParseTimeTransitiveVisitor!AST +{ + alias visit = ParseTimeTransitiveVisitor!AST.visit; + + extern(D) this(string fileName) + { + this.fileName = fileName; + _messages = new MessageSet; + } + + /** + * Ensures that template AnalyzerInfo is instantiated in all classes + * deriving from this class + */ + extern(D) protected string getName() + { + assert(0); + } + + extern(D) Message[] messages() + { + return _messages[].array; + } + + +protected: + + extern(D) void addErrorMessage(size_t line, size_t column, string key, string message) + { + _messages.insert(Message(fileName, line, column, key, message, getName())); + } + + /** + * The file name + */ + extern(D) string fileName; + + extern(D) MessageSet _messages; +} diff --git a/src/dscanner/analysis/enumarrayliteral.d b/src/dscanner/analysis/enumarrayliteral.d index 96fcc0c..fce6ab7 100644 --- a/src/dscanner/analysis/enumarrayliteral.d +++ b/src/dscanner/analysis/enumarrayliteral.d @@ -5,81 +5,33 @@ module dscanner.analysis.enumarrayliteral; -import dparse.ast; -import dparse.lexer; import dscanner.analysis.base; -import std.algorithm : find, map; -import dsymbol.scope_ : Scope; -void doNothing(string, size_t, size_t, string, bool) +extern(C++) class EnumArrayVisitor(AST) : BaseAnalyzerDmd { -} - -final class EnumArrayLiteralCheck : BaseAnalyzer -{ - alias visit = BaseAnalyzer.visit; - mixin AnalyzerInfo!"enum_array_literal_check"; + alias visit = BaseAnalyzerDmd.visit; - this(BaseAnalyzerArguments args) + extern(D) this(string fileName) { - super(args); + super(fileName); } - bool looking; + override void visit(AST.VarDeclaration vd) + { + import dmd.astenums : STC, InitKind; + import std.string : toStringz; - mixin visitTemplate!ClassDeclaration; - mixin visitTemplate!InterfaceDeclaration; - mixin visitTemplate!UnionDeclaration; - mixin visitTemplate!StructDeclaration; - - override void visit(const AutoDeclaration autoDec) - { - auto enumToken = autoDec.storageClasses.find!(a => a.token == tok!"enum"); - if (enumToken.length) - { - foreach (part; autoDec.parts) - { - if (part.initializer is null) - continue; - if (part.initializer.nonVoidInitializer is null) - continue; - if (part.initializer.nonVoidInitializer.arrayInitializer is null) - continue; - addErrorMessage(part.initializer.nonVoidInitializer, - KEY, - "This enum may lead to unnecessary allocation at run-time." + string message = "This enum may lead to unnecessary allocation at run-time." ~ " Use 'static immutable " - ~ part.identifier.text ~ " = [ ...' instead.", - [ - AutoFix.replacement(enumToken[0].token, "static immutable") - ]); - } - } - autoDec.accept(this); + ~ vd.ident.toString().idup() ~ " = [ ...' instead."; + + if (!vd.type && vd._init.kind == InitKind.array && vd.storage_class & STC.manifest) + addErrorMessage(cast(ulong) vd.loc.linnum, + cast(ulong) vd.loc.charnum, KEY, + message); + super.visit(vd); } - private enum string KEY = "dscanner.performance.enum_array_literal"; -} - -unittest -{ - import dscanner.analysis.config : Check, disabledConfig, StaticAnalysisConfig; - import dscanner.analysis.helpers : assertAnalyzerWarnings, assertAutoFix; - import std.stdio : stderr; - - StaticAnalysisConfig sac = disabledConfig(); - sac.enum_array_literal_check = Check.enabled; - assertAnalyzerWarnings(q{ - enum x = [1, 2, 3]; /+ - ^^^^^^^^^ [warn]: This enum may lead to unnecessary allocation at run-time. Use 'static immutable x = [ ...' instead. +/ - }c, sac); - - assertAutoFix(q{ - enum x = [1, 2, 3]; // fix - }c, q{ - static immutable x = [1, 2, 3]; // fix - }c, sac); - - stderr.writeln("Unittest for EnumArrayLiteralCheck passed."); -} + private enum KEY = "dscanner.performance.enum_array_literal"; +} \ No newline at end of file diff --git a/src/dscanner/analysis/run.d b/src/dscanner/analysis/run.d index 7965135..d253a37 100644 --- a/src/dscanner/analysis/run.d +++ b/src/dscanner/analysis/run.d @@ -95,6 +95,8 @@ import dsymbol.modulecache : ModuleCache; import dscanner.utils; import dscanner.reports : DScannerJsonReporter, SonarQubeGenericIssueDataReporter; +import dmd.astbase : ASTBase; + bool first = true; private alias ASTAllocator = CAllocatorImpl!( @@ -380,10 +382,32 @@ void generateSonarQubeGenericIssueDataReport(string[] fileNames, const StaticAna bool analyze(string[] fileNames, const StaticAnalysisConfig config, string errorFormat, ref StringCache cache, ref ModuleCache moduleCache, bool staticAnalyze = true) { + import dmd.parse : Parser; + import dmd.astbase : ASTBase; + import dmd.id : Id; + import dmd.globals : global; + import dmd.identifier : Identifier; + import std.string : toStringz; + + Id.initialize(); + global._init(); + global.params.useUnitTests = true; + ASTBase.Type._init(); + + bool hasErrors; foreach (fileName; fileNames) { auto code = readFile(fileName); + + auto id = Identifier.idPool(fileName); + auto ast_m = new ASTBase.Module(fileName.toStringz, id, false, false); + auto input = cast(char[]) code; + input ~= '\0'; + scope p = new Parser!ASTBase(ast_m, input, false); + p.nextToken(); + ast_m.members = p.parseModule(); + // Skip files that could not be read and continue with the rest if (code.length == 0) continue; @@ -397,6 +421,11 @@ bool analyze(string[] fileNames, const StaticAnalysisConfig config, string error if (errorCount > 0 || (staticAnalyze && warningCount > 0)) hasErrors = true; MessageSet results = analyze(fileName, m, config, moduleCache, tokens, staticAnalyze); + MessageSet resultsDmd = analyzeDmd(fileName, ast_m); + foreach(result; resultsDmd[]) + { + results.insert(result); + } if (results is null) continue; foreach (result; results[]) @@ -787,10 +816,6 @@ private BaseAnalyzer[] getAnalyzersForModuleAndConfig(string fileName, checks ~= new DuplicateAttributeCheck(args.setSkipTests( analysisConfig.duplicate_attribute == Check.skipTests && !ut)); - if (moduleName.shouldRun!EnumArrayLiteralCheck(analysisConfig)) - checks ~= new EnumArrayLiteralCheck(args.setSkipTests( - analysisConfig.enum_array_literal_check == Check.skipTests && !ut)); - if (moduleName.shouldRun!PokemonExceptionCheck(analysisConfig)) checks ~= new PokemonExceptionCheck(args.setSkipTests( analysisConfig.exception_check == Check.skipTests && !ut)); @@ -1259,3 +1284,15 @@ version (unittest) } } } + +MessageSet analyzeDmd(string fileName, ASTBase.Module m) +{ + scope vis = new EnumArrayVisitor!ASTBase(fileName); + m.accept(vis); + + MessageSet set = new MessageSet; + foreach(message; vis.messages) + set.insert(message); + + return set; +}