Simon Jakobi pushed to branch wip/sjakobi/T26964 at Glasgow Haskell Compiler / GHC

Commits:

12 changed files:

Changes:

  • changelog.d/improve-check-prim-bounds-messages
    1
    +section: codegen
    
    2
    +synopsis: Improve ``-fcheck-prim-bounds`` runtime error messages
    
    3
    +issues: #26964 #24617
    
    4
    +mrs: !16130
    
    5
    +
    
    6
    +description: {
    
    7
    +  When an array access instrumented by ``-fcheck-prim-bounds`` fails at
    
    8
    +  runtime, the program now reports the failing primop, the offending index and
    
    9
    +  the array size, and exits with a normal non-zero status. Previously it
    
    10
    +  aborted with an "internal error" framed as a GHC bug, even though such
    
    11
    +  failures are almost always caused by incorrect use of unsafe primops in user
    
    12
    +  or library code.
    
    13
    +}

  • compiler/GHC/StgToCmm/Monad.hs
    ... ... @@ -42,6 +42,7 @@ module GHC.StgToCmm.Monad (
    42 42
             SelfLoopInfo(..),
    
    43 43
     
    
    44 44
             setTickyCtrLabel, getTickyCtrLabel,
    
    45
    +        withCurrentPrimOpName, getCurrentPrimOpName,
    
    45 46
             tickScope, getTickScope,
    
    46 47
     
    
    47 48
             withUpdFrameOff, getUpdFrameOff,
    
    ... ... @@ -304,6 +305,9 @@ data FCodeState =
    304 305
                                                              --   See Note [Self-recursive tail calls] in GHC.StgToCmm.Expr
    
    305 306
                   , fcs_ticky         :: !CLabel             -- ^ Destination for ticky counts
    
    306 307
                   , fcs_tickscope     :: !CmmTickScope       -- ^ Tick scope for new blocks & ticks
    
    308
    +              , fcs_prim_op       :: Maybe String        -- ^ Source name of the primop currently being
    
    309
    +                                                         --   compiled, used by -fcheck-prim-bounds error
    
    310
    +                                                         --   messages.
    
    307 311
                   }
    
    308 312
     
    
    309 313
     data HeapUsage   -- See Note [Virtual and real heap pointers]
    
    ... ... @@ -462,6 +466,7 @@ initFCodeState p =
    462 466
                    , fcs_selfloop      = Nothing
    
    463 467
                    , fcs_ticky         = mkTopTickyCtrLabel
    
    464 468
                    , fcs_tickscope     = GlobalScope
    
    469
    +               , fcs_prim_op       = Nothing
    
    465 470
                    }
    
    466 471
     
    
    467 472
     getFCodeState :: FCode FCodeState
    
    ... ... @@ -520,6 +525,20 @@ setTickyCtrLabel ticky code = do
    520 525
             fstate <- getFCodeState
    
    521 526
             withFCodeState code (fstate {fcs_ticky = ticky})
    
    522 527
     
    
    528
    +-- ----------------------------------------------------------------------------
    
    529
    +-- Track the primop currently being compiled
    
    530
    +
    
    531
    +-- | The source name of the primop currently being compiled (e.g.
    
    532
    +-- @"writeArray#"@), if any. Used to produce informative @-fcheck-prim-bounds@
    
    533
    +-- error messages.
    
    534
    +getCurrentPrimOpName :: FCode (Maybe String)
    
    535
    +getCurrentPrimOpName = fcs_prim_op <$> getFCodeState
    
    536
    +
    
    537
    +withCurrentPrimOpName :: String -> FCode a -> FCode a
    
    538
    +withCurrentPrimOpName name code = do
    
    539
    +        fstate <- getFCodeState
    
    540
    +        withFCodeState code (fstate {fcs_prim_op = Just name})
    
    541
    +
    
    523 542
     -- ----------------------------------------------------------------------------
    
    524 543
     -- Manage tick scopes
    
    525 544
     
    

  • compiler/GHC/StgToCmm/Prim.hs
    ... ... @@ -25,11 +25,13 @@ import GHC.StgToCmm.Layout
    25 25
     import GHC.StgToCmm.Foreign
    
    26 26
     import GHC.StgToCmm.Monad
    
    27 27
     import GHC.StgToCmm.Utils
    
    28
    +import GHC.StgToCmm.Lit ( newStringCLit )
    
    28 29
     import GHC.StgToCmm.Ticky
    
    29 30
     import GHC.StgToCmm.Heap
    
    30 31
     import GHC.StgToCmm.Prof ( costCentreFrom )
    
    31 32
     
    
    32 33
     import GHC.Types.Basic
    
    34
    +import GHC.Types.Name.Occurrence ( occNameString )
    
    33 35
     import GHC.Types.Literal.Floating
    
    34 36
     import GHC.Cmm.BlockId
    
    35 37
     import GHC.Cmm.Graph
    
    ... ... @@ -94,7 +96,13 @@ cmmPrimOpApp cfg primop cmm_args mres_ty = do
    94 96
          -- if the result type isn't explicitly given, we directly use the
    
    95 97
          -- result type of the primop.
    
    96 98
          res_ty = fromMaybe (primOpResultType primop) mres_ty
    
    97
    -  f res_ty
    
    99
    +  -- When -fcheck-prim-bounds is on, record the primop name so that any
    
    100
    +  -- bounds-check failure handler emitted while compiling it can name it in the
    
    101
    +  -- error message. Guarded by the flag so that the common (unchecked) case pays
    
    102
    +  -- nothing: no name thunk, no FCodeState update.
    
    103
    +  if stgToCmmDoBoundsCheck cfg
    
    104
    +    then withCurrentPrimOpName (occNameString (primOpOcc primop)) (f res_ty)
    
    105
    +    else f res_ty
    
    98 106
     
    
    99 107
     externalPrimop :: PrimOp -> [CmmExpr] -> PrimopCmmEmit
    
    100 108
     externalPrimop primop args = outOfLinePrimop (callExternalPrimop primop args)
    
    ... ... @@ -3615,8 +3623,12 @@ emitCheckedMemcpyCall dst src n align = do
    3615 3623
         emitMemcpyCall dst src n align
    
    3616 3624
       where
    
    3617 3625
         doCheck platform = do
    
    3626
    +        name <- fromMaybe "<unknown primop>" <$> getCurrentPrimOpName
    
    3627
    +        nameLbl <- newStringCLit name
    
    3618 3628
             overlapCheckFailed <- getCode $
    
    3619
    -          emitCCallNeverReturns [] (mkLblExpr mkMemcpyRangeOverlapLabel) []
    
    3629
    +          emitCCallNeverReturns []
    
    3630
    +            (mkLblExpr mkMemcpyRangeOverlapLabel)
    
    3631
    +            [ (CmmLit nameLbl, AddrHint) ]
    
    3620 3632
             emit =<< mkCmmIfThen' rangesOverlap overlapCheckFailed (Just False)
    
    3621 3633
           where
    
    3622 3634
             rangesOverlap = (checkDiff dst src `or` checkDiff src dst) `ne` zero
    
    ... ... @@ -3728,6 +3740,25 @@ whenCheckBounds a = do
    3728 3740
         False -> pure ()
    
    3729 3741
         True  -> a
    
    3730 3742
     
    
    3743
    +-- | Emit a call to the RTS @rtsOutOfBoundsAccess@ bounds-check failure
    
    3744
    +-- handler, passing the offending index, the number of accessed elements and
    
    3745
    +-- the array size, so that the runtime can produce an informative error
    
    3746
    +-- message. The name of the offending primop is read from the code generator
    
    3747
    +-- environment (see 'withCurrentPrimOpName').
    
    3748
    +emitBoundsCheckFailed :: CmmExpr  -- ^ accessed index
    
    3749
    +                      -> CmmExpr  -- ^ number of accessed elements
    
    3750
    +                      -> CmmExpr  -- ^ array size (in elements)
    
    3751
    +                      -> FCode ()
    
    3752
    +emitBoundsCheckFailed idx count sz = do
    
    3753
    +    name <- fromMaybe "<unknown primop>" <$> getCurrentPrimOpName
    
    3754
    +    nameLbl <- newStringCLit name
    
    3755
    +    emitCCallNeverReturns []
    
    3756
    +      (mkLblExpr mkOutOfBoundsAccessLabel)
    
    3757
    +      [ (idx,            NoHint)
    
    3758
    +      , (count,          NoHint)
    
    3759
    +      , (sz,             NoHint)
    
    3760
    +      , (CmmLit nameLbl, AddrHint) ]
    
    3761
    +
    
    3731 3762
     emitBoundsCheck :: CmmExpr  -- ^ accessed index
    
    3732 3763
                     -> CmmExpr  -- ^ array size (in elements)
    
    3733 3764
                     -> FCode ()
    
    ... ... @@ -3735,7 +3766,7 @@ emitBoundsCheck idx sz = do
    3735 3766
         assertM (stgToCmmDoBoundsCheck <$> getStgToCmmConfig)
    
    3736 3767
         platform <- getPlatform
    
    3737 3768
         boundsCheckFailed <- getCode $
    
    3738
    -      emitCCallNeverReturns [] (mkLblExpr mkOutOfBoundsAccessLabel) []
    
    3769
    +      emitBoundsCheckFailed idx (mkIntExpr platform 1) sz
    
    3739 3770
         let isOutOfBounds = cmmUGeWord platform idx sz
    
    3740 3771
         emit =<< mkCmmIfThen' isOutOfBounds boundsCheckFailed (Just False)
    
    3741 3772
     
    
    ... ... @@ -3754,7 +3785,7 @@ emitRangeBoundsCheck idx len arrSizeExpr = do
    3754 3785
         _ <- withSequel (AssignTo [lastSafeIndexReg, rangeTooLargeReg] False) $
    
    3755 3786
           cmmPrimOpApp config WordSubCOp [arrSize, len] Nothing
    
    3756 3787
         boundsCheckFailed <- getCode $
    
    3757
    -      emitCCallNeverReturns [] (mkLblExpr mkOutOfBoundsAccessLabel) []
    
    3788
    +      emitBoundsCheckFailed idx len arrSize
    
    3758 3789
         let
    
    3759 3790
           rangeTooLarge = CmmReg (CmmLocal rangeTooLargeReg)
    
    3760 3791
           lastSafeIndex = CmmReg (CmmLocal lastSafeIndexReg)
    

  • docs/users_guide/debugging.rst
    ... ... @@ -1167,10 +1167,25 @@ Checking for consistency
    1167 1167
         Typically primops operations like ``writeArray#`` exhibit unsafe behavior,
    
    1168 1168
         relying on the user to perform any bounds checking. This flag instructs the
    
    1169 1169
         code generator to instrument such operations with bound checking logic
    
    1170
    -    which aborts the program when an out-of-bounds access is detected.
    
    1170
    +    which terminates the program when an out-of-bounds access is detected.
    
    1171
    +
    
    1172
    +    When a check fails, the program prints a message naming the offending
    
    1173
    +    primop together with the offending index and the array size, and exits
    
    1174
    +    with a non-zero status, for example::
    
    1175
    +
    
    1176
    +        myprog: writeArray#: array access out of bounds:
    
    1177
    +            index 5 is not within [0, 3).
    
    1178
    +        This is usually caused by incorrect use of unsafe primops in user or library code.
    
    1179
    +
    
    1180
    +    The message does not identify the enclosing function or module. To obtain a
    
    1181
    +    backtrace pinpointing the failing call, build with profiling and run with
    
    1182
    +    ``+RTS -xc`` (see :ref:`prof-time-options`), or use :ghc-flag:`-finfo-table-map`
    
    1183
    +    together with ``+RTS -xc``.
    
    1171 1184
     
    
    1172 1185
         Note that this is only intended to be used as a debugging measure, not as
    
    1173
    -    the primary means of catching out-of-bounds accesses.
    
    1186
    +    the primary means of catching out-of-bounds accesses. Currently only the
    
    1187
    +    native code generator is instrumented; the JavaScript backend is unaffected
    
    1188
    +    by this flag.
    
    1174 1189
     
    
    1175 1190
     .. ghc-flag:: -fcmm-thread-sanitizer
    
    1176 1191
         :shortdesc: Enable ThreadSanitizer instrumentation of memory accesses.
    

  • rts/PrimOps.cmm
    ... ... @@ -88,10 +88,14 @@ import CLOSURE ghc_hs_iface;
    88 88
     #endif
    
    89 89
     
    
    90 90
     #if defined(DEBUG)
    
    91
    -#define ASSERT_IN_BOUNDS(ind, sz) \
    
    92
    -    if (ind >= sz) { ccall rtsOutOfBoundsAccess(); }
    
    91
    +// `op` is the source name of the primop being checked (e.g. "casIntArray#").
    
    92
    +// NB: in some callers `ind` is a byte offset rather than an element index, so
    
    93
    +// the index reported here may be in bytes. count is 1 since these checks cover
    
    94
    +// a single access.
    
    95
    +#define ASSERT_IN_BOUNDS(op, ind, sz) \
    
    96
    +    if (ind >= sz) { ccall rtsOutOfBoundsAccess(ind, 1, sz, op); }
    
    93 97
     #else
    
    94
    -#define ASSERT_IN_BOUNDS(ind, sz)
    
    98
    +#define ASSERT_IN_BOUNDS(op, ind, sz)
    
    95 99
     #endif
    
    96 100
     
    
    97 101
     /*-----------------------------------------------------------------------------
    
    ... ... @@ -336,7 +340,7 @@ stg_casIntArrayzh( gcptr arr, W_ ind, W_ old, W_ new )
    336 340
     {
    
    337 341
         W_ p, h;
    
    338 342
     
    
    339
    -    ASSERT_IN_BOUNDS(ind + WDS(1) - 1, StgArrBytes_bytes(arr));
    
    343
    +    ASSERT_IN_BOUNDS("casIntArray#", ind + WDS(1) - 1, StgArrBytes_bytes(arr));
    
    340 344
         p = arr + SIZEOF_StgArrBytes + WDS(ind);
    
    341 345
         (h) = prim %cmpxchgW(p, old, new);
    
    342 346
     
    
    ... ... @@ -350,7 +354,7 @@ stg_casInt8Arrayzh( gcptr arr, W_ ind, I8 old, I8 new )
    350 354
         W_ p;
    
    351 355
         I8 h;
    
    352 356
     
    
    353
    -    ASSERT_IN_BOUNDS(ind, StgArrBytes_bytes(arr));
    
    357
    +    ASSERT_IN_BOUNDS("casInt8Array#", ind, StgArrBytes_bytes(arr));
    
    354 358
         p = arr + SIZEOF_StgArrBytes + ind;
    
    355 359
         (h) = prim %cmpxchg8(p, old, new);
    
    356 360
     
    
    ... ... @@ -364,7 +368,7 @@ stg_casInt16Arrayzh( gcptr arr, W_ ind, I16 old, I16 new )
    364 368
         W_ p;
    
    365 369
         I16 h;
    
    366 370
     
    
    367
    -    ASSERT_IN_BOUNDS(ind + 1, StgArrBytes_bytes(arr));
    
    371
    +    ASSERT_IN_BOUNDS("casInt16Array#", ind + 1, StgArrBytes_bytes(arr));
    
    368 372
         p = arr + SIZEOF_StgArrBytes + ind*2;
    
    369 373
         (h) = prim %cmpxchg16(p, old, new);
    
    370 374
     
    
    ... ... @@ -378,7 +382,7 @@ stg_casInt32Arrayzh( gcptr arr, W_ ind, I32 old, I32 new )
    378 382
         W_ p;
    
    379 383
         I32 h;
    
    380 384
     
    
    381
    -    ASSERT_IN_BOUNDS(ind + 3, StgArrBytes_bytes(arr));
    
    385
    +    ASSERT_IN_BOUNDS("casInt32Array#", ind + 3, StgArrBytes_bytes(arr));
    
    382 386
         p = arr + SIZEOF_StgArrBytes + ind*4;
    
    383 387
         (h) = prim %cmpxchg32(p, old, new);
    
    384 388
     
    
    ... ... @@ -392,7 +396,7 @@ stg_casInt64Arrayzh( gcptr arr, W_ ind, I64 old, I64 new )
    392 396
         W_ p;
    
    393 397
         I64 h;
    
    394 398
     
    
    395
    -    ASSERT_IN_BOUNDS(ind + 7, StgArrBytes_bytes(arr));
    
    399
    +    ASSERT_IN_BOUNDS("casInt64Array#", ind + 7, StgArrBytes_bytes(arr));
    
    396 400
         p = arr + SIZEOF_StgArrBytes + ind*8;
    
    397 401
         (h) = prim %cmpxchg64(p, old, new);
    
    398 402
     
    
    ... ... @@ -470,7 +474,7 @@ stg_casArrayzh ( gcptr arr, W_ ind, gcptr old, gcptr new )
    470 474
         gcptr h;
    
    471 475
         W_ p, len;
    
    472 476
     
    
    473
    -    ASSERT_IN_BOUNDS(ind, StgMutArrPtrs_ptrs(arr));
    
    477
    +    ASSERT_IN_BOUNDS("casArray#", ind, StgMutArrPtrs_ptrs(arr));
    
    474 478
         p = arr + SIZEOF_StgMutArrPtrs + WDS(ind);
    
    475 479
         (h) = prim %cmpxchgW(p, old, new);
    
    476 480
     
    
    ... ... @@ -578,8 +582,8 @@ stg_copySmallArrayzh ( gcptr src, W_ src_off, gcptr dst, W_ dst_off, W_ n)
    578 582
     
    
    579 583
             SET_INFO(dst, stg_SMALL_MUT_ARR_PTRS_DIRTY_info);
    
    580 584
     
    
    581
    -        ASSERT_IN_BOUNDS(dst_off + n - 1, StgSmallMutArrPtrs_ptrs(dst));
    
    582
    -        ASSERT_IN_BOUNDS(src_off + n - 1, StgSmallMutArrPtrs_ptrs(src));
    
    585
    +        ASSERT_IN_BOUNDS("copySmallArray#", dst_off + n - 1, StgSmallMutArrPtrs_ptrs(dst));
    
    586
    +        ASSERT_IN_BOUNDS("copySmallArray#", src_off + n - 1, StgSmallMutArrPtrs_ptrs(src));
    
    583 587
             dst_p = dst + SIZEOF_StgSmallMutArrPtrs + WDS(dst_off);
    
    584 588
             src_p = src + SIZEOF_StgSmallMutArrPtrs + WDS(src_off);
    
    585 589
             bytes = WDS(n);
    
    ... ... @@ -601,8 +605,8 @@ stg_copySmallMutableArrayzh ( gcptr src, W_ src_off, gcptr dst, W_ dst_off, W_ n
    601 605
     
    
    602 606
             SET_INFO(dst, stg_SMALL_MUT_ARR_PTRS_DIRTY_info);
    
    603 607
     
    
    604
    -        ASSERT_IN_BOUNDS(dst_off + n - 1, StgSmallMutArrPtrs_ptrs(dst));
    
    605
    -        ASSERT_IN_BOUNDS(src_off + n - 1, StgSmallMutArrPtrs_ptrs(src));
    
    608
    +        ASSERT_IN_BOUNDS("copySmallMutableArray#", dst_off + n - 1, StgSmallMutArrPtrs_ptrs(dst));
    
    609
    +        ASSERT_IN_BOUNDS("copySmallMutableArray#", src_off + n - 1, StgSmallMutArrPtrs_ptrs(src));
    
    606 610
             dst_p = dst + SIZEOF_StgSmallMutArrPtrs + WDS(dst_off);
    
    607 611
             src_p = src + SIZEOF_StgSmallMutArrPtrs + WDS(src_off);
    
    608 612
             bytes = WDS(n);
    
    ... ... @@ -623,7 +627,7 @@ stg_casSmallArrayzh ( gcptr arr, W_ ind, gcptr old, gcptr new )
    623 627
         gcptr h;
    
    624 628
         W_ p, len;
    
    625 629
     
    
    626
    -    ASSERT_IN_BOUNDS(ind, StgSmallMutArrPtrs_ptrs(arr));
    
    630
    +    ASSERT_IN_BOUNDS("casSmallArray#", ind, StgSmallMutArrPtrs_ptrs(arr));
    
    627 631
         p = arr + SIZEOF_StgSmallMutArrPtrs + WDS(ind);
    
    628 632
         (h) = prim %cmpxchgW(p, old, new);
    
    629 633
     
    

  • rts/RtsMessages.c
    ... ... @@ -352,13 +352,32 @@ rtsBadAlignmentBarf(void)
    352 352
     }
    
    353 353
     
    
    354 354
     void
    
    355
    -rtsOutOfBoundsAccess(void)
    
    355
    +rtsOutOfBoundsAccess(StgInt index, StgWord count, StgWord size, const char *op)
    
    356 356
     {
    
    357
    -    barf("Encountered out of bounds array access.");
    
    357
    +    if (count <= 1) {
    
    358
    +        errorBelch("%s: array access out of bounds:\n"
    
    359
    +                   "    index %" FMT_Int " is not within [0, %" FMT_Word ").\n"
    
    360
    +                   "This is usually caused by incorrect use of unsafe primops "
    
    361
    +                   "in user or library code.",
    
    362
    +                   op, index, size);
    
    363
    +    } else {
    
    364
    +        errorBelch("%s: array access out of bounds:\n"
    
    365
    +                   "    range of %" FMT_Word " elements starting at index %" FMT_Int
    
    366
    +                   " is not within [0, %" FMT_Word ").\n"
    
    367
    +                   "This is usually caused by incorrect use of unsafe primops "
    
    368
    +                   "in user or library code.",
    
    369
    +                   op, count, index, size);
    
    370
    +    }
    
    371
    +    stg_exit(EXIT_FAILURE);
    
    358 372
     }
    
    359 373
     
    
    360 374
     void
    
    361
    -rtsMemcpyRangeOverlap(void)
    
    375
    +rtsMemcpyRangeOverlap(const char *op)
    
    362 376
     {
    
    363
    -    barf("Encountered overlapping source/destination ranges in a memcpy-using op.");
    
    377
    +    errorBelch("%s: overlapping source and destination ranges in a "
    
    378
    +               "memcpy-using operation.\n"
    
    379
    +               "This is usually caused by incorrect use of unsafe primops "
    
    380
    +               "in user or library code.",
    
    381
    +               op);
    
    382
    +    stg_exit(EXIT_FAILURE);
    
    364 383
     }

  • rts/include/rts/Messages.h
    ... ... @@ -108,5 +108,5 @@ extern RtsMsgFunction rtsSysErrorMsgFn;
    108 108
     
    
    109 109
     /* Used by code generator */
    
    110 110
     void rtsBadAlignmentBarf(void) STG_NORETURN;
    
    111
    -void rtsOutOfBoundsAccess(void) STG_NORETURN;
    
    112
    -void rtsMemcpyRangeOverlap(void) STG_NORETURN;
    111
    +void rtsOutOfBoundsAccess(StgInt index, StgWord count, StgWord size, const char *op) STG_NORETURN;
    
    112
    +void rtsMemcpyRangeOverlap(const char *op) STG_NORETURN;

  • testsuite/tests/codeGen/should_fail/T26964.hs
    1
    +{-# LANGUAGE UnboxedTuples #-}
    
    2
    +{-# LANGUAGE MagicHash #-}
    
    3
    +
    
    4
    +-- Test that -fcheck-prim-bounds reports the failing primop, the offending
    
    5
    +-- index and the array size. The negative index also checks that it is reported
    
    6
    +-- as a signed number (e.g. -1, not a huge unsigned word).
    
    7
    +
    
    8
    +module Main where
    
    9
    +
    
    10
    +import GHC.Exts
    
    11
    +import GHC.IO
    
    12
    +
    
    13
    +main :: IO ()
    
    14
    +main = do
    
    15
    +    IO $ \s0 ->
    
    16
    +      case newSmallArray# 5# () s0 of
    
    17
    +        (# s1, marr #) -> readSmallArray# marr (-1#) s1

  • testsuite/tests/codeGen/should_fail/T26964.stderr
    1
    +readSmallArray#: array access out of bounds:
    
    2
    +    index -1 is not within [0, 5).
    
    3
    +This is usually caused by incorrect use of unsafe primops in user or library code.

  • testsuite/tests/codeGen/should_fail/T26964b.hs
    1
    +{-# LANGUAGE UnboxedTuples #-}
    
    2
    +{-# LANGUAGE MagicHash #-}
    
    3
    +
    
    4
    +-- Test that -fcheck-prim-bounds reports a failing *range* access (count > 1)
    
    5
    +-- with the "range of N elements starting at index" wording. The source range
    
    6
    +-- is in bounds, but the destination range [6, 10) overruns the 8-byte array.
    
    7
    +
    
    8
    +module Main where
    
    9
    +
    
    10
    +import GHC.Exts
    
    11
    +import GHC.IO
    
    12
    +
    
    13
    +main :: IO ()
    
    14
    +main = IO $ \s0 ->
    
    15
    +  case newByteArray# 8# s0 of
    
    16
    +    (# s1, src #) ->
    
    17
    +      case newByteArray# 8# s1 of
    
    18
    +        (# s2, dst #) ->
    
    19
    +          case copyMutableByteArray# src 0# dst 6# 4# s2 of
    
    20
    +            s3 -> (# s3, () #)

  • testsuite/tests/codeGen/should_fail/T26964b.stderr
    1
    +copyMutableByteArray#: array access out of bounds:
    
    2
    +    range of 4 elements starting at index 6 is not within [0, 8).
    
    3
    +This is usually caused by incorrect use of unsafe primops in user or library code.

  • testsuite/tests/codeGen/should_fail/all.T
    ... ... @@ -6,8 +6,10 @@ test('T8131', [cmm_src, only_ways(llvm_ways)], compile_fail, ['-no-hs-main'])
    6 6
     
    
    7 7
     def check_bounds_test(name):
    
    8 8
         """ A -fcheck-prim-bounds test that is expected to fail. """
    
    9
    +    # The native backend exits with EXIT_FAILURE (1); the JS backend still
    
    10
    +    # aborts with h$exitProcess(134) (the improved messages are native-only).
    
    9 11
         test(name,
    
    10
    -         [ignore_stderr, omit_ghci, exit_code(127 if opsys('mingw32') else 134)],
    
    12
    +         [ignore_stderr, omit_ghci, exit_code(134 if js_arch() else 1)],
    
    11 13
              compile_and_run, ['-fcheck-prim-bounds'])
    
    12 14
     
    
    13 15
     check_bounds_test('CheckBoundsWriteArray') # Check past end
    
    ... ... @@ -25,3 +27,18 @@ check_bounds_test('CheckBoundsCompareByteArray3') # Check negative length
    25 27
     check_bounds_test('CheckOverlapCopyByteArray')
    
    26 28
     check_bounds_test('CheckOverlapCopyAddrToByteArray')
    
    27 29
     check_bounds_test('T26958')
    
    30
    +
    
    31
    +# Unlike the check_bounds_test cases above, these tests pin down the exact
    
    32
    +# -fcheck-prim-bounds error message. Drop the leading "<prog>: " prefix so the
    
    33
    +# tests don't depend on the binary's name/path (incl. Windows drive letters).
    
    34
    +def strip_prog_prefix(s):
    
    35
    +    return re.sub(r'(?m)^.*?(\w+#: array access)', r'\1', s)
    
    36
    +
    
    37
    +# T26964 checks the single-element message. T26964b checks the range variant.
    
    38
    +# Skipped on the JS backend, which emits no message and aborts with code 134.
    
    39
    +test('T26964',
    
    40
    +     [omit_ghci, js_skip, exit_code(1), normalise_errmsg_fun(strip_prog_prefix)],
    
    41
    +     compile_and_run, ['-fcheck-prim-bounds'])
    
    42
    +test('T26964b',
    
    43
    +     [omit_ghci, js_skip, exit_code(1), normalise_errmsg_fun(strip_prog_prefix)],
    
    44
    +     compile_and_run, ['-fcheck-prim-bounds'])