def testPrefixMinusLowPrecedence(self): query = "-5 * 5" expected = expression.Product( expression.Literal(-1), expression.Product(expression.Literal(5), expression.Literal(5))) self.assertQueryMatches(query, expected)
def testPrefixMinus(self): query = "-(5 + 5)" expected = expression.Product( expression.Literal(-1), expression.Sum(expression.Literal(5), expression.Literal(5))) self.assertQueryMatches(query, expected)
def testParens(self): query = "(3 + 2) * 5" expected = expression.Product( expression.Sum(expression.Literal(3), expression.Literal(2)), expression.Literal(5)) self.assertQueryMatches(query, expected)
def testPrecedence(self): query = "5 == 1 * 5 and Process/name is 'init'" expected = expression.Intersection( expression.Equivalence( expression.Literal(5), expression.Product(expression.Literal(1), expression.Literal(5))), expression.Equivalence(expression.Binding("Process/name"), expression.Literal("init"))) self.assertQueryMatches(query, expected)
def testLists(self): query = "'foo' in ('foo', 'bar') and 1 not in (5, 2, 3,17)" expected = expression.Intersection( expression.Membership(expression.Literal("foo"), expression.Literal(("foo", "bar"))), expression.Complement( expression.Membership(expression.Literal(1), expression.Literal((5, 2, 3, 17))))) self.assertQueryMatches(query, expected)
def testParamFailures(self): query = "{foo} == 1" params = ["Process/pid"] self.assertQueryRaises(query, params=params) # Even fixing the above, the left side should be a literal, not a # binding. query = "{foo} == 1" params = {"foo": "Process/pid"} exptected = expression.Equivalence(expression.Literal("Process/pid"), expression.Literal(1)) self.assertQueryMatches(query, exptected, params=params)
def testLetSubexpr(self): query = ("Process/parent matches (Process/command is 'init' and " "Process/pid is 1)") expected = expression.Let( expression.Binding("Process/parent"), expression.Intersection( expression.Equivalence(expression.Binding("Process/command"), expression.Literal("init")), expression.Equivalence(expression.Binding("Process/pid"), expression.Literal(1)))) self.assertQueryMatches(query, expected)
def testTemplateReplacements(self): query = "Process/pid == {}" params = [1] exptected = expression.Equivalence(expression.Binding("Process/pid"), expression.Literal(1)) self.assertQueryMatches(query, exptected, params=params) query = "Process/pid == {pid}" params = {"pid": 1} exptected = expression.Equivalence(expression.Binding("Process/pid"), expression.Literal(1)) self.assertQueryMatches(query, exptected, params=params)
def _solve_equivalence(self, expr, binding, literal): literal_value = literal.value if isinstance(literal_value, entity_id.Identity): results = set() for index in literal_value.indices: results |= self._solve_equivalence(expr, binding, expression.Literal(index)) return results table = self.lookup_tables.get(binding.value, None) if table: # Sweet, we have exact index for this. return self._as_entities(table.table.get(literal_value, set())) # Don't have an exact index, but can prefilter by component index. component, _ = binding.value.split("/", 1) slow_matcher = matcher.QueryMatcher(self._subquery(expr)) entities = set() candidates = self.lookup_tables["components"].table.get(component, []) for identity in candidates: entity = self.entities[identity.first_index] if slow_matcher.match(entity): entities.add(entity) return entities
def testLetSingleAny(self): query = "any Process/parent->Process/command is 'init'" expected = expression.LetAny( expression.Binding("Process/parent"), expression.Equivalence(expression.Binding("Process/command"), expression.Literal("init"))) self.assertQueryMatches(query, expected)
def testLetSubexprEach(self): query = "each Process/children matches Process/command is 'foo'" expected = expression.LetEach( expression.Binding("Process/children"), expression.Equivalence(expression.Binding("Process/command"), expression.Literal("foo"))) self.assertQueryMatches(query, expected)
def testBigQuery(self): query = ("(Process/pid is 1 and Process/command in ('init', 'initd')) " "or any Process/children matches (Process/command not in " "('launchd', 'foo'))") expected = expression.Union( expression.Intersection( expression.Equivalence(expression.Binding("Process/pid"), expression.Literal(1)), expression.Membership(expression.Binding("Process/command"), expression.Literal(("init", "initd")))), expression.LetAny( expression.Binding("Process/children"), expression.Complement( expression.Membership( expression.Binding("Process/command"), expression.Literal(("launchd", "foo")))))) self.assertQueryMatches(query, expected)
def as_query(self): """A query that'll match entities with this identity.""" union = [] for _, keys, vals in self.indices: if isinstance(keys, tuple): intersection = [] for idx, key in enumerate(keys): intersection.append( expression.Equivalence(expression.Binding(key), expression.Literal(vals[idx]))) union.append(expression.Intersection(*intersection)) else: union.append( expression.Equivalence(expression.Binding(keys), expression.Literal(vals))) if len(union) == 1: return union[0] return expression.Union(*union)
def render(self, renderer): renderer.table_header([("User", "user", "15"), ("Session", "session", "15"), ("Terminal vnode", "vnode", "30"), ("Recovered input", "input", "75"), ("Recovered output", "output", "75")]) for terminal in self.session.entities.find("has component Terminal"): buffer_in = self.session.entities.find_first( expression.Intersection( expression.Equivalence( expression.Binding("Buffer/purpose"), expression.Literal("terminal_input")), expression.Equivalence( expression.Binding("Buffer/context"), expression.Literal(terminal.identity)))) buffer_out = self.session.entities.find_first( expression.Intersection( expression.Equivalence( expression.Binding("Buffer/purpose"), expression.Literal("terminal_output")), expression.Equivalence( expression.Binding("Buffer/context"), expression.Literal(terminal.identity)))) renderer.table_row( terminal["Terminal/session"]["Session/user"]["User/username"], terminal["Terminal/session"]["Session/sid"], terminal.get("Terminal/file", complete=True)["File/path"], repr( self.SHORTENER.sub("<whitespace>", buffer_in["Buffer/contents"])), repr( self.SHORTENER.sub("<whitespace>", buffer_out["Buffer/contents"])))
def NegateValue(*args, **kwargs): return expression.Product(expression.Literal(-1), *args, **kwargs)
def testEquivalence(self): query = "10 is 10" expected = expression.Equivalence(expression.Literal(10), expression.Literal(10)) self.assertQueryMatches(query, expected)
def testLiterals(self): query = "0xff" expected = expression.Literal(255) self.assertQueryMatches(query, expected)
def testNestedParens(self): query = "Process/pid in ((1,2))" expected = expression.Membership(expression.Binding("Process/pid"), expression.Literal((1, 2))) self.assertQueryMatches(query, expected)
def next_atom(self): token = self.tokenizer.next_token() if token is None: return self.error("Unexpected end of input.") if token.name == "infix": if token.value == "-": # As it turns out, minus signs can be prefix operators! Who # knew? Certainly not the tokenizer. token.name = "prefix" else: return self.error("Unexpected infix operator.", token) if token.name == "prefix": operator = PREFIX[token.value] lhs = self.next_atom() rhs = self.next_expression(lhs, operator.precedence) return self._handle_expr(operator, rhs, start=token.start, end=rhs.end) if token.name == "literal": return expression.Literal(token.value, start=token.start, end=token.end) if token.name == "param": return expression.Literal(self._replace_param(token), start=token.start, end=token.end) if token.name == "symbol": return expression.Binding(token.value, start=token.start, end=token.end) if token.name == "lparen": # Parentheses can denote subexpressions or lists. Lists have at # least one comma before rparen (just like Python). lhs = self.next_atom() expr = self.next_expression(lhs, 0) if self.tokenizer.current_token is None: return self.error("End of input before closing parenthesis.", token) if self.tokenizer.peek().name == "comma": # It's a list, not an expression. Build it out as a literal. if not isinstance(lhs, expression.Literal): return self.error("Non-literal value in list.", lhs) self.tokenizer.next_token() vals = [lhs.value] while (self.tokenizer.current_token and self.tokenizer.current_token.name == "comma"): atom = self.next_atom() if not isinstance(atom, expression.Literal): return self.error("Non-literal value in list", atom) vals.append(atom.value) self.tokenizer.next_token() if (self.tokenizer.current_token is None or self.tokenizer.current_token.name != "rparen"): self.error("Lists must end with a closing paren.", self.tokenizer.current_token) return expression.Literal(tuple(vals), start=token.start, end=self.tokenizer.position) elif self.tokenizer.peek().name != "rparen": # We got here because there's still some stuff left to parse # and the next token is not an rparen. That can mean that an # infix operator is missing or that the parens are unmatched. # Decide which is more likely and raise the appropriate error. lparens = 1 rparens = 0 lookahead = 2 while self.tokenizer.peek(lookahead): if self.tokenizer.peek(lookahead).name == "lparen": lparens += 1 elif self.tokenizer.peek(lookahead).name == "rparen": rparens += 1 lookahead += 1 if lparens > rparens: return self.error("Ummatched left parenthesis.", token) else: next_token = self.tokenizer.peek() return self.error( "Was not expecting %s here." % next_token.value, next_token) self.tokenizer.next_token() return expr return self.error("Cannot handle token %s." % token, token)