[ant] Formatter: avoid reflective access to determine console encoding
- for java 17+, there is public API to get the console encoding -> no problem - for older java versions, try to use system property sun.jnu.encoding if it exists - only then use the fall-backs with illegal reflective access to private fields/methods on java.io.Console - Also avoid using reflection utils from apache commons, instead use reflection directly. The illegal access warnings are then properly reported against our class net.sourceforge.pmd.ant.Formatter. Fixes #1860
This commit is contained in:
parent
74ec6f45ee
commit
3f697aff35
@ -11,6 +11,7 @@ import java.io.IOException;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
@ -21,8 +22,6 @@ import java.util.List;
|
|||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
|
||||||
import org.apache.commons.lang3.reflect.MethodUtils;
|
|
||||||
import org.apache.tools.ant.BuildException;
|
import org.apache.tools.ant.BuildException;
|
||||||
import org.apache.tools.ant.Project;
|
import org.apache.tools.ant.Project;
|
||||||
import org.apache.tools.ant.types.Parameter;
|
import org.apache.tools.ant.types.Parameter;
|
||||||
@ -200,10 +199,12 @@ public class Formatter {
|
|||||||
if (console != null) {
|
if (console != null) {
|
||||||
// Since Java 22, this returns a console even for redirected streams.
|
// Since Java 22, this returns a console even for redirected streams.
|
||||||
// In that case, we need to check Console.isTerminal()
|
// In that case, we need to check Console.isTerminal()
|
||||||
|
// https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/io/Console.html#isTerminal()
|
||||||
// See: JLine As The Default Console Provider (JDK-8308591)
|
// See: JLine As The Default Console Provider (JDK-8308591)
|
||||||
try {
|
try {
|
||||||
Boolean isTerminal = (Boolean) MethodUtils.invokeMethod(console, "isTerminal");
|
Method method = Console.class.getMethod("isTerminal");
|
||||||
if (!isTerminal) {
|
Object isTerminal = method.invoke(console);
|
||||||
|
if (isTerminal instanceof Boolean && !(Boolean) isTerminal) {
|
||||||
// stop here, we don't have an interactive console.
|
// stop here, we don't have an interactive console.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -211,39 +212,58 @@ public class Formatter {
|
|||||||
// fall-through - we use a Java Runtime < 22.
|
// fall-through - we use a Java Runtime < 22.
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// Maybe this is Java17+? Then there will be a public method charset()
|
||||||
Object res = FieldUtils.readDeclaredField(console, "cs", true);
|
|
||||||
if (res instanceof Charset) {
|
|
||||||
return ((Charset) res).name();
|
|
||||||
}
|
|
||||||
} catch (IllegalArgumentException | ReflectiveOperationException ignored) {
|
|
||||||
// fall-through
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maybe this is Java17+? Then there will be
|
|
||||||
// https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/Console.html#charset()
|
// https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/Console.html#charset()
|
||||||
// instead of the field "cs".
|
|
||||||
try {
|
try {
|
||||||
Method charsetMethod = Console.class.getDeclaredMethod("charset");
|
Method method = Console.class.getMethod("charset");
|
||||||
Charset charset = (Charset) charsetMethod.invoke(console);
|
Object charset = method.invoke(console);
|
||||||
return charset.name();
|
if (charset instanceof Charset) {
|
||||||
} catch (IllegalArgumentException | ReflectiveOperationException ignored) {
|
return ((Charset) charset).name();
|
||||||
|
}
|
||||||
|
} catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException ignored) {
|
||||||
// fall-through
|
// fall-through
|
||||||
}
|
}
|
||||||
return getNativeConsoleEncoding();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getNativeConsoleEncoding() {
|
{
|
||||||
|
// try to use the system property "sun.jnu.encoding", which is the platform encoding.
|
||||||
|
// this property is not specified and might not always be available, but it is for
|
||||||
|
// openjdk 11: https://github.com/openjdk/jdk11u/blob/cee8535a9d3de8558b4b5028d68e397e508bef71/src/java.base/share/native/libjava/System.c#L384
|
||||||
|
// if it exists, we use it - this avoids illegal reflective access below.
|
||||||
|
String jnuEncoding = System.getProperty("sun.jnu.encoding");
|
||||||
|
if (jnuEncoding != null) {
|
||||||
|
return jnuEncoding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the following parts are accessing private/protected fields via reflection
|
||||||
|
// this should work with Java 8 and 11. With Java 11, you'll see warnings abouts
|
||||||
|
// illegal reflective access, see #1860. However, the access still works.
|
||||||
|
|
||||||
|
// Fall-Back 1: private field "cs" in java.io.Console
|
||||||
try {
|
try {
|
||||||
Object res = MethodUtils.invokeStaticMethod(Console.class, "encoding");
|
Field field = Console.class.getDeclaredField("cs");
|
||||||
if (res instanceof String) {
|
field.setAccessible(true);
|
||||||
return (String) res;
|
Object csField = field.get(console);
|
||||||
|
if (csField instanceof Charset) {
|
||||||
|
return ((Charset) csField).name();
|
||||||
}
|
}
|
||||||
} catch (IllegalArgumentException | ReflectiveOperationException ignored) {
|
} catch (IllegalArgumentException | ReflectiveOperationException ignored) {
|
||||||
// fall-through
|
// fall-through
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fall-Back 2: private native method "encoding()" in java.io.Console
|
||||||
|
try {
|
||||||
|
Method method = Console.class.getDeclaredMethod("encoding");
|
||||||
|
method.setAccessible(true);
|
||||||
|
Object encoding = method.invoke(console);
|
||||||
|
if (encoding instanceof String) {
|
||||||
|
return (String) encoding;
|
||||||
|
}
|
||||||
|
} catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException ignored) {
|
||||||
|
// fall-through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// we couldn't determine the correct platform console encoding
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
package net.sourceforge.pmd.dist;
|
package net.sourceforge.pmd.dist;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.containsStringIgnoringCase;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -40,9 +42,9 @@ class AntIT extends AbstractBinaryDistributionTest {
|
|||||||
|
|
||||||
ExecutionResult result = runAnt(antBasepath, pmdHome, antTestProjectFolder);
|
ExecutionResult result = runAnt(antBasepath, pmdHome, antTestProjectFolder);
|
||||||
result.assertExitCode(0)
|
result.assertExitCode(0)
|
||||||
.assertStdOut(containsString("BUILD SUCCESSFUL"));
|
.assertStdOut(containsString("BUILD SUCCESSFUL"))
|
||||||
|
.assertStdOut(not(containsStringIgnoringCase("Illegal reflective access"))) // #1860
|
||||||
// the no package rule
|
// the no package rule
|
||||||
result.assertExitCode(0)
|
|
||||||
.assertStdOut(containsString("NoPackage"));
|
.assertStdOut(containsString("NoPackage"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user