Skip to content

Commit 885ae4e

Browse files
Merge pull request #15 from PurCL/feat/github-action
Support Github Action using Black and mypy
2 parents 16ba102 + 5c8b5a6 commit 885ae4e

29 files changed

Lines changed: 343 additions & 375 deletions

.github/workflows/black.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: "Black – code-style check"
2+
3+
on:
4+
pull_request:
5+
paths: ["**/*.py"]
6+
push:
7+
branches: [main]
8+
paths: ["**/*.py"]
9+
10+
jobs:
11+
black:
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
# ---------- pip wheel cache ----------
18+
- name: Cache pip
19+
uses: actions/cache@v4
20+
with:
21+
path: ~/.cache/pip
22+
key: ${{ runner.os }}-black-pip-${{ hashFiles('pyproject.toml') }}
23+
restore-keys: |
24+
${{ runner.os }}-black-pip-
25+
26+
# ---------- Black cache (formatting state) ----------
27+
- name: Cache Black .cache
28+
uses: actions/cache@v4
29+
with:
30+
path: .cache/black
31+
key: ${{ runner.os }}-black-${{ github.sha }}
32+
restore-keys: |
33+
${{ runner.os }}-black-
34+
35+
- name: Set up Python
36+
uses: actions/setup-python@v5
37+
with:
38+
python-version: "3.13"
39+
40+
- name: Install Black (pinned)
41+
run: |
42+
python -m pip install --upgrade pip
43+
pip install black==24.10.0
44+
45+
- name: Run Black in check mode
46+
run: black --check --diff src

.github/workflows/mypy.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: "mypy – static type checks"
2+
3+
on:
4+
pull_request:
5+
paths: ["**/*.py"]
6+
push:
7+
branches: [main]
8+
paths: ["**/*.py"]
9+
10+
jobs:
11+
mypy:
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
# ---------- pip wheel cache ----------
18+
- name: Cache pip
19+
uses: actions/cache@v4
20+
with:
21+
path: ~/.cache/pip
22+
key: ${{ runner.os }}-py${{ matrix.python-version }}-pip-${{ hashFiles('requirements.txt') }}
23+
restore-keys: |
24+
${{ runner.os }}-py${{ matrix.python-version }}-pip-
25+
26+
- name: Set up Python
27+
uses: actions/setup-python@v5
28+
with:
29+
python-version: "3.13"
30+
31+
- name: Install deps + mypy
32+
run: |
33+
python -m pip install --upgrade pip
34+
pip install -r requirements.txt
35+
36+
# ---------- mypy incremental cache ----------
37+
- name: Cache mypy .mypy_cache
38+
uses: actions/cache@v4
39+
with:
40+
path: .mypy_cache
41+
key: ${{ runner.os }}-mypy-${{ github.sha }}
42+
restore-keys: |
43+
${{ runner.os }}-mypy-
44+
45+
- name: Type‑check
46+
run: |
47+
mypy src

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ We are keeping implementing more agents and will open-source them very soon. Uti
2727

2828
## Installation
2929

30-
1. Create and activate a conda environment with Python 3.9.18:
30+
1. Create and activate a conda environment with Python 3.13:
3131

3232
```sh
33-
conda create -n repoaudit python=3.9.18
33+
conda create -n repoaudit python=3.13
3434
conda activate repoaudit
3535
```
3636

pyproject.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[tool.black]
2+
line-length = 88 # keep the default PEP‑8‑plus setting
3+
target-version = ["py39"] # ensures Python‑3.9 compatible formatting
4+
skip-string-normalization = false
5+
include = '\.pyi?$'
6+
7+
exclude = '''
8+
/(
9+
\.git
10+
| \.mypy_cache
11+
| \.pytest_cache
12+
| \.venv
13+
| build
14+
| dist
15+
)/
16+
'''

requirements.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,8 @@ streamlit
1212
botocore
1313
boto3
1414
black
15-
anthropic
15+
anthropic
16+
mypy
17+
types-networkx
18+
types-tqdm
19+
boto3-stubs[essential]

src/__init__.py

Whitespace-only changes.

src/agent/dfbscan.py

