]> granicus.if.org Git - re2c/commitdiff
Simplified [-Wmatch-empty-rule] analyses.
authorUlya Trofimovich <skvadrik@gmail.com>
Wed, 17 Feb 2016 14:46:43 +0000 (14:46 +0000)
committerUlya Trofimovich <skvadrik@gmail.com>
Wed, 17 Feb 2016 14:46:43 +0000 (14:46 +0000)
Before this patch [-Wmatch-empty-rule] was based on:
    - DFA structural analyses (skeleton phase)
    - rule reachability analyses (skeleton phase)

Now it is based on:
    - NFA structural analyses (NFA phase)
    - rule reachability analyses (skeleton phase)

It's much easier to find nullable rules in NFA than in DFA.
The problem with DFA is in rules with trailing context, both
dynamic and especially static (as it leaves no trace in DFA
states). re2c currently treats static context as dynamic, but
it will change soon.

On the other side NFA may give some false positives because of
unreachable rules:
    [^] {}
    ""  {}
infinite rules:
    [^]* {}
or self-shadowing rules:
    [^]?
Reachability analyses in skeleton helps to filter out unreachable
and infinite rules, but not self-shadowing ones.

16 files changed:
re2c/Makefile.am
re2c/src/codegen/emit_dfa.cc
re2c/src/ir/compile.cc
re2c/src/ir/regexp/nullable.cc [new file with mode: 0644]
re2c/src/ir/regexp/regexp.h
re2c/src/ir/regexp/regexp_alt.h
re2c/src/ir/regexp/regexp_cat.h
re2c/src/ir/regexp/regexp_close.h
re2c/src/ir/regexp/regexp_match.h
re2c/src/ir/regexp/regexp_null.h
re2c/src/ir/regexp/regexp_rule.h
re2c/src/ir/skeleton/match_empty.cc [deleted file]
re2c/src/ir/skeleton/skeleton.cc
re2c/src/ir/skeleton/skeleton.h
re2c/src/ir/skeleton/unreachable_nullable.cc [moved from re2c/src/ir/skeleton/unreachable.cc with 55% similarity]
re2c/test/bug57_original.bi--case-insensitive.c

index f1c416a2d491dc8738f9441d7e5e7d04890fc38a..c058e01a675e61755ed67527d2cb0cd46b0c02d2 100644 (file)
@@ -103,16 +103,16 @@ SRC = \
        src/ir/regexp/encoding/utf16/utf16.cc \
        src/ir/regexp/encoding/utf16/utf16_range.cc \
        src/ir/regexp/fixed_length.cc \
+       src/ir/regexp/nullable.cc \
        src/ir/regexp/regexp.cc \
        src/ir/compile.cc \
        src/ir/rule_rank.cc \
        src/ir/skeleton/control_flow.cc \
        src/ir/skeleton/generate_code.cc \
        src/ir/skeleton/generate_data.cc \
-       src/ir/skeleton/match_empty.cc \
        src/ir/skeleton/maxlen.cc \
        src/ir/skeleton/skeleton.cc \
-       src/ir/skeleton/unreachable.cc \
+       src/ir/skeleton/unreachable_nullable.cc \
        src/ir/skeleton/way.cc \
        src/main.cc \
        src/parse/code.cc \
