2007-02-27 02:29:16 +00:00
|
|
|
/*
|
2007-04-11 08:12:51 +00:00
|
|
|
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
|
2011-03-13 05:46:29 -07:00
|
|
|
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
|
2012-02-24 04:21:59 -08:00
|
|
|
* Copyright 2009-2012 Canonical Ltd.
|
2007-02-27 02:29:16 +00:00
|
|
|
*
|
2011-03-13 05:46:29 -07:00
|
|
|
* The libapparmor library is licensed under the terms of the GNU
|
|
|
|
* Lesser General Public License, version 2.1. Please see the file
|
|
|
|
* COPYING.LGPL.
|
2010-08-04 10:23:22 -07:00
|
|
|
*
|
2011-03-13 05:46:29 -07:00
|
|
|
* 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.
|
2010-08-04 10:23:22 -07:00
|
|
|
*
|
2011-03-13 05:46:29 -07:00
|
|
|
* 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/>.
|
2010-08-04 10:23:22 -07:00
|
|
|
*
|
|
|
|
*
|
2011-03-13 05:46:29 -07:00
|
|
|
* Base of implementation based on the Lexical Analysis chapter of:
|
|
|
|
* Alfred V. Aho, Ravi Sethi, Jeffrey D. Ullman:
|
|
|
|
* Compilers: Principles, Techniques, and Tools (The "Dragon Book"),
|
|
|
|
* Addison-Wesley, 1986.
|
2008-11-07 13:00:05 +00:00
|
|
|
*/
|
2007-02-27 02:29:16 +00:00
|
|
|
|
2011-03-13 05:46:29 -07:00
|
|
|
#include <list>
|
|
|
|
#include <vector>
|
|
|
|
#include <stack>
|
|
|
|
#include <set>
|
|
|
|
#include <map>
|
|
|
|
#include <ostream>
|
|
|
|
#include <iostream>
|
|
|
|
#include <fstream>
|
2012-01-06 09:03:20 -08:00
|
|
|
#include <string.h>
|
2024-02-29 17:21:37 -08:00
|
|
|
#include <stdint.h>
|
2011-03-13 05:46:29 -07:00
|
|
|
#include "expr-tree.h"
|
2011-03-13 05:50:34 -07:00
|
|
|
#include "hfa.h"
|
2020-06-18 05:49:20 -07:00
|
|
|
#include "policy_compat.h"
|
2007-02-27 02:29:16 +00:00
|
|
|
#include "../immunix.h"
|
2020-06-18 05:49:20 -07:00
|
|
|
#include "../perms.h"
|
2011-12-15 05:14:37 -08:00
|
|
|
|
|
|
|
ostream &operator<<(ostream &os, const CacheStats &cache)
|
|
|
|
{
|
|
|
|
/* dump the state label */
|
|
|
|
os << "cache: size=";
|
|
|
|
os << cache.size();
|
|
|
|
os << " dups=";
|
|
|
|
os << cache.dup;
|
|
|
|
os << " longest=";
|
|
|
|
os << cache.max;
|
|
|
|
if (cache.size()) {
|
|
|
|
os << " avg=";
|
|
|
|
os << cache.sum / cache.size();
|
|
|
|
}
|
|
|
|
return os;
|
|
|
|
}
|
|
|
|
|
2011-12-15 05:12:30 -08:00
|
|
|
ostream &operator<<(ostream &os, const ProtoState &proto)
|
|
|
|
{
|
|
|
|
/* dump the state label */
|
|
|
|
os << '{';
|
2011-12-15 05:14:37 -08:00
|
|
|
os << proto.nnodes;
|
|
|
|
os << ',';
|
|
|
|
os << proto.anodes;
|
2011-12-15 05:12:30 -08:00
|
|
|
os << '}';
|
|
|
|
return os;
|
|
|
|
}
|
|
|
|
|
2011-03-13 05:55:25 -07:00
|
|
|
ostream &operator<<(ostream &os, const State &state)
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
{
|
2010-11-11 16:06:52 -08:00
|
|
|
/* dump the state label */
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
os << '{';
|
|
|
|
os << state.label;
|
|
|
|
os << '}';
|
|
|
|
return os;
|
|
|
|
}
|
|
|
|
|
2019-08-08 03:17:24 -07:00
|
|
|
ostream &operator<<(ostream &os, State &state)
|
|
|
|
{
|
|
|
|
/* dump the state label */
|
|
|
|
os << '{';
|
|
|
|
os << state.label;
|
|
|
|
os << '}';
|
|
|
|
return os;
|
|
|
|
}
|
|
|
|
|
2024-12-30 01:27:21 -08:00
|
|
|
ostream &operator<<(ostream &os,
|
|
|
|
const std::pair<State * const, Renumber_Map *> &p)
|
|
|
|
{
|
|
|
|
/* dump the state label */
|
|
|
|
if (p.second && (*p.second)[p.first] != (size_t) p.first->label) {
|
|
|
|
os << '{';
|
|
|
|
os << (*p.second)[p.first];
|
|
|
|
os << " == " << *(p.first);
|
|
|
|
os << '}';
|
|
|
|
} else {
|
|
|
|
os << *(p.first);
|
|
|
|
}
|
|
|
|
return os;
|
|
|
|
}
|
|
|
|
|
2014-01-09 16:55:55 -08:00
|
|
|
/**
|
|
|
|
* diff_weight - Find differential compression distance between @rel and @this
|
|
|
|
* @rel: State to compare too
|
|
|
|
* Returns: An integer indicating how good rel is as a base, larger == better
|
|
|
|
*
|
|
|
|
* Find the relative weighted difference for differential state compression
|
|
|
|
* with queried state being compressed against @rel
|
|
|
|
*
|
|
|
|
* +1 for each transition that matches (char and dest - saves a transition)
|
|
|
|
* 0 for each transition that doesn't match and exists in both states
|
|
|
|
* 0 for transition that self has and @other doesn't (no extra required)
|
|
|
|
* -1 for each transition that is in @rel and not in @this (have to override)
|
|
|
|
*
|
|
|
|
* @rel should not be a state that has already been made differential or it may
|
|
|
|
* introduce extra transitions as it does not recurse to find all transitions
|
|
|
|
*
|
|
|
|
* Should be applied after state minimization
|
|
|
|
*/
|
2019-08-11 06:18:27 -07:00
|
|
|
int State::diff_weight(State *rel, int max_range, int upper_bound)
|
2014-01-09 16:55:55 -08:00
|
|
|
{
|
|
|
|
int weight = 0;
|
2019-08-11 06:18:27 -07:00
|
|
|
int first = 0;
|
2014-01-09 16:55:55 -08:00
|
|
|
|
|
|
|
if (this == rel)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (rel->diff->rel) {
|
|
|
|
/* Can only be diff encoded against states that are relative
|
|
|
|
* to a state of a lower depth. ie, at most one sibling in
|
|
|
|
* the chain
|
|
|
|
*/
|
|
|
|
if (rel->diff->rel->diff->depth >= this->diff->depth)
|
|
|
|
return 0;
|
|
|
|
} else if (rel->diff->depth >= this->diff->depth)
|
|
|
|
return 0;
|
|
|
|
|
2019-08-11 06:18:27 -07:00
|
|
|
if (rel->trans.begin()->first.c < first)
|
|
|
|
first = rel->trans.begin()->first.c;
|
2014-01-09 16:55:55 -08:00
|
|
|
if (rel->flags & DiffEncodeFlag) {
|
2019-08-11 06:18:27 -07:00
|
|
|
for (int i = first; i < upper_bound; i++) {
|
2014-01-09 16:55:55 -08:00
|
|
|
State *state = rel->next(i);
|
|
|
|
StateTrans::iterator j = trans.find(i);
|
|
|
|
if (j != trans.end()) {
|
|
|
|
if (state == j->second)
|
|
|
|
weight++;
|
|
|
|
/* else
|
|
|
|
0 - keep transition to mask
|
|
|
|
*/
|
|
|
|
} else if (state == otherwise) {
|
|
|
|
/* 0 - match of default against @rel
|
|
|
|
* We don't save a transition but don't have
|
|
|
|
* to mask either
|
|
|
|
*/
|
|
|
|
} else {
|
|
|
|
/* @rel has transition not covered by @this.
|
|
|
|
* Need to add a transition to mask it
|
|
|
|
*/
|
|
|
|
weight--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return weight;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int count = 0;
|
|
|
|
for (StateTrans::iterator i = rel->trans.begin(); i != rel->trans.end();
|
|
|
|
i++) {
|
|
|
|
StateTrans::iterator j = trans.find(i->first);
|
|
|
|
if (j != trans.end()) {
|
|
|
|
if (i->second == j->second)
|
|
|
|
weight++;
|
|
|
|
/* } else {
|
|
|
|
0 - keep transition to mask
|
|
|
|
*/
|
|
|
|
count++;
|
|
|
|
} else if (i->second == otherwise) {
|
|
|
|
/* 0 - match of default against @rel
|
|
|
|
* We don't save a transition but don't have to
|
|
|
|
* mask either
|
|
|
|
*/
|
|
|
|
} else {
|
|
|
|
/* rel has transition not covered by @this. Need to
|
|
|
|
* add a transition to mask
|
|
|
|
*/
|
|
|
|
weight--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* cover transitions in @this but not in @rel */
|
|
|
|
unsigned int this_count = 0;
|
|
|
|
if (count < trans.size()) {
|
|
|
|
for (StateTrans::iterator i = trans.begin(); i != trans.end(); i++) {
|
|
|
|
StateTrans::iterator j = rel->trans.find(i->first);
|
|
|
|
if (j == rel->trans.end()) {
|
|
|
|
this_count++;
|
|
|
|
if (i->second == rel->otherwise)
|
|
|
|
/* replaced by rel->cases.otherwise */
|
|
|
|
weight++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rel->otherwise != otherwise) {
|
|
|
|
/* rel default transitions have to be masked with transitions
|
|
|
|
* This covers all transitions not covered above
|
|
|
|
*/
|
2019-08-11 06:18:27 -07:00
|
|
|
weight -= (max_range) - (rel->trans.size() + this_count);
|
2014-01-09 16:55:55 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
return weight;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* make_relative - Make this state relative to @rel
|
|
|
|
* @rel: state to make this state relative too
|
2019-08-11 06:18:27 -07:00
|
|
|
* @upper_bound: the largest value for an input transition (256 for a byte).
|
2014-01-09 16:55:55 -08:00
|
|
|
*
|
|
|
|
* @rel can be a relative (differentially compressed state)
|
|
|
|
*/
|
2019-08-11 06:18:27 -07:00
|
|
|
int State::make_relative(State *rel, int upper_bound)
|
2014-01-09 16:55:55 -08:00
|
|
|
{
|
|
|
|
int weight = 0;
|
2019-08-11 06:18:27 -07:00
|
|
|
int first = 0;
|
2014-01-09 16:55:55 -08:00
|
|
|
|
|
|
|
if (this == rel || !rel)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (flags & DiffEncodeFlag)
|
|
|
|
return 0;
|
|
|
|
|
2019-08-11 06:18:27 -07:00
|
|
|
if (rel->trans.begin()->first.c < 0)
|
|
|
|
first = rel->trans.begin()->first.c;
|
|
|
|
|
2014-01-09 16:55:55 -08:00
|
|
|
flags |= DiffEncodeFlag;
|
|
|
|
|
2019-08-11 06:18:27 -07:00
|
|
|
for (int i = first; i < upper_bound ; i++) {
|
2014-01-09 16:55:55 -08:00
|
|
|
State *next = rel->next(i);
|
|
|
|
|
|
|
|
StateTrans::iterator j = trans.find(i);
|
|
|
|
if (j != trans.end()) {
|
|
|
|
if (j->second == next) {
|
|
|
|
trans.erase(j);
|
|
|
|
weight++;
|
|
|
|
}
|
|
|
|
/* else keep transition to mask */
|
|
|
|
} else if (otherwise == next) {
|
|
|
|
/* do nothing, otherwise transition disappears when
|
|
|
|
* reassigned
|
|
|
|
*/
|
|
|
|
} else {
|
|
|
|
/* need a new transition to mask those in lower state */
|
|
|
|
trans[i] = otherwise;
|
|
|
|
weight--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
otherwise = rel;
|
|
|
|
|
|
|
|
return weight;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* flatten_differential - remove differential encode from this state
|
2019-08-11 06:18:27 -07:00
|
|
|
* @nonmatching: the nonmatching state for the state machine
|
|
|
|
* @upper_bound: the largest value for an input transition (256 for a byte).
|
2014-01-09 16:55:55 -08:00
|
|
|
*/
|
2019-08-11 06:18:27 -07:00
|
|
|
void State::flatten_relative(State *nonmatching, int upper_bound)
|
2014-01-09 16:55:55 -08:00
|
|
|
{
|
|
|
|
if (!(flags & DiffEncodeFlag))
|
|
|
|
return;
|
|
|
|
|
|
|
|
map<State *, int> count;
|
|
|
|
|
2019-08-11 06:18:27 -07:00
|
|
|
int first = 0;
|
|
|
|
if (next(-1) != nonmatching)
|
|
|
|
first = -1;
|
|
|
|
|
|
|
|
for (int i = first; i < upper_bound; i++)
|
2014-01-09 16:55:55 -08:00
|
|
|
count[next(i)] += 1;
|
|
|
|
|
2019-08-11 06:18:27 -07:00
|
|
|
int j = first;
|
|
|
|
State *def = next(first);
|
|
|
|
for (int i = first + 1; i < upper_bound; i++) {
|
2014-01-09 16:55:55 -08:00
|
|
|
if (count[next(i)] > count[next(j)]) {
|
|
|
|
j = i;
|
|
|
|
def = next(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-11 06:18:27 -07:00
|
|
|
for (int i = first; i < upper_bound; i++) {
|
2014-01-09 16:55:55 -08:00
|
|
|
if (trans.find(i) != trans.end()) {
|
|
|
|
if (trans[i] == def)
|
|
|
|
trans.erase(i);
|
|
|
|
} else {
|
|
|
|
if (trans[i] != def)
|
|
|
|
trans[i] = next(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
otherwise = def;
|
|
|
|
flags = flags & ~DiffEncodeFlag;
|
|
|
|
}
|
|
|
|
|
2011-12-15 05:14:37 -08:00
|
|
|
static void split_node_types(NodeSet *nodes, NodeSet **anodes, NodeSet **nnodes
|
|
|
|
)
|
2010-11-11 16:16:38 -08:00
|
|
|
{
|
2011-12-15 05:14:37 -08:00
|
|
|
*anodes = *nnodes = NULL;
|
|
|
|
for (NodeSet::iterator i = nodes->begin(); i != nodes->end(); ) {
|
|
|
|
if ((*i)->is_accept()) {
|
|
|
|
if (!*anodes)
|
|
|
|
*anodes = new NodeSet;
|
|
|
|
(*anodes)->insert(*i);
|
|
|
|
NodeSet::iterator k = i++;
|
|
|
|
nodes->erase(k);
|
|
|
|
} else
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
*nnodes = nodes;
|
2010-11-11 16:16:38 -08:00
|
|
|
}
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
|
2024-12-24 01:27:10 -08:00
|
|
|
State *DFA::add_new_state(optflags const &opts, NodeSet *anodes,
|
|
|
|
NodeSet *nnodes, State *other)
|
2010-11-11 16:20:32 -08:00
|
|
|
{
|
2021-01-30 02:32:38 -08:00
|
|
|
NodeVec *nnodev, *anodev;
|
2011-12-15 05:16:03 -08:00
|
|
|
nnodev = nnodes_cache.insert(nnodes);
|
2021-01-30 02:32:38 -08:00
|
|
|
anodev = anodes_cache.insert(anodes);
|
2010-11-11 16:20:32 -08:00
|
|
|
|
2012-05-30 14:31:41 -07:00
|
|
|
ProtoState proto;
|
2021-01-30 02:32:38 -08:00
|
|
|
proto.init(nnodev, anodev);
|
2024-12-24 01:27:10 -08:00
|
|
|
State *state = new State(opts, node_map.size(), proto, other, filedfa);
|
2011-12-15 05:14:37 -08:00
|
|
|
pair<NodeMap::iterator,bool> x = node_map.insert(proto, state);
|
|
|
|
if (x.second == false) {
|
|
|
|
delete state;
|
2010-11-11 16:20:32 -08:00
|
|
|
} else {
|
2011-12-15 05:14:37 -08:00
|
|
|
states.push_back(state);
|
|
|
|
work_queue.push_back(state);
|
2010-11-11 16:20:32 -08:00
|
|
|
}
|
|
|
|
|
2011-12-15 05:14:37 -08:00
|
|
|
return x.first->second;
|
2010-11-11 16:20:32 -08:00
|
|
|
}
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
|
2024-12-24 01:27:10 -08:00
|
|
|
State *DFA::add_new_state(optflags const &opts, NodeSet *nodes, State *other)
|
2014-09-03 14:36:08 -07:00
|
|
|
{
|
|
|
|
/* The splitting of nodes should probably get pushed down into
|
|
|
|
* follow(), ie. put in separate lists from the start
|
|
|
|
*/
|
|
|
|
NodeSet *anodes, *nnodes;
|
|
|
|
split_node_types(nodes, &anodes, &nnodes);
|
|
|
|
|
2024-12-24 01:27:10 -08:00
|
|
|
State *state = add_new_state(opts, anodes, nnodes, other);
|
2014-09-03 14:36:08 -07:00
|
|
|
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
2024-12-24 01:27:10 -08:00
|
|
|
void DFA::update_state_transitions(optflags const &opts, State *state)
|
2010-11-11 16:18:48 -08:00
|
|
|
{
|
|
|
|
/* Compute possible transitions for state->nodes. This is done by
|
|
|
|
* iterating over all the nodes in state->nodes and combining the
|
|
|
|
* transitions.
|
|
|
|
*
|
|
|
|
* The resultant transition set is a mapping of characters to
|
|
|
|
* sets of nodes.
|
2011-12-15 05:14:37 -08:00
|
|
|
*
|
|
|
|
* Note: the follow set for accept nodes is always empty so we don't
|
|
|
|
* need to compute follow for the accept nodes in a protostate
|
2010-11-11 16:18:48 -08:00
|
|
|
*/
|
2011-12-15 04:59:55 -08:00
|
|
|
Cases cases;
|
2021-01-30 02:23:54 -08:00
|
|
|
for (NodeVec::iterator i = state->proto.nnodes->begin(); i != state->proto.nnodes->end(); i++)
|
2010-11-11 16:18:48 -08:00
|
|
|
(*i)->follow(cases);
|
|
|
|
|
|
|
|
/* Now for each set of nodes in the computed transitions, make
|
|
|
|
* sure that there is a state that maps to it, and add the
|
|
|
|
* matching case to the state.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* check the default transition first */
|
2010-11-11 16:20:32 -08:00
|
|
|
if (cases.otherwise)
|
2024-12-24 01:27:10 -08:00
|
|
|
state->otherwise = add_new_state(opts, cases.otherwise,
|
|
|
|
nonmatching);
|
2011-12-15 05:01:35 -08:00
|
|
|
else
|
|
|
|
state->otherwise = nonmatching;
|
2010-11-11 16:18:48 -08:00
|
|
|
|
|
|
|
/* For each transition from *from, check if the set of nodes it
|
|
|
|
* transitions to already has been mapped to a state
|
|
|
|
*/
|
2011-12-15 04:59:55 -08:00
|
|
|
for (Cases::iterator j = cases.begin(); j != cases.end(); j++) {
|
2010-11-11 16:18:48 -08:00
|
|
|
State *target;
|
2024-12-24 01:27:10 -08:00
|
|
|
target = add_new_state(opts, j->second, nonmatching);
|
2010-11-11 16:20:32 -08:00
|
|
|
|
2011-12-15 04:58:33 -08:00
|
|
|
/* Don't insert transition that the otherwise transition
|
2010-11-11 16:18:48 -08:00
|
|
|
* already covers
|
|
|
|
*/
|
2019-08-11 06:18:27 -07:00
|
|
|
if (target != state->otherwise) {
|
2011-12-15 04:58:33 -08:00
|
|
|
state->trans[j->first] = target;
|
2019-08-11 06:18:27 -07:00
|
|
|
if (j->first.c < 0 && -j->first.c > oob_range)
|
|
|
|
oob_range = -j->first.c;
|
|
|
|
}
|
2010-11-11 16:18:48 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-11-11 16:08:02 -08:00
|
|
|
/* WARNING: This routine can only be called from within DFA creation as
|
|
|
|
* the nodes value is only valid during dfa construction.
|
|
|
|
*/
|
|
|
|
void DFA::dump_node_to_dfa(void)
|
2010-11-09 11:55:40 -08:00
|
|
|
{
|
|
|
|
cerr << "Mapping of States to expr nodes\n"
|
|
|
|
" State <= Nodes\n"
|
|
|
|
"-------------------\n";
|
2010-11-11 16:08:02 -08:00
|
|
|
for (Partition::iterator i = states.begin(); i != states.end(); i++)
|
2011-12-15 05:12:30 -08:00
|
|
|
cerr << " " << (*i)->label << " <= " << (*i)->proto << "\n";
|
2010-11-09 11:55:40 -08:00
|
|
|
}
|
|
|
|
|
2023-07-06 16:41:56 -07:00
|
|
|
void DFA::process_work_queue(const char *header, optflags const &opts)
|
2014-09-03 14:32:52 -07:00
|
|
|
{
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
while (!work_queue.empty()) {
|
2023-07-08 19:49:34 -07:00
|
|
|
if (i % 1000 == 0 && (opts.dump & DUMP_DFA_PROGRESS)) {
|
2014-09-03 14:32:52 -07:00
|
|
|
cerr << "\033[2K" << header << ": queue "
|
|
|
|
<< work_queue.size()
|
|
|
|
<< "\tstates "
|
|
|
|
<< states.size()
|
|
|
|
<< "\teliminated duplicates "
|
|
|
|
<< node_map.dup
|
|
|
|
<< "\r";
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
|
|
|
|
State *from = work_queue.front();
|
|
|
|
work_queue.pop_front();
|
|
|
|
|
|
|
|
/* Update 'from's transitions, and if it transitions to any
|
|
|
|
* unknown State create it and add it to the work_queue
|
|
|
|
*/
|
2024-12-24 01:27:10 -08:00
|
|
|
update_state_transitions(opts, from);
|
2014-09-03 14:32:52 -07:00
|
|
|
} /* while (!work_queue.empty()) */
|
|
|
|
}
|
|
|
|
|
2007-02-27 02:29:16 +00:00
|
|
|
/**
|
|
|
|
* Construct a DFA from a syntax tree.
|
|
|
|
*/
|
2023-07-06 16:41:56 -07:00
|
|
|
DFA::DFA(Node *root, optflags const &opts, bool buildfiledfa): root(root), filedfa(buildfiledfa)
|
2007-02-27 02:29:16 +00:00
|
|
|
{
|
2014-01-09 16:55:55 -08:00
|
|
|
diffcount = 0; /* set by diff_encode */
|
2019-08-11 06:18:27 -07:00
|
|
|
max_range = 256;
|
|
|
|
upper_bound = 256;
|
|
|
|
oob_range = 0;
|
|
|
|
ord_range = 8;
|
2010-01-08 02:17:45 -08:00
|
|
|
|
2023-07-08 19:49:34 -07:00
|
|
|
if (opts.dump & DUMP_DFA_PROGRESS)
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
fprintf(stderr, "Creating dfa:\r");
|
2010-01-08 02:17:45 -08:00
|
|
|
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
for (depth_first_traversal i(root); i; i++) {
|
|
|
|
(*i)->compute_nullable();
|
|
|
|
(*i)->compute_firstpos();
|
|
|
|
(*i)->compute_lastpos();
|
|
|
|
}
|
2010-01-08 02:17:45 -08:00
|
|
|
|
2023-07-08 19:49:34 -07:00
|
|
|
if (opts.dump & DUMP_DFA_PROGRESS)
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
fprintf(stderr, "Creating dfa: followpos\r");
|
|
|
|
for (depth_first_traversal i(root); i; i++) {
|
|
|
|
(*i)->compute_followpos();
|
|
|
|
}
|
|
|
|
|
2024-12-24 01:27:10 -08:00
|
|
|
nonmatching = add_new_state(opts, new NodeSet, NULL);
|
|
|
|
start = add_new_state(opts, new NodeSet(root->firstpos), nonmatching);
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
|
2010-11-11 16:19:47 -08:00
|
|
|
/* the work_queue contains the states that need to have their
|
|
|
|
* transitions computed. This could be done with a recursive
|
|
|
|
* algorithm instead of a work_queue, but it would be slightly slower
|
|
|
|
* and consume more memory.
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
*
|
|
|
|
* TODO: currently the work_queue is treated in a breadth first
|
|
|
|
* search manner. Test using the work_queue in a depth first
|
|
|
|
* manner, this may help reduce the number of entries on the
|
|
|
|
* work_queue at any given time, thus reducing peak memory use.
|
|
|
|
*/
|
2010-11-11 16:19:47 -08:00
|
|
|
work_queue.push_back(start);
|
2023-07-06 16:41:56 -07:00
|
|
|
process_work_queue("Creating dfa", opts);
|
2019-08-11 06:18:27 -07:00
|
|
|
max_range += oob_range;
|
|
|
|
/* if oob_range is ever greater than 256 need to move to computing this */
|
|
|
|
if (oob_range)
|
|
|
|
ord_range = 9;
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
/* cleanup Sets of nodes used computing the DFA as they are no longer
|
|
|
|
* needed.
|
|
|
|
*/
|
|
|
|
for (depth_first_traversal i(root); i; i++) {
|
|
|
|
(*i)->firstpos.clear();
|
|
|
|
(*i)->lastpos.clear();
|
|
|
|
(*i)->followpos.clear();
|
2007-02-27 02:29:16 +00:00
|
|
|
}
|
2010-11-09 11:55:40 -08:00
|
|
|
|
2023-07-08 19:49:34 -07:00
|
|
|
if (opts.dump & DUMP_DFA_NODE_TO_DFA)
|
2010-11-11 16:08:02 -08:00
|
|
|
dump_node_to_dfa();
|
2010-11-09 11:55:40 -08:00
|
|
|
|
2023-07-08 19:49:34 -07:00
|
|
|
if (opts.dump & (DUMP_DFA_STATS)) {
|
2011-12-15 05:14:37 -08:00
|
|
|
cerr << "\033[2KCreated dfa: states "
|
|
|
|
<< states.size()
|
|
|
|
<< " proto { "
|
|
|
|
<< node_map
|
|
|
|
<< " }, nnodes { "
|
|
|
|
<< nnodes_cache
|
|
|
|
<< " }, anodes { "
|
|
|
|
<< anodes_cache
|
|
|
|
<< " }\n";
|
|
|
|
}
|
2010-07-10 17:47:25 -07:00
|
|
|
|
2011-12-15 05:14:37 -08:00
|
|
|
/* Clear out uniq_nnodes as they are no longer needed.
|
|
|
|
* Do not clear out uniq_anodes, as we need them for minimizations
|
|
|
|
* diffs, unions, ...
|
|
|
|
*/
|
|
|
|
nnodes_cache.clear();
|
|
|
|
node_map.clear();
|
2007-02-27 02:29:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DFA::~DFA()
|
|
|
|
{
|
2011-12-15 05:14:37 -08:00
|
|
|
anodes_cache.clear();
|
|
|
|
nnodes_cache.clear();
|
|
|
|
|
2011-03-13 05:55:25 -07:00
|
|
|
for (Partition::iterator i = states.begin(); i != states.end(); i++)
|
|
|
|
delete *i;
|
2007-02-27 02:29:16 +00:00
|
|
|
}
|
|
|
|
|
2012-01-06 09:03:20 -08:00
|
|
|
State *DFA::match_len(State *state, const char *str, size_t len)
|
|
|
|
{
|
|
|
|
for (; len > 0; ++str, --len)
|
|
|
|
state = state->next(*str);
|
|
|
|
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
2012-01-06 09:04:36 -08:00
|
|
|
State *DFA::match_until(State *state, const char *str, const char term)
|
|
|
|
{
|
|
|
|
while (*str != term)
|
|
|
|
state = state->next(*str++);
|
|
|
|
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
2012-01-06 09:03:20 -08:00
|
|
|
State *DFA::match(const char *str)
|
|
|
|
{
|
2012-01-06 09:04:36 -08:00
|
|
|
return match_until(start, str, 0);
|
2012-01-06 09:03:20 -08:00
|
|
|
}
|
|
|
|
|
2010-11-09 11:56:28 -08:00
|
|
|
void DFA::dump_uniq_perms(const char *s)
|
|
|
|
{
|
2012-02-16 07:40:21 -08:00
|
|
|
set<perms_t> uniq;
|
2010-11-09 11:56:28 -08:00
|
|
|
for (Partition::iterator i = states.begin(); i != states.end(); i++)
|
2012-02-16 07:40:21 -08:00
|
|
|
uniq.insert((*i)->perms);
|
2010-11-09 11:56:28 -08:00
|
|
|
|
|
|
|
cerr << "Unique Permission sets: " << s << " (" << uniq.size() << ")\n";
|
|
|
|
cerr << "----------------------\n";
|
2012-02-16 07:40:21 -08:00
|
|
|
for (set<perms_t >::iterator i = uniq.begin(); i != uniq.end(); i++) {
|
|
|
|
cerr << " allow:" << hex << i->allow << " deny:"
|
|
|
|
<< i->deny << " audit:" << i->audit
|
|
|
|
<< " quiet:" << i->quiet << dec << "\n";
|
2010-11-09 11:56:28 -08:00
|
|
|
}
|
2023-04-23 19:52:38 -07:00
|
|
|
//TODO: add prompt
|
2010-11-09 11:56:28 -08:00
|
|
|
}
|
|
|
|
|
2025-01-01 16:17:58 -08:00
|
|
|
// make sure work_queue and reachable insertion are always done together
|
|
|
|
static void push_reachable(set<State *> &reachable, list<State *> &work_queue,
|
|
|
|
State *state)
|
|
|
|
{
|
|
|
|
work_queue.push_back(state);
|
|
|
|
reachable.insert(state);
|
|
|
|
}
|
|
|
|
|
2010-01-20 03:32:34 -08:00
|
|
|
/* Remove dead or unreachable states */
|
2023-07-06 16:41:56 -07:00
|
|
|
void DFA::remove_unreachable(optflags const &opts)
|
2010-01-20 03:32:34 -08:00
|
|
|
{
|
2011-03-13 05:55:25 -07:00
|
|
|
set<State *> reachable;
|
2010-01-20 03:32:34 -08:00
|
|
|
|
|
|
|
/* find the set of reachable states */
|
|
|
|
reachable.insert(nonmatching);
|
2025-01-01 16:17:58 -08:00
|
|
|
push_reachable(reachable, work_queue, start);
|
2010-01-20 03:32:34 -08:00
|
|
|
while (!work_queue.empty()) {
|
|
|
|
State *from = work_queue.front();
|
|
|
|
work_queue.pop_front();
|
|
|
|
|
2011-12-15 05:01:35 -08:00
|
|
|
if (from->otherwise != nonmatching &&
|
|
|
|
reachable.find(from->otherwise) == reachable.end())
|
2025-01-01 16:17:58 -08:00
|
|
|
push_reachable(reachable, work_queue, from->otherwise);
|
2010-01-20 03:32:34 -08:00
|
|
|
|
2011-12-15 04:58:33 -08:00
|
|
|
for (StateTrans::iterator j = from->trans.begin(); j != from->trans.end(); j++) {
|
2010-01-20 03:32:34 -08:00
|
|
|
if (reachable.find(j->second) == reachable.end())
|
2025-01-01 16:17:58 -08:00
|
|
|
push_reachable(reachable, work_queue, j->second);
|
2010-01-20 03:32:34 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* walk the set of states and remove any that aren't reachable */
|
|
|
|
if (reachable.size() < states.size()) {
|
|
|
|
int count = 0;
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
Partition::iterator i;
|
|
|
|
Partition::iterator next;
|
2010-01-20 03:32:34 -08:00
|
|
|
for (i = states.begin(); i != states.end(); i = next) {
|
|
|
|
next = i;
|
|
|
|
next++;
|
|
|
|
if (reachable.find(*i) == reachable.end()) {
|
2023-07-08 19:49:34 -07:00
|
|
|
if (opts.dump & DUMP_DFA_UNREACHABLE) {
|
2011-03-13 05:55:25 -07:00
|
|
|
cerr << "unreachable: " << **i;
|
2010-01-20 03:32:34 -08:00
|
|
|
if (*i == start)
|
|
|
|
cerr << " <==";
|
2012-03-22 13:21:55 -07:00
|
|
|
if ((*i)->perms.is_accept())
|
2012-03-22 07:49:43 -07:00
|
|
|
(*i)->perms.dump(cerr);
|
2011-03-13 05:55:25 -07:00
|
|
|
cerr << "\n";
|
2010-01-20 03:32:34 -08:00
|
|
|
}
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
State *current = *i;
|
|
|
|
states.erase(i);
|
|
|
|
delete(current);
|
|
|
|
count++;
|
2010-01-20 03:32:34 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-08 19:49:34 -07:00
|
|
|
if (count && (opts.dump & DUMP_DFA_STATS))
|
2010-01-20 03:32:34 -08:00
|
|
|
cerr << "DFA: states " << states.size() << " removed "
|
|
|
|
<< count << " unreachable states\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* test if two states have the same transitions under partition_map */
|
2010-11-09 11:57:43 -08:00
|
|
|
bool DFA::same_mappings(State *s1, State *s2)
|
2010-01-20 03:32:34 -08:00
|
|
|
{
|
Fix dfa minimization
So DFA minimization has a bug and feature that keeps it from minimizing
some dfas completely. This feature/bug did not result in incorrect dfas,
it just fails to result in full minimization.
The same mappings comparison is wrong. Or more correctly it is right when
transitions are not remapped to minimization partitions, but it may be
wrong when states are remapped. This means it will cause excess
partitioning (not removing all the states it should).
The trans hashing does a "guess" at partition splitting as a performance
enhancement. Basically it leverages the information that states that have
different transitions or transitions on different characters are not the
same. However this isn't always the case, because minimization can cause
some of those transitions to be altered. In previous testing this was
always a win, with only a few extra states being added some times. However
this changes with when the same mappings are fixed, as the hashing that was
done was based on the same flawed mapping as the broken same mappings.
If the same mappings are fixed and the hashing is not removed then there
is little to no change. However with both changes applied some dfas see
significant improvements. These improvements often result in performance
improvements despite minimization doing more work, because it means less
work to be done in the chfa comb compression
eg. test case that raised the issue (thanks tyler)
/t { mount fstype=ext2, mount, }
used to be minimized to
{1} <== (allow/deny/audit/quiet)
{6} (0x 2/0/0/0)
{1} -> {2}: 0x7
{2} -> {3}: 0x0
{2} -> {2}: []
{3} -> {4}: 0x0
{3} -> {3}: []
{4} -> {6}: 0x0
{4} -> {7}: 0x65 e
{4} -> {5}: []
{5} -> {6}: 0x0
{5} -> {5}: []
{6} (0x 2/0/0/0) -> {6}: [^\0x0]
{7} -> {6}: 0x0
{7} -> {8}: 0x78 x
{7} -> {5}: []
{8} -> {6}: 0x0
{8} -> {5}: 0x74 t
{8} -> {5}: []
with the patch it is now properly minimized to
{1} <== (allow/deny/audit/quiet)
{6} (0x 2/0/0/0)
{1} -> {2}: 0x7
{2} -> {3}: 0x0
{2} -> {2}: []
{3} -> {4}: 0x0
{3} -> {3}: []
{4} -> {6}: 0x0
{4} -> {4}: []
{6} (0x 2/0/0/0) -> {6}: [^\0x0]
The evince profile set sees some significant improvements picking a couple
example from its "minimized" dfas (it has 12) we see a reduction from 9720
states to 6232 states, and 6537 states to 3653 states. All told seeing the
performance/profile size going from
2.8 parser: 4.607s 1007267 bytes
dev head: 3.48s 1007267 bytes
min fix: 2.68s 549603 bytes
of course evince is an extreme example so a few more
firefox
2.066s 404549 bytes
to
1.336s 250585 bytes
cupsd
0.365s 90834 bytes
to
0.293s 58855 bytes
dnsmasq
0.118s 35689 bytes
to
0.112s 27992 bytes
smbd
0.187s 40897 bytes
to
0.162s 33665 bytes
weather applet profile from ubuntu touch
0.618s 105673 bytes
to
0.432s 89300 bytes
I have not seen a case where the parser regresses on performance but it is
possible. This patch will not cause a regression on generated policy size,
at worst it will result in policy that is the same size
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Steve Beattie <steve@nxnw.org>
2014-01-09 17:06:48 -08:00
|
|
|
/* assumes otherwise is set to best choice, if there are multiple
|
2014-01-09 17:24:40 -08:00
|
|
|
* otherwise choices this will fail to fully minimize the dfa
|
Fix dfa minimization
So DFA minimization has a bug and feature that keeps it from minimizing
some dfas completely. This feature/bug did not result in incorrect dfas,
it just fails to result in full minimization.
The same mappings comparison is wrong. Or more correctly it is right when
transitions are not remapped to minimization partitions, but it may be
wrong when states are remapped. This means it will cause excess
partitioning (not removing all the states it should).
The trans hashing does a "guess" at partition splitting as a performance
enhancement. Basically it leverages the information that states that have
different transitions or transitions on different characters are not the
same. However this isn't always the case, because minimization can cause
some of those transitions to be altered. In previous testing this was
always a win, with only a few extra states being added some times. However
this changes with when the same mappings are fixed, as the hashing that was
done was based on the same flawed mapping as the broken same mappings.
If the same mappings are fixed and the hashing is not removed then there
is little to no change. However with both changes applied some dfas see
significant improvements. These improvements often result in performance
improvements despite minimization doing more work, because it means less
work to be done in the chfa comb compression
eg. test case that raised the issue (thanks tyler)
/t { mount fstype=ext2, mount, }
used to be minimized to
{1} <== (allow/deny/audit/quiet)
{6} (0x 2/0/0/0)
{1} -> {2}: 0x7
{2} -> {3}: 0x0
{2} -> {2}: []
{3} -> {4}: 0x0
{3} -> {3}: []
{4} -> {6}: 0x0
{4} -> {7}: 0x65 e
{4} -> {5}: []
{5} -> {6}: 0x0
{5} -> {5}: []
{6} (0x 2/0/0/0) -> {6}: [^\0x0]
{7} -> {6}: 0x0
{7} -> {8}: 0x78 x
{7} -> {5}: []
{8} -> {6}: 0x0
{8} -> {5}: 0x74 t
{8} -> {5}: []
with the patch it is now properly minimized to
{1} <== (allow/deny/audit/quiet)
{6} (0x 2/0/0/0)
{1} -> {2}: 0x7
{2} -> {3}: 0x0
{2} -> {2}: []
{3} -> {4}: 0x0
{3} -> {3}: []
{4} -> {6}: 0x0
{4} -> {4}: []
{6} (0x 2/0/0/0) -> {6}: [^\0x0]
The evince profile set sees some significant improvements picking a couple
example from its "minimized" dfas (it has 12) we see a reduction from 9720
states to 6232 states, and 6537 states to 3653 states. All told seeing the
performance/profile size going from
2.8 parser: 4.607s 1007267 bytes
dev head: 3.48s 1007267 bytes
min fix: 2.68s 549603 bytes
of course evince is an extreme example so a few more
firefox
2.066s 404549 bytes
to
1.336s 250585 bytes
cupsd
0.365s 90834 bytes
to
0.293s 58855 bytes
dnsmasq
0.118s 35689 bytes
to
0.112s 27992 bytes
smbd
0.187s 40897 bytes
to
0.162s 33665 bytes
weather applet profile from ubuntu touch
0.618s 105673 bytes
to
0.432s 89300 bytes
I have not seen a case where the parser regresses on performance but it is
possible. This patch will not cause a regression on generated policy size,
at worst it will result in policy that is the same size
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Steve Beattie <steve@nxnw.org>
2014-01-09 17:06:48 -08:00
|
|
|
* if we are not careful. Make sure in cases with multiple
|
|
|
|
* equiv otherwise we always choose the same otherwise to avoid
|
|
|
|
*/
|
2012-03-22 07:50:35 -07:00
|
|
|
if (s1->otherwise->partition != s2->otherwise->partition)
|
2010-01-20 03:32:34 -08:00
|
|
|
return false;
|
|
|
|
|
Fix dfa minimization
So DFA minimization has a bug and feature that keeps it from minimizing
some dfas completely. This feature/bug did not result in incorrect dfas,
it just fails to result in full minimization.
The same mappings comparison is wrong. Or more correctly it is right when
transitions are not remapped to minimization partitions, but it may be
wrong when states are remapped. This means it will cause excess
partitioning (not removing all the states it should).
The trans hashing does a "guess" at partition splitting as a performance
enhancement. Basically it leverages the information that states that have
different transitions or transitions on different characters are not the
same. However this isn't always the case, because minimization can cause
some of those transitions to be altered. In previous testing this was
always a win, with only a few extra states being added some times. However
this changes with when the same mappings are fixed, as the hashing that was
done was based on the same flawed mapping as the broken same mappings.
If the same mappings are fixed and the hashing is not removed then there
is little to no change. However with both changes applied some dfas see
significant improvements. These improvements often result in performance
improvements despite minimization doing more work, because it means less
work to be done in the chfa comb compression
eg. test case that raised the issue (thanks tyler)
/t { mount fstype=ext2, mount, }
used to be minimized to
{1} <== (allow/deny/audit/quiet)
{6} (0x 2/0/0/0)
{1} -> {2}: 0x7
{2} -> {3}: 0x0
{2} -> {2}: []
{3} -> {4}: 0x0
{3} -> {3}: []
{4} -> {6}: 0x0
{4} -> {7}: 0x65 e
{4} -> {5}: []
{5} -> {6}: 0x0
{5} -> {5}: []
{6} (0x 2/0/0/0) -> {6}: [^\0x0]
{7} -> {6}: 0x0
{7} -> {8}: 0x78 x
{7} -> {5}: []
{8} -> {6}: 0x0
{8} -> {5}: 0x74 t
{8} -> {5}: []
with the patch it is now properly minimized to
{1} <== (allow/deny/audit/quiet)
{6} (0x 2/0/0/0)
{1} -> {2}: 0x7
{2} -> {3}: 0x0
{2} -> {2}: []
{3} -> {4}: 0x0
{3} -> {3}: []
{4} -> {6}: 0x0
{4} -> {4}: []
{6} (0x 2/0/0/0) -> {6}: [^\0x0]
The evince profile set sees some significant improvements picking a couple
example from its "minimized" dfas (it has 12) we see a reduction from 9720
states to 6232 states, and 6537 states to 3653 states. All told seeing the
performance/profile size going from
2.8 parser: 4.607s 1007267 bytes
dev head: 3.48s 1007267 bytes
min fix: 2.68s 549603 bytes
of course evince is an extreme example so a few more
firefox
2.066s 404549 bytes
to
1.336s 250585 bytes
cupsd
0.365s 90834 bytes
to
0.293s 58855 bytes
dnsmasq
0.118s 35689 bytes
to
0.112s 27992 bytes
smbd
0.187s 40897 bytes
to
0.162s 33665 bytes
weather applet profile from ubuntu touch
0.618s 105673 bytes
to
0.432s 89300 bytes
I have not seen a case where the parser regresses on performance but it is
possible. This patch will not cause a regression on generated policy size,
at worst it will result in policy that is the same size
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Steve Beattie <steve@nxnw.org>
2014-01-09 17:06:48 -08:00
|
|
|
StateTrans::iterator j1;
|
|
|
|
StateTrans::iterator j2;
|
|
|
|
for (j1 = s1->trans.begin(), j2 = s2->trans.begin();
|
|
|
|
j1 != s1->trans.end() && j2 != s2->trans.end();
|
|
|
|
/*inc inline*/) {
|
|
|
|
if (j1->first < j2->first) {
|
|
|
|
if (j1->second->partition != s2->otherwise->partition)
|
|
|
|
return false;
|
|
|
|
j1++;
|
|
|
|
} else if (j1->first == j2->first) {
|
|
|
|
if (j1->second->partition != j2->second->partition)
|
|
|
|
return false;
|
|
|
|
j1++;
|
|
|
|
j2++;
|
|
|
|
} else {
|
|
|
|
if (s1->otherwise->partition != j2->second->partition)
|
|
|
|
return false;
|
|
|
|
j2++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for ( ; j1 != s1->trans.end(); j1++) {
|
|
|
|
if (j1->second->partition != s2->otherwise->partition)
|
2010-01-20 03:32:34 -08:00
|
|
|
return false;
|
Fix dfa minimization
So DFA minimization has a bug and feature that keeps it from minimizing
some dfas completely. This feature/bug did not result in incorrect dfas,
it just fails to result in full minimization.
The same mappings comparison is wrong. Or more correctly it is right when
transitions are not remapped to minimization partitions, but it may be
wrong when states are remapped. This means it will cause excess
partitioning (not removing all the states it should).
The trans hashing does a "guess" at partition splitting as a performance
enhancement. Basically it leverages the information that states that have
different transitions or transitions on different characters are not the
same. However this isn't always the case, because minimization can cause
some of those transitions to be altered. In previous testing this was
always a win, with only a few extra states being added some times. However
this changes with when the same mappings are fixed, as the hashing that was
done was based on the same flawed mapping as the broken same mappings.
If the same mappings are fixed and the hashing is not removed then there
is little to no change. However with both changes applied some dfas see
significant improvements. These improvements often result in performance
improvements despite minimization doing more work, because it means less
work to be done in the chfa comb compression
eg. test case that raised the issue (thanks tyler)
/t { mount fstype=ext2, mount, }
used to be minimized to
{1} <== (allow/deny/audit/quiet)
{6} (0x 2/0/0/0)
{1} -> {2}: 0x7
{2} -> {3}: 0x0
{2} -> {2}: []
{3} -> {4}: 0x0
{3} -> {3}: []
{4} -> {6}: 0x0
{4} -> {7}: 0x65 e
{4} -> {5}: []
{5} -> {6}: 0x0
{5} -> {5}: []
{6} (0x 2/0/0/0) -> {6}: [^\0x0]
{7} -> {6}: 0x0
{7} -> {8}: 0x78 x
{7} -> {5}: []
{8} -> {6}: 0x0
{8} -> {5}: 0x74 t
{8} -> {5}: []
with the patch it is now properly minimized to
{1} <== (allow/deny/audit/quiet)
{6} (0x 2/0/0/0)
{1} -> {2}: 0x7
{2} -> {3}: 0x0
{2} -> {2}: []
{3} -> {4}: 0x0
{3} -> {3}: []
{4} -> {6}: 0x0
{4} -> {4}: []
{6} (0x 2/0/0/0) -> {6}: [^\0x0]
The evince profile set sees some significant improvements picking a couple
example from its "minimized" dfas (it has 12) we see a reduction from 9720
states to 6232 states, and 6537 states to 3653 states. All told seeing the
performance/profile size going from
2.8 parser: 4.607s 1007267 bytes
dev head: 3.48s 1007267 bytes
min fix: 2.68s 549603 bytes
of course evince is an extreme example so a few more
firefox
2.066s 404549 bytes
to
1.336s 250585 bytes
cupsd
0.365s 90834 bytes
to
0.293s 58855 bytes
dnsmasq
0.118s 35689 bytes
to
0.112s 27992 bytes
smbd
0.187s 40897 bytes
to
0.162s 33665 bytes
weather applet profile from ubuntu touch
0.618s 105673 bytes
to
0.432s 89300 bytes
I have not seen a case where the parser regresses on performance but it is
possible. This patch will not cause a regression on generated policy size,
at worst it will result in policy that is the same size
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Steve Beattie <steve@nxnw.org>
2014-01-09 17:06:48 -08:00
|
|
|
}
|
|
|
|
for ( ; j2 != s2->trans.end(); j2++) {
|
|
|
|
if (j2->second->partition != s1->otherwise->partition)
|
2010-01-20 03:32:34 -08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-02-16 07:41:40 -08:00
|
|
|
int DFA::apply_and_clear_deny(void)
|
|
|
|
{
|
|
|
|
int c = 0;
|
|
|
|
for (Partition::iterator i = states.begin(); i != states.end(); i++)
|
|
|
|
c += (*i)->apply_and_clear_deny();
|
|
|
|
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
2023-04-23 19:52:38 -07:00
|
|
|
|
2010-01-20 03:32:34 -08:00
|
|
|
/* minimize the number of dfa states */
|
2023-07-06 16:41:56 -07:00
|
|
|
void DFA::minimize(optflags const &opts)
|
2010-01-20 03:32:34 -08:00
|
|
|
{
|
2024-05-09 20:00:41 -07:00
|
|
|
map<perms_t, Partition *> perm_map;
|
2011-03-13 05:55:25 -07:00
|
|
|
list<Partition *> partitions;
|
|
|
|
|
2010-11-09 11:22:54 -08:00
|
|
|
/* Set up the initial partitions
|
2020-11-19 12:30:04 -08:00
|
|
|
* minimum of - 1 non accepting, and 1 accepting
|
2010-01-20 03:32:34 -08:00
|
|
|
*/
|
|
|
|
int accept_count = 0;
|
2010-11-09 11:26:50 -08:00
|
|
|
int final_accept = 0;
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
|
2024-05-09 20:00:41 -07:00
|
|
|
map<perms_t, Partition *>::iterator p = perm_map.find((*i)->perms);
|
2010-01-20 03:32:34 -08:00
|
|
|
if (p == perm_map.end()) {
|
2010-01-31 23:18:14 -08:00
|
|
|
Partition *part = new Partition();
|
2010-01-31 23:19:54 -08:00
|
|
|
part->push_back(*i);
|
2024-05-09 20:00:41 -07:00
|
|
|
perm_map.insert(make_pair((*i)->perms, part));
|
2010-01-20 03:32:34 -08:00
|
|
|
partitions.push_back(part);
|
2010-11-09 11:57:43 -08:00
|
|
|
(*i)->partition = part;
|
2024-05-09 20:00:41 -07:00
|
|
|
if ((*i)->perms.is_accept())
|
2010-01-20 03:32:34 -08:00
|
|
|
accept_count++;
|
|
|
|
} else {
|
2010-11-09 11:57:43 -08:00
|
|
|
(*i)->partition = p->second;
|
2010-01-31 23:19:54 -08:00
|
|
|
p->second->push_back(*i);
|
2010-01-20 03:32:34 -08:00
|
|
|
}
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
|
2023-07-08 19:49:34 -07:00
|
|
|
if ((opts.dump & DUMP_DFA_PROGRESS) && (partitions.size() % 1000 == 0))
|
2011-03-13 05:55:25 -07:00
|
|
|
cerr << "\033[2KMinimize dfa: partitions "
|
|
|
|
<< partitions.size() << "\tinit " << partitions.size()
|
|
|
|
<< " (accept " << accept_count << ")\r";
|
2010-01-20 03:32:34 -08:00
|
|
|
}
|
2012-02-16 07:41:40 -08:00
|
|
|
|
2010-11-09 11:26:18 -08:00
|
|
|
/* perm_map is no longer needed so free the memory it is using.
|
|
|
|
* Don't remove - doing it manually here helps reduce peak memory usage.
|
|
|
|
*/
|
|
|
|
perm_map.clear();
|
|
|
|
|
2010-01-20 03:32:34 -08:00
|
|
|
int init_count = partitions.size();
|
2023-07-08 19:49:34 -07:00
|
|
|
if (opts.dump & DUMP_DFA_PROGRESS)
|
2011-03-13 05:55:25 -07:00
|
|
|
cerr << "\033[2KMinimize dfa: partitions " << partitions.size()
|
|
|
|
<< "\tinit " << init_count << " (accept "
|
|
|
|
<< accept_count << ")\r";
|
2010-01-20 03:32:34 -08:00
|
|
|
|
|
|
|
/* Now do repartitioning until each partition contains the set of
|
|
|
|
* states that are the same. This will happen when the partition
|
|
|
|
* splitting stables. With a worse case of 1 state per partition
|
|
|
|
* ie. already minimized.
|
|
|
|
*/
|
2010-01-31 23:18:14 -08:00
|
|
|
Partition *new_part;
|
2010-01-20 03:32:34 -08:00
|
|
|
int new_part_count;
|
|
|
|
do {
|
|
|
|
new_part_count = 0;
|
2011-03-13 05:55:25 -07:00
|
|
|
for (list<Partition *>::iterator p = partitions.begin();
|
2010-01-20 03:32:34 -08:00
|
|
|
p != partitions.end(); p++) {
|
|
|
|
new_part = NULL;
|
|
|
|
State *rep = *((*p)->begin());
|
2010-01-31 23:18:14 -08:00
|
|
|
Partition::iterator next;
|
2011-03-13 05:55:25 -07:00
|
|
|
for (Partition::iterator s = ++(*p)->begin(); s != (*p)->end();) {
|
2010-11-09 11:57:43 -08:00
|
|
|
if (same_mappings(rep, *s)) {
|
2010-01-31 23:21:00 -08:00
|
|
|
++s;
|
2010-01-20 03:32:34 -08:00
|
|
|
continue;
|
2010-01-31 23:21:00 -08:00
|
|
|
}
|
2010-01-20 03:32:34 -08:00
|
|
|
if (!new_part) {
|
2010-01-31 23:18:14 -08:00
|
|
|
new_part = new Partition;
|
2011-03-13 05:55:25 -07:00
|
|
|
list<Partition *>::iterator tmp = p;
|
2010-01-31 23:21:00 -08:00
|
|
|
partitions.insert(++tmp, new_part);
|
|
|
|
new_part_count++;
|
2010-01-20 03:32:34 -08:00
|
|
|
}
|
2010-01-31 23:19:54 -08:00
|
|
|
new_part->push_back(*s);
|
2010-01-31 23:21:00 -08:00
|
|
|
s = (*p)->erase(s);
|
2010-01-20 03:32:34 -08:00
|
|
|
}
|
2010-01-31 23:21:00 -08:00
|
|
|
/* remapping partition_map for new_part entries
|
|
|
|
* Do not do this above as it messes up same_mappings
|
|
|
|
*/
|
2010-01-20 03:32:34 -08:00
|
|
|
if (new_part) {
|
2010-01-31 23:18:14 -08:00
|
|
|
for (Partition::iterator m = new_part->begin();
|
2010-01-20 03:32:34 -08:00
|
|
|
m != new_part->end(); m++) {
|
2010-11-09 11:57:43 -08:00
|
|
|
(*m)->partition = new_part;
|
2010-01-20 03:32:34 -08:00
|
|
|
}
|
|
|
|
}
|
2023-07-08 19:49:34 -07:00
|
|
|
if ((opts.dump & DUMP_DFA_PROGRESS) && (partitions.size() % 100 == 0))
|
2011-03-13 05:55:25 -07:00
|
|
|
cerr << "\033[2KMinimize dfa: partitions "
|
|
|
|
<< partitions.size() << "\tinit "
|
|
|
|
<< init_count << " (accept "
|
|
|
|
<< accept_count << ")\r";
|
2010-01-31 23:12:33 -08:00
|
|
|
}
|
2011-03-13 05:55:25 -07:00
|
|
|
} while (new_part_count);
|
2010-01-20 03:32:34 -08:00
|
|
|
|
2010-11-09 11:26:50 -08:00
|
|
|
if (partitions.size() == states.size()) {
|
2023-07-08 19:49:34 -07:00
|
|
|
if (opts.dump & DUMP_DFA_STATS)
|
2011-03-13 05:55:25 -07:00
|
|
|
cerr << "\033[2KDfa minimization no states removed: partitions "
|
|
|
|
<< partitions.size() << "\tinit " << init_count
|
|
|
|
<< " (accept " << accept_count << ")\n";
|
2010-01-20 03:32:34 -08:00
|
|
|
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Remap the dfa so it uses the representative states
|
|
|
|
* Use the first state of a partition as the representative state
|
2020-11-19 12:30:04 -08:00
|
|
|
* At this point all states with in a partition have transitions
|
2010-11-09 11:27:36 -08:00
|
|
|
* to states within the same partitions, however this can slow
|
|
|
|
* down compressed dfa compression as there are more states,
|
2010-01-20 03:32:34 -08:00
|
|
|
*/
|
2023-07-08 19:49:34 -07:00
|
|
|
if (opts.dump & DUMP_DFA_MIN_PARTS)
|
2011-05-20 09:26:44 -07:00
|
|
|
cerr << "Partitions after minimization\n";
|
2011-03-13 05:55:25 -07:00
|
|
|
for (list<Partition *>::iterator p = partitions.begin();
|
2010-01-20 03:32:34 -08:00
|
|
|
p != partitions.end(); p++) {
|
|
|
|
/* representative state for this partition */
|
|
|
|
State *rep = *((*p)->begin());
|
2023-07-08 19:49:34 -07:00
|
|
|
if (opts.dump & DUMP_DFA_MIN_PARTS)
|
2011-05-20 09:26:44 -07:00
|
|
|
cerr << *rep << " : ";
|
2010-01-20 03:32:34 -08:00
|
|
|
|
|
|
|
/* update representative state's transitions */
|
2012-03-22 07:50:35 -07:00
|
|
|
rep->otherwise = *rep->otherwise->partition->begin();
|
|
|
|
|
Fix dfa minimization
So DFA minimization has a bug and feature that keeps it from minimizing
some dfas completely. This feature/bug did not result in incorrect dfas,
it just fails to result in full minimization.
The same mappings comparison is wrong. Or more correctly it is right when
transitions are not remapped to minimization partitions, but it may be
wrong when states are remapped. This means it will cause excess
partitioning (not removing all the states it should).
The trans hashing does a "guess" at partition splitting as a performance
enhancement. Basically it leverages the information that states that have
different transitions or transitions on different characters are not the
same. However this isn't always the case, because minimization can cause
some of those transitions to be altered. In previous testing this was
always a win, with only a few extra states being added some times. However
this changes with when the same mappings are fixed, as the hashing that was
done was based on the same flawed mapping as the broken same mappings.
If the same mappings are fixed and the hashing is not removed then there
is little to no change. However with both changes applied some dfas see
significant improvements. These improvements often result in performance
improvements despite minimization doing more work, because it means less
work to be done in the chfa comb compression
eg. test case that raised the issue (thanks tyler)
/t { mount fstype=ext2, mount, }
used to be minimized to
{1} <== (allow/deny/audit/quiet)
{6} (0x 2/0/0/0)
{1} -> {2}: 0x7
{2} -> {3}: 0x0
{2} -> {2}: []
{3} -> {4}: 0x0
{3} -> {3}: []
{4} -> {6}: 0x0
{4} -> {7}: 0x65 e
{4} -> {5}: []
{5} -> {6}: 0x0
{5} -> {5}: []
{6} (0x 2/0/0/0) -> {6}: [^\0x0]
{7} -> {6}: 0x0
{7} -> {8}: 0x78 x
{7} -> {5}: []
{8} -> {6}: 0x0
{8} -> {5}: 0x74 t
{8} -> {5}: []
with the patch it is now properly minimized to
{1} <== (allow/deny/audit/quiet)
{6} (0x 2/0/0/0)
{1} -> {2}: 0x7
{2} -> {3}: 0x0
{2} -> {2}: []
{3} -> {4}: 0x0
{3} -> {3}: []
{4} -> {6}: 0x0
{4} -> {4}: []
{6} (0x 2/0/0/0) -> {6}: [^\0x0]
The evince profile set sees some significant improvements picking a couple
example from its "minimized" dfas (it has 12) we see a reduction from 9720
states to 6232 states, and 6537 states to 3653 states. All told seeing the
performance/profile size going from
2.8 parser: 4.607s 1007267 bytes
dev head: 3.48s 1007267 bytes
min fix: 2.68s 549603 bytes
of course evince is an extreme example so a few more
firefox
2.066s 404549 bytes
to
1.336s 250585 bytes
cupsd
0.365s 90834 bytes
to
0.293s 58855 bytes
dnsmasq
0.118s 35689 bytes
to
0.112s 27992 bytes
smbd
0.187s 40897 bytes
to
0.162s 33665 bytes
weather applet profile from ubuntu touch
0.618s 105673 bytes
to
0.432s 89300 bytes
I have not seen a case where the parser regresses on performance but it is
possible. This patch will not cause a regression on generated policy size,
at worst it will result in policy that is the same size
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Steve Beattie <steve@nxnw.org>
2014-01-09 17:06:48 -08:00
|
|
|
for (StateTrans::iterator c = rep->trans.begin(); c != rep->trans.end(); ) {
|
2010-11-09 11:57:43 -08:00
|
|
|
Partition *partition = c->second->partition;
|
Fix dfa minimization
So DFA minimization has a bug and feature that keeps it from minimizing
some dfas completely. This feature/bug did not result in incorrect dfas,
it just fails to result in full minimization.
The same mappings comparison is wrong. Or more correctly it is right when
transitions are not remapped to minimization partitions, but it may be
wrong when states are remapped. This means it will cause excess
partitioning (not removing all the states it should).
The trans hashing does a "guess" at partition splitting as a performance
enhancement. Basically it leverages the information that states that have
different transitions or transitions on different characters are not the
same. However this isn't always the case, because minimization can cause
some of those transitions to be altered. In previous testing this was
always a win, with only a few extra states being added some times. However
this changes with when the same mappings are fixed, as the hashing that was
done was based on the same flawed mapping as the broken same mappings.
If the same mappings are fixed and the hashing is not removed then there
is little to no change. However with both changes applied some dfas see
significant improvements. These improvements often result in performance
improvements despite minimization doing more work, because it means less
work to be done in the chfa comb compression
eg. test case that raised the issue (thanks tyler)
/t { mount fstype=ext2, mount, }
used to be minimized to
{1} <== (allow/deny/audit/quiet)
{6} (0x 2/0/0/0)
{1} -> {2}: 0x7
{2} -> {3}: 0x0
{2} -> {2}: []
{3} -> {4}: 0x0
{3} -> {3}: []
{4} -> {6}: 0x0
{4} -> {7}: 0x65 e
{4} -> {5}: []
{5} -> {6}: 0x0
{5} -> {5}: []
{6} (0x 2/0/0/0) -> {6}: [^\0x0]
{7} -> {6}: 0x0
{7} -> {8}: 0x78 x
{7} -> {5}: []
{8} -> {6}: 0x0
{8} -> {5}: 0x74 t
{8} -> {5}: []
with the patch it is now properly minimized to
{1} <== (allow/deny/audit/quiet)
{6} (0x 2/0/0/0)
{1} -> {2}: 0x7
{2} -> {3}: 0x0
{2} -> {2}: []
{3} -> {4}: 0x0
{3} -> {3}: []
{4} -> {6}: 0x0
{4} -> {4}: []
{6} (0x 2/0/0/0) -> {6}: [^\0x0]
The evince profile set sees some significant improvements picking a couple
example from its "minimized" dfas (it has 12) we see a reduction from 9720
states to 6232 states, and 6537 states to 3653 states. All told seeing the
performance/profile size going from
2.8 parser: 4.607s 1007267 bytes
dev head: 3.48s 1007267 bytes
min fix: 2.68s 549603 bytes
of course evince is an extreme example so a few more
firefox
2.066s 404549 bytes
to
1.336s 250585 bytes
cupsd
0.365s 90834 bytes
to
0.293s 58855 bytes
dnsmasq
0.118s 35689 bytes
to
0.112s 27992 bytes
smbd
0.187s 40897 bytes
to
0.162s 33665 bytes
weather applet profile from ubuntu touch
0.618s 105673 bytes
to
0.432s 89300 bytes
I have not seen a case where the parser regresses on performance but it is
possible. This patch will not cause a regression on generated policy size,
at worst it will result in policy that is the same size
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Steve Beattie <steve@nxnw.org>
2014-01-09 17:06:48 -08:00
|
|
|
if (rep->otherwise != *partition->begin()) {
|
|
|
|
c->second = *partition->begin();
|
|
|
|
c++;
|
|
|
|
} else
|
|
|
|
/* transition is now covered by otherwise */
|
|
|
|
c = rep->trans.erase(c);
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
}
|
|
|
|
|
2010-11-09 11:24:51 -08:00
|
|
|
/* clear the state label for all non representative states,
|
|
|
|
* and accumulate permissions */
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
for (Partition::iterator i = ++(*p)->begin(); i != (*p)->end(); i++) {
|
2023-07-08 19:49:34 -07:00
|
|
|
if (opts.dump & DUMP_DFA_MIN_PARTS)
|
2011-05-20 09:26:44 -07:00
|
|
|
cerr << **i << ", ";
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
(*i)->label = -1;
|
2020-07-07 09:42:41 -07:00
|
|
|
rep->perms.add((*i)->perms, filedfa);
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
}
|
2012-03-22 13:21:55 -07:00
|
|
|
if (rep->perms.is_accept())
|
2010-11-09 11:26:50 -08:00
|
|
|
final_accept++;
|
2023-07-08 19:49:34 -07:00
|
|
|
if (opts.dump & DUMP_DFA_MIN_PARTS)
|
2011-05-20 09:26:44 -07:00
|
|
|
cerr << "\n";
|
2010-01-20 03:32:34 -08:00
|
|
|
}
|
2023-07-08 19:49:34 -07:00
|
|
|
if (opts.dump & DUMP_DFA_STATS)
|
2011-03-13 05:55:25 -07:00
|
|
|
cerr << "\033[2KMinimized dfa: final partitions "
|
|
|
|
<< partitions.size() << " (accept " << final_accept
|
|
|
|
<< ")" << "\tinit " << init_count << " (accept "
|
|
|
|
<< accept_count << ")\n";
|
2010-01-20 03:32:34 -08:00
|
|
|
|
|
|
|
/* make sure nonmatching and start state are up to date with the
|
|
|
|
* mappings */
|
|
|
|
{
|
2010-11-09 11:57:43 -08:00
|
|
|
Partition *partition = nonmatching->partition;
|
2010-01-20 03:32:34 -08:00
|
|
|
if (*partition->begin() != nonmatching) {
|
|
|
|
nonmatching = *partition->begin();
|
|
|
|
}
|
|
|
|
|
2010-11-09 11:57:43 -08:00
|
|
|
partition = start->partition;
|
2010-01-20 03:32:34 -08:00
|
|
|
if (*partition->begin() != start) {
|
|
|
|
start = *partition->begin();
|
|
|
|
}
|
|
|
|
}
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
|
2010-01-20 03:32:34 -08:00
|
|
|
/* Now that the states have been remapped, remove all states
|
2020-11-19 12:30:04 -08:00
|
|
|
* that are not the representative states for their partition, they
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
* will have a label == -1
|
2010-01-20 03:32:34 -08:00
|
|
|
*/
|
2011-03-13 05:55:25 -07:00
|
|
|
for (Partition::iterator i = states.begin(); i != states.end();) {
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
if ((*i)->label == -1) {
|
2010-03-13 02:23:23 -08:00
|
|
|
State *s = *i;
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
i = states.erase(i);
|
2010-03-13 02:23:23 -08:00
|
|
|
delete(s);
|
This patch reworks the internal structures used to compute the dfa. It is on
the large side, and I experimented with different ways to split this up but in
the end, anything I could do would result in a series of dependent patches
that would require all of them to be applied to get meaningful functional
changes.
The patch structural reworks the dfa so that
- there is a new State class, it takes the place of sets of nodes in the
dfa, and allows storing state information within the state
- removes the dfa transition table, which mapped sets of nodes to a
transition table, by moving the transition into the new state class
- computes dfa state permissions once (stored in the state)
- expression tree nodes are independent from a created dfa. This allows
computed expression trees, and sets of Nodes (used as protostates when
computing the dfa). To be managed independent of the dfa life time.
This will allow reducing the amount of memory used, in the future,
and will also allow separating the expression tree logic out into
its own file.
The patch has some effect on reducing peak memory usage, and computation
time. The actual amount of reduction is dependent on the number of states
in the dfa with larger saving being achieved on larger dfas. Eg. for
the test evince profile I was using it makes the parser about 7% faster with a
peak memory usage about 12% less.
This patch changes the initial partition hashing of minimization resulting
in slightly smaller dfas.
2010-11-09 11:14:55 -08:00
|
|
|
} else
|
|
|
|
i++;
|
2010-01-20 03:32:34 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
/* Cleanup */
|
|
|
|
while (!partitions.empty()) {
|
2010-01-31 23:18:14 -08:00
|
|
|
Partition *p = partitions.front();
|
2010-01-20 03:32:34 -08:00
|
|
|
partitions.pop_front();
|
|
|
|
delete(p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-09 16:55:55 -08:00
|
|
|
|
|
|
|
/* diff_encode helper functions */
|
|
|
|
static unsigned int add_to_dag(DiffDag *dag, State *state,
|
|
|
|
State *parent)
|
|
|
|
{
|
|
|
|
unsigned int rc = 0;
|
|
|
|
if (!state->diff) {
|
|
|
|
dag->rel = NULL;
|
|
|
|
if (parent)
|
|
|
|
dag->depth = parent->diff->depth + 1;
|
|
|
|
else
|
|
|
|
dag->depth = 1;
|
|
|
|
dag->state = state;
|
|
|
|
state->diff = dag;
|
|
|
|
rc = 1;
|
|
|
|
}
|
|
|
|
if (parent && parent->diff->depth < state->diff->depth)
|
|
|
|
state->diff->parents.push_back(parent);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2019-08-11 06:18:27 -07:00
|
|
|
static int diff_partition(State *state, Partition &part, int max_range, int upper_bound, State **candidate)
|
2014-01-09 16:55:55 -08:00
|
|
|
{
|
|
|
|
int weight = 0;
|
|
|
|
*candidate = NULL;
|
|
|
|
|
|
|
|
for (Partition::iterator i = part.begin(); i != part.end(); i++) {
|
|
|
|
if (*i == state)
|
|
|
|
continue;
|
|
|
|
|
2019-08-11 06:18:27 -07:00
|
|
|
int tmp = state->diff_weight(*i, max_range, upper_bound);
|
2014-01-09 16:55:55 -08:00
|
|
|
if (tmp > weight) {
|
|
|
|
weight = tmp;
|
|
|
|
*candidate = *i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return weight;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* diff_encode - compress dfa by differentially encoding state transitions
|
2023-07-06 16:41:56 -07:00
|
|
|
* @opts: flags controlling dfa creation
|
2014-01-09 16:55:55 -08:00
|
|
|
*
|
|
|
|
* This function reduces the number of transitions that need to be stored
|
|
|
|
* by encoding transitions as the difference between the state and a
|
|
|
|
* another transitions that is set as the states default.
|
|
|
|
*
|
|
|
|
* For performance reasons this function does not try to compute the
|
|
|
|
* absolute best encoding (maximal spanning tree) but instead computes
|
|
|
|
* a very good encoding within the following limitations.
|
|
|
|
* - Not all states have to be differentially encoded. This allows for
|
|
|
|
* multiple states to be used as a terminating basis.
|
|
|
|
* - The number of state transitions needed to match an input of length
|
|
|
|
* m will be 2m
|
|
|
|
*
|
2020-11-19 12:30:04 -08:00
|
|
|
* To guarantee this the ordering and distance calculation is done in the
|
2014-01-09 16:55:55 -08:00
|
|
|
* following manner.
|
|
|
|
* - A DAG of the DFA is created starting with the start state(s).
|
|
|
|
* - A state can only be relative (have a differential encoding) to
|
|
|
|
* another state if that state has
|
|
|
|
* - a lower depth in the DAG
|
|
|
|
* - is a sibling (same depth) that is not relative
|
|
|
|
* - is a sibling that is relative to a state with lower depth in the DAG
|
|
|
|
*
|
|
|
|
* The run time constraints are maintained by the DAG ordering + relative
|
|
|
|
* state constraints. For any input character C when at state S with S being
|
|
|
|
* at level N in the DAG then at most 2N states must be traversed to find the
|
|
|
|
* transition for C. However on the maximal number of transitions is not m*m,
|
|
|
|
* because when a character is matched and forward movement is made through
|
|
|
|
* the DFA any relative transition search will move back through the DAG order.
|
|
|
|
* So say for character C we start matching on a state S that is at depth 10
|
|
|
|
* in the DAG. The transition for C is not found in S and we recurse backwards
|
|
|
|
* to a depth of 6. A transition is found and it steps to the next state, but
|
|
|
|
* the state transition at most will only move 1 deeper into the DAG so for
|
|
|
|
* the next state the maximum number of states traversed is 2*7.
|
|
|
|
*/
|
2023-07-06 16:41:56 -07:00
|
|
|
void DFA::diff_encode(optflags const &opts)
|
2014-01-09 16:55:55 -08:00
|
|
|
{
|
|
|
|
DiffDag *dag;
|
|
|
|
unsigned int xcount = 0, xweight = 0, transitions = 0, depth = 0;
|
|
|
|
|
|
|
|
/* clear the depth flag */
|
|
|
|
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
|
|
|
|
(*i)->diff = NULL;
|
|
|
|
transitions += (*i)->trans.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Prealloc structures we need. We know the exact number of elements,
|
|
|
|
* and once setup they don't change so we don't need the flexibility
|
|
|
|
* or overhead of stl, just allocate the needed data as an array
|
|
|
|
*/
|
|
|
|
dag = new DiffDag [states.size()];
|
|
|
|
|
|
|
|
/* Generate DAG ordering and parent sets */
|
|
|
|
add_to_dag(&dag[0], nonmatching, NULL);
|
|
|
|
add_to_dag(&dag[1], start, NULL);
|
|
|
|
|
|
|
|
unsigned int tail = 2;
|
|
|
|
for (unsigned int i = 1; i < tail; i++) {
|
|
|
|
State *state = dag[i].state;
|
|
|
|
State *child = dag[i].state->otherwise;
|
|
|
|
if (child)
|
|
|
|
tail += add_to_dag(&dag[tail], child, state);
|
|
|
|
|
|
|
|
for (StateTrans::iterator j = state->trans.begin(); j != state->trans.end(); j++) {
|
|
|
|
child = j->second;
|
|
|
|
tail += add_to_dag(&dag[tail], child, state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
depth = dag[tail - 1].depth;
|
|
|
|
|
|
|
|
/* calculate which state to make a transitions relative too */
|
|
|
|
for (unsigned int i = 2; i < tail; i++) {
|
|
|
|
State *state = dag[i].state;
|
|
|
|
State *candidate = NULL;
|
|
|
|
|
|
|
|
int weight = diff_partition(state,
|
2019-08-11 06:18:27 -07:00
|
|
|
state->otherwise->diff->parents, max_range,
|
|
|
|
upper_bound, &candidate);
|
2014-01-09 16:55:55 -08:00
|
|
|
|
|
|
|
for (StateTrans::iterator j = state->trans.begin(); j != state->trans.end(); j++) {
|
|
|
|
State *tmp_candidate;
|
|
|
|
int tmp = diff_partition(state,
|
2019-08-11 06:18:27 -07:00
|
|
|
j->second->diff->parents, max_range,
|
|
|
|
upper_bound, &tmp_candidate);
|
2014-01-09 16:55:55 -08:00
|
|
|
if (tmp > weight) {
|
|
|
|
weight = tmp;
|
|
|
|
candidate = tmp_candidate;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-08 19:49:34 -07:00
|
|
|
if ((opts.dump & DUMP_DFA_DIFF_PROGRESS) && (i % 100 == 0))
|
2014-01-09 16:55:55 -08:00
|
|
|
cerr << "\033[2KDiff Encode: " << i << " of "
|
|
|
|
<< tail << ". Diff states " << xcount
|
|
|
|
<< " Savings " << xweight << "\r";
|
|
|
|
|
|
|
|
state->diff->rel = candidate;
|
|
|
|
if (candidate) {
|
|
|
|
xcount++;
|
|
|
|
xweight += weight;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* now make transitions relative, start at the back of the list so
|
|
|
|
* as to start with the last transitions and work backwards to avoid
|
|
|
|
* having to traverse multiple previous states (that have been made
|
|
|
|
* relative already) to reconstruct previous state transition table
|
|
|
|
*/
|
|
|
|
unsigned int aweight = 0;
|
|
|
|
diffcount = 0;
|
|
|
|
for (int i = tail - 1; i > 1; i--) {
|
|
|
|
if (dag[i].rel) {
|
2019-08-11 06:18:27 -07:00
|
|
|
int weight = dag[i].state->make_relative(dag[i].rel, upper_bound);
|
2014-01-09 16:55:55 -08:00
|
|
|
aweight += weight;
|
|
|
|
diffcount++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-08 19:49:34 -07:00
|
|
|
if (opts.dump & DUMP_DFA_DIFF_STATS)
|
2014-01-09 16:55:55 -08:00
|
|
|
cerr << "Diff encode states: " << diffcount << " of "
|
|
|
|
<< tail << " reached @ depth " << depth << ". "
|
|
|
|
<< aweight << " trans removed\n";
|
|
|
|
|
|
|
|
if (xweight != aweight)
|
|
|
|
cerr << "Diff encode error: actual savings " << aweight
|
|
|
|
<< " != expected " << xweight << "\n";
|
|
|
|
|
|
|
|
if (xcount != diffcount)
|
|
|
|
cerr << "Diff encode error: actual count " << diffcount
|
|
|
|
<< " != expected " << xcount << " \n";
|
|
|
|
|
|
|
|
/* cleanup */
|
|
|
|
for (unsigned int i = 0; i < tail; i++)
|
|
|
|
dag[i].parents.clear();
|
|
|
|
delete [] dag;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* flatten_differential - remove differential state encoding
|
|
|
|
*
|
|
|
|
* Flatten the dfa back into a flat encoding.
|
|
|
|
*/
|
|
|
|
void DFA::undiff_encode(void)
|
|
|
|
{
|
|
|
|
for (Partition::iterator i = states.begin(); i != states.end(); i++)
|
2019-08-11 06:18:27 -07:00
|
|
|
(*i)->flatten_relative(nonmatching, upper_bound);
|
2014-01-09 16:55:55 -08:00
|
|
|
diffcount = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DFA::dump_diff_chain(ostream &os, map<State *, Partition> &relmap,
|
|
|
|
Partition &chain, State *state, unsigned int &count,
|
|
|
|
unsigned int &total, unsigned int &max)
|
|
|
|
{
|
|
|
|
if (relmap[state].size() == 0) {
|
|
|
|
for (Partition::iterator i = chain.begin(); i != chain.end(); i++)
|
|
|
|
os << **i << " <- ";
|
|
|
|
os << *state << "\n";
|
|
|
|
|
|
|
|
count++;
|
|
|
|
total += chain.size() + 1;
|
|
|
|
if (chain.size() + 1 > max)
|
|
|
|
max = chain.size() + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
chain.push_back(state);
|
|
|
|
for (Partition::iterator i = relmap[state].begin(); i != relmap[state].end(); i++)
|
|
|
|
dump_diff_chain(os, relmap, chain, *i, count, total, max);
|
|
|
|
chain.pop_back();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Dump the DFA diff_encoding chains */
|
|
|
|
void DFA::dump_diff_encode(ostream &os)
|
|
|
|
{
|
|
|
|
map<State *, Partition> rel;
|
|
|
|
Partition base, chain;
|
|
|
|
|
|
|
|
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
|
|
|
|
if ((*i)->flags & DiffEncodeFlag)
|
|
|
|
rel[(*i)->otherwise].push_back(*i);
|
|
|
|
else
|
|
|
|
base.push_back(*i);
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int count = 0, total = 0, max = 0;
|
|
|
|
for (Partition::iterator i = base.begin(); i != base.end(); i++)
|
|
|
|
dump_diff_chain(os, rel, chain, *i, count, total, max);
|
|
|
|
|
|
|
|
os << base.size() << " non-differentially encoded states\n";
|
|
|
|
os << "chains: " << count - base.size() << "\n";
|
|
|
|
os << "average chain size: " << (double) (total - base.size()) / (double) (count - base.size()) << "\n";
|
|
|
|
os << "longest chain: " << max << "\n";
|
|
|
|
}
|
|
|
|
|
2007-02-27 02:29:16 +00:00
|
|
|
/**
|
|
|
|
* text-dump the DFA (for debugging).
|
|
|
|
*/
|
2024-12-30 01:27:21 -08:00
|
|
|
void DFA::dump(ostream &os, Renumber_Map *renum)
|
2007-02-27 02:29:16 +00:00
|
|
|
{
|
2011-03-13 05:55:25 -07:00
|
|
|
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
|
2012-03-22 13:21:55 -07:00
|
|
|
if (*i == start || (*i)->perms.is_accept()) {
|
2024-12-30 01:27:21 -08:00
|
|
|
os << make_pair(*i, renum);
|
2023-04-23 19:03:38 -07:00
|
|
|
if (*i == start) {
|
|
|
|
os << " <== ";
|
|
|
|
(*i)->perms.dump_header(os);
|
|
|
|
}
|
2012-03-22 13:21:55 -07:00
|
|
|
if ((*i)->perms.is_accept())
|
2012-03-22 07:49:43 -07:00
|
|
|
(*i)->perms.dump(os);
|
2011-03-13 05:55:25 -07:00
|
|
|
os << "\n";
|
|
|
|
}
|
2007-02-27 02:29:16 +00:00
|
|
|
}
|
2011-03-13 05:55:25 -07:00
|
|
|
os << "\n";
|
|
|
|
|
|
|
|
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
|
2012-03-09 04:18:35 -08:00
|
|
|
Chars excluded;
|
2019-08-08 03:17:24 -07:00
|
|
|
bool first = true;
|
2012-03-09 04:18:35 -08:00
|
|
|
|
2011-12-15 04:58:33 -08:00
|
|
|
for (StateTrans::iterator j = (*i)->trans.begin();
|
|
|
|
j != (*i)->trans.end(); j++) {
|
2012-03-09 04:18:35 -08:00
|
|
|
if (j->second == nonmatching) {
|
|
|
|
excluded.insert(j->first);
|
|
|
|
} else {
|
2019-08-08 03:17:24 -07:00
|
|
|
if (first) {
|
|
|
|
first = false;
|
2024-12-30 01:27:21 -08:00
|
|
|
os << make_pair(*i, renum) << " perms: ";
|
2019-08-08 03:17:24 -07:00
|
|
|
if ((*i)->perms.is_accept())
|
|
|
|
(*i)->perms.dump(os);
|
|
|
|
else
|
|
|
|
os << "none";
|
|
|
|
os << "\n";
|
|
|
|
}
|
|
|
|
os << " "; j->first.dump(os) << " -> " <<
|
2024-12-30 01:27:21 -08:00
|
|
|
make_pair(j->second, renum);
|
2019-08-08 03:17:24 -07:00
|
|
|
if ((j)->second->perms.is_accept())
|
|
|
|
os << " ", (j->second)->perms.dump(os);
|
|
|
|
os << "\n";
|
2012-03-09 04:18:35 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((*i)->otherwise != nonmatching) {
|
2019-08-08 03:17:24 -07:00
|
|
|
if (first) {
|
|
|
|
first = false;
|
2024-12-30 01:27:21 -08:00
|
|
|
os << make_pair(*i, renum) << " perms: ";
|
2019-08-08 03:17:24 -07:00
|
|
|
if ((*i)->perms.is_accept())
|
|
|
|
(*i)->perms.dump(os);
|
|
|
|
else
|
|
|
|
os << "none";
|
|
|
|
os << "\n";
|
|
|
|
}
|
|
|
|
os << " [";
|
2012-03-09 04:18:35 -08:00
|
|
|
if (!excluded.empty()) {
|
|
|
|
os << "^";
|
|
|
|
for (Chars::iterator k = excluded.begin();
|
|
|
|
k != excluded.end(); k++) {
|
2019-08-08 03:17:24 -07:00
|
|
|
os << *k;
|
2012-03-09 04:18:35 -08:00
|
|
|
}
|
|
|
|
}
|
2024-12-30 01:27:21 -08:00
|
|
|
os << "] -> " << make_pair((*i)->otherwise, renum);
|
2019-08-08 03:17:24 -07:00
|
|
|
if ((*i)->otherwise->perms.is_accept())
|
|
|
|
os << " ", (*i)->otherwise->perms.dump(os);
|
|
|
|
os << "\n";
|
2011-03-13 05:55:25 -07:00
|
|
|
}
|
2007-02-27 02:29:16 +00:00
|
|
|
}
|
2011-03-13 05:55:25 -07:00
|
|
|
os << "\n";
|
2007-02-27 02:29:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a dot (graphviz) graph from the DFA (for debugging).
|
|
|
|
*/
|
2011-03-13 05:55:25 -07:00
|
|
|
void DFA::dump_dot_graph(ostream & os)
|
2007-02-27 02:29:16 +00:00
|
|
|
{
|
2011-03-13 05:55:25 -07:00
|
|
|
os << "digraph \"dfa\" {" << "\n";
|
2007-02-27 02:29:16 +00:00
|
|
|
|
2011-03-13 05:55:25 -07:00
|
|
|
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
|
|
|
|
if (*i == nonmatching)
|
|
|
|
continue;
|
2007-02-27 02:29:16 +00:00
|
|
|
|
2011-03-13 05:55:25 -07:00
|
|
|
os << "\t\"" << **i << "\" [" << "\n";
|
|
|
|
if (*i == start) {
|
|
|
|
os << "\t\tstyle=bold" << "\n";
|
|
|
|
}
|
2012-03-22 13:21:55 -07:00
|
|
|
if ((*i)->perms.is_accept()) {
|
2012-03-22 07:49:43 -07:00
|
|
|
os << "\t\tlabel=\"" << **i << "\\n";
|
|
|
|
(*i)->perms.dump(os);
|
|
|
|
os << "\"\n";
|
2011-03-13 05:55:25 -07:00
|
|
|
}
|
|
|
|
os << "\t]" << "\n";
|
2007-02-27 02:29:16 +00:00
|
|
|
}
|
2011-03-13 05:55:25 -07:00
|
|
|
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
|
|
|
|
Chars excluded;
|
|
|
|
|
2011-12-15 04:58:33 -08:00
|
|
|
for (StateTrans::iterator j = (*i)->trans.begin(); j != (*i)->trans.end(); j++) {
|
2011-03-13 05:55:25 -07:00
|
|
|
if (j->second == nonmatching)
|
|
|
|
excluded.insert(j->first);
|
|
|
|
else {
|
2011-05-20 09:24:40 -07:00
|
|
|
os << "\t\"" << **i << "\" -> \"" << *j->second
|
2011-03-13 05:55:25 -07:00
|
|
|
<< "\" [" << "\n";
|
2012-03-09 04:18:35 -08:00
|
|
|
os << "\t\tlabel=\"";
|
2019-08-08 03:17:24 -07:00
|
|
|
j->first.dump(os);
|
2012-03-09 04:18:35 -08:00
|
|
|
os << "\"\n\t]" << "\n";
|
2011-03-13 05:55:25 -07:00
|
|
|
}
|
|
|
|
}
|
2011-12-15 05:01:35 -08:00
|
|
|
if ((*i)->otherwise != nonmatching) {
|
2012-03-09 04:18:35 -08:00
|
|
|
os << "\t\"" << **i << "\" -> \"" << *(*i)->otherwise
|
2011-03-13 05:55:25 -07:00
|
|
|
<< "\" [" << "\n";
|
|
|
|
if (!excluded.empty()) {
|
|
|
|
os << "\t\tlabel=\"[^";
|
|
|
|
for (Chars::iterator i = excluded.begin();
|
|
|
|
i != excluded.end(); i++) {
|
2019-08-08 03:17:24 -07:00
|
|
|
i->dump(os);
|
2011-03-13 05:55:25 -07:00
|
|
|
}
|
|
|
|
os << "]\"" << "\n";
|
|
|
|
}
|
|
|
|
os << "\t]" << "\n";
|
2007-02-27 02:29:16 +00:00
|
|
|
}
|
|
|
|
}
|
2011-03-13 05:55:25 -07:00
|
|
|
os << '}' << "\n";
|
2007-02-27 02:29:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compute character equivalence classes in the DFA to save space in the
|
|
|
|
* transition table.
|
|
|
|
*/
|
2023-07-06 16:41:56 -07:00
|
|
|
map<transchar, transchar> DFA::equivalence_classes(optflags const &opts)
|
2007-02-27 02:29:16 +00:00
|
|
|
{
|
2019-08-08 00:41:57 -07:00
|
|
|
map<transchar, transchar> classes;
|
|
|
|
transchar next_class = 1;
|
2011-03-13 05:55:25 -07:00
|
|
|
|
|
|
|
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
|
|
|
|
/* Group edges to the same next state together */
|
|
|
|
map<const State *, Chars> node_sets;
|
2019-08-11 06:18:27 -07:00
|
|
|
for (StateTrans::iterator j = (*i)->trans.begin(); j != (*i)->trans.end(); j++) {
|
|
|
|
if (j->first.c < 0)
|
|
|
|
continue;
|
2011-03-13 05:55:25 -07:00
|
|
|
node_sets[j->second].insert(j->first);
|
2019-08-11 06:18:27 -07:00
|
|
|
}
|
2011-03-13 05:55:25 -07:00
|
|
|
for (map<const State *, Chars>::iterator j = node_sets.begin();
|
|
|
|
j != node_sets.end(); j++) {
|
|
|
|
/* Group edges to the same next state together by class */
|
2019-08-08 00:41:57 -07:00
|
|
|
map<transchar, Chars> node_classes;
|
2011-03-13 05:55:25 -07:00
|
|
|
bool class_used = false;
|
|
|
|
for (Chars::iterator k = j->second.begin();
|
|
|
|
k != j->second.end(); k++) {
|
2019-08-08 00:41:57 -07:00
|
|
|
pair<map<transchar, transchar>::iterator, bool> x = classes.insert(make_pair(*k, next_class));
|
2011-03-13 05:55:25 -07:00
|
|
|
if (x.second)
|
|
|
|
class_used = true;
|
2019-08-08 00:41:57 -07:00
|
|
|
pair<map<transchar, Chars>::iterator, bool> y = node_classes.insert(make_pair(x.first->second, Chars()));
|
2011-03-13 05:55:25 -07:00
|
|
|
y.first->second.insert(*k);
|
|
|
|
}
|
|
|
|
if (class_used) {
|
|
|
|
next_class++;
|
|
|
|
class_used = false;
|
|
|
|
}
|
2019-08-08 00:41:57 -07:00
|
|
|
for (map<transchar, Chars>::iterator k = node_classes.begin();
|
2011-03-13 05:55:25 -07:00
|
|
|
k != node_classes.end(); k++) {
|
|
|
|
/**
|
|
|
|
* If any other characters are in the same class, move
|
|
|
|
* the characters in this class into their own new
|
|
|
|
* class
|
|
|
|
*/
|
2019-08-08 00:41:57 -07:00
|
|
|
map<transchar, transchar>::iterator l;
|
2011-03-13 05:55:25 -07:00
|
|
|
for (l = classes.begin(); l != classes.end(); l++) {
|
|
|
|
if (l->second == k->first &&
|
|
|
|
k->second.find(l->first) == k->second.end()) {
|
|
|
|
class_used = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (class_used) {
|
|
|
|
for (Chars::iterator l = k->second.begin();
|
|
|
|
l != k->second.end(); l++) {
|
|
|
|
classes[*l] = next_class;
|
|
|
|
}
|
|
|
|
next_class++;
|
|
|
|
class_used = false;
|
|
|
|
}
|
|
|
|
}
|
2007-02-27 02:29:16 +00:00
|
|
|
}
|
|
|
|
}
|
2010-01-08 02:17:45 -08:00
|
|
|
|
2023-07-08 19:49:34 -07:00
|
|
|
if (opts.dump & DUMP_DFA_EQUIV_STATS)
|
2011-03-13 05:55:25 -07:00
|
|
|
fprintf(stderr, "Equiv class reduces to %d classes\n",
|
2019-08-08 00:05:33 -07:00
|
|
|
next_class.c - 1);
|
2011-03-13 05:55:25 -07:00
|
|
|
return classes;
|
2007-02-27 02:29:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Text-dump the equivalence classes (for debugging).
|
|
|
|
*/
|
2019-08-08 00:41:57 -07:00
|
|
|
void dump_equivalence_classes(ostream &os, map<transchar, transchar> &eq)
|
2007-02-27 02:29:16 +00:00
|
|
|
{
|
2019-08-08 00:41:57 -07:00
|
|
|
map<transchar, Chars> rev;
|
2011-03-13 05:55:25 -07:00
|
|
|
|
2019-08-08 00:41:57 -07:00
|
|
|
for (map<transchar, transchar>::iterator i = eq.begin(); i != eq.end(); i++) {
|
2011-03-13 05:55:25 -07:00
|
|
|
Chars &chars = rev.insert(make_pair(i->second, Chars())).first->second;
|
|
|
|
chars.insert(i->first);
|
|
|
|
}
|
|
|
|
os << "(eq):" << "\n";
|
2019-08-08 00:41:57 -07:00
|
|
|
for (map<transchar, Chars>::iterator i = rev.begin(); i != rev.end(); i++) {
|
2019-08-08 00:05:33 -07:00
|
|
|
os << i->first.c << ':';
|
2011-03-13 05:55:25 -07:00
|
|
|
Chars &chars = i->second;
|
|
|
|
for (Chars::iterator j = chars.begin(); j != chars.end(); j++) {
|
|
|
|
os << ' ' << *j;
|
|
|
|
}
|
|
|
|
os << "\n";
|
2007-02-27 02:29:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Replace characters with classes (which are also represented as
|
|
|
|
* characters) in the DFA transition table.
|
|
|
|
*/
|
2019-08-08 00:41:57 -07:00
|
|
|
void DFA::apply_equivalence_classes(map<transchar, transchar> &eq)
|
2007-02-27 02:29:16 +00:00
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Note: We only transform the transition table; the nodes continue to
|
|
|
|
* contain the original characters.
|
|
|
|
*/
|
2011-03-13 05:55:25 -07:00
|
|
|
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
|
2019-08-08 00:41:57 -07:00
|
|
|
map<transchar, State *> tmp;
|
2011-12-15 04:58:33 -08:00
|
|
|
tmp.swap((*i)->trans);
|
2019-08-11 06:18:27 -07:00
|
|
|
for (StateTrans::iterator j = tmp.begin(); j != tmp.end(); j++) {
|
|
|
|
if (j->first.c < 0)
|
|
|
|
continue;
|
2011-12-15 04:58:33 -08:00
|
|
|
(*i)->trans.insert(make_pair(eq[j->first], j->second));
|
2019-08-11 06:18:27 -07:00
|
|
|
}
|
2011-03-13 05:55:25 -07:00
|
|
|
}
|
2007-02-27 02:29:16 +00:00
|
|
|
}
|
|
|
|
|
2020-06-18 05:49:20 -07:00
|
|
|
void DFA::compute_perms_table_ent(State *state, size_t pos,
|
2023-04-23 20:27:51 -07:00
|
|
|
vector <aa_perms> &perms_table,
|
|
|
|
bool prompt)
|
2020-06-18 05:49:20 -07:00
|
|
|
{
|
2023-04-23 19:03:38 -07:00
|
|
|
uint32_t accept1, accept2, accept3;
|
2020-06-18 05:49:20 -07:00
|
|
|
|
2023-04-23 19:03:38 -07:00
|
|
|
// until front end doesn't map the way it does
|
2023-04-23 20:27:51 -07:00
|
|
|
state->map_perms_to_accept(accept1, accept2, accept3, prompt);
|
2020-06-18 05:49:20 -07:00
|
|
|
if (filedfa) {
|
|
|
|
state->idx = pos * 2;
|
2023-04-23 19:03:38 -07:00
|
|
|
perms_table[pos*2] = compute_fperms_user(accept1, accept2, accept3);
|
|
|
|
perms_table[pos*2 + 1] = compute_fperms_other(accept1, accept2, accept3);
|
2020-06-18 05:49:20 -07:00
|
|
|
} else {
|
|
|
|
state->idx = pos;
|
2023-04-23 19:03:38 -07:00
|
|
|
perms_table[pos] = compute_perms_entry(accept1, accept2, accept3);
|
2020-06-18 05:49:20 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-23 20:27:51 -07:00
|
|
|
void DFA::compute_perms_table(vector <aa_perms> &perms_table, bool prompt)
|
2020-06-18 05:49:20 -07:00
|
|
|
{
|
|
|
|
size_t mult = filedfa ? 2 : 1;
|
|
|
|
size_t pos = 2;
|
|
|
|
|
|
|
|
assert(states.size() >= 2);
|
|
|
|
perms_table.resize(states.size() * mult);
|
|
|
|
|
|
|
|
// nonmatching and start need to be 0 and 1 so handle outside of loop
|
2024-10-28 09:22:02 -06:00
|
|
|
compute_perms_table_ent(nonmatching, 0, perms_table, prompt);
|
2023-04-23 20:27:51 -07:00
|
|
|
compute_perms_table_ent(start, 1, perms_table, prompt);
|
2020-06-18 05:49:20 -07:00
|
|
|
|
|
|
|
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
|
|
|
|
if (*i == nonmatching || *i == start)
|
|
|
|
continue;
|
2023-04-23 20:27:51 -07:00
|
|
|
compute_perms_table_ent(*i, pos, perms_table, prompt);
|
2020-06-18 05:49:20 -07:00
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-03-30 15:20:57 +00:00
|
|
|
#if 0
|
2011-03-13 05:55:25 -07:00
|
|
|
typedef set <ImportantNode *>AcceptNodes;
|
|
|
|
map<ImportantNode *, AcceptNodes> dominance(DFA & dfa)
|
2007-02-27 02:29:16 +00:00
|
|
|
{
|
2011-03-13 05:55:25 -07:00
|
|
|
map<ImportantNode *, AcceptNodes> is_dominated;
|
2007-02-27 02:29:16 +00:00
|
|
|
|
2011-03-13 05:55:25 -07:00
|
|
|
for (States::iterator i = dfa.states.begin(); i != dfa.states.end(); i++) {
|
|
|
|
AcceptNodes set1;
|
|
|
|
for (State::iterator j = (*i)->begin(); j != (*i)->end(); j++) {
|
|
|
|
if (AcceptNode * accept = dynamic_cast<AcceptNode *>(*j))
|
|
|
|
set1.insert(accept);
|
|
|
|
}
|
|
|
|
for (AcceptNodes::iterator j = set1.begin(); j != set1.end(); j++) {
|
|
|
|
pair<map<ImportantNode *, AcceptNodes>::iterator, bool> x = is_dominated.insert(make_pair(*j, set1));
|
|
|
|
if (!x.second) {
|
|
|
|
AcceptNodes & set2(x.first->second), set3;
|
|
|
|
for (AcceptNodes::iterator l = set2.begin();
|
|
|
|
l != set2.end(); l++) {
|
|
|
|
if (set1.find(*l) != set1.end())
|
|
|
|
set3.insert(*l);
|
|
|
|
}
|
|
|
|
set3.swap(set2);
|
|
|
|
}
|
2007-02-27 02:29:16 +00:00
|
|
|
}
|
|
|
|
}
|
2011-03-13 05:55:25 -07:00
|
|
|
return is_dominated;
|
2007-02-27 02:29:16 +00:00
|
|
|
}
|
2007-03-30 15:20:57 +00:00
|
|
|
#endif
|
2007-02-27 02:29:16 +00:00
|
|
|
|
2023-08-04 01:49:10 -07:00
|
|
|
static inline int diff_qualifiers(perm32_t perm1, perm32_t perm2)
|
2007-11-16 09:27:34 +00:00
|
|
|
{
|
2008-04-16 04:44:21 +00:00
|
|
|
return ((perm1 & AA_EXEC_TYPE) && (perm2 & AA_EXEC_TYPE) &&
|
|
|
|
(perm1 & AA_EXEC_TYPE) != (perm2 & AA_EXEC_TYPE));
|
2007-11-16 09:27:34 +00:00
|
|
|
}
|
2007-03-30 20:38:51 +00:00
|
|
|
|
2024-12-24 01:27:10 -08:00
|
|
|
/* update a single permission based on priority - only called if match->perm | match-> audit bit set */
|
|
|
|
static int pri_update_perm(optflags const &opts, vector<int> &priority, int i,
|
|
|
|
MatchFlag *match, perms_t &perms, perms_t &exact,
|
|
|
|
bool filedfa)
|
|
|
|
{
|
|
|
|
if (priority[i] > match->priority) {
|
|
|
|
if (opts.dump & DUMP_DFA_PERMS)
|
|
|
|
cerr << " " << match << "[" << i << "]=" << priority[i] << " > " << match->priority << " SKIPPING " << hex << (match->perms) << "/" << (match->audit) << dec << "\n";
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
perm32_t xmask = 0;
|
|
|
|
perm32_t mask = 1 << i;
|
|
|
|
perm32_t amask = mask;
|
|
|
|
|
|
|
|
// drop once we move the xindex out of the perms in the front end
|
|
|
|
if (filedfa) {
|
|
|
|
if (mask & AA_USER_EXEC) {
|
|
|
|
xmask = AA_USER_EXEC_TYPE;
|
|
|
|
// ix implies EXEC_MMAP
|
|
|
|
if (match->perms & AA_EXEC_INHERIT) {
|
|
|
|
xmask |= AA_USER_EXEC_MMAP;
|
|
|
|
//USER_EXEC_MAP = 6
|
|
|
|
if (priority[6] < match->priority)
|
|
|
|
priority[6] = match->priority;
|
|
|
|
}
|
|
|
|
amask = mask | xmask;
|
|
|
|
} else if (mask & AA_OTHER_EXEC) {
|
|
|
|
xmask = AA_OTHER_EXEC_TYPE;
|
|
|
|
// ix implies EXEC_MMAP
|
|
|
|
if (match->perms & AA_OTHER_EXEC_INHERIT) {
|
|
|
|
xmask |= AA_OTHER_EXEC_MMAP;
|
|
|
|
//OTHER_EXEC_MAP = 20
|
|
|
|
if (priority[20] < match->priority)
|
|
|
|
priority[20] = match->priority;
|
|
|
|
}
|
|
|
|
amask = mask | xmask;
|
|
|
|
} else if (((mask & AA_USER_EXEC_MMAP) &&
|
|
|
|
(match->perms & AA_USER_EXEC_INHERIT)) ||
|
|
|
|
((mask & AA_OTHER_EXEC_MMAP) &&
|
|
|
|
(match->perms & AA_OTHER_EXEC_INHERIT))) {
|
|
|
|
// if exec && ix we handled mmp above
|
|
|
|
if (opts.dump & DUMP_DFA_PERMS)
|
|
|
|
cerr << " " << match << "[" << i << "]=" << priority[i] << " <= " << match->priority << " SKIPPING mmap unmasked " << hex << match->perms << "/" << match->audit << " masked " << (match->perms & amask) << "/" << (match->audit & amask) << " data " << (perms.allow & mask) << "/" << (perms.audit & mask) << " exact " << (exact.allow & mask) << "/" << (exact.audit & mask) << dec << "\n";
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opts.dump & DUMP_DFA_PERMS)
|
|
|
|
cerr << " " << match << "[" << i << "]=" << priority[i] << " vs. " << match->priority << " mask: " << hex << mask << " xmask: " << xmask << " amask: " << amask << dec << "\n";
|
|
|
|
if (priority[i] < match->priority) {
|
|
|
|
if (opts.dump & DUMP_DFA_PERMS)
|
|
|
|
cerr << " " << match << "[" << i << "]=" << priority[i] << " < " << match->priority << " clearing " << hex << (perms.allow & amask) << "/" << (perms.audit & amask) << " -> " << dec;
|
|
|
|
priority[i] = match->priority;
|
|
|
|
perms.clear_bits(amask);
|
|
|
|
exact.clear_bits(amask);
|
|
|
|
if (opts.dump & DUMP_DFA_PERMS)
|
|
|
|
cerr << hex << (perms.allow & amask) << "/" << (perms.audit & amask) << dec << "\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
// the if conditions in order of permission priority
|
|
|
|
if (match->is_type(NODE_TYPE_DENYMATCHFLAG)) {
|
|
|
|
if (opts.dump & DUMP_DFA_PERMS)
|
|
|
|
cerr << " " << match << "[" << i << "]=" << priority[i] << " <= " << match->priority << " deny " << hex << (match->perms & amask) << "/" << (match->audit & amask) << dec << "\n";
|
|
|
|
perms.deny |= match->perms & amask;
|
|
|
|
perms.quiet |= match->audit & amask;
|
|
|
|
|
|
|
|
perms.allow &= ~amask;
|
|
|
|
perms.audit &= ~amask;
|
|
|
|
perms.prompt &= ~amask;
|
|
|
|
} else if (match->is_type(NODE_TYPE_EXACTMATCHFLAG)) {
|
|
|
|
/* exact match only asserts dominance on the XTYPE */
|
|
|
|
if (opts.dump & DUMP_DFA_PERMS)
|
|
|
|
cerr << " " << match << "[" << i << "]=" << priority[i] << " <= " << match->priority << " exact " << hex << (match->perms & amask) << "/" << (match->audit & amask) << dec << "\n";
|
|
|
|
if (filedfa &&
|
|
|
|
!is_merged_x_consistent(exact.allow, match->perms & amask)) {
|
|
|
|
if (opts.dump & DUMP_DFA_PERMS)
|
|
|
|
cerr << " " << match << "[" << i << "]=" << priority[i] << " <= " << match->priority << " exact match conflict" << "\n";
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
exact.allow |= match->perms & amask;
|
|
|
|
exact.audit |= match->audit & amask;
|
|
|
|
|
|
|
|
// dominance is only done for XTYPE so only clear that
|
|
|
|
// note xmask only set if setting x perm bit, so this
|
|
|
|
// won't clear for other bit types
|
|
|
|
perms.allow &= ~xmask;
|
|
|
|
perms.audit &= ~xmask;
|
|
|
|
perms.prompt &= ~xmask;
|
|
|
|
|
|
|
|
perms.allow |= match->perms & amask;
|
|
|
|
perms.audit |= match->audit & amask;
|
|
|
|
// can't specify exact prompt atm
|
|
|
|
|
|
|
|
} else if (!match->is_type(NODE_TYPE_PROMPTMATCHFLAG)) {
|
|
|
|
// allow perms, if exact has been encountered will already be set
|
|
|
|
// if overlaps x here, don't conflict, because exact will override
|
|
|
|
if (opts.dump & DUMP_DFA_PERMS)
|
|
|
|
cerr << " " << match << "[" << i << "]=" << priority[i] << " <= " << match->priority << " allow " << hex << (match->perms & amask) << "/" << (match->audit & amask) << dec << "\n";
|
|
|
|
if (filedfa && !(exact.allow & mask) &&
|
|
|
|
!is_merged_x_consistent(perms.allow, match->perms & amask)) {
|
|
|
|
if (opts.dump & DUMP_DFA_PERMS)
|
|
|
|
cerr << " " << match << "[" << i << "]=" << priority[i] << " <= " << match->priority << " allow match conflict" << "\n";
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
// mask off if XTYPE in xmatch
|
|
|
|
if ((exact.allow | exact.audit) & mask) {
|
|
|
|
// mask == amask & ~xmask
|
|
|
|
perms.allow |= match->perms & mask;
|
|
|
|
perms.audit |= match->audit & mask;
|
|
|
|
} else {
|
|
|
|
perms.allow |= match->perms & amask;
|
|
|
|
perms.audit |= match->audit & amask;
|
|
|
|
}
|
|
|
|
} else { // if (match->is_type(NODE_TYPE_PROMPTMATCHFLAG)) {
|
|
|
|
if (opts.dump & DUMP_DFA_PERMS)
|
2025-02-18 11:53:35 -08:00
|
|
|
cerr << " " << match << "[" << i << "]=" << priority[i] << " <= " << match->priority << " prompt " << hex << (match->perms & amask) << "/" << (match->audit & amask) << dec << "\n";
|
2024-12-24 01:27:10 -08:00
|
|
|
if (filedfa && !((exact.allow | perms.allow) & mask) &&
|
|
|
|
!is_merged_x_consistent(perms.allow, match->perms & amask)) {
|
|
|
|
if (opts.dump & DUMP_DFA_PERMS)
|
|
|
|
cerr << " " << match << "[" << i << "]=" << priority[i] << " <= " << match->priority << " prompt match conflict" << "\n";
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if ((exact.allow | exact.audit | perms.allow | perms.audit) & mask) {
|
|
|
|
// mask == amask & ~xmask
|
|
|
|
perms.prompt |= match->perms & mask;
|
|
|
|
perms.audit |= match->audit & mask;
|
|
|
|
} else {
|
|
|
|
perms.prompt |= match->perms & amask;
|
|
|
|
perms.audit |= match->audit & amask;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-03-30 20:38:51 +00:00
|
|
|
/**
|
|
|
|
* Compute the permission flags that this state corresponds to. If we
|
|
|
|
* have any exact matches, then they override the execute and safe
|
|
|
|
* execute flags.
|
|
|
|
*/
|
2024-12-24 01:27:10 -08:00
|
|
|
int accept_perms(optflags const &opts, NodeVec *state, perms_t &perms,
|
|
|
|
bool filedfa)
|
2007-03-30 20:38:51 +00:00
|
|
|
{
|
2012-02-16 07:40:21 -08:00
|
|
|
int error = 0;
|
2024-05-10 03:06:22 -07:00
|
|
|
perms_t exact;
|
2024-12-24 01:27:10 -08:00
|
|
|
std::vector<int> priority(sizeof(perm32_t)*8, MIN_INTERNAL_PRIORITY); // 32 but wan't tied to perm32_t
|
2012-02-16 07:40:21 -08:00
|
|
|
perms.clear();
|
|
|
|
|
|
|
|
if (!state)
|
|
|
|
return error;
|
2024-12-24 01:27:10 -08:00
|
|
|
if (opts.dump & DUMP_DFA_PERMS)
|
|
|
|
cerr << "Building\n";
|
2021-01-30 02:32:38 -08:00
|
|
|
for (NodeVec::iterator i = state->begin(); i != state->end(); i++) {
|
parser: replace dynamic_cast with is_type method
The dynamic_cast operator is slow as it needs to look at RTTI
information and even does some string comparisons, especially in deep
hierarchies like the one for Node. Profiling with callgrind showed
that dynamic_cast can eat a huge portion of the running time, as it
takes most of the time that is spent in the simplify_tree()
function. For some complex profiles, the number of calls to
dynamic_cast can be in the range of millions.
This commit replaces the use of dynamic_cast in the Node hierarchy
with a method called is_type(), which returns true if the pointer can
be casted to the specified type. It works by looking at a Node object
field that is an integer with bits set for each type up in the
hierarchy. Therefore, dynamic_cast is replaced by a simple bits
operation.
This change can reduce the compilation times for some profiles more
that 50%, especially in arm/arm64 arch. This opens the door to maybe
avoid "-O no-expr-simplify" in the snapd daemon, as now that option
would make the compilation slower in almost all cases.
This is the example profile used in some of my tests, with this change
the run-time is around 1/3 of what it was before on an x86 laptop:
profile "test" (attach_disconnected,mediate_deleted) {
dbus send
bus={fcitx,session}
path=/inputcontext_[0-9]*
interface=org.fcitx.Fcitx.InputContext
member="{Close,Destroy,Enable}IC"
peer=(label=unconfined),
dbus send
bus={fcitx,session}
path=/inputcontext_[0-9]*
interface=org.fcitx.Fcitx.InputContext
member=Reset
peer=(label=unconfined),
dbus receive
bus=fcitx
peer=(label=unconfined),
dbus receive
bus=session
interface=org.fcitx.Fcitx.*
peer=(label=unconfined),
dbus send
bus={fcitx,session}
path=/inputcontext_[0-9]*
interface=org.fcitx.Fcitx.InputContext
member="Focus{In,Out}"
peer=(label=unconfined),
dbus send
bus={fcitx,session}
path=/inputcontext_[0-9]*
interface=org.fcitx.Fcitx.InputContext
member="{CommitPreedit,Set*}"
peer=(label=unconfined),
dbus send
bus={fcitx,session}
path=/inputcontext_[0-9]*
interface=org.fcitx.Fcitx.InputContext
member="{MouseEvent,ProcessKeyEvent}"
peer=(label=unconfined),
dbus send
bus={fcitx,session}
path=/inputcontext_[0-9]*
interface=org.freedesktop.DBus.Properties
member=GetAll
peer=(label=unconfined),
dbus (send)
bus=session
path=/org/a11y/bus
interface=org.a11y.Bus
member=GetAddress
peer=(label=unconfined),
dbus (send)
bus=session
path=/org/a11y/bus
interface=org.freedesktop.DBus.Properties
member=Get{,All}
peer=(label=unconfined),
dbus (receive, send)
bus=accessibility
path=/org/a11y/atspi/**
peer=(label=unconfined),
dbus (send)
bus=system
path=/org/freedesktop/Accounts
interface=org.freedesktop.DBus.Introspectable
member=Introspect
peer=(label=unconfined),
dbus (send)
bus=system
path=/org/freedesktop/Accounts
interface=org.freedesktop.Accounts
member=FindUserById
peer=(label=unconfined),
dbus (receive, send)
bus=system
path=/org/freedesktop/Accounts/User[0-9]*
interface=org.freedesktop.DBus.Properties
member={Get,PropertiesChanged}
peer=(label=unconfined),
dbus (send)
bus=session
interface=org.gtk.Actions
member=Changed
peer=(name=org.freedesktop.DBus, label=unconfined),
dbus (receive)
bus=session
interface=org.gtk.Actions
member={Activate,DescribeAll,SetState}
peer=(label=unconfined),
dbus (receive)
bus=session
interface=org.gtk.Menus
member={Start,End}
peer=(label=unconfined),
dbus (send)
bus=session
interface=org.gtk.Menus
member=Changed
peer=(name=org.freedesktop.DBus, label=unconfined),
dbus (send)
bus=session
path="/com/ubuntu/MenuRegistrar"
interface="com.ubuntu.MenuRegistrar"
member="{Register,Unregister}{App,Surface}Menu"
peer=(label=unconfined),
}
2021-02-15 16:26:18 +01:00
|
|
|
if (!(*i)->is_type(NODE_TYPE_MATCHFLAG))
|
2011-03-13 05:55:25 -07:00
|
|
|
continue;
|
parser: replace dynamic_cast with is_type method
The dynamic_cast operator is slow as it needs to look at RTTI
information and even does some string comparisons, especially in deep
hierarchies like the one for Node. Profiling with callgrind showed
that dynamic_cast can eat a huge portion of the running time, as it
takes most of the time that is spent in the simplify_tree()
function. For some complex profiles, the number of calls to
dynamic_cast can be in the range of millions.
This commit replaces the use of dynamic_cast in the Node hierarchy
with a method called is_type(), which returns true if the pointer can
be casted to the specified type. It works by looking at a Node object
field that is an integer with bits set for each type up in the
hierarchy. Therefore, dynamic_cast is replaced by a simple bits
operation.
This change can reduce the compilation times for some profiles more
that 50%, especially in arm/arm64 arch. This opens the door to maybe
avoid "-O no-expr-simplify" in the snapd daemon, as now that option
would make the compilation slower in almost all cases.
This is the example profile used in some of my tests, with this change
the run-time is around 1/3 of what it was before on an x86 laptop:
profile "test" (attach_disconnected,mediate_deleted) {
dbus send
bus={fcitx,session}
path=/inputcontext_[0-9]*
interface=org.fcitx.Fcitx.InputContext
member="{Close,Destroy,Enable}IC"
peer=(label=unconfined),
dbus send
bus={fcitx,session}
path=/inputcontext_[0-9]*
interface=org.fcitx.Fcitx.InputContext
member=Reset
peer=(label=unconfined),
dbus receive
bus=fcitx
peer=(label=unconfined),
dbus receive
bus=session
interface=org.fcitx.Fcitx.*
peer=(label=unconfined),
dbus send
bus={fcitx,session}
path=/inputcontext_[0-9]*
interface=org.fcitx.Fcitx.InputContext
member="Focus{In,Out}"
peer=(label=unconfined),
dbus send
bus={fcitx,session}
path=/inputcontext_[0-9]*
interface=org.fcitx.Fcitx.InputContext
member="{CommitPreedit,Set*}"
peer=(label=unconfined),
dbus send
bus={fcitx,session}
path=/inputcontext_[0-9]*
interface=org.fcitx.Fcitx.InputContext
member="{MouseEvent,ProcessKeyEvent}"
peer=(label=unconfined),
dbus send
bus={fcitx,session}
path=/inputcontext_[0-9]*
interface=org.freedesktop.DBus.Properties
member=GetAll
peer=(label=unconfined),
dbus (send)
bus=session
path=/org/a11y/bus
interface=org.a11y.Bus
member=GetAddress
peer=(label=unconfined),
dbus (send)
bus=session
path=/org/a11y/bus
interface=org.freedesktop.DBus.Properties
member=Get{,All}
peer=(label=unconfined),
dbus (receive, send)
bus=accessibility
path=/org/a11y/atspi/**
peer=(label=unconfined),
dbus (send)
bus=system
path=/org/freedesktop/Accounts
interface=org.freedesktop.DBus.Introspectable
member=Introspect
peer=(label=unconfined),
dbus (send)
bus=system
path=/org/freedesktop/Accounts
interface=org.freedesktop.Accounts
member=FindUserById
peer=(label=unconfined),
dbus (receive, send)
bus=system
path=/org/freedesktop/Accounts/User[0-9]*
interface=org.freedesktop.DBus.Properties
member={Get,PropertiesChanged}
peer=(label=unconfined),
dbus (send)
bus=session
interface=org.gtk.Actions
member=Changed
peer=(name=org.freedesktop.DBus, label=unconfined),
dbus (receive)
bus=session
interface=org.gtk.Actions
member={Activate,DescribeAll,SetState}
peer=(label=unconfined),
dbus (receive)
bus=session
interface=org.gtk.Menus
member={Start,End}
peer=(label=unconfined),
dbus (send)
bus=session
interface=org.gtk.Menus
member=Changed
peer=(name=org.freedesktop.DBus, label=unconfined),
dbus (send)
bus=session
path="/com/ubuntu/MenuRegistrar"
interface="com.ubuntu.MenuRegistrar"
member="{Register,Unregister}{App,Surface}Menu"
peer=(label=unconfined),
}
2021-02-15 16:26:18 +01:00
|
|
|
|
|
|
|
MatchFlag *match = static_cast<MatchFlag *>(*i);
|
2024-05-10 03:06:22 -07:00
|
|
|
|
2024-12-24 01:27:10 -08:00
|
|
|
perm32_t bit = 1;
|
|
|
|
perm32_t check = match->perms | match->audit;
|
|
|
|
if (filedfa)
|
|
|
|
check &= ~ALL_AA_EXEC_TYPE;
|
2007-03-30 20:38:51 +00:00
|
|
|
|
2024-12-24 01:27:10 -08:00
|
|
|
for (int i = 0; check; i++) {
|
|
|
|
if (check & bit) {
|
|
|
|
error = pri_update_perm(opts, priority, i, match, perms, exact, filedfa);
|
|
|
|
if (error)
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
check &= ~bit;
|
|
|
|
bit <<= 1;
|
|
|
|
}
|
2011-03-13 05:55:25 -07:00
|
|
|
}
|
2024-12-24 01:27:10 -08:00
|
|
|
if (opts.dump & DUMP_DFA_PERMS) {
|
|
|
|
cerr << " computed: "; perms.dump(cerr); cerr << "\n";
|
2012-02-16 07:41:40 -08:00
|
|
|
}
|
2024-12-24 01:27:10 -08:00
|
|
|
out:
|
2012-02-16 07:40:21 -08:00
|
|
|
if (error)
|
2011-03-13 05:55:25 -07:00
|
|
|
fprintf(stderr, "profile has merged rule with conflicting x modifiers\n");
|
2011-02-22 03:47:03 -08:00
|
|
|
|
2012-02-16 07:40:21 -08:00
|
|
|
return error;
|
2007-03-30 20:38:51 +00:00
|
|
|
}
|