Repository: jhy/jsoup
Pull request #1585 (2021-07-12 23:02:27)

Bump maven-surefire-plugin from 3.0.0-M3 to 3.0.0-M5
Bumps [maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.0.0-M3 to 3.0.0-M5.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/apache/maven-surefire/commit/1eb35fd1ec6e54545c080079b23aacbb66e4936d"><code>1eb35fd</code></a> [maven-release-plugin] prepare release surefire-3.0.0-M5_vote-1</li>
<li><a href="https://github.com/apache/maven-surefire/commit/02cca4dc3fba70a17b3a8fecc07c374a35e49d15"><code>02cca4d</code></a> [GH] performance problem on Windows nodes</li>
<li><a href="https://github.com/apache/maven-surefire/commit/e8f65b99aaaa7c04e1ad3770309cd626d3fe5ab0"><code>e8f65b9</code></a> [SUREFIRE-1797] Surefire report with parameterized tests contain all names of...</li>
<li><a href="https://github.com/apache/maven-surefire/commit/2e14ba6e8cb8883ef0c50175c0f0d6af676fac55"><code>2e14ba6</code></a> updated the road map for 3.0.0-M5 and next versions</li>
<li><a href="https://github.com/apache/maven-surefire/commit/5f492fa0f22c3b5a18508c11c3429bcc62388d5f"><code>5f492fa</code></a> Using localhost (not local loopback) in SurefireForkChannel.java</li>
<li><a href="https://github.com/apache/maven-surefire/commit/eb48f1b59ca5ccf6954ef33ecab03dbaf93214cd"><code>eb48f1b</code></a> featurematrix.apt.vm with issue SUREFIRE-1795</li>
<li><a href="https://github.com/apache/maven-surefire/commit/5925e3d8fdc52e3ad58d7fd35d679277f763a8bf"><code>5925e3d</code></a> providerJpmsArguments are already controlled by isMainDescriptor in ProviderInfo</li>
<li><a href="https://github.com/apache/maven-surefire/commit/6237868c88dfd6edc805982747d61094b3b79c25"><code>6237868</code></a> Updated java9.md with new Toolchain capabilities</li>
<li><a href="https://github.com/apache/maven-surefire/commit/c2a32917a82189a77750932aef821ac6a4cb2906"><code>c2a3291</code></a> [GH] performance problem on Windows nodes</li>
<li><a href="https://github.com/apache/maven-surefire/commit/a3f827f0ce2f204599b757a0cc1f45814771f4ae"><code>a3f827f</code></a> [GH] Workflows j14</li>
<li>Additional commits viewable in <a href="https://github.com/apache/maven-surefire/compare/surefire-3.0.0-M3...surefire-3.0.0-M5">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.apache.maven.plugins:maven-surefire-plugin&package-manager=maven&previous-version=3.0.0-M3&new-version=3.0.0-M5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/CHANGES b/CHANGES
index b86ecbf7..d60b728b 100644
--- a/CHANGES
+++ b/CHANGES
@@ -23,6 +23,10 @@ jsoup changelog
   * Bugfix [Fuzz]: malformed deeply nested table elements could create a stack overflow.
     <https://github.com/jhy/jsoup/issues/1577>
 
+  * Bugfix [Fuzz]: Speed optimized malformed HTML creating elements with thousands of elements - limit the attribute
+    count per element when parsing to 512 (in real-world HTML, P99 is ~ 8).
+    <https://github.com/jhy/jsoup/issues/1578>
+
 *** Release 1.14.1 [2021-Jul-10]
   * Change: updated the minimum supported Java version from Java 7 to Java 8.
 
diff --git a/pom.xml b/pom.xml
index ab146c3c..733a6cc7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -83,7 +83,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-javadoc-plugin</artifactId>
-        <version>3.1.0</version>
+        <version>3.3.0</version>
         <configuration>
             <doclint>none</doclint>
             <source>7</source>
@@ -156,7 +156,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-resources-plugin</artifactId>
-        <version>3.0.1</version>
+        <version>3.2.0</version>
       </plugin>
       <plugin>
         <artifactId>maven-release-plugin</artifactId>
@@ -165,7 +165,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-plugin</artifactId>
-        <version>3.0.0-M3</version>
+        <version>3.0.0-M5</version>
         <configuration>
           <!-- smaller stack to find stack overflows -->
           <argLine>-Xss256k</argLine>
@@ -308,7 +308,7 @@
     <dependency>
       <groupId>org.junit.jupiter</groupId>
       <artifactId>junit-jupiter</artifactId>
-      <version>5.6.0</version>
+      <version>5.7.2</version>
       <scope>test</scope>
     </dependency>
 
diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java
index b2dc18ef..6e0d7898 100644
--- a/src/main/java/org/jsoup/nodes/Attributes.java
+++ b/src/main/java/org/jsoup/nodes/Attributes.java
@@ -46,6 +46,7 @@ public class Attributes implements Iterable<Attribute>, Cloneable {
     static final int NotFound = -1;
     private static final String EmptyString = "";
 
+    // the number of instance fields is kept as low as possible giving an object size of 24 bytes
     private int size = 0; // number of slots used (not total capacity, which is keys.length)
     String[] keys = new String[InitialCapacity];
     String[] vals = new String[InitialCapacity];
@@ -246,16 +247,12 @@ public class Attributes implements Iterable<Attribute>, Cloneable {
     }
 
     /**
-     Get the number of attributes in this set.
+     Get the number of attributes in this set, including any jsoup internal-only attributes. Internal attributes are
+     excluded from the {@link #html()}, {@link #asList()}, and {@link #iterator()} methods.
      @return size
      */
     public int size() {
-        int s = 0;
-        for (int i = 0; i < size; i++) {
-            if (!isInternalKey(keys[i]))
-                s++;
-        }
-        return s;
+        return size;
     }
 
     /**
diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java
index 35f52fa7..e2297494 100644
--- a/src/main/java/org/jsoup/parser/CharacterReader.java
+++ b/src/main/java/org/jsoup/parser/CharacterReader.java
@@ -555,7 +555,7 @@ public final class CharacterReader {
     }
 
     /**
-     * Caches short strings, as a flywheel pattern, to reduce GC load. Just for this doc, to prevent leaks.
+     * Caches short strings, as a flyweight pattern, to reduce GC load. Just for this doc, to prevent leaks.
      * <p />
      * Simplistic, and on hash collisions just falls back to creating a new string, vs a full HashMap with Entry list.
      * That saves both having to create objects as hash keys, and running through the entry list, at the expense of
@@ -569,27 +569,22 @@ public final class CharacterReader {
             return "";
 
         // calculate hash:
-        int hash = 31 * count;
-        int offset = start;
+        int hash = 0;
         for (int i = 0; i < count; i++) {
-            hash = 31 * hash + charBuf[offset++];
+            hash = 31 * hash + charBuf[start + i];
         }
 
         // get from cache
         final int index = hash & stringCacheSize - 1;
         String cached = stringCache[index];
 
-        if (cached == null) { // miss, add
+        if (cached != null && rangeEquals(charBuf, start, count, cached)) // positive hit
+            return cached;
+        else {
             cached = new String(charBuf, start, count);
-            stringCache[index] = cached;
-        } else { // hashcode hit, check equality
-            if (rangeEquals(charBuf, start, count, cached)) { // hit
-                return cached;
-            } else { // hashcode conflict
-                cached = new String(charBuf, start, count);
-                stringCache[index] = cached; // update the cache, as recently used strings are more likely to show up again
-            }
+            stringCache[index] = cached; // add or replace, assuming most recently used are most likely to recur next
         }
+
         return cached;
     }
 
diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java
index 9ade8e0d..0b3cb6c5 100644
--- a/src/main/java/org/jsoup/parser/Token.java
+++ b/src/main/java/org/jsoup/parser/Token.java
@@ -97,11 +97,16 @@ abstract class Token {
             return this;
         }
 
+        /* Limits runaway crafted HTML from spewing attributes and getting a little sluggish in ensureCapacity.
+        Real-world HTML will P99 around 8 attributes, so plenty of headroom. Implemented here and not in the Attributes
+        object so that API users can add more if ever required. */
+        private static final int MaxAttributes = 512;
+
         final void newAttribute() {
             if (attributes == null)
                 attributes = new Attributes();
 
-            if (pendingAttributeName != null) {
+            if (pendingAttributeName != null && attributes.size() < MaxAttributes) {
                 // the tokeniser has skipped whitespace control chars, but trimming could collapse to empty for other control codes, so verify here
                 pendingAttributeName = pendingAttributeName.trim();
                 if (pendingAttributeName.length() > 0) {


Test patch:
diff --git a/src/test/java/org/jsoup/nodes/AttributesTest.java b/src/test/java/org/jsoup/nodes/AttributesTest.java
index 84d9ed78..d2f986b0 100644
--- a/src/test/java/org/jsoup/nodes/AttributesTest.java
+++ b/src/test/java/org/jsoup/nodes/AttributesTest.java
@@ -225,6 +225,10 @@ public class AttributesTest {
 
         a.put(Attributes.internalKey("baseUri"), "example.com");
         a.put(Attributes.internalKey("another"), "example.com");
-        assertEquals(2, a.size());
+        a.put(Attributes.internalKey("last"), "example.com");
+        a.remove(Attributes.internalKey("last"));
+
+        assertEquals(4, a.size());
+        assertEquals(2, a.asList().size()); // excluded from lists
     }
 }