From 2e97532ed1236cce9feb674ba78d1579c2858ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Thu, 10 Apr 2025 23:03:35 +0200 Subject: [PATCH 1/2] Use updates launchers (version 3-282bbc032bcd) --- harness/apisupport.harness/external/binaries-list | 2 +- ...launcher-external-binaries-3-282bbc032bcd-license.txt} | 2 +- harness/apisupport.harness/nbproject/project.properties | 4 ++-- nb/ide.launcher/external/binaries-list | 2 +- .../antsrc/org/netbeans/nbbuild/extlibs/ignored-binaries | 2 +- .../antsrc/org/netbeans/nbbuild/extlibs/ignored-overlaps | 8 ++++---- nbbuild/build.xml | 2 +- platform/o.n.bootstrap/external/binaries-list | 2 +- ...launcher-external-binaries-3-282bbc032bcd-license.txt} | 2 +- platform/o.n.bootstrap/nbproject/project.properties | 8 ++++---- 10 files changed, 17 insertions(+), 17 deletions(-) rename harness/apisupport.harness/external/{launcher-external-binaries-2-6c17cc6-license.txt => launcher-external-binaries-3-282bbc032bcd-license.txt} (99%) rename platform/o.n.bootstrap/external/{launcher-external-binaries-2-6c17cc6-license.txt => launcher-external-binaries-3-282bbc032bcd-license.txt} (99%) diff --git a/harness/apisupport.harness/external/binaries-list b/harness/apisupport.harness/external/binaries-list index d7a6c1e06c01..5cb8ec83fbfd 100644 --- a/harness/apisupport.harness/external/binaries-list +++ b/harness/apisupport.harness/external/binaries-list @@ -20,4 +20,4 @@ # D4EF66C1CC8A5B3C97E0CC7C210227AAEC1F1086 jsearch-2.0_05.jar A806D99716C5E9441BFD8B401176FDDEFC673022 bindex-2.2.jar 6FF4C411EE231AB8042C9047373F87FC64D0299D harness-launchers-8.2.zip -DB781CBFDA3565BC55742D90E0C111204764D85C https://archive.apache.org/dist/netbeans/native/netbeans-launchers/2-6c17cc6/launcher-external-binaries-2-6c17cc6.zip launcher-external-binaries-2-6c17cc6.zip +1D8777C057A3CFE0EE5A9E2BC7DF73BEC967E730 https://archive.apache.org/dist/netbeans/native/netbeans-launchers/3-282bbc032bcd/launcher-external-binaries-3-282bbc032bcd.zip launcher-external-binaries-3-282bbc032bcd.zip diff --git a/harness/apisupport.harness/external/launcher-external-binaries-2-6c17cc6-license.txt b/harness/apisupport.harness/external/launcher-external-binaries-3-282bbc032bcd-license.txt similarity index 99% rename from harness/apisupport.harness/external/launcher-external-binaries-2-6c17cc6-license.txt rename to harness/apisupport.harness/external/launcher-external-binaries-3-282bbc032bcd-license.txt index 68b8a38209f5..ab6af637ef4a 100644 --- a/harness/apisupport.harness/external/launcher-external-binaries-2-6c17cc6-license.txt +++ b/harness/apisupport.harness/external/launcher-external-binaries-3-282bbc032bcd-license.txt @@ -1,6 +1,6 @@ Name: NetBeans Application Launchers Description: Windows Launchers for NetBeans Applications -Version: 2-6c17cc6 +Version: 3-282bbc032bcd License: Apache-2.0 Source: https://netbeans.org/ Origin: NetBeans diff --git a/harness/apisupport.harness/nbproject/project.properties b/harness/apisupport.harness/nbproject/project.properties index ad293bfa0ab1..d78afca9b3c9 100644 --- a/harness/apisupport.harness/nbproject/project.properties +++ b/harness/apisupport.harness/nbproject/project.properties @@ -30,8 +30,8 @@ release.../../nbbuild/jdk.xml=jdk.xml # release.external/jsearch-2.0_05.jar=antlib/jsearch-2.0_05.jar release.external/bindex-2.2.jar=antlib/bindex-2.2.jar #release.external/jnlp-servlet.jar=jnlp/jnlp-servlet.jar -release.external/launcher-external-binaries-2-6c17cc6.zip!/app.exe=launchers/app.exe -release.external/launcher-external-binaries-2-6c17cc6.zip!/app64.exe=launchers/app64.exe +release.external/launcher-external-binaries-3-282bbc032bcd.zip!/app.exe=launchers/app.exe +release.external/launcher-external-binaries-3-282bbc032bcd.zip!/app64.exe=launchers/app64.exe release.external/harness-launchers-8.2.zip!/pre7_app.exe=launchers/pre7_app.exe release.external/harness-launchers-8.2.zip!/pre7_app_w.exe=launchers/pre7_app_w.exe nbm.executable.files=launchers/app.sh diff --git a/nb/ide.launcher/external/binaries-list b/nb/ide.launcher/external/binaries-list index dc9b3d1e1f84..ff5d89b69d30 100644 --- a/nb/ide.launcher/external/binaries-list +++ b/nb/ide.launcher/external/binaries-list @@ -14,4 +14,4 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -DB781CBFDA3565BC55742D90E0C111204764D85C https://archive.apache.org/dist/netbeans/native/netbeans-launchers/2-6c17cc6/launcher-external-binaries-2-6c17cc6.zip launcher-external-binaries-2-6c17cc6.zip +1D8777C057A3CFE0EE5A9E2BC7DF73BEC967E730 https://archive.apache.org/dist/netbeans/native/netbeans-launchers/3-282bbc032bcd/launcher-external-binaries-3-282bbc032bcd.zip launcher-external-binaries-3-282bbc032bcd.zip diff --git a/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-binaries b/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-binaries index 489ec5ffcd09..57fe99d88935 100644 --- a/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-binaries +++ b/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-binaries @@ -63,7 +63,7 @@ extide/gradle/netbeans-gradle-tooling/gradle/wrapper/gradle-wrapper.jar extide/gradle/release/modules/gradle/netbeans-gradle-tooling.jar # ide.launcher is a special semi-module, does not have nbproject/project.xml, so is not recognized: -nb/ide.launcher/external/launcher-external-binaries-2-6c17cc6.zip +nb/ide.launcher/external/launcher-external-binaries-3-282bbc032bcd.zip # webcommon/libs.oracle.jsparser builds a test-only binaries into build/webcommon. Not distributed. contrib/libs.oracle.jsparser/build/webcommon/** diff --git a/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-overlaps b/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-overlaps index 38b07df8a1b4..4cd75f7b5265 100644 --- a/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-overlaps +++ b/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-overlaps @@ -89,10 +89,10 @@ webcommon/javascript2.extdoc/external/testfiles-jsdoc-1.zip webcommon/javascript webcommon/javascript2.jsdoc/external/testfiles-jsdoc-1.zip webcommon/javascript2.sdoc/external/testfiles-jsdoc-1.zip # not part of the product: -harness/apisupport.harness/external/launcher-external-binaries-2-6c17cc6.zip platform/o.n.bootstrap/external/launcher-external-binaries-2-6c17cc6.zip -harness/apisupport.harness/external/launcher-external-binaries-2-6c17cc6.zip nb/ide.launcher/external/launcher-external-binaries-2-6c17cc6.zip -nb/ide.launcher/external/launcher-external-binaries-2-6c17cc6.zip platform/o.n.bootstrap/external/launcher-external-binaries-2-6c17cc6.zip -nb/ide.launcher/external/launcher-external-binaries-2-6c17cc6.zip harness/apisupport.harness/external/launcher-external-binaries-2-6c17cc6.zip +harness/apisupport.harness/external/launcher-external-binaries-3-282bbc032bcd.zip platform/o.n.bootstrap/external/launcher-external-binaries-3-282bbc032bcd.zip +harness/apisupport.harness/external/launcher-external-binaries-3-282bbc032bcd.zip nb/ide.launcher/external/launcher-external-binaries-3-282bbc032bcd.zip +nb/ide.launcher/external/launcher-external-binaries-3-282bbc032bcd.zip platform/o.n.bootstrap/external/launcher-external-binaries-3-282bbc032bcd.zip +nb/ide.launcher/external/launcher-external-binaries-3-282bbc032bcd.zip harness/apisupport.harness/external/launcher-external-binaries-3-282bbc032bcd.zip # only one is part of the product: java/libs.javacapi/external/nb-javac-jdk-24-ga-api.jar java/libs.nbjavacapi/external/nb-javac-jdk-24-ga-api.jar diff --git a/nbbuild/build.xml b/nbbuild/build.xml index 449c86d5b8c7..43589655979b 100644 --- a/nbbuild/build.xml +++ b/nbbuild/build.xml @@ -467,7 +467,7 @@ metabuild.hash=${metabuild.hash} - + diff --git a/platform/o.n.bootstrap/external/binaries-list b/platform/o.n.bootstrap/external/binaries-list index dc9b3d1e1f84..ff5d89b69d30 100644 --- a/platform/o.n.bootstrap/external/binaries-list +++ b/platform/o.n.bootstrap/external/binaries-list @@ -14,4 +14,4 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -DB781CBFDA3565BC55742D90E0C111204764D85C https://archive.apache.org/dist/netbeans/native/netbeans-launchers/2-6c17cc6/launcher-external-binaries-2-6c17cc6.zip launcher-external-binaries-2-6c17cc6.zip +1D8777C057A3CFE0EE5A9E2BC7DF73BEC967E730 https://archive.apache.org/dist/netbeans/native/netbeans-launchers/3-282bbc032bcd/launcher-external-binaries-3-282bbc032bcd.zip launcher-external-binaries-3-282bbc032bcd.zip diff --git a/platform/o.n.bootstrap/external/launcher-external-binaries-2-6c17cc6-license.txt b/platform/o.n.bootstrap/external/launcher-external-binaries-3-282bbc032bcd-license.txt similarity index 99% rename from platform/o.n.bootstrap/external/launcher-external-binaries-2-6c17cc6-license.txt rename to platform/o.n.bootstrap/external/launcher-external-binaries-3-282bbc032bcd-license.txt index 68b8a38209f5..ab6af637ef4a 100644 --- a/platform/o.n.bootstrap/external/launcher-external-binaries-2-6c17cc6-license.txt +++ b/platform/o.n.bootstrap/external/launcher-external-binaries-3-282bbc032bcd-license.txt @@ -1,6 +1,6 @@ Name: NetBeans Application Launchers Description: Windows Launchers for NetBeans Applications -Version: 2-6c17cc6 +Version: 3-282bbc032bcd License: Apache-2.0 Source: https://netbeans.org/ Origin: NetBeans diff --git a/platform/o.n.bootstrap/nbproject/project.properties b/platform/o.n.bootstrap/nbproject/project.properties index 07ffd35a1e93..7d85c8c0aee6 100644 --- a/platform/o.n.bootstrap/nbproject/project.properties +++ b/platform/o.n.bootstrap/nbproject/project.properties @@ -20,10 +20,10 @@ javac.release=11 module.jar.dir=lib module.jar.basename=boot.jar release.launcher/unix/nbexec=lib/nbexec -release.external/launcher-external-binaries-2-6c17cc6.zip!/nbexec.exe=lib/nbexec.exe -release.external/launcher-external-binaries-2-6c17cc6.zip!/nbexec64.exe=lib/nbexec64.exe -release.external/launcher-external-binaries-2-6c17cc6.zip!/nbexec.dll=lib/nbexec.dll -release.external/launcher-external-binaries-2-6c17cc6.zip!/nbexec64.dll=lib/nbexec64.dll +release.external/launcher-external-binaries-3-282bbc032bcd.zip!/nbexec.exe=lib/nbexec.exe +release.external/launcher-external-binaries-3-282bbc032bcd.zip!/nbexec64.exe=lib/nbexec64.exe +release.external/launcher-external-binaries-3-282bbc032bcd.zip!/nbexec.dll=lib/nbexec.dll +release.external/launcher-external-binaries-3-282bbc032bcd.zip!/nbexec64.dll=lib/nbexec64.dll nbm.executable.files=lib/nbexec javadoc.arch=${basedir}/arch.xml From 9b6f8a0a6432154c0257d9fa247302adf910092f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Tue, 8 Apr 2025 19:41:32 +0200 Subject: [PATCH 2/2] Add javaagent to patch WClipboard It was observed, that clipboard on windows sometimes reacts flaky. Investigation hints that missing synchronization in the AWT implementation/integration. Testers reported, that the changes implemented here: - the change handling from the system clipboard is dispatched into the event loop and - the change handling is modified to be called synchronized It is expected, that fixing this upstream in the JDK will take longer and so runtime patching must be considered. Runtime patching in this case is accomplished using the javaagent mechanism. The IDE launcher is modified to load an agent, that will intercept loading classes and modify WClipboard while it is being loaded. The transformer is written, so that it will only transform the target class and will act transparantly for all other classes. --- ide/o.n.agent/build.xml | 65 +++++ ide/o.n.agent/manifest.mf | 5 + ide/o.n.agent/nbproject/project.properties | 29 +++ ide/o.n.agent/nbproject/project.xml | 45 ++++ ide/o.n.agent/src-agent/META-INF/MANIFEST.MF | 1 + ide/o.n.agent/src-agent/META-INF/NOTICE | 44 ++++ .../org/netbeans/agent/NetBeansAgent.java | 49 ++++ .../netbeans/agent/WClipboardTransformer.java | 239 ++++++++++++++++++ .../src/org/netbeans/agent/Bundle.properties | 18 ++ nb/ide.launcher/netbeans.conf | 2 +- nbbuild/cluster.properties | 1 + 11 files changed, 497 insertions(+), 1 deletion(-) create mode 100644 ide/o.n.agent/build.xml create mode 100644 ide/o.n.agent/manifest.mf create mode 100644 ide/o.n.agent/nbproject/project.properties create mode 100644 ide/o.n.agent/nbproject/project.xml create mode 100644 ide/o.n.agent/src-agent/META-INF/MANIFEST.MF create mode 100644 ide/o.n.agent/src-agent/META-INF/NOTICE create mode 100644 ide/o.n.agent/src-agent/org/netbeans/agent/NetBeansAgent.java create mode 100644 ide/o.n.agent/src-agent/org/netbeans/agent/WClipboardTransformer.java create mode 100644 ide/o.n.agent/src/org/netbeans/agent/Bundle.properties diff --git a/ide/o.n.agent/build.xml b/ide/o.n.agent/build.xml new file mode 100644 index 000000000000..c6608d4765a2 --- /dev/null +++ b/ide/o.n.agent/build.xml @@ -0,0 +1,65 @@ + + + + Builds, tests, and runs the project org.netbeans.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ide/o.n.agent/manifest.mf b/ide/o.n.agent/manifest.mf new file mode 100644 index 000000000000..6785c26a1679 --- /dev/null +++ b/ide/o.n.agent/manifest.mf @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +AutoUpdate-Show-In-Client: true +OpenIDE-Module: org.netbeans.agent +OpenIDE-Module-Localizing-Bundle: org/netbeans/agent/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 diff --git a/ide/o.n.agent/nbproject/project.properties b/ide/o.n.agent/nbproject/project.properties new file mode 100644 index 000000000000..02c4449647a3 --- /dev/null +++ b/ide/o.n.agent/nbproject/project.properties @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +javac.release=17 +javac.compilerargs=-Xlint -Xlint:-serial + +agent.src.dir=src-agent +agent.build.classes.dir=${build.dir}/agent/classes/ +agent.jar=${cluster}/netbeans-javaagent.jar +agent.asm.lib.1=../../platform/libs.asm/external/asm-commons-9.8.jar +agent.asm.lib.2=../../platform/libs.asm/external/asm-tree-9.8.jar +agent.asm.lib.3=../../platform/libs.asm/external/asm-9.8.jar +agent.cp=${agent.asm.lib.1}:${agent.asm.lib.2}:${agent.asm.lib.3} + +extra.module.files=${cluster}/netbeans-javaagent.jar \ No newline at end of file diff --git a/ide/o.n.agent/nbproject/project.xml b/ide/o.n.agent/nbproject/project.xml new file mode 100644 index 000000000000..82db691fce24 --- /dev/null +++ b/ide/o.n.agent/nbproject/project.xml @@ -0,0 +1,45 @@ + + + + org.netbeans.modules.apisupport.project + + + org.netbeans.agent + + + org.netbeans.libs.asm + + + + 5.31 + + + + + + + ${agent.src.dir} + ${agent.cp} + ${agent.build.classes.dir} + ${cluster}/netbeans-javaagent.jar + + + + diff --git a/ide/o.n.agent/src-agent/META-INF/MANIFEST.MF b/ide/o.n.agent/src-agent/META-INF/MANIFEST.MF new file mode 100644 index 000000000000..809c6cb4eed9 --- /dev/null +++ b/ide/o.n.agent/src-agent/META-INF/MANIFEST.MF @@ -0,0 +1 @@ +Premain-Class: org.netbeans.agent.NetBeansAgent diff --git a/ide/o.n.agent/src-agent/META-INF/NOTICE b/ide/o.n.agent/src-agent/META-INF/NOTICE new file mode 100644 index 000000000000..ec9f165c9380 --- /dev/null +++ b/ide/o.n.agent/src-agent/META-INF/NOTICE @@ -0,0 +1,44 @@ +Apache NetBeans +Copyright 2017-2025 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +The code is based on NetBeans, that has been kindly donated to the Apache +Software Foundation by Oracle. + +The code was Copyright 1997-2016 Oracle and/or its affiliates. The Initial +Developer of the Original Software was Sun Microsystems, Inc. Portions +Copyright 1997-2006 Sun Microsystems, Inc. + +The agent jar embeds a copy of ASM for bytecode manipulation: + +******************************************************************************* +* ASM: a very small and fast Java bytecode manipulation framework +* Copyright (c) 2000-2011 INRIA, France Telecom +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* 3. Neither the name of the copyright holders nor the names of its +* contributors may be used to endorse or promote products derived from +* this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +* THE POSSIBILITY OF SUCH DAMAGE. +******************************************************************************* diff --git a/ide/o.n.agent/src-agent/org/netbeans/agent/NetBeansAgent.java b/ide/o.n.agent/src-agent/org/netbeans/agent/NetBeansAgent.java new file mode 100644 index 000000000000..ffa4175a0b19 --- /dev/null +++ b/ide/o.n.agent/src-agent/org/netbeans/agent/NetBeansAgent.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.agent; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of a java agent. The JAR containing this class must be + * referenced by the JVM with the agent infrastructure. At time of writing this + * means, that the JAR must be passed via the `-javaagent:JARPATH` construct. + * JARPATH in this case must be the absolute path to the netbeans-javaagent.jar. + * + * The `premain` method in this class is then invoked by the JVM _before_ the + * applications main method is invoked. + * + * This should be used as a last resort. For classes loaded throught the + * module system that faciliy should be prefered. + */ +public class NetBeansAgent { + + public static void premain(String arg, Instrumentation instrumentation) { + List transformer = new ArrayList<>(2); + if ((!Boolean.getBoolean(WClipboardTransformer.DEBUG_DISABLE_TRANSFORMER)) + && System.getProperty("os.name").toLowerCase().contains("windows")) { + transformer.add(new WClipboardTransformer(instrumentation)); + } + transformer.forEach(cft -> instrumentation.addTransformer(cft, false)); + } + +} diff --git a/ide/o.n.agent/src-agent/org/netbeans/agent/WClipboardTransformer.java b/ide/o.n.agent/src-agent/org/netbeans/agent/WClipboardTransformer.java new file mode 100644 index 000000000000..d97e174e9e42 --- /dev/null +++ b/ide/o.n.agent/src-agent/org/netbeans/agent/WClipboardTransformer.java @@ -0,0 +1,239 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.agent; + +import java.io.IOException; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.lang.instrument.Instrumentation; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.security.ProtectionDomain; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.GeneratorAdapter; +import org.objectweb.asm.commons.Method; + +import static org.objectweb.asm.Opcodes.H_INVOKESTATIC; +import static org.objectweb.asm.Opcodes.H_INVOKEVIRTUAL; + +/** + * Transform the sun.awt.windows.WClipboard class to fix a copy-n-paste problem + * observed on windows. It was observed, that sometime copying from NetBeans to + * other windows applications fails. + * + * Discussion can be found here: https://github.com/apache/netbeans/discussions/7051 + * + * A test was provided to reproduce. The test uses java.awt.Robot to do this: + * + * 1. Start notepad.exe and enter "sometext" into notepad window + * 2. Copy current line to clipboard + * 3. Input a newline into the notepad window + * 4. Switch to NetBeans + * 5. Past text into current NetBeans editor window + * 6. Append loop index to pasted line + * 7. Copy the current line to clipboard + * 8. Input a newline into the NetBeans editor + * 9. Switch to notepad.exe + * 10. Paste text into notepad.exe + * 11. Repeat from step 2 + * + * The transformer can be debugged: + * + * - The system property "org.netbeans.agent.WClipboardTransformer.disable" + * takes a boolean to disable the transformer. I.e. running NetBeans as + * + * NBBASEPATH/bin/netbeans64.exe -J-Dorg.netbeans.agent.WClipboardTransformer.disable=true + * + * will run without this. + * + * - The system property "org.netbeans.agent.WClipboardTransformer.dumpPath" + * takes a string. The string is expected to be a path and the transformed + * class data will be dumped at that location. It is expected, that a path + * including the filename is specified. + * + * - The system property "org.netbeans.agent.WClipboardTransformer.debug" + * can be set to true to get info about the transformation process + */ +public class WClipboardTransformer implements ClassFileTransformer { + + public static final String DEBUG_DISABLE_TRANSFORMER = "org.netbeans.agent.WClipboardTransformer.disable"; + + private static final Type TYPE_JAVA_AWT_AWT_EVENT = Type.getType("Ljava/awt/AWTEvent;"); + private static final Type TYPE_JAVA_AWT_EVENT_INVOCATION_EVENT = Type.getType("Ljava/awt/event/InvocationEvent;"); + private static final Type TYPE_JAVA_AWT_TOOLKIT = Type.getType("Ljava/awt/Toolkit;"); + private static final Type TYPE_JAVA_LANG_EXCEPTION = Type.getType("Ljava/lang/Exception;"); + private static final Type TYPE_JAVA_LANG_THROWABLE = Type.getType("Ljava/lang/Throwable;"); + private static final Type TYPE_JAVA_LANG_OBJECT = Type.getType("Ljava/lang/Object;"); + private static final Type TYPE_JAVA_LANG_RUNNABLE = Type.getType("Ljava/lang/Runnable;"); + private static final Type TYPE_JAVA_LANG_STRING = Type.getType("Ljava/lang/String;"); + private static final Type TYPE_JAVA_LANG_SYSTEM = Type.getType("Ljava/lang/System;"); + private static final Type TYPE_JAVA_LANG_SYSTEM_LOGGER = Type.getType("Ljava/lang/System$Logger;"); + private static final Type TYPE_JAVA_LANG_SYSTEM_LOGGER_LEVEL = Type.getType("Ljava/lang/System$Logger$Level;"); + private static final Type TYPE_SUN_AWT_APP_CONTEXT = Type.getType("Lsun/awt/AppContext;"); + private static final Type TYPE_SUN_AWT_SUN_TOOLKIT = Type.getType("Lsun/awt/SunToolkit;"); + private static final Type TYPE_SUN_AWT_WINDOWS_WCLIPBOARD = Type.getType("Lsun/awt/windows/WClipboard;"); + + private static final String DEBUG_DUMP_TRANSFORMED_CLASS = System.getProperty("org.netbeans.agent.WClipboardTransformer.dumpPath"); + private static final boolean DEBUG_TRANSFORMER = Boolean.getBoolean("org.netbeans.agent.WClipboardTransformer.debug"); + private static final String WCLIPBOARD_LOGGER_NAME = "sun.awt.windows.WClipboard"; + + private final Instrumentation instrumentation; + + public WClipboardTransformer(Instrumentation instrumentation) { + this.instrumentation = instrumentation; + } + + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { + // Only transform WClipboard class - ignore all other classes + if ("sun/awt/windows/WClipboard".equals(className)) { + logMsg("%s: Transforming %s", WClipboardTransformer.class.getName(), className); + try { + ClassReader cr = new ClassReader(classfileBuffer); + ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) { + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + // The original handleContentsChanged method is retained under the name + // handleContentsChanged0. Apart from the rename, the method is also + // marked as being synchronized. + if ("handleContentsChanged".equals(name)) { + logMsg("%s: Renaming handleContentsChanged -> handleContentsChanged0 and marking it synchronized", WClipboardTransformer.class.getName()); + return super.visitMethod(access | Opcodes.ACC_SYNCHRONIZED, "handleContentsChanged0", descriptor, signature, exceptions); + } else { + return super.visitMethod(access, name, descriptor, signature, exceptions); + } + } + + @Override + public void visitEnd() { + logMsg("%s: Creating handleContentsChanged Wrapper", WClipboardTransformer.class.getName()); + // Implement replacement handleContentsChanged method, + // that essentially dispatches the change handling into + // the event loop. + MethodVisitor mv = super.visitMethod(Opcodes.ACC_PRIVATE, "handleContentsChanged", "()V", null, new String[]{}); + GeneratorAdapter ga = new GeneratorAdapter(mv, Opcodes.ACC_PRIVATE, "handleContentsChanged", "()V"); + Label start = ga.mark(); + Label end = ga.newLabel(); + generateLoggerInvocation(ga, WCLIPBOARD_LOGGER_NAME, "DEBUG", "handleContentsChanged entered"); + ga.invokeStatic(TYPE_SUN_AWT_APP_CONTEXT, new Method("getAppContext", TYPE_SUN_AWT_APP_CONTEXT, new Type[0])); + ga.storeLocal(1, TYPE_SUN_AWT_APP_CONTEXT); + ga.loadLocal(1); + Label afterAppContextCheck = ga.newLabel(); + ga.ifNonNull(afterAppContextCheck); + // Fallback path, if no AppContext is available, directly call the original method + generateLoggerInvocation(ga, WCLIPBOARD_LOGGER_NAME, "DEBUG", "Entering fallback path (no AppContext found)"); + ga.loadThis(); + ga.invokeVirtual(TYPE_SUN_AWT_WINDOWS_WCLIPBOARD, new Method("handleContentsChanged0", Type.VOID_TYPE, new Type[0])); + ga.returnValue(); + ga.mark(afterAppContextCheck); + // The new/fix path uses an InvocationEvent to dispatch update handling + // into the event loop. The InvocationEvent is created with a lambda, that + // essentially calls handleContentsChanged0 + generateLoggerInvocation(ga, WCLIPBOARD_LOGGER_NAME, "DEBUG", "Dispatching update to event loop"); + ga.loadLocal(1); + ga.newInstance(TYPE_JAVA_AWT_EVENT_INVOCATION_EVENT); + ga.dup(); + ga.invokeStatic(TYPE_JAVA_AWT_TOOLKIT, new Method("getDefaultToolkit", TYPE_JAVA_AWT_TOOLKIT, new Type[0])); + ga.loadThis(); + ga.invokeDynamic( + "run", + "(Lsun/awt/windows/WClipboard;)Ljava/lang/Runnable;", + new Handle( + H_INVOKESTATIC, + "java/lang/invoke/LambdaMetafactory", + "metafactory", + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", + false + ), + Type.getMethodType(Type.VOID_TYPE), + new Handle( + H_INVOKEVIRTUAL, + "sun/awt/windows/WClipboard", + "handleContentsChanged0", + "()V", + false + ), + Type.getMethodType(Type.VOID_TYPE)); + ga.invokeConstructor(TYPE_JAVA_AWT_EVENT_INVOCATION_EVENT, new Method("", Type.VOID_TYPE, new Type[]{TYPE_JAVA_LANG_OBJECT, TYPE_JAVA_LANG_RUNNABLE})); + ga.invokeStatic(TYPE_SUN_AWT_SUN_TOOLKIT, new Method("postEvent", Type.VOID_TYPE, new Type[]{TYPE_SUN_AWT_APP_CONTEXT, TYPE_JAVA_AWT_AWT_EVENT})); + ga.returnValue(); + ga.mark(end); + // Catch Exceptions and log them + ga.catchException(start, end, TYPE_JAVA_LANG_EXCEPTION); + ga.push(WCLIPBOARD_LOGGER_NAME ); + ga.invokeStatic(TYPE_JAVA_LANG_SYSTEM, new Method("getLogger", TYPE_JAVA_LANG_SYSTEM_LOGGER, new Type[]{TYPE_JAVA_LANG_STRING})); + ga.swap(); + ga.getStatic(TYPE_JAVA_LANG_SYSTEM_LOGGER_LEVEL, "WARNING", TYPE_JAVA_LANG_SYSTEM_LOGGER_LEVEL); + ga.swap(); + ga.push("Exception in handleContentsChanged invocation"); + ga.swap(); + ga.invokeInterface(TYPE_JAVA_LANG_SYSTEM_LOGGER, new Method("log", Type.VOID_TYPE, new Type[]{TYPE_JAVA_LANG_SYSTEM_LOGGER_LEVEL, TYPE_JAVA_LANG_STRING, TYPE_JAVA_LANG_THROWABLE})); + ga.returnValue(); + ga.endMethod(); + logMsg("%s: Creating handleContentsChanged Wrapper done", WClipboardTransformer.class.getName()); + super.visitEnd(); + } + + private void generateLoggerInvocation(GeneratorAdapter ga, String loggerName, String levelName, String message) { + ga.push(loggerName); + ga.invokeStatic(TYPE_JAVA_LANG_SYSTEM, new Method("getLogger", TYPE_JAVA_LANG_SYSTEM_LOGGER, new Type[]{TYPE_JAVA_LANG_STRING})); + ga.getStatic(TYPE_JAVA_LANG_SYSTEM_LOGGER_LEVEL, levelName, TYPE_JAVA_LANG_SYSTEM_LOGGER_LEVEL); + ga.push(message); // STRING + ga.invokeInterface(TYPE_JAVA_LANG_SYSTEM_LOGGER, new Method("log", Type.VOID_TYPE, new Type[]{TYPE_JAVA_LANG_SYSTEM_LOGGER_LEVEL, TYPE_JAVA_LANG_STRING})); + } + + }; + cr.accept(cv, 0); + byte[] result = cw.toByteArray(); + if (DEBUG_DUMP_TRANSFORMED_CLASS != null && !DEBUG_DUMP_TRANSFORMED_CLASS.isBlank()) { + Files.write(Path.of(DEBUG_DUMP_TRANSFORMED_CLASS), result, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } + logMsg("%s: Transforming %s done", WClipboardTransformer.class.getName(), className); + return result; + } catch (IOException | RuntimeException ex) { + logErr("%s: Transforming %s failed", WClipboardTransformer.class.getName(), className); + ex.printStackTrace(System.err); + } finally { + instrumentation.removeTransformer(this); + } + } + return null; + } + + // Don't use JUL as this might interfer with initialization of the logging + // system in the application/tests + private void logMsg(String msg, Object... params) { + if(DEBUG_TRANSFORMER) { + System.err.printf(msg + "%n", params); + } + } + + private void logErr(String msg, Object... params) { + System.err.printf(msg + "%n", params); + } +} diff --git a/ide/o.n.agent/src/org/netbeans/agent/Bundle.properties b/ide/o.n.agent/src/org/netbeans/agent/Bundle.properties new file mode 100644 index 000000000000..e07056490b8b --- /dev/null +++ b/ide/o.n.agent/src/org/netbeans/agent/Bundle.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +OpenIDE-Module-Name=NetBeans Agent +OpenIDE-Module-Display-Category=Base IDE \ No newline at end of file diff --git a/nb/ide.launcher/netbeans.conf b/nb/ide.launcher/netbeans.conf index c9c6cb166449..8690aaf7bf66 100644 --- a/nb/ide.launcher/netbeans.conf +++ b/nb/ide.launcher/netbeans.conf @@ -66,7 +66,7 @@ netbeans_default_cachedir="${DEFAULT_CACHEDIR_ROOT}/@@metabuild.RawVersion@@" # The automatically selected value can be overridden by specifying -J-Xmx # here or on the command line. # -netbeans_default_options="-J-XX:+UseStringDeduplication -J-Xss2m @@metabuild.logcli@@ -J-Djava.lang.Runtime.level=FINE -J-Dapple.laf.useScreenMenuBar=true -J-Dapple.awt.application.appearance=system -J-Dsun.java2d.noddraw=true -J-Dsun.java2d.dpiaware=true -J-Dplugin.manager.check.updates=false -J-Dnetbeans.extbrowser.manual_chrome_plugin_install=yes @@metabuild.jms-flags@@ -J-XX:+IgnoreUnrecognizedVMOptions" +netbeans_default_options="-J-XX:+UseStringDeduplication -J-Xss2m @@metabuild.logcli@@ -J-Djava.lang.Runtime.level=FINE -J-Dapple.laf.useScreenMenuBar=true -J-Dapple.awt.application.appearance=system -J-Dsun.java2d.noddraw=true -J-Dsun.java2d.dpiaware=true -J-Dplugin.manager.check.updates=false -J-Dnetbeans.extbrowser.manual_chrome_plugin_install=yes @@metabuild.jms-flags@@ -J-XX:+IgnoreUnrecognizedVMOptions -J-javaagent:\"${BASEDIR}/ide/netbeans-javaagent.jar\"" # Default location of JDK: # (set by installer or commented out if launcher should decide) diff --git a/nbbuild/cluster.properties b/nbbuild/cluster.properties index e7d1b89fb06b..928d0cc0ed71 100644 --- a/nbbuild/cluster.properties +++ b/nbbuild/cluster.properties @@ -444,6 +444,7 @@ nb.cluster.ide=\ o.eclipse.mylyn.wikitext.core,\ o.eclipse.mylyn.wikitext.markdown.core,\ o.eclipse.mylyn.wikitext.textile.core,\ + o.n.agent,\ o.n.swing.dirchooser,\ o.openidex.util,\ options.editor,\