Revert "ByteProcessor shouldn't throw checked exception"

This reverts commit b70d0fa6e3.
This commit is contained in:
Chris Vest 2020-11-03 16:12:54 +01:00
parent b70d0fa6e3
commit ff2e790e89
16 changed files with 128 additions and 72 deletions

View File

@ -1262,37 +1262,58 @@ public abstract class AbstractByteBuf extends ByteBuf {
@Override @Override
public int forEachByte(ByteProcessor processor) { public int forEachByte(ByteProcessor processor) {
ensureAccessible(); ensureAccessible();
try {
return forEachByteAsc0(readerIndex, writerIndex, processor); return forEachByteAsc0(readerIndex, writerIndex, processor);
} catch (Exception e) {
PlatformDependent.throwException(e);
return -1;
}
} }
@Override @Override
public int forEachByte(int index, int length, ByteProcessor processor) { public int forEachByte(int index, int length, ByteProcessor processor) {
checkIndex(index, length); checkIndex(index, length);
try {
return forEachByteAsc0(index, index + length, processor); return forEachByteAsc0(index, index + length, processor);
} catch (Exception e) {
PlatformDependent.throwException(e);
return -1;
}
} }
int forEachByteAsc0(int start, int end, ByteProcessor processor) { int forEachByteAsc0(int start, int end, ByteProcessor processor) throws Exception {
for (; start < end; ++start) { for (; start < end; ++start) {
if (!processor.process(_getByte(start))) { if (!processor.process(_getByte(start))) {
return start; return start;
} }
} }
return -1; return -1;
} }
@Override @Override
public int forEachByteDesc(ByteProcessor processor) { public int forEachByteDesc(ByteProcessor processor) {
ensureAccessible(); ensureAccessible();
try {
return forEachByteDesc0(writerIndex - 1, readerIndex, processor); return forEachByteDesc0(writerIndex - 1, readerIndex, processor);
} catch (Exception e) {
PlatformDependent.throwException(e);
return -1;
}
} }
@Override @Override
public int forEachByteDesc(int index, int length, ByteProcessor processor) { public int forEachByteDesc(int index, int length, ByteProcessor processor) {
checkIndex(index, length); checkIndex(index, length);
try {
return forEachByteDesc0(index + length - 1, index, processor); return forEachByteDesc0(index + length - 1, index, processor);
} catch (Exception e) {
PlatformDependent.throwException(e);
return -1;
}
} }
int forEachByteDesc0(int rStart, final int rEnd, ByteProcessor processor) { int forEachByteDesc0(int rStart, final int rEnd, ByteProcessor processor) throws Exception {
for (; rStart >= rEnd; --rStart) { for (; rStart >= rEnd; --rStart) {
if (!processor.process(_getByte(rStart))) { if (!processor.process(_getByte(rStart))) {
return rStart; return rStart;

View File

@ -660,7 +660,7 @@ public class CompositeByteBuf extends AbstractReferenceCountedByteBuf implements
} }
@Override @Override
protected int forEachByteAsc0(int start, int end, ByteProcessor processor) { protected int forEachByteAsc0(int start, int end, ByteProcessor processor) throws Exception {
if (end <= start) { if (end <= start) {
return -1; return -1;
} }
@ -686,7 +686,7 @@ public class CompositeByteBuf extends AbstractReferenceCountedByteBuf implements
} }
@Override @Override
protected int forEachByteDesc0(int rStart, int rEnd, ByteProcessor processor) { protected int forEachByteDesc0(int rStart, int rEnd, ByteProcessor processor) throws Exception {
if (rEnd > rStart) { // rStart *and* rEnd are inclusive if (rEnd > rStart) { // rStart *and* rEnd are inclusive
return -1; return -1;
} }

View File

@ -2284,7 +2284,7 @@ public abstract class AbstractByteBufTest {
int i = CAPACITY / 4; int i = CAPACITY / 4;
@Override @Override
public boolean process(byte value) { public boolean process(byte value) throws Exception {
assertThat(value, is((byte) (i + 1))); assertThat(value, is((byte) (i + 1)));
lastIndex.set(i); lastIndex.set(i);
i ++; i ++;
@ -2307,7 +2307,7 @@ public abstract class AbstractByteBufTest {
int i = CAPACITY / 3; int i = CAPACITY / 3;
@Override @Override
public boolean process(byte value) { public boolean process(byte value) throws Exception {
assertThat(value, is((byte) (i + 1))); assertThat(value, is((byte) (i + 1)));
if (i == stop) { if (i == stop) {
return false; return false;
@ -4608,7 +4608,7 @@ public abstract class AbstractByteBufTest {
private int index = bytes.length - 1; private int index = bytes.length - 1;
@Override @Override
public boolean process(byte value) { public boolean process(byte value) throws Exception {
bytes[index--] = value; bytes[index--] = value;
return true; return true;
} }
@ -4631,7 +4631,7 @@ public abstract class AbstractByteBufTest {
private int index; private int index;
@Override @Override
public boolean process(byte value) { public boolean process(byte value) throws Exception {
bytes[index++] = value; bytes[index++] = value;
return true; return true;
} }
@ -4804,7 +4804,7 @@ public abstract class AbstractByteBufTest {
private static final class TestByteProcessor implements ByteProcessor { private static final class TestByteProcessor implements ByteProcessor {
@Override @Override
public boolean process(byte value) { public boolean process(byte value) throws Exception {
return true; return true;
} }
} }

View File

@ -24,6 +24,7 @@ import io.netty.handler.codec.HeadersUtils;
import io.netty.handler.codec.ValueConverter; import io.netty.handler.codec.ValueConverter;
import io.netty.util.AsciiString; import io.netty.util.AsciiString;
import io.netty.util.ByteProcessor; import io.netty.util.ByteProcessor;
import io.netty.util.internal.PlatformDependent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
@ -49,10 +50,14 @@ public class DefaultHttpHeaders extends HttpHeaders {
}; };
static final NameValidator<CharSequence> HttpNameValidator = name -> { static final NameValidator<CharSequence> HttpNameValidator = name -> {
if (name == null || name.length() == 0) { if (name == null || name.length() == 0) {
throw new IllegalArgumentException("empty headers are not allowed [" + name + ']'); throw new IllegalArgumentException("empty headers are not allowed [" + name + "]");
} }
if (name instanceof AsciiString) { if (name instanceof AsciiString) {
try {
((AsciiString) name).forEachByte(HEADER_NAME_VALIDATOR); ((AsciiString) name).forEachByte(HEADER_NAME_VALIDATOR);
} catch (Exception e) {
PlatformDependent.throwException(e);
}
} else { } else {
// Go through each character in the name // Go through each character in the name
for (int index = 0; index < name.length(); ++index) { for (int index = 0; index < name.length(); ++index) {
@ -68,7 +73,7 @@ public class DefaultHttpHeaders extends HttpHeaders {
} }
/** /**
* <b>Warning!</b> Setting {@code validate} to {@code false} will mean that Netty won't * <b>Warning!</b> Setting <code>validate</code> to <code>false</code> will mean that Netty won't
* validate & protect against user-supplied header values that are malicious. * validate & protect against user-supplied header values that are malicious.
* This can leave your server implementation vulnerable to * This can leave your server implementation vulnerable to
* <a href="https://cwe.mitre.org/data/definitions/113.html"> * <a href="https://cwe.mitre.org/data/definitions/113.html">

View File

@ -936,7 +936,7 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
} }
@Override @Override
public boolean process(byte value) { public boolean process(byte value) throws Exception {
char nextByte = (char) (value & 0xFF); char nextByte = (char) (value & 0xFF);
if (nextByte == HttpConstants.LF) { if (nextByte == HttpConstants.LF) {
int len = seq.length(); int len = seq.length();
@ -983,7 +983,7 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
} }
@Override @Override
public boolean process(byte value) { public boolean process(byte value) throws Exception {
if (currentState == State.SKIP_CONTROL_CHARS) { if (currentState == State.SKIP_CONTROL_CHARS) {
char c = (char) (value & 0xFF); char c = (char) (value & 0xFF);
if (Character.isISOControl(c) || Character.isWhitespace(c)) { if (Character.isISOControl(c) || Character.isWhitespace(c)) {

View File

@ -706,7 +706,7 @@ public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestD
private static final class UrlEncodedDetector implements ByteProcessor { private static final class UrlEncodedDetector implements ByteProcessor {
@Override @Override
public boolean process(byte value) { public boolean process(byte value) throws Exception {
return value != '%' && value != '+'; return value != '%' && value != '+';
} }
} }

View File

@ -84,7 +84,7 @@ final class Utf8Validator implements ByteProcessor {
} }
@Override @Override
public boolean process(byte b) { public boolean process(byte b) throws Exception {
byte type = TYPES[b & 0xFF]; byte type = TYPES[b & 0xFF];
codep = state != UTF8_ACCEPT ? b & 0x3f | codep << 6 : 0xff >> type & b; codep = state != UTF8_ACCEPT ? b & 0x3f | codep << 6 : 0xff >> type & b;

View File

@ -38,7 +38,18 @@ public class DefaultHttp2Headers
"empty headers are not allowed [%s]", name)); "empty headers are not allowed [%s]", name));
} }
if (name instanceof AsciiString) { if (name instanceof AsciiString) {
int index = ((AsciiString) name).forEachByte(HTTP2_NAME_VALIDATOR_PROCESSOR); final int index;
try {
index = ((AsciiString) name).forEachByte(HTTP2_NAME_VALIDATOR_PROCESSOR);
} catch (Http2Exception e) {
PlatformDependent.throwException(e);
return;
} catch (Throwable t) {
PlatformDependent.throwException(connectionError(PROTOCOL_ERROR, t,
"unexpected error. invalid header name [%s]", name));
return;
}
if (index != -1) { if (index != -1) {
PlatformDependent.throwException(connectionError(PROTOCOL_ERROR, PlatformDependent.throwException(connectionError(PROTOCOL_ERROR,
"invalid header name [%s]", name)); "invalid header name [%s]", name));
@ -98,7 +109,7 @@ public class DefaultHttp2Headers
@Override @Override
public Http2Headers clear() { public Http2Headers clear() {
firstNonPseudo = head; this.firstNonPseudo = head;
return super.clear(); return super.clear();
} }

View File

@ -70,9 +70,11 @@ final class HpackHuffmanEncoder {
requireNonNull(out, "out"); requireNonNull(out, "out");
if (data instanceof AsciiString) { if (data instanceof AsciiString) {
AsciiString string = (AsciiString) data; AsciiString string = (AsciiString) data;
encodeProcessor.out = out;
try { try {
encodeProcessor.out = out;
string.forEachByte(encodeProcessor); string.forEachByte(encodeProcessor);
} catch (Exception e) {
PlatformDependent.throwException(e);
} finally { } finally {
encodeProcessor.end(); encodeProcessor.end();
} }
@ -116,9 +118,14 @@ final class HpackHuffmanEncoder {
int getEncodedLength(CharSequence data) { int getEncodedLength(CharSequence data) {
if (data instanceof AsciiString) { if (data instanceof AsciiString) {
AsciiString string = (AsciiString) data; AsciiString string = (AsciiString) data;
try {
encodedLengthProcessor.reset(); encodedLengthProcessor.reset();
string.forEachByte(encodedLengthProcessor); string.forEachByte(encodedLengthProcessor);
return encodedLengthProcessor.length(); return encodedLengthProcessor.length();
} catch (Exception e) {
PlatformDependent.throwException(e);
return -1;
}
} else { } else {
return getEncodedLengthSlowPath(data); return getEncodedLengthSlowPath(data);
} }

View File

@ -427,6 +427,7 @@ public final class HttpConversionUtil {
while (valuesIter.hasNext()) { while (valuesIter.hasNext()) {
AsciiString lowerCased = AsciiString.of(valuesIter.next()).toLowerCase(); AsciiString lowerCased = AsciiString.of(valuesIter.next()).toLowerCase();
try {
int index = lowerCased.forEachByte(FIND_COMMA); int index = lowerCased.forEachByte(FIND_COMMA);
if (index != -1) { if (index != -1) {
int start = 0; int start = 0;
@ -439,6 +440,11 @@ public final class HttpConversionUtil {
} else { } else {
result.add(lowerCased.trim(), EMPTY_STRING); result.add(lowerCased.trim(), EMPTY_STRING);
} }
} catch (Exception e) {
// This is not expect to happen because FIND_COMMA never throws but must be caught
// because of the ByteProcessor interface.
throw new IllegalStateException(e);
}
} }
return result; return result;
} }
@ -483,6 +489,7 @@ public final class HttpConversionUtil {
AsciiString value = AsciiString.of(entry.getValue()); AsciiString value = AsciiString.of(entry.getValue());
// split up cookies to allow for better compression // split up cookies to allow for better compression
// https://tools.ietf.org/html/rfc7540#section-8.1.2.5 // https://tools.ietf.org/html/rfc7540#section-8.1.2.5
try {
int index = value.forEachByte(FIND_SEMI_COLON); int index = value.forEachByte(FIND_SEMI_COLON);
if (index != -1) { if (index != -1) {
int start = 0; int start = 0;
@ -499,6 +506,11 @@ public final class HttpConversionUtil {
} else { } else {
out.add(COOKIE, value); out.add(COOKIE, value);
} }
} catch (Exception e) {
// This is not expect to happen because FIND_SEMI_COLON never throws but must be caught
// because of the ByteProcessor interface.
throw new IllegalStateException(e);
}
} else { } else {
out.add(aName, entry.getValue()); out.add(aName, entry.getValue());
} }

View File

@ -311,7 +311,7 @@ public final class RedisDecoder extends ByteToMessageDecoder {
private long result; private long result;
@Override @Override
public boolean process(byte value) { public boolean process(byte value) throws Exception {
if (value < '0' || value > '9') { if (value < '0' || value > '9') {
throw new RedisCodecException("bad byte in number: " + value); throw new RedisCodecException("bad byte in number: " + value);
} }

View File

@ -274,7 +274,7 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
} }
@Override @Override
public boolean process(byte nextByte) { public boolean process(byte nextByte) throws Exception {
if (nextByte == StompConstants.CR) { if (nextByte == StompConstants.CR) {
++lineLength; ++lineLength;
return true; return true;
@ -353,7 +353,7 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
} }
@Override @Override
public boolean process(byte nextByte) { public boolean process(byte nextByte) throws Exception {
if (nextByte == StompConstants.COLON) { if (nextByte == StompConstants.COLON) {
if (name == null) { if (name == null) {
AppendableCharSequence charSeq = charSequence(); AppendableCharSequence charSeq = charSequence();

View File

@ -331,7 +331,7 @@ public final class Base64 {
} }
@Override @Override
public boolean process(byte value) { public boolean process(byte value) throws Exception {
if (value > 0) { if (value > 0) {
byte sbiDecode = decodabet[value]; byte sbiDecode = decodabet[value];
if (sbiDecode >= WHITE_SPACE_ENC) { // White space, Equals sign or better if (sbiDecode >= WHITE_SPACE_ENC) { // White space, Equals sign or better

View File

@ -255,7 +255,7 @@ public final class AsciiString implements CharSequence, Comparable<CharSequence>
* @return {@code -1} if the processor iterated to or beyond the end of the readable bytes. * @return {@code -1} if the processor iterated to or beyond the end of the readable bytes.
* The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}. * The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}.
*/ */
public int forEachByte(ByteProcessor visitor) { public int forEachByte(ByteProcessor visitor) throws Exception {
return forEachByte0(0, length(), visitor); return forEachByte0(0, length(), visitor);
} }
@ -266,7 +266,7 @@ public final class AsciiString implements CharSequence, Comparable<CharSequence>
* @return {@code -1} if the processor iterated to or beyond the end of the specified area. * @return {@code -1} if the processor iterated to or beyond the end of the specified area.
* The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}. * The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}.
*/ */
public int forEachByte(int index, int length, ByteProcessor visitor) { public int forEachByte(int index, int length, ByteProcessor visitor) throws Exception {
if (isOutOfBounds(index, length, length())) { if (isOutOfBounds(index, length, length())) {
throw new IndexOutOfBoundsException("expected: " + "0 <= index(" + index + ") <= start + length(" + length throw new IndexOutOfBoundsException("expected: " + "0 <= index(" + index + ") <= start + length(" + length
+ ") <= " + "length(" + length() + ')'); + ") <= " + "length(" + length() + ')');
@ -274,7 +274,7 @@ public final class AsciiString implements CharSequence, Comparable<CharSequence>
return forEachByte0(index, length, visitor); return forEachByte0(index, length, visitor);
} }
private int forEachByte0(int index, int length, ByteProcessor visitor) { private int forEachByte0(int index, int length, ByteProcessor visitor) throws Exception {
final int len = offset + index + length; final int len = offset + index + length;
for (int i = offset + index; i < len; ++i) { for (int i = offset + index; i < len; ++i) {
if (!visitor.process(value[i])) { if (!visitor.process(value[i])) {
@ -290,7 +290,7 @@ public final class AsciiString implements CharSequence, Comparable<CharSequence>
* @return {@code -1} if the processor iterated to or beyond the beginning of the readable bytes. * @return {@code -1} if the processor iterated to or beyond the beginning of the readable bytes.
* The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}. * The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}.
*/ */
public int forEachByteDesc(ByteProcessor visitor) { public int forEachByteDesc(ByteProcessor visitor) throws Exception {
return forEachByteDesc0(0, length(), visitor); return forEachByteDesc0(0, length(), visitor);
} }
@ -301,7 +301,7 @@ public final class AsciiString implements CharSequence, Comparable<CharSequence>
* @return {@code -1} if the processor iterated to or beyond the beginning of the specified area. * @return {@code -1} if the processor iterated to or beyond the beginning of the specified area.
* The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}. * The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}.
*/ */
public int forEachByteDesc(int index, int length, ByteProcessor visitor) { public int forEachByteDesc(int index, int length, ByteProcessor visitor) throws Exception {
if (isOutOfBounds(index, length, length())) { if (isOutOfBounds(index, length, length())) {
throw new IndexOutOfBoundsException("expected: " + "0 <= index(" + index + ") <= start + length(" + length throw new IndexOutOfBoundsException("expected: " + "0 <= index(" + index + ") <= start + length(" + length
+ ") <= " + "length(" + length() + ')'); + ") <= " + "length(" + length() + ')');
@ -309,7 +309,7 @@ public final class AsciiString implements CharSequence, Comparable<CharSequence>
return forEachByteDesc0(index, length, visitor); return forEachByteDesc0(index, length, visitor);
} }
private int forEachByteDesc0(int index, int length, ByteProcessor visitor) { private int forEachByteDesc0(int index, int length, ByteProcessor visitor) throws Exception {
final int end = offset + index; final int end = offset + index;
for (int i = offset + index + length - 1; i >= end; --i) { for (int i = offset + index + length - 1; i >= end; --i) {
if (!visitor.process(value[i])) { if (!visitor.process(value[i])) {

View File

@ -124,5 +124,5 @@ public interface ByteProcessor {
* @return {@code true} if the processor wants to continue the loop and handle the next byte in the buffer. * @return {@code true} if the processor wants to continue the loop and handle the next byte in the buffer.
* {@code false} if the processor wants to stop handling bytes and abort the loop. * {@code false} if the processor wants to stop handling bytes and abort the loop.
*/ */
boolean process(byte value); boolean process(byte value) throws Exception;
} }

View File

@ -78,13 +78,13 @@ public class AsciiStringMemoryTest {
} }
@Test @Test
public void forEachTest() { public void forEachTest() throws Exception {
final AtomicReference<Integer> aCount = new AtomicReference<>(0); final AtomicReference<Integer> aCount = new AtomicReference<>(0);
final AtomicReference<Integer> bCount = new AtomicReference<>(0); final AtomicReference<Integer> bCount = new AtomicReference<>(0);
aAsciiString.forEachByte(new ByteProcessor() { aAsciiString.forEachByte(new ByteProcessor() {
int i; int i;
@Override @Override
public boolean process(byte value) { public boolean process(byte value) throws Exception {
assertEquals("failed at index: " + i, value, bAsciiString.byteAt(i++)); assertEquals("failed at index: " + i, value, bAsciiString.byteAt(i++));
aCount.set(aCount.get() + 1); aCount.set(aCount.get() + 1);
return true; return true;
@ -93,7 +93,7 @@ public class AsciiStringMemoryTest {
bAsciiString.forEachByte(new ByteProcessor() { bAsciiString.forEachByte(new ByteProcessor() {
int i; int i;
@Override @Override
public boolean process(byte value) { public boolean process(byte value) throws Exception {
assertEquals("failed at index: " + i, value, aAsciiString.byteAt(i++)); assertEquals("failed at index: " + i, value, aAsciiString.byteAt(i++));
bCount.set(bCount.get() + 1); bCount.set(bCount.get() + 1);
return true; return true;
@ -104,25 +104,25 @@ public class AsciiStringMemoryTest {
} }
@Test @Test
public void forEachWithIndexEndTest() { public void forEachWithIndexEndTest() throws Exception {
assertNotEquals(-1, aAsciiString.forEachByte(aAsciiString.length() - 1, assertNotEquals(-1, aAsciiString.forEachByte(aAsciiString.length() - 1,
1, new IndexOfProcessor(aAsciiString.byteAt(aAsciiString.length() - 1)))); 1, new IndexOfProcessor(aAsciiString.byteAt(aAsciiString.length() - 1))));
} }
@Test @Test
public void forEachWithIndexBeginTest() { public void forEachWithIndexBeginTest() throws Exception {
assertNotEquals(-1, aAsciiString.forEachByte(0, assertNotEquals(-1, aAsciiString.forEachByte(0,
1, new IndexOfProcessor(aAsciiString.byteAt(0)))); 1, new IndexOfProcessor(aAsciiString.byteAt(0))));
} }
@Test @Test
public void forEachDescTest() { public void forEachDescTest() throws Exception {
final AtomicReference<Integer> aCount = new AtomicReference<>(0); final AtomicReference<Integer> aCount = new AtomicReference<>(0);
final AtomicReference<Integer> bCount = new AtomicReference<>(0); final AtomicReference<Integer> bCount = new AtomicReference<>(0);
aAsciiString.forEachByteDesc(new ByteProcessor() { aAsciiString.forEachByteDesc(new ByteProcessor() {
int i = 1; int i = 1;
@Override @Override
public boolean process(byte value) { public boolean process(byte value) throws Exception {
assertEquals("failed at index: " + i, value, bAsciiString.byteAt(bAsciiString.length() - (i++))); assertEquals("failed at index: " + i, value, bAsciiString.byteAt(bAsciiString.length() - (i++)));
aCount.set(aCount.get() + 1); aCount.set(aCount.get() + 1);
return true; return true;
@ -131,7 +131,7 @@ public class AsciiStringMemoryTest {
bAsciiString.forEachByteDesc(new ByteProcessor() { bAsciiString.forEachByteDesc(new ByteProcessor() {
int i = 1; int i = 1;
@Override @Override
public boolean process(byte value) { public boolean process(byte value) throws Exception {
assertEquals("failed at index: " + i, value, aAsciiString.byteAt(aAsciiString.length() - (i++))); assertEquals("failed at index: " + i, value, aAsciiString.byteAt(aAsciiString.length() - (i++)));
bCount.set(bCount.get() + 1); bCount.set(bCount.get() + 1);
return true; return true;
@ -142,13 +142,13 @@ public class AsciiStringMemoryTest {
} }
@Test @Test
public void forEachDescWithIndexEndTest() { public void forEachDescWithIndexEndTest() throws Exception {
assertNotEquals(-1, bAsciiString.forEachByteDesc(bAsciiString.length() - 1, assertNotEquals(-1, bAsciiString.forEachByteDesc(bAsciiString.length() - 1,
1, new IndexOfProcessor(bAsciiString.byteAt(bAsciiString.length() - 1)))); 1, new IndexOfProcessor(bAsciiString.byteAt(bAsciiString.length() - 1))));
} }
@Test @Test
public void forEachDescWithIndexBeginTest() { public void forEachDescWithIndexBeginTest() throws Exception {
assertNotEquals(-1, bAsciiString.forEachByteDesc(0, assertNotEquals(-1, bAsciiString.forEachByteDesc(0,
1, new IndexOfProcessor(bAsciiString.byteAt(0)))); 1, new IndexOfProcessor(bAsciiString.byteAt(0))));
} }