Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove global state #35

Merged
merged 4 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/codemod_pygoat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ jobs:
- name: Run Codemodder
run: codemodder --output output.codetf pygoat
- name: Check PyGoat Findings
run: pytest -v ci_tests/test_webgoat_findings.py
run: make webgoat-test
12 changes: 5 additions & 7 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
- name: Black Format Check
run: black --check .
- name: Run pylint
run: pylint codemodder/ tests/ integration_tests/ -v
run: make lint

complexity:
name: Code Complexity
Expand All @@ -50,10 +50,8 @@ jobs:
run: |
pip install -r requirements/complexity.txt
- name: Run Radon
run: |
radon cc codemodder --min A --total-average
run: make radon
- name: Run Xenon
run: |
# threshold for pipeline to fail if we go below average, module, or block complexity
# https://github.com/rubik/xenon
xenon codemodder --max-average A --max-modules C --max-absolute C
# threshold for pipeline to fail if we go below average, module, or block complexity
# https://github.com/rubik/xenon
run: make xenon
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ jobs:
- name: Install Dependencies
run: pip install -r requirements/test.txt
- name: Run unit tests
run: pytest -v --cov-fail-under=95 --cov=codemodder tests --numprocesses auto
run: make test
- name: Run integration tests
run: pytest -v integration_tests
run: make integration-test
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
env:
Expand Down
23 changes: 23 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
PYTEST = pytest -v
COV_FLAGS = --cov-fail-under=95 --cov=codemodder
XDIST_FLAGS = --numprocesses auto

test:
${PYTEST} ${COV_FLAGS} tests ${XDIST_FLAGS}

integration-test:
${PYTEST} integration_tests

webgoat-test:
${PYTEST} -v ci_tests/test_webgoat_findings.py

lint:
pylint -v codemodder tests integration_tests

radon:
radon cc codemodder --min A --total-average

# threshold for pipeline to fail if we go below average, module, or block complexity
# https://github.com/rubik/xenon
xenon:
xenon codemodder --max-average A --max-modules C --max-absolute C
1 change: 0 additions & 1 deletion integration_tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
from tests.shared import reset_global_state # pylint: disable=unused-import
7 changes: 3 additions & 4 deletions integration_tests/semgrep/test_semgrep.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from codemodder import global_state
from codemodder.semgrep import run as semgrep_run
from codemodder.codemods import SecureRandom, UrlSandbox

Expand Down Expand Up @@ -32,10 +31,10 @@ def _assert_secure_random_results(self, results):
assert location["region"]["endLine"] == 3
assert location["region"]["snippet"]["text"] == "random.random()"

def test_two_codemods(self):
global_state.set_directory("tests/samples/")
def test_two_codemods(self, mocker):
context = mocker.MagicMock(directory="tests/samples")
results_by_path_and_id = semgrep_run(
{"secure-random": SecureRandom, "url-sandbox": UrlSandbox}
context, {"secure-random": SecureRandom, "url-sandbox": UrlSandbox}
)

