Merge remote-tracking branch 'upstream/master' into phobos
This commit is contained in:
commit
7b3542fb6a
|
@ -1,5 +1,5 @@
|
||||||
; Configure which static analysis checks are enabled
|
; Configure which static analysis checks are enabled
|
||||||
[dscanner.analysis.config.StaticAnalysisConfig]
|
[analysis.config.StaticAnalysisConfig]
|
||||||
; Check variable, class, struct, interface, union, and function names against t
|
; Check variable, class, struct, interface, union, and function names against t
|
||||||
; he Phobos style guide
|
; he Phobos style guide
|
||||||
style_check="disabled"
|
style_check="disabled"
|
||||||
|
|
23
.travis.yml
23
.travis.yml
|
@ -16,14 +16,29 @@ script: "./.travis.sh"
|
||||||
jobs:
|
jobs:
|
||||||
include:
|
include:
|
||||||
- stage: GitHub Release
|
- stage: GitHub Release
|
||||||
|
if: tag IS present
|
||||||
d: ldc
|
d: ldc
|
||||||
os: linux
|
os: linux
|
||||||
script: echo "Deploying to GitHub releases ..." && make ldcbuild
|
script: echo "Deploying to GitHub releases ..." && make release
|
||||||
deploy:
|
deploy:
|
||||||
provider: releases
|
provider: releases
|
||||||
api_key:
|
api_key: $GH_REPO_TOKEN
|
||||||
secure: pbrrm6E0SPfVwt9g+e/ZFQfrmRuGBNA6KwMMLUhI+2+kbRzNquxvrYAUC7YcRX7xiRL/gugKHmOXEi1Dv9IEdSQ732M06H7ikZT9T9oQWYbsZzmVICBWgIovyM8XIPpVAwP8D7jq0JgMiBicqfEZfoz2SIJjo6aYbyQbCASCu8U=
|
file_glob: true
|
||||||
file: bin/dscanner
|
file: bin/dscanner-*.tar.gz
|
||||||
|
skip_cleanup: true
|
||||||
|
on:
|
||||||
|
repo: dlang-community/D-Scanner
|
||||||
|
tags: true
|
||||||
|
- stage: GitHub Release
|
||||||
|
if: tag IS present
|
||||||
|
d: ldc
|
||||||
|
os: osx
|
||||||
|
script: echo "Deploying to GitHub releases ..." && make release
|
||||||
|
deploy:
|
||||||
|
provider: releases
|
||||||
|
api_key: $GH_REPO_TOKEN
|
||||||
|
file_glob: true
|
||||||
|
file: bin/dscanner-*.tar.gz
|
||||||
skip_cleanup: true
|
skip_cleanup: true
|
||||||
on:
|
on:
|
||||||
repo: dlang-community/D-Scanner
|
repo: dlang-community/D-Scanner
|
||||||
|
|
|
@ -141,6 +141,7 @@ Note that the "--skipTests" option is the equivalent of changing each
|
||||||
* Public declarations without a documented unittest. By default disabled.
|
* Public declarations without a documented unittest. By default disabled.
|
||||||
* Asserts without an explanatory message. By default disabled.
|
* Asserts without an explanatory message. By default disabled.
|
||||||
* Indentation of if constraints
|
* Indentation of if constraints
|
||||||
|
* Check that `@trusted` is not applied to a whole scope. Trusting a whole scope can be a problem when new declarations are added and if they are not verified manually to be trustable.
|
||||||
|
|
||||||
#### Wishlist
|
#### Wishlist
|
||||||
|
|
||||||
|
|
2
dub.json
2
dub.json
|
@ -14,7 +14,7 @@
|
||||||
"dependencies" : {
|
"dependencies" : {
|
||||||
"libdparse": "~>0.8.0-alpha.5",
|
"libdparse": "~>0.8.0-alpha.5",
|
||||||
"dsymbol" : "~>0.3.0-alpha.3",
|
"dsymbol" : "~>0.3.0-alpha.3",
|
||||||
"inifiled" : "~>1.1.0",
|
"inifiled" : "~>1.2.0",
|
||||||
"emsi_containers" : "~>0.6.0",
|
"emsi_containers" : "~>0.6.0",
|
||||||
"libddoc" : "~>0.3.0-beta.1",
|
"libddoc" : "~>0.3.0-beta.1",
|
||||||
"stdx-allocator" : "~>2.77.0"
|
"stdx-allocator" : "~>2.77.0"
|
||||||
|
|
2
inifiled
2
inifiled
|
@ -1 +1 @@
|
||||||
Subproject commit e15038a5c265a9fdaea354476e7759d04e8d0bf9
|
Subproject commit 971c5356388a73ebbf69e32f7f5e97cfc06cdcff
|
25
makefile
25
makefile
|
@ -27,6 +27,7 @@ VERSIONS =
|
||||||
DEBUG_VERSIONS = -version=dparse_verbose
|
DEBUG_VERSIONS = -version=dparse_verbose
|
||||||
DMD_FLAGS = -w -inline -release -O -J. -od${OBJ_DIR} -version=StdLoggerDisableWarning -fPIC
|
DMD_FLAGS = -w -inline -release -O -J. -od${OBJ_DIR} -version=StdLoggerDisableWarning -fPIC
|
||||||
DMD_TEST_FLAGS = -w -g -J. -version=StdLoggerDisableWarning
|
DMD_TEST_FLAGS = -w -g -J. -version=StdLoggerDisableWarning
|
||||||
|
SHELL:=/bin/bash
|
||||||
|
|
||||||
all: dmdbuild
|
all: dmdbuild
|
||||||
ldc: ldcbuild
|
ldc: ldcbuild
|
||||||
|
@ -72,3 +73,27 @@ clean:
|
||||||
report: all
|
report: all
|
||||||
dscanner --report src > src/dscanner-report.json
|
dscanner --report src > src/dscanner-report.json
|
||||||
sonar-runner
|
sonar-runner
|
||||||
|
|
||||||
|
.ONESHELL:
|
||||||
|
release:
|
||||||
|
@set -eux -o pipefail
|
||||||
|
VERSION=$$(git describe --abbrev=0 --tags)
|
||||||
|
ARCH="$${ARCH:-64}"
|
||||||
|
unameOut="$$(uname -s)"
|
||||||
|
case "$$unameOut" in
|
||||||
|
Linux*) OS=linux; ;;
|
||||||
|
Darwin*) OS=osx; ;;
|
||||||
|
*) echo "Unknown OS: $$unameOut"; exit 1
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$$ARCH" in
|
||||||
|
64) ARCH_SUFFIX="x86_64";;
|
||||||
|
32) ARCH_SUFFIX="x86";;
|
||||||
|
*) echo "Unknown ARCH: $$ARCH"; exit 1
|
||||||
|
esac
|
||||||
|
|
||||||
|
archiveName="dscanner-$$VERSION-$$OS-$$ARCH_SUFFIX.tar.gz"
|
||||||
|
|
||||||
|
echo "Building $$archiveName"
|
||||||
|
${MAKE} ldcbuild
|
||||||
|
tar cvfz "bin/$$archiveName" -C bin dscanner
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
module dscanner.analysis.assert_without_msg;
|
module dscanner.analysis.assert_without_msg;
|
||||||
|
|
||||||
import dscanner.analysis.base : BaseAnalyzer;
|
import dscanner.analysis.base : BaseAnalyzer;
|
||||||
|
import dscanner.utils : safeAccess;
|
||||||
import dsymbol.scope_ : Scope;
|
import dsymbol.scope_ : Scope;
|
||||||
import dparse.lexer;
|
import dparse.lexer;
|
||||||
import dparse.ast;
|
import dparse.ast;
|
||||||
|
@ -37,11 +38,10 @@ class AssertWithoutMessageCheck : BaseAnalyzer
|
||||||
if (!isStdExceptionImported)
|
if (!isStdExceptionImported)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (expr.unaryExpression !is null &&
|
if (const IdentifierOrTemplateInstance iot = safeAccess(expr)
|
||||||
expr.unaryExpression.primaryExpression !is null &&
|
.unaryExpression.primaryExpression.identifierOrTemplateInstance)
|
||||||
expr.unaryExpression.primaryExpression.identifierOrTemplateInstance !is null)
|
|
||||||
{
|
{
|
||||||
auto ident = expr.unaryExpression.primaryExpression.identifierOrTemplateInstance.identifier;
|
auto ident = iot.identifier;
|
||||||
if (ident.text == "enforce" && expr.arguments !is null && expr.arguments.argumentList !is null &&
|
if (ident.text == "enforce" && expr.arguments !is null && expr.arguments.argumentList !is null &&
|
||||||
expr.arguments.argumentList.items.length < 2)
|
expr.arguments.argumentList.items.length < 2)
|
||||||
addErrorMessage(ident.line, ident.column, KEY, MESSAGE);
|
addErrorMessage(ident.line, ident.column, KEY, MESSAGE);
|
||||||
|
|
|
@ -36,7 +36,6 @@ void enabled2SkipTests(ref StaticAnalysisConfig config)
|
||||||
{
|
{
|
||||||
if (__traits(getMember, config, mem) == Check.enabled)
|
if (__traits(getMember, config, mem) == Check.enabled)
|
||||||
__traits(getMember, config, mem) = Check.skipTests;
|
__traits(getMember, config, mem) = Check.skipTests;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +53,7 @@ StaticAnalysisConfig disabledConfig()
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@INI("Configure which static analysis checks are enabled")
|
@INI("Configure which static analysis checks are enabled", "analysis.config.StaticAnalysisConfig")
|
||||||
struct StaticAnalysisConfig
|
struct StaticAnalysisConfig
|
||||||
{
|
{
|
||||||
@INI("Check variable, class, struct, interface, union, and function names against the Phobos style guide")
|
@INI("Check variable, class, struct, interface, union, and function names against the Phobos style guide")
|
||||||
|
@ -195,6 +194,9 @@ struct StaticAnalysisConfig
|
||||||
@INI("Check indent of if constraints")
|
@INI("Check indent of if constraints")
|
||||||
string if_constraints_indent = Check.disabled;
|
string if_constraints_indent = Check.disabled;
|
||||||
|
|
||||||
|
@INI("Check for @trusted applied to a bigger scope than a single function")
|
||||||
|
string trust_too_much = Check.enabled;
|
||||||
|
|
||||||
@INI("Module-specific filters")
|
@INI("Module-specific filters")
|
||||||
ModuleFilters filters;
|
ModuleFilters filters;
|
||||||
}
|
}
|
||||||
|
@ -211,7 +213,7 @@ private template ModuleFiltersMixin(A)
|
||||||
}();
|
}();
|
||||||
}
|
}
|
||||||
|
|
||||||
@INI("ModuleFilters. +std.,-std.internal")
|
@INI("ModuleFilters for selectively enabling (+std) and disabling (-std.internal) individual checks", "analysis.config.ModuleFilters")
|
||||||
struct ModuleFilters
|
struct ModuleFilters
|
||||||
{
|
{
|
||||||
mixin(ModuleFiltersMixin!int);
|
mixin(ModuleFiltersMixin!int);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
module dscanner.analysis.mismatched_args;
|
module dscanner.analysis.mismatched_args;
|
||||||
|
|
||||||
import dscanner.analysis.base : BaseAnalyzer;
|
import dscanner.analysis.base : BaseAnalyzer;
|
||||||
|
import dscanner.utils : safeAccess;
|
||||||
import dsymbol.scope_;
|
import dsymbol.scope_;
|
||||||
import dsymbol.symbol;
|
import dsymbol.symbol;
|
||||||
import dparse.ast;
|
import dparse.ast;
|
||||||
|
@ -126,17 +127,16 @@ final class ArgVisitor : ASTVisitor
|
||||||
{
|
{
|
||||||
import dsymbol.string_interning : internString;
|
import dsymbol.string_interning : internString;
|
||||||
|
|
||||||
if (unary.primaryExpression is null)
|
if (auto iot = unary.safeAccess.primaryExpression.identifierOrTemplateInstance.unwrap)
|
||||||
|
{
|
||||||
|
if (iot.identifier == tok!"")
|
||||||
return;
|
return;
|
||||||
if (unary.primaryExpression.identifierOrTemplateInstance is null)
|
immutable t = iot.identifier;
|
||||||
return;
|
|
||||||
if (unary.primaryExpression.identifierOrTemplateInstance.identifier == tok!"")
|
|
||||||
return;
|
|
||||||
immutable t = unary.primaryExpression.identifierOrTemplateInstance.identifier;
|
|
||||||
lines ~= t.line;
|
lines ~= t.line;
|
||||||
columns ~= t.column;
|
columns ~= t.column;
|
||||||
args ~= internString(t.text);
|
args ~= internString(t.text);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
alias visit = ASTVisitor.visit;
|
alias visit = ASTVisitor.visit;
|
||||||
|
|
||||||
|
|
|
@ -166,8 +166,11 @@ private:
|
||||||
import std.array : array;
|
import std.array : array;
|
||||||
|
|
||||||
const comment = parseComment(commentText, null);
|
const comment = parseComment(commentText, null);
|
||||||
if (!comment.isDitto && !withinTemplate)
|
if (withinTemplate) {
|
||||||
{
|
const paramSection = comment.sections.find!(s => s.name == "Params");
|
||||||
|
if (!paramSection.empty)
|
||||||
|
lastSeenFun.ddocParams ~= paramSection[0].mapping.map!(a => a[0]).array;
|
||||||
|
} else if (!comment.isDitto) {
|
||||||
// check old function for invalid ddoc params
|
// check old function for invalid ddoc params
|
||||||
if (lastSeenFun.active)
|
if (lastSeenFun.active)
|
||||||
postCheckSeenDdocParams();
|
postCheckSeenDdocParams();
|
||||||
|
@ -796,6 +799,40 @@ string bar(P, R)(R r){}// [warn]: %s
|
||||||
}c.format(
|
}c.format(
|
||||||
ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("P")
|
ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("P")
|
||||||
), sac);
|
), sac);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/dlang-community/D-Scanner/issues/583
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
StaticAnalysisConfig sac = disabledConfig;
|
||||||
|
sac.properly_documented_public_functions = Check.enabled;
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
/++
|
||||||
|
Implements the homonym function (also known as `accumulate`)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
the accumulated `result`
|
||||||
|
|
||||||
|
Params:
|
||||||
|
fun = one or more functions
|
||||||
|
+/
|
||||||
|
template reduce(fun...)
|
||||||
|
if (fun.length >= 1)
|
||||||
|
{
|
||||||
|
/++
|
||||||
|
No-seed version. The first element of `r` is used as the seed's value.
|
||||||
|
|
||||||
|
Params:
|
||||||
|
r = an iterable value as defined by `isIterable`
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
the final result of the accumulator applied to the iterable
|
||||||
|
+/
|
||||||
|
auto reduce(R)(R r){}
|
||||||
|
}
|
||||||
|
}c.format(
|
||||||
|
), sac);
|
||||||
|
|
||||||
stderr.writeln("Unittest for ProperlyDocumentedPublicFunctions passed.");
|
stderr.writeln("Unittest for ProperlyDocumentedPublicFunctions passed.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,7 @@ import dscanner.analysis.redundant_attributes;
|
||||||
import dscanner.analysis.has_public_example;
|
import dscanner.analysis.has_public_example;
|
||||||
import dscanner.analysis.assert_without_msg;
|
import dscanner.analysis.assert_without_msg;
|
||||||
import dscanner.analysis.if_constraints_indent;
|
import dscanner.analysis.if_constraints_indent;
|
||||||
|
import dscanner.analysis.trust_too_much;
|
||||||
|
|
||||||
import dsymbol.string_interning : internString;
|
import dsymbol.string_interning : internString;
|
||||||
import dsymbol.scope_;
|
import dsymbol.scope_;
|
||||||
|
@ -81,7 +82,7 @@ import dsymbol.conversion.first;
|
||||||
import dsymbol.conversion.second;
|
import dsymbol.conversion.second;
|
||||||
import dsymbol.modulecache : ModuleCache;
|
import dsymbol.modulecache : ModuleCache;
|
||||||
|
|
||||||
import dscanner.readers;
|
import dscanner.utils;
|
||||||
|
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
|
||||||
|
@ -511,6 +512,10 @@ MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig a
|
||||||
checks ~= new IfConstraintsIndentCheck(fileName, tokens,
|
checks ~= new IfConstraintsIndentCheck(fileName, tokens,
|
||||||
analysisConfig.if_constraints_indent == Check.skipTests && !ut);
|
analysisConfig.if_constraints_indent == Check.skipTests && !ut);
|
||||||
|
|
||||||
|
if (moduleName.shouldRun!"trust_too_much"(analysisConfig))
|
||||||
|
checks ~= new TrustTooMuchCheck(fileName,
|
||||||
|
analysisConfig.trust_too_much == Check.skipTests && !ut);
|
||||||
|
|
||||||
version (none)
|
version (none)
|
||||||
if (moduleName.shouldRun!"redundant_if_check"(analysisConfig))
|
if (moduleName.shouldRun!"redundant_if_check"(analysisConfig))
|
||||||
checks ~= new IfStatementCheck(fileName, moduleScope,
|
checks ~= new IfStatementCheck(fileName, moduleScope,
|
||||||
|
|
|
@ -8,6 +8,7 @@ module dscanner.analysis.static_if_else;
|
||||||
import dparse.ast;
|
import dparse.ast;
|
||||||
import dparse.lexer;
|
import dparse.lexer;
|
||||||
import dscanner.analysis.base;
|
import dscanner.analysis.base;
|
||||||
|
import dscanner.utils : safeAccess;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for potentially mistaken static if / else if.
|
* Checks for potentially mistaken static if / else if.
|
||||||
|
@ -47,17 +48,7 @@ class StaticIfElse : BaseAnalyzer
|
||||||
|
|
||||||
const(IfStatement) getIfStatement(const ConditionalStatement cc)
|
const(IfStatement) getIfStatement(const ConditionalStatement cc)
|
||||||
{
|
{
|
||||||
if (cc.falseStatement.statement)
|
return safeAccess(cc).falseStatement.statement.statementNoCaseNoDefault.ifStatement;
|
||||||
{
|
|
||||||
if (cc.falseStatement.statement.statementNoCaseNoDefault)
|
|
||||||
{
|
|
||||||
if (cc.falseStatement.statement.statementNoCaseNoDefault.ifStatement)
|
|
||||||
{
|
|
||||||
return cc.falseStatement.statement.statementNoCaseNoDefault.ifStatement;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum KEY = "dscanner.suspicious.static_if_else";
|
enum KEY = "dscanner.suspicious.static_if_else";
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
// Copyright The dlang community - 2018
|
||||||
|
// 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.trust_too_much;
|
||||||
|
|
||||||
|
import std.stdio;
|
||||||
|
import dparse.ast;
|
||||||
|
import dparse.lexer;
|
||||||
|
import dscanner.analysis.base;
|
||||||
|
import dsymbol.scope_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that `@trusted` is only applied to a a single function
|
||||||
|
*/
|
||||||
|
class TrustTooMuchCheck : BaseAnalyzer
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
|
static immutable MESSAGE = "Trusting a whole scope is a bad idea, " ~
|
||||||
|
"`@trusted` should only be attached to a single function";
|
||||||
|
static immutable string KEY = "dscanner.trust_too_much";
|
||||||
|
|
||||||
|
bool checkAtAttribute = true;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
alias visit = BaseAnalyzer.visit;
|
||||||
|
|
||||||
|
///
|
||||||
|
this(string fileName, bool skipTests = false)
|
||||||
|
{
|
||||||
|
super(fileName, sc, skipTests);
|
||||||
|
}
|
||||||
|
|
||||||
|
override void visit(const AtAttribute d)
|
||||||
|
{
|
||||||
|
if (checkAtAttribute && d.identifier.text == "trusted")
|
||||||
|
{
|
||||||
|
const Token t = d.identifier;
|
||||||
|
addErrorMessage(t.line, t.column, KEY, MESSAGE);
|
||||||
|
}
|
||||||
|
d.accept(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// always applied to function body, so OK
|
||||||
|
override void visit(const MemberFunctionAttribute d)
|
||||||
|
{
|
||||||
|
const oldCheckAtAttribute = checkAtAttribute;
|
||||||
|
checkAtAttribute = false;
|
||||||
|
d.accept(this);
|
||||||
|
checkAtAttribute = oldCheckAtAttribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handles `@trusted{}` and old style, leading, atAttribute for single funcs
|
||||||
|
override void visit(const Declaration d)
|
||||||
|
{
|
||||||
|
const oldCheckAtAttribute = checkAtAttribute;
|
||||||
|
checkAtAttribute = d.functionDeclaration is null;
|
||||||
|
d.accept(this);
|
||||||
|
checkAtAttribute = oldCheckAtAttribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
// issue #588
|
||||||
|
override void visit(const AliasDeclaration d)
|
||||||
|
{
|
||||||
|
const oldCheckAtAttribute = checkAtAttribute;
|
||||||
|
checkAtAttribute = false;
|
||||||
|
d.accept(this);
|
||||||
|
checkAtAttribute = oldCheckAtAttribute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
|
||||||
|
import dscanner.analysis.helpers : assertAnalyzerWarnings;
|
||||||
|
import std.format : format;
|
||||||
|
|
||||||
|
StaticAnalysisConfig sac = disabledConfig();
|
||||||
|
sac.trust_too_much = Check.enabled;
|
||||||
|
const msg = TrustTooMuchCheck.MESSAGE;
|
||||||
|
|
||||||
|
//--- fail cases ---//
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
@trusted: // [warn]: %s
|
||||||
|
void test();
|
||||||
|
}c.format(msg), sac);
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
@trusted @nogc: // [warn]: %s
|
||||||
|
void test();
|
||||||
|
}c.format(msg), sac);
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
@trusted { // [warn]: %s
|
||||||
|
void test();
|
||||||
|
void test();
|
||||||
|
}
|
||||||
|
}c.format(msg), sac);
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
@safe {
|
||||||
|
@trusted @nogc { // [warn]: %s
|
||||||
|
void test();
|
||||||
|
void test();
|
||||||
|
}}
|
||||||
|
}c.format(msg), sac);
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
@nogc @trusted { // [warn]: %s
|
||||||
|
void test();
|
||||||
|
void test();
|
||||||
|
}
|
||||||
|
}c.format(msg), sac);
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
@trusted template foo(){ // [warn]: %s
|
||||||
|
}
|
||||||
|
}c.format(msg), sac);
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
struct foo{
|
||||||
|
@trusted: // [warn]: %s
|
||||||
|
}
|
||||||
|
}c.format(msg), sac);
|
||||||
|
//--- pass cases ---//
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
void test() @trusted {}
|
||||||
|
}c, sac);
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
@trusted void test();
|
||||||
|
}c, sac);
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
@nogc template foo(){
|
||||||
|
}
|
||||||
|
}c , sac);
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
alias nothrow @trusted uint F4();
|
||||||
|
}c , sac);
|
||||||
|
|
||||||
|
stderr.writeln("Unittest for TrustTooMuchCheck passed.");
|
||||||
|
}
|
|
@ -5,6 +5,7 @@
|
||||||
module dscanner.analysis.unmodified;
|
module dscanner.analysis.unmodified;
|
||||||
|
|
||||||
import dscanner.analysis.base;
|
import dscanner.analysis.base;
|
||||||
|
import dscanner.utils : safeAccess;
|
||||||
import dsymbol.scope_ : Scope;
|
import dsymbol.scope_ : Scope;
|
||||||
import std.container;
|
import std.container;
|
||||||
import dparse.ast;
|
import dparse.ast;
|
||||||
|
@ -217,12 +218,9 @@ private:
|
||||||
|
|
||||||
bool initializedFromNew(const Initializer initializer)
|
bool initializedFromNew(const Initializer initializer)
|
||||||
{
|
{
|
||||||
if (initializer && initializer.nonVoidInitializer &&
|
if (const UnaryExpression ue = cast(UnaryExpression) safeAccess(initializer)
|
||||||
initializer.nonVoidInitializer.assignExpression &&
|
.nonVoidInitializer.assignExpression)
|
||||||
cast(UnaryExpression) initializer.nonVoidInitializer.assignExpression)
|
|
||||||
{
|
{
|
||||||
const UnaryExpression ue =
|
|
||||||
cast(UnaryExpression) initializer.nonVoidInitializer.assignExpression;
|
|
||||||
return ue.newExpression !is null;
|
return ue.newExpression !is null;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
module dscanner.analysis.useless_initializer;
|
module dscanner.analysis.useless_initializer;
|
||||||
|
|
||||||
import dscanner.analysis.base;
|
import dscanner.analysis.base;
|
||||||
|
import dscanner.utils : safeAccess;
|
||||||
import containers.dynamicarray;
|
import containers.dynamicarray;
|
||||||
import containers.hashmap;
|
import containers.hashmap;
|
||||||
import dparse.ast;
|
import dparse.ast;
|
||||||
|
@ -171,16 +172,15 @@ public:
|
||||||
bool isStr, isSzInt;
|
bool isStr, isSzInt;
|
||||||
Token customType;
|
Token customType;
|
||||||
|
|
||||||
if (decl.type.type2.typeIdentifierPart &&
|
if (const TypeIdentifierPart tip = safeAccess(decl).type.type2.typeIdentifierPart)
|
||||||
decl.type.type2.typeIdentifierPart.typeIdentifierPart is null)
|
|
||||||
{
|
{
|
||||||
const IdentifierOrTemplateInstance idt =
|
if (!tip.typeIdentifierPart)
|
||||||
decl.type.type2.typeIdentifierPart.identifierOrTemplateInstance;
|
{
|
||||||
|
customType = tip.identifierOrTemplateInstance.identifier;
|
||||||
customType = idt.identifier;
|
|
||||||
isStr = customType.text.among("string", "wstring", "dstring") != 0;
|
isStr = customType.text.among("string", "wstring", "dstring") != 0;
|
||||||
isSzInt = customType.text.among("size_t", "ptrdiff_t") != 0;
|
isSzInt = customType.text.among("size_t", "ptrdiff_t") != 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- 'BasicType/Symbol AssignExpression' ---//
|
// --- 'BasicType/Symbol AssignExpression' ---//
|
||||||
const NonVoidInitializer nvi = declarator.initializer.nonVoidInitializer;
|
const NonVoidInitializer nvi = declarator.initializer.nonVoidInitializer;
|
||||||
|
@ -230,10 +230,11 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (const IdentifierOrTemplateInstance iot = safeAccess(ue)
|
||||||
|
.unaryExpression.primaryExpression.identifierOrTemplateInstance)
|
||||||
|
{
|
||||||
// Symbol s = Symbol.init
|
// Symbol s = Symbol.init
|
||||||
else if (ue && customType != tok!"" && ue.unaryExpression && ue.unaryExpression.primaryExpression &&
|
if (ue && customType != tok!"" && iot.identifier == customType &&
|
||||||
ue.unaryExpression.primaryExpression.identifierOrTemplateInstance &&
|
|
||||||
ue.unaryExpression.primaryExpression.identifierOrTemplateInstance.identifier == customType &&
|
|
||||||
ue.identifierOrTemplateInstance && ue.identifierOrTemplateInstance.identifier.text == "init")
|
ue.identifierOrTemplateInstance && ue.identifierOrTemplateInstance.identifier.text == "init")
|
||||||
{
|
{
|
||||||
if (customType.text in _structCanBeInit)
|
if (customType.text in _structCanBeInit)
|
||||||
|
@ -242,6 +243,7 @@ public:
|
||||||
mixin(warn);
|
mixin(warn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 'Symbol ArrayInitializer' : assumes Symbol is an array b/c of the Init
|
// 'Symbol ArrayInitializer' : assumes Symbol is an array b/c of the Init
|
||||||
else if (nvi.arrayInitializer && (isArr || isStr))
|
else if (nvi.arrayInitializer && (isArr || isStr))
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
module dscanner.analysis.vcall_in_ctor;
|
module dscanner.analysis.vcall_in_ctor;
|
||||||
|
|
||||||
import dscanner.analysis.base;
|
import dscanner.analysis.base;
|
||||||
|
import dscanner.utils;
|
||||||
import dparse.ast, dparse.lexer;
|
import dparse.ast, dparse.lexer;
|
||||||
import std.algorithm: among;
|
import std.algorithm: among;
|
||||||
import std.algorithm.iteration : filter;
|
import std.algorithm.iteration : filter;
|
||||||
|
@ -220,16 +221,12 @@ public:
|
||||||
|
|
||||||
override void visit(const(UnaryExpression) exp)
|
override void visit(const(UnaryExpression) exp)
|
||||||
{
|
{
|
||||||
|
if (isInCtor)
|
||||||
// get function identifier for a call, only for this member (so no ident chain)
|
// get function identifier for a call, only for this member (so no ident chain)
|
||||||
if (isInCtor && exp.functionCallExpression &&
|
if (const IdentifierOrTemplateInstance iot = safeAccess(exp)
|
||||||
exp.functionCallExpression.unaryExpression &&
|
.functionCallExpression.unaryExpression.primaryExpression.identifierOrTemplateInstance)
|
||||||
exp.functionCallExpression.unaryExpression.primaryExpression &&
|
|
||||||
exp.functionCallExpression.unaryExpression.primaryExpression
|
|
||||||
.identifierOrTemplateInstance)
|
|
||||||
{
|
{
|
||||||
const Token t = exp.functionCallExpression.unaryExpression
|
const Token t = iot.identifier;
|
||||||
.primaryExpression.identifierOrTemplateInstance.identifier;
|
|
||||||
|
|
||||||
if (t != tok!"")
|
if (t != tok!"")
|
||||||
{
|
{
|
||||||
_ctorCalls[$-1] ~= t;
|
_ctorCalls[$-1] ~= t;
|
||||||
|
|
|
@ -12,7 +12,7 @@ import dparse.rollback_allocator;
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
import std.container.rbtree;
|
import std.container.rbtree;
|
||||||
import std.functional : toDelegate;
|
import std.functional : toDelegate;
|
||||||
import dscanner.readers;
|
import dscanner.utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AST visitor that collects modules imported to an R-B tree.
|
* AST visitor that collects modules imported to an R-B tree.
|
||||||
|
|
|
@ -31,7 +31,7 @@ import dscanner.symbol_finder;
|
||||||
import dscanner.analysis.run;
|
import dscanner.analysis.run;
|
||||||
import dscanner.analysis.config;
|
import dscanner.analysis.config;
|
||||||
import dscanner.dscanner_version;
|
import dscanner.dscanner_version;
|
||||||
import dscanner.readers;
|
import dscanner.utils;
|
||||||
|
|
||||||
import inifiled;
|
import inifiled;
|
||||||
|
|
||||||
|
@ -68,7 +68,6 @@ else
|
||||||
bool printVersion;
|
bool printVersion;
|
||||||
bool explore;
|
bool explore;
|
||||||
string errorFormat;
|
string errorFormat;
|
||||||
bool patchConfig;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -97,8 +96,7 @@ else
|
||||||
"muffinButton", &muffin,
|
"muffinButton", &muffin,
|
||||||
"explore", &explore,
|
"explore", &explore,
|
||||||
"skipTests", &skipTests,
|
"skipTests", &skipTests,
|
||||||
"errorFormat|f", &errorFormat,
|
"errorFormat|f", &errorFormat);
|
||||||
"patchConfig", &patchConfig);
|
|
||||||
//dfmt on
|
//dfmt on
|
||||||
}
|
}
|
||||||
catch (ConvException e)
|
catch (ConvException e)
|
||||||
|
@ -235,11 +233,7 @@ else
|
||||||
StaticAnalysisConfig config = defaultStaticAnalysisConfig();
|
StaticAnalysisConfig config = defaultStaticAnalysisConfig();
|
||||||
string s = configLocation is null ? getConfigurationLocation() : configLocation;
|
string s = configLocation is null ? getConfigurationLocation() : configLocation;
|
||||||
if (s.exists())
|
if (s.exists())
|
||||||
{
|
|
||||||
if (hasWrongIniFileSection(s, patchConfig))
|
|
||||||
return 0;
|
|
||||||
readINIFile(config, s);
|
readINIFile(config, s);
|
||||||
}
|
|
||||||
if (skipTests)
|
if (skipTests)
|
||||||
config.enabled2SkipTests;
|
config.enabled2SkipTests;
|
||||||
if (report)
|
if (report)
|
||||||
|
@ -393,10 +387,7 @@ Options:
|
||||||
Generates a default configuration file for the static analysis checks,
|
Generates a default configuration file for the static analysis checks,
|
||||||
|
|
||||||
--skipTests
|
--skipTests
|
||||||
Does not analyze in the unittests. Only works if --styleCheck.,
|
Does not analyze in the unittests. Only works if --styleCheck.`,
|
||||||
|
|
||||||
--patchConfig
|
|
||||||
Patches the configuration file passed as parameter for v0.5.0.`,
|
|
||||||
|
|
||||||
programName);
|
programName);
|
||||||
}
|
}
|
||||||
|
@ -416,24 +407,28 @@ version (OSX) version = useXDG;
|
||||||
*/
|
*/
|
||||||
string getDefaultConfigurationLocation()
|
string getDefaultConfigurationLocation()
|
||||||
{
|
{
|
||||||
|
import std.process : environment;
|
||||||
|
import std.exception : enforce;
|
||||||
version (useXDG)
|
version (useXDG)
|
||||||
{
|
{
|
||||||
import std.process : environment;
|
|
||||||
|
|
||||||
string configDir = environment.get("XDG_CONFIG_HOME", null);
|
string configDir = environment.get("XDG_CONFIG_HOME", null);
|
||||||
if (configDir is null)
|
if (configDir is null)
|
||||||
{
|
{
|
||||||
configDir = environment.get("HOME", null);
|
configDir = environment.get("HOME", null);
|
||||||
if (configDir is null)
|
enforce(configDir !is null, "Both $XDG_CONFIG_HOME and $HOME are unset");
|
||||||
throw new Exception("Both $XDG_CONFIG_HOME and $HOME are unset");
|
|
||||||
configDir = buildPath(configDir, ".config", "dscanner", CONFIG_FILE_NAME);
|
configDir = buildPath(configDir, ".config", "dscanner", CONFIG_FILE_NAME);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
configDir = buildPath(configDir, "dscanner", CONFIG_FILE_NAME);
|
configDir = buildPath(configDir, "dscanner", CONFIG_FILE_NAME);
|
||||||
return configDir;
|
return configDir;
|
||||||
}
|
}
|
||||||
else version (Windows)
|
else version(Windows)
|
||||||
return CONFIG_FILE_NAME;
|
{
|
||||||
|
string configDir = environment.get("APPDATA", null);
|
||||||
|
enforce(configDir !is null, "%APPDATA% is unset");
|
||||||
|
configDir = buildPath(configDir, "dscanner", CONFIG_FILE_NAME);
|
||||||
|
return configDir;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -472,41 +467,3 @@ string getConfigurationLocation()
|
||||||
|
|
||||||
return getDefaultConfigurationLocation();
|
return getDefaultConfigurationLocation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Patch the INI file to v0.5.0 format.
|
|
||||||
//TODO: remove this from v0.6.0
|
|
||||||
bool hasWrongIniFileSection(string configFilename, bool patch)
|
|
||||||
{
|
|
||||||
import std.string : indexOf;
|
|
||||||
import std.array : replace;
|
|
||||||
|
|
||||||
bool result;
|
|
||||||
|
|
||||||
static immutable v1 = "analysis.config.StaticAnalysisConfig";
|
|
||||||
static immutable v2 = "dscanner.analysis.config.StaticAnalysisConfig";
|
|
||||||
|
|
||||||
char[] c = cast(char[]) readFile(configFilename);
|
|
||||||
try if (c.indexOf(v2) < 0)
|
|
||||||
{
|
|
||||||
if (!patch)
|
|
||||||
{
|
|
||||||
writeln("warning, the configuration file `", configFilename, "` contains an outdated property");
|
|
||||||
writeln("change manually [", v1, "] to [", v2, "]" );
|
|
||||||
writeln("or restart D-Scanner with the `--patchConfig` option");
|
|
||||||
result = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
c = replace(c, v1, v2);
|
|
||||||
std.file.write(configFilename, c);
|
|
||||||
writeln("the configuration file `", configFilename, "` has been updated correctly");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
stderr.writeln("error encountered when trying to verify the INI file compatibility");
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
module dscanner.readers;
|
|
||||||
|
|
||||||
import std.array : appender, uninitializedArray;
|
|
||||||
import std.stdio : stdin, stderr, File;
|
|
||||||
import std.conv : to;
|
|
||||||
import std.file : exists;
|
|
||||||
|
|
||||||
ubyte[] readStdin()
|
|
||||||
{
|
|
||||||
auto sourceCode = appender!(ubyte[])();
|
|
||||||
ubyte[4096] buf;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
auto b = stdin.rawRead(buf);
|
|
||||||
if (b.length == 0)
|
|
||||||
break;
|
|
||||||
sourceCode.put(b);
|
|
||||||
}
|
|
||||||
return sourceCode.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
ubyte[] readFile(string fileName)
|
|
||||||
{
|
|
||||||
if (fileName == "stdin")
|
|
||||||
return readStdin();
|
|
||||||
if (!exists(fileName))
|
|
||||||
{
|
|
||||||
stderr.writefln("%s does not exist", fileName);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
File f = File(fileName);
|
|
||||||
if (f.size == 0)
|
|
||||||
return [];
|
|
||||||
ubyte[] sourceCode = uninitializedArray!(ubyte[])(to!size_t(f.size));
|
|
||||||
f.rawRead(sourceCode);
|
|
||||||
return sourceCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
string[] expandArgs(string[] args)
|
|
||||||
{
|
|
||||||
import std.file : isFile, FileException, dirEntries, SpanMode;
|
|
||||||
import std.algorithm.iteration : map;
|
|
||||||
import std.algorithm.searching : endsWith;
|
|
||||||
|
|
||||||
// isFile can throw if it's a broken symlink.
|
|
||||||
bool isFileSafe(T)(T a)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
return isFile(a);
|
|
||||||
catch (FileException)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
string[] rVal;
|
|
||||||
if (args.length == 1)
|
|
||||||
args ~= ".";
|
|
||||||
foreach (arg; args[1 .. $])
|
|
||||||
{
|
|
||||||
if (arg == "stdin" || isFileSafe(arg))
|
|
||||||
rVal ~= arg;
|
|
||||||
else
|
|
||||||
foreach (item; dirEntries(arg, SpanMode.breadth).map!(a => a.name))
|
|
||||||
{
|
|
||||||
if (isFileSafe(item) && (item.endsWith(`.d`) || item.endsWith(`.di`)))
|
|
||||||
rVal ~= item;
|
|
||||||
else
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rVal;
|
|
||||||
}
|
|
|
@ -0,0 +1,194 @@
|
||||||
|
module dscanner.utils;
|
||||||
|
|
||||||
|
import std.array : appender, uninitializedArray;
|
||||||
|
import std.stdio : stdin, stderr, File;
|
||||||
|
import std.conv : to;
|
||||||
|
import std.file : exists;
|
||||||
|
|
||||||
|
ubyte[] readStdin()
|
||||||
|
{
|
||||||
|
auto sourceCode = appender!(ubyte[])();
|
||||||
|
ubyte[4096] buf;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto b = stdin.rawRead(buf);
|
||||||
|
if (b.length == 0)
|
||||||
|
break;
|
||||||
|
sourceCode.put(b);
|
||||||
|
}
|
||||||
|
return sourceCode.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
ubyte[] readFile(string fileName)
|
||||||
|
{
|
||||||
|
if (fileName == "stdin")
|
||||||
|
return readStdin();
|
||||||
|
if (!exists(fileName))
|
||||||
|
{
|
||||||
|
stderr.writefln("%s does not exist", fileName);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
File f = File(fileName);
|
||||||
|
if (f.size == 0)
|
||||||
|
return [];
|
||||||
|
ubyte[] sourceCode = uninitializedArray!(ubyte[])(to!size_t(f.size));
|
||||||
|
f.rawRead(sourceCode);
|
||||||
|
return sourceCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] expandArgs(string[] args)
|
||||||
|
{
|
||||||
|
import std.file : isFile, FileException, dirEntries, SpanMode;
|
||||||
|
import std.algorithm.iteration : map;
|
||||||
|
import std.algorithm.searching : endsWith;
|
||||||
|
|
||||||
|
// isFile can throw if it's a broken symlink.
|
||||||
|
bool isFileSafe(T)(T a)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
return isFile(a);
|
||||||
|
catch (FileException)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] rVal;
|
||||||
|
if (args.length == 1)
|
||||||
|
args ~= ".";
|
||||||
|
foreach (arg; args[1 .. $])
|
||||||
|
{
|
||||||
|
if (arg == "stdin" || isFileSafe(arg))
|
||||||
|
rVal ~= arg;
|
||||||
|
else
|
||||||
|
foreach (item; dirEntries(arg, SpanMode.breadth).map!(a => a.name))
|
||||||
|
{
|
||||||
|
if (isFileSafe(item) && (item.endsWith(`.d`) || item.endsWith(`.di`)))
|
||||||
|
rVal ~= item;
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to build access chains of class members as done with the $(D ?.) operator
|
||||||
|
* in other languages. In the chain, any $(D null) member that is a class instance
|
||||||
|
* or that returns one, has for effect to shortcut the complete evaluation.
|
||||||
|
*
|
||||||
|
* This function is copied from https://github.com/BBasile/iz to avoid a new submodule.
|
||||||
|
* Any change made to this copy should also be applied to the origin.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* M = The class type of the chain entry point.
|
||||||
|
*
|
||||||
|
* Bugs:
|
||||||
|
* Assigning a member only works with $(D unwrap).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
struct SafeAccess(M)
|
||||||
|
if (is(M == class))
|
||||||
|
{
|
||||||
|
M m;
|
||||||
|
|
||||||
|
@disable this();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* m = An instance of the entry point type. It is usually only
|
||||||
|
* $(D null) when the constructor is used internally, to build
|
||||||
|
* the chain.
|
||||||
|
*/
|
||||||
|
this(M m)
|
||||||
|
{
|
||||||
|
this.m = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
alias m this;
|
||||||
|
/// Unprotect the class instance.
|
||||||
|
alias unwrap = m;
|
||||||
|
|
||||||
|
/// Handles safe access.
|
||||||
|
auto ref opDispatch(string member, A...)(auto ref A a)
|
||||||
|
{
|
||||||
|
import std.traits : ReturnType;
|
||||||
|
alias T = typeof(__traits(getMember, m, member));
|
||||||
|
static if (is(T == class))
|
||||||
|
{
|
||||||
|
return (!m || !__traits(getMember, m, member))
|
||||||
|
? SafeAccess!T(null)
|
||||||
|
: SafeAccess!T(__traits(getMember, m, member));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
import std.traits : ReturnType, Parameters, isFunction;
|
||||||
|
static if (isFunction!T)
|
||||||
|
{
|
||||||
|
// otherwise there's a missing return statement.
|
||||||
|
alias R = ReturnType!T;
|
||||||
|
static if (!is(R == void) &&
|
||||||
|
!(is(R == class) && Parameters!T.length == 0))
|
||||||
|
pragma(msg, __FILE__ ~ "(" ~ __LINE__.stringof ~ "): error, " ~
|
||||||
|
"only `void function`s or `class` getters can be called without unwrap");
|
||||||
|
|
||||||
|
static if (is(R == class))
|
||||||
|
{
|
||||||
|
return (m is null)
|
||||||
|
? SafeAccess!R(null)
|
||||||
|
: SafeAccess!R(__traits(getMember, m, member)(a));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m)
|
||||||
|
__traits(getMember, m, member)(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m)
|
||||||
|
__traits(getMember, m, member) = a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// General usage
|
||||||
|
@safe unittest
|
||||||
|
{
|
||||||
|
class LongLineOfIdent3{int foo; void setFoo(int v) @safe{foo = v;}}
|
||||||
|
class LongLineOfIdent2{LongLineOfIdent3 longLineOfIdent3;}
|
||||||
|
class LongLineOfIdent1{LongLineOfIdent2 longLineOfIdent2;}
|
||||||
|
class Root {LongLineOfIdent1 longLineOfIdent1;}
|
||||||
|
|
||||||
|
SafeAccess!Root sar = SafeAccess!Root(new Root);
|
||||||
|
// without the SafeAccess we would receive a SIGSEGV here
|
||||||
|
sar.longLineOfIdent1.longLineOfIdent2.longLineOfIdent3.setFoo(0xDEADBEEF);
|
||||||
|
|
||||||
|
bool notAccessed = true;
|
||||||
|
// the same with `&&` whould be much longer
|
||||||
|
if (LongLineOfIdent3 a = sar.longLineOfIdent1.longLineOfIdent2.longLineOfIdent3)
|
||||||
|
{
|
||||||
|
notAccessed = false;
|
||||||
|
}
|
||||||
|
assert(notAccessed);
|
||||||
|
|
||||||
|
// checks that forwarding actually works
|
||||||
|
sar.m.longLineOfIdent1 = new LongLineOfIdent1;
|
||||||
|
sar.m.longLineOfIdent1.longLineOfIdent2 = new LongLineOfIdent2;
|
||||||
|
sar.m.longLineOfIdent1.longLineOfIdent2.longLineOfIdent3 = new LongLineOfIdent3;
|
||||||
|
|
||||||
|
sar.longLineOfIdent1.longLineOfIdent2.longLineOfIdent3.setFoo(42);
|
||||||
|
assert(sar.longLineOfIdent1.longLineOfIdent2.longLineOfIdent3.unwrap.foo == 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IFTI helper for $(D SafeAccess).
|
||||||
|
*
|
||||||
|
* Returns:
|
||||||
|
* $(D m) with the ability to safely access its members that are class
|
||||||
|
* instances.
|
||||||
|
*/
|
||||||
|
auto ref safeAccess(M)(M m)
|
||||||
|
{
|
||||||
|
return SafeAccess!M(m);
|
||||||
|
}
|
Loading…
Reference in New Issue