Made Addrcheck distinguish between invalid reads and invalid writes (previously
was just saying "invalid memory access").

Added a regression test for this, for memcheck and addrcheck.  Also made
Addrcheck use Memcheck's fprw regtest.  Was able to remove the not-very-useful
'true' test for Addrcheck now that it has a couple of real tests.


git-svn-id: svn://svn.valgrind.org/valgrind/trunk@1815 a5019735-40e9-0310-863c-91ae7b9d1cf9
diff --git a/addrcheck/ac_main.c b/addrcheck/ac_main.c
index 2efaa3a..760e219 100644
--- a/addrcheck/ac_main.c
+++ b/addrcheck/ac_main.c
@@ -55,9 +55,11 @@
       case AddrErr:
          switch (err_extra->axskind) {
             case ReadAxs:
+               VG_(message)(Vg_UserMsg, "Invalid read of size %d", 
+                                        err_extra->size ); 
+               break;
             case WriteAxs:
-               /* These two aren't actually differentiated ever. */
-               VG_(message)(Vg_UserMsg, "Invalid memory access of size %d", 
+               VG_(message)(Vg_UserMsg, "Invalid write of size %d", 
                                         err_extra->size ); 
                break;
             case ExecAxs:
@@ -156,10 +158,10 @@
 /*--- Function declarations.                               ---*/
 /*------------------------------------------------------------*/
 
-static void ac_ACCESS4_SLOWLY ( Addr a );
-static void ac_ACCESS2_SLOWLY ( Addr a );
-static void ac_ACCESS1_SLOWLY ( Addr a );
-static void ac_fpu_ACCESS_check_SLOWLY ( Addr addr, Int size );
+static void ac_ACCESS4_SLOWLY ( Addr a, Bool isWrite );
+static void ac_ACCESS2_SLOWLY ( Addr a, Bool isWrite );
+static void ac_ACCESS1_SLOWLY ( Addr a, Bool isWrite );
+static void ac_fpu_ACCESS_check_SLOWLY ( Addr addr, Int size, Bool isWrite );
 
 /*------------------------------------------------------------*/
 /*--- Data defns.                                          ---*/
@@ -682,11 +684,10 @@
    Under all other circumstances, it defers to the relevant _SLOWLY
    function, which can handle all situations.
 */
-__attribute__ ((regparm(1)))
-static void ac_helperc_ACCESS4 ( Addr a )
+static __inline__ void ac_helperc_ACCESS4 ( Addr a, Bool isWrite )
 {
 #  ifdef VG_DEBUG_MEMORY
-   return ac_ACCESS4_SLOWLY(a);
+   return ac_ACCESS4_SLOWLY(a, isWrite);
 #  else
    UInt    sec_no = rotateRight16(a) & 0x3FFFF;
    AcSecMap* sm     = primary_map[sec_no];
@@ -701,16 +702,15 @@
       return;
    } else {
       /* Slow but general case. */
-      ac_ACCESS4_SLOWLY(a);
+      ac_ACCESS4_SLOWLY(a, isWrite);
    }
 #  endif
 }
 
-__attribute__ ((regparm(1)))
-static void ac_helperc_ACCESS2 ( Addr a )
+static __inline__ void ac_helperc_ACCESS2 ( Addr a, Bool isWrite )
 {
 #  ifdef VG_DEBUG_MEMORY
-   return ac_ACCESS2_SLOWLY(a);
+   return ac_ACCESS2_SLOWLY(a, isWrite);
 #  else
    UInt    sec_no = rotateRight16(a) & 0x1FFFF;
    AcSecMap* sm     = primary_map[sec_no];
@@ -721,16 +721,15 @@
       return;
    } else {
       /* Slow but general case. */
-      ac_ACCESS2_SLOWLY(a);
+      ac_ACCESS2_SLOWLY(a, isWrite);
    }
 #  endif
 }
 