Lines changed: 66 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@
3737
class DFBScanAgent(Agent):
3838
def __init__(
3939
self,
40-
bug_type,
41-
is_reachable,
42-
project_path,
43-
language,
44-
ts_analyzer,
45-
model_name,
46-
temperature,
47-
call_depth,
48-
max_neural_workers=30,
40+
bug_type: str,
41+
is_reachable: bool,
42+
project_path: str,
43+
language: str,
44+
ts_analyzer: TSAnalyzer,
45+
model_name: str,
46+
temperature: float,
47+
call_depth: int,
48+
max_neural_workers: int = 30,
4949
agent_id: int = 0,
5050
) -> None:
5151
self.bug_type = bug_type
@@ -112,7 +112,9 @@ def __obtain_extractor(self) -> DFBScanExtractor:
112112
elif self.language == "Go":
113113
if self.bug_type == "NPD":
114114
return Go_NPD_Extractor(self.ts_analyzer)
115-
return None
115+
raise NotImplementedError(
116+
f"Unsupported bug type: {self.bug_type} in {self.language}"
117+
)
116118

117119
def __update_worklist(
118120
self,
@@ -174,21 +176,23 @@ def __update_worklist(
174176
if not is_CFL_reachable:
175177
continue
176178

177-
for para in callee_function.paras:
178-
if para.index == value.index:
179-
delta_worklist.append(
180-
(para, callee_function, new_call_context)
181-
)
182-
self.state.update_external_value_match(
183-
(value, call_context), set({(para, new_call_context)})
184-
)
179+
if callee_function.paras is not None:
180+
for para in callee_function.paras:
181+
if para.index == value.index:
182+
delta_worklist.append(
183+
(para, callee_function, new_call_context)
184+
)
185+
self.state.update_external_value_match(
186+
(value, call_context),
187+
set({(para, new_call_context)}),
188+
)
185189

186190
if value.label == ValueLabel.PARA:
187191
# Consider side-effect.
188192
# Example: the parameter *p is used in the function: p->f = null;
189193
# We need to consider the side-effect of p.
190-
caller_function = self.ts_analyzer.get_all_caller_functions(function)
191-
for caller_function in caller_function:
194+
caller_functions = self.ts_analyzer.get_all_caller_functions(function)
195+
for caller_function in caller_functions:
192196
new_call_context = copy.deepcopy(call_context)
193197
top_unmatched_context_label = (
194198
new_call_context.get_top_unmatched_context_label()
@@ -442,9 +446,13 @@ def start_scan_sequential(self) -> None:
442446
ret.name,
443447
ret.line_number - start_function.start_line_number + 1,
444448
)
445-
for ret in start_function.retvals
449+
for ret in (
450+
start_function.retvals
451+
if start_function.retvals is not None
452+
else []
453+
)
446454
]
447-
input = IntraDataFlowAnalyzerInput(
455+
df_input = IntraDataFlowAnalyzerInput(
448456
start_function,
449457
start_value,
450458
sink_values,
@@ -453,20 +461,22 @@ def start_scan_sequential(self) -> None:
453461
)
454462

455463
# Invoke the intra-procedural data-flow analysis
456-
output = self.intra_dfa.invoke(input)
457-
if output is None:
464+
df_output = self.intra_dfa.invoke(
465+
df_input, IntraDataFlowAnalyzerOutput
466+
)
467+
if df_output is None:
458468
continue
459469

460-
for path_index in range(len(output.reachable_values)):
470+
for path_index in range(len(df_output.reachable_values)):
461471
reachable_values_in_single_path = set([])
462-
for value in output.reachable_values[path_index]:
472+
for value in df_output.reachable_values[path_index]:
463473
reachable_values_in_single_path.add((value, call_context))
464474
self.state.update_reachable_values_per_path(
465475
(start_value, call_context), reachable_values_in_single_path
466476
)
467477

468478
delta_worklist = self.__update_worklist(
469-
input, output, call_context, path_index
479+
df_input, df_output, call_context, path_index
470480
)
471481
worklist.extend(delta_worklist)
472482

@@ -479,20 +489,22 @@ def start_scan_sequential(self) -> None:
479489
continue
480490

481491
for buggy_path in self.state.potential_buggy_paths[src_value].values():
482-
input = PathValidatorInput(
492+
pv_input = PathValidatorInput(
483493
self.bug_type,
484494
buggy_path,
485495
{
486496
value: self.ts_analyzer.get_function_from_localvalue(value)
487497
for value in buggy_path
488498
},
489499
)
490-
output: PathValidatorOutput = self.path_validator.invoke(input)
500+
pv_output = self.path_validator.invoke(
501+
pv_input, PathValidatorOutput
502+
)
491503

492-
if output is None:
504+
if pv_output is None:
493505
continue
494506

495-
if output.is_reachable:
507+
if pv_output.is_reachable:
496508
relevant_functions = {}
497509
for value in buggy_path:
498510
function = self.ts_analyzer.get_function_from_localvalue(
@@ -505,7 +517,7 @@ def start_scan_sequential(self) -> None:
505517
self.bug_type,
506518
src_value,
507519
relevant_functions,
508-
output.explanation_str,
520+
pv_output.explanation_str,
509521
)
510522
self.state.update_bug_report(bug_report)
511523

@@ -606,28 +618,30 @@ def __process_src_value(self, src_value: Value) -> None:
606618

607619
ret_values = [
608620
(ret.name, ret.line_number - start_function.start_line_number + 1)
609-
for ret in start_function.retvals
621+
for ret in (
622+
start_function.retvals if start_function.retvals is not None else []
623+
)
610624
]
611-
input = IntraDataFlowAnalyzerInput(
625+
df_input = IntraDataFlowAnalyzerInput(
612626
start_function, start_value, sink_values, call_statements, ret_values
613627
)
614628

615629
# Invoke the intra-procedural data-flow analysis
616-
output = self.intra_dfa.invoke(input)
630+
df_output = self.intra_dfa.invoke(df_input, IntraDataFlowAnalyzerOutput)
617631

618-
if output is None:
632+
if df_output is None:
619633
continue
620634

621-
for path_index in range(len(output.reachable_values)):
635+
for path_index in range(len(df_output.reachable_values)):
622636
reachable_values_in_single_path = set([])
623-
for value in output.reachable_values[path_index]:
637+
for value in df_output.reachable_values[path_index]:
624638
reachable_values_in_single_path.add((value, call_context))
625639
self.state.update_reachable_values_per_path(
626640
(start_value, call_context), reachable_values_in_single_path
627641
)
628642

629643
delta_worklist = self.__update_worklist(
630-
input, output, call_context, path_index
644+
df_input, df_output, call_context, path_index
631645
)
632646
worklist.extend(delta_worklist)
633647

@@ -645,30 +659,36 @@ def __process_src_value(self, src_value: Value) -> None:
645659
for value in buggy_path
646660
}
647661

648-
relevant_functions = values_to_functions.values()
662+
functions: Set[Function] = set()
663+
for func in values_to_functions.values():
664+
if func is not None:
665+
functions.add(func)
649666

650-
if self.state.check_existence(src_value, relevant_functions):
667+
if self.state.check_existence(src_value, functions):
651668
continue
652669

653-
input = PathValidatorInput(
670+
pv_input = PathValidatorInput(
654671
self.bug_type,
655672
buggy_path,
656673
values_to_functions,
657674
)
658-
output: PathValidatorOutput = self.path_validator.invoke(input)
675+
pv_output = self.path_validator.invoke(pv_input, PathValidatorOutput)
659676

660-
if output is None:
677+
if pv_output is None:
661678
continue
662679

663-
if output.is_reachable:
680+
if pv_output.is_reachable:
664681
relevant_functions = {}
665682
for value in buggy_path:
666683
function = self.ts_analyzer.get_function_from_localvalue(value)
667684
if function is not None:
668685
relevant_functions[function.function_id] = function
669686

670687
bug_report = BugReport(
671-
self.bug_type, src_value, relevant_functions, output.explanation_str
688+
self.bug_type,
689+
src_value,
690+
relevant_functions,
691+
pv_output.explanation_str,
672692
)
673693
self.state.update_bug_report(bug_report)
674694
bug_report_dict = {

0 commit comments

Comments
 (0)