assert sorted(results_by_path_and_id.keys()) == [
Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ codemodder = "codemodder.__main__:main"

[tool.setuptools.package-data]
"codemodder.codemods.semgrep" = ["src/codemodder/codemods/semgrep/*.yaml"]

[tool.pytest.ini_options]
# Ignore integration tests and ci tests by default
testpaths = ["tests"]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could include integration tests here if we want but mainly I want to avoid running the webgoat tests by accident.

1 change: 1 addition & 0 deletions requirements/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ mock==5.1.*
pre-commit<4
pytest==7.4.*
pytest-cov~=4.1.0
pytest-mock~=3.11.0
pytest-xdist==3.*
types-mock==5.1.*
15 changes: 5 additions & 10 deletions src/codemodder/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,11 @@
from codemodder.code_directory import file_line_patterns, match_files
from codemodder.codemods import match_codemods
from codemodder.context import CodemodExecutionContext, ChangeSet
from codemodder.dependency_manager import DependencyManager
from codemodder.dependency_manager import write_dependencies
from codemodder.report.codetf_reporter import report_default
from codemodder.semgrep import run as semgrep_run
from codemodder.sarifs import parse_sarif_files

# Must use from import here to point to latest state
from codemodder import global_state # TODO: should not use global state


def update_code(file_path, new_code):
"""
Expand All @@ -40,8 +37,7 @@ def run_codemods_for_file(
wrapper = cst.MetadataWrapper(source_tree)
codemod = codemod_kls(
CodemodContext(wrapper=wrapper),
# TODO: eventually pass execution context here
# It will be used for things like dependency management
execution_context,
file_context,
)
if not codemod.should_transform:
Expand Down Expand Up @@ -140,7 +136,6 @@ def run(argv, original_args) -> int:
# project directory doesn't exist or can’t be read
return 1

global_state.set_directory(Path(argv.directory))
context = CodemodExecutionContext(Path(argv.directory), argv.dry_run)

codemods_to_run = match_codemods(argv.codemod_include, argv.codemod_exclude)
Expand All @@ -155,7 +150,7 @@ def run(argv, original_args) -> int:
sarif_results = parse_sarif_files(argv.sarif or [])

# run semgrep and gather the results
semgrep_results = semgrep_run(codemods_to_run)
semgrep_results = semgrep_run(context, codemods_to_run)

# merge the results
sarif_results.update(semgrep_results)
Expand All @@ -164,7 +159,7 @@ def run(argv, original_args) -> int:
logger.warning("No sarif results.")

files_to_analyze = match_files(
global_state.DIRECTORY, argv.path_exclude, argv.path_include
context.directory, argv.path_exclude, argv.path_include
)
if not files_to_analyze:
logger.warning("No files matched.")
Expand All @@ -183,7 +178,7 @@ def run(argv, original_args) -> int:

results = compile_results(context, codemods_to_run)

DependencyManager().write(dry_run=context.dry_run)
write_dependencies(context)
elapsed = datetime.datetime.now() - start
elapsed_ms = int(elapsed.total_seconds() * 1000)
report_default(elapsed_ms, argv, original_args, results)
Expand Down
File renamed without changes.
20 changes: 10 additions & 10 deletions src/codemodder/codemods/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
)

from codemodder.codemods.base_visitor import BaseTransformer
from codemodder.codemods.change import Change
from codemodder.change import Change
from codemodder.context import CodemodExecutionContext
from codemodder.file_context import FileContext
from codemodder.semgrep import rule_ids_from_yaml_files
from .helpers import Helpers
Expand Down Expand Up @@ -100,15 +101,14 @@ def __init_subclass__(cls):
cls.YAML_FILES = _create_temp_yaml_file(cls, cls.METADATA)
cls.RULE_IDS = rule_ids_from_yaml_files(cls.YAML_FILES)

def __init__(self, codemod_context: CodemodContext, file_context: FileContext):
_SemgrepCodemod.__init__(self, file_context)
BaseTransformer.__init__(
self,
codemod_context,
self._results,
file_context.line_exclude,
file_context.line_include,
)
def __init__(
self,
codemod_context: CodemodContext,
execution_context: CodemodExecutionContext,
file_context: FileContext,
):
_SemgrepCodemod.__init__(self, execution_context, file_context)
BaseTransformer.__init__(self, codemod_context, self._results)

# TODO: there needs to be a way to generalize this so that it applies
# more broadly than to just a specific kind of node. There's probably a
Expand Down
13 changes: 12 additions & 1 deletion src/codemodder/codemods/base_codemod.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import itertools
from typing import List, ClassVar

from codemodder.context import CodemodExecutionContext
from codemodder.file_context import FileContext
from codemodder.semgrep import rule_ids_from_yaml_files

Expand All @@ -26,6 +27,7 @@ class BaseCodemod:
SUMMARY: ClassVar[str] = NotImplemented
IS_SEMGREP = False

execution_context: CodemodExecutionContext
file_context: FileContext

def __init_subclass__(cls, **kwargs):
Expand All @@ -47,7 +49,8 @@ def __init_subclass__(cls, **kwargs):
if not v:
raise NotImplementedError(f"METADATA.{k} should not be None or empty")

def __init__(self, file_context):
def __init__(self, execution_context: CodemodExecutionContext, file_context):
self.execution_context = execution_context
self.file_context = file_context

@classmethod
Expand All @@ -72,6 +75,14 @@ def node_position(self, node):
def lineno_for_node(self, node):
return self.node_position(node).start.line

@property
def line_exclude(self):
return self.file_context.line_exclude

@property
def line_include(self):
return self.file_context.line_include


class SemgrepCodemod(BaseCodemod):
YAML_FILES: ClassVar[List[str]] = NotImplemented
Expand Down
4 changes: 1 addition & 3 deletions src/codemodder/codemods/base_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@
class UtilsMixin:
METADATA_DEPENDENCIES: Tuple[Any, ...] = (PositionProvider,)

def __init__(self, context, results, line_exclude=None, line_include=None):
def __init__(self, context, results):
super().__init__(context)
self.results = results
self.line_exclude = line_exclude or []
self.line_include = line_include or []

def filter_by_result(self, pos_to_match):
all_pos = [extract_pos_from_result(result) for result in self.results]
Expand Down
15 changes: 6 additions & 9 deletions src/codemodder/codemods/django_debug_flag_on.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import libcst as cst
from libcst.codemod import Codemod, CodemodContext
from libcst.metadata import PositionProvider
from codemodder.change import Change
from codemodder.codemods.base_visitor import BaseTransformer
from codemodder.codemods.change import Change
from codemodder.codemods.base_codemod import (
SemgrepCodemod,
CodemodMetadata,
Expand All @@ -26,9 +26,9 @@ class DjangoDebugFlagOn(SemgrepCodemod, Codemod):

METADATA_DEPENDENCIES = (PositionProvider,)

def __init__(self, codemod_context: CodemodContext, file_context: FileContext):
def __init__(self, codemod_context: CodemodContext, *args):
Codemod.__init__(self, codemod_context)
SemgrepCodemod.__init__(self, file_context)
SemgrepCodemod.__init__(self, *args)

def transform_module_impl(self, tree: cst.Module) -> cst.Module:
# checks if the file we looking is a settings.py file from django's default directory structure
Expand All @@ -49,12 +49,9 @@ class DebugFlagTransformer(BaseTransformer):
def __init__(
self, codemod_context: CodemodContext, file_context: FileContext, results
):
super().__init__(
codemod_context,
results,
file_context.line_exclude,
file_context.line_include,
)
super().__init__(codemod_context, results)
self.line_exclude = file_context.line_exclude
self.line_include = file_context.line_include
self.changes_in_file: List[Change] = []

def leave_Assign(
Expand Down
15 changes: 6 additions & 9 deletions src/codemodder/codemods/django_session_cookie_secure_off.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import libcst as cst
from libcst.codemod import Codemod, CodemodContext
from libcst.metadata import PositionProvider
from codemodder.change import Change
from codemodder.codemods.base_visitor import BaseTransformer
from codemodder.codemods.change import Change
from codemodder.codemods.base_codemod import (
SemgrepCodemod,
CodemodMetadata,
Expand All @@ -27,9 +27,9 @@ class DjangoSessionCookieSecureOff(SemgrepCodemod, Codemod):

METADATA_DEPENDENCIES = (PositionProvider,)

def __init__(self, codemod_context: CodemodContext, file_context: FileContext):
def __init__(self, codemod_context: CodemodContext, *args):
Codemod.__init__(self, codemod_context)
SemgrepCodemod.__init__(self, file_context)
SemgrepCodemod.__init__(self, *args)

def transform_module_impl(self, tree: cst.Module) -> cst.Module:
if is_django_settings_file(self.file_context.file_path):
Expand All @@ -47,12 +47,9 @@ class SessionCookieSecureTransformer(BaseTransformer):
def __init__(
self, codemod_context: CodemodContext, file_context: FileContext, results
):
super().__init__(
codemod_context,
results,
file_context.line_exclude,
file_context.line_include,
)
super().__init__(codemod_context, results)
self.line_exclude = file_context.line_exclude
self.line_include = file_context.line_include
self.changes_in_file: List[Change] = []
self.flag_correctly_set = False

Expand Down
8 changes: 3 additions & 5 deletions src/codemodder/codemods/https_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
CodemodMetadata,
ReviewGuidance,
)
from codemodder.codemods.change import Change
from codemodder.change import Change
from codemodder.codemods.utils_mixin import NameResolutionMixin
from codemodder.file_context import FileContext
import libcst as cst
Expand All @@ -36,11 +36,9 @@ class HTTPSConnection(BaseCodemod, Codemod):
"urllib3.connectionpool.HTTPConnectionPool",
}

def __init__(self, codemod_context: CodemodContext, file_context: FileContext):
def __init__(self, codemod_context: CodemodContext, *codemod_args):
Codemod.__init__(self, codemod_context)
BaseCodemod.__init__(self, file_context)
self.line_exclude = file_context.line_exclude
self.line_include = file_context.line_include
BaseCodemod.__init__(self, *codemod_args)

def transform_module_impl(self, tree: cst.Module) -> cst.Module:
visitor = ConnectionPollVisitor(self.context, self.file_context)
Expand Down
12 changes: 4 additions & 8 deletions src/codemodder/codemods/order_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@
CodemodMetadata,
ReviewGuidance,
)
from codemodder.codemods.change import Change
from codemodder.change import Change
from codemodder.codemods.transformations.clean_imports import (
GatherTopLevelImportBlocks,
OrderImportsBlocksTransform,
)
from codemodder.file_context import FileContext
import libcst as cst
from libcst.codemod import Codemod, CodemodContext
import codemodder.global_state


class OrderImports(BaseCodemod, Codemod):
Expand All @@ -26,11 +24,9 @@ class OrderImports(BaseCodemod, Codemod):

METADATA_DEPENDENCIES = (PositionProvider,)

def __init__(self, codemod_context: CodemodContext, file_context: FileContext):
def __init__(self, codemod_context: CodemodContext, *codemod_args):
Codemod.__init__(self, codemod_context)
BaseCodemod.__init__(self, file_context)
self.line_exclude = file_context.line_exclude
self.line_include = file_context.line_include
BaseCodemod.__init__(self, *codemod_args)

def transform_module_impl(self, tree: cst.Module) -> cst.Module:
top_imports_visitor = GatherTopLevelImportBlocks()
Expand All @@ -45,7 +41,7 @@ def transform_module_impl(self, tree: cst.Module) -> cst.Module:
filtered_blocks.append(block)
if filtered_blocks:
order_transformer = OrderImportsBlocksTransform(
codemodder.global_state.DIRECTORY, filtered_blocks
self.execution_context.directory, filtered_blocks
)
result_tree = tree.visit(order_transformer)

Expand Down
Loading