diff -r 643978a35f6e -r 94691d8e746f test/jdk/sun/security/util/ManifestDigester/FindSection.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/sun/security/util/ManifestDigester/FindSection.java Thu Jul 18 08:53:06 2019 +0800 @@ -0,0 +1,750 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.concurrent.Callable; +import java.util.function.Consumer; + +import sun.security.util.ManifestDigester; + +import org.testng.annotations.Test; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Factory; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.testng.Assert.*; + +/** + * @test + * @bug 8217375 + * @modules java.base/sun.security.util:+open + * @compile ../../tools/jarsigner/Utils.java + * @run testng/othervm FindSection + * @summary Check {@link ManifestDigester#findSection}. + */ +public class FindSection { + + /* + * TODO: + * FIXED_8217375 is not intended to keep. it is intended to show what + * exactly has changed with respect to the previous version for which no + * such test existed. + */ + static final boolean FIXED_8217375 = true; + + /** + * {@link ManifestDigester.Entry#digestWorkaround} should not feed the + * trailing blank line into the digester. Before resolution of 8217375 it + * fed the trailing blank line into the digest if the second line break + * was at the end of the file due to
+     * if (allBlank || (i == len-1)) {
+     *     if (i == len-1)
+     *         pos.endOfSection = i;
+     *     else
+     *         pos.endOfSection = last;
+     * 
in {@link ManifestDigester#findSection}. In that case at the end + * of the manifest file, {@link ManifestDigester.Entry#digestWorkaround} + * would have produced the same digest as + * {@link ManifestDigester.Entry#digest} which was wrong and without effect + * at best. + *

+ * Once this fix is accepted, this flag can be removed along with + * {@link #actualEndOfSection8217375}. + */ + static final boolean FIXED_8217375_EOF_ENDOFSECTION = FIXED_8217375; + + /** + * {@link ManifestDigester.Position.endOfSection} usually points to the + * start position of the blank line trailing a section minus one. + * If a {@link ManifestDigester.Position} returned by + * {@link ManifestDigester#findSection} is based on a portion that starts + * with a blank line, above statement is (or was) not true, because of the + * initialization of {@code last} in {@link ManifestDigester#findSection} + *

+     * int last = offset;
+     * 
+ * which would point after incrementing it in {@code pos.endOfSection + 1} + * on line 128 (line number before this change) or {@code int sectionLen = + * pos.endOfSection-start+1;} on line 133 (line number before this change) + * at one byte after the first line break character of usually two and + * possibly (assuming "{@code \r\n}" default line break normally) in between + * the two characters of a line break. After subtracting again the index of + * the section start position on former line 133, the last byte would be + * missed to be digested by {@link ManifestDigester.Entry#digestWorkaround}. + *

+ * All this, however could possibly matter (or have mattered) only when + * {@link ManifestDigester#findSection} was invoked with an offset position + * pointing straight to a line break which happens if a manifest starts + * with an empty line or if there are superfluous blank lines between + * sections in both cases no useful manifest portion is identified. + * Superfluous blank lines are not identified as sections (because they + * don't have a name and specifically don't meet {@code if (len > 6) {} on + * former line 136. Manifests starting with a line break are not any more + * useful either. + *

+ * Once this fix is accepted, this flag can be removed along with + * {@link #actualEndOfSection8217375}. + */ + static final boolean FIXED_8217375_STARTWITHBLANKLINE_ENDOFSECTION = + FIXED_8217375; + + static Constructor PositionConstructor; + static Method findSection; + static Field rawBytes; + static Field endOfFirstLine; + static Field endOfSection; + static Field startOfNext; + + @BeforeClass + public static void setFindSectionAccessible() throws Exception { + Class Position = Arrays.stream(ManifestDigester.class. + getDeclaredClasses()).filter(c -> c.getSimpleName(). + equals("Position")).findFirst().get(); + PositionConstructor = Position.getDeclaredConstructor(); + PositionConstructor.setAccessible(true); + findSection = ManifestDigester.class.getDeclaredMethod("findSection", + int.class, Position); + findSection.setAccessible(true); + rawBytes = ManifestDigester.class.getDeclaredField("rawBytes"); + rawBytes.setAccessible(true); + endOfFirstLine = Position.getDeclaredField("endOfFirstLine"); + endOfFirstLine.setAccessible(true); + endOfSection = Position.getDeclaredField("endOfSection"); + endOfSection.setAccessible(true); + startOfNext = Position.getDeclaredField("startOfNext"); + startOfNext.setAccessible(true); + } + + static class Position { + final int endOfFirstLine; // not including newline character + + final int endOfSection; // end of section, not including the blank line + // between sections + final int startOfNext; // the start of the next section + + Position(Object pos) throws ReflectiveOperationException { + endOfFirstLine = FindSection.endOfFirstLine.getInt(pos); + endOfSection = FindSection.endOfSection.getInt(pos); + startOfNext = FindSection.startOfNext.getInt(pos); + } + } + + Position findSection(byte[] manifestBytes) + throws ReflectiveOperationException { + ManifestDigester manDig = new ManifestDigester("\n\n".getBytes(UTF_8)); + FindSection.rawBytes.set(manDig, manifestBytes); + Object pos = PositionConstructor.newInstance(); + Object result = findSection.invoke(manDig, offset, pos); + if (Boolean.FALSE.equals(result)) { + return null; // indicates findSection having returned false + } else { + return new Position(pos); + } + } + + @DataProvider(name = "parameters") + public static Object[][] parameters() { + return new Object[][] { { 0 }, { 42 } }; + } + + @Factory(dataProvider = "parameters") + public static Object[] createTests(int offset) { + return new Object[]{ new FindSection(offset) }; + } + + final int offset; + + FindSection(int offset) { + this.offset = offset; + } + + @BeforeMethod + public void verbose() { + System.out.println("offset = " + offset); + } + + Position findSection(String manifestString) + throws ReflectiveOperationException { + byte[] manifestBytes = manifestString.getBytes(UTF_8); + byte[] manifestWithOffset = new byte[manifestBytes.length + offset]; + System.arraycopy(manifestBytes, 0, manifestWithOffset, offset, + manifestBytes.length); + return findSection(manifestWithOffset); + } + + /** + * Surprising, but the offset actually makes a difference in + * {@link ManifestDigester#findSection} return value. + */ + @SuppressWarnings("unused") + int actualEndOfFirstLine8217375(int correctPosition) { + // if the parsed portion of the manifest starts with a blank line, + // and offset is 0, "pos.endOfFirstLine = -1;" probably denoting a + // yet uninitialized value coincides with the assignment by + // "pos.endOfFirstLine = i-1;" if i == 0 and + // "if (pos.endOfFirstLine == -1)" after "case '\n':" happens to + // become true even though already assigned. + if (offset == 0 && correctPosition == -1 && !FIXED_8217375) return 0; + return correctPosition; + } + + @SuppressWarnings("unused") + int actualEndOfSection8217375(int correctPosition, boolean eof, int lbl) { + // if the parsed portion of the manifest ends with a blank line and + // just before eof, the blank line is included in Position.endOfSection/ + // Section.length (the one usually without blank line as well as in + // Position.startOfNext/Section.lengthWithBlankLine) which is used + // in digestWorkaround (independent of the digest without workaround) + if (eof && !FIXED_8217375_EOF_ENDOFSECTION) { + return correctPosition + lbl; + } else if (correctPosition == -1 + && !FIXED_8217375_STARTWITHBLANKLINE_ENDOFSECTION) { + return 0; + } else { + return correctPosition; + } + } + + AssertionError collectErrors(AssertionError a, Runnable run) { + try { + run.run(); + } catch (AssertionError e) { + if (a == null) a = new AssertionError(); + a.addSuppressed(e); + } + return a; + } + + void assertPosition(Position pos, + int endOfFirstLine, int endOfSection, int startOfNext) { + AssertionError a = null; + a = collectErrors(a, () -> assertEquals( + pos.endOfFirstLine, endOfFirstLine + offset, "endOfFirstLine")); + a = collectErrors(a, () -> assertEquals( + pos.endOfSection, endOfSection + offset, "endOfSection")); + a = collectErrors(a, () -> assertEquals( + pos.startOfNext, startOfNext + offset, "startOfNext")); + if (a != null) throw a; + } + + void catchCrCausesIndexOutOfBoundsException( + Callable test, Consumer asserts) { + try { + Position x = test.call(); + if (!FIXED_8217375) fail(); + asserts.accept(x); + } catch (Exception e) { + if (e instanceof IndexOutOfBoundsException || + e.getCause() instanceof IndexOutOfBoundsException) { + if (FIXED_8217375) throw new AssertionError(e); + } else { + throw new AssertionError(e); + } + } + } + + @Test + public void testEmpty() throws Exception { + assertNull(findSection("")); + } + + @Test + public void testOneLineBreakCr() throws Exception { + catchCrCausesIndexOutOfBoundsException( + () -> findSection("\r"), + p -> assertPosition(p, + -1, actualEndOfSection8217375(-1, false, 1), 1) + ); + } + + @Test + public void testOneLineBreakLf() throws Exception { + assertPosition(findSection("\n"), + -1, actualEndOfSection8217375(-1, false, 1), 1); + } + + @Test + public void testOneLineBreakCrLf() throws Exception { + assertPosition(findSection("\r\n"), + actualEndOfFirstLine8217375(-1), + actualEndOfSection8217375(-1, true, 2), + 2); + } + + @Test + public void testSpaceAndLineBreakCr() throws Exception { + catchCrCausesIndexOutOfBoundsException( + () -> findSection(" \r"), + p -> assertPosition(p, 2, 3, 4) + ); + } + + @Test + public void testSpaceAndOneLineBreakLf() throws Exception { + assertPosition(findSection(" \n"), 2, 3, 4); + } + + @Test + public void testSpaceAndOneLineBreakCrLf() throws Exception { + assertPosition(findSection(" \r\n"), 2, 4, 5); + } + + @Test + public void testOneLineBreakCrAndSpace() throws Exception { + assertPosition(findSection("\r "), + -1, actualEndOfSection8217375(-1, false, 1), 1); + } + + @Test + public void testOneLineBreakLfAndSpace() throws Exception { + assertPosition(findSection("\n "), + -1, actualEndOfSection8217375(-1, false, 1), 1); + } + + @Test + public void testOneLineBreakCrLfAndSpace() throws Exception { + assertPosition(findSection("\r\n "), + actualEndOfFirstLine8217375(-1), + actualEndOfSection8217375(-1, false, 1), + 2); + } + + @Test + public void testCrEof() throws Exception { + catchCrCausesIndexOutOfBoundsException( + () -> findSection("abc\r"), + p -> assertPosition(p, 2, 3, 4) + ); + } + + @Test + public void testLfEof() throws Exception { + assertPosition(findSection("abc\n"), 2, 3, 4); + } + + @Test + public void testCrLfEof() throws Exception { + assertPosition(findSection("abc\r\n"), 2, 4, 5); + } + + @Test + public void testCrContinued() throws Exception { + assertPosition(findSection("abc\rxyz\r\n\r\n "), 2, 8, 11); + } + + @Test + public void testLfContinued() throws Exception { + assertPosition(findSection("abc\nxyz\r\n\r\n "), 2, 8, 11); + } + + @Test + public void testCrLfContinued() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\n "), 2, 9, 12); + } + + @Test + public void testCrCrEof() throws Exception { + catchCrCausesIndexOutOfBoundsException( + () -> findSection("abc\r\nxyz\r\r"), + p -> assertPosition(p, + 2, actualEndOfSection8217375(8, true, 1), 10) + ); + } + + @Test + public void testCrCrContinued() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\r "), 2, 8, 10); + } + + @Test + public void testLfLfEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\n\n"), + 2, actualEndOfSection8217375(8, true, 1), 10); + } + + @Test + public void testLfLfContinued() throws Exception { + assertPosition(findSection("abc\r\nxyz\n\n "), 2, 8, 10); + } + + @Test + public void testCrLfEof2() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n"), 2, 9, 10); + } + + @Test + public void testMainSectionNotTerminatedWithLineBreak() throws Exception { + assertNull(findSection("abc\r\nxyz\r\n ")); + } + + @Test + public void testLfCrEof() throws Exception { + catchCrCausesIndexOutOfBoundsException( + () -> findSection("abc\r\nxyz\n\r"), + p -> assertPosition(p, + 2, actualEndOfSection8217375(8, true, 1), 10) + ); + } + + @Test + public void testLfCrContinued() throws Exception { + assertPosition(findSection("abc\r\nxyz\n\r "), 2, 8, 10); + } + + @Test + public void testCrLfCrEof() throws Exception { + catchCrCausesIndexOutOfBoundsException( + () -> findSection("abc\r\nxyz\r\n\r"), + p -> assertPosition(p, + 2, actualEndOfSection8217375(9, true, 2), 11) + ); + } + + @Test + public void testCrLfCrContinued() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r "), 2, 9, 11); + } + + @Test + public void testCrLfLfEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\n"), + 2, actualEndOfSection8217375(9, true, 1), 11); + } + + @Test + public void testCrLfLfContinued() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\n "), 2, 9, 11); + } + + @Test + public void testCrLfCrLfEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\n"), + 2, actualEndOfSection8217375(9, true, 2), 12); + } + + @Test + public void testCrLfCfLfContinued() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\n "), 2, 9, 12); + } + + @Test + public void testCrLfCrCrEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\r"), 2, 9, 11); + } + + @Test + public void testCrLfCrCrContinued() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\r "), 2, 9, 11); + } + + @Test + public void testCrLfLfCrEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\n\r"), 2, 9, 11); + } + + @Test + public void testCrLfLfCrContinued() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\n\r "), 2, 9, 11); + } + + @Test + public void testCrLfCrLfCrEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\n\r"), 2, 9, 12); + } + + @Test + public void testCrLfCfLfCrContinued() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\n\r "), 2, 9, 12); + } + + @Test + public void testCrLfCrLfContinued() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\n "), 2, 9, 12); + } + + @Test + public void testCrLfLfLfEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\n\n"), 2, 9, 11); + } + + @Test + public void testCrLfLfLfContinued() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\n\n "), 2, 9, 11); + } + + @Test + public void testCrLfCrLfLfContinued() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\n\n "), 2, 9, 12); + } + + @Test + public void testCrLfCrCrLfEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\r\n"), 2, 9, 11); + } + + @Test + public void testCrLfCrCrLfContinued() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\r\n "), 2, 9, 11); + } + + @Test + public void testCrLfLfCrLfEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\n\r\n"), 2, 9, 11); + } + + @Test + public void testCrLfLfCrLfContinued() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\n\r\n "), 2, 9, 11); + } + + @Test + public void testCrLfCrLfCrLfEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\n\r\n"), 2, 9, 12); + } + + @Test + public void testCrLfCfLfCrLfContinued() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\n\r\n "), 2, 9, 12); + } + + @Test + public void testCrLfLfCrCrEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\n\r\r"), 2, 9, 11); + } + + @Test + public void testCrLfCrLfCrCrEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\n\r\r"), 2, 9, 12); + } + + @Test + public void testCrLfCrLfCrContinued() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\n\r "), 2, 9, 12); + } + + @Test + public void testCrLfLfLfCrEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\n\n\r"), 2, 9, 11); + } + + @Test + public void testCrLfLfCrLfCrEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\n\r\n\r"), 2, 9, 11); + } + + @Test + public void testCrLfLfLfLfEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\n\n\n"), 2, 9, 11); + } + + @Test + public void testCrLfLfCrLfLfEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\n\r\n\n"), 2, 9, 11); + } + + @Test + public void testCrLfLfCrCrLfEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\n\r\r\n"), 2, 9, 11); + } + + @Test + public void testCrLfCrLfCrCrLfEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\n\r\r\n"), 2, 9, 12); + } + + @Test + public void testCrLfCrLfCrLfContinued() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\n\r\n "), 2, 9, 12); + } + + @Test + public void testCrLfLfLfCrLfEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\n\n\r\n"), 2, 9, 11); + } + + @Test + public void testCrLfLfCrLfCrLfEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\n\r\n\r\n"), 2, 9, 11); + } + + @Test + public void testCrLfCrCrLfCrCrEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\r\n\r"), 2, 9, 11); + } + + @Test + public void testCrLfCrCrCrCrEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\r\r"), 2, 9, 11); + } + + @Test + public void testCrLfCrCrLfLfEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\r\n\n"), 2, 9, 11); + } + + @Test + public void testCrLfCrCrLfCrLfEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\r\n\r\n"), 2, 9, 11); + } + + @Test + public void testCrLfCrCrCrLfEof() throws Exception { + assertPosition(findSection("abc\r\nxyz\r\n\r\r\r\n"), 2, 9, 11); + } + + /* + * endOfFirstLine is the same regardless of the line break delimiter + */ + @Test + public void testEndOfFirstLineVsLineBreak() throws Exception { + for (String lb : new String[] { "\r", "\n", "\r\n" }) { + Position p = findSection("abc" + lb + "xyz" + lb + lb + " "); + + // main assertion showing endOfFirstLine independent of line break + assertEquals(p.endOfFirstLine, 2 + offset); + + // assert remaining positions as well just for completeness + assertPosition(p, 2, 5 + 2 * lb.length(), 6 + 3 * lb.length()); + } + } + + /* + * '\r' at the end of the bytes causes index out of bounds exception + */ + @Test + public void testCrLastCausesIndexOutOfBounds() throws Exception { + catchCrCausesIndexOutOfBoundsException( + () -> findSection("\r"), + p -> assertPosition(p, + -1, actualEndOfSection8217375(-1, true, 1), 1) + ); + } + + /* + * endOfSection includes second line break if at end of bytes only + */ + @Test + public void testEndOfSectionWithLineBreakVsEof() throws Exception { + AssertionError errors = new AssertionError("offset = " + offset); + for (String lb : new String[] { "\r", "\n", "\r\n" }) { + for (boolean eof : new boolean[] { false, true }) { + Position p; + try { + p = findSection("abc" + lb + lb + (eof ? "" : "xyz")); + } catch (RuntimeException | ReflectiveOperationException e) { + if ((e instanceof IndexOutOfBoundsException || + e.getCause() instanceof IndexOutOfBoundsException) + && eof && "\r".equals(lb) && !FIXED_8217375) continue; + throw e; + } + + AssertionError a = new AssertionError("offset = " + offset + + ", lb = " + Utils.escapeStringWithNumbers(lb) + ", " + + "eof = " + eof); + + // main assertion showing endOfSection including second line + // break when at end of file + a = collectErrors(a, () -> assertEquals( + p.endOfSection, + actualEndOfSection8217375( + 2 + lb.length() + offset, eof, lb.length()) )); + + // assert remaining positions as well just for completeness + a = collectErrors(a, () -> assertPosition(p, + 2, + actualEndOfSection8217375( + 2 + lb.length(), eof, lb.length()), + 3 + lb.length() * 2)); + + if (a.getSuppressed().length > 0) errors.addSuppressed(a); + } + } + if (errors.getSuppressed().length > 0) throw errors; + } + + /* + * returns position even if only one line break before end of bytes. + * because no name will be found the result will be skipped and no entry + * will be created. + */ + @Test + public void testReturnPosVsEof() throws Exception { + for (String lb : new String[] { "\r", "\n", "\r\n" }) { + for (boolean eof : new boolean[] { false, true }) { + try { + Position p = findSection("abc" + lb + (eof ? "" : "xyz")); + assertTrue(p != null == eof); + } catch (RuntimeException | ReflectiveOperationException e) { + if ((e instanceof IndexOutOfBoundsException || + e.getCause() instanceof IndexOutOfBoundsException) + && eof && "\r".equals(lb) && !FIXED_8217375) continue; + throw e; + } + } + } + } + + /* + * it could be normally be expected that startOfNext would point to the + * start of the next section after a blank line but that is not the case + * if a section ends with only one line break and no blank line immediately + * before eof of the manifest. + * such an entry will be digested without the trailing blank line which is + * only fine until another section should be added afterwards. + */ + @Test + public void testStartOfNextPointsToEofWithNoBlankLine() throws Exception { + for (String lb : new String[] { "\r", "\n", "\r\n" }) { + for (boolean blank : new boolean[] { false, true }) { + String manifest = "abc" + lb + "xyz" + lb + (blank ? lb : ""); + try { + Position p = findSection(manifest); + + // assert that startOfNext points to eof in all cases + // whether with or without a blank line before eof + assertEquals(p.startOfNext, manifest.length() + offset); + + // assert remaining positions as well just for completeness + assertPosition(p, + 2, + actualEndOfSection8217375( + 5 + lb.length() * 2, + true, + blank ? lb.length() : 0), + manifest.length()); + } catch (RuntimeException | ReflectiveOperationException e) { + if ((e instanceof IndexOutOfBoundsException || + e.getCause() instanceof IndexOutOfBoundsException) + && "\r".equals(lb) && !FIXED_8217375) continue; + throw e; + } + } + } + } + +}