mirror of
https://github.com/trailofbits/buttercup
synced 2026-06-21 14:11:39 +00:00
165 lines
9.8 KiB
Python
165 lines
9.8 KiB
Python
from collections.abc import Iterator
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
from redis import Redis
|
|
|
|
from buttercup.common.stack_parsing import CrashSet, get_crash_data, get_inst_key, parse_stacktrace
|
|
|
|
# ruff: noqa: E501
|
|
|
|
JAVA_TEST_INST_KEY = "JXPathFuzzer\norg.apache.commons.beanutils.DynaBean\norg.apache.commons.jxpath.CompiledExpression\norg.apache.commons.jxpath.ExpressionContext\norg.apache.commons.jxpath.Function\norg.apache.commons.jxpath.Functions\norg.apache.commons.jxpath.JXPathContext\norg.apache.commons.jxpath.JXPathContextFactory\norg.apache.commons.jxpath.JXPathContextFactoryConfigurationError\norg.apache.commons.jxpath.JXPathException\norg.apache.commons.jxpath.JXPathFunctionNotFoundException\norg.apache.commons.jxpath.JXPathInvalidAccessException\norg.apache.commons.jxpath.JXPathInvalidSyntaxException\norg.apache.commons.jxpath.JXPathNotFoundException\norg.apache.commons.jxpath.JXPathTypeConversionException\norg.apache.commons.jxpath.NodeSet\norg.apache.commons.jxpath.PackageFunctions\norg.apache.commons.jxpath.Pointer\norg.apache.commons.jxpath.Variables\norg.apache.commons.jxpath.ri.Compiler\norg.apache.commons.jxpath.ri.EvalContext\norg.apache.commons.jxpath.ri.InfoSetUtil\norg.apache.commons.jxpath.ri.JXPathContextFactoryReferenceImpl\norg.apache.commons.jxpath.ri.JXPathContextReferenceImpl\norg.apache.commons.jxpath.ri.NamespaceResolver\norg.apache.commons.jxpath.ri.Parser\norg.apache.commons.jxpath.ri.QName\norg.apache.commons.jxpath.ri.axes.AncestorContext\norg.apache.commons.jxpath.ri.axes.AttributeContext\norg.apache.commons.jxpath.ri.axes.ChildContext\norg.apache.commons.jxpath.ri.axes.DescendantContext\norg.apache.commons.jxpath.ri.axes.InitialContext\norg.apache.commons.jxpath.ri.axes.NamespaceContext\norg.apache.commons.jxpath.ri.axes.NodeSetContext\norg.apache.commons.jxpath.ri.axes.ParentContext\norg.apache.commons.jxpath.ri.axes.PrecedingOrFollowingContext\norg.apache.commons.jxpath.ri.axes.PredicateContext\norg.apache.commons.jxpath.ri.axes.RootContext\norg.apache.commons.jxpath.ri.axes.SelfContext\norg.apache.commons.jxpath.ri.axes.UnionContext\norg.apache.commons.jxpath.ri.compiler.Constant\norg.apache.commons.jxpath.ri.compiler.CoreFunction\norg.apache.commons.jxpath.ri.compiler.CoreOperation\norg.apache.commons.jxpath.ri.compiler.CoreOperationGreaterThan\norg.apache.commons.jxpath.ri.compiler.CoreOperationNegate\norg.apache.commons.jxpath.ri.compiler.CoreOperationRelationalExpression\norg.apache.commons.jxpath.ri.compiler.Expression\norg.apache.commons.jxpath.ri.compiler.ExpressionPath\norg.apache.commons.jxpath.ri.compiler.LocationPath\norg.apache.commons.jxpath.ri.compiler.NodeNameTest\norg.apache.commons.jxpath.ri.compiler.NodeTest\norg.apache.commons.jxpath.ri.compiler.NodeTypeTest\norg.apache.commons.jxpath.ri.compiler.Operation\norg.apache.commons.jxpath.ri.compiler.Path\norg.apache.commons.jxpath.ri.compiler.Step\norg.apache.commons.jxpath.ri.compiler.TreeCompiler\norg.apache.commons.jxpath.ri.model.NodeIterator\norg.apache.commons.jxpath.ri.model.NodePointer\norg.apache.commons.jxpath.ri.model.NodePointerFactory\norg.apache.commons.jxpath.ri.model.VariablePointer\norg.apache.commons.jxpath.ri.model.VariablePointerFactory\norg.apache.commons.jxpath.ri.model.beans.BeanPointer\norg.apache.commons.jxpath.ri.model.beans.BeanPointerFactory\norg.apache.commons.jxpath.ri.model.beans.CollectionPointer\norg.apache.commons.jxpath.ri.model.beans.CollectionPointerFactory\norg.apache.commons.jxpath.ri.model.beans.NullPointer\norg.apache.commons.jxpath.ri.model.beans.NullPropertyPointer\norg.apache.commons.jxpath.ri.model.beans.PropertyOwnerPointer\norg.apache.commons.jxpath.ri.model.beans.PropertyPointer\norg.apache.commons.jxpath.ri.model.container.ContainerPointer\norg.apache.commons.jxpath.ri.model.container.ContainerPointerFactory\norg.apache.commons.jxpath.ri.model.dom.DOMNodePointer\norg.apache.commons.jxpath.ri.model.dom.DOMPointerFactory\norg.apache.commons.jxpath.ri.model.dynabeans.DynaBeanPointer\norg.apache.commons.jxpath.ri.model.dynabeans.DynaBeanPointerFactory\norg.apache.commons.jxpath.ri.model.dynamic.DynamicPointer\norg.apache.commons.jxpath.ri.model.dynamic.DynamicPointerFactory\norg.apache.commons.jxpath.ri.model.jdom.JDOMNodePointer\norg.apache.commons.jxpath.ri.model.jdom.JDOMPointerFactory\norg.apache.commons.jxpath.ri.parser.ParseException\norg.apache.commons.jxpath.ri.parser.SimpleCharStream\norg.apache.commons.jxpath.ri.parser.Token\norg.apache.commons.jxpath.ri.parser.TokenMgrError\norg.apache.commons.jxpath.ri.parser.XPathParser\norg.apache.commons.jxpath.ri.parser.XPathParserConstants\norg.apache.commons.jxpath.ri.parser.XPathParserTokenManager\norg.apache.commons.jxpath.util.ClassLoaderUtil\norg.jdom.Comment\norg.jdom.Content\norg.jdom.ContentList\norg.jdom.DocType\norg.jdom.Document\norg.jdom.Element\norg.jdom.IllegalAddException\norg.jdom.Parent\norg.jdom.ProcessingInstruction\norg.w3c.dom.Document\norg.w3c.dom.DocumentType\norg.w3c.dom.Element\norg.w3c.dom.ElementTraversal\norg.w3c.dom.Node\norg.w3c.dom.NodeList\norg.w3c.dom.TypeInfo\norg.w3c.dom.events.DocumentEvent\norg.w3c.dom.events.EventTarget\norg.w3c.dom.ranges.DocumentRange\norg.w3c.dom.traversal.DocumentTraversal\norg.xml.sax.ContentHandler\norg.xml.sax.DTDHandler\norg.xml.sax.EntityResolver\norg.xml.sax.ErrorHandler\norg.xml.sax.InputSource\norg.xml.sax.SAXException\norg.xml.sax.SAXParseException\norg.xml.sax.helpers.DefaultHandler"
|
|
|
|
|
|
def get_tc_file(name: str) -> Path:
|
|
"""Get testcase file"""
|
|
crash_file = Path(__file__).parent / "data" / "stacktrace_corpus" / f"{name}_stacktrace.txt"
|
|
if not crash_file.exists():
|
|
raise FileNotFoundError(f"Crash file not found at {crash_file}")
|
|
|
|
return crash_file
|
|
|
|
|
|
@pytest.fixture
|
|
def java_crash_testcase() -> Path:
|
|
"""Get java crash_test_case"""
|
|
return get_tc_file("java")
|
|
|
|
|
|
@pytest.fixture
|
|
def c_crash_testcase() -> Path:
|
|
"""Get c crash_test_case"""
|
|
return get_tc_file("c")
|
|
|
|
|
|
@pytest.fixture
|
|
def c2_crash_testcase() -> Path:
|
|
"""Get c crash_test_case"""
|
|
return get_tc_file("c_2")
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_crash_set() -> Iterator[CrashSet]:
|
|
"""Get mock crash_set"""
|
|
res = CrashSet(MagicMock(spec=Redis))
|
|
set_data = set()
|
|
|
|
def add_mock(value: str) -> bool:
|
|
if value in set_data:
|
|
return True
|
|
set_data.add(value)
|
|
return False
|
|
|
|
res.set.add = add_mock
|
|
return res
|
|
|
|
|
|
def test_get_crash_data_basic():
|
|
# Simple stacktrace example
|
|
stacktrace = """
|
|
#0 0x7f339b644844 in foo::bar::crash() /src/foo/bar.cc:123:4
|
|
#1 0x7f339b644900 in main /src/main.cc:45:2
|
|
"""
|
|
crash_state = get_crash_data(stacktrace)
|
|
assert crash_state is not None
|
|
assert isinstance(crash_state, str)
|
|
|
|
|
|
def test_get_crash_data_symbolized():
|
|
# Test with symbolized stacktrace
|
|
stacktrace = """
|
|
#0 foo::bar::crash() /src/foo/bar.cc:123:4
|
|
#1 main /src/main.cc:45:2
|
|
"""
|
|
crash_state = get_crash_data(stacktrace, symbolized=True)
|
|
assert crash_state is not None
|
|
assert isinstance(crash_state, str)
|
|
|
|
|
|
def test_get_crash_data_empty():
|
|
# Test with empty stacktrace
|
|
stacktrace = ""
|
|
crash_state = get_crash_data(stacktrace)
|
|
assert crash_state is not None
|
|
assert isinstance(crash_state, str)
|
|
|
|
|
|
def test_get_crash_data_invalid():
|
|
# Test with invalid stacktrace format
|
|
stacktrace = "This is not a valid stacktrace"
|
|
crash_state = get_crash_data(stacktrace)
|
|
assert crash_state is not None
|
|
assert isinstance(crash_state, str)
|
|
|
|
|
|
def test_java_stacktrace(java_crash_testcase: Path):
|
|
with open(java_crash_testcase) as f:
|
|
trace = f.read()
|
|
|
|
crash_state = get_crash_data(trace)
|
|
expected = "org.apache.commons.jxpath.ri.compiler.CoreOperation.parenthesize\norg.apache.commons.jxpath.ri.compiler.CoreOperation.toString\norg.apache.commons.jxpath.ri.compiler.CoreOperation.parenthesize\n"
|
|
assert crash_state == expected
|
|
|
|
|
|
def test_c_stacktrace(c_crash_testcase: Path):
|
|
with open(c_crash_testcase) as f:
|
|
trace = f.read()
|
|
|
|
crash_state = get_crash_data(trace)
|
|
print(crash_state)
|
|
assert crash_state is not None
|
|
assert isinstance(crash_state, str)
|
|
expected = "cil_destroy_block\ncil_destroy_data\ncil_tree_node_destroy\n"
|
|
assert crash_state == expected
|
|
|
|
|
|
def test_c_instrumentation_key(c_crash_testcase: Path):
|
|
with open(c_crash_testcase) as f:
|
|
trace = f.read()
|
|
|
|
inst_key = get_inst_key(trace)
|
|
print(inst_key)
|
|
assert inst_key == ""
|
|
|
|
|
|
def test_java_instrumentation_key(java_crash_testcase: Path):
|
|
with open(java_crash_testcase) as f:
|
|
trace = f.read()
|
|
|
|
inst_key = get_inst_key(trace)
|
|
print(inst_key)
|
|
assert inst_key is not None
|
|
assert isinstance(inst_key, str)
|
|
assert inst_key == JAVA_TEST_INST_KEY
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"stacktrace_path,line_number",
|
|
[
|
|
(get_tc_file("c"), 249),
|
|
(get_tc_file("c_2"), 230),
|
|
(get_tc_file("java"), 0),
|
|
],
|
|
)
|
|
def test_crash_set(mock_crash_set: CrashSet, stacktrace_path: Path, line_number: int):
|
|
with stacktrace_path.open("r") as f:
|
|
stacktrace = f.read()
|
|
|
|
assert not mock_crash_set.add("test", "test", "test", "test", stacktrace)
|
|
assert mock_crash_set.add("test", "test", "test", "test", stacktrace)
|
|
assert mock_crash_set._get_final_line_number(parse_stacktrace(stacktrace)) == line_number
|
|
|
|
|
|
def test_crash_set_diff_line_numbers(mock_crash_set: CrashSet, c_crash_testcase: Path, c2_crash_testcase: Path):
|
|
with c_crash_testcase.open("r") as f:
|
|
stacktrace1 = f.read()
|
|
with c2_crash_testcase.open("r") as f:
|
|
stacktrace2 = f.read()
|
|
|
|
assert not mock_crash_set.add("test", "test", "test", "test", stacktrace1)
|
|
assert not mock_crash_set.add("test", "test", "test", "test", stacktrace2)
|
|
|
|
assert mock_crash_set.add("test", "test", "test", "test", stacktrace1)
|
|
assert mock_crash_set.add("test", "test", "test", "test", stacktrace2)
|