From ac3e7ab09189f07fb39d201873d6e425c3a7cc5a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Jun 2026 07:37:42 +0000 Subject: [PATCH 1/3] Initial plan From 52e20d112301b85c5fe1ce87a2174402fb450abc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Jun 2026 07:44:10 +0000 Subject: [PATCH 2/3] Patch leaked toStringAs global in foreign linker --- .../PureScript/Backend/Lua/Linker/Foreign.hs | 9 ++++++++- .../Backend/Lua/Linker/Foreign/Spec.hs | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/Language/PureScript/Backend/Lua/Linker/Foreign.hs b/lib/Language/PureScript/Backend/Lua/Linker/Foreign.hs index 420e9d7..cb834ea 100644 --- a/lib/Language/PureScript/Backend/Lua/Linker/Foreign.hs +++ b/lib/Language/PureScript/Backend/Lua/Linker/Foreign.hs @@ -17,6 +17,7 @@ import Data.String qualified as String import Data.Text qualified as Text import Language.PureScript.Backend.Lua.Key (Key) import Language.PureScript.Backend.Lua.Key qualified as Key +import Language.PureScript.Backend.Lua.Name qualified as Name import Path (Abs, Dir, File, Path, toFilePath, ()) import Path qualified import Path.IO qualified as Path @@ -54,7 +55,7 @@ parseForeignSource foreigns path = runExceptT do Left err → throwE $ ForeignErrorParse filePath err Right parsed → do let header = guarded (not . Text.null) (Text.strip (unlines headerLines)) - pure $ Source header parsed + pure $ Source header (fmap patchToStringAsGlobalLeak parsed) where isReturn ∷ Text → Bool isReturn = Text.isPrefixOf "return" @@ -116,6 +117,12 @@ valueParser = char '(' *> go 0 DL.empty <* MP.space char ∷ Char → Parser () char c = MP.char c *> MP.space +patchToStringAsGlobalLeak ∷ (Key, Text) → (Key, Text) +patchToStringAsGlobalLeak (key, value) + | Key.toSafeName key == Name.unsafeName "toStringAs" = + (key, Text.replace "n = floor(i)" "local n = floor(i)" value) + | otherwise = (key, value) + -------------------------------------------------------------------------------- -- Errors ---------------------------------------------------------------------- diff --git a/test/Language/PureScript/Backend/Lua/Linker/Foreign/Spec.hs b/test/Language/PureScript/Backend/Lua/Linker/Foreign/Spec.hs index 83a3955..e8ebb7f 100644 --- a/test/Language/PureScript/Backend/Lua/Linker/Foreign/Spec.hs +++ b/test/Language/PureScript/Backend/Lua/Linker/Foreign/Spec.hs @@ -66,6 +66,13 @@ rawExports = baz = (function(unused) return zoo end), + toStringAs = (function(radix) + return function(i) + local floor = math.floor + n = floor(i) + return n + end + end), [ "if"]= (function() return "if" end), } |] @@ -75,6 +82,17 @@ parsedExports = (unsafeKey "foo", "42") :| [ (unsafeKey "bar", "\"ok\"") , (unsafeKey "baz", "function(unused)\n return zoo\n end") + , + ( unsafeKey "toStringAs" + , + "function(radix)\n" + <> " return function(i)\n" + <> " local floor = math.floor\n" + <> " local n = floor(i)\n" + <> " return n\n" + <> " end\n" + <> " end" + ) , (KeyReserved "if", "function() return \"if\" end") ] From 13012403bc64b0bb2affab43286ce7b8e04acdb7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Jun 2026 07:47:51 +0000 Subject: [PATCH 3/3] Prevent toStringAs global n leak during foreign import parsing --- .../PureScript/Backend/Lua/Linker/Foreign.hs | 42 ++++++++++++++++--- .../Backend/Lua/Linker/Foreign/Spec.hs | 6 ++- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/lib/Language/PureScript/Backend/Lua/Linker/Foreign.hs b/lib/Language/PureScript/Backend/Lua/Linker/Foreign.hs index cb834ea..c7c9b87 100644 --- a/lib/Language/PureScript/Backend/Lua/Linker/Foreign.hs +++ b/lib/Language/PureScript/Backend/Lua/Linker/Foreign.hs @@ -11,6 +11,7 @@ module Language.PureScript.Backend.Lua.Linker.Foreign import Control.Monad.Combinators.NonEmpty qualified as NE import Control.Monad.Trans.Except (except, throwE) +import Data.Char qualified as Char import Data.DList (DList) import Data.DList qualified as DL import Data.String qualified as String @@ -55,7 +56,7 @@ parseForeignSource foreigns path = runExceptT do Left err → throwE $ ForeignErrorParse filePath err Right parsed → do let header = guarded (not . Text.null) (Text.strip (unlines headerLines)) - pure $ Source header (fmap patchToStringAsGlobalLeak parsed) + pure $ Source header (fmap fixDataIntToStringAsGlobalLeak parsed) where isReturn ∷ Text → Bool isReturn = Text.isPrefixOf "return" @@ -117,11 +118,42 @@ valueParser = char '(' *> go 0 DL.empty <* MP.space char ∷ Char → Parser () char c = MP.char c *> MP.space -patchToStringAsGlobalLeak ∷ (Key, Text) → (Key, Text) -patchToStringAsGlobalLeak (key, value) - | Key.toSafeName key == Name.unsafeName "toStringAs" = - (key, Text.replace "n = floor(i)" "local n = floor(i)" value) +fixDataIntToStringAsGlobalLeak ∷ (Key, Text) → (Key, Text) +fixDataIntToStringAsGlobalLeak (key, value) + | Key.toSafeName key == Name.unsafeName "toStringAs" + , hasKnownContext linesOfValue = + (key, Text.intercalate "\n" (fmap patchLine linesOfValue)) | otherwise = (key, value) + where + linesOfValue = Text.lines value + + hasKnownContext ls = + any (("math.floor" `Text.isInfixOf`) . normalizeSpaces) ls + && any (("table.insert" `Text.isInfixOf`) . normalizeSpaces) ls + && any hasReturnFunctionArg ls + + hasReturnFunctionArg line = + let compact = normalizeSpaces line + in "return" `Text.isPrefixOf` compact + && "function(i)" `Text.isSuffixOf` compact + + patchLine line + | Just indent <- parseFloorAssignment line = + indent <> "local n = floor(i)" + | otherwise = line + + parseFloorAssignment line = do + let indent = Text.takeWhile Char.isSpace line + stripped = Text.strip line + guard (not ("local " `Text.isPrefixOf` stripped)) + (lhs, rhsWithEq) <- Just (Text.breakOn "=" stripped) + guard (not (Text.null rhsWithEq)) + guard (Text.strip lhs == "n") + let rhs = Text.strip (Text.drop 1 rhsWithEq) + guard (rhs == "floor(i)") + pure indent + + normalizeSpaces = Text.filter (not . Char.isSpace) -------------------------------------------------------------------------------- -- Errors ---------------------------------------------------------------------- diff --git a/test/Language/PureScript/Backend/Lua/Linker/Foreign/Spec.hs b/test/Language/PureScript/Backend/Lua/Linker/Foreign/Spec.hs index e8ebb7f..2388858 100644 --- a/test/Language/PureScript/Backend/Lua/Linker/Foreign/Spec.hs +++ b/test/Language/PureScript/Backend/Lua/Linker/Foreign/Spec.hs @@ -68,8 +68,9 @@ rawExports = end), toStringAs = (function(radix) return function(i) - local floor = math.floor + local floor, insert = math.floor, table.insert n = floor(i) + insert({}, n) return n end end), @@ -87,8 +88,9 @@ parsedExports = , "function(radix)\n" <> " return function(i)\n" - <> " local floor = math.floor\n" + <> " local floor, insert = math.floor, table.insert\n" <> " local n = floor(i)\n" + <> " insert({}, n)\n" <> " return n\n" <> " end\n" <> " end"