-__attribute__ ((regparm(1)))
-static void ac_helperc_ACCESS1 ( Addr a )
+static __inline__ void ac_helperc_ACCESS1 ( Addr a, Bool isWrite )
 {
 #  ifdef VG_DEBUG_MEMORY
-   return ac_ACCESS1_SLOWLY(a);
+   return ac_ACCESS1_SLOWLY(a, isWrite);
 #  else
    UInt    sec_no = shiftRight16(a);
    AcSecMap* sm   = primary_map[sec_no];
@@ -741,18 +740,51 @@
       return;
    } else {
       /* Slow but general case. */
-      ac_ACCESS1_SLOWLY(a);
+      ac_ACCESS1_SLOWLY(a, isWrite);
    }
 #  endif
 }
 
+__attribute__ ((regparm(1)))
+static void ac_helperc_LOAD4 ( Addr a )
+{
+   ac_helperc_ACCESS4 ( a, /*isWrite*/False );
+}
+__attribute__ ((regparm(1)))
+static void ac_helperc_STORE4 ( Addr a )
+{
+   ac_helperc_ACCESS4 ( a, /*isWrite*/True );
+}
+
+__attribute__ ((regparm(1)))
+static void ac_helperc_LOAD2 ( Addr a )
+{
+   ac_helperc_ACCESS2 ( a, /*isWrite*/False );
+}
+__attribute__ ((regparm(1)))
+static void ac_helperc_STORE2 ( Addr a )
+{
+   ac_helperc_ACCESS2 ( a, /*isWrite*/True );
+}
+
+__attribute__ ((regparm(1)))
+static void ac_helperc_LOAD1 ( Addr a )
+{
+   ac_helperc_ACCESS1 ( a, /*isWrite*/False );
+}
+__attribute__ ((regparm(1)))
+static void ac_helperc_STORE1 ( Addr a )
+{
+   ac_helperc_ACCESS1 ( a, /*isWrite*/True );
+}
+
 
 /*------------------------------------------------------------*/
 /*--- Fallback functions to handle cases that the above    ---*/
-/*--- VG_(helperc_ACCESS{1,2,4}) can't manage.             ---*/
+/*--- ac_helperc_ACCESS{1,2,4} can't manage.               ---*/
 /*------------------------------------------------------------*/
 
-static void ac_ACCESS4_SLOWLY ( Addr a )
+static void ac_ACCESS4_SLOWLY ( Addr a, Bool isWrite )
 {
    Bool a0ok, a1ok, a2ok, a3ok;
 
@@ -781,7 +813,7 @@
    if (!MAC_(clo_partial_loads_ok) 
        || ((a & 3) != 0)
        || (!a0ok && !a1ok && !a2ok && !a3ok)) {
-      MAC_(record_address_error)( VG_(get_current_tid)(), a, 4, False );
+      MAC_(record_address_error)( VG_(get_current_tid)(), a, 4, isWrite );
       return;
    }
 
@@ -797,7 +829,7 @@
    }
 }
 
-static void ac_ACCESS2_SLOWLY ( Addr a )
+static void ac_ACCESS2_SLOWLY ( Addr a, Bool isWrite )
 {
    /* Check the address for validity. */
    Bool aerr = False;
@@ -808,11 +840,11 @@
 
    /* If an address error has happened, report it. */
    if (aerr) {
-      MAC_(record_address_error)( VG_(get_current_tid)(), a, 2, False );
+      MAC_(record_address_error)( VG_(get_current_tid)(), a, 2, isWrite );
    }
 }
 
-static void ac_ACCESS1_SLOWLY ( Addr a )
+static void ac_ACCESS1_SLOWLY ( Addr a, Bool isWrite)
 {
    /* Check the address for validity. */
    Bool aerr = False;
@@ -822,7 +854,7 @@
 
    /* If an address error has happened, report it. */
    if (aerr) {
-      MAC_(record_address_error)( VG_(get_current_tid)(), a, 1, False );
+      MAC_(record_address_error)( VG_(get_current_tid)(), a, 1, isWrite );
    }
 }
 
@@ -831,8 +863,8 @@
    FPU load and store checks, called from generated code.
    ------------------------------------------------------------------ */
 
-__attribute__ ((regparm(2)))
-static void ac_fpu_ACCESS_check ( Addr addr, Int size )
+static __inline__ 
+void ac_fpu_ACCESS_check ( Addr addr, Int size, Bool isWrite )
 {
    /* Ensure the read area is both addressible and valid (ie,
       readable).  If there's an address error, don't report a value
@@ -849,7 +881,7 @@
    PROF_EVENT(90);
 
 #  ifdef VG_DEBUG_MEMORY
-   ac_fpu_ACCESS_check_SLOWLY ( addr, size );
+   ac_fpu_ACCESS_check_SLOWLY ( addr, size, isWrite );
 #  else
 
    if (size == 4) {
@@ -863,7 +895,7 @@
       /* Properly aligned and addressible. */
       return;
      slow4:
-      ac_fpu_ACCESS_check_SLOWLY ( addr, 4 );
+      ac_fpu_ACCESS_check_SLOWLY ( addr, 4, isWrite );
       return;
    }
 
@@ -887,7 +919,7 @@
       /* Both halves properly aligned and addressible. */
       return;
      slow8:
-      ac_fpu_ACCESS_check_SLOWLY ( addr, 8 );
+      ac_fpu_ACCESS_check_SLOWLY ( addr, 8, isWrite );
       return;
    }
 
@@ -895,13 +927,13 @@
       cases go quickly.  */
    if (size == 2) {
       PROF_EVENT(93);
-      ac_fpu_ACCESS_check_SLOWLY ( addr, 2 );
+      ac_fpu_ACCESS_check_SLOWLY ( addr, 2, isWrite );
       return;
    }
 
    if (size == 16 || size == 10 || size == 28 || size == 108) {
       PROF_EVENT(94);
-      ac_fpu_ACCESS_check_SLOWLY ( addr, size );
+      ac_fpu_ACCESS_check_SLOWLY ( addr, size, isWrite );
       return;
    }
 
@@ -910,12 +942,23 @@
 #  endif
 }
 
+__attribute__ ((regparm(2)))
+static void ac_fpu_READ_check ( Addr addr, Int size )
+{
+   ac_fpu_ACCESS_check ( addr, size, /*isWrite*/False );
+}
+
+__attribute__ ((regparm(2)))
+static void ac_fpu_WRITE_check ( Addr addr, Int size )
+{
+   ac_fpu_ACCESS_check ( addr, size, /*isWrite*/True );
+}
 
 /* ---------------------------------------------------------------------
    Slow, general cases for FPU access checks.
    ------------------------------------------------------------------ */
 
-void ac_fpu_ACCESS_check_SLOWLY ( Addr addr, Int size )
+void ac_fpu_ACCESS_check_SLOWLY ( Addr addr, Int size, Bool isWrite )
 {
    Int  i;
    Bool aerr = False;
@@ -927,7 +970,7 @@
    }
 
    if (aerr) {
-      MAC_(record_address_error)( VG_(get_current_tid)(), addr, size, False );
+      MAC_(record_address_error)( VG_(get_current_tid)(), addr, size, isWrite );
    }
 }
 
@@ -945,6 +988,7 @@
    Int         i;
    UInstr*     u_in;
    Int         t_addr, t_size;
+   Addr        helper;
 
    cb = VG_(setup_UCodeBlock)(cb_in);
 
@@ -960,28 +1004,33 @@
          /* For memory-ref instrs, copy the data_addr into a temporary to be
           * passed to the helper at the end of the instruction.
           */
