diff --git a/ath.sh b/ath.sh index 12eca9c947fb..7009d692b8d0 100644 --- a/ath.sh +++ b/ath.sh @@ -6,7 +6,7 @@ set -o xtrace cd "$(dirname "$0")" # https://github.com/jenkinsci/acceptance-test-harness/releases -export ATH_VERSION=6016.v3a_e3864eb_993 +export ATH_VERSION=6038.v190f938efc87 if [[ $# -eq 0 ]]; then export JDK=17 diff --git a/core/src/main/java/hudson/util/RobustCollectionConverter.java b/core/src/main/java/hudson/util/RobustCollectionConverter.java index 64dbbc7d9e9a..f914d909be27 100644 --- a/core/src/main/java/hudson/util/RobustCollectionConverter.java +++ b/core/src/main/java/hudson/util/RobustCollectionConverter.java @@ -26,6 +26,7 @@ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.XStreamException; +import com.thoughtworks.xstream.converters.ConversionException; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.converters.collections.CollectionConverter; import com.thoughtworks.xstream.converters.reflection.ReflectionProvider; @@ -34,11 +35,15 @@ import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.mapper.Mapper; import com.thoughtworks.xstream.security.InputManipulationException; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import hudson.diagnosis.OldDataMonitor; +import java.lang.reflect.Type; import java.util.Collection; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; import java.util.logging.Logger; import jenkins.util.xstream.CriticalXStreamException; +import org.jvnet.tiger_types.Types; /** * {@link CollectionConverter} that ignores {@link XStreamException}. @@ -52,14 +57,39 @@ @SuppressWarnings({"rawtypes", "unchecked"}) public class RobustCollectionConverter extends CollectionConverter { private final SerializableConverter sc; + /** + * When available, this field holds the declared type of the collection being deserialized. + */ + private final @CheckForNull Class elementType; public RobustCollectionConverter(XStream xs) { - this(xs.getMapper(), xs.getReflectionProvider()); + this(xs.getMapper(), xs.getReflectionProvider(), null); } public RobustCollectionConverter(Mapper mapper, ReflectionProvider reflectionProvider) { + this(mapper, reflectionProvider, null); + } + + /** + * Creates a converter that will validate the types of collection elements during deserialization. + *

Elements with invalid types will be omitted from deserialized collections and may result in an + * {@link OldDataMonitor} warning. + *

This type checking currently uses the erasure of the type argument, so for example, the element type for a + * {@code List>} is just a raw {@code Optional}, so non-integer values inside of the optional + * would still deserialize successfully and the resulting optional would be included in the list. + * + * @see RobustReflectionConverter#unmarshalField + */ + public RobustCollectionConverter(Mapper mapper, ReflectionProvider reflectionProvider, Type collectionType) { super(mapper); sc = new SerializableConverter(mapper, reflectionProvider, new ClassLoaderReference(null)); + if (collectionType != null && Collection.class.isAssignableFrom(Types.erasure(collectionType))) { + var baseType = Types.getBaseClass(collectionType, Collection.class); + var typeArg = Types.getTypeArgument(baseType, 0, Object.class); + this.elementType = Types.erasure(typeArg); + } else { + this.elementType = null; + } } @Override @@ -85,9 +115,19 @@ protected void populateCollection(HierarchicalStreamReader reader, Unmarshalling reader.moveDown(); try { Object item = readBareItem(reader, context, collection); - long nanoNow = System.nanoTime(); - collection.add(item); - XStream2SecurityUtils.checkForCollectionDoSAttack(context, nanoNow); + if (elementType != null && item != null && !elementType.isInstance(item)) { + var exception = new ConversionException("Invalid type for collection element"); + // c.f. TreeUnmarshaller.addInformationTo + exception.add("required-type", elementType.getName()); + exception.add("class", item.getClass().getName()); + exception.add("converter-type", getClass().getName()); + reader.appendErrors(exception); + RobustReflectionConverter.addErrorInContext(context, exception); + } else { + long nanoNow = System.nanoTime(); + collection.add(item); + XStream2SecurityUtils.checkForCollectionDoSAttack(context, nanoNow); + } } catch (CriticalXStreamException e) { throw e; } catch (InputManipulationException e) { diff --git a/core/src/main/java/hudson/util/RobustMapConverter.java b/core/src/main/java/hudson/util/RobustMapConverter.java index f845e38771cc..c802959d0d09 100644 --- a/core/src/main/java/hudson/util/RobustMapConverter.java +++ b/core/src/main/java/hudson/util/RobustMapConverter.java @@ -31,9 +31,13 @@ import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.mapper.Mapper; import com.thoughtworks.xstream.security.InputManipulationException; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import hudson.diagnosis.OldDataMonitor; +import java.lang.reflect.Type; import java.util.Map; import java.util.logging.Logger; import jenkins.util.xstream.CriticalXStreamException; +import org.jvnet.tiger_types.Types; /** * Loads a {@link Map} while tolerating read errors on its keys and values. @@ -42,13 +46,47 @@ final class RobustMapConverter extends MapConverter { private static final Object ERROR = new Object(); + /** + * When available, this field holds the declared type of the keys of the map being deserialized. + */ + private final @CheckForNull Class keyType; + + /** + * When available, this field holds the declared type of the values of the map being deserialized. + */ + private final @CheckForNull Class valueType; + RobustMapConverter(Mapper mapper) { + this(mapper, null); + } + + /** + * Creates a converter that will validate the types of map entry keys and values during deserialization. + *

Map entries whose key or value has an invalid type will be omitted from deserialized maps and may result in + * an {@link OldDataMonitor} warning. + *

This type checking currently uses the erasure of the type argument, so for example, the value type for a + * {@code Map>} is just a raw {@code Optional}, so non-integer values inside of the + * optional would still deserialize successfully and the resulting map entry would be included in the map. + * + * @see RobustReflectionConverter#unmarshalField + */ + RobustMapConverter(Mapper mapper, Type mapType) { super(mapper); + if (mapType != null && Map.class.isAssignableFrom(Types.erasure(mapType))) { + var baseType = Types.getBaseClass(mapType, Map.class); + var keyTypeArg = Types.getTypeArgument(baseType, 0, Object.class); + this.keyType = Types.erasure(keyTypeArg); + var valueTypeArg = Types.getTypeArgument(baseType, 1, Object.class); + this.valueType = Types.erasure(valueTypeArg); + } else { + this.keyType = null; + this.valueType = null; + } } @Override protected void putCurrentEntryIntoMap(HierarchicalStreamReader reader, UnmarshallingContext context, Map map, Map target) { - Object key = read(reader, context, map); - Object value = read(reader, context, map); + Object key = read(reader, context, map, keyType); + Object value = read(reader, context, map, valueType); if (key != ERROR && value != ERROR) { try { long nanoNow = System.nanoTime(); @@ -64,7 +102,7 @@ final class RobustMapConverter extends MapConverter { } } - private Object read(HierarchicalStreamReader reader, UnmarshallingContext context, Map map) { + private Object read(HierarchicalStreamReader reader, UnmarshallingContext context, Map map, @CheckForNull Class expectedType) { if (!reader.hasMoreChildren()) { var exception = new ConversionException("Invalid map entry"); reader.appendErrors(exception); @@ -73,7 +111,18 @@ private Object read(HierarchicalStreamReader reader, UnmarshallingContext contex } reader.moveDown(); try { - return readBareItem(reader, context, map); + var object = readBareItem(reader, context, map); + if (expectedType != null && object != null && !expectedType.isInstance(object)) { + var exception = new ConversionException("Invalid type for map entry key/value"); + // c.f. TreeUnmarshaller.addInformationTo + exception.add("required-type", expectedType.getName()); + exception.add("class", object.getClass().getName()); + exception.add("converter-type", getClass().getName()); + reader.appendErrors(exception); + RobustReflectionConverter.addErrorInContext(context, exception); + return ERROR; + } + return object; } catch (CriticalXStreamException x) { throw x; } catch (XStreamException | LinkageError x) { diff --git a/core/src/main/java/hudson/util/RobustReflectionConverter.java b/core/src/main/java/hudson/util/RobustReflectionConverter.java index d1bc500003e1..686aad13c342 100644 --- a/core/src/main/java/hudson/util/RobustReflectionConverter.java +++ b/core/src/main/java/hudson/util/RobustReflectionConverter.java @@ -48,6 +48,7 @@ import hudson.model.Saveable; import hudson.security.ACL; import java.lang.reflect.Field; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -65,6 +66,7 @@ import jenkins.util.xstream.CriticalXStreamException; import net.jcip.annotations.GuardedBy; import org.acegisecurity.Authentication; +import org.jvnet.tiger_types.Types; /** * Custom {@link ReflectionConverter} that handle errors more gracefully. @@ -80,7 +82,7 @@ @SuppressWarnings({"rawtypes", "unchecked"}) public class RobustReflectionConverter implements Converter { - private static /* non-final for Groovy */ boolean RECORD_FAILURES_FOR_ALL_AUTHENTICATIONS = SystemProperties.getBoolean(RobustReflectionConverter.class.getName() + ".recordFailuresForAllAuthentications", false); + static /* non-final for Groovy */ boolean RECORD_FAILURES_FOR_ALL_AUTHENTICATIONS = SystemProperties.getBoolean(RobustReflectionConverter.class.getName() + ".recordFailuresForAllAuthentications", false); private static /* non-final for Groovy */ boolean RECORD_FAILURES_FOR_ADMINS = SystemProperties.getBoolean(RobustReflectionConverter.class.getName() + ".recordFailuresForAdmins", false); protected final ReflectionProvider reflectionProvider; @@ -324,7 +326,8 @@ public Object doUnmarshal(final Object result, final HierarchicalStreamReader re } } - Map implicitCollectionsForCurrentObject = null; + Map> implicitCollectionsForCurrentObject = new HashMap<>(); + Map> implicitCollectionElementTypesForCurrentObject = new HashMap<>(); while (reader.hasMoreChildren()) { reader.moveDown(); @@ -365,7 +368,7 @@ public Object doUnmarshal(final Object result, final HierarchicalStreamReader re reflectionProvider.writeField(result, fieldName, value, classDefiningField); seenFields.add(classDefiningField, fieldName); } else { - implicitCollectionsForCurrentObject = writeValueToImplicitCollection(context, value, implicitCollectionsForCurrentObject, result, fieldName); + writeValueToImplicitCollection(reader, context, value, implicitCollectionsForCurrentObject, implicitCollectionElementTypesForCurrentObject, result, fieldName); } } } catch (CriticalXStreamException e) { @@ -451,18 +454,23 @@ private boolean fieldDefinedInClass(Object result, String attrName) { protected Object unmarshalField(final UnmarshallingContext context, final Object result, Class type, Field field) { Converter converter = mapper.getLocalConverter(field.getDeclaringClass(), field.getName()); + if (converter == null) { + if (new RobustCollectionConverter(mapper, reflectionProvider).canConvert(type)) { + converter = new RobustCollectionConverter(mapper, reflectionProvider, field.getGenericType()); + } else if (new RobustMapConverter(mapper).canConvert(type)) { + converter = new RobustMapConverter(mapper, field.getGenericType()); + } + } return context.convertAnother(result, type, converter); } - private Map writeValueToImplicitCollection(UnmarshallingContext context, Object value, Map implicitCollections, Object result, String itemFieldName) { + private void writeValueToImplicitCollection(HierarchicalStreamReader reader, UnmarshallingContext context, Object value, Map> implicitCollections, Map> implicitCollectionElementTypes, Object result, String itemFieldName) { String fieldName = mapper.getFieldNameForItemTypeAndName(context.getRequiredType(), value.getClass(), itemFieldName); if (fieldName != null) { - if (implicitCollections == null) { - implicitCollections = new HashMap(); // lazy instantiation - } - Collection collection = (Collection) implicitCollections.get(fieldName); + Collection collection = implicitCollections.get(fieldName); if (collection == null) { - Class fieldType = mapper.defaultImplementationOf(reflectionProvider.getFieldType(result, fieldName, null)); + Field field = reflectionProvider.getField(result.getClass(), fieldName); + Class fieldType = mapper.defaultImplementationOf(field.getType()); if (!Collection.class.isAssignableFrom(fieldType)) { throw new ObjectAccessException("Field " + fieldName + " of " + result.getClass().getName() + " is configured for an implicit Collection, but field is of type " + fieldType.getName()); @@ -473,10 +481,25 @@ private Map writeValueToImplicitCollection(UnmarshallingContext context, Object collection = (Collection) pureJavaReflectionProvider.newInstance(fieldType); reflectionProvider.writeField(result, fieldName, collection, null); implicitCollections.put(fieldName, collection); + Type fieldGenericType = field.getGenericType(); + Type elementGenericType = Types.getTypeArgument(Types.getBaseClass(fieldGenericType, Collection.class), 0, Object.class); + Class elementType = Types.erasure(elementGenericType); + implicitCollectionElementTypes.put(fieldName, elementType); + } + Class elementType = implicitCollectionElementTypes.getOrDefault(fieldName, Object.class); + if (!elementType.isInstance(value)) { + var exception = new ConversionException("Invalid element type for implicit collection for field: " + fieldName); + // c.f. TreeUnmarshaller.addInformationTo + exception.add("required-type", elementType.getName()); + exception.add("class", value.getClass().getName()); + exception.add("converter-type", getClass().getName()); + reader.appendErrors(exception); + throw exception; } collection.add(value); + } else { + // TODO: Should we warn in this case? The value will be ignored. } - return implicitCollections; } private Class determineWhichClassDefinesField(HierarchicalStreamReader reader) { diff --git a/core/src/main/java/jenkins/model/queue/ItemDeletion.java b/core/src/main/java/jenkins/model/queue/ItemDeletion.java index a2d954fbc459..b278f4d24c93 100644 --- a/core/src/main/java/jenkins/model/queue/ItemDeletion.java +++ b/core/src/main/java/jenkins/model/queue/ItemDeletion.java @@ -266,12 +266,10 @@ public static void cancelBuildsInProgress(@NonNull Item initiatingItem) throws F // comparison with executor.getCurrentExecutable() == executable currently should always be // true as we no longer recycle Executors, but safer to future-proof in case we ever // revisit recycling. - if (!entry.getKey().isAlive() + if (!entry.getKey().isActive() || entry.getValue() != entry.getKey().getCurrentExecutable()) { iterator.remove(); } - // I don't know why, but we have to keep interrupting - entry.getKey().interrupt(Result.ABORTED); } Thread.sleep(50L); } diff --git a/core/src/site/site.xml b/core/src/site/site.xml index 56902cecd2a1..8b17895170df 100644 --- a/core/src/site/site.xml +++ b/core/src/site/site.xml @@ -6,7 +6,7 @@ + diff --git a/core/src/test/java/hudson/util/RobustCollectionConverterTest.java b/core/src/test/java/hudson/util/RobustCollectionConverterTest.java index 7786fb0833f7..57f2ad42af39 100644 --- a/core/src/test/java/hudson/util/RobustCollectionConverterTest.java +++ b/core/src/test/java/hudson/util/RobustCollectionConverterTest.java @@ -32,6 +32,8 @@ import static org.junit.Assert.assertTrue; import com.thoughtworks.xstream.security.InputManipulationException; +import hudson.model.Saveable; +import java.io.IOException; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; @@ -42,10 +44,24 @@ import java.util.Map; import java.util.Set; import jenkins.util.xstream.CriticalXStreamException; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.jvnet.hudson.test.Issue; public class RobustCollectionConverterTest { + private final boolean originalRecordFailures = RobustReflectionConverter.RECORD_FAILURES_FOR_ALL_AUTHENTICATIONS; + + @Before + public void before() { + RobustReflectionConverter.RECORD_FAILURES_FOR_ALL_AUTHENTICATIONS = true; + } + + @After + public void after() { + RobustReflectionConverter.RECORD_FAILURES_FOR_ALL_AUTHENTICATIONS = originalRecordFailures; + } + @Test public void workingByDefaultWithSimplePayload() { XStream2 xstream2 = new XStream2(); @@ -173,4 +189,58 @@ private Set preparePayload() { } return set; } + + @Issue("JENKINS-63343") + @Test + public void checkElementTypes() { + var xmlContent = + """ + + + 1 + 2 + oops! + + 3 + + + """; + var actual = (Data) new XStream2().fromXML(xmlContent); + assertEquals(Arrays.asList(1, 2, null, 3), actual.numbers); + } + + @Test + public void rawtypes() { + var xmlContent = + """ + + + 1 + 2 + oops! + 3 + + + """; + var actual = (DataRaw) new XStream2().fromXML(xmlContent); + assertEquals(List.of(1, 2, "oops!", 3), actual.values); + } + + public static class Data implements Saveable { + private List numbers; + + @Override + public void save() throws IOException { + // We only implement Saveable so that RobustReflectionConverter logs deserialization problems. + } + } + + public static class DataRaw implements Saveable { + private List values; + + @Override + public void save() throws IOException { + // We only implement Saveable so that RobustReflectionConverter logs deserialization problems. + } + } } diff --git a/core/src/test/java/hudson/util/RobustMapConverterTest.java b/core/src/test/java/hudson/util/RobustMapConverterTest.java index b74ac9303f71..c46a7ade2f57 100644 --- a/core/src/test/java/hudson/util/RobustMapConverterTest.java +++ b/core/src/test/java/hudson/util/RobustMapConverterTest.java @@ -32,13 +32,29 @@ import static org.junit.Assert.assertTrue; import com.thoughtworks.xstream.security.InputManipulationException; +import hudson.model.Saveable; +import java.io.IOException; import java.util.HashMap; import java.util.Map; import jenkins.util.xstream.CriticalXStreamException; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.jvnet.hudson.test.Issue; public class RobustMapConverterTest { + private final boolean originalRecordFailures = RobustReflectionConverter.RECORD_FAILURES_FOR_ALL_AUTHENTICATIONS; + + @Before + public void before() { + RobustReflectionConverter.RECORD_FAILURES_FOR_ALL_AUTHENTICATIONS = true; + } + + @After + public void after() { + RobustReflectionConverter.RECORD_FAILURES_FOR_ALL_AUTHENTICATIONS = originalRecordFailures; + } + /** * As RobustMapConverter is the replacer of the default MapConverter * We had to patch it in order to not be impacted by CVE-2021-43859 @@ -146,6 +162,7 @@ private Map preparePayload() { @Test public void robustAgainstInvalidEntry() { + RobustReflectionConverter.RECORD_FAILURES_FOR_ALL_AUTHENTICATIONS = true; XStream2 xstream2 = new XStream2(); String xml = """ @@ -184,7 +201,103 @@ public void robustAgainstInvalidEntryWithNoValue() { assertThat(data.map, equalTo(Map.of("key2", "value2"))); } - private static final class Data { + @Issue("JENKINS-63343") + @Test + public void robustAgainstInvalidKeyType() { + XStream2 xstream2 = new XStream2(); + String xml = + """ + + + + 1 + value1 + + + key2 + value2 + + + + value3 + + + + """; + Data data = (Data) xstream2.fromXML(xml); + var map = new HashMap<>(); + map.put("key2", "value2"); + map.put(null, "value3"); + assertThat(data.map, equalTo(map)); + } + + @Issue("JENKINS-63343") + @Test + public void robustAgainstInvalidValueType() { + XStream2 xstream2 = new XStream2(); + String xml = + """ + + + + key1 + value1 + + + key2 + 2 + + + key3 + + + + + """; + Data data = (Data) xstream2.fromXML(xml); + var map = new HashMap<>(); + map.put("key1", "value1"); + map.put("key3", null); + assertThat(data.map, equalTo(map)); + } + + @Test + public void rawtypes() { + XStream2 xstream2 = new XStream2(); + String xml = + """ + + + + key1 + value1 + + + key2 + 2 + + + + """; + var data = (DataRaw) xstream2.fromXML(xml); + assertThat(data.map, equalTo(Map.of("key1", "value1", "key2", 2))); + } + + private static class Data implements Saveable { Map map; + + @Override + public void save() throws IOException { + // We only implement Saveable so that RobustReflectionConverter logs deserialization problems. + } + } + + private static class DataRaw implements Saveable { + Map map; + + @Override + public void save() throws IOException { + // We only implement Saveable so that RobustReflectionConverter logs deserialization problems. + } } } diff --git a/core/src/test/java/hudson/util/RobustReflectionConverterTest.java b/core/src/test/java/hudson/util/RobustReflectionConverterTest.java index bb392132afdd..9f3da4b754b3 100644 --- a/core/src/test/java/hudson/util/RobustReflectionConverterTest.java +++ b/core/src/test/java/hudson/util/RobustReflectionConverterTest.java @@ -26,6 +26,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -41,6 +42,8 @@ import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.mapper.Mapper; import com.thoughtworks.xstream.security.InputManipulationException; +import hudson.model.Saveable; +import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -52,6 +55,9 @@ import java.util.logging.Logger; import jenkins.util.xstream.CriticalXStreamException; import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.jvnet.hudson.test.Issue; @@ -59,11 +65,22 @@ * @author Kohsuke Kawaguchi */ public class RobustReflectionConverterTest { + private final boolean originalRecordFailures = RobustReflectionConverter.RECORD_FAILURES_FOR_ALL_AUTHENTICATIONS; static { Logger.getLogger(RobustReflectionConverter.class.getName()).setLevel(Level.OFF); } + @Before + public void before() { + RobustReflectionConverter.RECORD_FAILURES_FOR_ALL_AUTHENTICATIONS = true; + } + + @After + public void after() { + RobustReflectionConverter.RECORD_FAILURES_FOR_ALL_AUTHENTICATIONS = originalRecordFailures; + } + @Test public void robustUnmarshalling() { Point p = read(new XStream2()); @@ -132,8 +149,72 @@ public void implicitCollection() { "", xs.toXML(h)); } - public static class Hold { + @Ignore("Throws an NPE in writeValueToImplicitCollection. Issue has existed since RobustReflectionConverter was created.") + @Test + public void implicitCollectionsAllowNullElements() { + XStream2 xs = new XStream2(); + xs.alias("hold", Hold.class); + xs.addImplicitCollection(Hold.class, "items", "item", String.class); + Hold h = (Hold) xs.fromXML("b"); + assertThat(h.items, Matchers.containsInAnyOrder(null, "b")); + assertEquals("\n" + + " \n" + + " b\n" + + "", xs.toXML(h)); + } + + @Issue("JENKINS-63343") + @Test + public void robustAgainstImplicitCollectionElementsWithBadTypes() { + XStream2 xs = new XStream2(); + xs.alias("hold", Hold.class); + // Note that the fix only matters for `addImplicitCollection` overloads like the following where the element type is not provided. + xs.addImplicitCollection(Hold.class, "items"); + Hold h = (Hold) xs.fromXML( + """ + + 123 + abc + 456 + def + + """); + assertThat(h.items, equalTo(List.of("abc", "def"))); + } + + public static class Hold implements Saveable { List items; + + @Override + public void save() throws IOException { + // We only implement Saveable so that RobustReflectionConverter logs deserialization problems. + } + } + + @Test + public void implicitCollectionRawtypes() { + XStream2 xs = new XStream2(); + xs.alias("hold", HoldRaw.class); + xs.addImplicitCollection(HoldRaw.class, "items"); + var h = (HoldRaw) xs.fromXML( + """ + + 123 + abc + 456 + def + + """); + assertThat(h.items, equalTo(List.of(123, "abc", 456, "def"))); + } + + public static class HoldRaw implements Saveable { + List items; + + @Override + public void save() throws IOException { + // We only implement Saveable so that RobustReflectionConverter logs deserialization problems. + } } @Retention(RetentionPolicy.RUNTIME) @interface Owner { diff --git a/package.json b/package.json index bd27d5b79eff..6e01d39f94bf 100644 --- a/package.json +++ b/package.json @@ -26,12 +26,12 @@ "@babel/cli": "7.25.7", "@babel/core": "7.25.7", "@babel/preset-env": "7.25.7", - "@eslint/js": "9.11.1", + "@eslint/js": "9.12.0", "babel-loader": "9.2.1", "clean-webpack-plugin": "4.0.0", "css-loader": "7.1.2", "css-minimizer-webpack-plugin": "7.0.0", - "eslint": "9.11.1", + "eslint": "9.12.0", "eslint-config-prettier": "9.1.0", "eslint-formatter-checkstyle": "8.40.0", "globals": "15.10.0", @@ -39,7 +39,7 @@ "mini-css-extract-plugin": "2.9.1", "postcss": "8.4.47", "postcss-loader": "8.1.1", - "postcss-preset-env": "10.0.5", + "postcss-preset-env": "10.0.6", "postcss-scss": "4.0.9", "prettier": "3.3.3", "sass": "1.79.4", diff --git a/pom.xml b/pom.xml index 0dde62a0569d..0ae24f547d80 100644 --- a/pom.xml +++ b/pom.xml @@ -73,9 +73,9 @@ THE SOFTWARE. - 2.480 + 2.481 -SNAPSHOT - 2024-09-27T11:17:44Z + 2024-10-08T14:08:31Z github @@ -98,7 +98,7 @@ THE SOFTWARE. false 8.1 - 20.17.0 + 20.18.0 org.jenkins-ci.plugins scm-api - 696.v778d637b_a_762 + 698.v8e3b_c788f0a_6 @@ -178,7 +178,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-test-harness - 2307.v10e5d0701b_e5 + 2341.v35346d95d2b_7 test @@ -224,13 +224,13 @@ THE SOFTWARE. org.jenkins-ci.plugins credentials - 1381.v2c3a_12074da_b_ + 1389.vd7a_b_f5fa_50a_2 test org.jenkins-ci.plugins junit - 1303.v05e2505656b_7 + 1304.vc85a_b_ca_96613 test @@ -248,7 +248,7 @@ THE SOFTWARE. org.jenkins-ci.plugins matrix-project - 838.v4d7b_7b_f9b_d4b_ + 839.vff91cd7e3a_b_2 test diff --git a/war/pom.xml b/war/pom.xml index 8dfca6d4ce29..6511cded90fc 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -300,7 +300,7 @@ THE SOFTWARE. org.jenkins-ci.plugins matrix-project - 838.v4d7b_7b_f9b_d4b_ + 839.vff91cd7e3a_b_2 hpi @@ -314,7 +314,7 @@ THE SOFTWARE. org.jenkins-ci.plugins junit - 1303.v05e2505656b_7 + 1304.vc85a_b_ca_96613 hpi @@ -418,7 +418,7 @@ THE SOFTWARE. org.jenkins-ci.plugins scm-api - 696.v778d637b_a_762 + 698.v8e3b_c788f0a_6 hpi @@ -502,7 +502,7 @@ THE SOFTWARE. io.jenkins.plugins asm-api - 9.7-33.v4d23ef79fcc8 + 9.7.1-95.v9f552033802a_ hpi diff --git a/war/src/main/js/components/dropdowns/utils.js b/war/src/main/js/components/dropdowns/utils.js index a15c4d2c2ba5..27931dc7de32 100644 --- a/war/src/main/js/components/dropdowns/utils.js +++ b/war/src/main/js/components/dropdowns/utils.js @@ -12,6 +12,10 @@ const SELECTED_ITEM_CLASS = "jenkins-dropdown__item--selected"; * @param callback - called to retrieve the list of dropdown items */ function generateDropdown(element, callback, immediate) { + if (element._tippy && element._tippy.props.theme === "dropdown") { + element._tippy.destroy(); + } + tippy( element, Object.assign({}, Templates.dropdown(), { diff --git a/yarn.lock b/yarn.lock index 46e29f140aa7..1c9ced4c8b74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1871,10 +1871,10 @@ __metadata: languageName: node linkType: hard -"@eslint/js@npm:9.11.1": - version: 9.11.1 - resolution: "@eslint/js@npm:9.11.1" - checksum: 10c0/22916ef7b09c6f60c62635d897c66e1e3e38d90b5a5cf5e62769033472ecbcfb6ec7c886090a4b32fe65d6ce371da54384e46c26a899e38184dfc152c6152f7b +"@eslint/js@npm:9.12.0": + version: 9.12.0 + resolution: "@eslint/js@npm:9.12.0" + checksum: 10c0/325650a59a1ce3d97c69441501ebaf415607248bacbe8c8ca35adc7cb73b524f592f266a75772f496b06f3239e3ee1996722a242148085f0ee5fb3dd7065897c languageName: node linkType: hard @@ -1894,6 +1894,23 @@ __metadata: languageName: node linkType: hard +"@humanfs/core@npm:^0.19.0": + version: 0.19.0 + resolution: "@humanfs/core@npm:0.19.0" + checksum: 10c0/f87952d5caba6ae427a620eff783c5d0b6cef0cfc256dec359cdaa636c5f161edb8d8dad576742b3de7f0b2f222b34aad6870248e4b7d2177f013426cbcda232 + languageName: node + linkType: hard + +"@humanfs/node@npm:^0.16.5": + version: 0.16.5 + resolution: "@humanfs/node@npm:0.16.5" + dependencies: + "@humanfs/core": "npm:^0.19.0" + "@humanwhocodes/retry": "npm:^0.3.0" + checksum: 10c0/41c365ab09e7c9eaeed373d09243195aef616d6745608a36fc3e44506148c28843872f85e69e2bf5f1e992e194286155a1c1cecfcece6a2f43875e37cd243935 + languageName: node + linkType: hard + "@humanwhocodes/module-importer@npm:^1.0.1": version: 1.0.1 resolution: "@humanwhocodes/module-importer@npm:1.0.1" @@ -1901,10 +1918,10 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/retry@npm:^0.3.0": - version: 0.3.0 - resolution: "@humanwhocodes/retry@npm:0.3.0" - checksum: 10c0/7111ec4e098b1a428459b4e3be5a5d2a13b02905f805a2468f4fa628d072f0de2da26a27d04f65ea2846f73ba51f4204661709f05bfccff645e3cedef8781bb6 +"@humanwhocodes/retry@npm:^0.3.0, @humanwhocodes/retry@npm:^0.3.1": + version: 0.3.1 + resolution: "@humanwhocodes/retry@npm:0.3.1" + checksum: 10c0/f0da1282dfb45e8120480b9e2e275e2ac9bbe1cf016d046fdad8e27cc1285c45bb9e711681237944445157b430093412b4446c1ab3fc4bb037861b5904101d3b languageName: node linkType: hard @@ -2021,7 +2038,7 @@ __metadata: languageName: node linkType: hard -"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": +"@nodelib/fs.walk@npm:^1.2.3": version: 1.2.8 resolution: "@nodelib/fs.walk@npm:1.2.8" dependencies: @@ -3503,13 +3520,13 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:^8.0.2": - version: 8.0.2 - resolution: "eslint-scope@npm:8.0.2" +"eslint-scope@npm:^8.1.0": + version: 8.1.0 + resolution: "eslint-scope@npm:8.1.0" dependencies: esrecurse: "npm:^4.3.0" estraverse: "npm:^5.2.0" - checksum: 10c0/477f820647c8755229da913025b4567347fd1f0bf7cbdf3a256efff26a7e2e130433df052bd9e3d014025423dc00489bea47eb341002b15553673379c1a7dc36 + checksum: 10c0/ae1df7accae9ea90465c2ded70f7064d6d1f2962ef4cc87398855c4f0b3a5ab01063e0258d954bb94b184f6759febe04c3118195cab5c51978a7229948ba2875 languageName: node linkType: hard @@ -3520,27 +3537,27 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^4.0.0": - version: 4.0.0 - resolution: "eslint-visitor-keys@npm:4.0.0" - checksum: 10c0/76619f42cf162705a1515a6868e6fc7567e185c7063a05621a8ac4c3b850d022661262c21d9f1fc1d144ecf0d5d64d70a3f43c15c3fc969a61ace0fb25698cf5 +"eslint-visitor-keys@npm:^4.1.0": + version: 4.1.0 + resolution: "eslint-visitor-keys@npm:4.1.0" + checksum: 10c0/5483ef114c93a136aa234140d7aa3bd259488dae866d35cb0d0b52e6a158f614760a57256ac8d549acc590a87042cb40f6951815caa821e55dc4fd6ef4c722eb languageName: node linkType: hard -"eslint@npm:9.11.1": - version: 9.11.1 - resolution: "eslint@npm:9.11.1" +"eslint@npm:9.12.0": + version: 9.12.0 + resolution: "eslint@npm:9.12.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.2.0" "@eslint-community/regexpp": "npm:^4.11.0" "@eslint/config-array": "npm:^0.18.0" "@eslint/core": "npm:^0.6.0" "@eslint/eslintrc": "npm:^3.1.0" - "@eslint/js": "npm:9.11.1" + "@eslint/js": "npm:9.12.0" "@eslint/plugin-kit": "npm:^0.2.0" + "@humanfs/node": "npm:^0.16.5" "@humanwhocodes/module-importer": "npm:^1.0.1" - "@humanwhocodes/retry": "npm:^0.3.0" - "@nodelib/fs.walk": "npm:^1.2.8" + "@humanwhocodes/retry": "npm:^0.3.1" "@types/estree": "npm:^1.0.6" "@types/json-schema": "npm:^7.0.15" ajv: "npm:^6.12.4" @@ -3548,9 +3565,9 @@ __metadata: cross-spawn: "npm:^7.0.2" debug: "npm:^4.3.2" escape-string-regexp: "npm:^4.0.0" - eslint-scope: "npm:^8.0.2" - eslint-visitor-keys: "npm:^4.0.0" - espree: "npm:^10.1.0" + eslint-scope: "npm:^8.1.0" + eslint-visitor-keys: "npm:^4.1.0" + espree: "npm:^10.2.0" esquery: "npm:^1.5.0" esutils: "npm:^2.0.2" fast-deep-equal: "npm:^3.1.3" @@ -3560,13 +3577,11 @@ __metadata: ignore: "npm:^5.2.0" imurmurhash: "npm:^0.1.4" is-glob: "npm:^4.0.0" - is-path-inside: "npm:^3.0.3" json-stable-stringify-without-jsonify: "npm:^1.0.1" lodash.merge: "npm:^4.6.2" minimatch: "npm:^3.1.2" natural-compare: "npm:^1.4.0" optionator: "npm:^0.9.3" - strip-ansi: "npm:^6.0.1" text-table: "npm:^0.2.0" peerDependencies: jiti: "*" @@ -3575,18 +3590,18 @@ __metadata: optional: true bin: eslint: bin/eslint.js - checksum: 10c0/fc9afc31155fef8c27fc4fd00669aeafa4b89ce5abfbf6f60e05482c03d7ff1d5e7546e416aa47bf0f28c9a56597a94663fd0264c2c42a1890f53cac49189f24 + checksum: 10c0/67cf6ea3ea28dcda7dd54aac33e2d4028eb36991d13defb0d2339c3eaa877d5dddd12cd4416ddc701a68bcde9e0bb9e65524c2e4e9914992c724f5b51e949dda languageName: node linkType: hard -"espree@npm:^10.0.1, espree@npm:^10.1.0": - version: 10.1.0 - resolution: "espree@npm:10.1.0" +"espree@npm:^10.0.1, espree@npm:^10.2.0": + version: 10.2.0 + resolution: "espree@npm:10.2.0" dependencies: acorn: "npm:^8.12.0" acorn-jsx: "npm:^5.3.2" - eslint-visitor-keys: "npm:^4.0.0" - checksum: 10c0/52e6feaa77a31a6038f0c0e3fce93010a4625701925b0715cd54a2ae190b3275053a0717db698697b32653788ac04845e489d6773b508d6c2e8752f3c57470a0 + eslint-visitor-keys: "npm:^4.1.0" + checksum: 10c0/2b6bfb683e7e5ab2e9513949879140898d80a2d9867ea1db6ff5b0256df81722633b60a7523a7c614f05a39aeea159dd09ad2a0e90c0e218732fc016f9086215 languageName: node linkType: hard @@ -4338,13 +4353,6 @@ __metadata: languageName: node linkType: hard -"is-path-inside@npm:^3.0.3": - version: 3.0.3 - resolution: "is-path-inside@npm:3.0.3" - checksum: 10c0/cf7d4ac35fb96bab6a1d2c3598fe5ebb29aafb52c0aaa482b5a3ed9d8ba3edc11631e3ec2637660c44b3ce0e61a08d54946e8af30dec0b60a7c27296c68ffd05 - languageName: node - linkType: hard - "is-plain-object@npm:^2.0.4": version: 2.0.4 resolution: "is-plain-object@npm:2.0.4" @@ -4402,12 +4410,12 @@ __metadata: "@babel/cli": "npm:7.25.7" "@babel/core": "npm:7.25.7" "@babel/preset-env": "npm:7.25.7" - "@eslint/js": "npm:9.11.1" + "@eslint/js": "npm:9.12.0" babel-loader: "npm:9.2.1" clean-webpack-plugin: "npm:4.0.0" css-loader: "npm:7.1.2" css-minimizer-webpack-plugin: "npm:7.0.0" - eslint: "npm:9.11.1" + eslint: "npm:9.12.0" eslint-config-prettier: "npm:9.1.0" eslint-formatter-checkstyle: "npm:8.40.0" globals: "npm:15.10.0" @@ -4419,7 +4427,7 @@ __metadata: mini-css-extract-plugin: "npm:2.9.1" postcss: "npm:8.4.47" postcss-loader: "npm:8.1.1" - postcss-preset-env: "npm:10.0.5" + postcss-preset-env: "npm:10.0.6" postcss-scss: "npm:4.0.9" prettier: "npm:3.3.3" sass: "npm:1.79.4" @@ -5422,9 +5430,9 @@ __metadata: languageName: node linkType: hard -"postcss-custom-media@npm:^11.0.1": - version: 11.0.1 - resolution: "postcss-custom-media@npm:11.0.1" +"postcss-custom-media@npm:^11.0.2": + version: 11.0.2 + resolution: "postcss-custom-media@npm:11.0.2" dependencies: "@csstools/cascade-layer-name-parser": "npm:^2.0.1" "@csstools/css-parser-algorithms": "npm:^3.0.1" @@ -5432,7 +5440,7 @@ __metadata: "@csstools/media-query-list-parser": "npm:^3.0.1" peerDependencies: postcss: ^8.4 - checksum: 10c0/771b281a28f105370ede7c4a86f9e3dd8d9ec3bf2d2883d4f2cfe9c42b5ec1bf88f713458b356870315d0ba3a285fbeb7bb514a1203d1c4fb113bd9044369bf2 + checksum: 10c0/7bec2b1e0b5d786c33c5715b611ffc8b9737252ee6bf77ca59255ac16f91ce614406923f43250e5c88b04f1bb050f155dc5ed4d9350dbd704c45fbd72e5a9a04 languageName: node linkType: hard @@ -5907,9 +5915,9 @@ __metadata: languageName: node linkType: hard -"postcss-preset-env@npm:10.0.5": - version: 10.0.5 - resolution: "postcss-preset-env@npm:10.0.5" +"postcss-preset-env@npm:10.0.6": + version: 10.0.6 + resolution: "postcss-preset-env@npm:10.0.6" dependencies: "@csstools/postcss-cascade-layers": "npm:^5.0.0" "@csstools/postcss-color-function": "npm:^4.0.2" @@ -5952,7 +5960,7 @@ __metadata: postcss-color-functional-notation: "npm:^7.0.2" postcss-color-hex-alpha: "npm:^10.0.0" postcss-color-rebeccapurple: "npm:^10.0.0" - postcss-custom-media: "npm:^11.0.1" + postcss-custom-media: "npm:^11.0.2" postcss-custom-properties: "npm:^14.0.1" postcss-custom-selectors: "npm:^8.0.1" postcss-dir-pseudo-class: "npm:^9.0.0" @@ -5974,7 +5982,7 @@ __metadata: postcss-selector-not: "npm:^8.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/db5eb1175cb26bed3f1a4c47acc67935ffc784520321470520e59de366ac6f91be1e609fe36056af707ed20f7910721287cff0fae416c437dd3e944de13ffd05 + checksum: 10c0/01660acf3b9ddf4d612a31819e9a5de9fe5383e9eddd2c130180f66ae90c5a881eb408e73454fd50e1839eae71678d738bf72073de08f9013c183b0bd9950fe5 languageName: node linkType: hard