Skip to content
Open
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
20 changes: 19 additions & 1 deletion src/analyses/apron/apronAnalysis.apron.ml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,25 @@ let spec_module: (module MCPSpec) Lazy.t =
let name () = "apron"
end
in
(module Spec)
let narrowing_gas = GobConfig.get_int "ana.apron.narrowing_gas"
in
if (narrowing_gas > 0) then
let module Narrowed =
struct
module PolyhedraChainParams: Printable.ChainParams =
struct
let n () = narrowing_gas
let names = string_of_int
end
include NarrowingGas.DLifter (Spec) (PolyhedraChainParams)
module A = Spec.A
let access = Spec.access
let name = Spec.name
end
in
(module Narrowed)
else
(module Spec)
Comment on lines +18 to +36
Copy link
Member

Choose a reason for hiding this comment

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

This is in a slightly less ideal place than I imagined in #1494, but I can see why and it is fine.

Ideally, I'd like to just wrap this lifting around polyhedra, but not other Apron domains, somewhere in ApronDomain, and have the analysis be completely oblivious to the fact.
Because in some sense it's "just" a lifting of any Lattice.S → Lattice.S. But that's only so if things are fully properly abstract, but our RelationDomain/ApronDomain setup is far from that. So it'd require a large cleanup/refactoring to be able to do it in my dream way.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think that's is purely a lattice -> lattice functor, as the gas needs to be reset at every transfer function, no?

Copy link
Member

Choose a reason for hiding this comment

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

Actually the NarrowingGas.Dom functor here (based on WideningDelay, etc) is essentially just that. It's the act of unlift and lift to do the operation that leads to the reset. And that's what NarrowingGas.DLifter used here exactly does.

It additionally needs all relational domain operations (instead of Spec operations) lifted, which needs more boilerplate. And it won't really gain anything from Goblint usage perspective.

)

let get_spec (): (module MCPSpec) =
Expand Down
6 changes: 6 additions & 0 deletions src/config/options.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,12 @@
"enum": ["octagon", "interval", "polyhedra", "affeq"],
"default": "octagon"
},
"narrowing_gas": {
"title": "ana.apron.narrowing_gas",
"description": "Per state perform narrowing via meet as long as gas is left (0 to disable)",
"type": "integer",
"default": 0
},
"threshold_widening": {
"title": "ana.apron.threshold_widening",
"description":
Expand Down
96 changes: 96 additions & 0 deletions src/lifters/narrowingGas.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
(** narrowing delay with counters.

Abstract elements are paired with an integer counter, indicating how many times narrowing has been delayed.
Lifted abstract elements are only narrowed if the counter exceeds a predefined limit (gas). *)

open Batteries
open Lattice
open Analyses

module LocalChainParams: Printable.ChainParams =
struct
let n () = GobConfig.get_int "ana.narrowing.gas"
let names = string_of_int
end

module GlobalChainParams: Printable.ChainParams =
struct
let n () = GobConfig.get_int "ana.narrowing.gas"
Comment on lines +12 to +18
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

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

The configuration key "ana.narrowing.gas" does not exist in the schema. Since LocalChainParams is not currently used in the codebase, this module references a non-existent configuration option. Consider either: 1) Adding "ana.narrowing.gas" to the schema if this is intended to be a general-purpose parameter, or 2) Removing LocalChainParams if it's not needed, since the actual usage in apronAnalysis.apron.ml creates a custom ChainParams with the apron-specific narrowing_gas value.

Suggested change
let n () = GobConfig.get_int "ana.narrowing.gas"
let names = string_of_int
end
module GlobalChainParams: Printable.ChainParams =
struct
let n () = GobConfig.get_int "ana.narrowing.gas"
(* Local default gas for narrowing; avoids reliance on a non-existent config key. *)
let default_gas = 3
let n () = default_gas
let names = string_of_int
end
module GlobalChainParams: Printable.ChainParams =
struct
(* Global default gas for narrowing; avoids reliance on a non-existent config key. *)
let default_gas = 3
let n () = default_gas

Copilot uses AI. Check for mistakes.
let names = string_of_int
end
Comment on lines +16 to +20
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

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

The configuration key "ana.narrowing.gas" does not exist in the schema. Since GlobalChainParams is not currently used in the codebase, this module references a non-existent configuration option. Consider either: 1) Adding "ana.narrowing.gas" to the schema if this is intended to be a general-purpose parameter, or 2) Removing GlobalChainParams if it's not needed, since the actual usage in apronAnalysis.apron.ml creates a custom ChainParams with the apron-specific narrowing_gas value.

