crosshair-tool 0.0.56__cp39-cp39-macosx_11_0_arm64.whl → 0.0.100__cp39-cp39-macosx_11_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- _crosshair_tracers.cpython-39-darwin.so +0 -0
- crosshair/__init__.py +1 -1
- crosshair/_mark_stacks.h +51 -24
- crosshair/_tracers.h +9 -5
- crosshair/_tracers_test.py +19 -9
- crosshair/auditwall.py +9 -8
- crosshair/auditwall_test.py +31 -19
- crosshair/codeconfig.py +3 -2
- crosshair/condition_parser.py +17 -133
- crosshair/condition_parser_test.py +54 -96
- crosshair/conftest.py +1 -1
- crosshair/copyext.py +91 -22
- crosshair/copyext_test.py +33 -0
- crosshair/core.py +259 -203
- crosshair/core_and_libs.py +20 -0
- crosshair/core_regestered_types_test.py +82 -0
- crosshair/core_test.py +693 -664
- crosshair/diff_behavior.py +76 -21
- crosshair/diff_behavior_test.py +132 -23
- crosshair/dynamic_typing.py +128 -18
- crosshair/dynamic_typing_test.py +91 -4
- crosshair/enforce.py +1 -6
- crosshair/enforce_test.py +15 -23
- crosshair/examples/check_examples_test.py +2 -1
- crosshair/fnutil.py +2 -3
- crosshair/fnutil_test.py +0 -7
- crosshair/fuzz_core_test.py +70 -83
- crosshair/libimpl/arraylib.py +10 -7
- crosshair/libimpl/binascii_ch_test.py +30 -0
- crosshair/libimpl/binascii_test.py +67 -0
- crosshair/libimpl/binasciilib.py +150 -0
- crosshair/libimpl/bisectlib_test.py +5 -5
- crosshair/libimpl/builtinslib.py +1002 -682
- crosshair/libimpl/builtinslib_ch_test.py +108 -30
- crosshair/libimpl/builtinslib_test.py +431 -143
- crosshair/libimpl/codecslib.py +22 -2
- crosshair/libimpl/codecslib_test.py +41 -9
- crosshair/libimpl/collectionslib.py +44 -8
- crosshair/libimpl/collectionslib_test.py +108 -20
- crosshair/libimpl/copylib.py +1 -1
- crosshair/libimpl/copylib_test.py +18 -0
- crosshair/libimpl/datetimelib.py +84 -67
- crosshair/libimpl/datetimelib_ch_test.py +12 -7
- crosshair/libimpl/datetimelib_test.py +5 -6
- crosshair/libimpl/decimallib.py +5257 -0
- crosshair/libimpl/decimallib_ch_test.py +78 -0
- crosshair/libimpl/decimallib_test.py +76 -0
- crosshair/libimpl/encodings/_encutil.py +21 -11
- crosshair/libimpl/fractionlib.py +16 -0
- crosshair/libimpl/fractionlib_test.py +80 -0
- crosshair/libimpl/functoolslib.py +19 -7
- crosshair/libimpl/functoolslib_test.py +22 -6
- crosshair/libimpl/hashliblib.py +30 -0
- crosshair/libimpl/hashliblib_test.py +18 -0
- crosshair/libimpl/heapqlib.py +32 -5
- crosshair/libimpl/heapqlib_test.py +15 -12
- crosshair/libimpl/iolib.py +7 -4
- crosshair/libimpl/ipaddresslib.py +8 -0
- crosshair/libimpl/itertoolslib_test.py +1 -1
- crosshair/libimpl/mathlib.py +165 -2
- crosshair/libimpl/mathlib_ch_test.py +44 -0
- crosshair/libimpl/mathlib_test.py +59 -16
- crosshair/libimpl/oslib.py +7 -0
- crosshair/libimpl/pathliblib_test.py +10 -0
- crosshair/libimpl/randomlib.py +1 -0
- crosshair/libimpl/randomlib_test.py +6 -4
- crosshair/libimpl/relib.py +180 -59
- crosshair/libimpl/relib_ch_test.py +26 -2
- crosshair/libimpl/relib_test.py +77 -14
- crosshair/libimpl/timelib.py +35 -13
- crosshair/libimpl/timelib_test.py +13 -3
- crosshair/libimpl/typeslib.py +15 -0
- crosshair/libimpl/typeslib_test.py +36 -0
- crosshair/libimpl/unicodedatalib_test.py +3 -3
- crosshair/libimpl/weakreflib.py +13 -0
- crosshair/libimpl/weakreflib_test.py +69 -0
- crosshair/libimpl/zliblib.py +15 -0
- crosshair/libimpl/zliblib_test.py +13 -0
- crosshair/lsp_server.py +21 -10
- crosshair/main.py +48 -28
- crosshair/main_test.py +59 -14
- crosshair/objectproxy.py +39 -14
- crosshair/objectproxy_test.py +27 -13
- crosshair/opcode_intercept.py +212 -24
- crosshair/opcode_intercept_test.py +172 -18
- crosshair/options.py +0 -1
- crosshair/patch_equivalence_test.py +5 -21
- crosshair/path_cover.py +7 -5
- crosshair/path_search.py +6 -4
- crosshair/path_search_test.py +1 -2
- crosshair/pathing_oracle.py +53 -10
- crosshair/pathing_oracle_test.py +21 -0
- crosshair/pure_importer_test.py +5 -21
- crosshair/register_contract.py +16 -6
- crosshair/register_contract_test.py +2 -14
- crosshair/simplestructs.py +154 -85
- crosshair/simplestructs_test.py +16 -2
- crosshair/smtlib.py +24 -0
- crosshair/smtlib_test.py +14 -0
- crosshair/statespace.py +319 -196
- crosshair/statespace_test.py +45 -0
- crosshair/stubs_parser.py +0 -2
- crosshair/test_util.py +87 -25
- crosshair/test_util_test.py +26 -0
- crosshair/tools/check_init_and_setup_coincide.py +0 -3
- crosshair/tools/generate_demo_table.py +2 -2
- crosshair/tracers.py +141 -49
- crosshair/type_repo.py +11 -4
- crosshair/unicode_categories.py +1 -0
- crosshair/util.py +158 -76
- crosshair/util_test.py +13 -20
- crosshair/watcher.py +4 -4
- crosshair/z3util.py +1 -1
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/METADATA +45 -36
- crosshair_tool-0.0.100.dist-info/RECORD +176 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/WHEEL +2 -1
- crosshair/examples/hypothesis/__init__.py +0 -2
- crosshair/examples/hypothesis/bugs_detected/simple_strategies.py +0 -74
- crosshair_tool-0.0.56.dist-info/RECORD +0 -152
- /crosshair/{examples/hypothesis/bugs_detected/__init__.py → py.typed} +0 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/entry_points.txt +0 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info/licenses}/LICENSE +0 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/top_level.txt +0 -0
|
Binary file
|
crosshair/__init__.py
CHANGED
|
@@ -15,7 +15,7 @@ from crosshair.statespace import StateSpace
|
|
|
15
15
|
from crosshair.tracers import NoTracing, ResumedTracing
|
|
16
16
|
from crosshair.util import IgnoreAttempt, debug
|
|
17
17
|
|
|
18
|
-
__version__ = "0.0.
|
|
18
|
+
__version__ = "0.0.100" # Do not forget to update in setup.py!
|
|
19
19
|
__author__ = "Phillip Schanely"
|
|
20
20
|
__license__ = "MIT"
|
|
21
21
|
__status__ = "Alpha"
|
crosshair/_mark_stacks.h
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
// This file includes a modified version of CPython's mark_stacks
|
|
6
|
-
// implementation
|
|
6
|
+
// implementation from:
|
|
7
7
|
// https://github.com/python/cpython/blob/v3.12.0/Objects/frameobject.c
|
|
8
8
|
|
|
9
9
|
// The shared source code is licensed under the PSF license and is
|
|
@@ -36,6 +36,7 @@ _ch_pop_to_level(int64_t stack, int level) {
|
|
|
36
36
|
// Python 3.13
|
|
37
37
|
// ===========
|
|
38
38
|
|
|
39
|
+
// from Include/internal/pycore_opcode_metadata.h
|
|
39
40
|
const uint8_t _ch_PyOpcode_Caches[256] = {
|
|
40
41
|
[JUMP_BACKWARD] = 1,
|
|
41
42
|
[TO_BOOL] = 3,
|
|
@@ -48,6 +49,7 @@ const uint8_t _ch_PyOpcode_Caches[256] = {
|
|
|
48
49
|
[LOAD_SUPER_ATTR] = 1,
|
|
49
50
|
[LOAD_ATTR] = 9,
|
|
50
51
|
[COMPARE_OP] = 1,
|
|
52
|
+
[CONTAINS_OP] = 1,
|
|
51
53
|
[POP_JUMP_IF_TRUE] = 1,
|
|
52
54
|
[POP_JUMP_IF_FALSE] = 1,
|
|
53
55
|
[POP_JUMP_IF_NONE] = 1,
|
|
@@ -57,6 +59,7 @@ const uint8_t _ch_PyOpcode_Caches[256] = {
|
|
|
57
59
|
[BINARY_OP] = 1,
|
|
58
60
|
};
|
|
59
61
|
|
|
62
|
+
// from Include/internal/pycore_opcode_metadata.h
|
|
60
63
|
const uint8_t _ch_PyOpcode_Deopt[256] = {
|
|
61
64
|
[BEFORE_ASYNC_WITH] = BEFORE_ASYNC_WITH,
|
|
62
65
|
[BEFORE_WITH] = BEFORE_WITH,
|
|
@@ -87,6 +90,7 @@ const uint8_t _ch_PyOpcode_Deopt[256] = {
|
|
|
87
90
|
[CALL] = CALL,
|
|
88
91
|
[CALL_ALLOC_AND_ENTER_INIT] = CALL,
|
|
89
92
|
[CALL_BOUND_METHOD_EXACT_ARGS] = CALL,
|
|
93
|
+
[CALL_BOUND_METHOD_GENERAL] = CALL,
|
|
90
94
|
[CALL_BUILTIN_CLASS] = CALL,
|
|
91
95
|
[CALL_BUILTIN_FAST] = CALL,
|
|
92
96
|
[CALL_BUILTIN_FAST_WITH_KEYWORDS] = CALL,
|
|
@@ -102,8 +106,9 @@ const uint8_t _ch_PyOpcode_Deopt[256] = {
|
|
|
102
106
|
[CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = CALL,
|
|
103
107
|
[CALL_METHOD_DESCRIPTOR_NOARGS] = CALL,
|
|
104
108
|
[CALL_METHOD_DESCRIPTOR_O] = CALL,
|
|
109
|
+
[CALL_NON_PY_GENERAL] = CALL,
|
|
105
110
|
[CALL_PY_EXACT_ARGS] = CALL,
|
|
106
|
-
[
|
|
111
|
+
[CALL_PY_GENERAL] = CALL,
|
|
107
112
|
[CALL_STR_1] = CALL,
|
|
108
113
|
[CALL_TUPLE_1] = CALL,
|
|
109
114
|
[CALL_TYPE_1] = CALL,
|
|
@@ -115,6 +120,8 @@ const uint8_t _ch_PyOpcode_Deopt[256] = {
|
|
|
115
120
|
[COMPARE_OP_INT] = COMPARE_OP,
|
|
116
121
|
[COMPARE_OP_STR] = COMPARE_OP,
|
|
117
122
|
[CONTAINS_OP] = CONTAINS_OP,
|
|
123
|
+
[CONTAINS_OP_DICT] = CONTAINS_OP,
|
|
124
|
+
[CONTAINS_OP_SET] = CONTAINS_OP,
|
|
118
125
|
[CONVERT_VALUE] = CONVERT_VALUE,
|
|
119
126
|
[COPY] = COPY,
|
|
120
127
|
[COPY_FREE_VARS] = COPY_FREE_VARS,
|
|
@@ -268,6 +275,7 @@ const uint8_t _ch_PyOpcode_Deopt[256] = {
|
|
|
268
275
|
[YIELD_VALUE] = YIELD_VALUE,
|
|
269
276
|
};
|
|
270
277
|
|
|
278
|
+
// from Python/instrumentation.c
|
|
271
279
|
static const uint8_t _ch_DE_INSTRUMENT[256] = {
|
|
272
280
|
[INSTRUMENTED_RESUME] = RESUME,
|
|
273
281
|
[INSTRUMENTED_RETURN_VALUE] = RETURN_VALUE,
|
|
@@ -530,26 +538,13 @@ static const uint8_t _ch_DE_INSTRUMENT[256] = {
|
|
|
530
538
|
#endif
|
|
531
539
|
#endif
|
|
532
540
|
|
|
533
|
-
/* Get the underlying opcode, stripping instrumentation */
|
|
534
|
-
int _ch_Py_GetBaseOpcode(PyCodeObject *code, int i)
|
|
535
|
-
{
|
|
536
|
-
int opcode = _PyCode_CODE(code)[i].op.code;
|
|
537
|
-
if (opcode == INSTRUMENTED_LINE) {
|
|
538
|
-
opcode = code->_co_monitoring->lines[i].original_opcode;
|
|
539
|
-
}
|
|
540
|
-
if (opcode == INSTRUMENTED_INSTRUCTION) {
|
|
541
|
-
opcode = code->_co_monitoring->per_instruction_opcodes[i];
|
|
542
|
-
}
|
|
543
|
-
int deinstrumented = _ch_DE_INSTRUMENT[opcode];
|
|
544
|
-
if (deinstrumented) {
|
|
545
|
-
return deinstrumented;
|
|
546
|
-
}
|
|
547
|
-
return _ch_PyOpcode_Deopt[opcode];
|
|
548
|
-
}
|
|
549
|
-
|
|
550
541
|
static int64_t *
|
|
551
542
|
_ch_mark_stacks(PyCodeObject *code_obj, int len)
|
|
552
543
|
{
|
|
544
|
+
// This differs from the corresponding function in CPython in a few ways:
|
|
545
|
+
// 1) The returned stack values represent stack depths, not stack content types.
|
|
546
|
+
// 2) The low bit of each depth is a flag that indicates whether this is an
|
|
547
|
+
// instruction that we trace, or it could follow an instruction that we trace.
|
|
553
548
|
PyObject *co_code = PyCode_GetCode(code_obj);
|
|
554
549
|
// printf("co_code %d\n", co_code);
|
|
555
550
|
if (co_code == NULL) {
|
|
@@ -557,34 +552,47 @@ _ch_mark_stacks(PyCodeObject *code_obj, int len)
|
|
|
557
552
|
}
|
|
558
553
|
_Py_CODEUNIT *code = (_Py_CODEUNIT *)PyBytes_AS_STRING(co_code);
|
|
559
554
|
int64_t *stacks = PyMem_New(int64_t, len+1);
|
|
560
|
-
int i, j, opcode;
|
|
561
|
-
|
|
562
555
|
if (stacks == NULL) {
|
|
563
556
|
PyErr_NoMemory();
|
|
564
557
|
Py_DECREF(co_code);
|
|
565
558
|
return NULL;
|
|
566
559
|
}
|
|
560
|
+
uint8_t *enabled_tracing = PyMem_New(uint8_t, len+1);
|
|
561
|
+
if (enabled_tracing == NULL) {
|
|
562
|
+
PyErr_NoMemory();
|
|
563
|
+
PyMem_Free(stacks);
|
|
564
|
+
Py_DECREF(co_code);
|
|
565
|
+
return NULL;
|
|
566
|
+
}
|
|
567
|
+
int i, j, opcode;
|
|
568
|
+
|
|
567
569
|
for (int i = 1; i <= len; i++) {
|
|
568
570
|
stacks[i] = UNINITIALIZED;
|
|
571
|
+
enabled_tracing[i] = 0;
|
|
569
572
|
}
|
|
570
573
|
stacks[0] = EMPTY_STACK;
|
|
574
|
+
#if PY_VERSION_HEX < 0x030D0000
|
|
575
|
+
// Python 3.12
|
|
571
576
|
if (code_obj->co_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR))
|
|
572
577
|
{
|
|
573
578
|
// Generators get sent None while starting:
|
|
574
579
|
stacks[0]++;
|
|
575
580
|
}
|
|
581
|
+
#endif
|
|
576
582
|
int todo = 1;
|
|
577
583
|
while (todo) {
|
|
578
584
|
todo = 0;
|
|
579
585
|
/* Scan instructions */
|
|
580
586
|
for (i = 0; i < len;) {
|
|
581
587
|
int64_t next_stack = stacks[i];
|
|
582
|
-
opcode =
|
|
588
|
+
opcode = code[i].op.code;
|
|
589
|
+
uint8_t trace_enabled_here = _ch_TRACABLE_INSTRUCTIONS[opcode];
|
|
590
|
+
enabled_tracing[i] |= trace_enabled_here;
|
|
583
591
|
int oparg = 0;
|
|
584
592
|
while (opcode == EXTENDED_ARG) {
|
|
585
593
|
oparg = (oparg << 8) | code[i].op.arg;
|
|
586
594
|
i++;
|
|
587
|
-
opcode =
|
|
595
|
+
opcode = code[i].op.code;
|
|
588
596
|
stacks[i] = next_stack;
|
|
589
597
|
}
|
|
590
598
|
int next_i = i + _ch_PyOpcode_Caches[opcode] + 1;
|
|
@@ -607,6 +615,7 @@ _ch_mark_stacks(PyCodeObject *code_obj, int len)
|
|
|
607
615
|
target_stack = next_stack;
|
|
608
616
|
assert(stacks[j] == UNINITIALIZED || stacks[j] == target_stack);
|
|
609
617
|
stacks[j] = target_stack;
|
|
618
|
+
enabled_tracing[j] |= trace_enabled_here;
|
|
610
619
|
stacks[next_i] = next_stack;
|
|
611
620
|
break;
|
|
612
621
|
}
|
|
@@ -615,6 +624,7 @@ _ch_mark_stacks(PyCodeObject *code_obj, int len)
|
|
|
615
624
|
assert(j < len);
|
|
616
625
|
assert(stacks[j] == UNINITIALIZED || stacks[j] == next_stack);
|
|
617
626
|
stacks[j] = next_stack;
|
|
627
|
+
enabled_tracing[j] |= trace_enabled_here;
|
|
618
628
|
stacks[next_i] = next_stack;
|
|
619
629
|
break;
|
|
620
630
|
case JUMP_FORWARD:
|
|
@@ -622,6 +632,7 @@ _ch_mark_stacks(PyCodeObject *code_obj, int len)
|
|
|
622
632
|
assert(j < len);
|
|
623
633
|
assert(stacks[j] == UNINITIALIZED || stacks[j] == next_stack);
|
|
624
634
|
stacks[j] = next_stack;
|
|
635
|
+
enabled_tracing[j] |= trace_enabled_here;
|
|
625
636
|
break;
|
|
626
637
|
case JUMP_BACKWARD:
|
|
627
638
|
case JUMP_BACKWARD_NO_INTERRUPT:
|
|
@@ -633,6 +644,7 @@ _ch_mark_stacks(PyCodeObject *code_obj, int len)
|
|
|
633
644
|
}
|
|
634
645
|
assert(stacks[j] == UNINITIALIZED || stacks[j] == next_stack);
|
|
635
646
|
stacks[j] = next_stack;
|
|
647
|
+
enabled_tracing[j] |= trace_enabled_here;
|
|
636
648
|
break;
|
|
637
649
|
case GET_ITER:
|
|
638
650
|
case GET_AITER:
|
|
@@ -646,10 +658,18 @@ _ch_mark_stacks(PyCodeObject *code_obj, int len)
|
|
|
646
658
|
assert(j < len);
|
|
647
659
|
assert(stacks[j] == UNINITIALIZED || stacks[j] == target_stack);
|
|
648
660
|
stacks[j] = target_stack;
|
|
661
|
+
enabled_tracing[j] |= trace_enabled_here;
|
|
649
662
|
break;
|
|
650
663
|
}
|
|
651
664
|
case END_ASYNC_FOR:
|
|
665
|
+
#if PY_VERSION_HEX < 0x030D0000
|
|
666
|
+
// Python 3.12
|
|
667
|
+
next_stack--;
|
|
668
|
+
#else
|
|
669
|
+
// Python 3.13+
|
|
652
670
|
next_stack--;
|
|
671
|
+
next_stack--;
|
|
672
|
+
#endif
|
|
653
673
|
stacks[next_i] = next_stack;
|
|
654
674
|
break;
|
|
655
675
|
case PUSH_EXC_INFO:
|
|
@@ -676,10 +696,10 @@ _ch_mark_stacks(PyCodeObject *code_obj, int len)
|
|
|
676
696
|
case LOAD_GLOBAL:
|
|
677
697
|
{
|
|
678
698
|
int j = oparg;
|
|
699
|
+
next_stack++;
|
|
679
700
|
if (j & 1) {
|
|
680
701
|
next_stack++;
|
|
681
702
|
}
|
|
682
|
-
next_stack++;
|
|
683
703
|
stacks[next_i] = next_stack;
|
|
684
704
|
break;
|
|
685
705
|
}
|
|
@@ -725,6 +745,7 @@ _ch_mark_stacks(PyCodeObject *code_obj, int len)
|
|
|
725
745
|
stacks[next_i] = next_stack;
|
|
726
746
|
}
|
|
727
747
|
}
|
|
748
|
+
enabled_tracing[next_i] |= trace_enabled_here;
|
|
728
749
|
i = next_i;
|
|
729
750
|
}
|
|
730
751
|
/* Scan exception table */
|
|
@@ -759,5 +780,11 @@ _ch_mark_stacks(PyCodeObject *code_obj, int len)
|
|
|
759
780
|
}
|
|
760
781
|
}
|
|
761
782
|
Py_DECREF(co_code);
|
|
783
|
+
for (i = 0; i < len; i++) {
|
|
784
|
+
if (stacks[i] >= 0) {
|
|
785
|
+
stacks[i] = (stacks[i] << 1) | enabled_tracing[i];
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
PyMem_Free(enabled_tracing);
|
|
762
789
|
return stacks;
|
|
763
790
|
}
|
crosshair/_tracers.h
CHANGED
|
@@ -50,10 +50,11 @@ typedef struct HandlerTable {
|
|
|
50
50
|
} HandlerTable;
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
typedef struct
|
|
54
|
-
|
|
53
|
+
typedef struct FrameNextIandCallback {
|
|
54
|
+
PyFrameObject* frame;
|
|
55
|
+
int expected_i;
|
|
55
56
|
PyObject* callback;
|
|
56
|
-
}
|
|
57
|
+
} FrameNextIandCallback;
|
|
57
58
|
|
|
58
59
|
|
|
59
60
|
typedef struct CodeAndStacks {
|
|
@@ -62,7 +63,7 @@ typedef struct CodeAndStacks {
|
|
|
62
63
|
} CodeAndStacks;
|
|
63
64
|
|
|
64
65
|
|
|
65
|
-
DEFINE_VEC(
|
|
66
|
+
DEFINE_VEC(FrameNextIandCallbackVec, FrameNextIandCallback, init_framecbvec, push_framecb);
|
|
66
67
|
DEFINE_VEC(ModuleVec, PyObject*, init_modulevec, push_module);
|
|
67
68
|
DEFINE_VEC(TableVec, HandlerTable, init_tablevec, push_table_entry)
|
|
68
69
|
|
|
@@ -70,9 +71,10 @@ typedef struct CTracer {
|
|
|
70
71
|
PyObject_HEAD
|
|
71
72
|
ModuleVec modules;
|
|
72
73
|
TableVec handlers;
|
|
73
|
-
|
|
74
|
+
FrameNextIandCallbackVec postop_callbacks;
|
|
74
75
|
BOOL enabled;
|
|
75
76
|
BOOL handling;
|
|
77
|
+
BOOL trace_all_opcodes;
|
|
76
78
|
int thread_id;
|
|
77
79
|
} CTracer;
|
|
78
80
|
|
|
@@ -87,4 +89,6 @@ typedef struct TraceSwap {
|
|
|
87
89
|
|
|
88
90
|
extern PyTypeObject TraceSwapType;
|
|
89
91
|
|
|
92
|
+
extern const uint8_t _ch_TRACABLE_INSTRUCTIONS[256];
|
|
93
|
+
|
|
90
94
|
#endif /* _COVERAGE_TRACER_H */
|
crosshair/_tracers_test.py
CHANGED
|
@@ -5,7 +5,11 @@ from typing import List
|
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
7
|
|
|
8
|
-
from _crosshair_tracers import
|
|
8
|
+
from _crosshair_tracers import ( # type: ignore
|
|
9
|
+
CTracer,
|
|
10
|
+
code_stack_depths,
|
|
11
|
+
frame_stack_read,
|
|
12
|
+
)
|
|
9
13
|
from crosshair.util import mem_usage_kb
|
|
10
14
|
|
|
11
15
|
|
|
@@ -15,19 +19,19 @@ class ExampleModule:
|
|
|
15
19
|
|
|
16
20
|
def test_CTracer_module_refcounts_dont_leak():
|
|
17
21
|
mod = ExampleModule()
|
|
18
|
-
|
|
22
|
+
base_count = sys.getrefcount(mod)
|
|
19
23
|
tracer = CTracer()
|
|
20
24
|
tracer.push_module(mod)
|
|
21
|
-
assert sys.getrefcount(mod) ==
|
|
25
|
+
assert sys.getrefcount(mod) == base_count + 1
|
|
22
26
|
tracer.push_module(mod)
|
|
23
27
|
tracer.start()
|
|
24
28
|
tracer.stop()
|
|
25
|
-
assert sys.getrefcount(mod) ==
|
|
29
|
+
assert sys.getrefcount(mod) == base_count + 2
|
|
26
30
|
tracer.pop_module(mod)
|
|
27
|
-
assert sys.getrefcount(mod) ==
|
|
31
|
+
assert sys.getrefcount(mod) == base_count + 1
|
|
28
32
|
del tracer
|
|
29
33
|
gc.collect()
|
|
30
|
-
assert sys.getrefcount(mod) ==
|
|
34
|
+
assert sys.getrefcount(mod) == base_count
|
|
31
35
|
|
|
32
36
|
|
|
33
37
|
def _get_depths(fn):
|
|
@@ -85,7 +89,10 @@ def _log_execution_stacks(fn, *a, **kw):
|
|
|
85
89
|
return stacks
|
|
86
90
|
|
|
87
91
|
|
|
88
|
-
@pytest.mark.skipif(
|
|
92
|
+
@pytest.mark.skipif(
|
|
93
|
+
sys.version_info < (3, 12) or sys.version_info >= (3, 14),
|
|
94
|
+
reason="stack depths only in 3.12 & 3.13",
|
|
95
|
+
)
|
|
89
96
|
def test_one_function_stack_depth():
|
|
90
97
|
_E = (TypeError, KeyboardInterrupt)
|
|
91
98
|
|
|
@@ -96,7 +103,10 @@ def test_one_function_stack_depth():
|
|
|
96
103
|
_log_execution_stacks(a, 4)
|
|
97
104
|
|
|
98
105
|
|
|
99
|
-
@pytest.mark.skipif(
|
|
106
|
+
@pytest.mark.skipif(
|
|
107
|
+
sys.version_info < (3, 12) or sys.version_info >= (3, 14),
|
|
108
|
+
reason="stack depths only in 3.12 & 3.13",
|
|
109
|
+
)
|
|
100
110
|
def test_stack_get():
|
|
101
111
|
def to_be_traced(x):
|
|
102
112
|
r = 8 - x
|
|
@@ -125,4 +135,4 @@ def test_CTracer_does_not_leak_memory():
|
|
|
125
135
|
if i == 100:
|
|
126
136
|
usage = mem_usage_kb()
|
|
127
137
|
usage_increase = mem_usage_kb() - usage
|
|
128
|
-
assert usage_increase <
|
|
138
|
+
assert usage_increase < 200
|
crosshair/auditwall.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import importlib
|
|
2
|
+
import inspect
|
|
2
3
|
import os
|
|
3
4
|
import sys
|
|
4
5
|
import traceback
|
|
@@ -28,9 +29,10 @@ def reject(event: str, args: Tuple) -> None:
|
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
def inside_module(modules: Iterable[ModuleType]) -> bool:
|
|
31
|
-
|
|
32
|
-
for frame,
|
|
33
|
-
|
|
32
|
+
"""Checks whether the current call stack is inside one of the given modules."""
|
|
33
|
+
for frame, _lineno in traceback.walk_stack(None):
|
|
34
|
+
frame_module = inspect.getmodule(frame)
|
|
35
|
+
if frame_module and frame_module in modules:
|
|
34
36
|
return True
|
|
35
37
|
return False
|
|
36
38
|
|
|
@@ -60,7 +62,7 @@ def check_msvcrt_open(event: str, args: Tuple) -> None:
|
|
|
60
62
|
_MODULES_THAT_CAN_POPEN: Optional[Set[ModuleType]] = None
|
|
61
63
|
|
|
62
64
|
|
|
63
|
-
def
|
|
65
|
+
def modules_with_allowed_subprocess():
|
|
64
66
|
global _MODULES_THAT_CAN_POPEN
|
|
65
67
|
if _MODULES_THAT_CAN_POPEN is None:
|
|
66
68
|
allowed_module_names = ("_aix_support", "ctypes", "platform", "uuid")
|
|
@@ -74,13 +76,14 @@ def modules_with_allowed_popen():
|
|
|
74
76
|
|
|
75
77
|
|
|
76
78
|
def check_subprocess(event: str, args: Tuple) -> None:
|
|
77
|
-
if not inside_module(
|
|
79
|
+
if not inside_module(modules_with_allowed_subprocess()):
|
|
78
80
|
reject(event, args)
|
|
79
81
|
|
|
80
82
|
|
|
81
83
|
_SPECIAL_HANDLERS = {
|
|
82
84
|
"open": check_open,
|
|
83
85
|
"subprocess.Popen": check_subprocess,
|
|
86
|
+
"os.posix_spawn": check_subprocess,
|
|
84
87
|
"msvcrt.open_osfhandle": check_msvcrt_open,
|
|
85
88
|
}
|
|
86
89
|
|
|
@@ -135,7 +138,6 @@ def make_handler(event: str) -> Callable[[str, Tuple], None]:
|
|
|
135
138
|
"imaplib",
|
|
136
139
|
"msvcrt",
|
|
137
140
|
"nntplib",
|
|
138
|
-
"os",
|
|
139
141
|
"pathlib",
|
|
140
142
|
"poplib",
|
|
141
143
|
"shutil",
|
|
@@ -179,8 +181,7 @@ def opened_auditwall() -> Generator:
|
|
|
179
181
|
|
|
180
182
|
def engage_auditwall() -> None:
|
|
181
183
|
sys.dont_write_bytecode = True # disable .pyc file writing
|
|
182
|
-
|
|
183
|
-
sys.addaudithook(audithook)
|
|
184
|
+
sys.addaudithook(audithook)
|
|
184
185
|
|
|
185
186
|
|
|
186
187
|
def disable_auditwall() -> None:
|
crosshair/auditwall_test.py
CHANGED
|
@@ -11,33 +11,44 @@ from crosshair.auditwall import SideEffectDetected, engage_auditwall
|
|
|
11
11
|
# audit hooks cannot be uninstalled, and we don't want to wall off the
|
|
12
12
|
# testing process. Spawn subprcoesses instead.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
pyexec = sys.executable
|
|
14
|
+
pyexec = sys.executable
|
|
16
15
|
|
|
17
|
-
def test_fs_read_allowed():
|
|
18
|
-
assert call([pyexec, __file__, "read_open", "withwall"]) != 10
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
def test_fs_read_allowed():
|
|
18
|
+
assert call([pyexec, __file__, "read_open", "withwall"]) != 10
|
|
22
19
|
|
|
23
|
-
def test_import_allowed():
|
|
24
|
-
assert call([pyexec, __file__, "import", "withwall"]) == 0
|
|
25
20
|
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
def test_scandir_allowed():
|
|
22
|
+
assert call([pyexec, __file__, "scandir", "withwall"]) == 0
|
|
28
23
|
|
|
29
|
-
def test_http_disallowed():
|
|
30
|
-
assert call([pyexec, __file__, "http", "withwall"]) == 10
|
|
31
24
|
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
def test_import_allowed():
|
|
26
|
+
assert call([pyexec, __file__, "import", "withwall"]) == 0
|
|
34
27
|
|
|
35
|
-
def test_popen_disallowed():
|
|
36
|
-
assert call([pyexec, __file__, "popen", "withwall"]) == 10
|
|
37
28
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
def test_fs_write_disallowed():
|
|
30
|
+
assert call([pyexec, __file__, "write_open", "withwall"]) == 10
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_http_disallowed():
|
|
34
|
+
assert call([pyexec, __file__, "http", "withwall"]) == 10
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_unlink_disallowed():
|
|
38
|
+
assert call([pyexec, __file__, "unlink", "withwall"]) == 10
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_popen_disallowed():
|
|
42
|
+
assert call([pyexec, __file__, "popen", "withwall"]) == 10
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_chdir_allowed():
|
|
46
|
+
assert call([pyexec, __file__, "chdir", "withwall"]) == 0
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.mark.skipif(sys.version_info < (3, 9), reason="Python 3.9+ required")
|
|
50
|
+
def test_popen_via_platform_allowed():
|
|
51
|
+
assert call([pyexec, __file__, "popen_via_platform", "withwall"]) == 0
|
|
41
52
|
|
|
42
53
|
|
|
43
54
|
_ACTIONS = {
|
|
@@ -51,6 +62,7 @@ _ACTIONS = {
|
|
|
51
62
|
"popen_via_platform": lambda: platform._syscmd_ver( # type: ignore
|
|
52
63
|
supported_platforms=(sys.platform,)
|
|
53
64
|
),
|
|
65
|
+
"chdir": lambda: os.chdir("."),
|
|
54
66
|
}
|
|
55
67
|
|
|
56
68
|
if __name__ == "__main__":
|
crosshair/codeconfig.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Configure analysis options at different levels."""
|
|
2
|
+
|
|
2
3
|
import importlib.resources
|
|
3
4
|
import inspect
|
|
4
5
|
import re
|
|
@@ -25,7 +26,7 @@ def get_directives(source_text: str) -> Iterable[Tuple[int, int, str]]:
|
|
|
25
26
|
ret = []
|
|
26
27
|
tokens = tokenize.generate_tokens(StringIO(source_text).readline)
|
|
27
28
|
# TODO catch tokenize.TokenError ... just in case?
|
|
28
|
-
for
|
|
29
|
+
for toktyp, tokval, begin, _, _ in tokens:
|
|
29
30
|
linenum, colnum = begin
|
|
30
31
|
if toktyp == tokenize.COMMENT:
|
|
31
32
|
directive = _COMMENT_TOKEN_RE.sub(r"\1", tokval)
|
|
@@ -39,7 +40,7 @@ class InvalidDirective(Exception):
|
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
def parse_directives(
|
|
42
|
-
directive_lines: Iterable[Tuple[int, int, str]]
|
|
43
|
+
directive_lines: Iterable[Tuple[int, int, str]],
|
|
43
44
|
) -> AnalysisOptionSet:
|
|
44
45
|
"""
|
|
45
46
|
Parse options from directives in comments.
|