use
B
qw(walkoptree_slow main_root walksymtable svref_2object parents
OPf_WANT_LIST OPf_WANT OPf_STACKED G_ARRAY
)
;
my
$file
=
"unknown"
;
my
$line
= 0;
my
$curstash
=
"main"
;
my
%check
;
my
%implies_ok_context
;
BEGIN {
map
(
$implies_ok_context
{
$_
}++,
qw(scalar av2arylen aelem aslice helem hslice
keys values hslice defined undef delete)
);
}
my
@default_checks
=
qw(context)
;
my
%valid_check
;
BEGIN {
map
(
$valid_check
{
$_
}++,
qw(context implicit_read implicit_write dollar_underscore
private_names undefined_subs regexp_variables)
);
}
my
(
$debug_op
);
my
%done_cv
;
my
@extra_packages
;
sub
warning {
my
$format
= (
@_
< 2) ?
"%s"
:
shift
;
warn
sprintf
(
"$format at %s line %d\n"
,
@_
,
$file
,
$line
);
}
sub
gimme {
my
$op
=
shift
;
my
$flags
=
$op
->flags;
if
(
$flags
& OPf_WANT) {
return
((
$flags
& OPf_WANT_LIST) ? 1 : 0);
}
return
undef
;
}
sub
B::OP::lint {}
sub
B::COP::lint {
my
$op
=
shift
;
if
(
$op
->name eq
"nextstate"
) {
$file
=
$op
->file;
$line
=
$op
->line;
$curstash
=
$op
->stash->NAME;
}
}
sub
B::UNOP::lint {
my
$op
=
shift
;
my
$opname
=
$op
->name;
if
(
$check
{context} && (
$opname
eq
"rv2av"
||
$opname
eq
"rv2hv"
)) {
my
$parent
= parents->[0];
my
$pname
=
$parent
->name;
return
if
gimme(
$op
) ||
$implies_ok_context
{
$pname
};
if
(
$pname
eq
"null"
) {
my
$gpname
= parents->[1]->name;
return
if
$gpname
eq
"enteriter"
||
$gpname
eq
"delete"
;
}
warning(
"Implicit scalar context for %s in %s"
,
$opname
eq
"rv2av"
?
"array"
:
"hash"
,
$parent
->desc);
}
if
(
$check
{private_names} &&
$opname
eq
"method"
) {
my
$methop
=
$op
->first;
if
(
$methop
->name eq
"const"
) {
my
$method
=
$methop
->sv->PV;
if
(
$method
=~ /^_/ && !
defined
(&{
"$curstash\::$method"
})) {
warning(
"Illegal reference to private method name $method"
);
}
}
}
}
sub
B::PMOP::lint {
my
$op
=
shift
;
if
(
$check
{implicit_read}) {
if
(
$op
->name eq
"match"
&& !(
$op
->flags & OPf_STACKED)) {
warning(
'Implicit match on $_'
);
}
}
if
(
$check
{implicit_write}) {
if
(
$op
->name eq
"subst"
&& !(
$op
->flags & OPf_STACKED)) {
warning(
'Implicit substitution on $_'
);
}
}
}
sub
B::LOOP::lint {
my
$op
=
shift
;
if
(
$check
{implicit_read} ||
$check
{implicit_write}) {
if
(
$op
->name eq
"enteriter"
) {
my
$last
=
$op
->
last
;
if
(
$last
->name eq
"gv"
&&
$last
->gv->NAME eq
"_"
) {
warning(
'Implicit use of $_ in foreach'
);
}
}
}
}
sub
B::SVOP::lint {
my
$op
=
shift
;
if
(
$check
{dollar_underscore} &&
$op
->name eq
"gvsv"
&&
$op
->gv->NAME eq
"_"
)
{
warning(
'Use of $_'
);
}
if
(
$check
{private_names}) {
my
$opname
=
$op
->name;
if
(
$opname
eq
"gv"
||
$opname
eq
"gvsv"
) {
my
$gv
=
$op
->gv;
if
(
$gv
->NAME =~ /^_./ &&
$gv
->STASH->NAME ne
$curstash
) {
warning(
'Illegal reference to private name %s'
,
$gv
->NAME);
}
}
}
if
(
$check
{undefined_subs}) {
if
(
$op
->name eq
"gv"
&&
$op
->
next
->name eq
"entersub"
)
{
my
$gv
=
$op
->gv;
my
$subname
=
$gv
->STASH->NAME .
"::"
.
$gv
->NAME;
no
strict
'refs'
;
if
(!
defined
(
&$subname
)) {
$subname
=~ s/^main:://;
warning(
'Undefined subroutine %s called'
,
$subname
);
}
}
}
if
(
$check
{regexp_variables} &&
$op
->name eq
"gvsv"
) {
my
$name
=
$op
->gv->NAME;
if
(
$name
=~ /^[&'`]$/) {
warning(
'Use of regexp variable $%s'
,
$name
);
}
}
}
sub
B::GV::lintcv {
my
$gv
=
shift
;
my
$cv
=
$gv
->CV;
return
if
!
$$cv
||
$done_cv
{
$$cv
}++;
my
$root
=
$cv
->ROOT;
walkoptree_slow(
$root
,
"lint"
)
if
$$root
;
}
sub
do_lint {
my
%search_pack
;
walkoptree_slow(main_root,
"lint"
)
if
${main_root()};
no
strict
qw(vars refs)
;
my
$sym
;
local
(
*glob
);
while
((
$sym
,
*glob
) =
each
%{
"main::"
}) {
svref_2object(\
*glob
)->EGV->lintcv
unless
$sym
=~ /::$/;
}
map
{
$search_pack
{
$_
} = 1 }
@extra_packages
;
walksymtable(\%{
"main::"
},
"lintcv"
,
sub
{
my
$package
=
shift
;
$package
=~ s/::$//;
return
exists
$search_pack
{
$package
};
});
}
sub
compile {
my
@options
=
@_
;
my
(
$option
,
$opt
,
$arg
);
for
$opt
(
@default_checks
) {
$check
{
$opt
} = 1;
}
OPTION:
while
(
$option
=
shift
@options
) {
if
(
$option
=~ /^-(.)(.*)/) {
$opt
= $1;
$arg
= $2;
}
else
{
unshift
@options
,
$option
;
last
OPTION;
}
if
(
$opt
eq
"-"
&&
$arg
eq
"-"
) {
shift
@options
;
last
OPTION;
}
elsif
(
$opt
eq
"D"
) {
$arg
||=
shift
@options
;
foreach
$arg
(
split
(//,
$arg
)) {
if
(
$arg
eq
"o"
) {
B->debug(1);
}
elsif
(
$arg
eq
"O"
) {
$debug_op
= 1;
}
}
}
elsif
(
$opt
eq
"u"
) {
$arg
||=
shift
@options
;
push
(
@extra_packages
,
$arg
);
}
}
foreach
$opt
(
@default_checks
,
@options
) {
$opt
=~
tr
/-/_/;
if
(
$opt
eq
"all"
) {
%check
=
%valid_check
;
}
elsif
(
$opt
eq
"none"
) {
%check
= ();
}
else
{
if
(
$opt
=~ s/^
no
-//) {
$check
{
$opt
} = 0;
}
else
{
$check
{
$opt
} = 1;
}
warn
"No such check: $opt\n"
unless
defined
$valid_check
{
$opt
};
}
}
return
\
&do_lint
;
}
1;