@@ -84,34 +84,41 @@ enum ParserAction : UInt8 {
8484 case dcsUnhook
8585}
8686
87- class TransitionTable {
87+ final class TransitionTable {
8888 // data is packed like this:
8989 // currentState << 8 | characterCode --> action << 4 | nextState
90- var table : [ UInt8 ]
90+ let table : [ UInt8 ]
9191
92+ fileprivate init ( _ table: [ UInt8 ] )
93+ {
94+ self . table = table
95+ }
96+
97+ subscript ( idx: Int ) -> UInt8 {
98+ table [ idx]
99+ }
100+ }
101+
102+ fileprivate struct TransitionTableBuilder {
103+ var table : [ UInt8 ]
104+
92105 init ( len: Int )
93106 {
94107 table = Array . init ( repeating: 0 , count: len)
95108 }
96109
97- func add ( code: UInt8 , state: ParserState , action: ParserAction , next: ParserState )
110+ mutating func add ( code: UInt8 , state: ParserState , action: ParserAction , next: ParserState )
98111 {
99112 let v = ( UInt8 ( action. rawValue) << 4 ) | next. rawValue
100113 table [ ( Int ( state. rawValue) << 8 ) | Int ( code) ] = v
101114 }
102115
103- func add ( codes: [ UInt8 ] , state: ParserState , action: ParserAction , next: ParserState )
116+ mutating func add ( codes: [ UInt8 ] , state: ParserState , action: ParserAction , next: ParserState )
104117 {
105118 for c in codes {
106119 add ( code: c, state: state, action: action, next: next)
107120 }
108121 }
109-
110- subscript ( idx: Int ) -> UInt8 {
111- get {
112- return table [ idx]
113- }
114- }
115122}
116123
117124protocol DcsHandler {
@@ -157,7 +164,7 @@ public class EscapeSequenceParser {
157164
158165 static func buildVt500TransitionTable ( ) -> TransitionTable
159166 {
160- let table = TransitionTable ( len: 4095 )
167+ var table = TransitionTableBuilder ( len: 4095 )
161168 let states = rinclusive ( low: . ground, high: . dcsPassthrough)
162169
163170 // table with default transition
@@ -282,7 +289,7 @@ public class EscapeSequenceParser {
282289 table. add ( codes: [ 0x1b , 0x9c ] , state: . dcsPassthrough, action: . dcsUnhook, next: . ground)
283290 table. add ( code: NonAsciiPrintable, state: . oscString, action: . oscPut, next: . oscString)
284291 table. add ( code: NonAsciiPrintable, state: . apcString, action: . oscPut, next: . apcString)
285- return table
292+ return TransitionTable ( table. table )
286293 }
287294
288295 // Array of parameters, and "collect" string
@@ -338,20 +345,38 @@ public class EscapeSequenceParser {
338345 var _pars : [ Int ]
339346 var _parsTxt : [ UInt8 ]
340347 var _collect : cstring
348+ var _parameterLimitExceeded : Bool
341349 var printHandler : PrintHandler = { ( slice : ArraySlice < UInt8 > ) -> ( ) in }
342350 var printStateReset : ( ) -> ( ) = { }
343351
344- var table : TransitionTable
352+ private static let sharedVt500Table = EscapeSequenceParser . buildVt500TransitionTable ( )
353+
354+ /// CSI and DCS parameter values use the same 16-bit saturating range as Ghostty.
355+ static let maximumParameterValue = Int ( UInt16 . max)
356+
357+ /// Sequences beyond this limit are dropped instead of growing parser state without bound.
358+ static let maximumParameterCount = 24
359+ let table : TransitionTable
345360
346361 init ( terminal: Terminal ? = nil )
347362 {
348363 self . terminal = terminal
349- table = EscapeSequenceParser . buildVt500TransitionTable ( )
364+ table = EscapeSequenceParser . sharedVt500Table
350365 _osc = [ ]
351366 _apc = [ ]
352367 _pars = [ 0 ]
353368 _parsTxt = [ ]
354369 _collect = [ ]
370+ _parameterLimitExceeded = false
371+ }
372+
373+ @inline ( __always)
374+ private static func appendingParameterDigit( _ code: UInt8 , to currentValue: Int ) -> Int {
375+ let digit = Int ( code) - 48
376+ if currentValue > ( maximumParameterValue - digit) / 10 {
377+ return maximumParameterValue
378+ }
379+ return currentValue * 10 + digit
355380 }
356381
357382 // MARK: - Dispatch Methods
@@ -596,7 +621,9 @@ public class EscapeSequenceParser {
596621 _osc = [ ]
597622 _apc = [ ]
598623 _pars = [ 0 ]
624+ _parsTxt = [ ]
599625 _collect = [ ]
626+ _parameterLimitExceeded = false
600627 activeDcsHandler = nil
601628 printStateReset ( )
602629 }
@@ -640,6 +667,7 @@ public class EscapeSequenceParser {
640667 var collect = self . _collect
641668 var pars = self . _pars
642669 var parsTxt = self . _parsTxt
670+ var parameterLimitExceeded = self . _parameterLimitExceeded
643671 let tableData = table. table
644672 var dcsHandler = activeDcsHandler
645673
@@ -668,11 +696,11 @@ public class EscapeSequenceParser {
668696
669697 // shortcut for CSI params
670698 if currentState == . csiParam && ( code > 0x2f && code < 0x3a ) {
671- let newV = pars [ pars . count - 1 ] * 10 + Int ( code ) - 48
672-
673- // Prevent attempts at overflowing - crash
674- let willOverflow = newV > ( ( Int . max / 10 ) - 10 )
675- pars [ pars . count - 1 ] = willOverflow ? 0 : newV
699+ if !parameterLimitExceeded {
700+ pars [ pars . count - 1 ] = EscapeSequenceParser . appendingParameterDigit (
701+ code ,
702+ to : pars [ pars . count - 1 ] )
703+ }
676704 i += 1
677705 continue
678706 }
@@ -738,18 +766,22 @@ public class EscapeSequenceParser {
738766 error = false ;
739767 }
740768 case . csiDispatch:
741- _parsTxt = parsTxt
742- dispatchCsi ( code: code, pars: pars, collect: collect)
769+ if !parameterLimitExceeded {
770+ _parsTxt = parsTxt
771+ dispatchCsi ( code: code, pars: pars, collect: collect)
772+ }
743773 case . param:
744774 if code == 0x3b || code == 0x3a {
745- parsTxt. append ( code)
746- pars. append ( 0 )
747- } else {
748- let newV = pars [ pars. count - 1 ] * 10 + Int( code) - 48
749-
750- // Prevent attempts at overflowing - crash
751- let willOverflow = newV > ( ( Int . max/ 10 ) - 10 )
752- pars [ pars. count - 1 ] = willOverflow ? 0 : newV
775+ if pars. count >= EscapeSequenceParser . maximumParameterCount {
776+ parameterLimitExceeded = true
777+ } else if !parameterLimitExceeded {
778+ parsTxt. append ( code)
779+ pars. append ( 0 )
780+ }
781+ } else if !parameterLimitExceeded {
782+ pars [ pars. count - 1 ] = EscapeSequenceParser . appendingParameterDigit (
783+ code,
784+ to: pars [ pars. count - 1 ] )
753785 }
754786 case . escDispatch:
755787 dispatchEsc ( collect: collect, code: code)
@@ -765,10 +797,12 @@ public class EscapeSequenceParser {
765797 pars = [ 0 ]
766798 parsTxt = [ ]
767799 collect = [ ]
800+ parameterLimitExceeded = false
768801 dcs = - 1
769802 printStateReset ( )
770803 case . dcsHook:
771- if let handler = dispatchDcs ( collect: collect, code: code, pars: pars) {
804+ if !parameterLimitExceeded,
805+ let handler = dispatchDcs ( collect: collect, code: code, pars: pars) {
772806 dcsHandler = handler
773807 handler. hook ( collect: collect, parameters: pars, flag: code)
774808 }
@@ -790,6 +824,7 @@ public class EscapeSequenceParser {
790824 pars = [ 0 ]
791825 parsTxt = [ ]
792826 collect = [ ]
827+ parameterLimitExceeded = false
793828 dcs = - 1
794829 printStateReset ( )
795830 case . oscStart:
@@ -828,18 +863,20 @@ public class EscapeSequenceParser {
828863 }
829864 } else {
830865 if osc. count != 0 && code != ControlCodes . CAN && code != ControlCodes . SUB {
831- var oscCode : Int
866+ let oscCode : Int ?
832867 var content : ArraySlice < UInt8 >
833868 let semiColonAscii = 59 // ';'
834869
835870 if let idx = osc. firstIndex ( of: UInt8 ( semiColonAscii) ) {
836- oscCode = EscapeSequenceParser . parseInt ( osc [ 0 ..< idx] )
871+ oscCode = EscapeSequenceParser . parseDecimal ( osc [ 0 ..< idx] )
837872 content = osc [ ( idx+ 1 ) ... ]
838873 } else {
839- oscCode = EscapeSequenceParser . parseInt ( osc [ 0 ... ] )
874+ oscCode = EscapeSequenceParser . parseDecimal ( osc [ 0 ... ] )
840875 content = [ ]
841876 }
842- dispatchOsc ( code: oscCode, data: content)
877+ if let oscCode {
878+ dispatchOsc ( code: oscCode, data: content)
879+ }
843880 }
844881 }
845882 if code == 0x1b {
@@ -850,6 +887,7 @@ public class EscapeSequenceParser {
850887 pars = [ 0 ]
851888 parsTxt = [ ]
852889 collect = [ ]
890+ parameterLimitExceeded = false
853891 dcs = - 1
854892 printStateReset ( )
855893 }
@@ -868,6 +906,7 @@ public class EscapeSequenceParser {
868906 _collect = collect
869907 _pars = pars
870908 _parsTxt = parsTxt
909+ _parameterLimitExceeded = parameterLimitExceeded
871910
872911 // save active dcs handler reference
873912 activeDcsHandler = dcsHandler
@@ -878,20 +917,20 @@ public class EscapeSequenceParser {
878917
879918 }
880919
881- static func parseInt ( _ str: ArraySlice < UInt8 > ) -> Int
920+ /// Parses a complete decimal value, rejecting malformed input and integer overflow.
921+ static func parseDecimal ( _ str: ArraySlice < UInt8 > ) -> Int ?
882922 {
923+ guard !str. isEmpty else { return nil }
924+
883925 var result = 0
884926 for x in str {
885- if x < 48 || x > 57 {
886- return result
887- }
888-
889- let newV = result * 10 + Int ( ( x - 48 ) )
890- let willOverflow = newV > ( ( Int . max/ 10 ) - 10 )
891- if willOverflow {
892- return 0
927+ guard x >= 48 && x <= 57 else { return nil }
928+
929+ let digit = Int ( x - 48 )
930+ guard result <= ( Int . max - digit) / 10 else {
931+ return nil
893932 }
894- result = newV
933+ result = result * 10 + digit
895934 }
896935 return result
897936 }
0 commit comments