packages feed

wasmedge-hs-0.17.1.0: examples/Stress/ChaosFault.hs

{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedStrings #-}

{- |
Module      : Stress.ChaosFault
Description : Chaos engineering and fault injection resilience stress test
Copyright   : (c) 2026 Tushar Adhatrao
License     : MIT
Stability   : experimental

Tests runtime resilience under adverse fault conditions:
  1. WebAssembly 'unreachable' traps
  2. Integer division-by-zero
  3. Out-of-bounds linear memory access
  4. Non-existent function execution
  5. Corrupted bytecode headers and truncated sections
  6. Concurrent chaos storm across multiple threads verifying zero crashes/segfaults
-}
module Stress.ChaosFault
  ( runChaosFaultSuite
  , ChaosMetrics (..)
  ) where

import Control.Concurrent.Async (forConcurrently)
import Control.Exception (throwIO)
import Control.Monad (when)
import Data.Either (isLeft, isRight)
import Data.Text qualified as T
import System.Clock (Clock (Monotonic), diffTimeSpec, getTime, toNanoSecs)
import Text.Printf (printf)

import Stress.WasmBytecode
  ( addWasm
  , chaosWasm
  , corruptedMagicWasm
  , corruptedSectionWasm
  )
import WasmEdge.Error (WasmEdgeError (..))
import WasmEdge.Types (WasmVal (..))
import WasmEdge.VM

-- | Metrics recorded during chaos fault testing
data ChaosMetrics = ChaosMetrics
  { cfmTrapCaught :: !Bool
  , cfmDivByZeroCaught :: !Bool
  , cfmOobCaught :: !Bool
  , cfmCorruptCaught :: !Bool
  , cfmStormTotalFaults :: !Int
  , cfmStormSuccessRate :: !Double
  , cfmDurationSec :: !Double
  }
  deriving stock (Show)

-- | Run the Chaos and Fault Injection stress test suite.
runChaosFaultSuite :: IO ChaosMetrics
runChaosFaultSuite = do
  putStrLn "============================================================"
  putStrLn "  STRESS TEST: Chaos Fault Injection & Resilience"
  putStrLn "============================================================"

  t0 <- getTime Monotonic

  -- 1. Trap unreachable test
  putStrLn "--> [1/6] Fault Injection: 'unreachable' trap instruction..."
  trapOk <- withVM Nothing Nothing $ \vm -> do
    _ <- loadWasmBytes vm chaosWasm
    _ <- validate vm
    _ <- instantiate vm
    res <- execute vm "trap_unreachable" []
    case res of
      Left (WasmEdgeError _cat code msg) -> do
        putStrLn $ printf "    Caught expected trap: code=0x%03X (%s)" code (T.unpack (T.take 40 msg))
        pure True
      Right vals -> do
        putStrLn $ "    Unexpected success: " ++ show vals
        pure False

  -- 2. Division by zero test
  putStrLn "--> [2/6] Fault Injection: integer division by zero..."
  divOk <- withVM Nothing Nothing $ \vm -> do
    _ <- loadWasmBytes vm chaosWasm
    _ <- validate vm
    _ <- instantiate vm
    res <- execute vm "div_by_zero" [WasmI32 42]
    case res of
      Left (WasmEdgeError _cat code msg) -> do
        putStrLn $
          printf "    Caught expected div-by-zero: code=0x%03X (%s)" code (T.unpack (T.take 40 msg))
        pure True
      Right vals -> do
        putStrLn $ "    Unexpected success: " ++ show vals
        pure False

  -- 3. Out-of-bounds linear memory load test
  putStrLn "--> [3/6] Fault Injection: out-of-bounds memory load (offset 70000 > 65536)..."
  oobOk <- withVM Nothing Nothing $ \vm -> do
    _ <- loadWasmBytes vm chaosWasm
    _ <- validate vm
    _ <- instantiate vm
    res <- execute vm "oob_memory_load" [WasmI32 70000]
    case res of
      Left (WasmEdgeError _cat code msg) -> do
        putStrLn $ printf "    Caught expected OOB access: code=0x%03X (%s)" code (T.unpack (T.take 40 msg))
        pure True
      Right vals -> do
        putStrLn $ "    Unexpected success: " ++ show vals
        pure False

  -- 4. Calling nonexistent function
  putStrLn "--> [4/6] Fault Injection: invocation of nonexistent export..."
  nonexistentOk <- withVM Nothing Nothing $ \vm -> do
    _ <- loadWasmBytes vm chaosWasm
    _ <- validate vm
    _ <- instantiate vm
    res <- execute vm "ghost_function" []
    pure (isLeft res)
  putStrLn $ "    Nonexistent function error properly caught: " ++ show nonexistentOk

  -- 5. Loading corrupted bytecodes
  putStrLn "--> [5/6] Fault Injection: corrupted bytecode headers..."
  corruptOk <- withVM Nothing Nothing $ \vm -> do
    resMagic <- loadWasmBytes vm corruptedMagicWasm
    resSection <- loadWasmBytes vm corruptedSectionWasm
    pure (isLeft resMagic && isLeft resSection)
  putStrLn $ "    Corrupted bytecode rejection verified: " ++ show corruptOk

  -- 6. Concurrent Chaos Storm: 1,000 mixed requests across 20 threads
  putStrLn "--> [6/6] Chaos Storm: 1,000 mixed valid/faulty requests across 20 concurrent threads..."
  let stormCount = 1000 :: Int
  stormResults <- forConcurrently [1 .. stormCount] $ \i -> do
    withVM Nothing Nothing $ \vm -> do
      case i `mod` 5 of
        0 -> do
          -- Good call
          r <- runWasmBytes vm addWasm "add" [WasmI32 (fromIntegral i), WasmI32 1]
          pure (isRight r)
        1 -> do
          -- Trap
          _ <- loadWasmBytes vm chaosWasm
          _ <- validate vm
          _ <- instantiate vm
          r <- execute vm "trap_unreachable" []
          pure (isLeft r)
        2 -> do
          -- Div by zero
          _ <- loadWasmBytes vm chaosWasm
          _ <- validate vm
          _ <- instantiate vm
          r <- execute vm "div_by_zero" [WasmI32 (fromIntegral i)]
          pure (isLeft r)
        3 -> do
          -- OOB load
          _ <- loadWasmBytes vm chaosWasm
          _ <- validate vm
          _ <- instantiate vm
          r <- execute vm "oob_memory_load" [WasmI32 99999]
          pure (isLeft r)
        _ -> do
          -- Corrupt load
          r <- loadWasmBytes vm corruptedMagicWasm
          pure (isLeft r)

  let passedStorm = length (filter id stormResults)
      successRate = fromIntegral passedStorm / fromIntegral stormCount * 100.0 :: Double

  putStrLn $
    printf "    Chaos Storm result: %d/%d passed (%.1f%% resilience)" passedStorm stormCount successRate

  when (passedStorm /= stormCount) $
    throwIO (userError "Chaos storm failed to handle all errors safely!")

  t1 <- getTime Monotonic
  let durSec = fromIntegral (toNanoSecs (diffTimeSpec t1 t0)) / 1e9 :: Double
  printf "==> Chaos Suite PASSED in %.3f s (0 crashes, 100%% recovery)\n\n" durSec

  pure $
    ChaosMetrics
      { cfmTrapCaught = trapOk
      , cfmDivByZeroCaught = divOk
      , cfmOobCaught = oobOk
      , cfmCorruptCaught = corruptOk && nonexistentOk
      , cfmStormTotalFaults = stormCount
      , cfmStormSuccessRate = successRate
      , cfmDurationSec = durSec
      }