diff --git a/src/analysis/config.d b/src/analysis/config.d index 1c90a69..104be16 100644 --- a/src/analysis/config.d +++ b/src/analysis/config.d @@ -51,6 +51,9 @@ struct StaticAnalysisConfig @INI("Checks for unused variables and function parameters") bool unused_variable_check; + @INI("Checks for unused labels") + bool unused_label_check; + @INI("Checks for duplicate attributes") bool duplicate_attribute; diff --git a/src/analysis/run.d b/src/analysis/run.d index 92dac30..06ceafa 100644 --- a/src/analysis/run.d +++ b/src/analysis/run.d @@ -28,6 +28,7 @@ import analysis.range; import analysis.ifelsesame; import analysis.constructors; import analysis.unused; +import analysis.unused_label; import analysis.duplicate_attribute; import analysis.opequals_without_tohash; import analysis.length_subtraction; @@ -176,6 +177,7 @@ MessageSet analyze(string fileName, const Module m, if (analysisConfig.backwards_range_check) checks ~= new BackwardsRangeCheck(fileName); if (analysisConfig.if_else_same_check) checks ~= new IfElseSameCheck(fileName); if (analysisConfig.constructor_check) checks ~= new ConstructorCheck(fileName); + if (analysisConfig.unused_label_check) checks ~= new UnusedLabelCheck(fileName); if (analysisConfig.unused_variable_check) checks ~= new UnusedVariableCheck(fileName); if (analysisConfig.duplicate_attribute) checks ~= new DuplicateAttributeCheck(fileName); if (analysisConfig.opequals_tohash_check) checks ~= new OpEqualsWithoutToHashCheck(fileName); diff --git a/src/analysis/unused_label.d b/src/analysis/unused_label.d new file mode 100644 index 0000000..78ee83d --- /dev/null +++ b/src/analysis/unused_label.d @@ -0,0 +1,172 @@ +// 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.unused_label; + +import std.stdio; +import std.d.ast; +import std.d.lexer; +import analysis.base; +import analysis.helpers; + +class UnusedLabelCheck : BaseAnalyzer +{ + alias visit = BaseAnalyzer.visit; + + this(string fileName) + { + super(fileName); + } + + static struct Label + { + string name; + size_t line; + size_t column; + bool used; + } + + Label[string][] stack; + + auto ref current() @property + { + return stack[$-1]; + } + + void pushScope() + { + stack.length++; + } + + void popScope() + { + foreach (label; current.byValue()) + { + assert(label.line != size_t.max && label.column != size_t.max); + if (!label.used) + { + addErrorMessage(label.line, label.column, + "dscanner.suspicious.unused_label", + "Label \"" ~ label.name ~ "\" is not used."); + } + } + stack.length--; + } + + override void visit(const FunctionBody functionBody) + { + if (functionBody.blockStatement !is null) + { + pushScope(); + functionBody.blockStatement.accept(this); + popScope(); + } + if (functionBody.bodyStatement !is null) + { + pushScope(); + functionBody.bodyStatement.accept(this); + popScope(); + } + if (functionBody.outStatement !is null) + { + pushScope(); + functionBody.outStatement.accept(this); + popScope(); + } + if (functionBody.inStatement !is null) + { + pushScope(); + functionBody.inStatement.accept(this); + popScope(); + } + } + + override void visit(const LabeledStatement labeledStatement) + { + auto token = &labeledStatement.identifier; + Label* label = token.text in current; + if (label is null) + { + current[token.text] = Label(token.text, token.line, token.column, false); + } + else + { + label.line = token.line; + label.column = token.column; + } + labeledStatement.declarationOrStatement.accept(this); + } + + void labelUsed(string name) + { + Label* entry = name in current; + if (entry is null) + current[name] = Label(name, size_t.max, size_t.max, true); + else + entry.used = true; + } + + override void visit(const ContinueStatement contStatement) + { + if (contStatement.label.text.length) + labelUsed(contStatement.label.text); + } + override void visit(const BreakStatement breakStatement) + { + if (breakStatement.label.text.length) + labelUsed(breakStatement.label.text); + } + override void visit(const GotoStatement gotoStatement) + { + if (gotoStatement.label.text.length) + labelUsed(gotoStatement.label.text); + } +} + +unittest +{ + import analysis.config : StaticAnalysisConfig; + + StaticAnalysisConfig sac; + sac.unused_label_check = true; + assertAnalyzerWarnings(q{ + int testUnusedLabel() + { + int x = 0; + A: // [warn]: Label "A" is not used. + if (x) goto B; + x++; + B: + goto C; + void foo() + { + C: // [warn]: Label "C" is not used. + return; + } + C: + void bar() + { + goto D; + D: + return; + } + D: // [warn]: Label "D" is not used. + goto E; + () { + E: // [warn]: Label "E" is not used. + return; + }(); + E: + () { + goto F; + F: + return; + }(); + F: // [warn]: Label "F" is not used. + return x; + } + }c, sac); + + stderr.writeln("Unittest for UnusedLabelCheck passed."); +}