forcedotcom / forcedotcom/code-analyzer
[BUG][code-analyzer] sfge: indexing a value the engine cannot resolve aborts the entry point instead of degrading
- Langage dominant
- TypeScript
- Étoiles
- 240
- Forks
- 52
- Merge moyen
- 1 j 23 h
- PR mergées (30 j)
- 5
Description
### 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
Guide de contribution
Ouvrir le guide de contribution
Piste de recherche
Commencez par PathScopeVisitor.java autour des lignes 804-806 et 874-902, puis examinez ApexMapValue.java:183-191 et JSONDeserializeFactory.java:78-79 à la recherche d’autres valeurs indéterminées. Reproduisez le problème avec la commande sf code-analyzer et la classe Apex fournies. La tâche est terminée lorsque le point d’entrée termine l’analyse sans InternalExecutionError lorsqu’une valeur indéterminée est indexée, tout en conservant les résultats et la couverture attendus.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- java
- Domaine
- security, tooling
- Type d'issue
- Bug
- Difficulté
- 3/5
- Temps estimé
- 1-2 jours
- Activité
- Active
- Clarté
- Clairement spécifiée
- Accessibilité débutants
- 72/100