Skip to content

Added confidential checker JDK annotations#224

Open
iywang2016 wants to merge 50 commits into
typetools:masterfrom
iywang2016:iywang
Open

Added confidential checker JDK annotations#224
iywang2016 wants to merge 50 commits into
typetools:masterfrom
iywang2016:iywang

Conversation

@iywang2016
Copy link
Copy Markdown

@iywang2016 iywang2016 commented Mar 3, 2025

Associated with checker framework pull request #6981

mernst and others added 20 commits April 3, 2025 17:30
# Conflicts:
#	src/java.base/share/classes/java/lang/Object.java
#	src/java.base/share/classes/org/checkerframework/checker/confidential/qual/BottomConfidential.java
#	src/java.base/share/classes/org/checkerframework/checker/confidential/qual/Confidential.java
#	src/java.base/share/classes/org/checkerframework/checker/confidential/qual/NonConfidential.java
#	src/java.base/share/classes/org/checkerframework/checker/confidential/qual/PolyConfidential.java
#	src/java.base/share/classes/org/checkerframework/checker/confidential/qual/UnknownConfidential.java
#	src/java.base/share/classes/org/checkerframework/checker/sqlquotes/qual/SqlEvenQuotes.java
#	src/java.base/share/classes/org/checkerframework/checker/sqlquotes/qual/SqlOddQuotes.java
#	src/java.base/share/classes/org/checkerframework/checker/sqlquotes/qual/SqlQuotesBottom.java
#	src/java.base/share/classes/org/checkerframework/checker/sqlquotes/qual/SqlQuotesUnknown.java
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 26, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds confidentiality Checker Framework annotations and applies them in the changed ranges: java.lang.String (imports, @AnnotatedFor, equals, format), java.lang.Throwable (imports, @AnnotatedFor, getMessage), java.util.Map#get (receiver), and java.util.stream.Stream (imports, @AnnotatedFor, collect parameter). Each updated signature uses @PolyConfidential/@NonConfidential qualifiers where shown.

