frame-security 1.0.0__py3-none-any.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.
- frame/__init__.py +61 -0
- frame/__main__.py +17 -0
- frame/analysis/__init__.py +14 -0
- frame/analysis/footprint.py +334 -0
- frame/analysis/formula.py +457 -0
- frame/analysis/predicate_matching.py +309 -0
- frame/analysis/unification.py +339 -0
- frame/arithmetic/__init__.py +9 -0
- frame/arithmetic/check.py +213 -0
- frame/arithmetic/synth.py +294 -0
- frame/checking/__init__.py +9 -0
- frame/checking/_checker_core.py +532 -0
- frame/checking/_disjunct_handling.py +78 -0
- frame/checking/_formula_helpers.py +456 -0
- frame/checking/_frame_inference.py +299 -0
- frame/checking/_ls_heuristics.py +262 -0
- frame/checking/_satisfiability_check.py +152 -0
- frame/checking/_unification_matching.py +145 -0
- frame/checking/checker.py +176 -0
- frame/checking/footprint_check.py +977 -0
- frame/checking/frame_inference.py +417 -0
- frame/checking/heuristics.py +264 -0
- frame/checking/incorrectness.py +484 -0
- frame/checking/s2s_normalized.py +373 -0
- frame/cli.py +1309 -0
- frame/core/__init__.py +10 -0
- frame/core/_ast_arrays_bitvecs.py +302 -0
- frame/core/_ast_base.py +44 -0
- frame/core/_ast_errors.py +137 -0
- frame/core/_ast_expressions.py +78 -0
- frame/core/_ast_pure.py +189 -0
- frame/core/_ast_quantifiers.py +66 -0
- frame/core/_ast_security.py +94 -0
- frame/core/_ast_spatial.py +144 -0
- frame/core/_ast_strings.py +150 -0
- frame/core/_lexer.py +131 -0
- frame/core/_parser_helpers.py +434 -0
- frame/core/ast.py +97 -0
- frame/core/parser.py +487 -0
- frame/encoding/__init__.py +8 -0
- frame/encoding/_bitvec.py +164 -0
- frame/encoding/_pure.py +279 -0
- frame/encoding/_pure_core.py +544 -0
- frame/encoding/_spatial.py +105 -0
- frame/encoding/_spatial_core.py +562 -0
- frame/encoding/_string.py +70 -0
- frame/encoding/_wand.py +426 -0
- frame/encoding/_wand_sat_encoding.py +356 -0
- frame/encoding/_wand_utils.py +221 -0
- frame/encoding/debug_dump.py +37 -0
- frame/encoding/encoder.py +374 -0
- frame/encoding/regex_parser.py +263 -0
- frame/folding/__init__.py +29 -0
- frame/folding/_goal_directed_helpers.py +236 -0
- frame/folding/apply.py +255 -0
- frame/folding/blind.py +272 -0
- frame/folding/bud.py +197 -0
- frame/folding/cyclic_unfold.py +413 -0
- frame/folding/goal_directed.py +603 -0
- frame/folding/hierarchical.py +172 -0
- frame/folding/utils.py +83 -0
- frame/folding/verify.py +699 -0
- frame/heap/__init__.py +11 -0
- frame/heap/_fold_proposals.py +645 -0
- frame/heap/_heap_types.py +97 -0
- frame/heap/graph.py +469 -0
- frame/heap/graph_analysis.py +605 -0
- frame/lemmas/__init__.py +9 -0
- frame/lemmas/_lemma_application.py +1274 -0
- frame/lemmas/_matcher.py +260 -0
- frame/lemmas/_substitution.py +240 -0
- frame/lemmas/base.py +831 -0
- frame/lemmas/compositional.py +302 -0
- frame/lemmas/dll_lemmas.py +541 -0
- frame/lemmas/list_lemmas.py +536 -0
- frame/lemmas/other_lemmas.py +328 -0
- frame/lemmas/taint_lemmas.py +317 -0
- frame/lemmas/validator.py +288 -0
- frame/lemmas/wand_lemmas.py +139 -0
- frame/predicates/__init__.py +91 -0
- frame/predicates/base.py +246 -0
- frame/predicates/base_registry.py +459 -0
- frame/predicates/dll_predicates.py +60 -0
- frame/predicates/list_predicates.py +373 -0
- frame/predicates/lsso_predicates.py +48 -0
- frame/predicates/parsed.py +305 -0
- frame/predicates/registry.py +402 -0
- frame/predicates/skiplist_predicates.py +128 -0
- frame/predicates/sorted_predicates.py +60 -0
- frame/predicates/tree_predicates.py +88 -0
- frame/preprocessing/__init__.py +8 -0
- frame/preprocessing/equality.py +340 -0
- frame/sil/__init__.py +117 -0
- frame/sil/analyzers/__init__.py +44 -0
- frame/sil/analyzers/csharp_interprocedural_taint.py +912 -0
- frame/sil/analyzers/function_summary.py +170 -0
- frame/sil/analyzers/interprocedural_analyzer.py +4109 -0
- frame/sil/analyzers/interprocedural_taint.py +889 -0
- frame/sil/analyzers/memory_safety.py +309 -0
- frame/sil/analyzers/path_sensitive_analyzer.py +861 -0
- frame/sil/analyzers/semantic_patterns.py +658 -0
- frame/sil/analyzers/sl_memory_analyzer.py +614 -0
- frame/sil/analyzers/sl_semantic_analyzer.py +1429 -0
- frame/sil/analyzers/ts_function_summarizer.py +818 -0
- frame/sil/changes.py +45 -0
- frame/sil/cli.py +662 -0
- frame/sil/cwe_taxonomy.py +223 -0
- frame/sil/fingerprints.py +266 -0
- frame/sil/framework_evidence.py +36 -0
- frame/sil/frontends/__init__.py +68 -0
- frame/sil/frontends/_literal_fields.py +38 -0
- frame/sil/frontends/_python_types.py +273 -0
- frame/sil/frontends/c_frontend.py +2103 -0
- frame/sil/frontends/csharp_frontend.py +2359 -0
- frame/sil/frontends/java_frontend.py +3258 -0
- frame/sil/frontends/javascript_frontend.py +3782 -0
- frame/sil/frontends/python_frontend.py +2323 -0
- frame/sil/hardening.py +128 -0
- frame/sil/instructions.py +773 -0
- frame/sil/llm_agent.py +233 -0
- frame/sil/llm_client.py +337 -0
- frame/sil/llm_detect.py +474 -0
- frame/sil/llm_exploit.py +252 -0
- frame/sil/llm_fix.py +150 -0
- frame/sil/llm_triage.py +244 -0
- frame/sil/loop_exit.py +70 -0
- frame/sil/metadata.py +397 -0
- frame/sil/packs/__init__.py +12 -0
- frame/sil/packs/apply.py +386 -0
- frame/sil/packs/cli.py +59 -0
- frame/sil/packs/data/c/civetweb.toml +42 -0
- frame/sil/packs/data/c/fastcgi.toml +34 -0
- frame/sil/packs/data/c/libarchive.toml +22 -0
- frame/sil/packs/data/c/libcurl.toml +23 -0
- frame/sil/packs/data/c/libmicrohttpd.toml +46 -0
- frame/sil/packs/data/c/libxml2.toml +25 -0
- frame/sil/packs/data/c/mongoose.toml +39 -0
- frame/sil/packs/data/c/openssl.toml +42 -0
- frame/sil/packs/data/cpp/boost-asio-beast.toml +37 -0
- frame/sil/packs/data/cpp/cpp-httplib.toml +54 -0
- frame/sil/packs/data/cpp/crow.toml +37 -0
- frame/sil/packs/data/cpp/grpc.toml +22 -0
- frame/sil/packs/data/cpp/qt.toml +83 -0
- frame/sil/packs/data/cpp/sql-clients.toml +43 -0
- frame/sil/packs/data/csharp/aspnet-classic.toml +45 -0
- frame/sil/packs/data/csharp/aspnetcore-minimal.toml +19 -0
- frame/sil/packs/data/csharp/aspnetcore.toml +104 -0
- frame/sil/packs/data/csharp/azure-functions.toml +29 -0
- frame/sil/packs/data/csharp/dapper.toml +19 -0
- frame/sil/packs/data/csharp/dotnet-system.toml +53 -0
- frame/sil/packs/data/csharp/entity-framework.toml +12 -0
- frame/sil/packs/data/csharp/entity-framework6.toml +11 -0
- frame/sil/packs/data/csharp/grpc.toml +16 -0
- frame/sil/packs/data/csharp/nhibernate.toml +10 -0
- frame/sil/packs/data/csharp/wcf.toml +24 -0
- frame/sil/packs/data/java/deserialization.toml +21 -0
- frame/sil/packs/data/java/hibernate-jpa.toml +24 -0
- frame/sil/packs/data/java/http-clients.toml +43 -0
- frame/sil/packs/data/java/javalin.toml +26 -0
- frame/sil/packs/data/java/jaxrs.toml +45 -0
- frame/sil/packs/data/java/jdk-core.toml +14 -0
- frame/sil/packs/data/java/jooq.toml +24 -0
- frame/sil/packs/data/java/jsf.toml +47 -0
- frame/sil/packs/data/java/micronaut.toml +38 -0
- frame/sil/packs/data/java/play.toml +29 -0
- frame/sil/packs/data/java/sparkjava.toml +32 -0
- frame/sil/packs/data/java/spring.toml +98 -0
- frame/sil/packs/data/java/struts2.toml +36 -0
- frame/sil/packs/data/java/template-engines.toml +66 -0
- frame/sil/packs/data/java/vertx-web.toml +61 -0
- frame/sil/packs/data/javascript/angular.toml +32 -0
- frame/sil/packs/data/javascript/aws-lambda.toml +16 -0
- frame/sil/packs/data/javascript/electron.toml +39 -0
- frame/sil/packs/data/javascript/fastify.toml +37 -0
- frame/sil/packs/data/javascript/graphql.toml +16 -0
- frame/sil/packs/data/javascript/hapi.toml +34 -0
- frame/sil/packs/data/javascript/http-clients.toml +22 -0
- frame/sil/packs/data/javascript/koa.toml +31 -0
- frame/sil/packs/data/javascript/nextjs.toml +59 -0
- frame/sil/packs/data/javascript/node-core.toml +53 -0
- frame/sil/packs/data/javascript/react-dom.toml +20 -0
- frame/sil/packs/data/javascript/redis.toml +11 -0
- frame/sil/packs/data/javascript/restify.toml +11 -0
- frame/sil/packs/data/javascript/sails.toml +15 -0
- frame/sil/packs/data/javascript/sanitizers.toml +51 -0
- frame/sil/packs/data/javascript/socketio.toml +13 -0
- frame/sil/packs/data/javascript/sql-drivers.toml +38 -0
- frame/sil/packs/data/javascript/template-engines.toml +18 -0
- frame/sil/packs/data/javascript/typeorm.toml +22 -0
- frame/sil/packs/data/python/aiohttp.toml +52 -0
- frame/sil/packs/data/python/asyncpg.toml +19 -0
- frame/sil/packs/data/python/bottle.toml +32 -0
- frame/sil/packs/data/python/builtins.toml +15 -0
- frame/sil/packs/data/python/cherrypy.toml +30 -0
- frame/sil/packs/data/python/django-rest-framework.toml +30 -0
- frame/sil/packs/data/python/django.toml +52 -0
- frame/sil/packs/data/python/falcon.toml +27 -0
- frame/sil/packs/data/python/fastapi.toml +56 -0
- frame/sil/packs/data/python/flask-extensions.toml +28 -0
- frame/sil/packs/data/python/http-clients.toml +32 -0
- frame/sil/packs/data/python/process-execution.toml +53 -0
- frame/sil/packs/data/python/pymongo.toml +30 -0
- frame/sil/packs/data/python/pyramid.toml +43 -0
- frame/sil/packs/data/python/sanic.toml +24 -0
- frame/sil/packs/data/python/sql-libraries.toml +47 -0
- frame/sil/packs/data/python/templates.toml +34 -0
- frame/sil/packs/data/python/tornado.toml +31 -0
- frame/sil/packs/loader.py +95 -0
- frame/sil/packs/schema.py +262 -0
- frame/sil/procedure.py +1009 -0
- frame/sil/project.py +1453 -0
- frame/sil/scanner.py +2605 -0
- frame/sil/source_facts.py +47 -0
- frame/sil/specs/__init__.py +57 -0
- frame/sil/specs/c_specs.py +862 -0
- frame/sil/specs/csharp_specs.py +825 -0
- frame/sil/specs/java_specs.py +1130 -0
- frame/sil/specs/javascript_specs.py +693 -0
- frame/sil/specs/python_specs.py +1199 -0
- frame/sil/suppressions.py +187 -0
- frame/sil/templates.py +248 -0
- frame/sil/translator.py +8003 -0
- frame/sil/types.py +470 -0
- frame/sil/verify.py +186 -0
- frame/utils/__init__.py +12 -0
- frame/utils/_normalization.py +341 -0
- frame/utils/formula_utils.py +78 -0
- frame/utils/frame_rule.py +398 -0
- frame/utils/proof_state.py +168 -0
- frame/utils/satisfiability.py +738 -0
- frame_security-1.0.0.dist-info/METADATA +157 -0
- frame_security-1.0.0.dist-info/RECORD +236 -0
- frame_security-1.0.0.dist-info/WHEEL +5 -0
- frame_security-1.0.0.dist-info/entry_points.txt +2 -0
- frame_security-1.0.0.dist-info/licenses/LICENSE +201 -0
- frame_security-1.0.0.dist-info/top_level.txt +1 -0
frame/__init__.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Separation Logic Entailment Checker using Z3
|
|
3
|
+
|
|
4
|
+
A Python library for checking entailments in separation logic,
|
|
5
|
+
supporting basic assertions and inductive predicates for data structures.
|
|
6
|
+
|
|
7
|
+
The library is organized into logical modules:
|
|
8
|
+
- core: AST and parser
|
|
9
|
+
- encoding: Z3 SMT encoding
|
|
10
|
+
- checking: Entailment checking and heuristics
|
|
11
|
+
- analysis: Formula analysis and reasoning
|
|
12
|
+
- heap: Heap graph and pattern detection
|
|
13
|
+
- folding: Predicate folding/unfolding
|
|
14
|
+
- arithmetic: Arithmetic reasoning
|
|
15
|
+
- preprocessing: Formula preprocessing
|
|
16
|
+
- predicates: Inductive predicate definitions
|
|
17
|
+
- lemmas: Lemma library
|
|
18
|
+
- utils: Utilities and proof management
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Core abstractions
|
|
22
|
+
from frame.core.ast import (
|
|
23
|
+
Formula, Emp, PointsTo, SepConj, Wand, And, Or, Not, Eq, Neq,
|
|
24
|
+
Lt, Le, Gt, Ge, True_, False_, Exists, Forall, Var, Const, PredicateCall,
|
|
25
|
+
# String expressions
|
|
26
|
+
StrLiteral, StrConcat, StrLen, StrSubstr, StrContains, StrMatches,
|
|
27
|
+
# Security and taint tracking
|
|
28
|
+
Taint, Sanitized, Source, Sink,
|
|
29
|
+
# Error states for incorrectness logic
|
|
30
|
+
Error, NullDeref, UseAfterFree, BufferOverflow
|
|
31
|
+
)
|
|
32
|
+
from frame.core.parser import parse, parse_entailment
|
|
33
|
+
|
|
34
|
+
# Main checker
|
|
35
|
+
from frame.checking.checker import EntailmentChecker
|
|
36
|
+
|
|
37
|
+
# Predicates
|
|
38
|
+
from frame.predicates import (
|
|
39
|
+
PredicateRegistry, InductivePredicate, PredicateValidationError,
|
|
40
|
+
ListSegment, Tree, GenericPredicate
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
__version__ = "1.0.0"
|
|
44
|
+
__all__ = [
|
|
45
|
+
# Core formulas and expressions
|
|
46
|
+
"Formula", "Emp", "PointsTo", "SepConj", "Wand", "And", "Or", "Not",
|
|
47
|
+
"Eq", "Neq", "Lt", "Le", "Gt", "Ge", "True_", "False_", "Exists", "Forall", "Var", "Const",
|
|
48
|
+
"PredicateCall",
|
|
49
|
+
# String expressions
|
|
50
|
+
"StrLiteral", "StrConcat", "StrLen", "StrSubstr", "StrContains", "StrMatches",
|
|
51
|
+
# Security and taint
|
|
52
|
+
"Taint", "Sanitized", "Source", "Sink",
|
|
53
|
+
# Error states
|
|
54
|
+
"Error", "NullDeref", "UseAfterFree", "BufferOverflow",
|
|
55
|
+
# Checker and predicates
|
|
56
|
+
"EntailmentChecker", "PredicateRegistry",
|
|
57
|
+
"InductivePredicate", "PredicateValidationError",
|
|
58
|
+
"ListSegment", "Tree", "GenericPredicate",
|
|
59
|
+
# Parser
|
|
60
|
+
"parse", "parse_entailment"
|
|
61
|
+
]
|
frame/__main__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Frame CLI entry point for `python -m frame`.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python -m frame scan app.py
|
|
7
|
+
python -m frame solve "x |-> 5 |- x |-> 5"
|
|
8
|
+
python -m frame check entailments.txt
|
|
9
|
+
python -m frame parse "x |-> 5 * y |-> 3"
|
|
10
|
+
python -m frame repl
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
from frame.cli import main
|
|
15
|
+
|
|
16
|
+
if __name__ == "__main__":
|
|
17
|
+
sys.exit(main())
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Formula analysis and reasoning utilities.
|
|
3
|
+
|
|
4
|
+
This module provides various analysis capabilities:
|
|
5
|
+
- Formula structure analysis
|
|
6
|
+
- Unification algorithm
|
|
7
|
+
- Predicate pattern matching
|
|
8
|
+
- Footprint analysis
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from frame.analysis.formula import FormulaAnalyzer
|
|
12
|
+
from frame.analysis.unification import Unifier
|
|
13
|
+
from frame.analysis.predicate_matching import PredicateMatcher
|
|
14
|
+
from frame.analysis.footprint import FootprintAnalyzer
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Footprint analysis for separation logic formulas.
|
|
3
|
+
|
|
4
|
+
A footprint is the set of heap locations (variables) that a formula may allocate or access.
|
|
5
|
+
This is used for sound affine weakening in the frame rule.
|
|
6
|
+
|
|
7
|
+
Key principles:
|
|
8
|
+
- Allocated locations are always in footprint
|
|
9
|
+
- Variable values (potential pointers) are in footprint
|
|
10
|
+
- Constant values are NOT in footprint
|
|
11
|
+
- Inductive predicates use one-step unfolding for precision
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from typing import Set, List, Tuple
|
|
15
|
+
from frame.core.ast import (
|
|
16
|
+
Formula, Expr, Var, Const, Emp, PointsTo, SepConj, And, Or, Not,
|
|
17
|
+
PredicateCall, Exists, Forall, Wand
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FootprintAnalyzer:
|
|
22
|
+
"""
|
|
23
|
+
Analyzes footprints (allocated heap locations + stored pointers) of separation logic formulas.
|
|
24
|
+
|
|
25
|
+
Uses one-step unfolding of inductive predicates for sound and precise footprint tracking.
|
|
26
|
+
Conservative: includes symbolic values that could alias other heap cells.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, unfold_depth=1):
|
|
30
|
+
"""
|
|
31
|
+
Args:
|
|
32
|
+
unfold_depth: Depth of unfolding for inductive predicates (default: 1)
|
|
33
|
+
"""
|
|
34
|
+
self.unfold_depth = unfold_depth
|
|
35
|
+
self._predicate_registry = None # Will be set by EntailmentChecker if available
|
|
36
|
+
|
|
37
|
+
def _is_constant(self, expr: Expr) -> bool:
|
|
38
|
+
"""Check if expression is a constant (not a potential pointer)"""
|
|
39
|
+
if isinstance(expr, Const):
|
|
40
|
+
return True
|
|
41
|
+
if isinstance(expr, Var):
|
|
42
|
+
# Check if variable name looks like a constant (e.g., "nil", or numeric)
|
|
43
|
+
if expr.name in ["nil", "null"]:
|
|
44
|
+
return True
|
|
45
|
+
try:
|
|
46
|
+
int(expr.name) # Numeric string like "5"
|
|
47
|
+
return True
|
|
48
|
+
except ValueError:
|
|
49
|
+
return False # Regular variable name
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
def footprint(self, formula: Formula) -> Set[str]:
|
|
53
|
+
"""
|
|
54
|
+
Compute the footprint of a formula.
|
|
55
|
+
|
|
56
|
+
Footprint includes:
|
|
57
|
+
- All allocated locations (addresses in x |-> y)
|
|
58
|
+
- All variable values that could be pointers (y in x |-> y)
|
|
59
|
+
- Symbolic next values from unfolded predicates
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Set of variable names that may be allocated or referenced by this formula.
|
|
63
|
+
"""
|
|
64
|
+
if isinstance(formula, Emp):
|
|
65
|
+
return set()
|
|
66
|
+
|
|
67
|
+
elif isinstance(formula, PointsTo):
|
|
68
|
+
# x |-> y allocates location x
|
|
69
|
+
fp = set()
|
|
70
|
+
if isinstance(formula.location, Var):
|
|
71
|
+
fp.add(formula.location.name)
|
|
72
|
+
|
|
73
|
+
# Include values that are potential pointers (variables, not constants)
|
|
74
|
+
# x |-> y → include y (variable, might be pointer)
|
|
75
|
+
# x |-> 5 → don't include 5 (constant, not a pointer)
|
|
76
|
+
for val in formula.values:
|
|
77
|
+
if isinstance(val, Var) and not self._is_constant(val):
|
|
78
|
+
fp.add(val.name)
|
|
79
|
+
|
|
80
|
+
return fp
|
|
81
|
+
|
|
82
|
+
elif isinstance(formula, PredicateCall):
|
|
83
|
+
# One-step unfolding for precision
|
|
84
|
+
# list(z) unfolds to: z |-> next * list(next)
|
|
85
|
+
# Footprint: {z, z_next} where z_next is symbolic
|
|
86
|
+
fp = set()
|
|
87
|
+
|
|
88
|
+
# Always include root pointer
|
|
89
|
+
for arg in formula.args:
|
|
90
|
+
if isinstance(arg, Var):
|
|
91
|
+
fp.add(arg.name)
|
|
92
|
+
|
|
93
|
+
# Add symbolic next values (conservative: any variable from predicate might store pointers)
|
|
94
|
+
# This captures that list(z) might contain any heap value
|
|
95
|
+
if self.unfold_depth >= 1:
|
|
96
|
+
# For predicates, add symbolic "next" values
|
|
97
|
+
# This conservatively assumes the predicate stores pointers
|
|
98
|
+
for arg in formula.args:
|
|
99
|
+
if isinstance(arg, Var):
|
|
100
|
+
# Add a symbolic next value for this root
|
|
101
|
+
fp.add(f"{arg.name}_next")
|
|
102
|
+
|
|
103
|
+
return fp
|
|
104
|
+
|
|
105
|
+
elif isinstance(formula, SepConj):
|
|
106
|
+
# P * Q: union of footprints
|
|
107
|
+
left_fp = self.footprint(formula.left)
|
|
108
|
+
right_fp = self.footprint(formula.right)
|
|
109
|
+
return left_fp.union(right_fp)
|
|
110
|
+
|
|
111
|
+
elif isinstance(formula, And):
|
|
112
|
+
# P & Q: union of spatial footprints
|
|
113
|
+
left_fp = self.footprint(formula.left)
|
|
114
|
+
right_fp = self.footprint(formula.right)
|
|
115
|
+
return left_fp.union(right_fp)
|
|
116
|
+
|
|
117
|
+
elif isinstance(formula, Or):
|
|
118
|
+
# P | Q: union (overapproximation - may allocate either)
|
|
119
|
+
left_fp = self.footprint(formula.left)
|
|
120
|
+
right_fp = self.footprint(formula.right)
|
|
121
|
+
return left_fp.union(right_fp)
|
|
122
|
+
|
|
123
|
+
elif isinstance(formula, Not):
|
|
124
|
+
# ¬P: no allocation in classical logic
|
|
125
|
+
# (negation is typically pure)
|
|
126
|
+
return set()
|
|
127
|
+
|
|
128
|
+
elif isinstance(formula, (Exists, Forall)):
|
|
129
|
+
# Quantifiers: footprint of body
|
|
130
|
+
return self.footprint(formula.formula)
|
|
131
|
+
|
|
132
|
+
elif isinstance(formula, Wand):
|
|
133
|
+
# P -* Q: footprint of Q (what's allocated when P is available)
|
|
134
|
+
# Conservative: union of both
|
|
135
|
+
left_fp = self.footprint(formula.left)
|
|
136
|
+
right_fp = self.footprint(formula.right)
|
|
137
|
+
return left_fp.union(right_fp)
|
|
138
|
+
|
|
139
|
+
else:
|
|
140
|
+
# Pure formulas, True, False, etc.
|
|
141
|
+
return set()
|
|
142
|
+
|
|
143
|
+
def _get_root_vars(self, formula: Formula) -> Set[str]:
|
|
144
|
+
"""Extract root variables (allocated locations) from a formula"""
|
|
145
|
+
roots = set()
|
|
146
|
+
if isinstance(formula, PointsTo):
|
|
147
|
+
if isinstance(formula.location, Var):
|
|
148
|
+
roots.add(formula.location.name)
|
|
149
|
+
elif isinstance(formula, PredicateCall):
|
|
150
|
+
# For predicates, the first argument is typically the root
|
|
151
|
+
if formula.args and isinstance(formula.args[0], Var):
|
|
152
|
+
roots.add(formula.args[0].name)
|
|
153
|
+
elif isinstance(formula, SepConj):
|
|
154
|
+
roots.update(self._get_root_vars(formula.left))
|
|
155
|
+
roots.update(self._get_root_vars(formula.right))
|
|
156
|
+
return roots
|
|
157
|
+
|
|
158
|
+
def _contains_predicates(self, formula: Formula) -> bool:
|
|
159
|
+
"""Check if formula contains any predicate calls"""
|
|
160
|
+
if isinstance(formula, PredicateCall):
|
|
161
|
+
return True
|
|
162
|
+
elif isinstance(formula, SepConj):
|
|
163
|
+
return self._contains_predicates(formula.left) or self._contains_predicates(formula.right)
|
|
164
|
+
elif isinstance(formula, And):
|
|
165
|
+
return self._contains_predicates(formula.left) or self._contains_predicates(formula.right)
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
def _get_symbolic_values(self, formula: Formula) -> Set[str]:
|
|
169
|
+
"""
|
|
170
|
+
Extract symbolic values (variables, not constants) stored in cells.
|
|
171
|
+
For x |-> y: returns {y} if y is variable, {} if y is constant
|
|
172
|
+
For x |-> 5: returns {}
|
|
173
|
+
"""
|
|
174
|
+
values = set()
|
|
175
|
+
if isinstance(formula, PointsTo):
|
|
176
|
+
for val in formula.values:
|
|
177
|
+
if isinstance(val, Var) and not self._is_constant(val):
|
|
178
|
+
values.add(val.name)
|
|
179
|
+
elif isinstance(formula, PredicateCall):
|
|
180
|
+
# Predicates have symbolic _next values from one-step unfolding
|
|
181
|
+
# These are handled in footprint(), not here
|
|
182
|
+
pass
|
|
183
|
+
elif isinstance(formula, SepConj):
|
|
184
|
+
values.update(self._get_symbolic_values(formula.left))
|
|
185
|
+
values.update(self._get_symbolic_values(formula.right))
|
|
186
|
+
return values
|
|
187
|
+
|
|
188
|
+
def _flatten_sepconj(self, formula: Formula, start_pos: int = 0) -> List[Tuple[Formula, int]]:
|
|
189
|
+
"""
|
|
190
|
+
Flatten a SepConj structure to assign positions to each conjunct.
|
|
191
|
+
Returns list of (formula, position) tuples.
|
|
192
|
+
|
|
193
|
+
Left-most formulas get lower positions (earlier in * order).
|
|
194
|
+
Example: (A * B) * C → [(A, 0), (B, 1), (C, 2)]
|
|
195
|
+
"""
|
|
196
|
+
if isinstance(formula, SepConj):
|
|
197
|
+
# Recursively flatten left and right
|
|
198
|
+
left_flat = self._flatten_sepconj(formula.left, start_pos)
|
|
199
|
+
next_pos = start_pos + len(left_flat)
|
|
200
|
+
right_flat = self._flatten_sepconj(formula.right, next_pos)
|
|
201
|
+
return left_flat + right_flat
|
|
202
|
+
else:
|
|
203
|
+
# Base case: single formula
|
|
204
|
+
return [(formula, start_pos)]
|
|
205
|
+
|
|
206
|
+
def can_drop_safely_order_aware(self, remainder: Formula, remainder_pos: int,
|
|
207
|
+
other: Formula, other_pos: int) -> bool:
|
|
208
|
+
"""
|
|
209
|
+
Check if remainder can be safely dropped using order-aware heuristic.
|
|
210
|
+
|
|
211
|
+
Order-aware affine SL heuristic:
|
|
212
|
+
- Remainder AFTER kept predicate (higher position) → safe to drop if disjoint
|
|
213
|
+
- Remainder BEFORE kept predicate (lower position) → conservative, block if has symbolic values
|
|
214
|
+
|
|
215
|
+
This captures: earlier cells may be referenced by later predicates,
|
|
216
|
+
but cells added later are safe to drop.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
remainder: Formula to drop
|
|
220
|
+
remainder_pos: Position in * structure (lower = earlier)
|
|
221
|
+
other: Formula to keep
|
|
222
|
+
other_pos: Position in * structure
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
True if safe to drop, False otherwise
|
|
226
|
+
"""
|
|
227
|
+
fp_remainder = self.footprint(remainder)
|
|
228
|
+
fp_other = self.footprint(other)
|
|
229
|
+
|
|
230
|
+
remainder_roots = self._get_root_vars(remainder)
|
|
231
|
+
other_roots = self._get_root_vars(other)
|
|
232
|
+
remainder_symbolic_values = self._get_symbolic_values(remainder)
|
|
233
|
+
|
|
234
|
+
# Special case: List segments with same root
|
|
235
|
+
# ls(x,y) * ls(x,z) is only SAT when x=y=z (both empty)
|
|
236
|
+
# Let Z3 discover this constraint instead of blocking early
|
|
237
|
+
if isinstance(remainder, PredicateCall) and isinstance(other, PredicateCall):
|
|
238
|
+
if remainder.name == "ls" and other.name == "ls":
|
|
239
|
+
if remainder.args and other.args:
|
|
240
|
+
# Check if both have the same root variable
|
|
241
|
+
rem_root = remainder.args[0]
|
|
242
|
+
other_root = other.args[0]
|
|
243
|
+
if isinstance(rem_root, Var) and isinstance(other_root, Var):
|
|
244
|
+
if rem_root.name == other_root.name:
|
|
245
|
+
# Same root - allow Z3 to handle the constraints
|
|
246
|
+
return True
|
|
247
|
+
|
|
248
|
+
# Check 1: Locations must be disjoint
|
|
249
|
+
if not remainder_roots.isdisjoint(fp_other):
|
|
250
|
+
return False
|
|
251
|
+
|
|
252
|
+
# Check 2: Order-aware symbolic value check
|
|
253
|
+
if remainder_symbolic_values:
|
|
254
|
+
# Check if kept has symbolic footprint (predicates with _next)
|
|
255
|
+
has_symbolic_in_kept = any(v.endswith('_next') for v in fp_other)
|
|
256
|
+
|
|
257
|
+
if has_symbolic_in_kept:
|
|
258
|
+
# Check if remainder value matches kept predicate root
|
|
259
|
+
if not remainder_symbolic_values.isdisjoint(other_roots):
|
|
260
|
+
# Example: x |-> z * list(z) where value z IS the root
|
|
261
|
+
return False
|
|
262
|
+
|
|
263
|
+
# ORDER-AWARE HEURISTIC:
|
|
264
|
+
# If remainder occurs AFTER kept predicate → safe to drop (affine weakening)
|
|
265
|
+
# If remainder occurs BEFORE kept predicate → unsafe (conservative)
|
|
266
|
+
if remainder_pos < other_pos:
|
|
267
|
+
# Remainder before kept → may be referenced
|
|
268
|
+
# Example: x |-> y * list(z) where position(x|->y) < position(list(z))
|
|
269
|
+
return False
|
|
270
|
+
else:
|
|
271
|
+
# Remainder after kept → safe
|
|
272
|
+
# Example: list(x) * y |-> z where position(list(x)) < position(y|->z)
|
|
273
|
+
return True
|
|
274
|
+
|
|
275
|
+
# No symbolic values or no predicates → safe
|
|
276
|
+
return True
|
|
277
|
+
|
|
278
|
+
def can_drop_safely(self, remainder: Formula, other: Formula) -> bool:
|
|
279
|
+
"""
|
|
280
|
+
Check if remainder can be safely dropped (backwards compatibility).
|
|
281
|
+
|
|
282
|
+
This version flattens both formulas and checks all combinations.
|
|
283
|
+
For simple cases (single formulas), assigns same position.
|
|
284
|
+
"""
|
|
285
|
+
# Flatten both to get positions
|
|
286
|
+
remainder_flat = self._flatten_sepconj(remainder)
|
|
287
|
+
other_flat = self._flatten_sepconj(other)
|
|
288
|
+
|
|
289
|
+
# Check all combinations
|
|
290
|
+
for r_formula, r_pos in remainder_flat:
|
|
291
|
+
for o_formula, o_pos in other_flat:
|
|
292
|
+
if not self.can_drop_safely_order_aware(r_formula, r_pos, o_formula, o_pos):
|
|
293
|
+
return False
|
|
294
|
+
|
|
295
|
+
return True
|
|
296
|
+
|
|
297
|
+
def entails_emp_footprint_aware(self, antecedent: Formula, consequent: Formula) -> bool:
|
|
298
|
+
"""
|
|
299
|
+
Check if antecedent can be weakened to emp when consequent is emp.
|
|
300
|
+
|
|
301
|
+
This implements affine weakening with footprint safety:
|
|
302
|
+
- If antecedent is emp, trivially true
|
|
303
|
+
- If antecedent has concrete allocations disjoint from consequent, can drop
|
|
304
|
+
- Otherwise, cannot safely weaken to emp
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
antecedent: The formula we're checking (remainder after frame rule)
|
|
308
|
+
consequent: Should be emp or contain spatial parts
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
True if antecedent can safely entail emp in this context
|
|
312
|
+
"""
|
|
313
|
+
if isinstance(antecedent, Emp):
|
|
314
|
+
return True
|
|
315
|
+
|
|
316
|
+
fp_ante = self.footprint(antecedent)
|
|
317
|
+
|
|
318
|
+
# If antecedent has no footprint, it's effectively emp
|
|
319
|
+
if not fp_ante:
|
|
320
|
+
return True
|
|
321
|
+
|
|
322
|
+
# If consequent is emp, check if antecedent footprint is disjoint from anything
|
|
323
|
+
if isinstance(consequent, Emp):
|
|
324
|
+
# In affine SL, we can drop cells that don't interfere
|
|
325
|
+
# But we can't drop cells if they're the *only* thing
|
|
326
|
+
# (i.e., x |-> y ⊬ emp when there's nothing else)
|
|
327
|
+
# This is the soundness check
|
|
328
|
+
return False # Cannot weaken non-empty to emp without context
|
|
329
|
+
|
|
330
|
+
# If consequent has spatial parts, check overlap
|
|
331
|
+
fp_cons = self.footprint(consequent)
|
|
332
|
+
|
|
333
|
+
# Can drop antecedent if its footprint doesn't overlap with consequent
|
|
334
|
+
return fp_ante.isdisjoint(fp_cons)
|