Simon Jakobi pushed to branch wip/sjakobi/T26964 at Glasgow Haskell Compiler / GHC
Commits:
-
64b4252e
by Simon Jakobi at 2026-06-04T01:37:58+02:00
-
6bf57c99
by Simon Jakobi at 2026-06-04T01:37:58+02:00
12 changed files:
- + changelog.d/improve-check-prim-bounds-messages
- compiler/GHC/StgToCmm/Monad.hs
- compiler/GHC/StgToCmm/Prim.hs
- docs/users_guide/debugging.rst
- rts/PrimOps.cmm
- rts/RtsMessages.c
- rts/include/rts/Messages.h
- + testsuite/tests/codeGen/should_fail/T26964.hs
- + testsuite/tests/codeGen/should_fail/T26964.stderr
- + testsuite/tests/codeGen/should_fail/T26964b.hs
- + testsuite/tests/codeGen/should_fail/T26964b.stderr
- testsuite/tests/codeGen/should_fail/all.T
Changes:
| 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 | +} |
| ... | ... | @@ -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 |
| ... | ... | @@ -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)
|
| ... | ... | @@ -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.
|
| ... | ... | @@ -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 |
| ... | ... | @@ -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 | } |
| ... | ... | @@ -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; |
| 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 |
| 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. |
| 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, () #) |
| 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. |
| ... | ... | @@ -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']) |