1 /**
   2  * @test
   3  * @bug 7088367
   4  * @summary SourceDataLine.write and TargetDataLine.read don't throw ArrayIndexOutOfBoundsException
   5  * @author Alex Menkov
   6  */
   7 
   8 import javax.sound.sampled.AudioSystem;
   9 import javax.sound.sampled.DataLine;
  10 import javax.sound.sampled.Line;
  11 import javax.sound.sampled.LineUnavailableException;
  12 import javax.sound.sampled.Mixer;
  13 import javax.sound.sampled.SourceDataLine;
  14 import javax.sound.sampled.TargetDataLine;
  15 
  16 public class DataLine_ArrayIndexOutOfBounds {
  17 
  18     static int total = 0;
  19     static int failed = 0;
  20 
  21     // shared buffer for all tests
  22     static final byte[] buffer = new byte[5000000];
  23 
  24     // the class describes different test scenarios (buffer properties)
  25     static abstract class Scenario {
  26         abstract int getBufferOffset(DataLine line);
  27         abstract int getBufferLength(DataLine line);
  28     }
  29 
  30     // scenarios to tests
  31     static Scenario[] scenarios = new Scenario[]{
  32         new Scenario() {
  33             public String toString() {
  34                 return "offset is near Integer.MAX_VALUE";
  35             }
  36             public int getBufferOffset(DataLine line) {
  37                 return Integer.MAX_VALUE - 4096;
  38             }
  39             public int getBufferLength(DataLine line) {
  40                 return 65536;
  41             }
  42         },
  43         new Scenario() {
  44             public String toString() {
  45                 return "offset is less than buffer.length, length is large";
  46             }
  47             int getBufferOffset(DataLine line) {
  48                 return buffer.length / 10;
  49             }
  50             int getBufferLength(DataLine line) {
  51                 return Integer.MAX_VALUE - getBufferOffset(line) + 4096;
  52             }
  53         }
  54     };
  55 
  56     public static void main(String[] args) throws Exception {
  57         Mixer.Info[] infos = AudioSystem.getMixerInfo();
  58         log("" + infos.length + " mixers detected");
  59         for (int i=0; i<infos.length; i++) {
  60             Mixer mixer = AudioSystem.getMixer(infos[i]);
  61             log("Mixer " + (i+1) + ": " + infos[i]);
  62             try {
  63                 mixer.open();
  64                 for (Scenario scenario: scenarios) {
  65                     testSDL(mixer, scenario);
  66                     testTDL(mixer, scenario);
  67                 }
  68                 mixer.close();
  69             } catch (LineUnavailableException ex) {
  70                 log("LineUnavailableException: " + ex);
  71             }
  72         }
  73         if (failed == 0) {
  74             log("PASSED (" + total + " tests)");
  75         } else {
  76             log("FAILED (" + failed + " of " + total + " tests)");
  77             throw new Exception("Test FAILED");
  78         }
  79     }
  80 
  81     final static int STOPPER_DELAY = 5000;  // 1 sec
  82 
  83     static class AsyncLineStopper implements Runnable {
  84         private final DataLine line;
  85         private final long delayMS;  // delay before stop the line
  86         private final Thread thread;
  87         private final Object readyEvent = new Object();
  88         private final Object startEvent = new Object();
  89 
  90         public AsyncLineStopper(DataLine line, long delayMS) {
  91             this.line = line;
  92             this.delayMS = delayMS;
  93             thread = new Thread(this);
  94             thread.setDaemon(true);
  95             // starts the thread and waits until it becomes ready
  96             synchronized (readyEvent) {
  97                 thread.start();
  98                 try {
  99                     readyEvent.wait();
 100                 } catch (InterruptedException ex) { }
 101             }
 102         }
 103 
 104         // makes the delay and then stops the line
 105         public void schedule() {
 106             synchronized(startEvent) {
 107                 startEvent.notifyAll();
 108             }
 109         }
 110 
 111         // force stop/close the line
 112         public void force() {
 113             thread.interrupt();
 114             try {
 115                 thread.join();
 116             } catch (InterruptedException ex) {
 117                 log("join exception: " + ex);
 118             }
 119         }
 120 
 121         // Runnable implementation
 122         public void run() {
 123             try {
 124                 synchronized(readyEvent) {
 125                     readyEvent.notifyAll();
 126                 }
 127                 synchronized(startEvent) {
 128                     startEvent.wait();
 129                 }
 130                 // delay
 131                 Thread.sleep(delayMS);
 132             } catch (InterruptedException ex) {
 133                 log("    AsyncLineStopper has been interrupted: " + ex);
 134             }
 135             // and flush
 136             log("    stop...");
 137             line.stop();
 138             log("    close...");
 139             line.close();
 140         }
 141     }
 142 
 143     static void testSDL(Mixer mixer, Scenario scenario) {
 144         log("  Testing SDL (scenario: " + scenario + ")...");
 145         Line.Info linfo = new Line.Info(SourceDataLine.class);
 146         SourceDataLine line = null;
 147         try {
 148             line = (SourceDataLine)mixer.getLine(linfo);
 149             log("    got line: " + line);
 150             log("    open...");
 151             line.open();
 152         } catch (IllegalArgumentException ex) {
 153             log("    unsupported (IllegalArgumentException)");
 154             return;
 155         } catch (LineUnavailableException ex) {
 156             log("    unavailable: " + ex);
 157             return;
 158         }
 159 
 160         total++;
 161 
 162         log("    start...");
 163         line.start();
 164 
 165         AsyncLineStopper lineStopper = new AsyncLineStopper(line, STOPPER_DELAY);
 166         int offset = scenario.getBufferOffset(line);
 167         int len = scenario.getBufferLength(line);
 168         // ensure len represents integral number of frames
 169         len -= len % line.getFormat().getFrameSize();
 170 
 171         log("    write...");
 172         lineStopper.schedule();
 173         try {
 174             line.write(buffer, offset, len);
 175             log("    ERROR: didn't get ArrayIndexOutOfBoundsException");
 176             failed++;
 177         } catch (ArrayIndexOutOfBoundsException  ex) {
 178             log("    OK: got ArrayIndexOutOfBoundsException: " + ex);
 179         }
 180         lineStopper.force();
 181     }
 182 
 183     static void testTDL(Mixer mixer, Scenario scenario) {
 184         log("  Testing TDL (scenario: " + scenario + ")...");
 185         Line.Info linfo = new Line.Info(TargetDataLine.class);
 186         TargetDataLine line = null;
 187         try {
 188             line = (TargetDataLine)mixer.getLine(linfo);
 189             log("    got line: " + line);
 190             log("    open...");
 191             line.open();
 192         } catch (IllegalArgumentException ex) {
 193             log("    unsupported (IllegalArgumentException)");
 194             return;
 195         } catch (LineUnavailableException ex) {
 196             log("    unavailable: " + ex);
 197             return;
 198         }
 199 
 200         total++;
 201 
 202         log("    start...");
 203         line.start();
 204 
 205         AsyncLineStopper lineStopper = new AsyncLineStopper(line, STOPPER_DELAY);
 206         int offset = scenario.getBufferOffset(line);
 207         int len = scenario.getBufferLength(line);
 208         // ensure len represents integral number of frames
 209         len -= len % line.getFormat().getFrameSize();
 210 
 211         log("    read...");
 212         try {
 213             line.read(buffer, offset, len);
 214             log("    ERROR: didn't get ArrayIndexOutOfBoundsException");
 215             failed++;
 216         } catch (ArrayIndexOutOfBoundsException  ex) {
 217             log("    OK: got ArrayIndexOutOfBoundsException: " + ex);
 218         }
 219         lineStopper.force();
 220     }
 221 
 222     static void log(String s) {
 223         System.out.println(s);
 224         System.out.flush();
 225     }
 226 }