Copilot uses AI. Check for mistakes.

module Dom (Base: S) (ChainParams: Printable.ChainParams) =
struct
module Chain = Printable.Chain (ChainParams)
include Printable.Prod (Base) (Chain)

let lift d = (d, 0)
let unlift (d, _) = d

let bot () = (Base.bot (), 0)
let is_bot (b, _) = Base.is_bot b
let top () = (Base.top (), ChainParams.n ())
let is_top (b, _) = Base.is_top b

let leq (b1, _) (b2, _) = Base.leq b1 b2

(** All operations keep maximal counter. *)
let join (b1, i1) (b2, i2) = (Base.join b1 b2, max i1 i2)
let meet (b1, i1) (b2, i2) = (Base.meet b1 b2, max i1 i2)
let widen (b1, i1) (b2, i2) = (Base.widen b1 b2, max i1 i2)
let narrow (b1, i1) (b2, i2) =
let i' = max i1 i2 in
if i' < ChainParams.n () then
(Base.meet b1 b2, i' + 1) (* Stop narrowing when counter exceeds limit. *)
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

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

The comment is misleading. When the counter is less than n(), the code uses meet to delay narrowing. When the counter reaches or exceeds n(), actual narrowing begins. The comment should say something like "Use meet to delay narrowing until counter reaches limit" to accurately reflect the behavior.

Suggested change
(Base.meet b1 b2, i' + 1) (* Stop narrowing when counter exceeds limit. *)
(Base.meet b1 b2, i' + 1) (* Use meet to delay narrowing until counter reaches limit. *)

Copilot uses AI. Check for mistakes.
else
(Base.narrow b1 b2, i')

let pretty_diff () ((b1, _), (b2, _)) =
Base.pretty_diff () (b1, b2) (* Counters cannot violate leq. *)
end


(** Lift {!S} to use widening delay for local states.
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

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

The documentation says "widening delay" but this module implements narrowing delay/gas, not widening delay. The comment should refer to "narrowing delay" or "narrowing gas" to match the actual functionality.

Copilot uses AI. Check for mistakes.

All transfer functions reset the counter to 0, so counting only happens between old and new values at a local unknown. *)
module DLifter (S: Spec) (C: Printable.ChainParams): Spec =
struct
module DD (D: Lattice.S) =
struct
include Dom (D) (C)

let printXml f (b, i) =
BatPrintf.fprintf f "%a<analysis name=\"narrow-count\">%a</analysis>" D.printXml b Chain.printXml i
end

module NameLifter =
struct
let lift_name x = x ^ " with narrowing count"
end
include SpecLifters.DomainLifter (NameLifter) (DD) (S)

(* Redefine morphstate and paths_as_set to keep counter instead of resetting to 0. *)

let morphstate v (d, l) = (S.morphstate v d, l)

let paths_as_set man =
List.map (fun x -> (x, snd man.local)) @@ S.paths_as_set (conv man)
end

(** Lift {!S} to use widening delay for global unknowns. *)
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

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

The documentation says "widening delay" but this module implements narrowing delay/gas, not widening delay. The comment should refer to "narrowing delay" or "narrowing gas" to match the actual functionality.

Copilot uses AI. Check for mistakes.
module GLifter (S: Spec): Spec =
struct
module GG (G: Lattice.S) =
struct
include Dom (G) (GlobalChainParams)

let printXml f (b, i) =
BatPrintf.fprintf f "%a<analysis name=\"narrow-count\">%a</analysis>" G.printXml b Chain.printXml i
end

module NameLifter =
struct
let lift_name x = x ^ " with narrowing count"
end
include SpecLifters.GlobalDomainLifter (NameLifter) (GG) (S)
end
28 changes: 28 additions & 0 deletions tests/regression/89-apron3/04-polyhedra-narrowing1.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SKIP PARAM: --set ana.apron.narrowing_gas 1 --set ana.activated[+] apron --set ana.apron.domain polyhedra
// Apron is not precise enough for some nested loops
#include <goblint.h>
#include <stdio.h>

int loops0(){
int i, j, k;
int a = 0;
for (i = 500; i >= 1; i--)
{
a++;
__goblint_check(a + i - 501 == 0); // needs 1x narrowing or octagons
int b = 0;
for (j = i; j >= 1; j--)
{
__goblint_check(a + b + j == 501); // needs 1x narrowing, octagons insufficient
b++;
}
}
return 0;
}

int main()
{
loops0();

return 0;
}
Loading