From 85ea1c8eeb87574566231793e956803a6724c171 Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Mon, 25 Nov 2024 19:57:26 +0100 Subject: [PATCH] Don't crash when freezing a struct with a circular reference The Freeze method needs to check for a `frozen` condition just like all the other types of values, otherwise there can be infinite recursion. The test that I'm adding here would crash without the fix. --- starlarkstruct/struct.go | 8 ++++++-- starlarkstruct/testdata/struct.star | 9 +++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/starlarkstruct/struct.go b/starlarkstruct/struct.go index 2282d7fb..44612d28 100644 --- a/starlarkstruct/struct.go +++ b/starlarkstruct/struct.go @@ -99,6 +99,7 @@ func FromStringDict(constructor starlark.Value, d starlark.StringDict) *Struct { type Struct struct { constructor starlark.Value entries entries // sorted by name + frozen bool } // Default is the default constructor for structs. @@ -172,8 +173,11 @@ func (s *Struct) Hash() (uint32, error) { return x, nil } func (s *Struct) Freeze() { - for _, e := range s.entries { - e.value.Freeze() + if !s.frozen { + s.frozen = true + for _, e := range s.entries { + e.value.Freeze() + } } } diff --git a/starlarkstruct/testdata/struct.star b/starlarkstruct/testdata/struct.star index e54fe046..993a84fb 100644 --- a/starlarkstruct/testdata/struct.star +++ b/starlarkstruct/testdata/struct.star @@ -61,3 +61,12 @@ assert.eq(alice + bob, person(age = 50, city = "NYC", name = "bob")) # not comm assert.fails(lambda : alice + 1, "struct \\+ int") assert.eq(http + http, http) assert.fails(lambda : http + bob, "different constructors: hostport \\+ person") + +# Check that a struct with a circular reference doesn't crash when it gets frozen. +def struct_with_a_circular_reference(): + foo = lambda: print(self) + self = struct(foo = foo) + return self + +# A top-level assigment is what causes this value to be frozen at the end of the load. +toplevel_struct_with_a_circular_reference = struct_with_a_circular_reference()