Repository: rokucommunity/brighterscript
Pull request #1474 (2025-04-23 16:39:49)

Prevent runtime crash for non-referencable funcs in ternary and null coalescing
Certain global functions are unable to be referenced as variables, and cause syntax errors on-device at compile time. Here's the list  of offending functions. 

```
'createobject',
'type',
'getglobalaa',
'box',
'run',
'eval',
'getlastruncompileerror',
'getlastrunruntimeerror',
'tab',
'pos'
```

These functions will now be excluded from scope capturing when transpiling ternary and null coalescing.


Patch:
diff --git a/src/parser/Expression.ts b/src/parser/Expression.ts
index 754fd51a..f38e0829 100644
--- a/src/parser/Expression.ts
+++ b/src/parser/Expression.ts
@@ -1716,6 +1716,11 @@ export class TernaryExpression extends Expression {
 
         //get all unique variable names used in the consequent and alternate, and sort them alphabetically so the output is consistent
         let allUniqueVarNames = [...new Set([...consequentInfo.uniqueVarNames, ...alternateInfo.uniqueVarNames])].sort();
+        //discard names of global functions that cannot be passed by reference
+        allUniqueVarNames = allUniqueVarNames.filter(name => {
+            return !nonReferenceableFunctions.includes(name.toLowerCase());
+        });
+
         let mutatingExpressions = [
             ...consequentInfo.expressions,
             ...alternateInfo.expressions
@@ -1812,6 +1817,11 @@ export class NullCoalescingExpression extends Expression {
 
         //get all unique variable names used in the consequent and alternate, and sort them alphabetically so the output is consistent
         let allUniqueVarNames = [...new Set([...consequentInfo.uniqueVarNames, ...alternateInfo.uniqueVarNames])].sort();
+        //discard names of global functions that cannot be passed by reference
+        allUniqueVarNames = allUniqueVarNames.filter(name => {
+            return !nonReferenceableFunctions.includes(name.toLowerCase());
+        });
+
         let hasMutatingExpression = [
             ...consequentInfo.expressions,
             ...alternateInfo.expressions
@@ -2023,3 +2033,21 @@ function numberExpressionToValue(expr: LiteralExpression, operator = '') {
         return parseFloat(operator + expr.token.text);
     }
 }
+
+/**
+ * A list of names of functions that are restricted from being stored to a
+ * variable, property, or passed as an argument. (i.e. `type` or `createobject`).
+ * Names are stored in lower case.
+ */
+const nonReferenceableFunctions = [
+    'createobject',
+    'type',
+    'getglobalaa',
+    'box',
+    'run',
+    'eval',
+    'getlastruncompileerror',
+    'getlastrunruntimeerror',
+    'tab',
+    'pos'
+];


Test patch:
diff --git a/src/parser/tests/expression/NullCoalescenceExpression.spec.ts b/src/parser/tests/expression/NullCoalescenceExpression.spec.ts
index e0fc7e08..a9dfccc1 100644
--- a/src/parser/tests/expression/NullCoalescenceExpression.spec.ts
+++ b/src/parser/tests/expression/NullCoalescenceExpression.spec.ts
@@ -258,6 +258,54 @@ describe('NullCoalescingExpression', () => {
             `);
         });
 
+        it('does not capture restricted OS functions', () => {
+            testTranspile(`
+                sub main()
+                    num = 1
+                    test(num.ToStr() = "1" ?? [
+                        createObject("roDeviceInfo")
+                        type(true)
+                        GetGlobalAA()
+                        box(1)
+                        run("file.brs", invalid)
+                        eval("print 1")
+                        GetLastRunCompileError()
+                        GetLastRunRuntimeError()
+                        Tab(1)
+                        Pos(0)
+                    ])
+                end sub
+                sub test(p1)
+                end sub
+            `, `
+                sub main()
+                    num = 1
+                    test((function(num)
+                            __bsConsequent = num.ToStr() = "1"
+                            if __bsConsequent <> invalid then
+                                return __bsConsequent
+                            else
+                                return [
+                                    createObject("roDeviceInfo")
+                                    type(true)
+                                    GetGlobalAA()
+                                    box(1)
+                                    run("file.brs", invalid)
+                                    eval("print 1")
+                                    GetLastRunCompileError()
+                                    GetLastRunRuntimeError()
+                                    Tab(1)
+                                    Pos(0)
+                                ]
+                            end if
+                        end function)(num))
+                end sub
+
+                sub test(p1)
+                end sub
+            `);
+        });
+
         it('properly transpiles null coalesence assignments - complex alternate', () => {
             testTranspile(`
                 sub main()
diff --git a/src/parser/tests/expression/TernaryExpression.spec.ts b/src/parser/tests/expression/TernaryExpression.spec.ts
index 3903d25c..74fa61e7 100644
--- a/src/parser/tests/expression/TernaryExpression.spec.ts
+++ b/src/parser/tests/expression/TernaryExpression.spec.ts
@@ -556,6 +556,51 @@ describe('ternary expressions', () => {
             `);
         });
 
+        it('does not capture restricted OS functions', () => {
+            testTranspile(`
+                sub main()
+                    test(true ? invalid : [
+                        createObject("roDeviceInfo")
+                        type(true)
+                        GetGlobalAA()
+                        box(1)
+                        run("file.brs", invalid)
+                        eval("print 1")
+                        GetLastRunCompileError()
+                        GetLastRunRuntimeError()
+                        Tab(1)
+                        Pos(0)
+                    ])
+                end sub
+                sub test(p1)
+                end sub
+            `, `
+                sub main()
+                    test((function(__bsCondition)
+                            if __bsCondition then
+                                return invalid
+                            else
+                                return [
+                                    createObject("roDeviceInfo")
+                                    type(true)
+                                    GetGlobalAA()
+                                    box(1)
+                                    run("file.brs", invalid)
+                                    eval("print 1")
+                                    GetLastRunCompileError()
+                                    GetLastRunRuntimeError()
+                                    Tab(1)
+                                    Pos(0)
+                                ]
+                            end if
+                        end function)(true))
+                end sub
+
+                sub test(p1)
+                end sub
+            `);
+        });
+
         it('complex conditions do not cause scope capture', () => {
             testTranspile(`
                 sub main()