Skip to content

ParameterException: NullPointerException: null while processing argument at or before arg[0] (was: commandLine.parseArgs("--item="); and commandLine.parseArgs("--item", ""); should behave identically, but current; they don't) #1998

Closed

Description

@jiridanek

I've been wanting to remove my own Converter now that #1993 is implemented, but I found that there is a difference in behavior when I use the converter and when I do not use it.

--item in the reproducer below does not use converter (and does not pass one of the tests, while --item2 uses converter and both tests pass.

What I need to accomplish is that commandLine.parseArgs("--item=", "--item", "pepa"); parses into ["", "pepa"], but actually, the parsing throws an exception (see bottom of the post for the printout).

I can get the desired behavior when I specify the argument as commandLine.parseArgs("--item", "");, but I do need to support the former syntax with the = as well.

See void test_item__empty_equals() { which is the failing test.

import org.junit.jupiter.api.Test;
import org.powermock.reflect.Whitebox;
import picocli.CommandLine;

import java.util.List;
import java.util.concurrent.Callable;

import static com.google.common.truth.Truth.assertThat;

@CommandLine.Command(
    name = "command",
    mixinStandardHelpOptions = true,
    version = "0.0.0",
    description = ""
)
class CommandMain implements Callable<Integer> {
    static final String MY_NULL_VALUE = "_MY_" + CommandLine.Option.NULL_VALUE;

    @CommandLine.Option(names = {"--item"}, arity = "0..1", fallbackValue = CommandLine.Option.NULL_VALUE)
    private List<String> item;
    @CommandLine.Option(names = {"--item2"}, arity = "0..1", fallbackValue = MY_NULL_VALUE, converter = ItemNullValueConverter.class)
    private List<String> item2;

    @Override
    public Integer call() throws Exception {
        return 0;
    }
}

class ItemNullValueConverter implements CommandLine.ITypeConverter<String> {
    @Override
    public String convert(String value) throws Exception {
        if (value.equals(CommandMain.MY_NULL_VALUE)) {
            return null;
        }
        return value;
    }
}

class StandalonePicocliTest {
    CommandMain main = new CommandMain();
    CommandLine commandLine = new CommandLine(main);

    @Test  // PASS
    void test_item__null() {
        commandLine.parseArgs("--item", "--item", "pepa");

        // https://github.com/powermock/powermock/wiki/Bypass-Encapsulation
        List<String> v = Whitebox.getInternalState(main, "item", main.getClass());
        assertThat(v).containsExactly(null, "pepa");
    }

    @Test  // FAIL
    void test_item__empty_equals() {
        commandLine.parseArgs("--item=", "--item", "pepa");

        List<String> v = Whitebox.getInternalState(main, "item", main.getClass());
        assertThat(v).containsExactly("", "pepa");
    }

    @Test  // PASS
    void test_item2__null() {
        commandLine.parseArgs("--item2", "--item2", "pepa");

        // https://github.com/powermock/powermock/wiki/Bypass-Encapsulation
        List<String> v = Whitebox.getInternalState(main, "item2", main.getClass());
        assertThat(v).containsExactly(null, "pepa");
    }

    @Test  // PASS
    void test_item2__empty_equals() {
        commandLine.parseArgs("--item2=", "--item2", "pepa");

        List<String> v = Whitebox.getInternalState(main, "item2", main.getClass());
        assertThat(v).containsExactly("", "pepa");
    }
}

Exception from test_item__empty_equals()

picocli.CommandLine$ParameterException: NullPointerException: Cannot invoke "String.equals(Object)" because "originalArg" is null while processing argument at or before arg[0] '--item=' in [--item=, --item, pepa]: java.lang.NullPointerException: Cannot invoke "String.equals(Object)" because "originalArg" is null

	at picocli.CommandLine$ParameterException.create(CommandLine.java:18564)
	at picocli.CommandLine$ParameterException.access$20800(CommandLine.java:18488)
	at picocli.CommandLine$Interpreter.parse(CommandLine.java:13542)
	at picocli.CommandLine$Interpreter.parse(CommandLine.java:13501)
	at picocli.CommandLine$Interpreter.parse(CommandLine.java:13396)
	at picocli.CommandLine.parseArgs(CommandLine.java:1552)
	at com.redhat.mqe.StandalonePicocliTest.test_item__empty_equals(StandalonePicocliTest.java:76)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:217)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:147)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:127)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:90)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:55)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:102)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
	at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: java.lang.NullPointerException: Cannot invoke "String.equals(Object)" because "originalArg" is null
	at picocli.CommandLine$Interpreter.processArguments(CommandLine.java:13716)
	at picocli.CommandLine$Interpreter.parse(CommandLine.java:13533)
	... 74 more

Logs are

