Skip to content

Commit e257ae5

Browse files
committedJun 26, 2024
8051959: Add thread and timestamp options to java.security.debug system property
Reviewed-by: mbaesken Backport-of: 3b582dff849f1c25336e2efc415eb121f8b12189
1 parent 4dceb87 commit e257ae5

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

+99-20
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");
@@ -95,6 +113,11 @@ public static void Help()
95113
System.err.println("ts timestamping");
96114
System.err.println("x509 X.509 certificate debugging");
97115
System.err.println();
116+
System.err.println("+timestamp can be appended to any of above options to print");
117+
System.err.println(" a timestamp for that debug option");
118+
System.err.println("+thread can be appended to any of above options to print");
119+
System.err.println(" thread and caller information for that debug option");
120+
System.err.println();
98121
System.err.println("The following can be used with access:");
99122
System.err.println();
100123
System.err.println("stack include stack trace");
@@ -139,32 +162,65 @@ public static void Help()
139162
* option is set. Set the prefix to be the same as option.
140163
*/
141164

142-
public static Debug getInstance(String option)
143-
{
165+
public static Debug getInstance(String option) {
144166
return getInstance(option, option);
145167
}
146168

147169
/**
148170
* Get a Debug object corresponding to whether or not the given
149171
* option is set. Set the prefix to prefix.
150172
*/
151-
public static Debug getInstance(String option, String prefix)
152-
{
173+
public static Debug getInstance(String option, String prefix) {
153174
if (isOn(option)) {
154175
Debug d = new Debug();
155176
d.prefix = prefix;
177+
d.configureExtras(option);
156178
return d;
157179
} else {
158180
return null;
159181
}
160182
}
161183

184+
private static String formatCaller() {
185+
return StackWalker.getInstance().walk(s ->
186+
s.dropWhile(f ->
187+
f.getClassName().startsWith("sun.security.util.Debug"))
188+
.map(f -> f.getFileName() + ":" + f.getLineNumber())
189+
.findFirst().orElse("unknown caller"));
190+
}
191+
192+
// parse an option string to determine if extra details,
193+
// like thread and timestamp, should be printed
194+
private void configureExtras(String option) {
195+
// treat "all" as special case, only used for java.security.debug property
196+
this.printDateTime = timeStampInfoAll;
197+
this.printThreadDetails = threadInfoAll;
198+
199+
if (printDateTime && printThreadDetails) {
200+
// nothing left to configure
201+
return;
202+
}
203+
204+
// args is converted to lower case for the most part via marshal method
205+
int optionIndex = args.lastIndexOf(option);
206+
if (optionIndex == -1) {
207+
// option not in args list. Only here since "all" was present
208+
// in debug property argument. "all" option already parsed
209+
return;
210+
}
211+
int beginIndex = optionIndex + option.length();
212+
int commaIndex = args.indexOf(',', beginIndex);
213+
if (commaIndex == -1) commaIndex = args.length();
214+
String subOpt = args.substring(beginIndex, commaIndex);
215+
printDateTime = printDateTime || subOpt.contains(TIMESTAMP_OPTION);
216+
printThreadDetails = printThreadDetails || subOpt.contains(THREAD_OPTION);
217+
}
218+
162219
/**
163220
* True if the system property "security.debug" contains the
164221
* string "option".
165222
*/
166-
public static boolean isOn(String option)
167-
{
223+
public static boolean isOn(String option) {
168224
if (args == null)
169225
return false;
170226
else {
@@ -187,37 +243,53 @@ public static boolean isVerbose() {
187243
* created from the call to getInstance.
188244
*/
189245

190-
public void println(String message)
191-
{
192-
System.err.println(prefix + ": "+message);
246+
public void println(String message) {
247+
System.err.println(prefix + extraInfo() + ": " + message);
193248
}
194249

195250
/**
196251
* print a message to stderr that is prefixed with the prefix
197252
* created from the call to getInstance and obj.
198253
*/
199-
public void println(Object obj, String message)
200-
{
201-
System.err.println(prefix + " [" + obj.getClass().getSimpleName() +
254+
public void println(Object obj, String message) {
255+
System.err.println(prefix + extraInfo() + " [" + obj.getClass().getSimpleName() +
202256
"@" + System.identityHashCode(obj) + "]: "+message);
203257
}
204258

205259
/**
206260
* print a blank line to stderr that is prefixed with the prefix.
207261
*/
208262

209-
public void println()
210-
{
211-
System.err.println(prefix + ":");
263+
public void println() {
264+
System.err.println(prefix + extraInfo() + ":");
212265
}
213266

214267
/**
215268
* print a message to stderr that is prefixed with the prefix.
216269
*/
217270

218-
public static void println(String prefix, String message)
219-
{
220-
System.err.println(prefix + ": "+message);
271+
public void println(String prefix, String message) {
272+
System.err.println(prefix + extraInfo() + ": " + message);
273+
}
274+
275+
/**
276+
* If thread debug option enabled, include information containing
277+
* hex value of threadId and the current thread name
278+
* If timestamp debug option enabled, include timestamp string
279+
* @return extra info if debug option enabled.
280+
*/
281+
private String extraInfo() {
282+
String retString = "";
283+
if (printThreadDetails) {
284+
retString = "0x" + Long.toHexString(
285+
Thread.currentThread().threadId()).toUpperCase(Locale.ROOT) +
286+
"|" + Thread.currentThread().getName() + "|" + formatCaller();
287+
}
288+
if (printDateTime) {
289+
retString += (retString.isEmpty() ? "" : "|")
290+
+ FormatHolder.DATE_TIME_FORMATTER.format(Instant.now());
291+
}
292+
return retString.isEmpty() ? "" : "[" + retString + "]";
221293
}
222294

223295
/**
@@ -333,4 +405,11 @@ public static String toString(byte[] b) {
333405
return HexFormat.ofDelimiter(":").formatHex(b);
334406
}
335407

408+
// Holder class to break cyclic dependency seen during build
409+
private static class FormatHolder {
410+
private static final String PATTERN = "yyyy-MM-dd kk:mm:ss.SSS";
411+
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter
412+
.ofPattern(PATTERN, Locale.ENGLISH)
413+
.withZone(ZoneId.systemDefault());
414+
}
336415
}
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+
}

1 commit comments

Comments
 (1)

openjdk-notifier[bot] commented on Jun 26, 2024

@openjdk-notifier[bot]
Please sign in to comment.