Skip to content

Commit f277b39

Browse files
committed
8051959: Add thread and timestamp options to java.security.debug system property
Reviewed-by: mbaesken Backport-of: 3b582dff849f1c25336e2efc415eb121f8b12189
1 parent b99923c commit f277b39

File tree

2 files changed

+237
-20
lines changed

2 files changed

+237
-20
lines changed

src/java.base/share/classes/sun/security/util/Debug.java

Lines changed: 99 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727

2828
import java.io.PrintStream;
2929
import java.math.BigInteger;
30+
import java.time.Instant;
31+
import java.time.ZoneId;
32+
import java.time.format.DateTimeFormatter;
3033
import java.util.HexFormat;
3134
import java.util.regex.Pattern;
3235
import java.util.regex.Matcher;
@@ -41,8 +44,14 @@
4144
public class Debug {
4245

4346
private String prefix;
47+
private boolean printDateTime;
48+
private boolean printThreadDetails;
4449

4550
private static String args;
51+
private static boolean threadInfoAll;
52+
private static boolean timeStampInfoAll;
53+
private static final String TIMESTAMP_OPTION = "+timestamp";
54+
private static final String THREAD_OPTION = "+thread";
4655

4756
static {
4857
args = GetPropertyAction.privilegedGetProperty("java.security.debug");
@@ -61,12 +70,21 @@ public class Debug {
6170
args = marshal(args);
6271
if (args.equals("help")) {
6372
Help();
73+
} else if (args.contains("all")) {
74+
// "all" option has special handling for decorator options
75+
// If the thread or timestamp decorator option is detected
76+
// with the "all" option, then it impacts decorator options
77+
// for other categories
78+
int beginIndex = args.lastIndexOf("all") + "all".length();
79+
int commaIndex = args.indexOf(',', beginIndex);
80+
if (commaIndex == -1) commaIndex = args.length();
81+
threadInfoAll = args.substring(beginIndex, commaIndex).contains(THREAD_OPTION);
82+
timeStampInfoAll = args.substring(beginIndex, commaIndex).contains(TIMESTAMP_OPTION);
6483
}
6584
}
6685
}
6786

68-
public static void Help()
69-
{
87+
public static void Help() {
7088
System.err.println();
7189
System.err.println("all turn on all debugging");
7290
System.err.println("access print all checkPermission results");
@@ -92,6 +110,11 @@ public static void Help()
92110
System.err.println("securerandom SecureRandom");
93111
System.err.println("ts timestamping");
94112
System.err.println();
113+
System.err.println("+timestamp can be appended to any of above options to print");
114+
System.err.println(" a timestamp for that debug option");
115+
System.err.println("+thread can be appended to any of above options to print");
116+
System.err.println(" thread and caller information for that debug option");
117+
System.err.println();
95118
System.err.println("The following can be used with access:");
96119
System.err.println();
97120
System.err.println("stack include stack trace");
@@ -132,32 +155,65 @@ public static void Help()
132155
* option is set. Set the prefix to be the same as option.
133156
*/
134157

135-
public static Debug getInstance(String option)
136-
{
158+
public static Debug getInstance(String option) {
137159
return getInstance(option, option);
138160
}
139161

140162
/**
141163
* Get a Debug object corresponding to whether or not the given
142164
* option is set. Set the prefix to be prefix.
143165
*/
144-
public static Debug getInstance(String option, String prefix)
145-
{
166+
public static Debug getInstance(String option, String prefix) {
146167
if (isOn(option)) {
147168
Debug d = new Debug();
148169
d.prefix = prefix;
170+
d.configureExtras(option);
149171
return d;
150172
} else {
151173
return null;
152174
}
153175
}
154176

177+
private static String formatCaller() {
178+
return StackWalker.getInstance().walk(s ->
179+
s.dropWhile(f ->
180+
f.getClassName().startsWith("sun.security.util.Debug"))
181+
.map(f -> f.getFileName() + ":" + f.getLineNumber())
182+
.findFirst().orElse("unknown caller"));
183+
}
184+
185+
// parse an option string to determine if extra details,
186+
// like thread and timestamp, should be printed
187+
private void configureExtras(String option) {
188+
// treat "all" as special case, only used for java.security.debug property
189+
this.printDateTime = timeStampInfoAll;
190+
this.printThreadDetails = threadInfoAll;
191+
192+
if (printDateTime && printThreadDetails) {
193+
// nothing left to configure
194+
return;
195+
}
196+
197+
// args is converted to lower case for the most part via marshal method
198+
int optionIndex = args.lastIndexOf(option);
199+
if (optionIndex == -1) {
200+
// option not in args list. Only here since "all" was present
201+
// in debug property argument. "all" option already parsed
202+
return;
203+
}
204+
int beginIndex = optionIndex + option.length();
205+
int commaIndex = args.indexOf(',', beginIndex);
206+
if (commaIndex == -1) commaIndex = args.length();
207+
String subOpt = args.substring(beginIndex, commaIndex);
208+
printDateTime = printDateTime || subOpt.contains(TIMESTAMP_OPTION);
209+
printThreadDetails = printThreadDetails || subOpt.contains(THREAD_OPTION);
210+
}
211+
155212
/**
156213
* True if the system property "security.debug" contains the
157214
* string "option".
158215
*/
159-
public static boolean isOn(String option)
160-
{
216+
public static boolean isOn(String option) {
161217
if (args == null)
162218
return false;
163219
else {
@@ -180,37 +236,53 @@ public static boolean isVerbose() {
180236
* created from the call to getInstance.
181237
*/
182238

183-
public void println(String message)
184-
{
185-
System.err.println(prefix + ": "+message);
239+
public void println(String message) {
240+
System.err.println(prefix + extraInfo() + ": " + message);
186241
}
187242

188243
/**
189244
* print a message to stderr that is prefixed with the prefix
190245
* created from the call to getInstance and obj.
191246
*/
192-
public void println(Object obj, String message)
193-
{
194-
System.err.println(prefix + " [" + obj.getClass().getSimpleName() +
247+
public void println(Object obj, String message) {
248+
System.err.println(prefix + extraInfo() + " [" + obj.getClass().getSimpleName() +
195249
"@" + System.identityHashCode(obj) + "]: "+message);
196250
}
197251

198252
/**
199253
* print a blank line to stderr that is prefixed with the prefix.
200254
*/
201255

202-
public void println()
203-
{
204-
System.err.println(prefix + ":");
256+
public void println() {
257+
System.err.println(prefix + extraInfo() + ":");
205258
}
206259

207260
/**
208261
* print a message to stderr that is prefixed with the prefix.
209262
*/
210263

211-
public static void println(String prefix, String message)
212-
{
213-
System.err.println(prefix + ": "+message);
264+
public void println(String prefix, String message) {
265+
System.err.println(prefix + extraInfo() + ": " + message);
266+
}
267+
268+
/**
269+
* If thread debug option enabled, include information containing
270+
* hex value of threadId and the current thread name
271+
* If timestamp debug option enabled, include timestamp string
272+
* @return extra info if debug option enabled.
273+
*/
274+
private String extraInfo() {
275+
String retString = "";
276+
if (printThreadDetails) {
277+
retString = "0x" + Long.toHexString(
278+
Thread.currentThread().getId()).toUpperCase(Locale.ROOT) +
279+
"|" + Thread.currentThread().getName() + "|" + formatCaller();
280+
}
281+
if (printDateTime) {
282+
retString += (retString.isEmpty() ? "" : "|")
283+
+ FormatHolder.DATE_TIME_FORMATTER.format(Instant.now());
284+
}
285+
return retString.isEmpty() ? "" : "[" + retString + "]";
214286
}
215287

216288
/**
@@ -326,4 +398,11 @@ public static String toString(byte[] b) {
326398
return HexFormat.ofDelimiter(":").formatHex(b);
327399
}
328400

401+
// Holder class to break cyclic dependency seen during build
402+
private static class FormatHolder {
403+
private static final String PATTERN = "yyyy-MM-dd kk:mm:ss.SSS";
404+
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter
405+
.ofPattern(PATTERN, Locale.ENGLISH)
406+
.withZone(ZoneId.systemDefault());
407+
}
329408
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8051959
27+
* @summary Option to print extra information in java.security.debug output
28+
* @library /test/lib
29+
* @run junit DebugOptions
30+
*/
31+
32+
import org.junit.jupiter.params.ParameterizedTest;
33+
import org.junit.jupiter.params.provider.Arguments;
34+
import org.junit.jupiter.params.provider.MethodSource;
35+
36+
import java.security.KeyStore;
37+
import java.security.Security;
38+
import java.util.stream.Stream;
39+
40+
import jdk.test.lib.process.OutputAnalyzer;
41+
import jdk.test.lib.process.ProcessTools;
42+
43+
public class DebugOptions {
44+
45+
static final String DATE_REGEX = "\\d{4}-\\d{2}-\\d{2}";
46+
47+
private static Stream<Arguments> patternMatches() {
48+
return Stream.of(
49+
// no extra info present
50+
Arguments.of("properties",
51+
"properties: Initial",
52+
"properties\\["),
53+
// thread info only
54+
Arguments.of("properties+thread",
55+
"properties\\[.*\\|main\\|.*java.*]:",
56+
"properties\\[" + DATE_REGEX),
57+
// timestamp info only
58+
Arguments.of("properties+timestamp",
59+
"properties\\[" + DATE_REGEX + ".*\\]",
60+
"\\|main\\]:"),
61+
// both thread and timestamp
62+
Arguments.of("properties+timestamp+thread",
63+
"properties\\[.*\\|main|" + DATE_REGEX + ".*\\]:",
64+
"properties:"),
65+
// flip the arguments of previous test
66+
Arguments.of("properties+thread+timestamp",
67+
"properties\\[.*\\|main|" + DATE_REGEX + ".*\\]:",
68+
"properties:"),
69+
// comma not valid separator, ignore extra info printing request
70+
Arguments.of("properties,thread,timestamp",
71+
"properties:",
72+
"properties\\[.*\\|main|" + DATE_REGEX + ".*\\]:"),
73+
// no extra info for keystore debug prints
74+
Arguments.of("properties+thread+timestamp,keystore",
75+
"properties\\[.*\\|main|" + DATE_REGEX + ".*\\]:",
76+
"keystore\\["),
77+
// flip arguments around in last test - same outcome expected
78+
Arguments.of("keystore,properties+thread+timestamp",
79+
"properties\\[.*\\|main|" + DATE_REGEX + ".*\\]:",
80+
"keystore\\["),
81+
// turn on thread info for both keystore and properties components
82+
Arguments.of("keystore+thread,properties+thread",
83+
"properties\\[.*\\|main|.*\\Rkeystore\\[.*\\|main|.*\\]:",
84+
"\\|" + DATE_REGEX + ".*\\]:"),
85+
// same as above with erroneous comma at end of string. same output expected
86+
Arguments.of("keystore+thread,properties+thread,",
87+
"properties\\[.*\\|main|.*\\Rkeystore\\[.*\\|main|.*\\]:",
88+
"\\|" + DATE_REGEX + ".*\\]:"),
89+
// turn on thread info for properties and timestamp for keystore
90+
Arguments.of("keystore+timestamp,properties+thread",
91+
"properties\\[.*\\|main|.*\\Rkeystore\\[" + DATE_REGEX + ".*\\]:",
92+
"properties\\[.*\\|" + DATE_REGEX + ".*\\]:"),
93+
// turn on thread info for all components
94+
Arguments.of("all+thread",
95+
"properties\\[.*\\|main.*((.*\\R)*)keystore\\[.*\\|main.*java.*\\]:",
96+
"properties\\[" + DATE_REGEX + ".*\\]:"),
97+
// turn on thread info and timestamp for all components
98+
Arguments.of("all+thread+timestamp",
99+
"properties\\[.*\\|main.*\\|" + DATE_REGEX +
100+
".*\\]((.*\\R)*)keystore\\[.*\\|main.*\\|" + DATE_REGEX + ".*\\]:",
101+
"properties:"),
102+
// all decorator option should override other component options
103+
Arguments.of("all+thread+timestamp,properties",
104+
"properties\\[.*\\|main.*\\|" + DATE_REGEX +
105+
".*\\]((.*\\R)*)keystore\\[.*\\|main.*\\|" + DATE_REGEX + ".*\\]:",
106+
"properties:"),
107+
// thread details should only be printed for properties option
108+
Arguments.of("properties+thread,all",
109+
"properties\\[.*\\|main\\|.*\\]:",
110+
"keystore\\[.*\\|main\\|.*\\]:"),
111+
// thread details should be printed for all statements
112+
Arguments.of("properties,all+thread",
113+
"properties\\[.*\\|main.*java" +
114+
".*\\]((.*\\R)*)keystore\\[.*\\|main.*java.*\\]:",
115+
"properties:")
116+
);
117+
}
118+
119+
@ParameterizedTest
120+
@MethodSource("patternMatches")
121+
public void shouldContain(String params, String expected, String notExpected) throws Exception {
122+
OutputAnalyzer outputAnalyzer = ProcessTools.executeTestJava(
123+
"-Djava.security.debug=" + params,
124+
"DebugOptions"
125+
);
126+
outputAnalyzer.shouldHaveExitValue(0)
127+
.shouldMatch(expected)
128+
.shouldNotMatch(notExpected);
129+
}
130+
131+
public static void main(String[] args) throws Exception {
132+
// something to trigger "properties" debug output
133+
Security.getProperty("test");
134+
// trigger "keystore" debug output
135+
KeyStore ks = KeyStore.getInstance("PKCS12");
136+
ks.load(null, null);
137+
}
138+
}

0 commit comments

Comments
 (0)