svn commit: r1936447 - spamassassin/trunk/lib/Mail/SpamAssassin/Plugin
[email protected] Wed, 22 Jul 2026 06:40:01 -0000
| Newsgroups | gmane.mail.spam.spamassassin.cvs |
|---|---|
| Message-ID | <178470240102.347735.6960988417364799681@svn03-he-fi> |
Author: gbechis
Date: Wed Jul 22 06:40:00 2026
New Revision: 1936447
Log:
precompute the per-hit metadata that run_body_fast_scan() would
otherwise have to re-derive with regexps on every single hit
Modified:
spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Rule2XSBody.pm
Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Rule2XSBody.pm
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Rule2XSBody.pm Wed Jul 22 04:21:17 2026 (r1936446)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Rule2XSBody.pm Wed Jul 22 06:40:00 2026 (r1936447)
@@ -113,10 +113,27 @@ sub setup_test_set_pri {
$conf->{skip_body_rules} ||= { };
$conf->{need_one_line_sub} ||= { };
+ # cache the compiled scan() entry point once, instead of resolving it
+ # by symbolic string dereference on every line of every message
+ $self->{scan_fn}{$ruletype} = \&{$modname.'::scan'};
+
my %longname;
+ my $hit_meta = ($self->{hit_meta}{$ruletype} = { });
foreach my $nameandflags (keys %{$hasrules}) {
my $name = $nameandflags; $name =~ s/,\[.*?\]$//;
$longname{$name} = $nameandflags;
+
+ # precompute the per-hit metadata that run_body_fast_scan() would
+ # otherwise have to re-derive with regexps on every single hit:
+ # the plain rulename, whether the rule is a "lossless" (l=0) match,
+ # and whether it's flagged 'nosubject', all constant for the life
+ # of this compiled ruleset.
+ my $flags = ($nameandflags =~ /,\[(.*?)\]$/) ? $1 : '';
+ $hit_meta->{$nameandflags} = [
+ $name,
+ ($flags =~ /\bl=0/) ? 1 : 0,
+ (($conf->{tflags}->{$name}||'') =~ /\bnosubject\b/) ? 1 : 0,
+ ];
}
my $found = 0;
@@ -230,18 +247,32 @@ sub run_body_fast_scan {
my $do_dbg = (would_log('dbg', 'zoom') > 1);
my $scoresptr = $conf->{scores};
- my $modname = "Mail::SpamAssassin::CompiledRegexps::".$ruletype;
+ my $scanfn = $self->{scan_fn}{$ruletype};
+ my $hit_meta = $self->{hit_meta}{$ruletype};
+ my $fncache = ($self->{fn_cache} ||= { });
+
+ # cache the lowercased body lines on the scanner (PerMsgStatus) object,
+ # since run_body_fast_scan() is called once per priority level that has
+ # zoomed rules, and each call re-scans the very same body lines.
+ # Unfortunately, calling lc() here seems to be the fastest way to
+ # support this and still work with UTF-8 ok, so do it once per message
+ # rather than once per priority level.
+ my $srclines = $params->{lines};
+ my $lc_cache = $scanner->{zoom_lc_lines};
+ if (!$lc_cache || $lc_cache->[0] != $srclines) {
+ $lc_cache = $scanner->{zoom_lc_lines} = [ $srclines, [ map { lc $_ } @{$srclines} ] ];
+ }
+ my $lclines = $lc_cache->[1];
{
no strict "refs";
- my $lineidx;
- foreach my $line (@{$params->{lines}})
+ my $lineidx = 0;
+ foreach my $lcline (@{$lclines})
{
$lineidx++;
+ my $line = $srclines->[$lineidx-1]; # original case, for real-regexp validation
- # unfortunately, calling lc() here seems to be the fastest
- # way to support this and still work with UTF-8 ok
- my $results = &{$modname.'::scan'}(lc $line);
+ my $results = $scanfn->($lcline);
my %alreadydone;
foreach my $ruleandflags (@{$results})
@@ -250,21 +281,19 @@ sub run_body_fast_scan {
next if exists $alreadydone{$ruleandflags};
$alreadydone{$ruleandflags} = undef;
- my $rulename = $ruleandflags;
- my $flags = ($rulename =~ s/,\[(.*?)\]$//)?$1:'';
+ my $info = $hit_meta->{$ruleandflags} or next;
+ my ($rulename, $is_lossless, $nosubject) = @{$info};
# ignore 0-scored rules, of course
next unless $scoresptr->{$rulename};
# skip first line if nosubject tflag
- if ($lineidx == 1 && ($conf->{tflags}->{$rulename}||'') =~ /\bnosubject\b/) {
- next;
- }
+ next if $lineidx == 1 && $nosubject;
# non-lossy rules; the re2c version matches exactly what
# the perl regexp matches, so we don't need to perform
# a validation match to follow up; it's a hit!
- if ($flags =~ /\bl=0/) {
+ if ($is_lossless) {
$scanner->got_hit($rulename, "BODY: ", ruletype => "one_line_body");
# TODO: hit_rule_plugin_code? it's just debugging really
next;
@@ -273,16 +302,21 @@ sub run_body_fast_scan {
# dbg("zoom: base found for $rulename: $line");
# }
- my $fn = 'Mail::SpamAssassin::Plugin::Check::'.
- $rulename.'_one_line_body_test';
-
- # run the real regexp -- on this line alone.
- # don't try this unless the fn exists; this can happen if the
+ # run the real regexp, on this line alone.
+ # Don't try this unless the fn exists; this can happen if the
# installed compiled-rules file contains details of rules
# that are not in our current ruleset (e.g. gets out of
- # sync, or was compiled with extra rulesets installed)
- if (defined &{$fn}) {
- if (!&{$fn} ($scanner, $line) && $do_dbg) {
+ # sync, or was compiled with extra rulesets installed).
+ # the coderef, once resolved (or confirmed absent), is cached for
+ # the rest of the process's lifetime.
+ my $fn = exists $fncache->{$rulename} ? $fncache->{$rulename}
+ : ($fncache->{$rulename} =
+ (defined &{'Mail::SpamAssassin::Plugin::Check::'.$rulename.'_one_line_body_test'})
+ ? \&{'Mail::SpamAssassin::Plugin::Check::'.$rulename.'_one_line_body_test'}
+ : undef);
+
+ if ($fn) {
+ if (!$fn->($scanner, $line) && $do_dbg) {
$self->{rule2xs_misses}->{$rulename}++;
}
}