|
| 1 | +-- | Benchmark suite for sanctify-php |
| 2 | +-- SPDX-License-Identifier: PMPL-1.0-or-later |
| 3 | +module Main where |
| 4 | + |
| 5 | +import Criterion.Main |
| 6 | +import qualified Data.Text as T |
| 7 | +import qualified Data.Text.IO as TIO |
| 8 | +import System.FilePath ((</>)) |
| 9 | + |
| 10 | +import Sanctify.Parser |
| 11 | +import Sanctify.Analysis.Security |
| 12 | +import Sanctify.Analysis.Advanced |
| 13 | +import Sanctify.Transform.Sanitize |
| 14 | +import Sanctify.Transform.Strict |
| 15 | +import Sanctify.Emit |
| 16 | + |
| 17 | +main :: IO () |
| 18 | +main = do |
| 19 | + -- Read fixture files for benchmarking |
| 20 | + sqlCode <- TIO.readFile ("test" </> "fixtures" </> "vulnerable-sql.php") |
| 21 | + xssCode <- TIO.readFile ("test" </> "fixtures" </> "vulnerable-xss.php") |
| 22 | + wpCode <- TIO.readFile ("test" </> "fixtures" </> "wordpress-unsafe.php") |
| 23 | + |
| 24 | + -- Generate synthetic PHP for throughput testing |
| 25 | + let smallPhp = generatePhp 10 |
| 26 | + let mediumPhp = generatePhp 100 |
| 27 | + let largePhp = generatePhp 500 |
| 28 | + |
| 29 | + defaultMain |
| 30 | + [ bgroup "Parser" |
| 31 | + [ bench "small PHP (10 lines)" $ nf parseSmall smallPhp |
| 32 | + , bench "medium PHP (100 lines)" $ nf parseMedium mediumPhp |
| 33 | + , bench "large PHP (500 lines)" $ nf parseLarge largePhp |
| 34 | + , bench "sql-injection fixture" $ nf parseFixture sqlCode |
| 35 | + , bench "xss fixture" $ nf parseFixture xssCode |
| 36 | + , bench "wordpress fixture" $ nf parseFixture wpCode |
| 37 | + ] |
| 38 | + |
| 39 | + , bgroup "Security Analysis" |
| 40 | + [ bench "small PHP analysis" $ nf analyzeSmall smallPhp |
| 41 | + , bench "medium PHP analysis" $ nf analyzeMedium mediumPhp |
| 42 | + , bench "large PHP analysis" $ nf analyzeLarge largePhp |
| 43 | + , bench "sql-injection analysis" $ nf analyzeFixture sqlCode |
| 44 | + , bench "xss analysis" $ nf analyzeFixture xssCode |
| 45 | + ] |
| 46 | + |
| 47 | + , bgroup "Transformation" |
| 48 | + [ bench "strict transform (small)" $ nf transformSmallStrict smallPhp |
| 49 | + , bench "strict transform (medium)" $ nf transformMediumStrict mediumPhp |
| 50 | + , bench "sanitize transform (small)" $ nf transformSmallSanitize smallPhp |
| 51 | + , bench "sanitize transform (medium)" $ nf transformMediumSanitize mediumPhp |
| 52 | + ] |
| 53 | + |
| 54 | + , bgroup "Emission (Code Generation)" |
| 55 | + [ bench "emit small PHP" $ nf emitSmall smallPhp |
| 56 | + , bench "emit medium PHP" $ nf emitMedium mediumPhp |
| 57 | + , bench "emit large PHP" $ nf emitLarge largePhp |
| 58 | + ] |
| 59 | + |
| 60 | + , bgroup "Full Pipeline" |
| 61 | + [ bench "parse + analyze + emit (small)" $ nf fullPipelineSmall smallPhp |
| 62 | + , bench "parse + analyze + emit (medium)" $ nf fullPipelineMedium mediumPhp |
| 63 | + , bench "parse + analyze + emit (large)" $ nf fullPipelineLarge largePhp |
| 64 | + ] |
| 65 | + ] |
| 66 | + |
| 67 | +-- Helpers for small benchmarks |
| 68 | +parseSmall :: T.Text -> Either String () |
| 69 | +parseSmall code = case parsePhpString "test.php" code of |
| 70 | + Left _ -> Left "parse error" |
| 71 | + Right _ -> Right () |
| 72 | + |
| 73 | +parseMedium :: T.Text -> Either String () |
| 74 | +parseMedium code = case parsePhpString "test.php" code of |
| 75 | + Left _ -> Left "parse error" |
| 76 | + Right _ -> Right () |
| 77 | + |
| 78 | +parseLarge :: T.Text -> Either String () |
| 79 | +parseLarge code = case parsePhpString "test.php" code of |
| 80 | + Left _ -> Left "parse error" |
| 81 | + Right _ -> Right () |
| 82 | + |
| 83 | +parseFixture :: T.Text -> Either String () |
| 84 | +parseFixture code = case parsePhpString "fixture.php" code of |
| 85 | + Left _ -> Left "parse error" |
| 86 | + Right _ -> Right () |
| 87 | + |
| 88 | +analyzeSmall :: T.Text -> Either String Int |
| 89 | +analyzeSmall code = case parsePhpString "test.php" code of |
| 90 | + Left _ -> Left "parse error" |
| 91 | + Right ast -> Right (length (analyzeSecurityIssues ast)) |
| 92 | + |
| 93 | +analyzeMedium :: T.Text -> Either String Int |
| 94 | +analyzeMedium code = case parsePhpString "test.php" code of |
| 95 | + Left _ -> Left "parse error" |
| 96 | + Right ast -> Right (length (analyzeSecurityIssues ast)) |
| 97 | + |
| 98 | +analyzeLarge :: T.Text -> Either String Int |
| 99 | +analyzeLarge code = case parsePhpString "test.php" code of |
| 100 | + Left _ -> Left "parse error" |
| 101 | + Right ast -> Right (length (analyzeSecurityIssues ast)) |
| 102 | + |
| 103 | +analyzeFixture :: T.Text -> Either String Int |
| 104 | +analyzeFixture code = case parsePhpString "fixture.php" code of |
| 105 | + Left _ -> Left "parse error" |
| 106 | + Right ast -> Right (length (analyzeSecurityIssues ast)) |
| 107 | + |
| 108 | +transformSmallStrict :: T.Text -> Either String () |
| 109 | +transformSmallStrict code = case parsePhpString "test.php" code of |
| 110 | + Left _ -> Left "parse error" |
| 111 | + Right ast -> Right (let _ = transformStrict ast in ()) |
| 112 | + |
| 113 | +transformMediumStrict :: T.Text -> Either String () |
| 114 | +transformMediumStrict code = case parsePhpString "test.php" code of |
| 115 | + Left _ -> Left "parse error" |
| 116 | + Right ast -> Right (let _ = transformStrict ast in ()) |
| 117 | + |
| 118 | +transformSmallSanitize :: T.Text -> Either String () |
| 119 | +transformSmallSanitize code = case parsePhpString "test.php" code of |
| 120 | + Left _ -> Left "parse error" |
| 121 | + Right ast -> Right (let _ = transformSanitizeOutput ast in ()) |
| 122 | + |
| 123 | +transformMediumSanitize :: T.Text -> Either String () |
| 124 | +transformMediumSanitize code = case parsePhpString "test.php" code of |
| 125 | + Left _ -> Left "parse error" |
| 126 | + Right ast -> Right (let _ = transformSanitizeOutput ast in ()) |
| 127 | + |
| 128 | +emitSmall :: T.Text -> Either String () |
| 129 | +emitSmall code = case parsePhpString "test.php" code of |
| 130 | + Left _ -> Left "parse error" |
| 131 | + Right ast -> Right (let _ = emitPhp ast in ()) |
| 132 | + |
| 133 | +emitMedium :: T.Text -> Either String () |
| 134 | +emitMedium code = case parsePhpString "test.php" code of |
| 135 | + Left _ -> Left "parse error" |
| 136 | + Right ast -> Right (let _ = emitPhp ast in ()) |
| 137 | + |
| 138 | +emitLarge :: T.Text -> Either String () |
| 139 | +emitLarge code = case parsePhpString "test.php" code of |
| 140 | + Left _ -> Left "parse error" |
| 141 | + Right ast -> Right (let _ = emitPhp ast in ()) |
| 142 | + |
| 143 | +fullPipelineSmall :: T.Text -> Either String T.Text |
| 144 | +fullPipelineSmall code = case parsePhpString "test.php" code of |
| 145 | + Left _ -> Left "parse error" |
| 146 | + Right ast -> |
| 147 | + let transformed = transformSanitizeOutput (transformStrict ast) |
| 148 | + emitted = emitPhp transformed |
| 149 | + in Right emitted |
| 150 | + |
| 151 | +fullPipelineMedium :: T.Text -> Either String T.Text |
| 152 | +fullPipelineMedium code = case parsePhpString "test.php" code of |
| 153 | + Left _ -> Left "parse error" |
| 154 | + Right ast -> |
| 155 | + let transformed = transformSanitizeOutput (transformStrict ast) |
| 156 | + emitted = emitPhp transformed |
| 157 | + in Right emitted |
| 158 | + |
| 159 | +fullPipelineLarge :: T.Text -> Either String T.Text |
| 160 | +fullPipelineLarge code = case parsePhpString "test.php" code of |
| 161 | + Left _ -> Left "parse error" |
| 162 | + Right ast -> |
| 163 | + let transformed = transformSanitizeOutput (transformStrict ast) |
| 164 | + emitted = emitPhp transformed |
| 165 | + in Right emitted |
| 166 | + |
| 167 | +-- Generate synthetic PHP code for benchmarking |
| 168 | +generatePhp :: Int -> T.Text |
| 169 | +generatePhp lineCount = |
| 170 | + T.pack $ unlines $ |
| 171 | + [ "<?php" |
| 172 | + , "// SPDX-License-Identifier: PMPL-1.0-or-later" |
| 173 | + , "// Generated benchmark fixture" |
| 174 | + ] ++ replicate (lineCount - 3) "echo 'line';" |
0 commit comments