-         case LOAD: 
-            t_addr = u_in->val1; 
-            goto do_LOAD_or_STORE;
-         case STORE: t_addr = u_in->val2;
-            goto do_LOAD_or_STORE;
-           do_LOAD_or_STORE:
-            uInstr1(cb, CCALL, 0, TempReg, t_addr);
+         case LOAD:
             switch (u_in->size) {
-               case 4: uCCall(cb, (Addr) & ac_helperc_ACCESS4, 1, 1, False );
-                  break;
-               case 2: uCCall(cb, (Addr) & ac_helperc_ACCESS2, 1, 1, False );
-                  break;
-               case 1: uCCall(cb, (Addr) & ac_helperc_ACCESS1, 1, 1, False );
-                  break;
-               default: 
-                  VG_(skin_panic)("addrcheck::SK_(instrument):LOAD/STORE");
+               case 4:  helper = (Addr)ac_helperc_LOAD4; break;
+               case 2:  helper = (Addr)ac_helperc_LOAD2; break;
+               case 1:  helper = (Addr)ac_helperc_LOAD1; break;
+               default: VG_(skin_panic)("addrcheck::SK_(instrument):LOAD");
             }
+            uInstr1(cb, CCALL, 0, TempReg, u_in->val1);
+            uCCall (cb, helper, 1, 1, False );
+            VG_(copy_UInstr)(cb, u_in);
+            break;
+
+         case STORE:
+            switch (u_in->size) {
+               case 4:  helper = (Addr)ac_helperc_STORE4; break;
+               case 2:  helper = (Addr)ac_helperc_STORE2; break;
+               case 1:  helper = (Addr)ac_helperc_STORE1; break;
+               default: VG_(skin_panic)("addrcheck::SK_(instrument):STORE");
+            }
+            uInstr1(cb, CCALL, 0, TempReg, u_in->val2);
+            uCCall (cb, helper, 1, 1, False );
             VG_(copy_UInstr)(cb, u_in);
             break;
 
 	 case SSE3ag_MemRd_RegWr:
             sk_assert(u_in->size == 4 || u_in->size == 8);
+            helper = (Addr)ac_fpu_READ_check;
 	    goto do_Access_ARG1;
          do_Access_ARG1:
 	    sk_assert(u_in->tag1 == TempReg);
@@ -990,16 +1039,23 @@
 	    uInstr2(cb, MOV, 4, Literal, 0, TempReg, t_size);
 	    uLiteral(cb, u_in->size);
             uInstr2(cb, CCALL, 0, TempReg, t_addr, TempReg, t_size);
-            uCCall(cb, (Addr) & ac_fpu_ACCESS_check, 2, 2, False );
+            uCCall(cb, helper, 2, 2, False );
             VG_(copy_UInstr)(cb, u_in);
             break;
 
          case MMX2_MemRd:
+            sk_assert(u_in->size == 4 || u_in->size == 8);
+            helper = (Addr)ac_fpu_READ_check;
+	    goto do_Access_ARG2;
          case MMX2_MemWr:
             sk_assert(u_in->size == 4 || u_in->size == 8);
+            helper = (Addr)ac_fpu_WRITE_check;
 	    goto do_Access_ARG2;
          case FPU_R:
+            helper = (Addr)ac_fpu_READ_check;
+            goto do_Access_ARG2;
          case FPU_W:
+            helper = (Addr)ac_fpu_WRITE_check;
             goto do_Access_ARG2;
          do_Access_ARG2:
 	    sk_assert(u_in->tag2 == TempReg);
@@ -1008,25 +1064,27 @@
 	    uInstr2(cb, MOV, 4, Literal, 0, TempReg, t_size);
 	    uLiteral(cb, u_in->size);
             uInstr2(cb, CCALL, 0, TempReg, t_addr, TempReg, t_size);
-            uCCall(cb, (Addr) & ac_fpu_ACCESS_check, 2, 2, False );
+            uCCall(cb, helper, 2, 2, False );
             VG_(copy_UInstr)(cb, u_in);
             break;
 
          case SSE3a_MemRd: // this one causes trouble
          case SSE2a_MemRd:
