Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

[BUG][code-analyzer] sfge: the ?? null-coalescing operator is unmodelled — four distinct crashes, one of which aborts the entire scan

未关闭
#2,088 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
52/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
活跃
技术栈
java
领域
devtools, security

调研方向

首先,使用提供的 SFDX 类和 sfge 命令复现这四种情况。通过 BooleanExpressionVertex.java、ApexValue.java、ApexMapValue.java、MapEntryNodeVertex.java 和 MethodPathBuilderVisitor.visitForEachStatement 跟踪图的构建和子节点处理。完成的标准是:每个消费者都能获得两个操作数,这些情况的行为与受支持的三元形式一致,并且格式错误的情况不能中止整个扫描。

由索引模型根据 Issue 内容生成。

描述

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
  1. Create an empty SFDX project (sfdx-project.json with a single force-app package directory).

  2. 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;
        }
    }
    
  3. Add code-analyzer.yml:

    engines:
      sfge:
        java_thread_timeout: 900000
        java_thread_count: 4
    
  4. Run sf code-analyzer run --rule-selector sfge --workspace . --config-file code-analyzer.yml.

  5. 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

主要语言
TypeScript
星标
241
派生
51
PR 合并指标
30 天内没有已合并 PR

环境准备

  • 没有 Dockerfile 或 Docker Compose 文件
  • 没有 Pull Request 模板
  • 阅读贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

forcedotcom/code-analyzer 的其他 Issue

查看 forcedotcom/code-analyzer 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。