Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Fixes property-based parsing so that option values from Properties are applied to a cloned Option rather than mutating the caller-owned Options, preventing value “write-through” across parses and threads.
Changes:
- Clone
Optioninstances in both property-processing paths before applyingprocessValue()/ adding toCommandLine. - Add a regression test ensuring reusing the same
Optionsacross parses doesn’t leak values into earlierCommandLines or intoOptions.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/test/java/org/apache/commons/cli/AbstractParserTestCase.java | Adds regression test covering Options reuse across property-based parses. |
| src/main/java/org/apache/commons/cli/Parser.java | Clones options in processProperties before setting values / adding to CommandLine. |
| src/main/java/org/apache/commons/cli/DefaultParser.java | Clones options in handleProperties before setting values / passing to handleOption. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // the Options belong to the caller and outlive this parse, so hold the value on a copy | ||
| final Option copy = (Option) opt.clone(); | ||
| if (copy.hasArg()) { | ||
| if (copy.isValuesEmpty()) { |
There was a problem hiding this comment.
Done here too, same restructure: the continue happens first so no clone is made for a skipped flag.
| // the Options belong to the caller and outlive this parse, so hold the value on a copy | ||
| final Option copy = (Option) opt.clone(); | ||
| if (copy.hasArg()) { | ||
| if (copy.isValuesEmpty()) { | ||
| copy.processValue(stripLeadingAndTrailingQuotesDefaultOff(value)); |
There was a problem hiding this comment.
Good point, fixed. The no-arg skip check now runs against opt before the clone, so a flag whose value isn't yes/true/1 never allocates a copy.
|
@farkhalit |
Signed-off-by: farkhalit rida <farkhlait@sproutxp.com>
|
Reviewed both Copilot notes, they're fair. Pushed a commit that moves the no-arg skip check ahead of the clone in both paths, so a flag whose property value isn't yes/true/1 no longer allocates a copy. Behavior is unchanged and the parser tests stay green. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/main/java/org/apache/commons/cli/DefaultParser.java:531
handlePropertiesclonesoptintocopy, sets the property value on it, then passes it tohandleOption, which clones again. This creates an extra allocation per property option and makes the flow harder to follow. Consider adding the already-cloned, already-populatedcopydirectly tocmd(aftercheckRequiredArgs()/updateRequiredOptions(copy)) instead of callinghandleOption(copy).
final Option copy = (Option) opt.clone();
if (copy.hasArg() && copy.isValuesEmpty()) {
copy.processValue(stripLeadingAndTrailingQuotesDefaultOff(value));
}
handleOption(copy);
|
@farkhalit |
handleProperties cloned the Option itself and then passed the clone to handleOption, which clones again. Call handleOption first and store the value on currentOption, the copy it just made, which is the same idiom the argv paths use and leaves one clone per property option.
|
Fair point on the guide: it only describes define, parse, interrogate, and nothing in it promises parsing from several threads, so I've taken the concurrency angle out of the description. The case I'd stand on is narrower: parsing more than once with the same Two updates on the branch: merged current master to clear the conflict, and took the suppressed Copilot note, so I also corrected the checklist in the description: the AI box should have been ticked. Claude was used to help find and prepare this change. |
DefaultParser.handlePropertiescallsprocessValueon theOptioninstance owned by the caller'sOptionsrather than on a copy, so a value that arrives through the defaultsPropertiesis written into the sharedOptionand stays there after the parse. Reuse that sameOptionsfor a second parse and theisValuesEmptyguard silently serves the first parse's value instead of the second one's, and the value remains readable throughoptions.getOption(...).Parser.processPropertieshas the same write-through and also puts the sharedOptionon theCommandLine, so a later parse rewrites what an earlierCommandLinereturns.Both property paths now clone before storing the value, which is what the argv paths already do in
handleOptionandprocessArgs. The new test inAbstractParserTestCasefails forDefaultParser,BasicParser,GnuParserandPosixParserwithout the change.mvn; that'smvnon the command line by itself.