index 2ec642647b738a3194a70a5bf1782cf24f6bda08..16cccfa2e697ebb0f09b2ae648434f15091bed46 100644 (file)
@@ -137,8 +137,7 @@ void DFA::emit(Output & output, uint32_t& ind, bool isLastCond, bool& bPrologBra
        head->action.set_initial (initial_label, head->action.type == Action::SAVE);
 
        skeleton->warn_undefined_control_flow ();
-       skeleton->warn_unreachable_rules ();
-       skeleton->warn_match_empty ();
+       skeleton->warn_unreachable_nullable_rules ();
 
        if (opts->target == opt_t::SKELETON)
        {
index b38b398a1be74188830aa12f7657ab9c8c7b7581..f15d750dd2220f9c5b232c9583e2143dc883ff90 100644 (file)
@@ -54,6 +54,7 @@ smart_ptr<DFA> compile (Spec & spec, Output & output, const std::string & cond,
        // skeleton must be constructed after DFA construction
        // but prior to any other DFA transformations
        Skeleton *skeleton = new Skeleton(dfa, cs, spec.rules, name, cond, line);
+       spec.re->nullable_rules(skeleton->nullable_rules);
 
        minimization(dfa);
 
diff --git a/re2c/src/ir/regexp/nullable.cc b/re2c/src/ir/regexp/nullable.cc
new file mode 100644 (file)
index 0000000..f7eaba8
--- /dev/null
@@ -0,0 +1,61 @@
+#include "src/ir/regexp/regexp.h"
+#include "src/ir/regexp/regexp_alt.h"
+#include "src/ir/regexp/regexp_cat.h"
+#include "src/ir/regexp/regexp_close.h"
+#include "src/ir/regexp/regexp_match.h"
+#include "src/ir/regexp/regexp_null.h"
+#include "src/ir/regexp/regexp_rule.h"
+
+namespace re2c
+{
+
+bool AltOp::nullable() const
+{
+       return exp1->nullable()
+               || exp2->nullable();
+}
+
+bool CatOp::nullable() const
+{
+       return exp1->nullable()
+               && exp2->nullable();
+}
+
+bool CloseOp::nullable() const
+{
+       return true;
+}
+
+bool MatchOp::nullable() const
+{
+       return false;
+}
+
+bool NullOp::nullable() const
+{
+       return true;
+}
+
+bool RuleOp::nullable() const
+{
+       return exp->nullable();
+}
+
+void RegExp::nullable_rules(std::set<rule_rank_t>&) const {}
+
+void AltOp::nullable_rules(std::set<rule_rank_t> &rs) const
+{
+       exp1->nullable_rules(rs);
+       exp2->nullable_rules(rs);
+}
+
+void RuleOp::nullable_rules(std::set<rule_rank_t> &rs) const
+{
+       if (exp->nullable())
+       {
+               rs.insert(rank);
+       }
+}
+
+} // end namespace re2c
+
index 5d344dd34e3b14f1c9ef56bced1fc5ca862b06c6..bc927e1c18cd7673e27c0d112994f78f6dd12d8a 100644 (file)
@@ -6,6 +6,7 @@
 #include <set>
 #include <vector>
 
+#include "src/ir/rule_rank.h"
 #include "src/util/free_list.h"
 #include "src/util/forbid_copy.h"
 
@@ -33,6 +34,8 @@ public:
        virtual void split (std::set<uint32_t> &) = 0;
        virtual uint32_t calc_size() const = 0;
        virtual uint32_t fixedLength ();
+       virtual bool nullable() const = 0;
+       virtual void nullable_rules(std::set<rule_rank_t>&) const;
        virtual nfa_state_t *compile(nfa_t &nfa, nfa_state_t *n) = 0;
        virtual void display (std::ostream &) const = 0;
        friend std::ostream & operator << (std::ostream & o, const RegExp & re);
index 6f1c8ea4872767d2eccb163bddc1ed71c8668b2c..d7261a681f1d3b957e9a2e1c09fe69f6798b31bc 100644 (file)
@@ -19,6 +19,8 @@ public:
        void split (std::set<uint32_t> &);
        uint32_t calc_size() const;
        uint32_t fixedLength ();
+       bool nullable() const;
+       void nullable_rules(std::set<rule_rank_t>&) const;
        nfa_state_t *compile(nfa_t &nfa, nfa_state_t *n);
        void display (std::ostream & o) const;
        friend RegExp * mkAlt (RegExp *, RegExp *);
index d8176212ed34cd3d6c201af28ce3ef624e6e25bc..93c13a11668213a08d1cc2793225bd3b275c0ca5 100644 (file)
@@ -19,6 +19,7 @@ public:
        void split (std::set<uint32_t> &);
        uint32_t calc_size() const;
        uint32_t fixedLength ();
+       bool nullable() const;
        nfa_state_t *compile(nfa_t &nfa, nfa_state_t *n);
        void display (std::ostream & o) const;
 
index 02bea20f3e9d26a2ee89611420e166b1b60143b7..afd72373d08e8f7c2eb0d426bb4eb18853e83ef7 100644 (file)
@@ -16,6 +16,7 @@ public:
        {}
        void split (std::set<uint32_t> &);
        uint32_t calc_size() const;
+       bool nullable() const;
        nfa_state_t *compile(nfa_t &nfa, nfa_state_t *n);
        void display (std::ostream & o) const;
 
index 903697b6498e354ffb82882df83e48c02dfcf67d..cccbf4aaa162d4a9ff042d98bd4915c7d311d5ce 100644 (file)
@@ -18,6 +18,7 @@ public:
        void split (std::set<uint32_t> &);
        uint32_t calc_size() const;
        uint32_t fixedLength ();
+       bool nullable() const;
        nfa_state_t *compile(nfa_t &nfa, nfa_state_t *n);
        void display (std::ostream & o) const;
 
index 8168dbe559aabf8c03ef1ca9c9e744e766f7cd85..ebefb24ddd99b86be7e64609171a5388520c6d73 100644 (file)
@@ -12,6 +12,7 @@ public:
        void split (std::set<uint32_t> &);
        uint32_t calc_size() const;
        uint32_t fixedLength ();
+       bool nullable() const;
        nfa_state_t *compile(nfa_t &nfa, nfa_state_t *n);
        void display (std::ostream & o) const;
 };
index 1519fa23368d20212a881b81190c4e6242ffb3b2..d3456e1caa37497ebb44e7d614d1c1dfb953caaf 100644 (file)
@@ -4,7 +4,6 @@
 #include <string>
 
 #include "src/ir/regexp/regexp.h"
-#include "src/ir/rule_rank.h"
 #include "src/parse/code.h"
 
 namespace re2c
@@ -39,6 +38,8 @@ public:
                , code (c)
                , newcond (cond ? *cond : "")
        {}
+       bool nullable() const;
+       void nullable_rules(std::set<rule_rank_t>&) const;
        void display (std::ostream & o) const;
        void split (std::set<uint32_t> &);
        uint32_t calc_size() const;
diff --git a/re2c/src/ir/skeleton/match_empty.cc b/re2c/src/ir/skeleton/match_empty.cc
deleted file mode 100644 (file)
index 16fba61..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-#include <map>
-#include <set>
-
-#include "src/conf/warn.h"
-#include "src/globals.h"
-#include "src/ir/rule_rank.h"
-#include "src/ir/skeleton/path.h"
-#include "src/ir/skeleton/skeleton.h"
-#include "src/parse/rules.h"
-
-namespace re2c
-{
-
-void Skeleton::warn_match_empty ()
-{
-       Node & head = nodes[0];
-
-       head.calc_reachable ();
-       const std::set<rule_t> & reach = head.reachable;
-
-       // warn about rules that match empty string
-       if (!head.rule.rank.is_none ())
-       {
-               bool reachable = head.end ();
-               for (std::set<rule_t>::const_iterator i = reach.begin ();
-                       !reachable && i != reach.end (); ++i)
-               {
-                       reachable |= i->rank.is_none ();
-               }
-               if (reachable)
-               {
-                       warn.match_empty_string (rules[head.rule.rank].line);
-               }
-       }
-
-       // warn about rules that match empty string with nonempty trailing context
-       if (head.ctx)
-       {
-               for (std::set<rule_t>::const_iterator i = reach.begin (); i != reach.end (); ++i)
-               {
-                       if (i->restorectx)
-                       {
-                               warn.match_empty_string (rules[i->rank].line);
-                       }
-               }
-       }
-}
-
-} // namespace re2c
index deee1133406fae26b1af18852cfa65bda3668915..9e4e52eb32b510428b4accd8a1582328008f200e 100644 (file)
@@ -84,6 +84,7 @@ Skeleton::Skeleton
        , nodes (new Node [nodes_count + 1]) // +1 for default state
        , sizeof_key (4)
        , rules (rs)
+       , nullable_rules ()
 {
        const size_t nc = cs.size() - 1;
 
index 78c082716404baa3b44af56e5ce57d738b12f7b6..13b3f366c7188d6953d2ef89ccf5eee0c5e49a3d 100644 (file)
@@ -86,7 +86,7 @@ struct Node
        uint32_t dist;
 
        // rules reachable from this node (including absent rule)
-       std::set<rule_t> reachable;
+       std::set<rule_rank_t> reachable;
 
        // path to end node (for constructing path cover)
        path_t * suffix;
@@ -114,6 +114,7 @@ struct Skeleton
        Node * nodes;
        size_t sizeof_key;
        rules_t rules;
+       std::set<rule_rank_t> nullable_rules;
 
        Skeleton
                ( const dfa_t &dfa
@@ -125,8 +126,7 @@ struct Skeleton
                );
        ~Skeleton ();
        void warn_undefined_control_flow ();
-       void warn_unreachable_rules ();
-       void warn_match_empty ();
+       void warn_unreachable_nullable_rules ();
        void emit_data (const char * fname);
        static void emit_prolog (OutputFile & o);
        void emit_start
similarity index 55%
rename from re2c/src/ir/skeleton/unreachable.cc
rename to re2c/src/ir/skeleton/unreachable_nullable.cc
index fac41dfc365c9ee6376eda8c78d6a79f5d814d1a..b4a396a64fc843a6e4dde7c67f5e130dfc72aace 100644 (file)
@@ -21,7 +21,7 @@ void Node::calc_reachable ()
        }
        else if (end ())
        {
-               reachable.insert (rule);
+               reachable.insert (rule.rank);
        }
        else if (loop < 2)
        {
@@ -34,23 +34,24 @@ void Node::calc_reachable ()
        }
 }
 
-void Skeleton::warn_unreachable_rules ()
+void Skeleton::warn_unreachable_nullable_rules ()
 {
-       nodes->calc_reachable ();
+       // calculate reachable rules
+       nodes->calc_reachable();
        for (uint32_t i = 0; i < nodes_count; ++i)
        {
                const rule_rank_t r1 = nodes[i].rule.rank;
-               const std::set<rule_t> & rs = nodes[i].reachable;
-               for (std::set<rule_t>::const_iterator j = rs.begin (); j != rs.end (); ++j)
+               const std::set<rule_rank_t> & rs = nodes[i].reachable;
+               for (std::set<rule_rank_t>::const_iterator j = rs.begin(); j != rs.end(); ++j)
                {
-                       const rule_rank_t r2 = j->rank;
-                       if (r1 == r2 || r2.is_none ())
+                       const rule_rank_t r2 = *j;
+                       if (r1 == r2 || r2.is_none())
                        {
                                rules[r1].reachable = true;
                        }
                        else
                        {
-                               rules[r1].shadow.insert (r2);
+                               rules[r1].shadow.insert(r2);
                        }
                }
        }
@@ -60,12 +61,27 @@ void Skeleton::warn_unreachable_rules ()
        //   - infinite rules that consume infinitely many characters and fail on YYFILL, e.g. '[^]*'
        //   - rules that contain never-matching link, e.g. '[]' with option '--empty-class match-none'
        // default rule '*' should not be reported
-       for (rules_t::const_iterator i = rules.begin (); i != rules.end (); ++i)
+       for (rules_t::const_iterator i = rules.begin(); i != rules.end(); ++i)
        {
                const rule_rank_t r = i->first;
-               if (!r.is_none () && !r.is_def () && !rules[r].reachable)
+               if (!r.is_none() && !r.is_def() && !rules[r].reachable)
                {
-                       warn.unreachable_rule (cond, i->second, rules);
+                       warn.unreachable_rule(cond, i->second, rules);
+               }
+       }
+
+       // warn about nullable rules:
+       //    - rules that match empty string
+       //    - rules that match empty strins with nonempty trailing context
+       // false positives on partially shadowed (yet reachable) rules, e.g.:
+       //     [^]?
+       for (std::set<rule_rank_t>::const_iterator i = nullable_rules.begin();
+               i != nullable_rules.end(); ++i)
+       {
+               const rule_info_t &ri = rules[*i];
+               if (ri.reachable)
+               {
+                       warn.match_empty_string(ri.line);
                }
        }
 }
index a0bf5cff942fb4d4a91d1d9618f4bce8c9aafd36..7b017094af9e6c47b20f4447b5fbd7de21d02d9e 100644 (file)
@@ -9372,6 +9372,7 @@ re2c: warning: line 94: rule matches empty string [-Wmatch-empty-string]
 re2c: warning: line 105: rule matches empty string [-Wmatch-empty-string]
 re2c: warning: line 119: rule matches empty string [-Wmatch-empty-string]
 re2c: warning: line 133: rule matches empty string [-Wmatch-empty-string]
+re2c: warning: line 134: rule matches empty string [-Wmatch-empty-string]
 re2c: warning: line 149: rule matches empty string [-Wmatch-empty-string]
 re2c: warning: line 158: rule matches empty string [-Wmatch-empty-string]
 re2c: warning: line 170: rule matches empty string [-Wmatch-empty-string]