Possibly related PRs

  • typetools/jdk#276: Overlaps at java/lang/String.equals — that PR modifies nullness-related annotations at the same method site.
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/java.base/share/classes/java/lang/Exception.java`:
- Line 28: Subclasses of java.lang.Exception (notably IOException and
ReflectiveOperationException) must mirror the new `@PolyConfidential` constructor
signatures and module annotations: update each subclass's constructors that
accept message/cause (e.g., IOException(String message), IOException(String
message, Throwable cause), ReflectiveOperationException(...) and similar in
other Exception subclasses) to annotate the message and cause parameters with
`@PolyConfidential`, and add "confidential" to their `@AnnotatedFor` declarations so
they satisfy the Exception contract; ensure every super(...) call remains
compatible with the annotated types.

In `@src/java.base/share/classes/java/util/Map.java`:
- Around line 274-275: The implementations of Map.get() are missing the
`@PolyConfidential` annotation on their receiver which violates the interface
contract declared by Map.get(`@PolyConfidential` ... this) and breaks type
checking. For each implementing class (HashMap, LinkedHashMap, AbstractMap,
IdentityHashMap, WeakHashMap, TreeMap, EnumMap, ConcurrentHashMap) update the
get(...) method signature to include the `@PolyConfidential` annotation on the
receiver (matching Map.get's receiver annotation style, e.g., add
`@PolyConfidential` to the this parameter alongside existing
`@GuardSatisfied/`@UnknownSignedness annotations) and add any required imports so
the method signatures match the interface contract.

In `@src/java.logging/share/classes/java/util/logging/Handler.java`:
- Line 29: Add the `@NonConfidential` annotation to each override of the
publish(LogRecord record) method in the listed Handler subclasses so their
signatures match the annotated parent: update FileHandler.publish(LogRecord
record), SocketHandler.publish(LogRecord record),
StreamHandler.publish(LogRecord record), MemoryHandler.publish(LogRecord
record), ConsoleHandler.publish(LogRecord record), and
sun.rmi.runtime.Log.publish(LogRecord record) to include `@NonConfidential` on the
method declaration.

In `@src/java.logging/share/classes/java/util/logging/Logger.java`:
- Line 28: Remove the duplicate import of
org.checkerframework.checker.confidential.qual.NonConfidential in Logger.java:
locate the import declarations at the top of the file (related to class Logger)
and delete the redundant second occurrence so only a single import of
`@NonConfidential` remains.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 88f7bb0d-c5ca-4cdc-928f-4a2404844bb2

📥 Commits

Reviewing files that changed from the base of the PR and between ef188ca and 0388e83.

📒 Files selected for processing (18)
  • src/java.base/share/classes/java/io/PrintStream.java
  • src/java.base/share/classes/java/lang/Exception.java
  • src/java.base/share/classes/java/lang/Object.java
  • src/java.base/share/classes/java/lang/RuntimeException.java
  • src/java.base/share/classes/java/lang/String.java
  • src/java.base/share/classes/java/lang/Throwable.java
  • src/java.base/share/classes/java/util/Map.java
  • src/java.base/share/classes/java/util/Optional.java
  • src/java.base/share/classes/java/util/stream/Stream.java
  • src/java.base/share/classes/module-info.java
  • src/java.base/share/classes/org/checkerframework/checker/confidential/qual/BottomConfidential.java
  • src/java.base/share/classes/org/checkerframework/checker/confidential/qual/Confidential.java
  • src/java.base/share/classes/org/checkerframework/checker/confidential/qual/NonConfidential.java
  • src/java.base/share/classes/org/checkerframework/checker/confidential/qual/PolyConfidential.java
  • src/java.base/share/classes/org/checkerframework/checker/confidential/qual/UnknownConfidential.java
  • src/java.logging/share/classes/java/util/logging/Formatter.java
  • src/java.logging/share/classes/java/util/logging/Handler.java
  • src/java.logging/share/classes/java/util/logging/Logger.java
👮 Files not reviewed due to content moderation or server errors (5)
  • src/java.logging/share/classes/java/util/logging/Formatter.java
  • src/java.base/share/classes/org/checkerframework/checker/confidential/qual/BottomConfidential.java
  • src/java.base/share/classes/java/lang/Object.java
  • src/java.base/share/classes/java/lang/String.java
  • src/java.base/share/classes/java/io/PrintStream.java


package java.lang;

import org.checkerframework.checker.confidential.qual.PolyConfidential;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find Exception subclasses with constructors taking String message that lack `@PolyConfidential`
rg -l "extends Exception" --type=java | xargs -I{} sh -c 'echo "=== {} ===" && rg -n "public.*\(.*String message" {} | head -5'

Repository: typetools/jdk

Length of output: 36660


🏁 Script executed:

# Check Exception.java content
cat -n src/java.base/share/classes/java/lang/Exception.java | head -150

Repository: typetools/jdk

Length of output: 6726


🏁 Script executed:

# Check IOException to see if it extends Exception and its constructor signatures
cat -n src/java.base/share/classes/java/io/IOException.java | head -100

Repository: typetools/jdk

Length of output: 4428


🏁 Script executed:

# Check ReflectiveOperationException as well
cat -n src/java.base/share/classes/java/lang/ReflectiveOperationException.java

Repository: typetools/jdk

Length of output: 4257


LGTM! @PolyConfidential annotations properly added to Exception constructors.

The annotations enable confidentiality tracking through the exception hierarchy. The @PolyConfidential qualifier on message and cause parameters allows the checker to track whether exceptions contain confidential data.

However, IOException (which extends Exception) and other subclasses lack corresponding @PolyConfidential annotations on their constructors. When IOException calls super(message), it passes a plain String where Exception expects @PolyConfidential String, breaking the type contract. Update IOException, ReflectiveOperationException, and other Exception subclasses to include @PolyConfidential annotations and add "confidential" to their @AnnotatedFor declarations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/java.base/share/classes/java/lang/Exception.java` at line 28, Subclasses
of java.lang.Exception (notably IOException and ReflectiveOperationException)
must mirror the new `@PolyConfidential` constructor signatures and module
annotations: update each subclass's constructors that accept message/cause
(e.g., IOException(String message), IOException(String message, Throwable
cause), ReflectiveOperationException(...) and similar in other Exception
subclasses) to annotate the message and cause parameters with `@PolyConfidential`,
and add "confidential" to their `@AnnotatedFor` declarations so they satisfy the
Exception contract; ensure every super(...) call remains compatible with the
annotated types.