+            helper = (Addr)ac_fpu_READ_check;
+	    goto do_Access_ARG3;
          case SSE2a_MemWr:
 	 case SSE3a_MemWr:
-	    sk_assert(u_in->size == 4 || u_in->size == 8 
-                      || u_in->size == 16);
+            helper = (Addr)ac_fpu_WRITE_check;
 	    goto do_Access_ARG3;
          do_Access_ARG3:
+	    sk_assert(u_in->size == 4 || u_in->size == 8 || u_in->size == 16);
             sk_assert(u_in->tag3 == TempReg);
             t_addr = u_in->val3;
             t_size = newTemp(cb);
 	    uInstr2(cb, MOV, 4, Literal, 0, TempReg, t_size);
 	    uLiteral(cb, u_in->size);
             uInstr2(cb, CCALL, 0, TempReg, t_addr, TempReg, t_size);
-            uCCall(cb, (Addr) & ac_fpu_ACCESS_check, 2, 2, False );
+            uCCall(cb, helper, 2, 2, False );
             VG_(copy_UInstr)(cb, u_in);
             break;
 
@@ -1288,10 +1346,14 @@
    VG_(track_pre_mem_write)        ( & ac_check_is_writable );
    VG_(track_post_mem_write)       ( & ac_make_accessible );
 
-   VG_(register_compact_helper)((Addr) & ac_helperc_ACCESS4);
-   VG_(register_compact_helper)((Addr) & ac_helperc_ACCESS2);
-   VG_(register_compact_helper)((Addr) & ac_helperc_ACCESS1);
-   VG_(register_compact_helper)((Addr) & ac_fpu_ACCESS_check);
+   VG_(register_compact_helper)((Addr) & ac_helperc_LOAD4);
+   VG_(register_compact_helper)((Addr) & ac_helperc_LOAD2);
+   VG_(register_compact_helper)((Addr) & ac_helperc_LOAD1);
+   VG_(register_compact_helper)((Addr) & ac_helperc_STORE4);
+   VG_(register_compact_helper)((Addr) & ac_helperc_STORE2);
+   VG_(register_compact_helper)((Addr) & ac_helperc_STORE1);
+   VG_(register_noncompact_helper)((Addr) & ac_fpu_READ_check);
+   VG_(register_noncompact_helper)((Addr) & ac_fpu_WRITE_check);
 
    VGP_(register_profile_event) ( VgpSetMem,   "set-mem-perms" );
    VGP_(register_profile_event) ( VgpCheckMem, "check-mem-perms" );
diff --git a/addrcheck/tests/Makefile.am b/addrcheck/tests/Makefile.am
index e586af5..cf28101 100644
--- a/addrcheck/tests/Makefile.am
+++ b/addrcheck/tests/Makefile.am
@@ -1,4 +1,5 @@
 noinst_SCRIPTS = filter_stderr
 
 EXTRA_DIST = $(noinst_SCRIPTS) \
