Skip to content

Commit 70e0626

Browse files
cirrasfourls
authored andcommitted
Implement best-effort resolution of arguments to unresolved invocables
1 parent 347508b commit 70e0626

File tree

7 files changed

+122
-39
lines changed

7 files changed

+122
-39
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3636
- Detect tab-indented multiline strings in `TabulationCharacter`.
3737
- Improve support for evaluating name references in compiler directive expressions.
3838
- Improve overload resolution in cases involving generic type parameter constraints.
39-
- Improve handling for MSBuild properties, items, and conditional evaluation.
39+
- Improve handling for MSBuild properties, items, and conditional evaluation.
40+
- Perform best-effort name resolution of arguments to unresolved routines or array properties, which
41+
improves analysis quality in cases where symbol information is incomplete.
4042

4143
### Deprecated
4244

delphi-checks/src/main/java/au/com/integradev/delphi/checks/VariableInitializationCheck.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ private void handleNestedRoutineInvocations(StatementNode statement, DelphiCheck
194194

195195
private void searchForInitializationsByOutArgument(StatementNode statement) {
196196
findNameReferences(statement).stream()
197-
.filter(VariableInitializationCheck::isOutArgument)
197+
.filter(VariableInitializationCheck::isPotentiallyInitializingArgument)
198198
.map(this::getReferredInitializationState)
199199
.filter(Objects::nonNull)
200200
.forEach(InitializationState::assignedTo);
@@ -275,7 +275,7 @@ private void handleAssignment(StatementNode statement) {
275275
}
276276

277277
for (NameReferenceNode name : findNameReferences(assignmentStatement.getValue())) {
278-
if (name.getNameDeclaration() == declaration && !isOutArgument(name)) {
278+
if (name.getNameDeclaration() == declaration && !isPotentiallyInitializingArgument(name)) {
279279
return;
280280
}
281281
}
@@ -294,7 +294,7 @@ private void handleAssignment(StatementNode statement) {
294294
}
295295
}
296296

297-
private static boolean isOutArgument(NameReferenceNode name) {
297+
private static boolean isPotentiallyInitializingArgument(NameReferenceNode name) {
298298
ArgumentNode argument = getArgument(name);
299299
if (argument == null) {
300300
return false;
@@ -303,7 +303,10 @@ private static boolean isOutArgument(NameReferenceNode name) {
303303
ArgumentListNode argumentList = (ArgumentListNode) argument.getParent();
304304
ProceduralType procedural = getInvokedProcedural(argumentList);
305305
if (procedural == null) {
306-
return false;
306+
// If we can't find the procedural, it might mean:
307+
// - an unresolved procedural reference (potentially initializing)
308+
// - an explicit array constructor (not initializing)
309+
return !isExplicitArrayConstructor(argumentList);
307310
}
308311

309312
int argumentIndex = argumentList.getArgumentNodes().indexOf(argument);
@@ -400,6 +403,22 @@ private static ProceduralType getInvokedProcedural(ArgumentListNode argumentList
400403
return (ProceduralType) type;
401404
}
402405

406+
private static boolean isExplicitArrayConstructor(ArgumentListNode argumentList) {
407+
Node previous = argumentList.getParent().getChild(argumentList.getChildIndex() - 1);
408+
if (previous instanceof NameReferenceNode) {
409+
NameReferenceNode name = ((NameReferenceNode) previous).getLastName();
410+
NameReferenceNode prevName = name.prevName();
411+
if (prevName != null) {
412+
NameDeclaration arrayDeclaration = prevName.getNameDeclaration();
413+
if (arrayDeclaration instanceof TypeNameDeclaration) {
414+
return ((TypeNameDeclaration) arrayDeclaration).getType().isDynamicArray()
415+
&& name.simpleName().equalsIgnoreCase("Create");
416+
}
417+
}
418+
}
419+
return false;
420+
}
421+
403422
private static RoutineNameDeclaration getRoutineDeclaration(ArgumentListNode argumentList) {
404423
Node previous = argumentList.getParent().getChild(argumentList.getChildIndex() - 1);
405424
if (!(previous instanceof NameReferenceNode)) {

delphi-checks/src/test/java/au/com/integradev/delphi/checks/VariableInitializationCheckTest.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -720,22 +720,22 @@ void testPassedToProcVarOutParameterShouldNotAddIssue() {
720720
}
721721

722722
@Test
723-
void testFailOnUpgradeVarPassedToArrayProcVarOutParameterShouldAddIssue() {
723+
void testFailOnUpgradePassedToArrayElementProceduralShouldNotAddIssue() {
724724
CheckVerifier.newVerifier()
725725
.withCheck(new VariableInitializationCheck())
726726
.onFile(
727727
new DelphiTestUnitBuilder()
728728
.appendDecl("type")
729-
.appendDecl(" TTestProc = reference to procedure(out Int: Integer);")
729+
.appendDecl(" TTestProc = reference to procedure(Int: Integer);")
730730
.appendDecl("procedure Foo(Int: Integer);")
731731
.appendImpl("procedure Test(ProcArray: array of TTestProc);")
732732
.appendImpl("var")
733733
.appendImpl(" I: Integer;")
734734
.appendImpl("begin")
735-
.appendImpl(" ProcArray[0](I); // Noncompliant")
735+
.appendImpl(" ProcArray[0](I);")
736736
.appendImpl(" Foo(I);")
737737
.appendImpl("end;"))
738-
.verifyIssues();
738+
.verifyNoIssues();
739739
}
740740

741741
@Test
@@ -947,6 +947,24 @@ void testUnknownRoutineArgumentShouldNotAddIssue() {
947947
.verifyNoIssues();
948948
}
949949

950+
@Test
951+
void testUninitializedArgumentToExplicitArrayConstructorShouldAddIssue() {
952+
CheckVerifier.newVerifier()
953+
.withCheck(new VariableInitializationCheck())
954+
.onFile(
955+
new DelphiTestUnitBuilder()
956+
.appendImpl("procedure Test;")
957+
.appendImpl("type")
958+
.appendImpl(" TBar = array of Integer;")
959+
.appendImpl("var")
960+
.appendImpl(" Foo: Integer;")
961+
.appendImpl(" Bar: TBar;")
962+
.appendImpl("begin")
963+
.appendImpl(" Bar := TBar.Create(Foo); // Noncompliant")
964+
.appendImpl("end;"))
965+
.verifyIssues();
966+
}
967+
950968
@Test
951969
void testUninitializedObjectWithFreeAndNilShouldAddIssue() {
952970
CheckVerifier.newVerifier()

delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/InvocationArgument.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.sonar.plugins.communitydelphi.api.ast.utils.ExpressionNodeUtils;
3030
import org.sonar.plugins.communitydelphi.api.type.Type;
3131
import org.sonar.plugins.communitydelphi.api.type.Type.ProceduralType;
32+
import org.sonar.plugins.communitydelphi.api.type.TypeFactory;
3233
import org.sonar.plugins.communitydelphi.api.type.Typed;
3334

3435
public class InvocationArgument implements Typed {
@@ -55,7 +56,7 @@ public class InvocationArgument implements Typed {
5556
void resolve(Type parameterType) {
5657
if (resolver != null) {
5758
if (isRoutineReference(parameterType)) {
58-
disambiguateRoutineReference(resolver, parameterType);
59+
resolver.disambiguateRoutineReference((ProceduralType) parameterType);
5960
} else if (!resolver.isExplicitInvocation()) {
6061
resolver.disambiguateImplicitEmptyArgumentList();
6162
}
@@ -92,13 +93,12 @@ Type findRoutineReferenceType(Type parameterType) {
9293
Preconditions.checkNotNull(resolver);
9394

9495
NameResolver clone = new NameResolver(resolver);
95-
disambiguateRoutineReference(clone, parameterType);
96-
return clone.getApproximateType();
97-
}
96+
clone.disambiguateRoutineReference((ProceduralType) parameterType);
97+
if (!clone.isAmbiguous()) {
98+
return clone.getApproximateType();
99+
}
98100

99-
private static void disambiguateRoutineReference(NameResolver resolver, Type parameterType) {
100-
resolver.disambiguateRoutineReference((ProceduralType) parameterType);
101-
resolver.checkAmbiguity();
101+
return TypeFactory.unknownType();
102102
}
103103

104104
@Override

delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import static org.sonar.plugins.communitydelphi.api.type.TypeFactory.voidType;
2626

2727
import au.com.integradev.delphi.antlr.ast.node.ArrayAccessorNodeImpl;
28-
import au.com.integradev.delphi.antlr.ast.node.DelphiNodeImpl;
2928
import au.com.integradev.delphi.antlr.ast.node.NameReferenceNodeImpl;
3029
import au.com.integradev.delphi.symbol.Search;
3130
import au.com.integradev.delphi.symbol.SearchMode;
@@ -200,8 +199,12 @@ NameDeclaration addResolvedDeclaration() {
200199
return resolved;
201200
}
202201

203-
public void checkAmbiguity() {
204-
if (declarations.size() > 1) {
202+
public boolean isAmbiguous() {
203+
return declarations.size() > 1;
204+
}
205+
206+
private void checkAmbiguity() {
207+
if (isAmbiguous()) {
205208
if (LOG.isWarnEnabled()) {
206209
LOG.warn(
207210
"Ambiguous declarations could not be resolved\n[Occurrence] {}\n{}",
@@ -288,19 +291,11 @@ void readPrimaryExpression(PrimaryExpressionNode node) {
288291
}
289292

290293
for (DelphiNode child : node.getChildren()) {
291-
if (!readPrimaryExpressionPart(child)) {
292-
break;
293-
}
294+
readPrimaryExpressionPart(child);
294295
}
295296
}
296297

297-
/**
298-
* Reads part of a primary expression
299-
*
300-
* @param node part of a primary expression
301-
* @return false if a name resolution failure occurs
302-
*/
303-
private boolean readPrimaryExpressionPart(Node node) {
298+
private void readPrimaryExpressionPart(Node node) {
304299
if (node instanceof NameReferenceNode) {
305300
readNameReference((NameReferenceNode) node);
306301
} else if (node instanceof ArgumentListNode) {
@@ -314,11 +309,13 @@ private boolean readPrimaryExpressionPart(Node node) {
314309
} else {
315310
handlePrimaryExpressionToken(node);
316311
}
317-
318-
return !nameResolutionFailed();
319312
}
320313

321314
private void handlePrimaryExpressionToken(Node node) {
315+
if (nameResolutionFailed()) {
316+
return;
317+
}
318+
322319
switch (node.getTokenType()) {
323320
case DEREFERENCE:
324321
Type dereferenced = TypeUtils.dereference(getApproximateType());
@@ -389,9 +386,7 @@ private boolean handleInheritedExpression(PrimaryExpressionNode node) {
389386
int nextChild = ExpressionNodeUtils.isBareInherited(node) ? 1 : 2;
390387

391388
for (int i = nextChild; i < node.getChildren().size(); ++i) {
392-
if (!readPrimaryExpressionPart(node.getChild(i))) {
393-
break;
394-
}
389+
readPrimaryExpressionPart(node.getChild(i));
395390
}
396391

397392
return true;
@@ -520,8 +515,7 @@ private void handleTypeParameterReferences(List<TypeReferenceNode> typeReference
520515
occurrence.setNameDeclaration(declaration);
521516
((NameReferenceNodeImpl) typeReference).setNameOccurrence(occurrence);
522517

523-
NameResolver resolver =
524-
new NameResolver(((DelphiNodeImpl) typeReference).getTypeFactory(), searchMode);
518+
NameResolver resolver = new NameResolver(typeFactory, searchMode);
525519
resolver.resolvedDeclarations.add(declaration);
526520
resolver.names.add(occurrence);
527521
resolver.addToSymbolTable();
@@ -540,8 +534,7 @@ private void handleTypeParameterForwardReferences(
540534
occurrence.setNameDeclaration(declaration);
541535
((NameReferenceNodeImpl) typeReference).setNameOccurrence(occurrence);
542536

543-
NameResolver resolver =
544-
new NameResolver(((DelphiNodeImpl) typeNode).getTypeFactory(), searchMode);
537+
NameResolver resolver = new NameResolver(typeFactory, searchMode);
545538
resolver.resolvedDeclarations.add(declaration);
546539
resolver.names.add(occurrence);
547540
resolver.addToSymbolTable();
@@ -576,6 +569,10 @@ void readAttribute(AttributeNode node) {
576569
}
577570

578571
private void readNameReference(NameReferenceNode node, boolean inAttribute) {
572+
if (nameResolutionFailed()) {
573+
return;
574+
}
575+
579576
boolean couldBeUnitNameReference =
580577
currentScope == null
581578
|| (!(currentScope instanceof UnknownScope) && currentScope.equals(node.getScope()));
@@ -709,7 +706,7 @@ private boolean isTypeParameterConstructorInvocation(ArgumentListNode argumentLi
709706
}
710707

711708
private void readPossibleUnitNameReference(NameReferenceNode node, boolean inAttribute) {
712-
NameResolver unitNameResolver = new NameResolver(((DelphiNodeImpl) node).getTypeFactory());
709+
NameResolver unitNameResolver = new NameResolver(typeFactory);
713710
if (unitNameResolver.readUnitNameReference(node, inAttribute)) {
714711
this.currentType = unknownType();
715712
this.names.clear();
@@ -781,6 +778,11 @@ private boolean matchReferenceToUnitNameDeclaration(
781778
}
782779

783780
private void handleArrayAccessor(ArrayAccessorNode accessor) {
781+
if (nameResolutionFailed()) {
782+
resolveArgumentsBestEffort(accessor.getExpressions());
783+
return;
784+
}
785+
784786
Type type = TypeUtils.findBaseType(getApproximateType());
785787
if (type.isPointer()) {
786788
Type dereferenced = TypeUtils.dereference(type);
@@ -921,6 +923,14 @@ private void handleArgumentList(ArgumentListNode node) {
921923
}
922924
}
923925

926+
if (nameResolutionFailed()) {
927+
resolveArgumentsBestEffort(
928+
node.getArgumentNodes().stream()
929+
.map(ArgumentNode::getExpression)
930+
.collect(Collectors.toUnmodifiableList()));
931+
return;
932+
}
933+
924934
if (handleExplicitArrayConstructorInvocation(node)
925935
|| isTypeParameterConstructorInvocation(node)) {
926936
return;
@@ -1008,6 +1018,20 @@ private boolean handleProcVarInvocation(List<ExpressionNode> argumentExpressions
10081018
return true;
10091019
}
10101020

1021+
private void resolveArgumentsBestEffort(List<ExpressionNode> argumentExpressions) {
1022+
for (ExpressionNode expression : argumentExpressions) {
1023+
getNameResolutionHelper().resolveSubExpressions(expression);
1024+
if (expression instanceof PrimaryExpressionNode) {
1025+
NameResolver resolver = new NameResolver(typeFactory, searchMode);
1026+
resolver.readPrimaryExpression((PrimaryExpressionNode) expression);
1027+
if (resolver.isAmbiguous()) {
1028+
resolver.declarations.clear();
1029+
}
1030+
resolver.addToSymbolTable();
1031+
}
1032+
}
1033+
}
1034+
10111035
private void disambiguateArguments(List<ExpressionNode> argumentExpressions, boolean explicit) {
10121036
if (handleHardTypeCast(argumentExpressions) || handleProcVarInvocation(argumentExpressions)) {
10131037
return;

delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,12 @@ void testLabels() {
426426
verifyUsages(29, 6, reference(31, 7), reference(32, 2));
427427
}
428428

429+
@Test
430+
void testBestEffortArguments() {
431+
execute("BestEffortArguments.pas");
432+
verifyUsages(9, 2, reference(11, 6), reference(11, 11));
433+
}
434+
429435
@Test
430436
void testClassReferenceMethodResolution() {
431437
execute("classReferences/MethodResolution.pas");
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
unit BestEffortArguments;
2+
3+
interface
4+
5+
implementation
6+
7+
procedure Foo;
8+
var
9+
Bar: Integer;
10+
begin
11+
Baz(Bar)[Bar];
12+
end;
13+
14+
end.

0 commit comments

Comments
 (0)