Comment on lines 274 to +275
@Pure
@Nullable V get(@GuardSatisfied Map<K, V> this, @UnknownSignedness @GuardSatisfied Object key);
@Nullable V get(@GuardSatisfied @PolyConfidential Map<K, V> this, @UnknownSignedness @GuardSatisfied Object key);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if Map implementations have `@PolyConfidential` on their get() receivers
# Expected: Find whether implementations need to be updated

# Search for get() method signatures in Map implementations
rg -n --type=java -B2 -A2 'public.*\bV\b\s+get\s*\(' | rg -A4 -B4 'HashMap|ConcurrentHashMap|AbstractMap|LinkedHashMap|TreeMap|EnumMap|IdentityHashMap|WeakHashMap'

Repository: typetools/jdk

Length of output: 13632


Add @PolyConfidential annotation to the receiver of get() methods in all Map implementations.

The Map.get() interface declares @PolyConfidential on its receiver, but all implementing classes (HashMap, LinkedHashMap, AbstractMap, IdentityHashMap, WeakHashMap, TreeMap, EnumMap, ConcurrentHashMap) omit this annotation on their receivers. This inconsistency will cause type-checking failures since implementations do not satisfy the receiver type annotation contract established by the interface.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/java.base/share/classes/java/util/Map.java` around lines 274 - 275, The
implementations of Map.get() are missing the `@PolyConfidential` annotation on
their receiver which violates the interface contract declared by
Map.get(`@PolyConfidential` ... this) and breaks type checking. For each
implementing class (HashMap, LinkedHashMap, AbstractMap, IdentityHashMap,
WeakHashMap, TreeMap, EnumMap, ConcurrentHashMap) update the get(...) method
signature to include the `@PolyConfidential` annotation on the receiver (matching
Map.get's receiver annotation style, e.g., add `@PolyConfidential` to the this
parameter alongside existing `@GuardSatisfied/`@UnknownSignedness annotations) and
add any required imports so the method signatures match the interface contract.


package java.util.logging;

import org.checkerframework.checker.confidential.qual.NonConfidential;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all Handler subclasses with publish methods that might need `@NonConfidential`
rg -n "public void publish\(LogRecord" --type=java -C2 | grep -v "@NonConfidential"

Repository: typetools/jdk

Length of output: 23801


Handler subclasses must also include @NonConfidential annotation

The Handler.java changes are correct. The @NonConfidential annotation on Handler.publish(LogRecord record) properly enforces that log records must not contain confidential data.

However, the following Handler subclasses override publish() without this annotation and will trigger type-checker warnings:

  • FileHandler.java:778
  • SocketHandler.java:192
  • StreamHandler.java:221
  • MemoryHandler.java:181
  • ConsoleHandler.java:94
  • sun.rmi.runtime.Log.java:287

These subclass implementations must include @NonConfidential to match the parent signature.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/java.logging/share/classes/java/util/logging/Handler.java` at line 29,
Add the `@NonConfidential` annotation to each override of the publish(LogRecord
record) method in the listed Handler subclasses so their signatures match the
annotated parent: update FileHandler.publish(LogRecord record),
SocketHandler.publish(LogRecord record), StreamHandler.publish(LogRecord
record), MemoryHandler.publish(LogRecord record),
ConsoleHandler.publish(LogRecord record), and
sun.rmi.runtime.Log.publish(LogRecord record) to include `@NonConfidential` on the
method declaration.


package java.util.logging;

import org.checkerframework.checker.confidential.qual.NonConfidential;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove duplicate import of @NonConfidential.

The import org.checkerframework.checker.confidential.qual.NonConfidential appears twice:

  • Line 28
  • Line 38

This is redundant and will cause a compiler warning.

🔧 Proposed fix
 import org.checkerframework.checker.confidential.qual.NonConfidential;
 import org.checkerframework.checker.interning.qual.Interned;
 import org.checkerframework.checker.interning.qual.UsesObjectEquals;
 import org.checkerframework.checker.lock.qual.GuardSatisfied;
 import org.checkerframework.checker.nullness.qual.Nullable;
 import org.checkerframework.checker.signature.qual.BinaryName;
 import org.checkerframework.dataflow.qual.Pure;
 import org.checkerframework.dataflow.qual.SideEffectFree;
 import org.checkerframework.framework.qual.AnnotatedFor;
 import org.checkerframework.framework.qual.CFComment;
