rokucommunity / brighterscript

A superset of Roku's BrightScript language
MIT License
153 stars 47 forks source link

Stack Overflow on Roku when constructing a "grandchild class" in a namespace #267

Closed markwpearce closed 3 years ago

markwpearce commented 3 years ago

The following code causes a stack overflow.

For some reason, the super() call in StackOverflowTest.ClassB_Namespace calls itself - but only when a class that extends that class is constructed.

However, there is no issue with "grandchild" classes if they aren't in a namespace.

Tested with brighterscript@0.24.1

Here's some minimalist code that reproduces the issue:

main.bs:

sub Main()
  c_notInNamespace = new ClassC_NoNamespace("Clive")
  c_notInNamespace.output()

  b_inNamespace = new StackOverflowTest.ClassB_Namespace("Betty") ' This line will NOT stack overflow
  b_inNamespace.output()

  c_inNamespace = new StackOverflowTest.ClassC_Namespace("Charles") ' This line WILL stack overflow
  c_inNamespace.output()
end sub

StackOverflowTest.bs:

namespace StackOverflowTest
  class ClassA_Namespace
    name = ""

    function new(name)
      m.name = name
    end function

    function output()
      for each item in m.Items()
        print item.key, item.value
      end for
    end function
  end class

  class ClassB_Namespace extends ClassA_Namespace
    age = 0

    function new(name, age = 10)
      super(name) ' This line will stack overflow
      m.age = age
    end function
  end class

  class ClassC_Namespace extends ClassB_Namespace
    color = ""

    function new(name, age = 10, color = "")
      super(name)
      m.color = color
    end function
  end class

end namespace

class ClassA_NoNamespace
  name = ""

  function new(name)
    m.name = name
  end function

  function output()
    for each item in m.Items()
      print item.key, item.value
    end for
  end function
end class

class ClassB_NoNamespace extends ClassA_NoNamespace
  age = 0
  function new(name, age = 10)
    super(name)
    m.age = age
  end function
end class

class ClassC_NoNamespace extends ClassB_NoNamespace
  color = ""

  function new(name, age = 10, color = "")
    super(name)
    m.color = color
  end function
end class
markwpearce commented 3 years ago

The transpiled brs is as follows:

You can see that super0_new() gets overwritten in __StackOverflowTest_ClassC_Namespace_builder

function __StackOverflowTest_ClassA_Namespace_builder()
    instance = {}
    instance.new = function(name)
        m.name = ""
        m.name = name
    end function
    instance.output = function()
        for each item in m.Items()
            print item.key ; item.value
        end for
    end function
    return instance
end function
function StackOverflowTest_ClassA_Namespace(name)
    instance = __StackOverflowTest_ClassA_Namespace_builder()
    instance.new(name)
    return instance
end function
function __StackOverflowTest_ClassB_Namespace_builder()
    instance = __StackOverflowTest_ClassA_Namespace_builder()
    instance.super0_new = instance.new
    instance.new = function(name, age = 10)
        m.super0_new(name)
        m.age = 0
        ' This line will stack overflow
        m.age = age
    end function
    return instance
end function
function StackOverflowTest_ClassB_Namespace(name, age = 10)
    instance = __StackOverflowTest_ClassB_Namespace_builder()
    instance.new(name, age)
    return instance
end function
function __StackOverflowTest_ClassC_Namespace_builder()
    instance = __StackOverflowTest_ClassB_Namespace_builder()
    instance.super0_new = instance.new
    instance.new = function(name, age = 10, color = "")
        m.super0_new(name)
        m.color = ""
        m.color = color
    end function
    return instance
end function
function StackOverflowTest_ClassC_Namespace(name, age = 10, color = "")
    instance = __StackOverflowTest_ClassC_Namespace_builder()
    instance.new(name, age, color)
    return instance
end function
function __ClassA_NoNamespace_builder()
    instance = {}
    instance.new = function(name)
        m.name = ""
        m.name = name
    end function
    instance.output = function()
        for each item in m.Items()
            print item.key ; item.value
        end for
    end function
    return instance
end function
function ClassA_NoNamespace(name)
    instance = __ClassA_NoNamespace_builder()
    instance.new(name)
    return instance
end function
function __ClassB_NoNamespace_builder()
    instance = __ClassA_NoNamespace_builder()
    instance.super0_new = instance.new
    instance.new = function(name, age = 10)
        m.super0_new(name)
        m.age = 0
        m.age = age
    end function
    return instance
end function
function ClassB_NoNamespace(name, age = 10)
    instance = __ClassB_NoNamespace_builder()
    instance.new(name, age)
    return instance
end function
function __ClassC_NoNamespace_builder()
    instance = __ClassB_NoNamespace_builder()
    instance.super1_new = instance.new
    instance.new = function(name, age = 10, color = "")
        m.super1_new(name)
        m.color = ""
        m.color = color
    end function
    return instance
end function
function ClassC_NoNamespace(name, age = 10, color = "")
    instance = __ClassC_NoNamespace_builder()
    instance.new(name, age, color)
    return instance
end function
TwitchBronBron commented 3 years ago

Nice catch! So, if you see in the non-namespaced classes, each descendent constructor increments the number next to super. So So in ClassC's constructor, it will end up with this:

instance.super0 = ClassA_NoNamespace.new
instance.super1 = ClassB_NoNamespace.new
instance.new    = ClassC_NoNamespace.new

