mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-08-22 10:07:12 +00:00
Split out parsing and expression trees from regexp.y
Start of splitting regexp.y into logical components instead of the mess it is today. Split out the expr-tree and parsing components from regexp.y int expr-tree.x and parse.y and since regexp.y no longer does parsing rename it to hfa.cc Some code cleanups snuck their way into this patch and since I am to lazy to redo it, I have left them in. Signed-off-by: John Johansen <john.johansen@canonical.com> Acked-By: Steve Beattie <sbeattie@ubuntu.com>
This commit is contained in:
parent
50760ef05b
commit
846cee5066
@ -12,14 +12,20 @@ BISON := bison
|
|||||||
|
|
||||||
all : ${TARGET}
|
all : ${TARGET}
|
||||||
|
|
||||||
libapparmor_re.a: regexp.o
|
libapparmor_re.a: parse.o expr-tree.o hfa.o
|
||||||
ar ${ARFLAGS} $@ $^
|
ar ${ARFLAGS} $@ $^
|
||||||
|
|
||||||
regexp.o : regexp.cc apparmor_re.h
|
expr-tree.o: expr-tree.cc expr-tree.h
|
||||||
$(LINK.cc) $< -c -o $@
|
$(LINK.cc) $< -c -o $@
|
||||||
|
|
||||||
regexp.cc : regexp.y flex-tables.h ../immunix.h
|
hfa.o: hfa.cc apparmor_re.h
|
||||||
|
$(LINK.cc) $< -c -o $@
|
||||||
|
|
||||||
|
parse.o : parse.cc apparmor_re.h expr-tree.h
|
||||||
|
$(LINK.cc) $< -c -o $@
|
||||||
|
|
||||||
|
parse.cc : parse.y flex-tables.h ../immunix.h
|
||||||
${BISON} -o $@ $<
|
${BISON} -o $@ $<
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f regexp.o regexp.cc regexp.so regexp.a regexp ${TARGET}
|
rm -f *.o parse.cc ${TARGET}
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
#ifndef APPARMOR_RE_H
|
#ifndef APPARMOR_RE_H
|
||||||
#define APPARMOR_RE_H
|
#define APPARMOR_RE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
typedef enum dfaflags {
|
typedef enum dfaflags {
|
||||||
DFA_CONTROL_EQUIV = 1 << 0,
|
DFA_CONTROL_EQUIV = 1 << 0,
|
||||||
DFA_CONTROL_TREE_NORMAL = 1 << 1,
|
DFA_CONTROL_TREE_NORMAL = 1 << 1,
|
||||||
|
576
parser/libapparmor_re/expr-tree.cc
Normal file
576
parser/libapparmor_re/expr-tree.cc
Normal file
@ -0,0 +1,576 @@
|
|||||||
|
/*
|
||||||
|
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
|
||||||
|
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
|
||||||
|
* Copyright 2009-2010 Canonical Ltd.
|
||||||
|
*
|
||||||
|
* The libapparmor library is licensed under the terms of the GNU
|
||||||
|
* Lesser General Public License, version 2.1. Please see the file
|
||||||
|
* COPYING.LGPL.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Functions to create/manipulate an expression tree for regular expressions
|
||||||
|
* that have been parsed.
|
||||||
|
*
|
||||||
|
* The expression tree can be used directly after the parse creates it, or
|
||||||
|
* it can be factored so that the set of important nodes is smaller.
|
||||||
|
* Having a reduced set of important nodes generally results in a dfa that
|
||||||
|
* is closer to minimum (fewer redundant states are created). It also
|
||||||
|
* results in fewer important nodes in a the state set during subset
|
||||||
|
* construction resulting in less memory used to create a dfa.
|
||||||
|
*
|
||||||
|
* Generally it is worth doing expression tree simplification before dfa
|
||||||
|
* construction, if the regular expression tree contains any alternations.
|
||||||
|
* Even if the regular expression doesn't simplification should be fast
|
||||||
|
* enough that it can be used with minimal overhead.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "expr-tree.h"
|
||||||
|
#include "apparmor_re.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* Use a single static EpsNode as it carries no node specific information */
|
||||||
|
EpsNode epsnode;
|
||||||
|
|
||||||
|
|
||||||
|
ostream& operator<<(ostream& os, uchar c)
|
||||||
|
{
|
||||||
|
const char *search = "\a\033\f\n\r\t|*+[](). ",
|
||||||
|
*replace = "aefnrt|*+[](). ", *s;
|
||||||
|
|
||||||
|
if ((s = strchr(search, c)) && *s != '\0')
|
||||||
|
os << '\\' << replace[s - search];
|
||||||
|
else if (c < 32 || c >= 127)
|
||||||
|
os << '\\' << '0' << char('0' + (c >> 6))
|
||||||
|
<< char('0' + ((c >> 3) & 7)) << char('0' + (c & 7));
|
||||||
|
else
|
||||||
|
os << (char)c;
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text-dump a state (for debugging).
|
||||||
|
*/
|
||||||
|
ostream& operator<<(ostream& os, const NodeSet& state)
|
||||||
|
{
|
||||||
|
os << '{';
|
||||||
|
if (!state.empty()) {
|
||||||
|
NodeSet::iterator i = state.begin();
|
||||||
|
for(;;) {
|
||||||
|
os << (*i)->label;
|
||||||
|
if (++i == state.end())
|
||||||
|
break;
|
||||||
|
os << ',';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os << '}';
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
ostream& operator<<(ostream& os, Node& node)
|
||||||
|
{
|
||||||
|
node.dump(os);
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hash_NodeSet - generate a hash for the Nodes in the set
|
||||||
|
*/
|
||||||
|
unsigned long hash_NodeSet(NodeSet *ns)
|
||||||
|
{
|
||||||
|
unsigned long hash = 5381;
|
||||||
|
|
||||||
|
for (NodeSet::iterator i = ns->begin(); i != ns->end(); i++) {
|
||||||
|
hash = ((hash << 5) + hash) + (unsigned long) *i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* label_nodes - label the node positions for pretty-printing debug output
|
||||||
|
*
|
||||||
|
* TODO: separate - node labels should be separate and optional, if not
|
||||||
|
* present pretty printing should use Node address
|
||||||
|
*/
|
||||||
|
void label_nodes(Node *root)
|
||||||
|
{
|
||||||
|
int nodes = 1;
|
||||||
|
for (depth_first_traversal i(root); i; i++)
|
||||||
|
i->label = nodes++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text-dump the syntax tree (for debugging).
|
||||||
|
*/
|
||||||
|
void Node::dump_syntax_tree(ostream& os)
|
||||||
|
{
|
||||||
|
for (depth_first_traversal i(this); i; i++) {
|
||||||
|
os << i->label << '\t';
|
||||||
|
if ((*i)->child[0] == 0)
|
||||||
|
os << **i << '\t' << (*i)->followpos << endl;
|
||||||
|
else {
|
||||||
|
if ((*i)->child[1] == 0)
|
||||||
|
os << (*i)->child[0]->label << **i;
|
||||||
|
else
|
||||||
|
os << (*i)->child[0]->label << **i
|
||||||
|
<< (*i)->child[1]->label;
|
||||||
|
os << '\t' << (*i)->firstpos
|
||||||
|
<< (*i)->lastpos << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Normalize the regex parse tree for factoring and cancelations. Normalization
|
||||||
|
* reorganizes internal (alt and cat) nodes into a fixed "normalized" form that
|
||||||
|
* simplifies factoring code, in that it produces a canonicalized form for
|
||||||
|
* the direction being normalized so that the factoring code does not have
|
||||||
|
* to consider as many cases.
|
||||||
|
*
|
||||||
|
* left normalization (dir == 0) uses these rules
|
||||||
|
* (E | a) -> (a | E)
|
||||||
|
* (a | b) | c -> a | (b | c)
|
||||||
|
* (ab)c -> a(bc)
|
||||||
|
*
|
||||||
|
* right normalization (dir == 1) uses the same rules but reversed
|
||||||
|
* (a | E) -> (E | a)
|
||||||
|
* a | (b | c) -> (a | b) | c
|
||||||
|
* a(bc) -> (ab)c
|
||||||
|
*
|
||||||
|
* Note: This is written iteratively for a given node (the top node stays
|
||||||
|
* fixed and the children are rotated) instead of recursively.
|
||||||
|
* For a given node under examination rotate over nodes from
|
||||||
|
* dir to !dir. Until no dir direction node meets the criterial.
|
||||||
|
* Then recurse to the children (which will have a different node type)
|
||||||
|
* to make sure they are normalized.
|
||||||
|
* Normalization of a child node is guarenteed to not affect the
|
||||||
|
* normalization of the parent.
|
||||||
|
*
|
||||||
|
* For cat nodes the depth first traverse order is guarenteed to be
|
||||||
|
* maintained. This is not necessary for altnodes.
|
||||||
|
*
|
||||||
|
* Eg. For left normalization
|
||||||
|
*
|
||||||
|
* |1 |1
|
||||||
|
* / \ / \
|
||||||
|
* |2 T -> a |2
|
||||||
|
* / \ / \
|
||||||
|
* |3 c b |3
|
||||||
|
* / \ / \
|
||||||
|
* a b c T
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static void rotate_node(Node *t, int dir) {
|
||||||
|
// (a | b) | c -> a | (b | c)
|
||||||
|
// (ab)c -> a(bc)
|
||||||
|
Node *left = t->child[dir];
|
||||||
|
t->child[dir] = left->child[dir];
|
||||||
|
left->child[dir] = left->child[!dir];
|
||||||
|
left->child[!dir] = t->child[!dir];
|
||||||
|
t->child[!dir] = left;
|
||||||
|
}
|
||||||
|
|
||||||
|
void normalize_tree(Node *t, int dir)
|
||||||
|
{
|
||||||
|
if (dynamic_cast<LeafNode *>(t))
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if ((&epsnode == t->child[dir]) &&
|
||||||
|
(&epsnode != t->child[!dir]) &&
|
||||||
|
dynamic_cast<TwoChildNode *>(t)) {
|
||||||
|
// (E | a) -> (a | E)
|
||||||
|
// Ea -> aE
|
||||||
|
Node *c = t->child[dir];
|
||||||
|
t->child[dir] = t->child[!dir];
|
||||||
|
t->child[!dir] = c;
|
||||||
|
// Don't break here as 'a' may be a tree that
|
||||||
|
// can be pulled up.
|
||||||
|
} else if ((dynamic_cast<AltNode *>(t) &&
|
||||||
|
dynamic_cast<AltNode *>(t->child[dir])) ||
|
||||||
|
(dynamic_cast<CatNode *>(t) &&
|
||||||
|
dynamic_cast<CatNode *>(t->child[dir]))) {
|
||||||
|
// (a | b) | c -> a | (b | c)
|
||||||
|
// (ab)c -> a(bc)
|
||||||
|
rotate_node(t, dir);
|
||||||
|
} else if (dynamic_cast<AltNode *>(t) &&
|
||||||
|
dynamic_cast<CharSetNode *>(t->child[dir]) &&
|
||||||
|
dynamic_cast<CharNode *>(t->child[!dir])) {
|
||||||
|
// [a] | b -> b | [a]
|
||||||
|
Node *c = t->child[dir];
|
||||||
|
t->child[dir] = t->child[!dir];
|
||||||
|
t->child[!dir] = c;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (t->child[dir])
|
||||||
|
normalize_tree(t->child[dir], dir);
|
||||||
|
if (t->child[!dir])
|
||||||
|
normalize_tree(t->child[!dir], dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
//charset conversion is disabled for now,
|
||||||
|
//it hinders tree optimization in some cases, so it need to be either
|
||||||
|
//done post optimization, or have extra factoring rules added
|
||||||
|
#if 0
|
||||||
|
static Node *merge_charset(Node *a, Node *b)
|
||||||
|
{
|
||||||
|
if (dynamic_cast<CharNode *>(a) &&
|
||||||
|
dynamic_cast<CharNode *>(b)) {
|
||||||
|
Chars chars;
|
||||||
|
chars.insert(dynamic_cast<CharNode *>(a)->c);
|
||||||
|
chars.insert(dynamic_cast<CharNode *>(b)->c);
|
||||||
|
CharSetNode *n = new CharSetNode(chars);
|
||||||
|
return n;
|
||||||
|
} else if (dynamic_cast<CharNode *>(a) &&
|
||||||
|
dynamic_cast<CharSetNode *>(b)) {
|
||||||
|
Chars *chars = &dynamic_cast<CharSetNode *>(b)->chars;
|
||||||
|
chars->insert(dynamic_cast<CharNode *>(a)->c);
|
||||||
|
return b;
|
||||||
|
} else if (dynamic_cast<CharSetNode *>(a) &&
|
||||||
|
dynamic_cast<CharSetNode *>(b)) {
|
||||||
|
Chars *from = &dynamic_cast<CharSetNode *>(a)->chars;
|
||||||
|
Chars *to = &dynamic_cast<CharSetNode *>(b)->chars;
|
||||||
|
for (Chars::iterator i = from->begin(); i != from->end(); i++)
|
||||||
|
to->insert(*i);
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
//return ???;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Node *alt_to_charsets(Node *t, int dir)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Node *first = NULL;
|
||||||
|
Node *p = t;
|
||||||
|
Node *i = t;
|
||||||
|
for (;dynamic_cast<AltNode *>(i);) {
|
||||||
|
if (dynamic_cast<CharNode *>(i->child[dir]) ||
|
||||||
|
dynamic_cast<CharNodeSet *>(i->child[dir])) {
|
||||||
|
if (!first) {
|
||||||
|
first = i;
|
||||||
|
p = i;
|
||||||
|
i = i->child[!dir];
|
||||||
|
} else {
|
||||||
|
first->child[dir] = merge_charset(first->child[dir],
|
||||||
|
i->child[dir]);
|
||||||
|
p->child[!dir] = i->child[!dir];
|
||||||
|
Node *tmp = i;
|
||||||
|
i = tmp->child[!dir];
|
||||||
|
tmp->child[!dir] = NULL;
|
||||||
|
tmp->release();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p = i;
|
||||||
|
i = i->child[!dir];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// last altnode of chain check other dir as well
|
||||||
|
if (first && (dynamic_cast<charNode *>(i) ||
|
||||||
|
dynamic_cast<charNodeSet *>(i))) {
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (dynamic_cast<CharNode *>(t->child[dir]) ||
|
||||||
|
dynamic_cast<CharSetNode *>(t->child[dir]))
|
||||||
|
char_test = true;
|
||||||
|
(char_test &&
|
||||||
|
(dynamic_cast<CharNode *>(i->child[dir]) ||
|
||||||
|
dynamic_cast<CharSetNode *>(i->child[dir])))) {
|
||||||
|
*/
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static Node *basic_alt_factor(Node *t, int dir)
|
||||||
|
{
|
||||||
|
if (!dynamic_cast<AltNode *>(t))
|
||||||
|
return t;
|
||||||
|
|
||||||
|
if (t->child[dir]->eq(t->child[!dir])) {
|
||||||
|
// (a | a) -> a
|
||||||
|
Node *tmp = t->child[dir];
|
||||||
|
t->child[dir] = NULL;
|
||||||
|
t->release();
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// (ab) | (ac) -> a(b|c)
|
||||||
|
if (dynamic_cast<CatNode *>(t->child[dir]) &&
|
||||||
|
dynamic_cast<CatNode *>(t->child[!dir]) &&
|
||||||
|
t->child[dir]->child[dir]->eq(t->child[!dir]->child[dir])) {
|
||||||
|
// (ab) | (ac) -> a(b|c)
|
||||||
|
Node *left = t->child[dir];
|
||||||
|
Node *right = t->child[!dir];
|
||||||
|
t->child[dir] = left->child[!dir];
|
||||||
|
t->child[!dir] = right->child[!dir];
|
||||||
|
right->child[!dir] = NULL;
|
||||||
|
right->release();
|
||||||
|
left->child[!dir] = t;
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a | (ab) -> a (E | b) -> a (b | E)
|
||||||
|
if (dynamic_cast<CatNode *>(t->child[!dir]) &&
|
||||||
|
t->child[dir]->eq(t->child[!dir]->child[dir])) {
|
||||||
|
Node *c = t->child[!dir];
|
||||||
|
t->child[dir]->release();
|
||||||
|
t->child[dir] = c->child[!dir];
|
||||||
|
t->child[!dir] = &epsnode;
|
||||||
|
c->child[!dir] = t;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ab | (a) -> a (b | E)
|
||||||
|
if (dynamic_cast<CatNode *>(t->child[dir]) &&
|
||||||
|
t->child[dir]->child[dir]->eq(t->child[!dir])) {
|
||||||
|
Node *c = t->child[dir];
|
||||||
|
t->child[!dir]->release();
|
||||||
|
t->child[dir] = c->child[!dir];
|
||||||
|
t->child[!dir] = &epsnode;
|
||||||
|
c->child[!dir] = t;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Node *basic_simplify(Node *t, int dir)
|
||||||
|
{
|
||||||
|
if (dynamic_cast<CatNode *>(t) &&
|
||||||
|
&epsnode == t->child[!dir]) {
|
||||||
|
// aE -> a
|
||||||
|
Node *tmp = t->child[dir];
|
||||||
|
t->child[dir] = NULL;
|
||||||
|
t->release();
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return basic_alt_factor(t, dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* assumes a normalized tree. reductions shown for left normalization
|
||||||
|
* aE -> a
|
||||||
|
* (a | a) -> a
|
||||||
|
** factoring patterns
|
||||||
|
* a | (a | b) -> (a | b)
|
||||||
|
* a | (ab) -> a (E | b) -> a (b | E)
|
||||||
|
* (ab) | (ac) -> a(b|c)
|
||||||
|
*
|
||||||
|
* returns t - if no simplifications were made
|
||||||
|
* a new root node - if simplifications were made
|
||||||
|
*/
|
||||||
|
Node *simplify_tree_base(Node *t, int dir, bool &mod)
|
||||||
|
{
|
||||||
|
if (dynamic_cast<ImportantNode *>(t))
|
||||||
|
return t;
|
||||||
|
|
||||||
|
for (int i=0; i < 2; i++) {
|
||||||
|
if (t->child[i]) {
|
||||||
|
Node *c = simplify_tree_base(t->child[i], dir, mod);
|
||||||
|
if (c != t->child[i]) {
|
||||||
|
t->child[i] = c;
|
||||||
|
mod = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only iterate on loop if modification made
|
||||||
|
for (;; mod = true) {
|
||||||
|
|
||||||
|
Node *tmp = basic_simplify(t, dir);
|
||||||
|
if (tmp != t) {
|
||||||
|
t = tmp;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* all tests after this must meet 2 alt node condition */
|
||||||
|
if (!dynamic_cast<AltNode *>(t) ||
|
||||||
|
!dynamic_cast<AltNode *>(t->child[!dir]))
|
||||||
|
break;
|
||||||
|
|
||||||
|
// a | (a | b) -> (a | b)
|
||||||
|
// a | (b | (c | a)) -> (b | (c | a))
|
||||||
|
Node *p = t;
|
||||||
|
Node *i = t->child[!dir];
|
||||||
|
for (;dynamic_cast<AltNode *>(i); p = i, i = i->child[!dir]) {
|
||||||
|
if (t->child[dir]->eq(i->child[dir])) {
|
||||||
|
Node *tmp = t->child[!dir];
|
||||||
|
t->child[!dir] = NULL;
|
||||||
|
t->release();
|
||||||
|
t = tmp;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// last altnode of chain check other dir as well
|
||||||
|
if (t->child[dir]->eq(p->child[!dir])) {
|
||||||
|
Node *tmp = t->child[!dir];
|
||||||
|
t->child[!dir] = NULL;
|
||||||
|
t->release();
|
||||||
|
t = tmp;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//exact match didn't work, try factoring front
|
||||||
|
//a | (ac | (ad | () -> (a (E | c)) | (...)
|
||||||
|
//ab | (ac | (...)) -> (a (b | c)) | (...)
|
||||||
|
//ab | (a | (...)) -> (a (b | E)) | (...)
|
||||||
|
Node *pp;
|
||||||
|
int count = 0;
|
||||||
|
Node *subject = t->child[dir];
|
||||||
|
Node *a = subject;
|
||||||
|
if (dynamic_cast<CatNode *>(subject))
|
||||||
|
a = subject->child[dir];
|
||||||
|
|
||||||
|
for (pp = p = t, i = t->child[!dir];
|
||||||
|
dynamic_cast<AltNode *>(i); ) {
|
||||||
|
if ((dynamic_cast<CatNode *>(i->child[dir]) &&
|
||||||
|
a->eq(i->child[dir]->child[dir])) ||
|
||||||
|
(a->eq(i->child[dir]))) {
|
||||||
|
// extract matching alt node
|
||||||
|
p->child[!dir] = i->child[!dir];
|
||||||
|
i->child[!dir] = subject;
|
||||||
|
subject = basic_simplify(i, dir);
|
||||||
|
if (dynamic_cast<CatNode *>(subject))
|
||||||
|
a = subject->child[dir];
|
||||||
|
else
|
||||||
|
a = subject;
|
||||||
|
|
||||||
|
i = p->child[!dir];
|
||||||
|
count++;
|
||||||
|
} else {
|
||||||
|
pp = p; p = i; i = i->child[!dir];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// last altnode in chain check other dir as well
|
||||||
|
if ((dynamic_cast<CatNode *>(i) &&
|
||||||
|
a->eq(i->child[dir])) ||
|
||||||
|
(a->eq(i))) {
|
||||||
|
count++;
|
||||||
|
if (t == p) {
|
||||||
|
t->child[dir] = subject;
|
||||||
|
t = basic_simplify(t, dir);
|
||||||
|
} else {
|
||||||
|
t->child[dir] = p->child[dir];
|
||||||
|
p->child[dir] = subject;
|
||||||
|
pp->child[!dir] = basic_simplify(p, dir);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t->child[dir] = i;
|
||||||
|
p->child[!dir] = subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
int debug_tree(Node *t)
|
||||||
|
{
|
||||||
|
int nodes = 1;
|
||||||
|
|
||||||
|
if (!dynamic_cast<ImportantNode *>(t)) {
|
||||||
|
if (t->child[0])
|
||||||
|
nodes += debug_tree(t->child[0]);
|
||||||
|
if (t->child[1])
|
||||||
|
nodes += debug_tree(t->child[1]);
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void count_tree_nodes(Node *t, struct node_counts *counts)
|
||||||
|
{
|
||||||
|
if (dynamic_cast<AltNode *>(t)) {
|
||||||
|
counts->alt++;
|
||||||
|
count_tree_nodes(t->child[0], counts);
|
||||||
|
count_tree_nodes(t->child[1], counts);
|
||||||
|
} else if (dynamic_cast<CatNode *>(t)) {
|
||||||
|
counts->cat++;
|
||||||
|
count_tree_nodes(t->child[0], counts);
|
||||||
|
count_tree_nodes(t->child[1], counts);
|
||||||
|
} else if (dynamic_cast<PlusNode *>(t)) {
|
||||||
|
counts->plus++;
|
||||||
|
count_tree_nodes(t->child[0], counts);
|
||||||
|
} else if (dynamic_cast<StarNode *>(t)) {
|
||||||
|
counts->star++;
|
||||||
|
count_tree_nodes(t->child[0], counts);
|
||||||
|
} else if (dynamic_cast<CharNode *>(t)) {
|
||||||
|
counts->charnode++;
|
||||||
|
} else if (dynamic_cast<AnyCharNode *>(t)) {
|
||||||
|
counts->any++;
|
||||||
|
} else if (dynamic_cast<CharSetNode *>(t)) {
|
||||||
|
counts->charset++;
|
||||||
|
} else if (dynamic_cast<NotCharSetNode *>(t)) {
|
||||||
|
counts->notcharset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "stdio.h"
|
||||||
|
#include "stdint.h"
|
||||||
|
#include "apparmor_re.h"
|
||||||
|
|
||||||
|
Node *simplify_tree(Node *t, dfaflags_t flags)
|
||||||
|
{
|
||||||
|
bool update;
|
||||||
|
|
||||||
|
if (flags & DFA_DUMP_TREE_STATS) {
|
||||||
|
struct node_counts counts = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||||
|
count_tree_nodes(t, &counts);
|
||||||
|
fprintf(stderr, "expr tree: c %d, [] %d, [^] %d, | %d, + %d, * %d, . %d, cat %d\n", counts.charnode, counts.charset, counts.notcharset, counts.alt, counts.plus, counts.star, counts.any, counts.cat);
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
update = false;
|
||||||
|
//default to right normalize first as this reduces the number
|
||||||
|
//of trailing nodes which might follow an internal *
|
||||||
|
//or **, which is where state explosion can happen
|
||||||
|
//eg. in one test this makes the difference between
|
||||||
|
// the dfa having about 7 thousands states,
|
||||||
|
// and it having about 1.25 million states
|
||||||
|
int dir = 1;
|
||||||
|
if (flags & DFA_CONTROL_TREE_LEFT)
|
||||||
|
dir = 0;
|
||||||
|
for (int count = 0; count < 2; count++) {
|
||||||
|
bool modified;
|
||||||
|
do {
|
||||||
|
modified = false;
|
||||||
|
if (flags & DFA_CONTROL_TREE_NORMAL)
|
||||||
|
normalize_tree(t, dir);
|
||||||
|
t = simplify_tree_base(t, dir, modified);
|
||||||
|
if (modified)
|
||||||
|
update = true;
|
||||||
|
} while (modified);
|
||||||
|
if (flags & DFA_CONTROL_TREE_LEFT)
|
||||||
|
dir++;
|
||||||
|
else
|
||||||
|
dir--;
|
||||||
|
}
|
||||||
|
} while(update);
|
||||||
|
if (flags & DFA_DUMP_TREE_STATS) {
|
||||||
|
struct node_counts counts = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||||
|
count_tree_nodes(t, &counts);
|
||||||
|
fprintf(stderr, "simplified expr tree: c %d, [] %d, [^] %d, | %d, + %d, * %d, . %d, cat %d\n", counts.charnode, counts.charset, counts.notcharset, counts.alt, counts.plus, counts.star, counts.any, counts.cat);
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
627
parser/libapparmor_re/expr-tree.h
Normal file
627
parser/libapparmor_re/expr-tree.h
Normal file
@ -0,0 +1,627 @@
|
|||||||
|
/*
|
||||||
|
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
|
||||||
|
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
|
||||||
|
* Copyright 2009-2010 Canonical Ltd.
|
||||||
|
*
|
||||||
|
* The libapparmor library is licensed under the terms of the GNU
|
||||||
|
* Lesser General Public License, version 2.1. Please see the file
|
||||||
|
* COPYING.LGPL.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Functions to create/manipulate an expression tree for regular expressions
|
||||||
|
* that have been parsed.
|
||||||
|
*
|
||||||
|
* The expression tree can be used directly after the parse creates it, or
|
||||||
|
* it can be factored so that the set of important nodes is smaller.
|
||||||
|
* Having a reduced set of important nodes generally results in a dfa that
|
||||||
|
* is closer to minimum (fewer redundant states are created). It also
|
||||||
|
* results in fewer important nodes in a the state set during subset
|
||||||
|
* construction resulting in less memory used to create a dfa.
|
||||||
|
*
|
||||||
|
* Generally it is worth doing expression tree simplification before dfa
|
||||||
|
* construction, if the regular expression tree contains any alternations.
|
||||||
|
* Even if the regular expression doesn't simplification should be fast
|
||||||
|
* enough that it can be used with minimal overhead.
|
||||||
|
*/
|
||||||
|
#ifndef __LIBAA_RE_EXPR_H
|
||||||
|
#define __LIBAA_RE_EXPR_H
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <stack>
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
#include "apparmor_re.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
typedef unsigned char uchar;
|
||||||
|
typedef set<uchar> Chars;
|
||||||
|
|
||||||
|
ostream& operator<<(ostream& os, uchar c);
|
||||||
|
|
||||||
|
/* Compute the union of two sets. */
|
||||||
|
template<class T>
|
||||||
|
set<T> operator+(const set<T>& a, const set<T>& b)
|
||||||
|
{
|
||||||
|
set<T> c(a);
|
||||||
|
c.insert(b.begin(), b.end());
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When creating DFAs from regex trees, a DFA state is constructed from
|
||||||
|
* a set of important nodes in the syntax tree. This includes AcceptNodes,
|
||||||
|
* which indicate that when a match ends in a particular state, the
|
||||||
|
* regular expressions that the AcceptNode belongs to match.
|
||||||
|
*/
|
||||||
|
class Node;
|
||||||
|
class ImportantNode;
|
||||||
|
typedef set <ImportantNode *> NodeSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text-dump a state (for debugging).
|
||||||
|
*/
|
||||||
|
ostream& operator<<(ostream& os, const NodeSet& state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Out-edges from a state to another: we store the follow-set of Nodes
|
||||||
|
* for each input character that is not a default match in
|
||||||
|
* cases (i.e., following a CharNode or CharSetNode), and default
|
||||||
|
* matches in otherwise as well as in all matching explicit cases
|
||||||
|
* (i.e., following an AnyCharNode or NotCharSetNode). This avoids
|
||||||
|
* enumerating all the explicit tranitions for default matches.
|
||||||
|
*/
|
||||||
|
typedef struct NodeCases {
|
||||||
|
typedef map<uchar, NodeSet *>::iterator iterator;
|
||||||
|
iterator begin() { return cases.begin(); }
|
||||||
|
iterator end() { return cases.end(); }
|
||||||
|
|
||||||
|
NodeCases() : otherwise(0) { }
|
||||||
|
map<uchar, NodeSet *> cases;
|
||||||
|
NodeSet *otherwise;
|
||||||
|
} NodeCases;
|
||||||
|
|
||||||
|
|
||||||
|
ostream& operator<<(ostream& os, Node& node);
|
||||||
|
|
||||||
|
/* An abstract node in the syntax tree. */
|
||||||
|
class Node {
|
||||||
|
public:
|
||||||
|
Node() :
|
||||||
|
nullable(false) { child[0] = child[1] = 0; }
|
||||||
|
Node(Node *left) :
|
||||||
|
nullable(false) { child[0] = left; child[1] = 0; }
|
||||||
|
Node(Node *left, Node *right) :
|
||||||
|
nullable(false) { child[0] = left; child[1] = right; }
|
||||||
|
virtual ~Node()
|
||||||
|
{
|
||||||
|
if (child[0])
|
||||||
|
child[0]->release();
|
||||||
|
if (child[1])
|
||||||
|
child[1]->release();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See the "Dragon Book" for an explanation of nullable, firstpos,
|
||||||
|
* lastpos, and followpos.
|
||||||
|
*/
|
||||||
|
virtual void compute_nullable() { }
|
||||||
|
virtual void compute_firstpos() = 0;
|
||||||
|
virtual void compute_lastpos() = 0;
|
||||||
|
virtual void compute_followpos() { }
|
||||||
|
virtual int eq(Node *other) = 0;
|
||||||
|
virtual ostream& dump(ostream& os) = 0;
|
||||||
|
void dump_syntax_tree(ostream& os);
|
||||||
|
|
||||||
|
bool nullable;
|
||||||
|
NodeSet firstpos, lastpos, followpos;
|
||||||
|
/* child 0 is left, child 1 is right */
|
||||||
|
Node *child[2];
|
||||||
|
|
||||||
|
unsigned int label; /* unique number for debug etc */
|
||||||
|
/**
|
||||||
|
* We indirectly release Nodes through a virtual function because
|
||||||
|
* accept and Eps Nodes are shared, and must be treated specially.
|
||||||
|
* We could use full reference counting here but the indirect release
|
||||||
|
* is sufficient and has less overhead
|
||||||
|
*/
|
||||||
|
virtual void release(void) { delete this; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class InnerNode : public Node {
|
||||||
|
public:
|
||||||
|
InnerNode() : Node() { };
|
||||||
|
InnerNode(Node *left) : Node(left) {};
|
||||||
|
InnerNode(Node *left, Node *right) : Node(left, right) { };
|
||||||
|
};
|
||||||
|
|
||||||
|
class OneChildNode : public InnerNode {
|
||||||
|
public:
|
||||||
|
OneChildNode(Node *left) : InnerNode(left) { };
|
||||||
|
};
|
||||||
|
|
||||||
|
class TwoChildNode : public InnerNode {
|
||||||
|
public:
|
||||||
|
TwoChildNode(Node *left, Node *right) : InnerNode(left, right) { };
|
||||||
|
};
|
||||||
|
|
||||||
|
class LeafNode : public Node {
|
||||||
|
public:
|
||||||
|
LeafNode() : Node() { };
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Match nothing (//). */
|
||||||
|
class EpsNode : public LeafNode {
|
||||||
|
public:
|
||||||
|
EpsNode() : LeafNode()
|
||||||
|
{
|
||||||
|
nullable = true;
|
||||||
|
label = 0;
|
||||||
|
}
|
||||||
|
void release(void)
|
||||||
|
{
|
||||||
|
/* don't delete Eps nodes because there is a single static
|
||||||
|
* instance shared by all trees. Look for epsnode in the code
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void compute_firstpos() { }
|
||||||
|
void compute_lastpos() { }
|
||||||
|
int eq(Node *other)
|
||||||
|
{
|
||||||
|
if (dynamic_cast<EpsNode *>(other))
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ostream& dump(ostream& os)
|
||||||
|
{
|
||||||
|
return os << "[]";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leaf nodes in the syntax tree are important to us: they describe the
|
||||||
|
* characters that the regular expression matches. We also consider
|
||||||
|
* AcceptNodes import: they indicate when a regular expression matches.
|
||||||
|
*/
|
||||||
|
class ImportantNode : public LeafNode {
|
||||||
|
public:
|
||||||
|
ImportantNode() : LeafNode() { }
|
||||||
|
void compute_firstpos()
|
||||||
|
{
|
||||||
|
firstpos.insert(this);
|
||||||
|
}
|
||||||
|
void compute_lastpos() {
|
||||||
|
lastpos.insert(this);
|
||||||
|
}
|
||||||
|
virtual void follow(NodeCases& cases) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* common base class for all the different classes that contain
|
||||||
|
* character information.
|
||||||
|
*/
|
||||||
|
class CNode : public ImportantNode {
|
||||||
|
public:
|
||||||
|
CNode() : ImportantNode() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Match one specific character (/c/). */
|
||||||
|
class CharNode : public CNode {
|
||||||
|
public:
|
||||||
|
CharNode(uchar c) : c(c) { }
|
||||||
|
void follow(NodeCases& cases)
|
||||||
|
{
|
||||||
|
NodeSet **x = &cases.cases[c];
|
||||||
|
if (!*x) {
|
||||||
|
if (cases.otherwise)
|
||||||
|
*x = new NodeSet(*cases.otherwise);
|
||||||
|
else
|
||||||
|
*x = new NodeSet;
|
||||||
|
}
|
||||||
|
(*x)->insert(followpos.begin(), followpos.end());
|
||||||
|
}
|
||||||
|
int eq(Node *other)
|
||||||
|
{
|
||||||
|
CharNode *o = dynamic_cast<CharNode *>(other);
|
||||||
|
if (o) {
|
||||||
|
return c == o->c;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ostream& dump(ostream& os)
|
||||||
|
{
|
||||||
|
return os << c;
|
||||||
|
}
|
||||||
|
|
||||||
|
uchar c;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Match a set of characters (/[abc]/). */
|
||||||
|
class CharSetNode : public CNode {
|
||||||
|
public:
|
||||||
|
CharSetNode(Chars& chars) : chars(chars) { }
|
||||||
|
void follow(NodeCases& cases)
|
||||||
|
{
|
||||||
|
for (Chars::iterator i = chars.begin(); i != chars.end(); i++) {
|
||||||
|
NodeSet **x = &cases.cases[*i];
|
||||||
|
if (!*x) {
|
||||||
|
if (cases.otherwise)
|
||||||
|
*x = new NodeSet(*cases.otherwise);
|
||||||
|
else
|
||||||
|
*x = new NodeSet;
|
||||||
|
}
|
||||||
|
(*x)->insert(followpos.begin(), followpos.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int eq(Node *other)
|
||||||
|
{
|
||||||
|
CharSetNode *o = dynamic_cast<CharSetNode *>(other);
|
||||||
|
if (!o || chars.size() != o->chars.size())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (Chars::iterator i = chars.begin(), j = o->chars.begin();
|
||||||
|
i != chars.end() && j != o->chars.end();
|
||||||
|
i++, j++) {
|
||||||
|
if (*i != *j)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
ostream& dump(ostream& os)
|
||||||
|
{
|
||||||
|
os << '[';
|
||||||
|
for (Chars::iterator i = chars.begin(); i != chars.end(); i++)
|
||||||
|
os << *i;
|
||||||
|
return os << ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
Chars chars;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Match all except one character (/[^abc]/). */
|
||||||
|
class NotCharSetNode : public CNode {
|
||||||
|
public:
|
||||||
|
NotCharSetNode(Chars& chars) : chars(chars) { }
|
||||||
|
void follow(NodeCases& cases)
|
||||||
|
{
|
||||||
|
if (!cases.otherwise)
|
||||||
|
cases.otherwise = new NodeSet;
|
||||||
|
for (Chars::iterator j = chars.begin(); j != chars.end(); j++) {
|
||||||
|
NodeSet **x = &cases.cases[*j];
|
||||||
|
if (!*x)
|
||||||
|
*x = new NodeSet(*cases.otherwise);
|
||||||
|
}
|
||||||
|
/* Note: Add to the nonmatching characters after copying away
|
||||||
|
* the old otherwise state for the matching characters.
|
||||||
|
*/
|
||||||
|
cases.otherwise->insert(followpos.begin(), followpos.end());
|
||||||
|
for (NodeCases::iterator i = cases.begin(); i != cases.end();
|
||||||
|
i++) {
|
||||||
|
if (chars.find(i->first) == chars.end())
|
||||||
|
i->second->insert(followpos.begin(),
|
||||||
|
followpos.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int eq(Node *other)
|
||||||
|
{
|
||||||
|
NotCharSetNode *o = dynamic_cast<NotCharSetNode *>(other);
|
||||||
|
if (!o || chars.size() != o->chars.size())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (Chars::iterator i = chars.begin(), j = o->chars.begin();
|
||||||
|
i != chars.end() && j != o->chars.end();
|
||||||
|
i++, j++) {
|
||||||
|
if (*i != *j)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
ostream& dump(ostream& os)
|
||||||
|
{
|
||||||
|
os << "[^";
|
||||||
|
for (Chars::iterator i = chars.begin(); i != chars.end(); i++)
|
||||||
|
os << *i;
|
||||||
|
return os << ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
Chars chars;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Match any character (/./). */
|
||||||
|
class AnyCharNode : public CNode {
|
||||||
|
public:
|
||||||
|
AnyCharNode() { }
|
||||||
|
void follow(NodeCases& cases)
|
||||||
|
{
|
||||||
|
if (!cases.otherwise)
|
||||||
|
cases.otherwise = new NodeSet;
|
||||||
|
cases.otherwise->insert(followpos.begin(), followpos.end());
|
||||||
|
for (NodeCases::iterator i = cases.begin(); i != cases.end();
|
||||||
|
i++)
|
||||||
|
i->second->insert(followpos.begin(), followpos.end());
|
||||||
|
}
|
||||||
|
int eq(Node *other)
|
||||||
|
{
|
||||||
|
if (dynamic_cast<AnyCharNode *>(other))
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ostream& dump(ostream& os) {
|
||||||
|
return os << ".";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate that a regular expression matches. An AcceptNode itself
|
||||||
|
* doesn't match anything, so it will never generate any transitions.
|
||||||
|
*/
|
||||||
|
class AcceptNode : public ImportantNode {
|
||||||
|
public:
|
||||||
|
AcceptNode() {}
|
||||||
|
void release(void)
|
||||||
|
{
|
||||||
|
/* don't delete AcceptNode via release as they are shared, and
|
||||||
|
* will be deleted when the table the are stored in is deleted
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void follow(NodeCases& cases __attribute__((unused)))
|
||||||
|
{
|
||||||
|
/* Nothing to follow. */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* requires accept nodes to be common by pointer */
|
||||||
|
int eq(Node *other)
|
||||||
|
{
|
||||||
|
if (dynamic_cast<AcceptNode *>(other))
|
||||||
|
return (this == other);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Match a node zero or more times. (This is a unary operator.) */
|
||||||
|
class StarNode : public OneChildNode {
|
||||||
|
public:
|
||||||
|
StarNode(Node *left) : OneChildNode(left)
|
||||||
|
{
|
||||||
|
nullable = true;
|
||||||
|
}
|
||||||
|
void compute_firstpos()
|
||||||
|
{
|
||||||
|
firstpos = child[0]->firstpos;
|
||||||
|
}
|
||||||
|
void compute_lastpos()
|
||||||
|
{
|
||||||
|
lastpos = child[0]->lastpos;
|
||||||
|
}
|
||||||
|
void compute_followpos()
|
||||||
|
{
|
||||||
|
NodeSet from = child[0]->lastpos, to = child[0]->firstpos;
|
||||||
|
for(NodeSet::iterator i = from.begin(); i != from.end(); i++) {
|
||||||
|
(*i)->followpos.insert(to.begin(), to.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int eq(Node *other) {
|
||||||
|
if (dynamic_cast<StarNode *>(other))
|
||||||
|
return child[0]->eq(other->child[0]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ostream& dump(ostream& os)
|
||||||
|
{
|
||||||
|
os << '(';
|
||||||
|
child[0]->dump(os);
|
||||||
|
return os << ")*";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Match a node one or more times. (This is a unary operator.) */
|
||||||
|
class PlusNode : public OneChildNode {
|
||||||
|
public:
|
||||||
|
PlusNode(Node *left) : OneChildNode(left) { }
|
||||||
|
void compute_nullable()
|
||||||
|
{
|
||||||
|
nullable = child[0]->nullable;
|
||||||
|
}
|
||||||
|
void compute_firstpos()
|
||||||
|
{
|
||||||
|
firstpos = child[0]->firstpos;
|
||||||
|
}
|
||||||
|
void compute_lastpos()
|
||||||
|
{
|
||||||
|
lastpos = child[0]->lastpos;
|
||||||
|
}
|
||||||
|
void compute_followpos()
|
||||||
|
{
|
||||||
|
NodeSet from = child[0]->lastpos, to = child[0]->firstpos;
|
||||||
|
for(NodeSet::iterator i = from.begin(); i != from.end(); i++) {
|
||||||
|
(*i)->followpos.insert(to.begin(), to.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int eq(Node *other)
|
||||||
|
{
|
||||||
|
if (dynamic_cast<PlusNode *>(other))
|
||||||
|
return child[0]->eq(other->child[0]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ostream& dump(ostream& os)
|
||||||
|
{
|
||||||
|
os << '(';
|
||||||
|
child[0]->dump(os);
|
||||||
|
return os << ")+";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Match a pair of consecutive nodes. */
|
||||||
|
class CatNode : public TwoChildNode {
|
||||||
|
public:
|
||||||
|
CatNode(Node *left, Node *right) : TwoChildNode(left, right) { }
|
||||||
|
void compute_nullable()
|
||||||
|
{
|
||||||
|
nullable = child[0]->nullable && child[1]->nullable;
|
||||||
|
}
|
||||||
|
void compute_firstpos()
|
||||||
|
{
|
||||||
|
if (child[0]->nullable)
|
||||||
|
firstpos = child[0]->firstpos + child[1]->firstpos;
|
||||||
|
else
|
||||||
|
firstpos = child[0]->firstpos;
|
||||||
|
}
|
||||||
|
void compute_lastpos()
|
||||||
|
{
|
||||||
|
if (child[1]->nullable)
|
||||||
|
lastpos = child[0]->lastpos + child[1]->lastpos;
|
||||||
|
else
|
||||||
|
lastpos = child[1]->lastpos;
|
||||||
|
}
|
||||||
|
void compute_followpos()
|
||||||
|
{
|
||||||
|
NodeSet from = child[0]->lastpos, to = child[1]->firstpos;
|
||||||
|
for(NodeSet::iterator i = from.begin(); i != from.end(); i++) {
|
||||||
|
(*i)->followpos.insert(to.begin(), to.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int eq(Node *other) {
|
||||||
|
if (dynamic_cast<CatNode *>(other)) {
|
||||||
|
if (!child[0]->eq(other->child[0]))
|
||||||
|
return 0;
|
||||||
|
return child[1]->eq(other->child[1]);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ostream& dump(ostream& os)
|
||||||
|
{
|
||||||
|
child[0]->dump(os);
|
||||||
|
child[1]->dump(os);
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Match one of two alternative nodes. */
|
||||||
|
class AltNode : public TwoChildNode {
|
||||||
|
public:
|
||||||
|
AltNode(Node *left, Node *right) : TwoChildNode(left, right) { }
|
||||||
|
void compute_nullable()
|
||||||
|
{
|
||||||
|
nullable = child[0]->nullable || child[1]->nullable;
|
||||||
|
}
|
||||||
|
void compute_lastpos()
|
||||||
|
{
|
||||||
|
lastpos = child[0]->lastpos + child[1]->lastpos;
|
||||||
|
}
|
||||||
|
void compute_firstpos()
|
||||||
|
{
|
||||||
|
firstpos = child[0]->firstpos + child[1]->firstpos;
|
||||||
|
}
|
||||||
|
int eq(Node *other) {
|
||||||
|
if (dynamic_cast<AltNode *>(other)) {
|
||||||
|
if (!child[0]->eq(other->child[0]))
|
||||||
|
return 0;
|
||||||
|
return child[1]->eq(other->child[1]);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ostream& dump(ostream& os)
|
||||||
|
{
|
||||||
|
os << '(';
|
||||||
|
child[0]->dump(os);
|
||||||
|
os << '|';
|
||||||
|
child[1]->dump(os);
|
||||||
|
os << ')';
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* Traverse the syntax tree depth-first in an iterator-like manner. */
|
||||||
|
class depth_first_traversal {
|
||||||
|
stack<Node *> pos;
|
||||||
|
void push_left(Node *node)
|
||||||
|
{
|
||||||
|
pos.push(node);
|
||||||
|
|
||||||
|
while (dynamic_cast<InnerNode *>(node)) {
|
||||||
|
pos.push(node->child[0]);
|
||||||
|
node = node->child[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
depth_first_traversal(Node *node)
|
||||||
|
{
|
||||||
|
push_left(node);
|
||||||
|
}
|
||||||
|
Node *operator*()
|
||||||
|
{
|
||||||
|
return pos.top();
|
||||||
|
}
|
||||||
|
Node* operator->()
|
||||||
|
{
|
||||||
|
return pos.top();
|
||||||
|
}
|
||||||
|
operator bool()
|
||||||
|
{
|
||||||
|
return !pos.empty();
|
||||||
|
}
|
||||||
|
void operator++(int)
|
||||||
|
{
|
||||||
|
Node *last = pos.top();
|
||||||
|
pos.pop();
|
||||||
|
|
||||||
|
if (!pos.empty()) {
|
||||||
|
/* no need to dynamic cast, as we just popped a node so
|
||||||
|
* the top node must be an inner node */
|
||||||
|
InnerNode *node = (InnerNode *)(pos.top());
|
||||||
|
if (node->child[1] && node->child[1] != last) {
|
||||||
|
push_left(node->child[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct node_counts {
|
||||||
|
int charnode;
|
||||||
|
int charset;
|
||||||
|
int notcharset;
|
||||||
|
int alt;
|
||||||
|
int plus;
|
||||||
|
int star;
|
||||||
|
int any;
|
||||||
|
int cat;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern EpsNode epsnode;
|
||||||
|
|
||||||
|
int debug_tree(Node *t);
|
||||||
|
Node *simplify_tree(Node *t, dfaflags_t flags);
|
||||||
|
void label_nodes(Node *root);
|
||||||
|
unsigned long hash_NodeSet(NodeSet *ns);
|
||||||
|
|
||||||
|
|
||||||
|
/* Comparison operator for sets of <NodeSet *>.
|
||||||
|
* Compare set hashes, and if the sets have the same hash
|
||||||
|
* do compare pointer comparison on set of <Node *>, the pointer comparison
|
||||||
|
* allows us to determine which Sets of <Node *> we have seen already from
|
||||||
|
* new ones when constructing the DFA.
|
||||||
|
*/
|
||||||
|
struct deref_less_than {
|
||||||
|
bool operator()(pair <unsigned long, NodeSet *> const & lhs,
|
||||||
|
pair <unsigned long, NodeSet *> const & rhs) const
|
||||||
|
{
|
||||||
|
if (lhs.first == rhs.first)
|
||||||
|
return *(lhs.second) < *(rhs.second);
|
||||||
|
else
|
||||||
|
return lhs.first < rhs.first;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* __LIBAA_RE_EXPR */
|
File diff suppressed because it is too large
Load Diff
27
parser/libapparmor_re/parse.h
Normal file
27
parser/libapparmor_re/parse.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
|
||||||
|
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
|
||||||
|
* Copyright 2009-2010 Canonical Ltd.
|
||||||
|
*
|
||||||
|
* The libapparmor library is licensed under the terms of the GNU
|
||||||
|
* Lesser General Public License, version 2.1. Please see the file
|
||||||
|
* COPYING.LGPL.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Parsing of regular expression into expression trees as implemented in
|
||||||
|
* expr-tree
|
||||||
|
*/
|
||||||
|
#ifndef __LIBAA_RE_PARSE_H
|
||||||
|
#define __LIBAA_RE_PARSE_H
|
||||||
|
|
||||||
|
int regex_parse(Node **tree, const char *rule);
|
||||||
|
|
||||||
|
#endif /* __LIBAA_RE_PARSE_H */
|
266
parser/libapparmor_re/parse.y
Normal file
266
parser/libapparmor_re/parse.y
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
/*
|
||||||
|
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
|
||||||
|
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
|
||||||
|
* Copyright 2009-2010 Canonical Ltd.
|
||||||
|
*
|
||||||
|
* The libapparmor library is licensed under the terms of the GNU
|
||||||
|
* Lesser General Public License, version 2.1. Please see the file
|
||||||
|
* COPYING.LGPL.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Parsing of regular expression into expression trees as implemented in
|
||||||
|
* expr-tree
|
||||||
|
*/
|
||||||
|
|
||||||
|
%{
|
||||||
|
/* #define DEBUG_TREE */
|
||||||
|
#include "expr-tree.h"
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
%union {
|
||||||
|
char c;
|
||||||
|
Node *node;
|
||||||
|
Chars *cset;
|
||||||
|
}
|
||||||
|
|
||||||
|
%{
|
||||||
|
void regex_error(Node **, const char *, const char *);
|
||||||
|
# define YYLEX_PARAM &text
|
||||||
|
int regex_lex(YYSTYPE *, const char **);
|
||||||
|
|
||||||
|
static inline Chars*
|
||||||
|
insert_char(Chars* cset, uchar a)
|
||||||
|
{
|
||||||
|
cset->insert(a);
|
||||||
|
return cset;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline Chars*
|
||||||
|
insert_char_range(Chars* cset, uchar a, uchar b)
|
||||||
|
{
|
||||||
|
if (a > b)
|
||||||
|
swap(a, b);
|
||||||
|
for (uchar i = a; i <= b; i++)
|
||||||
|
cset->insert(i);
|
||||||
|
return cset;
|
||||||
|
}
|
||||||
|
%}
|
||||||
|
|
||||||
|
%pure-parser
|
||||||
|
/* %error-verbose */
|
||||||
|
%parse-param {Node **root}
|
||||||
|
%parse-param {const char *text}
|
||||||
|
%name-prefix = "regex_"
|
||||||
|
|
||||||
|
%token <c> CHAR
|
||||||
|
%type <c> regex_char cset_char1 cset_char cset_charN
|
||||||
|
%type <cset> charset cset_chars
|
||||||
|
%type <node> regex expr terms0 terms qterm term
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: destroy all nodes upon failure, but *not* the start symbol once
|
||||||
|
* parsing succeeds!
|
||||||
|
*/
|
||||||
|
%destructor { $$->release(); } expr terms0 terms qterm term
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
/* FIXME: Does not parse "[--]", "[---]", "[^^-x]". I don't actually know
|
||||||
|
which precise grammer Perl regexs use, and rediscovering that
|
||||||
|
is proving to be painful. */
|
||||||
|
|
||||||
|
regex : /* empty */ { *root = $$ = &epsnode; }
|
||||||
|
| expr { *root = $$ = $1; }
|
||||||
|
;
|
||||||
|
|
||||||
|
expr : terms
|
||||||
|
| expr '|' terms0 { $$ = new AltNode($1, $3); }
|
||||||
|
| '|' terms0 { $$ = new AltNode(&epsnode, $2); }
|
||||||
|
;
|
||||||
|
|
||||||
|
terms0 : /* empty */ { $$ = &epsnode; }
|
||||||
|
| terms
|
||||||
|
;
|
||||||
|
|
||||||
|
terms : qterm
|
||||||
|
| terms qterm { $$ = new CatNode($1, $2); }
|
||||||
|
;
|
||||||
|
|
||||||
|
qterm : term
|
||||||
|
| term '*' { $$ = new StarNode($1); }
|
||||||
|
| term '+' { $$ = new PlusNode($1); }
|
||||||
|
;
|
||||||
|
|
||||||
|
term : '.' { $$ = new AnyCharNode; }
|
||||||
|
| regex_char { $$ = new CharNode($1); }
|
||||||
|
| '[' charset ']' { $$ = new CharSetNode(*$2);
|
||||||
|
delete $2; }
|
||||||
|
| '[' '^' charset ']'
|
||||||
|
{ $$ = new NotCharSetNode(*$3);
|
||||||
|
delete $3; }
|
||||||
|
| '[' '^' '^' cset_chars ']'
|
||||||
|
{ $4->insert('^');
|
||||||
|
$$ = new NotCharSetNode(*$4);
|
||||||
|
delete $4; }
|
||||||
|
| '(' regex ')' { $$ = $2; }
|
||||||
|
;
|
||||||
|
|
||||||
|
regex_char : CHAR
|
||||||
|
| '^' { $$ = '^'; }
|
||||||
|
| '-' { $$ = '-'; }
|
||||||
|
| ']' { $$ = ']'; }
|
||||||
|
;
|
||||||
|
|
||||||
|
charset : cset_char1 cset_chars
|
||||||
|
{ $$ = insert_char($2, $1); }
|
||||||
|
| cset_char1 '-' cset_charN cset_chars
|
||||||
|
{ $$ = insert_char_range($4, $1, $3); }
|
||||||
|
;
|
||||||
|
|
||||||
|
cset_chars : /* nothing */ { $$ = new Chars; }
|
||||||
|
| cset_chars cset_charN
|
||||||
|
{ $$ = insert_char($1, $2); }
|
||||||
|
| cset_chars cset_charN '-' cset_charN
|
||||||
|
{ $$ = insert_char_range($1, $2, $4); }
|
||||||
|
;
|
||||||
|
|
||||||
|
cset_char1 : cset_char
|
||||||
|
| ']' { $$ = ']'; }
|
||||||
|
| '-' { $$ = '-'; }
|
||||||
|
;
|
||||||
|
|
||||||
|
cset_charN : cset_char
|
||||||
|
| '^' { $$ = '^'; }
|
||||||
|
;
|
||||||
|
|
||||||
|
cset_char : CHAR
|
||||||
|
| '[' { $$ = '['; }
|
||||||
|
| '*' { $$ = '*'; }
|
||||||
|
| '+' { $$ = '+'; }
|
||||||
|
| '.' { $$ = '.'; }
|
||||||
|
| '|' { $$ = '|'; }
|
||||||
|
| '(' { $$ = '('; }
|
||||||
|
| ')' { $$ = ')'; }
|
||||||
|
;
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
octdigit(char c)
|
||||||
|
{
|
||||||
|
if (c >= '0' && c <= '7')
|
||||||
|
return c - '0';
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
hexdigit(char c)
|
||||||
|
{
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
return c - '0';
|
||||||
|
else if (c >= 'A' && c <= 'F')
|
||||||
|
return 10 + c - 'A';
|
||||||
|
else if (c >= 'a' && c <= 'f')
|
||||||
|
return 10 + c - 'A';
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
regex_lex(YYSTYPE *val, const char **pos)
|
||||||
|
{
|
||||||
|
int c;
|
||||||
|
|
||||||
|
val->c = **pos;
|
||||||
|
switch(*(*pos)++) {
|
||||||
|
case '\0':
|
||||||
|
(*pos)--;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case '*': case '+': case '.': case '|': case '^': case '-':
|
||||||
|
case '[': case ']': case '(' : case ')':
|
||||||
|
return *(*pos - 1);
|
||||||
|
|
||||||
|
case '\\':
|
||||||
|
val->c = **pos;
|
||||||
|
switch(*(*pos)++) {
|
||||||
|
case '\0':
|
||||||
|
(*pos)--;
|
||||||
|
/* fall through */
|
||||||
|
case '\\':
|
||||||
|
val->c = '\\';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '0':
|
||||||
|
val->c = 0;
|
||||||
|
if ((c = octdigit(**pos)) >= 0) {
|
||||||
|
val->c = c;
|
||||||
|
(*pos)++;
|
||||||
|
}
|
||||||
|
if ((c = octdigit(**pos)) >= 0) {
|
||||||
|
val->c = (val->c << 3) + c;
|
||||||
|
(*pos)++;
|
||||||
|
}
|
||||||
|
if ((c = octdigit(**pos)) >= 0) {
|
||||||
|
val->c = (val->c << 3) + c;
|
||||||
|
(*pos)++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'x':
|
||||||
|
val->c = 0;
|
||||||
|
if ((c = hexdigit(**pos)) >= 0) {
|
||||||
|
val->c = c;
|
||||||
|
(*pos)++;
|
||||||
|
}
|
||||||
|
if ((c = hexdigit(**pos)) >= 0) {
|
||||||
|
val->c = (val->c << 4) + c;
|
||||||
|
(*pos)++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'a':
|
||||||
|
val->c = '\a';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'e':
|
||||||
|
val->c = 033 /* ESC */;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
val->c = '\f';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
val->c = '\n';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'r':
|
||||||
|
val->c = '\r';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
val->c = '\t';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CHAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
regex_error(Node ** __attribute__((unused)),
|
||||||
|
const char *text __attribute__((unused)),
|
||||||
|
const char *error __attribute__((unused)))
|
||||||
|
{
|
||||||
|
/* We don't want the library to print error messages. */
|
||||||
|
}
|
@ -1,10 +0,0 @@
|
|||||||
#ifndef __REGEXP_H
|
|
||||||
#define __REGEXP_H
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flex file format, but without state compression and with negative
|
|
||||||
* match results in the YYTD_ID_DEF table instead.
|
|
||||||
*/
|
|
||||||
#define YYTH_REGEXP_MAGIC 0x1B5E783D
|
|
||||||
|
|
||||||
#endif /* __REGEXP_H */
|
|
Loading…
x
Reference in New Issue
Block a user