JDK-4712766 : Channels.newOutputStream stream can fail w/ non-zero offsets
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.nio
  • Affected Version: 1.4.1
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • OS: windows_2000
  • CPU: x86
  • Submitted: 2002-07-10
  • Updated: 2003-04-12
  • Resolved: 2002-09-02
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
1.4.2 mantisFixed
Description

Name: nt126004			Date: 07/10/2002


FULL PRODUCT VERSION :
java version "1.4.1-beta"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1-beta-
b14)
Java HotSpot(TM) Client VM (build 1.4.1-beta-b14, mixed mode)


FULL OPERATING SYSTEM VERSION :
Windows 2000 CZ sp2 (Microsoft Windows
2000 [Verze 5.00.2195])

ADDITIONAL OPERATING SYSTEMS :
Linux


A DESCRIPTION OF THE PROBLEM :
OutputStream subclass returned by
java.nio.channels.Channels.newOutputStream() throws
IllegalArgumentException under certain circumstances on perfectly
legal request.
Problem stems from internally cahced instance of
ByteBuffer used to write byte[] into channel. When setting parameters of
ByteBuffer to match that of array fragment passed to be written to
channel, ByteBuffer's position is incorrectly set BEFORE it's limit.
This results in IllegalArgumentException when one part of array at the
beginning of array is written first, then another part of the same array
which starts after the fist part (+1) in the array. Cached ByteBuffer's
limit is set below start of the second fragment, at the end of the first
fragment, which relults in IllegalArgumentException when trying to
call ByteBuffer.position() with offset higher than current
limit.

  Fix:

In the implementation of anonymous OutputStream
subclass in Channels.newOutputStream(), following two lines must be
swapped to make it work:

       bb.position(off);
       
bb.limit(Math.min(off + len, bb.capacity()));


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Create any WritableByteChannel
2. Call
java.nio.channels.Channels.newOutputStream() on it to get
OutputStream instance (out)
3. let's say we have an_array =
byte[3]
4. call out.write(an_array,0,1)
5. call
out.write(an_array,2,1) -> IllegalArgumentException

See
attached code for a working example.


EXPECTED VERSUS ACTUAL BEHAVIOR :
Bytes should have been written into channel.
Instead,
IllegalArgumentException is thrown.


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.*;
import java.nio.*;
import java.nio.channels.*;

/**
 * Test case
to demonstrate bug in Channels.newOutputStream() in JDK 1.4.0/1.4.1beta.
 * It will create
file testfile.txt in current directory and try to write chunks of byte array
 * to it, using
OutputStream returned by Channels.newOutputStream() .
 *
 * @author Jan Hlavat?,
<###@###.###>
 */
public class NioBug {
    
    public static void main(String[] args) {

        try {
        
        // create test byte buffer and fill it in with data:
        byte[] testbuffer = new byte[3];
        testbuffer[0]=0x31;
        testbuffer[1]=0x32;
        testbuffer[2]=0x33;
        
        // create a Channel
        FileOutputStream ofs = new FileOutputStream("testfile.txt");
        FileChannel fch = ofs.getChannel();
        // create an OutputStream from Channel
        OutputStream out = Channels.newOutputStream(fch);
        
        //------ important part: ------------------
        // write part of array to the output stream, setting limit of cached ByteBuffer instance to 1
        out.write(testbuffer,0,1);
        try {
        // write the same array into output stream again, this time starting beyond limit at offset 2
        out.write(testbuffer,2,1);
        System.out.println("It works OK");
        } catch (IllegalArgumentException e) {
        System.out.println("It failed");    
		// this line gets executed, because cached ByteBuffer's
        // limit is 1 and position(2) will throw exception as it should.
        }
        //----- end of important part -------------
        
        // close OutputStream
        out.close();
        
        // close channel
        fch.close();
        ofs.close();
        

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
}
---------- END SOURCE ----------

CUSTOMER WORKAROUND :
Don't use Channels.newOutputStream(), make equivalent but working
replacement
(Review ID: 159141) 
======================================================================

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: mantis mantis-b02 FIXED IN: mantis mantis-b02 INTEGRATED IN: mantis mantis-b02 VERIFIED IN: mantis
14-06-2004

SUGGESTED FIX Below is correct code for Channels.newOutputStream. ###@###.### 2002-07-11 public static OutputStream newOutputStream(final WritableByteChannel ch) { return new OutputStream() { private ByteBuffer bb = null; private byte[] bs = null; // Invoker's previous array private byte[] b1 = null; public synchronized void write(int b) throws IOException { if (b1 == null) b1 = new byte[1]; b1[0] = (byte)b; this.write(b1); } public synchronized void write(byte[] bs, int off, int len) throws IOException { if ((off < 0) || (off > bs.length) || (len < 0) || ((off + len) > bs.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return; } ByteBuffer bb = ((this.bs == bs) ? this.bb : ByteBuffer.wrap(bs)); bb.limit(Math.min(off + len, bb.capacity())); bb.position(off); this.bb = bb; this.bs = bs; Channels.write(ch, bb); } public void close() throws IOException { ch.close(); } }; }
11-06-2004

EVALUATION Submitter is correct. ###@###.### 2002-07-10
10-07-2002