However, for the namespaced class C, we get this:

instance.super0 = StackOverflowTest_ClassA_Namespace.new
instance.super0 = StackOverflowTest_ClassB_Namespace.new
instance.new    = StackOverflowTest_ClassC_Namespace.new

I'll dig into this and see what I can find.

luis-soares-sky commented 7 months ago

Hi, I'm not sure if this is the same issue but I think I'm seeing a regression in 0.65.x:

' source/main.bs

sub Main(args as dynamic)
    a = new Tests.Children.ClassMyCustomBenchTest()
    print a
    sleep(5000)
end sub

' =========
'    APP
' =========

namespace App
    class ClassCore
        sub new()
            print "ClassCore.new()"
        end sub
    end class
end namespace

' =========
'   TESTS
' =========

namespace Tests
    class ClassTest extends App.ClassCore
    end class

    class ClassBenchTest extends ClassTest
    end class
end namespace

' ==========
'   CUSTOM
' ==========

namespace Tests.Children
    class ClassMyCustomBenchTest extends Tests.ClassBenchTest
    end class
end namespace
View the transpiled BrightScript code ```BrightScript sub Main(args as dynamic) a = Tests_Children_ClassMyCustomBenchTest() print a sleep(5000) end sub ' ========= ' APP ' ========= function __App_ClassCore_builder() instance = {} instance.new = sub() print "ClassCore.new()" end sub return instance end function function App_ClassCore() instance = __App_ClassCore_builder() instance.new() return instance end function ' ========= ' TESTS ' ========= function __Tests_ClassTest_builder() instance = __App_ClassCore_builder() instance.super0_new = instance.new instance.new = sub() m.super0_new() end sub return instance end function function Tests_ClassTest() instance = __Tests_ClassTest_builder() instance.new() return instance end function function __Tests_ClassBenchTest_builder() instance = __Tests_ClassTest_builder() instance.super1_new = instance.new instance.new = sub() m.super1_new() end sub return instance end function function Tests_ClassBenchTest() instance = __Tests_ClassBenchTest_builder() instance.new() return instance end function ' ========== ' CUSTOM ' ========== function __Tests_Children_ClassMyCustomBenchTest_builder() instance = __Tests_ClassBenchTest_builder() instance.super1_new = instance.new instance.new = sub() m.super1_new() end sub return instance end function function Tests_Children_ClassMyCustomBenchTest() instance = __Tests_Children_ClassMyCustomBenchTest_builder() instance.new() return instance end function'//# sourceMappingURL=./main.bs.map ```
View the telnet logs ``` ------ Running dev 'class-extends-bug-repro' main ------ 12-17 13:18:39.257 [scrpt.ctx.run.enter] UI: Entering 'class-extends-bug-repro', id 'dev' BrightScript Micro Debugger. Enter any BrightScript statement, debug commands, or HELP. Current Function: 040: sub() 041:* m.super1_new() 042: end sub Source Digest(s): pkg: dev 1.0.0 8ffa54c9 class-extends-bug-repro pkg: 632126 1.0.2 4257ed90 libplayready Stack overflow. (runtime error &hdf) in pkg:/source/Main.brs(41) Backtrace: #317 Function $anon_8() As Void file/line: pkg:/source/Main.brs(41) #316 Function $anon_8() As Void file/line: pkg:/source/Main.brs(41) #315 Function $anon_8() As Void file/line: pkg:/source/Main.brs(41) #314 Function $anon_8() As Void file/line: pkg:/source/Main.brs(41) #313 Function $anon_8() As Void file/line: pkg:/source/Main.brs(41) #312 Function $anon_8() As Void file/line: pkg:/source/Main.brs(41) #311 Function $anon_8() As Void file/line: pkg:/source/Main.brs(41) #310 Function $anon_8() As Void file/line: pkg:/source/Main.brs(41) #309 Function $anon_8() As Void file/line: pkg:/source/Main.brs(41) #308 Function $anon_8() As Void file/line: pkg:/source/Main.brs(41) #307 to #11 elided because backtrace is very long <============================================================> #10 Function $anon_8() As Void file/line: pkg:/source/Main.brs(41) #9 Function $anon_8() As Void file/line: pkg:/source/Main.brs(41) #8 Function $anon_8() As Void file/line: pkg:/source/Main.brs(41) #7 Function $anon_8() As Void file/line: pkg:/source/Main.brs(41) #6 Function $anon_8() As Void file/line: pkg:/source/Main.brs(41) #5 Function $anon_8() As Void file/line: pkg:/source/Main.brs(41) #4 Function $anon_8() As Void file/line: pkg:/source/Main.brs(41) #3 Function $anon_8() As Void file/line: pkg:/source/Main.brs(41) #2 Function $anon_b() As Void file/line: pkg:/source/Main.brs(57) #1 Function tests_children_classmycustombenchtest() As Dynamic file/line: pkg:/source/Main.brs(63) #0 Function main(args As Dynamic) As Void file/line: pkg:/source/Main.brs(2) Local Variables: global Interface:ifGlobal m roAssociativeArray refcnt=633 count:3 Brightscript Debugger> threads ID Location Source Code 0* pkg:/source/Main.brs(41) m.super1_new() *selected Brightscript Debugger> ```

EDIT: fixed with #990