Skip to content

Commit acf89a7

Browse files
authored
Close find_infer_type TK_TYPEALIASREF leak (#5203)
The TK_TYPEALIASREF case in find_infer_type allocates an unfolded tree via typealias_unfold but never frees it. The original comment said this was "to avoid a dangling pointer" — the actual reason was that ownership of the recursive call's return is genuinely ambiguous (it can be unfolded itself, an interior descendant, or a freshly-built tree from type_union/type_isect), and the code punted by never freeing. Dup the recursive result so the returned subtree is independent of the unfolded tree, then free unfolded. Clear the dup's scope pointer first because ast_dup copies it from result->parent, which can alias into the soon-to-be-freed unfolded tree — the same defusing pattern reach.c uses after deferred_reify. Add full-program tests for the case (a) and case (c) branches that the existing chained-tuple-destructure test doesn't reach, and cross-link that test to this issue. Closes #5199
1 parent 51e5a4f commit acf89a7

File tree

6 files changed

+88
-3
lines changed

6 files changed

+88
-3
lines changed

src/libponyc/expr/operator.c

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,38 @@ static ast_t* find_infer_type(pass_opt_t* opt, ast_t* type, infer_path_t* path)
9999

100100
case TK_TYPEALIASREF:
101101
{
102-
// Don't free: find_infer_type may return a pointer into the unfolded
103-
// tree (e.g., the TK_TUPLETYPE node itself or a child of it).
104102
ast_t* unfolded = typealias_unfold(type);
105103

106104
if(unfolded == NULL)
107105
return NULL;
108106

109-
return find_infer_type(opt, unfolded, path);
107+
ast_t* result = find_infer_type(opt, unfolded, path);
108+
109+
if(result == NULL)
110+
{
111+
ast_free_unattached(unfolded);
112+
return NULL;
113+
}
114+
115+
// The recursive call may return: (a) unfolded itself, (b) an
116+
// interior descendant of unfolded, or (c) a freshly-built tree from
117+
// type_union or type_isect. Dup result so the returned subtree is
118+
// independent of unfolded. ast_dup copies the root's scope pointer
119+
// from result->parent — which in cases (a) and (b) points inside
120+
// the unfolded tree we are about to free — so clear it before the
121+
// free. Do not remove the ast_set_scope call: no downstream pass
122+
// reads this scope today, but any future code walking ast_parent
123+
// or ast_get on the returned type would dereference freed memory.
124+
// Free unfolded (collects cases a and b) and, separately, any
125+
// fresh tree returned in case (c).
126+
ast_t* dup_result = ast_dup(result);
127+
ast_set_scope(dup_result, NULL);
128+
129+
if(result != unfolded)
130+
ast_free_unattached(result);
131+
132+
ast_free_unattached(unfolded);
133+
return dup_result;
110134
}
111135

112136
default:

test/full-program-tests/type-alias-chained-tuple-destructure/main.pony

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
//
2121
// The pre-fix counterfactual fires first at `operator.c:660`. With the
2222
// fix, all three sites are exercised end-to-end. See ponylang/ponyc#5195.
23+
//
24+
// This test also exercises the fix for ponylang/ponyc#5199 (memory leak
25+
// in `find_infer_type`'s TK_TYPEALIASREF case): the destructure routes
26+
// through `infer_local_inner` → `find_infer_type(TK_TYPEALIASREF, path)`
27+
// and hits case (b), where the recursive call returns an interior
28+
// descendant of the unfolded tree.
2329

2430
use @pony_exitcode[None](code: I32)
2531

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Regression test for find_infer_type's TK_TYPEALIASREF case (#5199),
2+
// case (a): the recursive find_infer_type call returns the unfolded
3+
// tree itself unchanged.
4+
//
5+
// `find_infer_type` in `src/libponyc/expr/operator.c` dispatches on
6+
// TK_TYPEALIASREF by unfolding the alias and recursing on the result.
7+
// When the right-hand side of an inferring let binding is an aliased
8+
// nominal type with no destructuring path, the recursive call hits the
9+
// default case with `path == NULL` and returns the unfolded pointer
10+
// unchanged. This exercises the `result == unfolded` branch of the fix
11+
// (where the inner free is skipped to avoid a double-free) and confirms
12+
// the dup-and-clear-scope sequence handles the result-equals-unfolded
13+
// case correctly.
14+
15+
use @pony_exitcode[None](code: I32)
16+
17+
type _Count is U64
18+
19+
actor Main
20+
new create(env: Env) =>
21+
let x: _Count = U64(42)
22+
let y = x
23+
if y == 42 then
24+
@pony_exitcode(1)
25+
end
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Regression test for find_infer_type's TK_TYPEALIASREF case (#5199),
2+
// case (c): the recursive find_infer_type call returns a freshly-built
3+
// tree from `type_union` (or `type_isect`).
4+
//
5+
// `find_infer_type` in `src/libponyc/expr/operator.c` dispatches on
6+
// TK_TYPEALIASREF by unfolding the alias and recursing on the result.
7+
// When the right-hand side of an inferring let binding is an aliased
8+
// union type with no destructuring path, the recursive call hits the
9+
// TK_UNIONTYPE branch which iterates the variants and assembles a new
10+
// union via `type_union`. The recursive call returns a fresh tree that
11+
// does not share storage with `unfolded`, so the fix must free both the
12+
// fresh tree (via the `result != unfolded` inner free) and `unfolded`
13+
// itself. This exercises the case (c) branch of the fix and confirms
14+
// the inferred type still supports exhaustive matching against the
15+
// union variants.
16+
17+
use @pony_exitcode[None](code: I32)
18+
19+
type _Either is (U64 | String)
20+
21+
actor Main
22+
new create(env: Env) =>
23+
let x: _Either = U64(42)
24+
let y = x
25+
match y
26+
| let n: U64 => if n == 42 then @pony_exitcode(1) end
27+
| let _: String => None
28+
end

0 commit comments

Comments
 (0)