-import org.checkerframework.checker.confidential.qual.NonConfidential;

Also applies to: 38-38

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/java.logging/share/classes/java/util/logging/Logger.java` at line 28,
Remove the duplicate import of
org.checkerframework.checker.confidential.qual.NonConfidential in Logger.java:
locate the import declarations at the top of the file (related to class Logger)
and delete the redundant second occurrence so only a single import of
`@NonConfidential` remains.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/java.base/share/classes/java/lang/String.java (1)

4586-4653: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Propagate @PolyConfidential consistently across String formatting entry points.

String.format(String, Object...) is partially qualified (@PolyConfidential return + varargs element type) but the format parameter is unannotated (line 4586), while String.format(Locale, String, Object...) and String.formatted(Object...) are entirely unannotated (lines 4630, 4652), creating confidentiality-drop paths.

Suggested fix
-    public static `@PolyConfidential` String format(String format, `@PolyConfidential` `@GuardSatisfied` `@Nullable` Object `@GuardSatisfied` ... args) {
+    public static `@PolyConfidential` String format(
+            `@PolyConfidential` String format,
+            `@PolyConfidential` `@GuardSatisfied` `@Nullable` Object `@GuardSatisfied` ... args) {
         return new Formatter().format(format, args).toString();
     }

-    public static String format(`@GuardSatisfied` `@Nullable` Locale l, String format, `@GuardSatisfied` `@Nullable` Object `@GuardSatisfied` ... args) {
+    public static `@PolyConfidential` String format(
+            `@GuardSatisfied` `@Nullable` Locale l,
+            `@PolyConfidential` String format,
+            `@PolyConfidential` `@GuardSatisfied` `@Nullable` Object `@GuardSatisfied` ... args) {
         return new Formatter(l).format(format, args).toString();
     }

-    public String formatted(Object... args) {
+    public `@PolyConfidential` String formatted(
+            `@PolyConfidential` Object... args) {
         return new Formatter().format(this, args).toString();
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/java.base/share/classes/java/lang/String.java` around lines 4586 - 4653,
The three formatting entry points create confidentiality-drop paths; update
their signatures to consistently propagate `@PolyConfidential`: annotate the
return type, the format String parameter, and the varargs element type with
`@PolyConfidential`. Concretely, change String.format(String, Object...) to keep
the `@PolyConfidential` on the return and add `@PolyConfidential` to the format
parameter and the Object varargs element; update String.format(Locale, String,
Object...) to annotate its return, the format parameter, and the varargs element
with `@PolyConfidential`; and update String.formatted(Object...) to annotate its
return and the Object varargs element with `@PolyConfidential` so all three
methods (format(String,...), format(Locale, String,...), and formatted(...))
consistently propagate confidentiality.
src/java.base/share/classes/java/lang/Throwable.java (2)

671-687: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove @SideEffectFree from Throwable#printStackTrace overloads (observable I/O + locking)

Throwable’s printStackTrace overloads are annotated @SideEffectFree but they produce observable output (via s.println(...) in lockedPrintStackTrace) and acquire locks (Object lock = s.lock() + locker.lock()/synchronized (lock)). This applies to printStackTrace() / printStackTrace(PrintStream) / the PrintStreamOrWriter helper and printStackTrace(PrintWriter).

Suggested fix
-    `@SideEffectFree`
     public void printStackTrace() {
         printStackTrace(System.err);
     }

-    `@SideEffectFree`
     public void printStackTrace(PrintStream s) {
         printStackTrace(new WrappedPrintStream(s));
     }

-    `@SideEffectFree`
     private void printStackTrace(PrintStreamOrWriter s) {
         Object lock = s.lock();
         if (lock instanceof InternalLock locker) {
             locker.lock();
             try {
                 lockedPrintStackTrace(s);
             } finally {
                 locker.unlock();
             }
         } else synchronized (lock) {
             lockedPrintStackTrace(s);
         }
     }

