[BUG][code-analyzer] sfge: the ?? null-coalescing operator is unmodelled — four distinct crashes, one of which aborts the entire scan
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 52/100
Hướng nghiên cứu
Bắt đầu bằng cách tái hiện bốn trường hợp với các lớp SFDX được cung cấp và lệnh sfge. Theo dõi quá trình xây dựng đồ thị và xử lý phần tử con qua BooleanExpressionVertex.java, ApexValue.java, ApexMapValue.java, MapEntryNodeVertex.java và MethodPathBuilderVisitor.visitForEachStatement. Hoàn thành nghĩa là cả hai toán hạng đều khả dụng cho mỗi consumer, các trường hợp hoạt động giống dạng toán tử ba ngôi được hỗ trợ và một trường hợp không đúng định dạng không thể làm dừng toàn bộ quá trình quét.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Have you tried to resolve this issue yourself first?
- I confirm I have gone through the above steps and still have an issue to report.
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.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.// 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
InternalExecutionErrorand yield no findings for that entry point. D returns a single Sev-1UnexpectedEngineErrorand 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
- Ngôn ngữ chính
- TypeScript
- Star
- 241
- Fork
- 51
- Chỉ số merge pull request
- Không có pull request nào được merge trong 30 ngày
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của forcedotcom/code-analyzer
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
forcedotcom/code-analyzer#2094 ·
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 88/100
forcedotcom/code-analyzer#2093 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
forcedotcom/code-analyzer#2091 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
forcedotcom/code-analyzer#2090 ·
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 72/100
forcedotcom/code-analyzer#2104 ·
Tất cả issue của forcedotcom/code-analyzer
Issue tương tự
-
fix(frontend): correct VITE_IS_DEVELPOMENT typo in shared/app.ts and align dev environment flagsĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
vicharanashala/ajrasakha#1439 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Add: TV5Monde AsieĐang mởapproved check:passed streams:add
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
Maintainer thường phản hồi trong vòng 1 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
codex-team/editor.js#3037 ·
-
complexity: trivial good first issue impact: low
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 78/100
ueberdosis/tiptap#8522 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Bump anybuf version to ^1.0.0Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
wormhole-foundation/wormhole#5062 ·
Maintainer thường phản hồi trong vòng 1 ngày