-	true.stderr.exp true.vgtest
+	badrw.stderr.exp badrw.vgtest \
+	fprw.stderr.exp fprw.vgtest
diff --git a/addrcheck/tests/badrw.stderr.exp b/addrcheck/tests/badrw.stderr.exp
new file mode 100644
index 0000000..c231c5d
--- /dev/null
+++ b/addrcheck/tests/badrw.stderr.exp
@@ -0,0 +1,59 @@
+Invalid read of size 4
+   at 0x........: main (badrw.c:12)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+   Address 0x........ is 4 bytes before a block of size 10 alloc'd
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: main (badrw.c:5)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+
+Invalid write of size 4
+   at 0x........: main (badrw.c:12)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+   Address 0x........ is 4 bytes before a block of size 10 alloc'd
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: main (badrw.c:5)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+
+Invalid read of size 2
+   at 0x........: main (badrw.c:13)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+   Address 0x........ is 4 bytes before a block of size 10 alloc'd
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: main (badrw.c:5)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+
+Invalid write of size 2
+   at 0x........: main (badrw.c:13)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+   Address 0x........ is 4 bytes before a block of size 10 alloc'd
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: main (badrw.c:5)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+
+Invalid read of size 1
+   at 0x........: main (badrw.c:14)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+   Address 0x........ is 1 bytes before a block of size 10 alloc'd
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: main (badrw.c:5)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+
+Invalid write of size 1
+   at 0x........: main (badrw.c:14)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+   Address 0x........ is 1 bytes before a block of size 10 alloc'd
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: main (badrw.c:5)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
diff --git a/addrcheck/tests/badrw.vgtest b/addrcheck/tests/badrw.vgtest
new file mode 100644
index 0000000..6f68301
--- /dev/null
+++ b/addrcheck/tests/badrw.vgtest
@@ -0,0 +1,2 @@
+vgopts: -q
+prog: ../../memcheck/tests/badrw
diff --git a/addrcheck/tests/fprw.stderr.exp b/addrcheck/tests/fprw.stderr.exp
new file mode 100644
index 0000000..41f8ada
--- /dev/null
+++ b/addrcheck/tests/fprw.stderr.exp
@@ -0,0 +1,56 @@
+Invalid read of size 8
+   at 0x........: main (fprw.c:20)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+   Address 0x........ is 0 bytes inside a block of size 8 free'd
+   at 0x........: free (vg_replace_malloc.c:...)
+   by 0x........: main (fprw.c:18)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+
+Invalid write of size 8
+   at 0x........: main (fprw.c:20)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+   Address 0x........ is 0 bytes inside a block of size 8 free'd
+   at 0x........: free (vg_replace_malloc.c:...)
+   by 0x........: main (fprw.c:18)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+
+Invalid read of size 4
+   at 0x........: main (fprw.c:21)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+   Address 0x........ is 0 bytes inside a block of size 4 free'd
+   at 0x........: free (vg_replace_malloc.c:...)
+   by 0x........: main (fprw.c:19)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+
+Invalid write of size 4
+   at 0x........: main (fprw.c:21)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+   Address 0x........ is 0 bytes inside a block of size 4 free'd
+   at 0x........: free (vg_replace_malloc.c:...)
+   by 0x........: main (fprw.c:19)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+
+Invalid free() / delete / delete[]
+   at 0x........: free (vg_replace_malloc.c:...)
+   by 0x........: main (fprw.c:22)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+   Address 0x........ is not stack'd, malloc'd or free'd
+
+Invalid write of size 8
+   at 0x........: main (fprw.c:24)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+   Address 0x........ is 0 bytes inside a block of size 4 alloc'd
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: main (fprw.c:23)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
diff --git a/addrcheck/tests/fprw.vgtest b/addrcheck/tests/fprw.vgtest
new file mode 100644
index 0000000..9db266e
--- /dev/null
+++ b/addrcheck/tests/fprw.vgtest
@@ -0,0 +1,2 @@
+vgopts: --single-step=yes -q
+prog:   ../../memcheck/tests/fprw
diff --git a/addrcheck/tests/true.stderr.exp b/addrcheck/tests/true.stderr.exp
deleted file mode 100644
index e69de29..0000000
--- a/addrcheck/tests/true.stderr.exp
+++ /dev/null
diff --git a/addrcheck/tests/true.vgtest b/addrcheck/tests/true.vgtest
deleted file mode 100644
index 5c1535c..0000000
--- a/addrcheck/tests/true.vgtest
+++ /dev/null
@@ -1,2 +0,0 @@
-prog: ../../tests/true
-vgopts: -q
diff --git a/memcheck/tests/Makefile.am b/memcheck/tests/Makefile.am
index f2ccf5b..7573eda 100644
--- a/memcheck/tests/Makefile.am
+++ b/memcheck/tests/Makefile.am
@@ -14,6 +14,7 @@
 	badfree.stderr.exp badfree.vgtest \
 	badjump.stderr.exp badjump.vgtest \
 	badloop.stderr.exp badloop.vgtest \