-    `@SideEffectFree`
     public void printStackTrace(PrintWriter s) {
         printStackTrace(new WrappedPrintWriter(s));
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/java.base/share/classes/java/lang/Throwable.java` around lines 671 - 687,
Remove the incorrect `@SideEffectFree` annotations from
Throwable#printStackTrace(), Throwable#printStackTrace(PrintStream),
Throwable#printStackTrace(PrintWriter) and the private helper
printStackTrace(PrintStreamOrWriter) because these methods perform observable
I/O and locking; locate the annotations on the methods named printStackTrace (no
matter the signature) and delete the `@SideEffectFree` marker lines so the methods
no longer claim to be side-effect free.

400-402: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix broken confidentiality polymorphism in Throwable.getMessage()
Throwable.getMessage(@PolyConfidential ... this) returns @Nullable @NonConfidential String, which blocks confidentiality flow from the polymorphic receiver and contradicts the adjacent TODO that the type should be @PolyConfidential.

Suggested fix
-    // TODO: The type should be `@Poly`.  Change it later.
-    public `@Nullable` `@NonConfidential` String getMessage(`@PolyConfidential` `@GuardSatisfied` Throwable this) {
+    public `@Nullable` `@PolyConfidential` String getMessage(`@PolyConfidential` `@GuardSatisfied` Throwable this) {
         return detailMessage;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/java.base/share/classes/java/lang/Throwable.java` around lines 400 - 402,
Throwable.getMessage currently declares the return type as `@Nullable`
`@NonConfidential` String while the receiver is annotated `@PolyConfidential`,
preventing confidentiality polymorphism and contradicting the TODO; change the
method signature so the return type is polymorphic with the receiver (e.g.,
`@PolyConfidential` `@Nullable` String) so the confidentiality flows from the
`@PolyConfidential` receiver through getMessage; ensure the implementation still
returns detailMessage and update any import/annotation usage if needed to use
the `@PolyConfidential/`@Poly annotations consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/java.base/share/classes/java/lang/String.java`:
- Around line 4586-4653: The three formatting entry points create
confidentiality-drop paths; update their signatures to consistently propagate
`@PolyConfidential`: annotate the return type, the format String parameter, and
the varargs element type with `@PolyConfidential`. Concretely, change
String.format(String, Object...) to keep the `@PolyConfidential` on the return and
add `@PolyConfidential` to the format parameter and the Object varargs element;
update String.format(Locale, String, Object...) to annotate its return, the
format parameter, and the varargs element with `@PolyConfidential`; and update
String.formatted(Object...) to annotate its return and the Object varargs
element with `@PolyConfidential` so all three methods (format(String,...),
format(Locale, String,...), and formatted(...)) consistently propagate
confidentiality.

In `@src/java.base/share/classes/java/lang/Throwable.java`:
- Around line 671-687: Remove the incorrect `@SideEffectFree` annotations from
Throwable#printStackTrace(), Throwable#printStackTrace(PrintStream),
Throwable#printStackTrace(PrintWriter) and the private helper
printStackTrace(PrintStreamOrWriter) because these methods perform observable
I/O and locking; locate the annotations on the methods named printStackTrace (no
matter the signature) and delete the `@SideEffectFree` marker lines so the methods
no longer claim to be side-effect free.
- Around line 400-402: Throwable.getMessage currently declares the return type
as `@Nullable` `@NonConfidential` String while the receiver is annotated
`@PolyConfidential`, preventing confidentiality polymorphism and contradicting the
TODO; change the method signature so the return type is polymorphic with the
receiver (e.g., `@PolyConfidential` `@Nullable` String) so the confidentiality flows
from the `@PolyConfidential` receiver through getMessage; ensure the
implementation still returns detailMessage and update any import/annotation
usage if needed to use the `@PolyConfidential/`@Poly annotations consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 75410cfb-d31d-465b-9481-c6d798c629c1

📥 Commits

Reviewing files that changed from the base of the PR and between 0388e83 and cb6e2c2.

📒 Files selected for processing (4)
  • src/java.base/share/classes/java/lang/String.java
  • src/java.base/share/classes/java/lang/Throwable.java
  • src/java.base/share/classes/java/util/Map.java
  • src/java.base/share/classes/java/util/stream/Stream.java
💤 Files with no reviewable changes (1)
  • src/java.base/share/classes/java/util/stream/Stream.java

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants