Skip to content

Commit c0a4bda

Browse files
committed
Fix memory leaks in quantifier expressions early exits
TN: UB12-016
1 parent 28010df commit c0a4bda

File tree

7 files changed

+107
-3
lines changed

7 files changed

+107
-3
lines changed

langkit/expressions/collections.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,12 @@ class Expr(ComputingExpr):
620620
static_type = T.Bool
621621
pretty_class_name = 'Quantifier'
622622

623+
serial_generator = iter(count(1))
624+
"""
625+
Generator of unique IDs for this expression. Used to create unique
626+
labels in the generated code.
627+
"""
628+
623629
def __init__(self, kind, collection, expr, element_vars, index_var,
624630
iter_scope, abstract_expr=None):
625631
"""
@@ -655,6 +661,11 @@ def __init__(self, kind, collection, expr, element_vars, index_var,
655661
self.iter_scope = iter_scope
656662
self.static_type = T.Bool
657663

664+
self.exit_label = f"Exit_{next(self.serial_generator)}"
665+
"""
666+
Exit label for early loop exits in the generated code.
667+
"""
668+
658669
with iter_scope.parent.use():
659670
super().__init__(
660671
'Quantifier_Result', abstract_expr=abstract_expr

langkit/templates/properties/quantifier_ada.mako

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,24 +52,36 @@ ${result_var} := ${'False' if quantifier.kind == ANY else 'True'};
5252
${quantifier.expr.render_pre()}
5353
5454
## Depending on the kind of the quantifier, we want to abort as soon
55-
## as the predicate holds or as soon as it does not hold.
55+
## as the predicate holds or as soon as it does not hold. To exit the
56+
## loop, go to the exit label, so that scope finalizers are called
57+
## before actually leaving the loop.
5658
% if quantifier.kind == ANY:
5759
if ${quantifier.expr.render_expr()} then
5860
${result_var} := True;
59-
exit;
61+
goto ${quantifier.exit_label};
6062
end if;
6163
% else:
6264
if not (${quantifier.expr.render_expr()}) then
6365
${result_var} := False;
64-
exit;
66+
goto ${quantifier.exit_label};
6567
end if;
6668
% endif
6769
6870
% if quantifier.index_var:
6971
${quantifier.index_var.name} := ${quantifier.index_var.name} + 1;
7072
% endif
7173
74+
<<${quantifier.exit_label}>> null;
7275
${scopes.finalize_scope(quantifier.iter_scope)}
76+
77+
## If we decided after this iteration to exit the loop, we can do it
78+
## now that the iteration scope it finalized.
79+
exit when
80+
% if quantifier.kind == ALL:
81+
not
82+
% endif
83+
${result_var}
84+
;
7385
end loop;
7486
end;
7587
</%def>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import lexer_example
2+
@with_lexer(foo_lexer)
3+
grammar foo_grammar {
4+
@main_rule main_rule <- list+(Example(@example))
5+
6+
}
7+
8+
@abstract class FooNode implements Node[FooNode] {
9+
10+
fun get_bigint(): BigInt = BigInt(1)
11+
12+
@export fun check(): Bool = node.children.all(
13+
(c) => # This condition is always False, so we have a loop early exit,
14+
# which used to leave the result of "c.get_bigint" (big integers
15+
# are ref-counted) allocated when exitting the scope: the finalizer
16+
# for that scope was not called in that case.
17+
c.get_bigint() = BigInt(0)
18+
)
19+
}
20+
21+
class Example : FooNode implements TokenNode {
22+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
with Ada.Text_IO; use Ada.Text_IO;
2+
3+
with Libfoolang.Analysis; use Libfoolang.Analysis;
4+
5+
procedure Main is
6+
Ctx : constant Analysis_Context := Create_Context;
7+
U : constant Analysis_Unit := Ctx.Get_From_Buffer
8+
(Filename => "main.txt",
9+
Buffer => "example");
10+
N : constant Foo_Node := U.Root;
11+
12+
begin
13+
if U.Has_Diagnostics then
14+
for D of U.Diagnostics loop
15+
Put_Line (U.Format_GNU_Diagnostic (D));
16+
end loop;
17+
raise Program_Error;
18+
end if;
19+
20+
Put_Line ("P_Check (" & N.Image & ") = " & N.P_Check'Image);
21+
end Main;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
P_Check (<ExampleList main.txt:1:1-1:8>) = FALSE
2+
Done
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""
2+
Check that the scope of a Qualifier expression is correctly finalized on early
3+
exit.
4+
"""
5+
6+
from langkit.dsl import ASTNode
7+
from langkit.expressions import BigIntLiteral, Self, langkit_property
8+
9+
from utils import build_and_run
10+
11+
12+
class FooNode(ASTNode):
13+
14+
@langkit_property()
15+
def get_bigint():
16+
return BigIntLiteral(1)
17+
18+
@langkit_property(public=True)
19+
def check():
20+
return Self.children.all(
21+
# This condition is always False, so we have a loop early exit,
22+
# which used to leave the result of "c.get_bigint" (big integers
23+
# are ref-counted) allocated when exitting the scope: the finalizer
24+
# for that scope was not called in that case.
25+
lambda c: c.get_bigint() == BigIntLiteral(0)
26+
)
27+
28+
29+
class Example(FooNode):
30+
token_node = True
31+
32+
33+
build_and_run(lkt_file='expected_concrete_syntax.lkt', ada_main="main.adb",
34+
types_from_lkt=False)
35+
print('Done')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
driver: python

0 commit comments

Comments
 (0)