fortify / fcli

fcli is a command-line utility for interacting with various Fortify products
https://fortify.github.io/fcli/
Other
33 stars 22 forks source link

-q "matches" operator does not handle null values #372

Closed psmf22 closed 1 year ago

psmf22 commented 1 year ago

The "matches" operator throws an IllegalStateException when being applied to a field with null value.

To reproduce execute fcli util sample-data list -q "stringValue matches 'value1|value2'

Stacktrace:

java.lang.IllegalStateException: Fcli unexpectedly terminated unsuccessfully java.lang.IllegalStateException: Error evaluating query expression: Message: EL1037E: First operand to matches operator must be a string. 'null' is not Expression: stringValue matches 'value1|value2' Record: { "id" : 15552, "stringValue" : null, "longValue" : 1000, "doubleValue" : 0.7, "booleanValue" : true, "dateValue" : "2000-01-01", "dateTimeValue" : "2000-01-01T00:00:00+00:00", "nestedObject" : { "stringValue" : "nestedObjectValue1", "booleanValue" : true }, "nestedObjectArray" : [ { "stringValue" : "nestedArrayValue1", "booleanValue" : true }, { "stringValue" : "nestedArrayValue2", "booleanValue" : false } ], "nestedStringAray" : [ "nestedArrayValue3", "nestedArrayValue4" ] } at com.fortify.cli.common.output.query.QueryExpression.matches(QueryExpression.java:32) at com.fortify.cli.common.output.writer.output.query.OutputWriterWithQuery.applyRecordOutputFilters(OutputWriterWithQuery.java:46) at com.fortify.cli.common.output.writer.output.standard.StandardOutputWriter.writeRecord(StandardOutputWriter.java:190) at com.fortify.cli.common.output.writer.output.standard.StandardOutputWriter.lambda$4(StandardOutputWriter.java:168) at java.base/java.util.ArrayList$Itr.forEachRemaining(ArrayList.java:1003) at com.fortify.cli.common.output.writer.output.standard.StandardOutputWriter.writeRecords(StandardOutputWriter.java:168) at com.fortify.cli.common.output.writer.output.standard.StandardOutputWriter.write(StandardOutputWriter.java:80) at com.fortify.cli.common.output.cli.mixin.AbstractOutputHelperMixin.write(AbstractOutputHelperMixin.java:80) at com.fortify.cli.common.output.cli.cmd.AbstractOutputCommand.run(AbstractOutputCommand.java:33) at picocli.CommandLine.executeUserObject(CommandLine.java:2103) at picocli.CommandLine$RunLast.executeUserObjectOfLastSubcommandWithSameParent(CommandLine.java:2538) at picocli.CommandLine$RunLast.handle(CommandLine.java:2530) at picocli.CommandLine$RunLast.handle(CommandLine.java:1) at picocli.CommandLine$AbstractParseResultHandler.execute(CommandLine.java:2350) at picocli.CommandLine$RunLast.execute(CommandLine.java:2494) at picocli.CommandLine.execute(CommandLine.java:2247) at com.fortify.cli.app.runner.DefaultFortifyCLIRunner.run(DefaultFortifyCLIRunner.java:48) at com.fortify.cli.app.runner.DefaultFortifyCLIRunner.run(DefaultFortifyCLIRunner.java:59) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) at java.base/java.lang.reflect.Method.invoke(Method.java:577) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:343) at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:328) at groovy.lang.MetaClassImpl.doInvokeMethod(MetaClassImpl.java:1332) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1087) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1006) at org.codehaus.groovy.runtime.InvokerHelper.invokePojoMethod(InvokerHelper.java:633) at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:624) at org.codehaus.groovy.runtime.DefaultGroovyMethods.invokeMethod(DefaultGroovyMethods.java:723) at com.fortify.cli.ftest._common.Fcli$ReflectiveRunner.run(Fcli.groovy:196) at com.fortify.cli.ftest._common.Fcli$run_closure3.doCall(Fcli.groovy:92) at com.fortify.cli.ftest._common.Fcli$run_closure3.call(Fcli.groovy) at org.codehaus.groovy.runtime.IOGroovyMethods.withCloseable(IOGroovyMethods.java:1616) at com.fortify.cli.ftest._common.Fcli._run(Fcli.groovy:91) at com.fortify.cli.ftest._common.Fcli.run(Fcli.groovy:44) at com.fortify.cli.ftest._common.Fcli.run(Fcli.groovy:67) at com.fortify.cli.ftest._common.Fcli.run(Fcli.groovy) at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321) at com.fortify.cli.ftest.core.QuerySpec.generate(QuerySpec.groovy:13) at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321) at com.fortify.cli.ftest.core.QuerySpec.$spock_feature_1_9(QuerySpec.groovy:126) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) at java.base/java.lang.reflect.Method.invoke(Method.java:577) at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:187) at org.spockframework.runtime.model.MethodInfo.lambda$new$0(MethodInfo.java:49) at org.spockframework.runtime.model.MethodInfo.invoke(MethodInfo.java:156) at org.spockframework.runtime.PlatformSpecRunner.invokeRaw(PlatformSpecRunner.java:407) at org.spockframework.runtime.PlatformSpecRunner.invoke(PlatformSpecRunner.java:390) at org.spockframework.runtime.PlatformSpecRunner.runFeatureMethod(PlatformSpecRunner.java:324) at org.spockframework.runtime.IterationNode.execute(IterationNode.java:50) at org.spockframework.runtime.SimpleFeatureNode.execute(SimpleFeatureNode.java:58) at org.spockframework.runtime.SimpleFeatureNode.execute(SimpleFeatureNode.java:15) 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.spockframework.runtime.SpockNode.sneakyInvoke(SpockNode.java:40) at org.spockframework.runtime.IterationNode.lambda$around$0(IterationNode.java:67) at org.spockframework.runtime.PlatformSpecRunner.lambda$createMethodInfoForDoRunIteration$5(PlatformSpecRunner.java:236) at org.spockframework.runtime.model.MethodInfo.invoke(MethodInfo.java:156) at org.spockframework.runtime.PlatformSpecRunner.invokeRaw(PlatformSpecRunner.java:407) at org.spockframework.runtime.PlatformSpecRunner.invoke(PlatformSpecRunner.java:390) at org.spockframework.runtime.PlatformSpecRunner.runIteration(PlatformSpecRunner.java:218) at org.spockframework.runtime.IterationNode.around(IterationNode.java:67) at org.spockframework.runtime.SimpleFeatureNode.lambda$around$0(SimpleFeatureNode.java:52) at org.spockframework.runtime.SpockNode.sneakyInvoke(SpockNode.java:40) at org.spockframework.runtime.FeatureNode.lambda$around$0(FeatureNode.java:41) at org.spockframework.runtime.PlatformSpecRunner.lambda$createMethodInfoForDoRunFeature$4(PlatformSpecRunner.java:199) at org.spockframework.runtime.model.MethodInfo.invoke(MethodInfo.java:156) at org.spockframework.runtime.PlatformSpecRunner.invokeRaw(PlatformSpecRunner.java:407) at org.spockframework.runtime.PlatformSpecRunner.invoke(PlatformSpecRunner.java:390) at org.spockframework.runtime.PlatformSpecRunner.runFeature(PlatformSpecRunner.java:192) at org.spockframework.runtime.FeatureNode.around(FeatureNode.java:41) at org.spockframework.runtime.SimpleFeatureNode.around(SimpleFeatureNode.java:52) at org.spockframework.runtime.SimpleFeatureNode.around(SimpleFeatureNode.java:15) 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.spockframework.runtime.SpockNode.sneakyInvoke(SpockNode.java:40) at org.spockframework.runtime.SpecNode.lambda$around$0(SpecNode.java:63) at org.spockframework.runtime.PlatformSpecRunner.lambda$createMethodInfoForDoRunSpec$0(PlatformSpecRunner.java:61) at org.spockframework.runtime.model.MethodInfo.invoke(MethodInfo.java:156) at org.spockframework.runtime.PlatformSpecRunner.invokeRaw(PlatformSpecRunner.java:407) at org.spockframework.runtime.PlatformSpecRunner.invoke(PlatformSpecRunner.java:390) at org.spockframework.runtime.PlatformSpecRunner.runSpec(PlatformSpecRunner.java:55) at org.spockframework.runtime.SpecNode.around(SpecNode.java:63) at org.spockframework.runtime.SpecNode.around(SpecNode.java:11) 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:95) at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:91) at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:60) at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:98) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:40) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:529) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:756) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:452) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210) Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1037E: First operand to matches operator must be a string. 'null' is not at org.springframework.expression.spel.ast.OperatorMatches.getValueInternal(OperatorMatches.java:90) at org.springframework.expression.spel.ast.OperatorMatches.getValueInternal(OperatorMatches.java:42) at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:119) at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:376) at com.fortify.cli.common.json.JsonHelper.evaluateSpelExpression(JsonHelper.java:75) at com.fortify.cli.common.output.query.QueryExpression.matches(QueryExpression.java:30) ... 123 more at com.fortify.cli.ftest._common.Fcli$FcliResult.expectSuccess(Fcli.groovy:148) at com.fortify.cli.ftest._common.Fcli.run_closure6(Fcli.groovy:65) at groovy.lang.Closure.call(Closure.java:433) at com.fortify.cli.ftest._common.Fcli.run(Fcli.groovy:45) at com.fortify.cli.ftest._common.Fcli.run(Fcli.groovy:67) at com.fortify.cli.ftest.core.QuerySpec.generate(QuerySpec.groovy:13) at com.fortify.cli.ftest.core.QuerySpec.core.query.matches(QuerySpec.groovy:126)

rsenden commented 1 year ago

Potentially we can implement and use a custom OperatorOverloader to handle this situation, overloading the matches operator to return false if any of the operands is null.

We'd need to configure this custom operator overloader on the SpEL evaluation context created in the JsonHelper::createSpelEvaluationContext method. However, we currently use a SimpleEvaluationContext, which doesn't support custom operator overloaders. So, in order to be able to use a custom overloader, we should either:

psmf22 commented 1 year ago

The OperatorOverloader does not apply to "matches" operations, there is no easy way to fix this. As a workaround guard your expressions with a null check:

If you want to include results with null values: stringValue == null || stringValue matches 'value1|value2'

To exclude results with null values: stringValue != null && stringValue matches 'value1|value2'

rsenden commented 1 year ago

As described in the previous comment, there's no easy/proper fix to handle this natively in fcli; users will need to explicitly handle null-values in their expressions if they wish to use the matches operator. Issue #382 has been opened to properly document this, and as such this issue will be closed.