forcedotcom / forcedotcom/code-analyzer
[BUG][code-analyzer] sfge: indexing a value the engine cannot resolve aborts the entry point instead of degrading
- Lenguaje dominante
- TypeScript
- Estrellas
- 240
- Forks
- 52
- Merge medio
- 1 d 23 h
- PR fusionados (30 d)
- 5
Descripción
### Have you tried to resolve this issue yourself first?
- [x] 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`
An array/index load whose target resolves to an indeterminate `ApexSingleValue` aborts path evaluation — in a method that already returns an indeterminate value for every other input.
```apex
List parts = (List) byKey.get('k'); // byKey is Map
String first = parts[0];
```
`ApexMapValue.apply` takes the indeterminate branch of `METHOD_GET` (`ApexMapValue.java:183-191`) and returns a bare indeterminate value typed from the map's declared value type. Because that type is `Object`, `build()` yields an `ApexSingleValue` rather than an `ApexListValue`, and indexing it hits the unhandled `else` at `PathScopeVisitor.java:894`. The Apex cast to `List` does not help — the engine keeps the map's declared value type. `JSONDeserializeFactory.java:78-79` builds the same shape for types it declines to model, so this is not the only route in.
**The fix already exists in the same method:**
```java
// PathScopeVisitor.java:874-902
private ApexValue getIndeterminantArrayLoadValue(@Nullable ApexValue apexValue) {
final Typeable listType;
if (apexValue instanceof ApexListValue) { ...
} else if (apexValue instanceof ApexForLoopValue) { ...
} else if (apexValue instanceof ApexSoqlValue) { ...
} else if (apexValue == null) {
listType = null; // <-- null is fine
} else {
throw new UnexpectedException(apexValue); // <-- everything else aborts
}
ApexValueBuilder builder = ApexValueBuilder.get(this).withStatus(ValueStatus.INDETERMINANT);
if (listType != null) {
return builder.declarationVertex(listType).build();
} else {
return builder.buildUnknownType(); // <-- the null branch lands here
}
}
```
A `null` `apexValue` — strictly *less* information than an indeterminate `ApexSingleValue` — falls through to `buildUnknownType()`.
### Output / Logs
```shell
UnexpectedException: ApexValue(ApexSingleValue) {status=INDETERMINANT, declarationVertex=SyntheticTypedVertex@...,
valueVertex=null, returnedFrom=ApexValue(ApexMapValue) {status=INDETERMINANT,
declarationVertex=Parameter{... Type=Map ... Name=byKey}} ...}
at com.salesforce.graph.symbols.PathScopeVisitor.getIndeterminantArrayLoadValue(PathScopeVisitor.java:894)
at com.salesforce.graph.symbols.PathScopeVisitor.afterVisit(PathScopeVisitor.java:810)
at com.salesforce.graph.symbols.DefaultSymbolProviderVertexVisitor.afterVisit(DefaultSymbolProviderVertexVisitor.java:737)
at com.salesforce.graph.vertex.ArrayLoadExpressionVertex.afterVisit(ArrayLoadExpressionVertex.java:58)
```
### Steps To Reproduce
1. Create an empty SFDX project (`sfdx-project.json` with a single `force-app` package directory).
2. Add `force-app/main/default/classes/IndeterminateMapGetIndex.cls` with the class shown below, plus a standard `IndeterminateMapGetIndex.cls-meta.xml` (apiVersion 62.0).
3. Add `code-analyzer.yml`:
```yaml
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. The run reports an `InternalExecutionError` for the entry point instead of analysing it. That entry point yields no `ApexFlsViolation` findings at all, and nothing in the summary indicates coverage was lost.
```apex
public with sharing class IndeterminateMapGetIndex {
@AuraEnabled
public static void run(Map byKey) {
List parts = (List) byKey.get('k');
String first = parts[0];
Account a = new Account();
a.Name = first;
insert a;
}
}
```
### Expected Behavior
Routing the `else` to `listType = null` would make the unknown case behave like the already-unknown case, which is what the method's name and Javadoc promise ("Generates a single indeterminant value based on the type contained in `apexValue`").
The caller has the same gap: `PathScopeVisitor.java:804-806` carries a matching bare `throw new UnexpectedException(vertex)` on its own `else`.
### 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)
`Map` is the standard way to accept a loosely typed payload in an `@AuraEnabled` method, and indexing a list pulled out of one is routine.
Costs three `@AuraEnabled` entry points in our codebase via two different routes — the map shape above, and a static initializer in a shared constants class that indexes a `String.split()` result. The second is the worse pattern: because it sits in a constants class, every entry point that touches it inherits the abort.
### Workaround
Assign through an intermediate typed local that sfge can resolve, or avoid indexing values pulled out of an `Object`-valued map on paths that reach DML. Neither is dependable.
### Urgency
Moderate
Guía de contribución
Línea de trabajo
Comienza con PathScopeVisitor.java alrededor de las líneas 804-806 y 874-902; después, inspecciona ApexMapValue.java:183-191 y JSONDeserializeFactory.java:78-79 en busca de otros valores indeterminados. Reproduce el problema con el comando sf code-analyzer y la clase Apex proporcionados. La tarea está terminada cuando el punto de entrada completa el análisis sin InternalExecutionError al indexar un valor indeterminado, conservando los hallazgos y la cobertura esperados.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- java
- Área
- security, tooling
- Tipo de issue
- Error
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Estado de actividad
- Activo
- Claridad
- Bien especificado
- Aptitud para principiantes
- 72/100