+	badrw.stderr.exp badrw.vgtest \
 	brk.stderr.exp brk.vgtest \
 	buflen_check.stderr.exp buflen_check.vgtest \
 	clientperm.stderr.exp \
@@ -62,7 +63,7 @@
 	writev.stderr.exp writev.vgtest
 
 check_PROGRAMS = \
-	badaddrvalue badfree badjump badloop brk buflen_check \
+	badaddrvalue badfree badjump badloop badrw brk buflen_check \
 	clientperm custom_alloc \
 	doublefree error_counts errs1 exitprog fprw fwrite inits inline \
 	malloc1 malloc2 malloc3 manuel1 manuel2 manuel3 \
@@ -81,6 +82,7 @@
 badfree_SOURCES 	= badfree.c
 badjump_SOURCES 	= badjump.c
 badloop_SOURCES 	= badloop.c
+badrw_SOURCES		= badrw.c
 brk_SOURCES 		= brk.c
 buflen_check_SOURCES	= buflen_check.c
 clientperm_SOURCES 	= clientperm.c
diff --git a/memcheck/tests/badrw.c b/memcheck/tests/badrw.c
new file mode 100644
index 0000000..b72f393
--- /dev/null
+++ b/memcheck/tests/badrw.c
@@ -0,0 +1,17 @@
+#include <stdlib.h>
+
+int main(void)
+{
+   void* x = malloc(10);
+
+   int*        x4 = x-4;
+   short int*  x2 = x-4;
+   char*       x1 = x-1;
+
+   // Invalid reads and writes of sizes 4, 2, 1
+   int       y4 = *x4;  *x4 = y4;
+   short int y2 = *x2;  *x2 = y2;
+   char      y1 = *x1;  *x1 = y1;
+   
+   return 0;
+}
diff --git a/memcheck/tests/badrw.stderr.exp b/memcheck/tests/badrw.stderr.exp
new file mode 100644
index 0000000..c231c5d
--- /dev/null
+++ b/memcheck/tests/badrw.stderr.exp
@@ -0,0 +1,59 @@
+Invalid read of size 4
+   at 0x........: main (badrw.c:12)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+   Address 0x........ is 4 bytes before a block of size 10 alloc'd
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: main (badrw.c:5)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+
+Invalid write of size 4
+   at 0x........: main (badrw.c:12)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+   Address 0x........ is 4 bytes before a block of size 10 alloc'd
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: main (badrw.c:5)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+
+Invalid read of size 2
+   at 0x........: main (badrw.c:13)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+   Address 0x........ is 4 bytes before a block of size 10 alloc'd
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: main (badrw.c:5)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+
+Invalid write of size 2
+   at 0x........: main (badrw.c:13)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+   Address 0x........ is 4 bytes before a block of size 10 alloc'd
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: main (badrw.c:5)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+
+Invalid read of size 1
+   at 0x........: main (badrw.c:14)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+   Address 0x........ is 1 bytes before a block of size 10 alloc'd
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: main (badrw.c:5)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+
+Invalid write of size 1
+   at 0x........: main (badrw.c:14)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
+   Address 0x........ is 1 bytes before a block of size 10 alloc'd
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: main (badrw.c:5)
+   by 0x........: __libc_start_main (...libc...)
+   by 0x........: ...
diff --git a/memcheck/tests/badrw.vgtest b/memcheck/tests/badrw.vgtest
new file mode 100644
index 0000000..09c70a0
--- /dev/null
+++ b/memcheck/tests/badrw.vgtest
@@ -0,0 +1,2 @@
+prog: badrw
+vgopts: -q