Skip to content

FIX(pmd): @W-18808343@: Prevent AppExchange rules from running in an infinite loop #312

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/code-analyzer-pmd-engine/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@salesforce/code-analyzer-pmd-engine",
"description": "Plugin package that adds 'pmd' and 'cpd' as engines into Salesforce Code Analyzer",
"version": "0.26.0",
"version": "0.26.1-SNAPSHOT",
"author": "The Salesforce Code Analyzer Team",
"license": "BSD-3-Clause",
"homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview",
Expand Down Expand Up @@ -71,4 +71,4 @@
"!src/index.ts"
]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,11 @@ public class DangerousChangeProtectionMethods extends AbstractApexRule {

@Override
public Object visit(ASTMethodCallExpression node, Object data) {
if (Helper.isTestMethodOrClass(node)) {
super.visit(node, data);
Copy link
Collaborator Author

@stephen-carter-at-sf stephen-carter-at-sf Jun 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue was because we super.visit was called twice (once here and once below). Ravindra should have returned the super.visit output here instead of continuing.

It caused an issue with this one file because this one file has a name that is referenced within itself. Since super was called twice it wasn't able to use its recursion detection I believe.

}

if (node.getFullMethodName().compareToIgnoreCase(
CHANGE_PROTECTION) == 0
|| node.getFullMethodName().compareToIgnoreCase(SYSTEM_CHANGE_PROTECTION) == 0) {
if (!Helper.isTestMethodOrClass(node) && (node.getFullMethodName().compareToIgnoreCase(CHANGE_PROTECTION) == 0
|| node.getFullMethodName().compareToIgnoreCase(SYSTEM_CHANGE_PROTECTION) == 0)) {
this.handleChangeProtection(node, data);
}
super.visit(node, data);
return data;
return super.visit(node, data);
}

private void handleChangeProtection(ASTMethodCallExpression node, Object data) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import net.sourceforge.pmd.lang.rule.RuleTargetSelector;

public class DangerousPasswordMethods extends AbstractApexRule {

private static final String MOVE_PASSWORD = "movePassword";
private static final String SYSTEM_MOVE_PASSWORD = "System.movePassword";

Expand Down Expand Up @@ -74,8 +74,7 @@ public Object visit(ASTMethodCallExpression node, Object data) {
{
this.handleSetPassword(node, data);
}
super.visit(node, data);
return data;
return super.visit(node, data);
}

private void handleMovePassword(ASTMethodCallExpression node, Object data) {
Expand Down Expand Up @@ -113,7 +112,7 @@ private void handleSetPassword(ASTMethodCallExpression node, Object data) {
} else {
asCtx(data).addViolationWithMessage(node, String.format(SET_PASSWORD_VIOLATION_LOW_CONFIDENCE, userIdVarName));
}

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
public class DetectHardcodedCredentialsInHttpRequests extends DetectHardcodedCredentialsBase {
private static final List<String> HTTP_AUTH_HEADERS = SecretsInPackageUtils.AUTH_FIELD_MAPPINGS_LIST;
private static final List<String> HTTP_AUTH_HEADER_VALUES_TO_IGNORE = Arrays.asList(SecretsInPackageUtils.STRINGS_TO_IGNORE);

private static final String HARD_CODED_SECRET_IN_HTTP_REQUEST_HEADER_VIOLATION = "Potentially hardcoded secret found in HTTP request header";
private static final String MERGE_FIELD_LITERAL="{!$Credential.";

Expand Down Expand Up @@ -49,7 +49,7 @@ public Object visit(ASTMethodCallExpression node, Object data) {

Node firstChild = childRefExpr.getNextSibling();
Node secondChild = firstChild.getNextSibling();

List<ASTLiteralExpression> firstArgLiterals = firstChild.descendantsOrSelf()
.filterIs(ASTLiteralExpression.class).toList();
List<ASTVariableExpression> firstArgVars =
Expand Down Expand Up @@ -99,8 +99,7 @@ public Object visit(ASTMethodCallExpression node, Object data) {
break;
}
}
super.visit(node, data);
return data;
return super.visit(node, data);
}

private boolean isAMergeField(String value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ public Object visit(ASTMethodCallExpression node, Object data) {
|| node.getFullMethodName().compareToIgnoreCase(SYSTEM_SET_PASSWORD) == 0) {
this.handleSetPassword(node, data);
}
super.visit(node, data);
return data;
return super.visit(node, data);
}

private void handleSetPassword(ASTMethodCallExpression node, Object data) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import net.sourceforge.pmd.reporting.RuleContext;

public class DetectCreateElementScriptTag extends AbstractVfRule {

private static ArrayList<Pattern> CREATE_ELEMENT_PATTERNS = new ArrayList<Pattern>();
private static final String[] CREATE_ELEMENT_REGEXES = {
"createElement.*['\"]script",
Expand All @@ -35,16 +35,15 @@ public void start(RuleContext ctx) {
public Object visit(ASTHtmlScript node, Object data) {
Chars c = node.getText();
searchForPatterns(node,data,c);
super.visit(node, data);
return data;
return super.visit(node, data);
}

private void searchForPatterns(ASTHtmlScript node, Object data, Chars c) {
String staticJS = c.toString();

for (Pattern nextPattern: CREATE_ELEMENT_PATTERNS) {
Matcher match = nextPattern.matcher(staticJS);
if (match.find()) {
if (match.find()) {
this.asCtx(data)
.addViolationWithMessage(node,
CREATE_ELEMENT_SCRIPT_CSS_VIOLATION);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ public Object visit(ASTElement node, Object data) {
} else if (APEX_ATTRIBUTE.equalsIgnoreCase(node.getName())) {
processASTAttribute(node,data);
}
super.visit(node, data);
return data;
return super.visit(node, data);
}

private void processComponentInclusion(ASTElement node,Object data) {
List<ASTAttribute> allAttributes = node.children(ASTAttribute.class).toList();

for (ASTAttribute nextAttr: allAttributes) {
String attrName = nextAttr.getName();
if (SecretsInPackageUtils.isAPotentialSecret(attrName)) {
Expand Down
16 changes: 16 additions & 0 deletions packages/code-analyzer-pmd-engine/test/pmd-engine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,22 @@ describe('Tests for the runRules method of PmdEngine', () => {
expect(errorLogEvents).toHaveLength(1);
expect(errorLogEvents[0].message).toContain('Message was null');
});

it('When running a file that has a method that references a class name that is the same as itself, then the AppExchange rules should not run in infinite loop', async () => {
// This test is to ensure that we have resolved the issue associated with: W-18808343
const engine: PmdEngine = new PmdEngine(DEFAULT_PMD_ENGINE_CONFIG);

// Get AppExchange rules
const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions());
const appExchangeRuleNames: string[] = ruleDescriptions.filter(r => r.tags.includes('AppExchange'))
.map(r => r.name);
expect(appExchangeRuleNames.length).toBeGreaterThan(0); // sanity check

const workspace: Workspace = new Workspace('id', [path.join(TEST_DATA_FOLDER, 'samplePmdWorkspaceFor_W-18808343')]);

const results: EngineRunResults = await engine.runRules(appExchangeRuleNames, createRunOptions(workspace)); // This should not run forever and ever.
expect(results.violations.length).toEqual(0);
});
});

describe('Tests for the getEngineVersion method of PmdEngine', () => {
Expand Down
Loading