Module refinery.lib.scripts.js.analysis.cache
A per-run cache of the JavaScript analysis models. The deobfuscation pipeline builds one cache over
the script being transformed and shares it across every transform in a run, rebuilding the models
only after that script's tree changes — whether a transform announces the change through
Transformer.changed or an in-pass mutation advances the script's
tree_version() counter — instead of each transform rebuilding from scratch on
every pass.
Expand source code Browse git
"""
A per-run cache of the JavaScript analysis models. The deobfuscation pipeline builds one cache over
the script being transformed and shares it across every transform in a run, rebuilding the models
only after that script's tree changes — whether a transform announces the change through
`refinery.lib.scripts.Transformer.changed` or an in-pass mutation advances the script's
`refinery.lib.scripts.tree_version` counter — instead of each transform rebuilding from scratch on
every pass.
"""
from __future__ import annotations
from refinery.lib.scripts import Transformer, tree_version
from refinery.lib.scripts.js.analysis.cfg import ControlFlowModel, build_control_flow_model
from refinery.lib.scripts.js.analysis.dominance import DominanceModel, build_dominance
from refinery.lib.scripts.js.analysis.effects import EffectModel, build_effects
from refinery.lib.scripts.js.analysis.liveness import LivenessModel, build_liveness
from refinery.lib.scripts.js.analysis.model import SemanticModel, build_semantic_model
from refinery.lib.scripts.js.analysis.reaching import ReachingModel, build_reaching
from refinery.lib.scripts.js.model import JsScript
class ModelCache:
"""
Lazily builds and memoizes the `refinery.lib.scripts.js.analysis.model.SemanticModel`, the
`refinery.lib.scripts.js.analysis.effects.EffectModel`, the
`refinery.lib.scripts.js.analysis.cfg.ControlFlowModel` shared by the
`refinery.lib.scripts.js.analysis.liveness.LivenessModel` and
`refinery.lib.scripts.js.analysis.dominance.DominanceModel`, and the
`refinery.lib.scripts.js.analysis.reaching.ReachingModel` layered on them, for one root script.
The memoized models are dropped whenever this root's AST-mutation counter
(`refinery.lib.scripts.tree_version`) advances past the value they were built at, so a transform
that reads the cache after an earlier mutation in the same pass — even one not yet announced
through `refinery.lib.scripts.Transformer.changed` — observes models consistent with the current
tree. `invalidate` forces the same drop explicitly. The derived models are always built on the
current semantic model, so dropping them together keeps them consistent.
"""
def __init__(self, root: JsScript):
self.root = root
self._version = tree_version(root)
self._model: SemanticModel | None = None
self._control_flow: ControlFlowModel | None = None
self._effects: EffectModel | None = None
self._liveness: LivenessModel | None = None
self._dominance: DominanceModel | None = None
self._reaching: ReachingModel | None = None
def invalidate(self) -> None:
self._model = None
self._control_flow = None
self._effects = None
self._liveness = None
self._dominance = None
self._reaching = None
def _ensure_fresh(self) -> None:
version = tree_version(self.root)
if version != self._version:
self._version = version
self.invalidate()
@property
def model(self) -> SemanticModel:
self._ensure_fresh()
if self._model is None:
self._model = build_semantic_model(self.root)
return self._model
@property
def effects(self) -> EffectModel:
self._ensure_fresh()
if self._effects is None:
self._effects = build_effects(self.model)
return self._effects
@property
def control_flow(self) -> ControlFlowModel:
self._ensure_fresh()
if self._control_flow is None:
self._control_flow = build_control_flow_model(self.root)
return self._control_flow
@property
def liveness(self) -> LivenessModel:
self._ensure_fresh()
if self._liveness is None:
self._liveness = build_liveness(self.model, self.control_flow)
return self._liveness
@property
def dominance(self) -> DominanceModel:
self._ensure_fresh()
if self._dominance is None:
self._dominance = build_dominance(self.model, self.control_flow)
return self._dominance
@property
def reaching(self) -> ReachingModel:
self._ensure_fresh()
if self._reaching is None:
self._reaching = build_reaching(self.dominance, self.effects)
return self._reaching
def model_cache(transformer: Transformer, root: JsScript) -> ModelCache:
"""
The pipeline's shared `ModelCache` for *root* when one is attached to *transformer* and built over
that same root, otherwise a fresh cache — stashed back onto *transformer* so later lookups within
its single-pass lifetime reuse it instead of rebuilding the models per call. A transform still runs
standalone (in tests, or outside the pipeline); freshness stays governed by the tree version, and a
standalone mutation now invalidates the stashed cache exactly as it would the shared one.
"""
cache = transformer.models
if isinstance(cache, ModelCache) and cache.root is root:
return cache
cache = ModelCache(root)
transformer.models = cache
return cache
Functions
def model_cache(transformer, root)-
The pipeline's shared
ModelCachefor root when one is attached to transformer and built over that same root, otherwise a fresh cache — stashed back onto transformer so later lookups within its single-pass lifetime reuse it instead of rebuilding the models per call. A transform still runs standalone (in tests, or outside the pipeline); freshness stays governed by the tree version, and a standalone mutation now invalidates the stashed cache exactly as it would the shared one.Expand source code Browse git
def model_cache(transformer: Transformer, root: JsScript) -> ModelCache: """ The pipeline's shared `ModelCache` for *root* when one is attached to *transformer* and built over that same root, otherwise a fresh cache — stashed back onto *transformer* so later lookups within its single-pass lifetime reuse it instead of rebuilding the models per call. A transform still runs standalone (in tests, or outside the pipeline); freshness stays governed by the tree version, and a standalone mutation now invalidates the stashed cache exactly as it would the shared one. """ cache = transformer.models if isinstance(cache, ModelCache) and cache.root is root: return cache cache = ModelCache(root) transformer.models = cache return cache
Classes
class ModelCache (root)-
Lazily builds and memoizes the
SemanticModel, theEffectModel, theControlFlowModelshared by theLivenessModelandDominanceModel, and theReachingModellayered on them, for one root script. The memoized models are dropped whenever this root's AST-mutation counter (tree_version()) advances past the value they were built at, so a transform that reads the cache after an earlier mutation in the same pass — even one not yet announced throughTransformer.changed— observes models consistent with the current tree.invalidateforces the same drop explicitly. The derived models are always built on the current semantic model, so dropping them together keeps them consistent.Expand source code Browse git
class ModelCache: """ Lazily builds and memoizes the `refinery.lib.scripts.js.analysis.model.SemanticModel`, the `refinery.lib.scripts.js.analysis.effects.EffectModel`, the `refinery.lib.scripts.js.analysis.cfg.ControlFlowModel` shared by the `refinery.lib.scripts.js.analysis.liveness.LivenessModel` and `refinery.lib.scripts.js.analysis.dominance.DominanceModel`, and the `refinery.lib.scripts.js.analysis.reaching.ReachingModel` layered on them, for one root script. The memoized models are dropped whenever this root's AST-mutation counter (`refinery.lib.scripts.tree_version`) advances past the value they were built at, so a transform that reads the cache after an earlier mutation in the same pass — even one not yet announced through `refinery.lib.scripts.Transformer.changed` — observes models consistent with the current tree. `invalidate` forces the same drop explicitly. The derived models are always built on the current semantic model, so dropping them together keeps them consistent. """ def __init__(self, root: JsScript): self.root = root self._version = tree_version(root) self._model: SemanticModel | None = None self._control_flow: ControlFlowModel | None = None self._effects: EffectModel | None = None self._liveness: LivenessModel | None = None self._dominance: DominanceModel | None = None self._reaching: ReachingModel | None = None def invalidate(self) -> None: self._model = None self._control_flow = None self._effects = None self._liveness = None self._dominance = None self._reaching = None def _ensure_fresh(self) -> None: version = tree_version(self.root) if version != self._version: self._version = version self.invalidate() @property def model(self) -> SemanticModel: self._ensure_fresh() if self._model is None: self._model = build_semantic_model(self.root) return self._model @property def effects(self) -> EffectModel: self._ensure_fresh() if self._effects is None: self._effects = build_effects(self.model) return self._effects @property def control_flow(self) -> ControlFlowModel: self._ensure_fresh() if self._control_flow is None: self._control_flow = build_control_flow_model(self.root) return self._control_flow @property def liveness(self) -> LivenessModel: self._ensure_fresh() if self._liveness is None: self._liveness = build_liveness(self.model, self.control_flow) return self._liveness @property def dominance(self) -> DominanceModel: self._ensure_fresh() if self._dominance is None: self._dominance = build_dominance(self.model, self.control_flow) return self._dominance @property def reaching(self) -> ReachingModel: self._ensure_fresh() if self._reaching is None: self._reaching = build_reaching(self.dominance, self.effects) return self._reachingInstance variables
var model-
Expand source code Browse git
@property def model(self) -> SemanticModel: self._ensure_fresh() if self._model is None: self._model = build_semantic_model(self.root) return self._model var effects-
Expand source code Browse git
@property def effects(self) -> EffectModel: self._ensure_fresh() if self._effects is None: self._effects = build_effects(self.model) return self._effects var control_flow-
Expand source code Browse git
@property def control_flow(self) -> ControlFlowModel: self._ensure_fresh() if self._control_flow is None: self._control_flow = build_control_flow_model(self.root) return self._control_flow var liveness-
Expand source code Browse git
@property def liveness(self) -> LivenessModel: self._ensure_fresh() if self._liveness is None: self._liveness = build_liveness(self.model, self.control_flow) return self._liveness var dominance-
Expand source code Browse git
@property def dominance(self) -> DominanceModel: self._ensure_fresh() if self._dominance is None: self._dominance = build_dominance(self.model, self.control_flow) return self._dominance var reaching-
Expand source code Browse git
@property def reaching(self) -> ReachingModel: self._ensure_fresh() if self._reaching is None: self._reaching = build_reaching(self.dominance, self.effects) return self._reaching
Methods
def invalidate(self)-
Expand source code Browse git
def invalidate(self) -> None: self._model = None self._control_flow = None self._effects = None self._liveness = None self._dominance = None self._reaching = None