From d27a5010aa1aceecf2f5fda6f795ff573f44c482 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 21 Sep 2020 14:56:51 -0400 Subject: [PATCH 01/23] Unforking XStream --- bom/pom.xml | 4 +-- core/pom.xml | 2 +- .../java/hudson/tools/ToolInstallation.java | 4 +-- core/src/main/java/hudson/util/XStream2.java | 26 +++++++++++++------ 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/bom/pom.xml b/bom/pom.xml index 3b42ce43958e..47fda0c1245a 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -326,9 +326,9 @@ THE SOFTWARE. - org.jvnet.hudson + com.thoughtworks.xstream xstream - 1.4.7-jenkins-1 + 1.4.13 net.sf.kxml diff --git a/core/pom.xml b/core/pom.xml index f975bb344795..478f9f8d135b 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -228,7 +228,7 @@ THE SOFTWARE. antlr - org.jvnet.hudson + com.thoughtworks.xstream xstream diff --git a/core/src/main/java/hudson/tools/ToolInstallation.java b/core/src/main/java/hudson/tools/ToolInstallation.java index df7e30f69f26..9883b8870fed 100644 --- a/core/src/main/java/hudson/tools/ToolInstallation.java +++ b/core/src/main/java/hudson/tools/ToolInstallation.java @@ -38,7 +38,6 @@ import java.io.IOException; import java.util.List; -import com.thoughtworks.xstream.annotations.XStreamSerializable; import com.thoughtworks.xstream.converters.UnmarshallingContext; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; @@ -82,8 +81,7 @@ public abstract class ToolInstallation extends AbstractDescribableImpl,ToolPropertyDescriptor> properties + private /*almost final*/ DescribableList,ToolPropertyDescriptor> properties = new DescribableList<>(Saveable.NOOP); /** diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index c86e82308ec0..6492e38cdd3f 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -30,16 +30,17 @@ import com.thoughtworks.xstream.mapper.AnnotationMapper; import com.thoughtworks.xstream.mapper.Mapper; import com.thoughtworks.xstream.mapper.MapperWrapper; -import com.thoughtworks.xstream.mapper.XStream11XmlFriendlyMapper; import com.thoughtworks.xstream.converters.ConversionException; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.ConverterMatcher; +import com.thoughtworks.xstream.converters.ConverterRegistry; import com.thoughtworks.xstream.converters.DataHolder; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.SingleValueConverter; import com.thoughtworks.xstream.converters.SingleValueConverterWrapper; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.converters.extended.DynamicProxyConverter; +import com.thoughtworks.xstream.core.ClassLoaderReference; import com.thoughtworks.xstream.core.JVM; import com.thoughtworks.xstream.core.util.Fields; import com.thoughtworks.xstream.io.HierarchicalStreamDriver; @@ -212,10 +213,11 @@ public void moveDown() { } @Override - protected Converter createDefaultConverter() { + protected void setupConverters() { + super.setupConverters(); // replace default reflection converter - reflectionConverter = new RobustReflectionConverter(getMapper(),new JVM().bestReflectionProvider(), new PluginClassOwnership()); - return reflectionConverter; + reflectionConverter = new RobustReflectionConverter(getMapper(), JVM.newReflectionProvider(), new PluginClassOwnership()); + registerConverter(reflectionConverter, PRIORITY_VERY_LOW + 1); } /** @@ -235,7 +237,7 @@ static String trimVersion(String version) { private void init() { // list up types that should be marshalled out like a value, without referential integrity tracking. - addImmutableType(Result.class); + addImmutableType(Result.class, false); // http://www.openwall.com/lists/oss-security/2017/04/03/4 denyTypes(new Class[] { void.class, Void.class }); @@ -258,7 +260,7 @@ private void init() { registerConverter(new BlacklistedTypesConverter(), PRIORITY_VERY_HIGH); // SECURITY-247 defense - registerConverter(new DynamicProxyConverter(getMapper()) { // SECURITY-105 defense + registerConverter(new DynamicProxyConverter(getMapper(), new ClassLoaderReference(getClassLoader())) { // SECURITY-105 defense @Override public boolean canConvert(Class type) { return /* this precedes NullConverter */ type != null && super.canConvert(type); } @@ -281,7 +283,15 @@ else if (type != null && ImmutableList.class.isAssignableFrom(type)) return super.serializedClass(type); } }); - AnnotationMapper a = new AnnotationMapper(m, getConverterRegistry(), getConverterLookup(), getClassLoader(), getReflectionProvider(), getJvm()); + ConverterRegistry converterRegistry; + try { + Field converterRegistryF = XStream.class.getDeclaredField("converterRegistry"); + converterRegistryF.setAccessible(true); + converterRegistry = (ConverterRegistry) converterRegistryF.get(this); + } catch (Exception x) { + throw new AssertionError(x); + } + AnnotationMapper a = new AnnotationMapper(m, converterRegistry, getConverterLookup(), new ClassLoaderReference(getClassLoader()), getReflectionProvider()); // TODO JENKINS-19561 this is unsafe: a.autodetectAnnotations(true); @@ -361,7 +371,7 @@ public void addCompatibilityAlias(String oldClassName, Class newClass) { /** * Prior to Hudson 1.106, XStream 1.1.x was used which encoded "$" in class names * as "-" instead of "_-" that is used now. Up through Hudson 1.348 compatibility - * for old serialized data was maintained via {@link XStream11XmlFriendlyMapper}. + * for old serialized data was maintained via {@link com.thoughtworks.xstream.mapper.XStream11XmlFriendlyMapper}. * However, it was found (HUDSON-5768) that this caused fields with "__" to fail * deserialization due to double decoding. Now this class is used for compatibility. */ From f5edc8ffd690e95335f6a750a3f76493d78d1020 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 21 Sep 2020 15:35:10 -0400 Subject: [PATCH 02/23] Some overrides missing from MapperDelegate --- .../main/java/hudson/util/xstream/MapperDelegate.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/src/main/java/hudson/util/xstream/MapperDelegate.java b/core/src/main/java/hudson/util/xstream/MapperDelegate.java index f7eb78b5516e..cb84d40803b6 100644 --- a/core/src/main/java/hudson/util/xstream/MapperDelegate.java +++ b/core/src/main/java/hudson/util/xstream/MapperDelegate.java @@ -150,4 +150,15 @@ public SingleValueConverter getConverterFromAttribute(Class type, String attribu public SingleValueConverter getConverterFromAttribute(Class definedIn, String attribute, Class type) { return delegate.getConverterFromAttribute(definedIn, attribute, type); } + + @Override + public boolean isIgnoredElement(String name) { + return delegate.isIgnoredElement(name); + } + + @Override + public boolean isReferenceable(Class type) { + return delegate.isReferenceable(type); + } + } From 0e5aeec4ccc67207c0912ffe471fe5cf2815cf04 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 23 Sep 2020 16:25:54 -0400 Subject: [PATCH 03/23] [XSTR-762] writeReplace & readResolve must be accessible to be used from subclasses --- core/src/main/java/hudson/model/AbstractItem.java | 2 +- core/src/main/java/hudson/model/ListView.java | 2 +- core/src/main/java/hudson/model/Queue.java | 2 +- core/src/main/java/hudson/model/Run.java | 2 +- test/pom.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java index 17025e877287..8fa716ded535 100644 --- a/core/src/main/java/hudson/model/AbstractItem.java +++ b/core/src/main/java/hudson/model/AbstractItem.java @@ -614,7 +614,7 @@ public final XmlFile getConfigFile() { return Items.getConfigFile(this); } - private Object writeReplace() { + protected Object writeReplace() { return XmlFile.replaceIfNotAtTopLevel(this, () -> new Replacer(this)); } private static class Replacer { diff --git a/core/src/main/java/hudson/model/ListView.java b/core/src/main/java/hudson/model/ListView.java index f66b0d628970..05caab691d85 100644 --- a/core/src/main/java/hudson/model/ListView.java +++ b/core/src/main/java/hudson/model/ListView.java @@ -133,7 +133,7 @@ public void setJobFilters(List jobFilters) throws IOException { this.jobFilters.replaceBy(jobFilters); } - private Object readResolve() { + protected Object readResolve() { if(includeRegex!=null) { try { includePattern = Pattern.compile(includeRegex); diff --git a/core/src/main/java/hudson/model/Queue.java b/core/src/main/java/hudson/model/Queue.java index 744582f02cf3..ac71e0417773 100644 --- a/core/src/main/java/hudson/model/Queue.java +++ b/core/src/main/java/hudson/model/Queue.java @@ -2352,7 +2352,7 @@ public Api getApi() throws AccessDeniedException { } } - private Object readResolve() { + protected Object readResolve() { this.future = new FutureImpl(task); return this; } diff --git a/core/src/main/java/hudson/model/Run.java b/core/src/main/java/hudson/model/Run.java index 52ee02bf14c4..2c3fa44d88b8 100644 --- a/core/src/main/java/hudson/model/Run.java +++ b/core/src/main/java/hudson/model/Run.java @@ -2069,7 +2069,7 @@ public synchronized void save() throws IOException { return new XmlFile(XSTREAM,new File(getRootDir(),"build.xml")); } - private Object writeReplace() { + protected Object writeReplace() { return XmlFile.replaceIfNotAtTopLevel(this, () -> new Replacer(this)); } private static class Replacer { diff --git a/test/pom.xml b/test/pom.xml index a0917c51c969..619a773a9a89 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -64,7 +64,7 @@ THE SOFTWARE. ${project.groupId} jenkins-test-harness - 2.63 + 2.68-rc1376.9775a55e4678 test From 69eaa764709006c05099ae9f2fb7573f53572c88 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 23 Sep 2020 17:01:10 -0400 Subject: [PATCH 04/23] DomElement.click overloads changed incompatibly --- test/src/test/java/hudson/model/ProjectTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/test/java/hudson/model/ProjectTest.java b/test/src/test/java/hudson/model/ProjectTest.java index fd2d03af44d6..5ec8cd75c7d8 100644 --- a/test/src/test/java/hudson/model/ProjectTest.java +++ b/test/src/test/java/hudson/model/ProjectTest.java @@ -256,7 +256,7 @@ public void testGetScmCheckoutRetryCount() throws Exception{ HtmlForm form = j.createWebClient().goTo(p.getUrl() + "/configure").getFormByName("config"); ((HtmlElement)form.getByXPath("//div[@class='advancedLink']//button").get(0)).click(); // required due to the new default behavior of click - form.getInputByName("hasCustomScmCheckoutRetryCount").click(new Event(), true); + form.getInputByName("hasCustomScmCheckoutRetryCount").click(new Event(), false, false, false, true); form.getInputByName("scmCheckoutRetryCount").setValueAttribute("7"); j.submit(form); assertEquals("Scm retry count was set.", 7, p.getScmCheckoutRetryCount()); From 4af60991e3cc2be81974e4ccd44a137812617fbb Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 23 Sep 2020 17:23:15 -0400 Subject: [PATCH 05/23] Giving up on ToolInstallation being Serializable. Fixing MavenInstallation accordingly. AntInstallation was already fixed: https://github.com/jenkinsci/ant-plugin/commit/c957dca4cd7789e3918e90ae020687aeaeea998e --- core/src/main/java/hudson/tasks/Maven.java | 35 +++++++++++-------- .../java/hudson/tools/ToolInstallation.java | 3 +- test/pom.xml | 5 +++ 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/hudson/tasks/Maven.java b/core/src/main/java/hudson/tasks/Maven.java index 438c51356220..8bcf2fad67a4 100644 --- a/core/src/main/java/hudson/tasks/Maven.java +++ b/core/src/main/java/hudson/tasks/Maven.java @@ -608,24 +608,29 @@ public boolean isMaven2_1(Launcher launcher) throws IOException, InterruptedExce * Gets the executable path of this maven on the given target system. */ public String getExecutable(Launcher launcher) throws IOException, InterruptedException { - return launcher.getChannel().call(new GetExecutable()); - } - private class GetExecutable extends MasterToSlaveCallable { - private static final long serialVersionUID = 2373163112639943768L; - @Override - public String call() throws IOException { - File exe = getExeFile("mvn"); - if(exe.exists()) - return exe.getPath(); - exe = getExeFile("maven"); - if(exe.exists()) - return exe.getPath(); - return null; + return launcher.getChannel().call(new GetExecutable(getHome())); + } + private static class GetExecutable extends MasterToSlaveCallable { + private final String rawHome; + GetExecutable(String rawHome) { + this.rawHome = rawHome; + } + @Override + public String call() throws IOException { + File exe = getExeFile("mvn", rawHome); + if (exe.exists()) { + return exe.getPath(); + } + exe = getExeFile("maven", rawHome); + if (exe.exists()) { + return exe.getPath(); } + return null; + } } - private File getExeFile(String execName) { - String m2Home = Util.replaceMacro(getHome(),EnvVars.masterEnvVars); + private static File getExeFile(String execName, String home) { + String m2Home = Util.replaceMacro(home, EnvVars.masterEnvVars); if(Functions.isWindows()) { File exeFile = new File(m2Home, "bin/" + execName + ".bat"); diff --git a/core/src/main/java/hudson/tools/ToolInstallation.java b/core/src/main/java/hudson/tools/ToolInstallation.java index 9883b8870fed..402d7d945213 100644 --- a/core/src/main/java/hudson/tools/ToolInstallation.java +++ b/core/src/main/java/hudson/tools/ToolInstallation.java @@ -74,7 +74,7 @@ * @author huybrechts * @since 1.286 */ -public abstract class ToolInstallation extends AbstractDescribableImpl implements Serializable, ExtensionPoint { +public abstract class ToolInstallation extends AbstractDescribableImpl implements ExtensionPoint { private final String name; private /*almost final*/ String home; @@ -242,5 +242,4 @@ public static DescriptorExtensionList> all() return Jenkins.get().getDescriptorList(ToolInstallation.class); } - private static final long serialVersionUID = 1L; } diff --git a/test/pom.xml b/test/pom.xml index 619a773a9a89..7c5bfeb9c3aa 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -51,6 +51,11 @@ THE SOFTWARE. pom import + + org.jenkins-ci.plugins + ant + 1.11 + From 08771f796e77016927955aa7c6ab31081467a1d1 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 24 Sep 2020 14:52:43 -0400 Subject: [PATCH 06/23] Relaxing message assertion in RobustReflectionConverter.randomExceptionsReported --- .../test/java/hudson/util/RobustReflectionConverterTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/test/java/hudson/util/RobustReflectionConverterTest.java b/test/src/test/java/hudson/util/RobustReflectionConverterTest.java index ddd65e757132..f4d1d32b2480 100644 --- a/test/src/test/java/hudson/util/RobustReflectionConverterTest.java +++ b/test/src/test/java/hudson/util/RobustReflectionConverterTest.java @@ -81,7 +81,7 @@ public class RobustReflectionConverterTest { Map data = odm.getData(); assertEquals(Collections.singleton(p), data.keySet()); String text = data.values().iterator().next().extra; - assertTrue(text, text.contains("Could not call hudson.triggers.TimerTrigger.readResolve")); + assertTrue(text, text.contains("hudson.triggers.TimerTrigger.readResolve")); } // Testing describable object to demonstrate what is expected with RobustReflectionConverter#addCriticalField From 232a012f4dcbe3f6f0df81678e3fad3a3eb8800e Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 24 Sep 2020 14:59:41 -0400 Subject: [PATCH 07/23] Picking up https://github.com/jenkinsci/matrix-auth-plugin/pull/89 caught via JobTest --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index aa734a465da1..bb5fa9ef3a17 100755 --- a/pom.xml +++ b/pom.xml @@ -90,7 +90,7 @@ THE SOFTWARE. https://api.github.com jenkins-jira - 2.6.2 + 2.6.4-rc330.062fa8d38aa3 1.17 0.11 ${skipTests} From 861ca26e8ce02960c9589102d113d195d1bd70ce Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 24 Sep 2020 15:46:29 -0400 Subject: [PATCH 08/23] https://github.com/jenkinsci/jenkins-test-harness/pull/243 released --- test/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pom.xml b/test/pom.xml index 7c5bfeb9c3aa..dfc82afe2ce9 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -69,7 +69,7 @@ THE SOFTWARE. ${project.groupId} jenkins-test-harness - 2.68-rc1376.9775a55e4678 + 2.68 test From b89f6d1b32101225eb41ca96747d2dbeba753fe6 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 24 Sep 2020 15:48:03 -0400 Subject: [PATCH 09/23] Another case forgotten in 0e5aeec4ccc67207c0912ffe471fe5cf2815cf04 --- core/src/main/java/jenkins/model/Jenkins.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 2766e5326a1a..c5259af85225 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -1035,7 +1035,7 @@ protected void doRun() throws Exception { * Maintains backwards compatibility. Invoked by XStream when this object is de-serialized. */ @SuppressWarnings({"unused"}) - private Object readResolve() { + protected Object readResolve() { if (jdks == null) { jdks = new ArrayList<>(); } From 1cec1e17943f6732e8187005af540267aeed733f Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 25 Sep 2020 13:48:12 -0400 Subject: [PATCH 10/23] XStream has SecurityMapper, but we have our own system already, so suppress warning --- core/src/main/java/hudson/util/XStream2.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index 6492e38cdd3f..78c0e1ec347d 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -48,6 +48,7 @@ import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.ReaderWrapper; import com.thoughtworks.xstream.mapper.CannotResolveClassException; +import com.thoughtworks.xstream.security.AnyTypePermission; import hudson.PluginManager; import hudson.PluginWrapper; import hudson.XmlFile; @@ -259,6 +260,7 @@ private void init() { registerConverter(new AssociatedConverterImpl(this), -10); registerConverter(new BlacklistedTypesConverter(), PRIORITY_VERY_HIGH); // SECURITY-247 defense + addPermission(AnyTypePermission.ANY); // covered by JEP-200, avoid securityWarningGiven registerConverter(new DynamicProxyConverter(getMapper(), new ClassLoaderReference(getClassLoader())) { // SECURITY-105 defense @Override public boolean canConvert(Class type) { From 0ba7df4cf3a2ddbe63323248a4162c394cf2a57b Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 25 Sep 2020 16:11:05 -0400 Subject: [PATCH 11/23] GetMavenVersion was also serializing its outer class --- core/src/main/java/hudson/tasks/Maven.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/hudson/tasks/Maven.java b/core/src/main/java/hudson/tasks/Maven.java index 8bcf2fad67a4..c7c10d5db0c2 100644 --- a/core/src/main/java/hudson/tasks/Maven.java +++ b/core/src/main/java/hudson/tasks/Maven.java @@ -553,7 +553,7 @@ public void buildEnvVars(EnvVars env) { public boolean meetsMavenReqVersion(Launcher launcher, int mavenReqVersion) throws IOException, InterruptedException { // FIXME using similar stuff as in the maven plugin could be better // olamy : but will add a dependency on maven in core -> so not so good - String mavenVersion = launcher.getChannel().call(new GetMavenVersion()); + String mavenVersion = launcher.getChannel().call(new GetMavenVersion(getHome())); if (!mavenVersion.equals("")) { if (mavenReqVersion == MAVEN_20) { @@ -572,11 +572,14 @@ else if (mavenReqVersion == MAVEN_30) { return false; } - private class GetMavenVersion extends MasterToSlaveCallable { - private static final long serialVersionUID = -4143159957567745621L; + private static class GetMavenVersion extends MasterToSlaveCallable { + private final String home; + GetMavenVersion(String home) { + this.home = home; + } @Override public String call() throws IOException { - File[] jars = new File(getHomeDir(), "lib").listFiles(); + File[] jars = new File(home, "lib").listFiles(); if (jars != null) { // be defensive for (File jar : jars) { if (jar.getName().startsWith("maven-")) { From cdc14144da8027d265676c94d34abda767f4d7cb Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 25 Sep 2020 17:24:24 -0400 Subject: [PATCH 12/23] Trying to make ToolInstallation serializable compatible --- .../java/hudson/tools/ToolInstallation.java | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/hudson/tools/ToolInstallation.java b/core/src/main/java/hudson/tools/ToolInstallation.java index 402d7d945213..8752ea7d2dfa 100644 --- a/core/src/main/java/hudson/tools/ToolInstallation.java +++ b/core/src/main/java/hudson/tools/ToolInstallation.java @@ -30,18 +30,26 @@ import hudson.ExtensionPoint; import hudson.diagnosis.OldDataMonitor; import hudson.model.*; +import hudson.remoting.Channel; import hudson.slaves.NodeSpecific; import hudson.util.DescribableList; import hudson.util.XStream2; import java.io.Serializable; +import java.io.StringReader; import java.io.IOException; import java.util.List; import com.thoughtworks.xstream.converters.UnmarshallingContext; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.logging.Level; +import java.util.logging.Logger; import jenkins.model.Jenkins; +import jenkins.util.Timer; +import org.dom4j.Document; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; /** * Formalization of a tool installed in nodes used for builds. @@ -74,7 +82,10 @@ * @author huybrechts * @since 1.286 */ -public abstract class ToolInstallation extends AbstractDescribableImpl implements ExtensionPoint { +public abstract class ToolInstallation extends AbstractDescribableImpl implements Serializable, ExtensionPoint { + + private static final Logger LOGGER = Logger.getLogger(ToolInstallation.class.getName()); + private final String name; private /*almost final*/ String home; @@ -143,8 +154,10 @@ public String getName() { public void buildEnvVars(EnvVars env) { } - public DescribableList,ToolPropertyDescriptor> getProperties() { - assert properties!=null; + public synchronized DescribableList,ToolPropertyDescriptor> getProperties() { + if (properties == null) { + properties = new DescribableList<>(Saveable.NOOP); + } return properties; } @@ -208,13 +221,32 @@ protected String translateFor(Node node, TaskListener log) throws IOException, I * Invoked by XStream when this object is read into memory. */ protected Object readResolve() { - if(properties==null) - properties = new DescribableList<>(Saveable.NOOP); - for (ToolProperty p : properties) - _setTool(p, this); + if (properties != null) { + for (ToolProperty p : properties) { + _setTool(p, this); + } + } return this; } + protected Object writeReplace() throws Exception { + if (Channel.current() == null) { // XStream + return this; + } else { // Remoting + LOGGER.log(Level.WARNING, "Serialization of " + getClass().getSimpleName() + " extends ToolInstallation over Remoting is deprecated", new Throwable()); + // Hack: properties is not serializable, so try to serialize as XML (in another thread); delete ; deserialize; then load a clone + String xml1 = Timer.get().submit(() -> Jenkins.XSTREAM2.toXML(this)).get(); + Document dom = new SAXReader().read(new StringReader(xml1)); + Element properties = dom.getRootElement().element("properties"); + if (properties != null) { + dom.getRootElement().remove(properties); + } + String xml2 = dom.asXML(); + ToolInstallation clone = (ToolInstallation) Timer.get().submit(() -> Jenkins.XSTREAM2.fromXML(xml2)).get(); + return clone; + } + } + @Override public String toString() { return getClass().getSimpleName() + "[" + name + "]"; } From b45ffbceb4f5cfa1dd91e1ff7ba7f57e8639b4da Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 28 Sep 2020 16:35:20 -0400 Subject: [PATCH 13/23] Avoid throwing IOException from PersistedList.inModified for common cases covered by https://github.com/jenkinsci/jenkins-test-harness/pull/243 --- .../main/java/hudson/util/PersistedList.java | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/util/PersistedList.java b/core/src/main/java/hudson/util/PersistedList.java index 78373c34c6bb..0379ea5bdfc1 100644 --- a/core/src/main/java/hudson/util/PersistedList.java +++ b/core/src/main/java/hudson/util/PersistedList.java @@ -23,6 +23,7 @@ */ package hudson.util; +import com.google.common.collect.ImmutableSet; import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.MarshallingContext; @@ -40,6 +41,10 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Collection whose change is notified to the parent object for persistence. @@ -48,6 +53,9 @@ * @since 1.333 */ public class PersistedList extends AbstractList { + + private static final Logger LOGGER = Logger.getLogger(PersistedList.class.getName()); + protected final CopyOnWriteList data = new CopyOnWriteList<>(); protected Saveable owner = Saveable.NOOP; @@ -170,7 +178,30 @@ public Iterator iterator() { * Called when a list is mutated. */ protected void onModified() throws IOException { - owner.save(); + try { + owner.save(); + } catch (IOException x) { + Optional ignored = stream().filter(PersistedList::ignoreSerializationErrors).findAny(); + if (ignored.isPresent()) { + LOGGER.log(Level.WARNING, "Ignoring serialization errors in " + ignored.get() + "; update your parent POM", x); + } else { + throw x; + } + } + } + + // TODO until https://github.com/jenkinsci/jenkins-test-harness/pull/243 is widely adopted: + private static final Set IGNORED_CLASSES = ImmutableSet.of("org.jvnet.hudson.test.TestBuilder", "org.jvnet.hudson.test.TestNotifier"); + // (SingleFileSCM & ExtractResourceWithChangesSCM would also be nice to suppress, but they are not kept in a PersistedList.) + private static boolean ignoreSerializationErrors(Object o) { + if (o != null) { + for (Class c = o.getClass(); c != Object.class; c = c.getSuperclass()) { + if (IGNORED_CLASSES.contains(c.getName())) { + return true; + } + } + } + return false; } /** From ef894943cca5ad3dd1229edefe053b44ffc4704c Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 28 Sep 2020 16:44:19 -0400 Subject: [PATCH 14/23] Found a way to avoid looking up XStream.converterRegistry by reflection --- core/src/main/java/hudson/util/XStream2.java | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index 78c0e1ec347d..76c66f9c39cb 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -33,7 +33,6 @@ import com.thoughtworks.xstream.converters.ConversionException; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.ConverterMatcher; -import com.thoughtworks.xstream.converters.ConverterRegistry; import com.thoughtworks.xstream.converters.DataHolder; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.SingleValueConverter; @@ -285,15 +284,7 @@ else if (type != null && ImmutableList.class.isAssignableFrom(type)) return super.serializedClass(type); } }); - ConverterRegistry converterRegistry; - try { - Field converterRegistryF = XStream.class.getDeclaredField("converterRegistry"); - converterRegistryF.setAccessible(true); - converterRegistry = (ConverterRegistry) converterRegistryF.get(this); - } catch (Exception x) { - throw new AssertionError(x); - } - AnnotationMapper a = new AnnotationMapper(m, converterRegistry, getConverterLookup(), new ClassLoaderReference(getClassLoader()), getReflectionProvider()); + AnnotationMapper a = new AnnotationMapper(m, this::registerConverter, getConverterLookup(), new ClassLoaderReference(getClassLoader()), getReflectionProvider()); // TODO JENKINS-19561 this is unsafe: a.autodetectAnnotations(true); From 82dd8f819717e749070b2be80e7a92d8e00c1bb5 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 28 Sep 2020 19:26:51 -0400 Subject: [PATCH 15/23] XStream2Test.crashXstream now throwing ConversionException, not ForbiddenClassException --- core/src/test/java/hudson/util/XStream2Test.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/hudson/util/XStream2Test.java b/core/src/test/java/hudson/util/XStream2Test.java index ec9e0bbbbb44..73c2ff3f1829 100644 --- a/core/src/test/java/hudson/util/XStream2Test.java +++ b/core/src/test/java/hudson/util/XStream2Test.java @@ -530,8 +530,8 @@ public void setListDefaultNull(List listDefaultNull) { public void crashXstream() throws Exception { try { new XStream2().fromXML(""); - fail("expected to throw ForbiddenClassException, but why are we still alive?"); - } catch (ForbiddenClassException ex) { + fail("expected to throw ConversionException, but why are we still alive?"); + } catch (XStreamException ex) { // pass } } From 2046d3658f92cb8994ab4c3696eb4021b4a8ece9 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 28 Sep 2020 22:02:56 -0400 Subject: [PATCH 16/23] Noting which version of the parent POM has https://github.com/jenkinsci/jenkins-test-harness/pull/243 --- core/src/main/java/hudson/util/PersistedList.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/util/PersistedList.java b/core/src/main/java/hudson/util/PersistedList.java index 0379ea5bdfc1..acbddee330e5 100644 --- a/core/src/main/java/hudson/util/PersistedList.java +++ b/core/src/main/java/hudson/util/PersistedList.java @@ -183,7 +183,7 @@ protected void onModified() throws IOException { } catch (IOException x) { Optional ignored = stream().filter(PersistedList::ignoreSerializationErrors).findAny(); if (ignored.isPresent()) { - LOGGER.log(Level.WARNING, "Ignoring serialization errors in " + ignored.get() + "; update your parent POM", x); + LOGGER.log(Level.WARNING, "Ignoring serialization errors in " + ignored.get() + "; update your parent POM to 4.8 or newer", x); } else { throw x; } From 09d7dac3df77ba9409fc2f1b843aa06d511818db Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 29 Sep 2020 14:02:20 -0400 Subject: [PATCH 17/23] Refreshed Javadoc for XStream2 --- core/src/main/java/hudson/util/XStream2.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index 76c66f9c39cb..8f1ed4512731 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -83,8 +83,8 @@ import edu.umd.cs.findbugs.annotations.NonNull; /** - * {@link XStream} enhanced for additional Java5 support and improved robustness. - * @author Kohsuke Kawaguchi + * {@link XStream} customized in various ways for Jenkins’ needs. + * Most importantly, integrates {@link RobustReflectionConverter}. */ public class XStream2 extends XStream { From 82037c3f9dd2fa984c0d134c3c342d491a76ba19 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 29 Sep 2020 14:16:51 -0400 Subject: [PATCH 18/23] The simplest solution to JENKINS-19561 seems to be to stop using autodetectAnnotations --- core/src/main/java/hudson/util/XStream2.java | 6 +--- .../test/java/hudson/util/XStream2Test.java | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index 8f1ed4512731..2fe7fd3082bd 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -27,7 +27,6 @@ import com.google.common.collect.ImmutableMap; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.KXml2Driver; -import com.thoughtworks.xstream.mapper.AnnotationMapper; import com.thoughtworks.xstream.mapper.Mapper; import com.thoughtworks.xstream.mapper.MapperWrapper; import com.thoughtworks.xstream.converters.ConversionException; @@ -284,11 +283,8 @@ else if (type != null && ImmutableList.class.isAssignableFrom(type)) return super.serializedClass(type); } }); - AnnotationMapper a = new AnnotationMapper(m, this::registerConverter, getConverterLookup(), new ClassLoaderReference(getClassLoader()), getReflectionProvider()); - // TODO JENKINS-19561 this is unsafe: - a.autodetectAnnotations(true); - mapperInjectionPoint = new MapperInjectionPoint(a); + mapperInjectionPoint = new MapperInjectionPoint(m); return mapperInjectionPoint; } diff --git a/core/src/test/java/hudson/util/XStream2Test.java b/core/src/test/java/hudson/util/XStream2Test.java index 73c2ff3f1829..bada50a42cc9 100644 --- a/core/src/test/java/hudson/util/XStream2Test.java +++ b/core/src/test/java/hudson/util/XStream2Test.java @@ -34,6 +34,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.thoughtworks.xstream.XStreamException; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.mapper.CannotResolveClassException; import com.thoughtworks.xstream.security.ForbiddenClassException; import hudson.model.Result; import hudson.model.Run; @@ -46,6 +48,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import jenkins.model.Jenkins; +import static org.hamcrest.Matchers.instanceOf; import org.junit.Test; import org.jvnet.hudson.test.Issue; @@ -535,4 +538,37 @@ public void crashXstream() throws Exception { // pass } } + + @Test + public void annotations1() throws Exception { + assertEquals("not registered, so sorry", "", Jenkins.XSTREAM2.toXML(new C1())); + assertEquals("manually registered", "", Jenkins.XSTREAM2.toXML(new C2())); + assertEquals("manually processed", "", Jenkins.XSTREAM2.toXML(new C3())); + try { + Jenkins.XSTREAM2.fromXML(""); + fail("this could never have worked anyway"); + } catch (CannotResolveClassException x) { + // OK + } + Jenkins.XSTREAM2.processAnnotations(C5.class); + assertThat("can deserialize from annotations so long as the processing happened at some point", Jenkins.XSTREAM2.fromXML(""), instanceOf(C5.class)); + } + @XStreamAlias("C-1") + public static final class C1 {} + public static final class C2 { + static { + Jenkins.XSTREAM2.alias("C-2", C2.class); + } + } + @XStreamAlias("C-3") + public static final class C3 { + static { + Jenkins.XSTREAM2.processAnnotations(C3.class); + } + } + @XStreamAlias("C-4") + public static final class C4 {} + @XStreamAlias("C-5") + public static final class C5 {} + } From 5b2b5f06a18a1cc0fabcf2a8802219614ba94302 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 29 Sep 2020 16:46:30 -0400 Subject: [PATCH 19/23] Fixing some deprecations in Robust*Converter --- .../hudson/util/RobustCollectionConverter.java | 6 ++++-- .../java/hudson/util/RobustMapConverter.java | 2 +- .../hudson/util/RobustReflectionConverter.java | 17 ++++++++++------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/hudson/util/RobustCollectionConverter.java b/core/src/main/java/hudson/util/RobustCollectionConverter.java index 7f95b2e873a4..4e0d01b15c1f 100644 --- a/core/src/main/java/hudson/util/RobustCollectionConverter.java +++ b/core/src/main/java/hudson/util/RobustCollectionConverter.java @@ -31,6 +31,7 @@ import com.thoughtworks.xstream.mapper.Mapper; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.XStreamException; +import com.thoughtworks.xstream.core.ClassLoaderReference; import java.util.Collection; import java.util.concurrent.CopyOnWriteArrayList; @@ -48,6 +49,7 @@ * * @author Kohsuke Kawaguchi */ +@SuppressWarnings({"rawtypes", "unchecked"}) public class RobustCollectionConverter extends CollectionConverter { private final SerializableConverter sc; @@ -57,7 +59,7 @@ public RobustCollectionConverter(XStream xs) { public RobustCollectionConverter(Mapper mapper, ReflectionProvider reflectionProvider) { super(mapper); - sc = new SerializableConverter(mapper,reflectionProvider); + sc = new SerializableConverter(mapper, reflectionProvider, new ClassLoaderReference(null)); } @Override @@ -82,7 +84,7 @@ protected void populateCollection(HierarchicalStreamReader reader, Unmarshalling while (reader.hasMoreChildren()) { reader.moveDown(); try { - Object item = readItem(reader, context, collection); + Object item = readBareItem(reader, context, collection); collection.add(item); } catch (CriticalXStreamException e) { throw e; diff --git a/core/src/main/java/hudson/util/RobustMapConverter.java b/core/src/main/java/hudson/util/RobustMapConverter.java index ce16bc0b1e4f..c83e7c8fe819 100644 --- a/core/src/main/java/hudson/util/RobustMapConverter.java +++ b/core/src/main/java/hudson/util/RobustMapConverter.java @@ -55,7 +55,7 @@ final class RobustMapConverter extends MapConverter { private Object read(HierarchicalStreamReader reader, UnmarshallingContext context, Map map) { reader.moveDown(); try { - return readItem(reader, context, map); + return readBareItem(reader, context, map); } 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 9b07e3188640..dddf70fd082e 100644 --- a/core/src/main/java/hudson/util/RobustReflectionConverter.java +++ b/core/src/main/java/hudson/util/RobustReflectionConverter.java @@ -33,9 +33,8 @@ import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider; import com.thoughtworks.xstream.converters.reflection.ReflectionConverter; import com.thoughtworks.xstream.converters.reflection.ReflectionProvider; -import com.thoughtworks.xstream.converters.reflection.SerializationMethodInvoker; import com.thoughtworks.xstream.core.util.Primitives; -import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper; +import com.thoughtworks.xstream.core.util.SerializationMembers; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.mapper.Mapper; @@ -71,11 +70,12 @@ * * */ +@SuppressWarnings({"rawtypes", "unchecked"}) public class RobustReflectionConverter implements Converter { protected final ReflectionProvider reflectionProvider; protected final Mapper mapper; - protected transient SerializationMethodInvoker serializationMethodInvoker; + protected transient SerializationMembers serializationMethodInvoker; private transient ReflectionProvider pureJavaReflectionProvider; private final @NonNull XStream2.ClassOwnership classOwnership; /** There are typically few critical fields around, but we end up looking up in this map a lot. @@ -95,7 +95,7 @@ public RobustReflectionConverter(Mapper mapper, ReflectionProvider reflectionPro this.reflectionProvider = reflectionProvider; assert classOwnership != null; this.classOwnership = classOwnership; - serializationMethodInvoker = new SerializationMethodInvoker(); + serializationMethodInvoker = new SerializationMembers(); } void addCriticalField(Class clazz, String field) { @@ -189,6 +189,8 @@ protected void doMarshal(final Object source, final HierarchicalStreamWriter wri // Attributes might be preferred to child elements ... reflectionProvider.visitSerializableFields(source, new ReflectionProvider.Visitor() { + @SuppressWarnings("deprecation") // deliberately calling deprecated methods? + @Override public void visit(String fieldName, Class type, Class definedIn, Object value) { SingleValueConverter converter = mapper.getConverterFromItemType(fieldName, type, definedIn); if (converter == null) converter = mapper.getConverterFromItemType(fieldName, type); @@ -226,12 +228,13 @@ public void visit(String fieldName, Class fieldType, Class definedIn, Object new } } + @SuppressWarnings("deprecation") // TODO HierarchicalStreamWriter#startNode(String, Class) in 1.5.0 private void writeField(String fieldName, String aliasName, Class fieldType, Class definedIn, Object newObj) { try { if (!mapper.shouldSerializeMember(definedIn, aliasName)) { return; } - ExtendedHierarchicalStreamWriterHelper.startNode(writer, mapper.serializedMember(definedIn, aliasName), fieldType); + com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper.startNode(writer, mapper.serializedMember(definedIn, aliasName), fieldType); Class actualType = newObj.getClass(); @@ -288,7 +291,7 @@ public Object doUnmarshal(final Object result, final HierarchicalStreamReader re SingleValueConverter converter = mapper.getConverterFromAttribute(field.getDeclaringClass(),attrName,field.getType()); Class type = field.getType(); if (converter == null) { - converter = mapper.getConverterFromItemType(type); + converter = mapper.getConverterFromItemType(type); // TODO add fieldName & definedIn args } if (converter != null) { Object value = converter.fromString(reader.getAttribute(attrAlias)); @@ -492,7 +495,7 @@ private Class determineType(HierarchicalStreamReader reader, boolean validField, } private Object readResolve() { - serializationMethodInvoker = new SerializationMethodInvoker(); + serializationMethodInvoker = new SerializationMembers(); return this; } From 5f7c40f93fcd1025a21405f2ef9edc6e20a9a816 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 30 Sep 2020 17:12:56 -0400 Subject: [PATCH 20/23] Display OldDataMonitor errors from RobustReflectionConverter during functional tests --- core/src/main/java/hudson/diagnosis/OldDataMonitor.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java index 82dbffcf085a..5a5950e975c6 100644 --- a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java +++ b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java @@ -27,6 +27,7 @@ import com.thoughtworks.xstream.converters.UnmarshallingContext; import hudson.Extension; import hudson.ExtensionList; +import hudson.Main; import hudson.XmlFile; import hudson.model.AdministrativeMonitor; import hudson.model.Item; @@ -207,6 +208,9 @@ public static void report(Saveable obj, Collection errors) { if (e instanceof ReportException) { report(obj, ((ReportException)e).version); } else { + if (Main.isUnitTest) { + LOGGER.log(Level.INFO, "Trouble loading " + obj, e); + } if (++i > 1) buf.append(", "); buf.append(e.getClass().getSimpleName()).append(": ").append(e.getMessage()); } From f1fc2f6d5c901c8c58102d63a136c839101b06c9 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 8 Oct 2020 14:36:47 -0400 Subject: [PATCH 21/23] Stray digit in test name --- core/src/test/java/hudson/util/XStream2Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/hudson/util/XStream2Test.java b/core/src/test/java/hudson/util/XStream2Test.java index bada50a42cc9..e32f5d3e26e3 100644 --- a/core/src/test/java/hudson/util/XStream2Test.java +++ b/core/src/test/java/hudson/util/XStream2Test.java @@ -540,7 +540,7 @@ public void crashXstream() throws Exception { } @Test - public void annotations1() throws Exception { + public void annotations() throws Exception { assertEquals("not registered, so sorry", "", Jenkins.XSTREAM2.toXML(new C1())); assertEquals("manually registered", "", Jenkins.XSTREAM2.toXML(new C2())); assertEquals("manually processed", "", Jenkins.XSTREAM2.toXML(new C3())); From 7b0270106a946a32cca56d26b30f46fd69d24808 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 8 Oct 2020 15:07:53 -0400 Subject: [PATCH 22/23] Added XStream2AnnotationTest to clearly document incompatibility --- .../hudson/util/XStream2AnnotationTest.java | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 test/src/test/java/hudson/util/XStream2AnnotationTest.java diff --git a/test/src/test/java/hudson/util/XStream2AnnotationTest.java b/test/src/test/java/hudson/util/XStream2AnnotationTest.java new file mode 100644 index 000000000000..dc9f7bf25930 --- /dev/null +++ b/test/src/test/java/hudson/util/XStream2AnnotationTest.java @@ -0,0 +1,122 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package hudson.util; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import hudson.ExtensionList; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import jenkins.model.GlobalConfiguration; +import org.apache.commons.io.FileUtils; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import org.junit.Test; +import org.junit.Rule; +import org.jvnet.hudson.test.JenkinsSessionRule; +import org.jvnet.hudson.test.TestExtension; + +public class XStream2AnnotationTest { + + @Rule + public JenkinsSessionRule rr = new JenkinsSessionRule(); + + @Test + public void xStreamAlias() throws Throwable { + rr.then(r -> { + AnnotatedProcessed annotatedProcessed = AnnotatedProcessed.get(); + annotatedProcessed.x = 1; + annotatedProcessed.save(); + assertThat(annotatedProcessed.xml(), is("1")); + AnnotatedUnprocessed annotatedUnprocessed = AnnotatedUnprocessed.get(); + annotatedUnprocessed.x = 2; + annotatedUnprocessed.save(); + assertThat(annotatedUnprocessed.xml(), is("2")); + Programmatic programmatic = Programmatic.get(); + programmatic.x = 3; + programmatic.save(); + assertThat(programmatic.xml(), is("3")); + }); + rr.then(r -> { + assertThat(AnnotatedProcessed.get().x, is(1)); + assertThat(AnnotatedUnprocessed.get().x, is(2)); + assertThat(Programmatic.get().x, is(3)); + // Typical content saved by Jenkins session when annotation autodetection was still enabled: + AnnotatedUnprocessed.get().writeXml("4"); + }); + rr.then(r -> { + assertThat("CannotResolveClassException/IOException caught in Descriptor.load", AnnotatedUnprocessed.get().x, is(0)); + }); + } + + @XStreamAlias("myconf-annotated-processed") + @TestExtension("xStreamAlias") + public static class AnnotatedProcessed extends GlobalConfiguration { + static AnnotatedProcessed get() { + return ExtensionList.lookupSingleton(AnnotatedProcessed.class); + } + int x; + public AnnotatedProcessed() { + getConfigFile().getXStream().processAnnotations(AnnotatedProcessed.class); + load(); + } + String xml() throws IOException { + return getConfigFile().asString().replaceAll("\n *", "").replaceAll("<[?].+?[?]>", ""); + } + } + + @XStreamAlias("myconf-annotated-unprocessed") + @TestExtension("xStreamAlias") + public static class AnnotatedUnprocessed extends GlobalConfiguration { + static AnnotatedUnprocessed get() { + return ExtensionList.lookupSingleton(AnnotatedUnprocessed.class); + } + int x; + public AnnotatedUnprocessed() { + load(); + } + String xml() throws IOException { + return getConfigFile().asString().replaceAll("\n *", "").replaceAll("<[?].+?[?]>", ""); + } + void writeXml(String xml) throws IOException { + FileUtils.write(getConfigFile().getFile(), xml, StandardCharsets.UTF_8); + } + } + + @TestExtension("xStreamAlias") + public static class Programmatic extends GlobalConfiguration { + static Programmatic get() { + return ExtensionList.lookupSingleton(Programmatic.class); + } + int x; + public Programmatic() { + getConfigFile().getXStream().alias("myconf-programmatic", Programmatic.class); + load(); + } + String xml() throws IOException { + return getConfigFile().asString().replaceAll("\n *", "").replaceAll("<[?].+?[?]>", ""); + } + } + +} From 91e0fa6f5eb52431b9541315429e5b7a345ad84c Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 3 Nov 2020 18:07:53 -0500 Subject: [PATCH 23/23] Picking up release of https://github.com/jenkinsci/matrix-auth-plugin/pull/89 Co-authored-by: Basil Crow --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8ec180abcbd7..b4c49f68ee73 100755 --- a/pom.xml +++ b/pom.xml @@ -90,7 +90,7 @@ THE SOFTWARE. https://api.github.com jenkins-jira - 2.6.4-rc330.062fa8d38aa3 + 2.6.4 1.17 0.11 ${skipTests}