Have you tried to resolve this issue yourself first?
Bug Description
Engine: sfge (Salesforce Graph Engine) · Rule: ApexFlsViolation (DevPreview) · Selector: --rule-selector sfge
The Apex null-coalescing operator ?? (API 60 / Spring '24) parses, but the vertex it produces in the sfge graph has zero children — the operands are never attached. Four different consumers then fail, depending on where the operator sits. I believe all four are one fix.
#1813 and #1967 already report one of these shapes (for (X x : list ?? new List<X>())). The other three are unreported, and one of them is much more severe than the for-loop case.
A. ?? in a condition — UnexpectedException: [] at BooleanExpressionVertex.java:25, whose check is if (children.size() != 2) throw new UnexpectedException(children). The payload prints as [] — the node has no operands at all.
if ((a ?? false) && (b ?? false)) { insert new Account(Name = 'x'); }
B. ?? as a method argument — UnexpectedException at ApexValue.java:610 via ApexMapValue.java:218, which asserts put has exactly 2 parameters. It is a 2-argument call in source, so the mis-modelled node changes the parameter count the engine sees.
Map<String, String> record = new Map<String, String>();
record.put('subField', maybeNull ?? '');
C. ?? as a map-literal value — IndexOutOfBoundsException at MapEntryNodeVertex.java:38 (getChildren().get(1) on an entry with one child).
Map<String, Object> row = new Map<String, Object>{ 'id' => '001', 'status' => status ?? 'N/A' };
D. ?? as a for-loop collection (the severe one, = #1813/#1967) — fails at graph build, so it does not abandon one entry point, it aborts the entire scan: a single Sev-1 UnexpectedEngineError and zero analysis of every class in the workspace. I confirmed this by putting a second, unrelated, perfectly analysable class alongside it — it also produced no findings.
for (Account a : aList ?? new List<Account>()) { a.Name = 'x'; }
For calibration, ?? does not crash in a plain assignment (String v = s ?? 'x';) or a return (return a ?? 'default';). It only crashes where the missing children are actually walked, which is probably why this has looked like a narrow for-loop problem.
Output / Logs
# A — ?? in a condition
UnexpectedException: null: com.salesforce.graph.vertex.SFVertexFactory.load(SFVertexFactory.java:245); ...
# the informative cause appears only in the sfge log, not in the violation:
Caused by: com.salesforce.exception.UnexpectedException: []
at com.salesforce.graph.vertex.BooleanExpressionVertex.<init>(BooleanExpressionVertex.java:25)
at com.salesforce.graph.vertex.BooleanExpressionVertex.<init>(BooleanExpressionVertex.java:18)
# B — ?? as a method argument
UnexpectedException: MethodCallExpressionVertex{fullMethodName=record.put, ... MethodName=put}:
com.salesforce.graph.symbols.apex.ApexValue.validateParameterSize(ApexValue.java:610);
com.salesforce.graph.symbols.apex.ApexMapValue.apply(ApexMapValue.java:218); ...
# C — ?? as a map-literal value
java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1
at java.base/java.util.Collections$UnmodifiableList.get(Collections.java:1310)
at com.salesforce.graph.vertex.MapEntryNodeVertex.getValue(MapEntryNodeVertex.java:38)
at com.salesforce.graph.symbols.apex.ApexMapValue.setValue(ApexMapValue.java:133)
at com.salesforce.graph.symbols.apex.ApexMapValue.<init>(ApexMapValue.java:47)
at com.salesforce.graph.symbols.apex.ApexValueBuilder.buildMap(ApexValueBuilder.java:253)
# D — ?? as a for-loop collection (aborts the whole run)
ERROR Sfge:159 - Unexpected exception while loading graph
com.salesforce.exception.UnexpectedException: vp[FirstChild->true], vp[BeginLine->4], ...
at com.salesforce.graph.build.GremlinUtil.getChildren(GremlinUtil.java:60)
at com.salesforce.graph.build.MethodPathBuilderVisitor.visitForEachStatement(MethodPathBuilderVisitor.java:235)
at com.salesforce.graph.build.MethodPathBuilderVisitor._visit(MethodPathBuilderVisitor.java:130)
at com.salesforce.graph.build.AbstractApexVertexBuilder.afterInsert(AbstractApexVertexBuilder.java:169)
at com.salesforce.graph.ops.GraphUtil.loadSourceFilesAndFolders(GraphUtil.java:170)
Steps To Reproduce
-
Create an empty SFDX project (sfdx-project.json with a single force-app package directory).
-
Add these four classes under force-app/main/default/classes/, each with a standard .cls-meta.xml (apiVersion 62.0). Put class D in a separate workspace from A-C — it aborts the whole run and will mask the other three.
// A
public with sharing class CoalesceInCondition {
@AuraEnabled
public static void run(Boolean a, Boolean b) {
if ((a ?? false) && (b ?? false)) { insert new Account(Name = 'x'); }
}
}
// B
public with sharing class CoalesceInPutArg {
@AuraEnabled
public static void run(String maybeNull) {
Map<String, String> record = new Map<String, String>();
record.put('subField', maybeNull ?? '');
insert new Account(Name = record.get('subField'));
}
}
// C
public with sharing class CoalesceInMapLiteral {
@AuraEnabled
public static void run(String status) {
Map<String, Object> row = new Map<String, Object>{
'id' => '001',
'status' => status ?? 'N/A'
};
insert new Account(Name = String.valueOf(row.get('id')));
}
}
// D — run this one in its own workspace
public with sharing class CoalesceInForLoop {
@AuraEnabled
public static void run(List<Account> aList) {
for (Account a : aList ?? new List<Account>()) { a.Name = 'x'; }
update aList;
}
}
-
Add code-analyzer.yml:
engines:
sfge:
java_thread_timeout: 900000
java_thread_count: 4
-
Run sf code-analyzer run --rule-selector sfge --workspace . --config-file code-analyzer.yml.
-
A, B and C each report an InternalExecutionError and yield no findings for that entry point. D returns a single Sev-1 UnexpectedEngineError and analyses nothing at all.
Expected Behavior
a ?? b should build a node with both operands attached, so path evaluation can treat it like the equivalent ternary (a != null ? a : b), which sfge already handles.
Failing that, an explicit unsupported-construct violation would at least make the lost coverage visible. Shape D in particular should not be able to take down an entire scan.
Operating System
macOS 26.5.2
Salesforce CLI Version
@salesforce/cli/2.147.7 darwin-arm64 node-v24.5.0
Code Analyzer Plugin (code-analyzer) Version
code-analyzer 5.15.0
Node Version
v24.5.0
Java Version
openjdk version "11.0.32" 2026-07-21
Python Version
N/A
Additional Context (Screenshots, Files, etc)
?? is idiomatic in any codebase on API 60 or later.
Shapes A-C each silently abandon one @AuraEnabled entry point — it produces no FLS findings, and nothing in the summary says coverage was lost. That is quiet in exactly the direction that matters for a security rule. In our codebase these three account for three distinct crash signatures.
Shape D is worse: one ?? in one for-loop header takes a team from a working scan to no security analysis at all. We happen not to have that shape, which is the only reason our runs produce results.
Related history: #1380, #1389 and #1608 covered ?? failing to parse. Parsing was fixed; graph modelling was not. #1240 is the same validateParameterSize throw as shape B but from a different trigger (Cache.Partition.put's 3-argument overload), closed in the 2026-06-30 pre-v5 sweep.
Workaround
Rewrite as an explicit ternary or null check:
String v = (s != null) ? s : '';
for (Account a : (aList != null ? aList : new List<Account>())) { ... }
That is a mechanical change but an unpleasant one to apply across a large codebase, and it means giving up a language feature to keep the security scanner working.
Urgency
High
Have you tried to resolve this issue yourself first?
Bug Description
Engine:
sfge(Salesforce Graph Engine) · Rule:ApexFlsViolation(DevPreview) · Selector:--rule-selector sfgeThe Apex null-coalescing operator
??(API 60 / Spring '24) parses, but the vertex it produces in the sfge graph has zero children — the operands are never attached. Four different consumers then fail, depending on where the operator sits. I believe all four are one fix.#1813 and #1967 already report one of these shapes (
for (X x : list ?? new List<X>())). The other three are unreported, and one of them is much more severe than the for-loop case.A.
??in a condition —UnexpectedException: []atBooleanExpressionVertex.java:25, whose check isif (children.size() != 2) throw new UnexpectedException(children). The payload prints as[]— the node has no operands at all.B.
??as a method argument —UnexpectedExceptionatApexValue.java:610viaApexMapValue.java:218, which assertsputhas exactly 2 parameters. It is a 2-argument call in source, so the mis-modelled node changes the parameter count the engine sees.C.
??as a map-literal value —IndexOutOfBoundsExceptionatMapEntryNodeVertex.java:38(getChildren().get(1)on an entry with one child).D.
??as a for-loop collection (the severe one, = #1813/#1967) — fails at graph build, so it does not abandon one entry point, it aborts the entire scan: a single Sev-1UnexpectedEngineErrorand zero analysis of every class in the workspace. I confirmed this by putting a second, unrelated, perfectly analysable class alongside it — it also produced no findings.For calibration,
??does not crash in a plain assignment (String v = s ?? 'x';) or a return (return a ?? 'default';). It only crashes where the missing children are actually walked, which is probably why this has looked like a narrow for-loop problem.Output / Logs
Steps To Reproduce
Create an empty SFDX project (
sfdx-project.jsonwith a singleforce-apppackage directory).Add these four classes under
force-app/main/default/classes/, each with a standard.cls-meta.xml(apiVersion 62.0). Put class D in a separate workspace from A-C — it aborts the whole run and will mask the other three.Add
code-analyzer.yml:Run
sf code-analyzer run --rule-selector sfge --workspace . --config-file code-analyzer.yml.A, B and C each report an
InternalExecutionErrorand yield no findings for that entry point. D returns a single Sev-1UnexpectedEngineErrorand analyses nothing at all.Expected Behavior
a ?? bshould build a node with both operands attached, so path evaluation can treat it like the equivalent ternary (a != null ? a : b), which sfge already handles.Failing that, an explicit unsupported-construct violation would at least make the lost coverage visible. Shape D in particular should not be able to take down an entire scan.
Operating System
macOS 26.5.2
Salesforce CLI Version
@salesforce/cli/2.147.7 darwin-arm64 node-v24.5.0
Code Analyzer Plugin (code-analyzer) Version
code-analyzer 5.15.0
Node Version
v24.5.0
Java Version
openjdk version "11.0.32" 2026-07-21
Python Version
N/A
Additional Context (Screenshots, Files, etc)
??is idiomatic in any codebase on API 60 or later.Shapes A-C each silently abandon one
@AuraEnabledentry point — it produces no FLS findings, and nothing in the summary says coverage was lost. That is quiet in exactly the direction that matters for a security rule. In our codebase these three account for three distinct crash signatures.Shape D is worse: one
??in one for-loop header takes a team from a working scan to no security analysis at all. We happen not to have that shape, which is the only reason our runs produce results.Related history: #1380, #1389 and #1608 covered
??failing to parse. Parsing was fixed; graph modelling was not. #1240 is the samevalidateParameterSizethrow as shape B but from a different trigger (Cache.Partition.put's 3-argument overload), closed in the 2026-06-30 pre-v5 sweep.Workaround
Rewrite as an explicit ternary or null check:
That is a mechanical change but an unpleasant one to apply across a large codebase, and it means giving up a language feature to keep the security scanner working.
Urgency
High