mirror of
https://github.com/bikini/patchwork
synced 2026-06-27 08:08:41 +00:00
Add 0.8 normal hardening transforms
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from .identifiers import IdentifierRenamer, collect_skip_names
|
||||
from .strings import StringEncryptor, build_decrypt_helper
|
||||
from .numbers import NumberObfuscator
|
||||
from .opaque import OpaquePredicateInjector
|
||||
__all__ = ['IdentifierRenamer', 'collect_skip_names', 'StringEncryptor', 'build_decrypt_helper', 'NumberObfuscator', 'OpaquePredicateInjector']
|
||||
from .identifiers import IdentifierRenamer, collect_skip_names
|
||||
from .strings import StringEncryptor, build_decrypt_helper
|
||||
from .numbers import NumberObfuscator
|
||||
from .opaque import OpaquePredicateInjector
|
||||
from .normalize import FStringLowerer, MatchLowerer
|
||||
__all__ = ['IdentifierRenamer', 'collect_skip_names', 'StringEncryptor', 'build_decrypt_helper', 'NumberObfuscator', 'OpaquePredicateInjector', 'FStringLowerer', 'MatchLowerer']
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
|
||||
|
||||
class FStringLowerer(ast.NodeTransformer):
|
||||
def visit_JoinedStr(self, node: ast.JoinedStr) -> ast.AST:
|
||||
parts: list[ast.expr] = []
|
||||
for value in node.values:
|
||||
if isinstance(value, ast.Constant) and isinstance(value.value, str):
|
||||
if value.value:
|
||||
parts.append(ast.Constant(value=value.value))
|
||||
elif isinstance(value, ast.FormattedValue):
|
||||
parts.append(self._format_value(value))
|
||||
else:
|
||||
return self.generic_visit(node)
|
||||
if not parts:
|
||||
return ast.Constant(value="")
|
||||
return ast.Call(
|
||||
func=ast.Attribute(value=ast.Constant(value=""), attr="join", ctx=ast.Load()),
|
||||
args=[ast.List(elts=parts, ctx=ast.Load())],
|
||||
keywords=[],
|
||||
)
|
||||
|
||||
def _format_value(self, node: ast.FormattedValue) -> ast.expr:
|
||||
value = self.visit(node.value)
|
||||
if node.conversion == 115:
|
||||
value = ast.Call(func=ast.Name(id="str", ctx=ast.Load()), args=[value], keywords=[])
|
||||
elif node.conversion == 114:
|
||||
value = ast.Call(func=ast.Name(id="repr", ctx=ast.Load()), args=[value], keywords=[])
|
||||
elif node.conversion == 97:
|
||||
value = ast.Call(func=ast.Name(id="ascii", ctx=ast.Load()), args=[value], keywords=[])
|
||||
elif node.conversion != -1:
|
||||
return ast.FormattedValue(value=value, conversion=node.conversion, format_spec=node.format_spec)
|
||||
if node.format_spec is None:
|
||||
spec: ast.expr = ast.Constant(value="")
|
||||
else:
|
||||
spec_node = self.visit(node.format_spec)
|
||||
if not isinstance(spec_node, ast.expr):
|
||||
return ast.FormattedValue(value=value, conversion=-1, format_spec=node.format_spec)
|
||||
spec = spec_node
|
||||
return ast.Call(func=ast.Name(id="format", ctx=ast.Load()), args=[value, spec], keywords=[])
|
||||
|
||||
|
||||
class MatchLowerer(ast.NodeTransformer):
|
||||
def __init__(self) -> None:
|
||||
self._counter = 0
|
||||
|
||||
def visit_Match(self, node: ast.Match) -> ast.AST:
|
||||
self.generic_visit(node)
|
||||
if any(case.guard is not None for case in node.cases):
|
||||
return node
|
||||
subject_name = self._name()
|
||||
subject_store = ast.Assign(targets=[ast.Name(id=subject_name, ctx=ast.Store())], value=node.subject)
|
||||
chain = self._case_chain(subject_name, node.cases)
|
||||
if chain is None:
|
||||
return node
|
||||
return [subject_store, chain]
|
||||
|
||||
def _name(self) -> str:
|
||||
self._counter += 1
|
||||
return f"_pw_match_{self._counter}"
|
||||
|
||||
def _case_chain(self, subject_name: str, cases: list[ast.match_case]) -> ast.stmt | None:
|
||||
next_stmt: ast.stmt | None = None
|
||||
for case in reversed(cases):
|
||||
lowered = self._case_if(subject_name, case, [] if next_stmt is None else [next_stmt])
|
||||
if lowered is None:
|
||||
return None
|
||||
next_stmt = lowered
|
||||
return next_stmt
|
||||
|
||||
def _case_if(self, subject_name: str, case: ast.match_case, orelse: list[ast.stmt]) -> ast.stmt | None:
|
||||
result = self._pattern(subject_name, case.pattern)
|
||||
if result is None:
|
||||
return None
|
||||
test, binders = result
|
||||
if case.guard is not None:
|
||||
test = ast.BoolOp(op=ast.And(), values=[test, case.guard])
|
||||
return ast.If(test=test, body=[*binders, *case.body], orelse=orelse)
|
||||
|
||||
def _pattern(self, subject_name: str, pattern: ast.pattern) -> tuple[ast.expr, list[ast.stmt]] | None:
|
||||
subject = ast.Name(id=subject_name, ctx=ast.Load())
|
||||
if isinstance(pattern, ast.MatchAs) and pattern.pattern is None:
|
||||
if pattern.name is None:
|
||||
return ast.Constant(value=True), []
|
||||
return ast.Constant(value=True), [self._bind(pattern.name, subject)]
|
||||
if isinstance(pattern, ast.MatchValue):
|
||||
return ast.Compare(left=subject, ops=[ast.Eq()], comparators=[pattern.value]), []
|
||||
if isinstance(pattern, ast.MatchSingleton):
|
||||
return ast.Compare(left=subject, ops=[ast.Is()], comparators=[ast.Constant(value=pattern.value)]), []
|
||||
if isinstance(pattern, ast.MatchSequence):
|
||||
return self._sequence_pattern(subject_name, pattern)
|
||||
return None
|
||||
|
||||
def _sequence_pattern(self, subject_name: str, pattern: ast.MatchSequence) -> tuple[ast.expr, list[ast.stmt]] | None:
|
||||
patterns = pattern.patterns
|
||||
star_index = next((index for index, item in enumerate(patterns) if isinstance(item, ast.MatchStar)), None)
|
||||
tests: list[ast.expr] = [self._sequence_test(subject_name)]
|
||||
binders: list[ast.stmt] = []
|
||||
if star_index is None:
|
||||
tests.append(self._len_compare(subject_name, ast.Eq(), len(patterns)))
|
||||
else:
|
||||
tests.append(self._len_compare(subject_name, ast.GtE(), len(patterns) - 1))
|
||||
for index, item in enumerate(patterns):
|
||||
if isinstance(item, ast.MatchAs):
|
||||
value = self._index(subject_name, index, star_index, len(patterns))
|
||||
if item.pattern is not None:
|
||||
return None
|
||||
if item.name is not None:
|
||||
binders.append(self._bind(item.name, value))
|
||||
elif isinstance(item, ast.MatchStar):
|
||||
if item.name is not None:
|
||||
binders.append(self._bind(item.name, self._star_slice(subject_name, index, len(patterns))))
|
||||
else:
|
||||
value = self._index(subject_name, index, star_index, len(patterns))
|
||||
sub_name = self._name()
|
||||
binders.append(ast.Assign(targets=[ast.Name(id=sub_name, ctx=ast.Store())], value=value))
|
||||
sub_result = self._pattern(sub_name, item)
|
||||
if sub_result is None:
|
||||
return None
|
||||
sub_test, sub_binders = sub_result
|
||||
tests.append(sub_test)
|
||||
binders.extend(sub_binders)
|
||||
return ast.BoolOp(op=ast.And(), values=tests), binders
|
||||
|
||||
def _len_compare(self, subject_name: str, op: ast.cmpop, length: int) -> ast.expr:
|
||||
return ast.Compare(
|
||||
left=ast.Call(func=ast.Name(id="len", ctx=ast.Load()), args=[ast.Name(id=subject_name, ctx=ast.Load())], keywords=[]),
|
||||
ops=[op],
|
||||
comparators=[ast.Constant(value=length)],
|
||||
)
|
||||
|
||||
def _sequence_test(self, subject_name: str) -> ast.expr:
|
||||
subject = ast.Name(id=subject_name, ctx=ast.Load())
|
||||
sequence_type = ast.Attribute(
|
||||
value=ast.Attribute(
|
||||
value=ast.Call(func=ast.Name(id="__import__", ctx=ast.Load()), args=[ast.Constant(value="collections.abc")], keywords=[]),
|
||||
attr="abc",
|
||||
ctx=ast.Load(),
|
||||
),
|
||||
attr="Sequence",
|
||||
ctx=ast.Load(),
|
||||
)
|
||||
return ast.BoolOp(
|
||||
op=ast.And(),
|
||||
values=[
|
||||
ast.Call(func=ast.Name(id="isinstance", ctx=ast.Load()), args=[subject, sequence_type], keywords=[]),
|
||||
ast.UnaryOp(
|
||||
op=ast.Not(),
|
||||
operand=ast.Call(
|
||||
func=ast.Name(id="isinstance", ctx=ast.Load()),
|
||||
args=[
|
||||
subject,
|
||||
ast.Tuple(
|
||||
elts=[
|
||||
ast.Name(id="str", ctx=ast.Load()),
|
||||
ast.Name(id="bytes", ctx=ast.Load()),
|
||||
ast.Name(id="bytearray", ctx=ast.Load()),
|
||||
],
|
||||
ctx=ast.Load(),
|
||||
),
|
||||
],
|
||||
keywords=[],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
def _index(self, subject_name: str, index: int, star_index: int | None, total: int) -> ast.expr:
|
||||
if star_index is not None and index > star_index:
|
||||
index = index - total
|
||||
return ast.Subscript(value=ast.Name(id=subject_name, ctx=ast.Load()), slice=ast.Constant(value=index), ctx=ast.Load())
|
||||
|
||||
def _star_slice(self, subject_name: str, index: int, total: int) -> ast.expr:
|
||||
stop = index - total + 1
|
||||
upper: ast.expr | None = None if stop == 0 else ast.Constant(value=stop)
|
||||
return ast.Subscript(
|
||||
value=ast.Call(func=ast.Name(id="list", ctx=ast.Load()), args=[ast.Name(id=subject_name, ctx=ast.Load())], keywords=[]),
|
||||
slice=ast.Slice(lower=ast.Constant(value=index), upper=upper, step=None),
|
||||
ctx=ast.Load(),
|
||||
)
|
||||
|
||||
def _bind(self, name: str, value: ast.expr) -> ast.stmt:
|
||||
return ast.Assign(targets=[ast.Name(id=name, ctx=ast.Store())], value=value)
|
||||
Reference in New Issue
Block a user