packages feed

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

{-# LANGUAGE OverloadedStrings #-}

{- |
Module      : Stress.CallerIntrospection
Description : CallingFrame introspection, guest memory read/write, and C-string extraction
Copyright   : (c) 2026 Tushar Adhatrao
License     : MIT
Stability   : experimental

Exercises:
  * 'getCallerMemory'
  * 'readCallerMemory'
  * 'writeCallerMemory'
  * 'readCallerCString'
  * 'readCallerCStringMax'
  * 'getCallerModuleInstance'
  * 'getCallerExecutor'
  * Safe error handling on null or invalid frames
-}
module Stress.CallerIntrospection
  ( runCallerIntrospectionSuite
  , CallerMetrics (..)
  ) where

import Control.Monad (unless, when)
import Data.ByteString (ByteString)
import Data.IORef
import Data.Maybe (isNothing)
import Foreign.Ptr (nullPtr)
import System.Clock (Clock (Monotonic), diffTimeSpec, getTime, toNanoSecs)
import Text.Printf (printf)

import Stress.WasmBytecode (callerGuestWasm)
import WasmEdge (createVM, runWasmBytes)
import WasmEdge.Caller
  ( getCallerExecutor
  , getCallerMemory
  , getCallerModuleInstance
  , readCallerCString
  , readCallerCStringMax
  , readCallerMemory
  , writeCallerMemory
  )
import WasmEdge.Error (WasmResult)
import WasmEdge.Function
  ( CallingFrame (..)
  , createFunction
  , createFunctionType
  , deleteFunctionType
  )
import WasmEdge.Internal.Ownership (BorrowedCtx (..))
import WasmEdge.Module (buildHostModule, builderAddFunction)
import WasmEdge.Types (ValType (..), WasmVal (..))
import WasmEdge.VM (registerModuleFromImport)

data CallerMetrics = CallerMetrics
  { cmNullSafetyVerified :: !Bool
  , cmMemoryReadWriteOK :: !Bool
  , cmCStringRoundTripOK :: !Bool
  , cmCStringMaxBoundedOK :: !Bool
  , cmFramePointersVerified :: !Bool
  , callermDurationSec :: !Double
  }
  deriving stock (Show)

runCallerIntrospectionSuite :: IO CallerMetrics
runCallerIntrospectionSuite = do
  putStrLn "============================================================"
  putStrLn "  STRESS TEST: CallingFrame Guest Memory Introspection"
  putStrLn "============================================================"

  t0 <- getTime Monotonic

  -- 1. Test null/invalid CallingFrame safety
  putStrLn "[1/4] Testing null CallingFrame safety..."
  let nullFrame = CallingFrame (BorrowedCtx nullPtr)
  mMemNull <- getCallerMemory nullFrame 0
  mReadNull <- readCallerMemory nullFrame 0 0 10
  mStrNull <- readCallerCString nullFrame 0 0
  pModNull <- getCallerModuleInstance nullFrame
  pExecNull <- getCallerExecutor nullFrame

  let nullSafe =
        isNothing mMemNull
          && isNothing mReadNull
          && isNothing mStrNull
          && pModNull == nullPtr
          && pExecNull == nullPtr
  unless nullSafe $ fail "Null CallingFrame operations should fail safely!"
  putStrLn "      Null frame operations handled safely without crashing (VERIFIED)."

  -- 2. Build host function that introspects caller memory during execution
  putStrLn "[2/4] Constructing host functions with CallingFrame introspection..."
  readWriteRef <- newIORef False
  cStringRef <- newIORef False
  cStringMaxRef <- newIORef False
  framePtrsRef <- newIORef False

  -- Host function: inspect_caller(offset: i32) -> i32
  let hostInspect :: CallingFrame -> [WasmVal] -> IO (WasmResult [WasmVal])
      hostInspect frame [WasmI32 offset] = do
        let off = fromIntegral offset
        -- 1. Read caller memory
        _ <- readCallerMemory frame 0 off 16

        -- 2. Write a null-terminated greeting into caller memory at offset + 64
        let greeting = "Production WasmEdge Guest\0" :: ByteString
        resWrite <- writeCallerMemory frame 0 (off + 64) greeting

        -- 3. Read back as C-String
        mCStr <- readCallerCString frame 0 (off + 64)
        mCStrBounded <- readCallerCStringMax frame 0 (off + 64) 100

        -- 4. Check caller pointers
        pMod <- getCallerModuleInstance frame
        pExec <- getCallerExecutor frame

        case (resWrite, mCStr, mCStrBounded) of
          (Right (), Just bs, Just bsBounded) -> do
            when (bs == "Production WasmEdge Guest" && bsBounded == "Production WasmEdge Guest") $ do
              writeIORef readWriteRef True
              writeIORef cStringRef True
              writeIORef cStringMaxRef True
          _ -> pure ()

        when (pMod /= nullPtr && pExec /= nullPtr) $
          writeIORef framePtrsRef True

        pure (Right [WasmI32 1])
      hostInspect _ _ = fail "Invalid arguments to hostInspect"

  ft <- createFunctionType [I32] [I32]
  inspectFn <- createFunction ft hostInspect
  deleteFunctionType ft

  hostMod <- buildHostModule "caller_env" $ \builder -> do
    builderAddFunction builder "inspect_caller" inspectFn

  -- 3. Register into VM and run a guest module with memory
  putStrLn "[3/4] Registering host module into VM & triggering host inspection..."
  vm <- createVM Nothing Nothing
  _ <- registerModuleFromImport vm hostMod

  -- 4. Trigger host function with active Wasm guest context
  putStrLn "[4/4] Invoking test_caller across boundary from Wasm guest..."
  fromGuest <- runWasmBytes vm callerGuestWasm "test_caller" [WasmI32 0]
  case fromGuest of
    Right [WasmI32 1] -> putStrLn "      test_caller -> inspect_caller returned success (VERIFIED)"
    other -> fail $ "Unexpected result from test_caller: " ++ show other

  rwOK <- readIORef readWriteRef
  csOK <- readIORef cStringRef
  csMaxOK <- readIORef cStringMaxRef
  fpOK <- readIORef framePtrsRef

  printf "      Memory read/write verified: %s\n" (show rwOK)
  printf "      C-String extraction verified: %s\n" (show csOK)
  printf "      Caller module/executor pointers non-null: %s\n" (show fpOK)

  unless (rwOK && csOK && csMaxOK && fpOK) $
    fail "Caller introspection checks did not all pass!"

  t1 <- getTime Monotonic
  let dur = fromIntegral (toNanoSecs (diffTimeSpec t1 t0)) / 1e9 :: Double
  printf ">>> Caller Introspection Suite completed in %.3f s\n\n" dur

  pure
    CallerMetrics
      { cmNullSafetyVerified = nullSafe
      , cmMemoryReadWriteOK = rwOK
      , cmCStringRoundTripOK = csOK
      , cmCStringMaxBoundedOK = csMaxOK
      , cmFramePointersVerified = fpOK
      , callermDurationSec = dur
      }