[picocli DEBUG] Creating CommandSpec for com.redhat.mqe.CommandMain@6af93788 with factory picocli.CommandLine$DefaultFactory
[picocli DEBUG] Creating CommandSpec for picocli.CommandLine$AutoHelpMixin@130d63be with factory picocli.CommandLine$DefaultFactory
[picocli INFO] Picocli version: 4.7.2, JVM: 17.0.6 (Red Hat, Inc. OpenJDK 64-Bit Server VM 17.0.6+10), OS: Linux 6.2.9-300.fc38.x86_64 amd64
[picocli INFO] Parsing 3 command line args [--item=, --item, pepa]
[picocli DEBUG] Parser configuration: optionsCaseInsensitive=false, subcommandsCaseInsensitive=false, abbreviatedOptionsAllowed=false, abbreviatedSubcommandsAllowed=false, allowOptionsAsOptionParameters=false, allowSubcommandsAsOptionParameters=false, aritySatisfiedByAttachedOptionParam=false, atFileCommentChar=#, caseInsensitiveEnumValuesAllowed=false, collectErrors=false, endOfOptionsDelimiter=--, expandAtFiles=true, limitSplit=false, overwrittenOptionsAllowed=false, posixClusteredShortOptionsAllowed=true, separator=null, splitQuotedStrings=false, stopAtPositional=false, stopAtUnmatched=false, toggleBooleanFlags=false, trimQuotes=false, unmatchedArgumentsAllowed=false, unmatchedOptionsAllowedAsOptionParameters=true, unmatchedOptionsArePositionalParams=false, useSimplifiedAtFiles=false
[picocli DEBUG] (ANSI is disabled by default: systemproperty[picocli.ansi]=null, isatty=false, TERM=null, OSTYPE=null, isWindows=false, JansiConsoleInstalled=false, ANSICON=null, ConEmuANSI=null, NO_COLOR=null, CLICOLOR=null, CLICOLOR_FORCE=null)
[picocli DEBUG] Initializing command 'command' (user object: com.redhat.mqe.CommandMain@6af93788): 4 options, 0 positional parameters, 0 required, 0 groups, 0 subcommands.
[picocli DEBUG] Set initial value for field java.util.List<String> com.redhat.mqe.CommandMain.item of type interface java.util.List to null.
[picocli DEBUG] Set initial value for field java.util.List<String> com.redhat.mqe.CommandMain.item2 of type interface java.util.List to null.
[picocli DEBUG] Set initial value for field boolean picocli.CommandLine$AutoHelpMixin.helpRequested of type boolean to false.
[picocli DEBUG] Set initial value for field boolean picocli.CommandLine$AutoHelpMixin.versionRequested of type boolean to false.
[picocli DEBUG] [0] Processing argument '--item='. Remainder=[--item, pepa]
[picocli DEBUG] Separated '--item' option from '' option parameter
[picocli DEBUG] Found option named '--item': field java.util.List<String> com.redhat.mqe.CommandMain.item, arity=1
[picocli DEBUG] '' doesn't resemble an option: 0 matching prefix chars out of 6 option names
[picocli INFO] Adding [] to field java.util.List<String> com.redhat.mqe.CommandMain.item for option --item on CommandMain@6af93788
[picocli DEBUG] Initializing binding for option '--item' (<item>) on CommandMain@6af93788 with empty List

Activity

  1. modified the milestones: 4.7.2, 4.7.3 on Apr 14, 2023
  2. remkop commented on Apr 14, 2023

    @remkop
    Owner

    Analysis

    This looks like a regression introduced in 4.7.2.

    // CommandLine.java L14551-14558
    String fallback = consumed == 0 && argSpec.isOption() && !OptionSpec.DEFAULT_FALLBACK_VALUE.equals(((OptionSpec) argSpec).fallbackValue())
            ? ((OptionSpec) argSpec).fallbackValue()
            : null;
    boolean hasFallback = fallback != null || (argSpec.isOption() && Option.NULL_VALUE.equals(((OptionSpec) argSpec).originalFallbackValue));
    if (hasFallback && (args.isEmpty() || !varargCanConsumeNextValue(argSpec, args.peek())
            || (!canConsumeOneArgument(argSpec, lookBehind, alreadyUnquoted, arity, consumed, args.peek(), argDescription)))) {
        args.push(fallback);
    }

    Should be:

    String fallback = argSpec.isOption() && !OptionSpec.DEFAULT_FALLBACK_VALUE.equals(((OptionSpec) argSpec).fallbackValue())
            ? ((OptionSpec) argSpec).fallbackValue()
            : null;
    boolean hasFallback = fallback != null || (argSpec.isOption() && Option.NULL_VALUE.equals(((OptionSpec) argSpec).originalFallbackValue));
    if (consumed == 0 && hasFallback && (args.isEmpty() || !varargCanConsumeNextValue(argSpec, args.peek())
            || (!canConsumeOneArgument(argSpec, lookBehind, alreadyUnquoted, arity, consumed, args.peek(), argDescription)))) {
        args.push(fallback);
    }

    The issue is that the current code uses consumed == 0 to determine the fallbackValue (this is unnecessary).
    Instead, it should use consumed == 0 to determine whether the fallback value should be pushed onto the stack as an additional argument to process.

  3. changed the title [-]`commandLine.parseArgs("--item=");` and `commandLine.parseArgs("--item", "");` should behave identically, but current; they don't[/-] [+]ParameterException: NullPointerException: null while processing argument at or before arg[0] (was: `commandLine.parseArgs("--item=");` and `commandLine.parseArgs("--item", "");` should behave identically, but current; they don't)[/+] on Apr 16, 2023
  4. added 2 commits that reference this issue on Apr 16, 2023
  5. remkop commented on Apr 16, 2023

    @remkop
    Owner

    Fixed.
    I released 4.7.3 for this fix.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions