in lib/Mail/SpamAssassin/Plugin/Check.pm [1048:1260]
sub run_eval_tests {
my ($self, $pms, $testtype, $evalname, $prepend2desc, $priority, @extraevalargs) = @_;
my $master_deadline = $pms->{master_deadline};
if ($pms->{deadline_exceeded}) {
return;
} elsif ($master_deadline && time > $master_deadline) {
info("check: (run_eval) exceeded time limit, skipping further tests");
$pms->{deadline_exceeded} = 1;
return;
} elsif ($self->{main}->call_plugins("have_shortcircuited",
{ permsgstatus => $pms })) {
$pms->{shortcircuited} = 1;
return;
}
my $conf = $pms->{conf};
my $doing_user_rules = $conf->{want_rebuild_for_type}->{$testtype};
if ($doing_user_rules) { $self->{done_user_rules}->{$testtype}++; }
# clean up priority value so it can be used in a subroutine name
my $clean_priority;
($clean_priority = $priority) =~ s/-/neg/;
my $scoreset = $conf->get_score_set();
my $package_name = __PACKAGE__;
my $methodname = '_eval_tests'.
'_type'.$testtype .
'_pri'.$clean_priority .
'_set'.$scoreset;
# Some of the rules are scoreset specific, so we need additional
# subroutines to handle those
if (defined &{"${package_name}::${methodname}"}
&& !$doing_user_rules)
{
my $method = "${package_name}::${methodname}";
#dbg("rules: run_eval_tests - calling previously compiled %s", $method);
my $t = Mail::SpamAssassin::Timeout->new({ deadline => $master_deadline });
my $err = $t->run(sub {
no strict "refs";
&{$method}($pms,@extraevalargs);
});
if ($t->timed_out() && $master_deadline && time > $master_deadline) {
info("check: exceeded time limit in $method, skipping further tests");
$pms->{deadline_exceeded} = 1;
}
return;
}
# look these up once in advance to save repeated lookups in loop below
my $evalhash = $conf->{$evalname}->{$priority};
my $tflagsref = $conf->{tflags};
my $scoresref = $conf->{scores};
my $eval_pluginsref = $conf->{eval_plugins};
my $have_ran_rule = $self->{main}->have_plugin("ran_rule");
# the buffer for the evaluated code
my $evalstr = '';
# conditionally include the dbg in the eval str
my $dbgstr = '';
if (would_log('dbg')) {
$dbgstr = 'dbg("rules: ran eval rule $rulename ======> got hit ($result)");';
}
if ($self->{main}->have_plugin("start_rules")) {
# XXX - should we use helper function here?
$evalstr .= '
$self->{main}->call_plugins("start_rules", {
permsgstatus => $self,
ruletype => "eval",
priority => '.$priority.'
});
';
}
while (my ($rulename, $test) = each %{$evalhash}) {
if ($tflagsref->{$rulename}) {
# If the rule is a net rule, and we are in a non-net scoreset, skip it.
if ($tflagsref->{$rulename} =~ /\bnet\b/) {
next if (($scoreset & 1) == 0);
}
# If the rule is a bayes rule, and we are in a non-bayes scoreset, skip it.
if ($tflagsref->{$rulename} =~ /\blearn\b/) {
next if (($scoreset & 2) == 0);
}
}
# skip if score zeroed
next if !$scoresref->{$rulename};
my $function = untaint_var($test->[0]); # was validated with \w+
if (!$function) {
warn "rules: no eval function defined for $rulename\n";
$pms->{rule_errors}++;
next;
}
if (!exists $conf->{eval_plugins}->{$function}) {
warn "rules: unknown eval '$function' for $rulename\n";
$pms->{rule_errors}++;
next;
}
$evalstr .= '
if ($scoresptr->{q{'.$rulename.'}}) {
$rulename = q#'.$rulename.'#;
';
# only need to set current_rule_name for plugin evals
if ($eval_pluginsref->{$function}) {
# let plugins get the name of the rule that is currently being run,
# and ensure their eval functions exist
$evalstr .= '
$self->{current_rule_name} = $rulename;
$self->register_plugin_eval_glue(q#'.$function.'#);
';
}
if ($would_log_rules_all) {
$evalstr .= '
dbg("rules-all: running eval rule %s (%s)", $rulename, q{'.$function.'});
';
}
$evalstr .= '
eval {
$result = $self->'.$function.'(@extraevalargs, @{$testptr->{$rulename}->[1]}); 1;
} or do {
$result = 0;
die "rules: $@\n" if index($@, "__alarm__ignore__") >= 0;
$self->handle_eval_rule_errors($rulename);
};
';
if ($have_ran_rule) {
# XXX - should we use helper function here?
$evalstr .= '
$self->{main}->call_plugins("ran_rule", {
permsgstatus => $self, ruletype => "eval", rulename => $rulename
});
';
}
# If eval returns undef, it means rule is running async and
# will be marked ready later by rule_ready() or got_hit()
$evalstr .= '
if (defined $result) {
if ($result) {
$self->got_hit($rulename, $prepend2desc, ruletype => "eval", value => $result);
'.$dbgstr.'
} else {
$self->rule_ready($rulename);
}
}
}
';
}
# don't free the eval ruleset here -- we need it in the compiled code!
# nothing done in the loop, that means no rules
return unless ($evalstr);
$evalstr = <<"EOT";
{
package $package_name;
sub ${methodname} {
my (\$self, \@extraevalargs) = \@_;
my \$testptr = \$self->{conf}->{$evalname}->{$priority};
my \$scoresptr = \$self->{conf}->{scores};
my \$prepend2desc = q#$prepend2desc#;
my \$rulename;
my \$result;
$evalstr
}
1;
}
EOT
undef &{$methodname};
dbg("rules: run_eval_tests - compiling eval code: %s, priority %s",
$testtype, $priority);
# dbg("rules: eval code(3): %s", $evalstr);
my $eval_result;
{ my $timer = $self->{main}->time_method('compile_eval');
$eval_result = eval($evalstr);
}
if (!$eval_result) {
my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
warn "rules: failed to compile eval tests, skipping some: $eval_stat\n";
$pms->{rule_errors}++;
}
else {
my $method = "${package_name}::${methodname}";
push (@TEMPORARY_METHODS, $methodname);
# dbg("rules: run_eval_tests - calling the just compiled %s", $method);
my $t = Mail::SpamAssassin::Timeout->new({ deadline => $master_deadline });
my $err = $t->run(sub {
no strict "refs";
&{$method}($pms,@extraevalargs);
});
if ($t->timed_out() && $master_deadline && time > $master_deadline) {
info("check: exceeded time limit in $method, skipping further tests");
$pms->{deadline_exceeded} = 1;
}
}
}