diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e95ad093..c6211c9e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Support for the `AtomicCmpExchange128` intrinsic routine. +- Support for the `GetTypeKind` intrinsic routine. +- Support for the `OpenString` intrinsic type. - **API:** `TypeParameterNode::getTypeParameters` method. - **API:** `InterfaceTypeNode::getGuidExpression` method. - **API:** `AttributeNode::getExpression` method. @@ -16,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Issue locations no longer span the entire routine declaration in `RoutineName`. +- Improve type modeling around the `VarArg*` intrinsic routines. ### Deprecated @@ -31,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Type resolution failures on `as` casts where the type is returned from a routine invocation. - Inaccurate type resolution when calling a constructor on a class reference type. - Grammar ambiguity causing attributes to be misinterpreted as interface GUIDs. +- Failure to resolve invocations of `System.IsManagedType` where a value is passed. ## [1.16.0] - 2025-05-09 diff --git a/delphi-checks-testkit/src/main/java/au/com/integradev/delphi/checks/verifier/CheckVerifierImpl.java b/delphi-checks-testkit/src/main/java/au/com/integradev/delphi/checks/verifier/CheckVerifierImpl.java index 3a02f3f63..9271a391a 100644 --- a/delphi-checks-testkit/src/main/java/au/com/integradev/delphi/checks/verifier/CheckVerifierImpl.java +++ b/delphi-checks-testkit/src/main/java/au/com/integradev/delphi/checks/verifier/CheckVerifierImpl.java @@ -790,6 +790,15 @@ private Path createStandardLibrary() { + " TCustomAttribute = class(TObject)\n" + " end;\n" + "\n" + + " TVarArgList = record\n" + + " end;\n" + + "\n" + + " TTypeKind = (\n" + + " tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat, tkString, tkSet,\n" + + " tkClass, tkMethod, tkWChar, tkLString, tkWString, tkVariant, tkArray,\n" + + " tkClassRef, tkPointer, tkProcedure, tkMRecord\n" + + " );\n" + + "\n" + "implementation\n" + "\n" + "end."); diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/SymbolTableVisitor.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/SymbolTableVisitor.java index 1cacad269..32e639fe7 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/SymbolTableVisitor.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/SymbolTableVisitor.java @@ -54,6 +54,7 @@ import au.com.integradev.delphi.type.TypeUtils; import au.com.integradev.delphi.type.factory.ClassReferenceTypeImpl; import au.com.integradev.delphi.type.factory.PointerTypeImpl; +import au.com.integradev.delphi.type.intrinsic.IntrinsicsInjector; import com.google.common.base.Preconditions; import com.google.common.collect.Iterables; import java.nio.file.Path; @@ -269,6 +270,22 @@ public Data visit(DelphiAst node, Data data) { return super.visit(node, data); } + @Override + public Data visit(InterfaceSectionNode node, Data data) { + DelphiScope scope = Objects.requireNonNull(data.getUnitDeclaration()).getScope(); + + if (scope instanceof SystemScope) { + IntrinsicsInjector injector = new IntrinsicsInjector(data.typeFactory); + injector.injectTypes(scope); + injector.injectConstants(scope); + super.visit(node, data); + injector.injectRoutines(scope); + return data; + } + + return super.visit(node, data); + } + @Override public Data visit(ImplementationSectionNode node, Data data) { return data; diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/scope/SystemScopeImpl.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/scope/SystemScopeImpl.java index 7c50c322b..294becb33 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/scope/SystemScopeImpl.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/scope/SystemScopeImpl.java @@ -18,7 +18,6 @@ */ package au.com.integradev.delphi.symbol.scope; -import au.com.integradev.delphi.type.intrinsic.IntrinsicsInjector; import org.sonar.plugins.communitydelphi.api.symbol.declaration.NameDeclaration; import org.sonar.plugins.communitydelphi.api.symbol.declaration.TypeNameDeclaration; import org.sonar.plugins.communitydelphi.api.symbol.scope.SystemScope; @@ -32,12 +31,6 @@ public class SystemScopeImpl extends FileScopeImpl implements SystemScope { public SystemScopeImpl(TypeFactory typeFactory) { super("System"); - injectIntrinsics(typeFactory); - } - - private void injectIntrinsics(TypeFactory typeFactory) { - IntrinsicsInjector injector = new IntrinsicsInjector(typeFactory); - injector.inject(this); } @Override diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/type/factory/TypeFactoryImpl.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/type/factory/TypeFactoryImpl.java index 74f17b2b1..a1fcd9692 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/type/factory/TypeFactoryImpl.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/type/factory/TypeFactoryImpl.java @@ -290,6 +290,8 @@ private void createIntrinsicTypes() { addString(IntrinsicType.UNICODESTRING, pointerSize(), IntrinsicType.WIDECHAR); addString(IntrinsicType.SHORTSTRING, 256, IntrinsicType.ANSICHAR); + addWeakAlias(IntrinsicType.OPENSTRING, IntrinsicType.SHORTSTRING); + if (isStringUnicode()) { addWeakAlias(IntrinsicType.STRING, IntrinsicType.UNICODESTRING); addWeakAlias(IntrinsicType.CHAR, IntrinsicType.WIDECHAR); diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/type/intrinsic/IntrinsicConstant.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/type/intrinsic/IntrinsicConstant.java new file mode 100644 index 000000000..293b4d4f4 --- /dev/null +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/type/intrinsic/IntrinsicConstant.java @@ -0,0 +1,39 @@ +/* + * Sonar Delphi Plugin + * Copyright (C) 2025 Integrated Application Development + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package au.com.integradev.delphi.type.intrinsic; + +import org.sonar.plugins.communitydelphi.api.type.IntrinsicType; + +final class IntrinsicConstant { + private final String name; + private final IntrinsicType type; + + public IntrinsicConstant(String name, IntrinsicType type) { + this.name = name; + this.type = type; + } + + public String getName() { + return name; + } + + public IntrinsicType getType() { + return type; + } +} diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/type/intrinsic/IntrinsicRoutine.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/type/intrinsic/IntrinsicRoutine.java index b24cbc1d8..74698352c 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/type/intrinsic/IntrinsicRoutine.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/type/intrinsic/IntrinsicRoutine.java @@ -25,6 +25,8 @@ import org.sonar.plugins.communitydelphi.api.symbol.Qualifiable; import org.sonar.plugins.communitydelphi.api.symbol.QualifiedName; import org.sonar.plugins.communitydelphi.api.symbol.declaration.RoutineKind; +import org.sonar.plugins.communitydelphi.api.symbol.declaration.TypedDeclaration; +import org.sonar.plugins.communitydelphi.api.symbol.scope.DelphiScope; import org.sonar.plugins.communitydelphi.api.type.Type; import org.sonar.plugins.communitydelphi.api.type.TypeFactory; @@ -123,8 +125,9 @@ Builder hasDefaultValue(boolean hasDefaultValue) { return this; } - IntrinsicParameterData build() { - return new IntrinsicParameterData(type, isOut, isVar, isConst, hasDefaultValue); + IntrinsicParameterData build(DelphiScope scope) { + Type resolvedType = resolveType(type, scope); + return new IntrinsicParameterData(resolvedType, isOut, isVar, isConst, hasDefaultValue); } } } @@ -185,26 +188,43 @@ Builder returns(Type returnType) { return this; } - IntrinsicRoutine build() { + IntrinsicRoutine build(DelphiScope scope) { return new IntrinsicRoutine( - routineName, buildParameters(), returnType, variadicParameter != null); + routineName, buildParameters(scope), buildReturnType(scope), variadicParameter != null); } - private List buildParameters() { + private List buildParameters(DelphiScope scope) { List result = new ArrayList<>(); for (int i = 0; i < parameters.size(); ++i) { IntrinsicParameterData.Builder paramBuilder = parameters.get(i); paramBuilder.hasDefaultValue(requiredParameters != -1 && i >= requiredParameters); - result.add(paramBuilder.build()); + result.add(paramBuilder.build(scope)); } if (variadicParameter != null) { variadicParameter.hasDefaultValue(true); - result.add(variadicParameter.build()); + result.add(variadicParameter.build(scope)); } return result; } + + private Type buildReturnType(DelphiScope scope) { + return resolveType(returnType, scope); + } + } + + private static Type resolveType(Type type, DelphiScope scope) { + if (type.isUnresolved()) { + String simpleName = type.getImage(); + type = + scope.getTypeDeclarations().stream() + .filter(declaration -> declaration.getName().equalsIgnoreCase(simpleName)) + .map(TypedDeclaration::getType) + .findFirst() + .orElse(type); + } + return type; } } diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/type/intrinsic/IntrinsicsInjector.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/type/intrinsic/IntrinsicsInjector.java index 09983ceda..b4363e3eb 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/type/intrinsic/IntrinsicsInjector.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/type/intrinsic/IntrinsicsInjector.java @@ -54,12 +54,13 @@ import au.com.integradev.delphi.symbol.declaration.TypeNameDeclarationImpl; import au.com.integradev.delphi.symbol.declaration.VariableNameDeclarationImpl; import au.com.integradev.delphi.symbol.scope.DelphiScopeImpl; +import au.com.integradev.delphi.type.UnresolvedTypeImpl; import au.com.integradev.delphi.type.factory.TypeFactoryImpl; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.sonar.plugins.communitydelphi.api.symbol.declaration.RoutineNameDeclaration; import org.sonar.plugins.communitydelphi.api.symbol.declaration.TypeNameDeclaration; +import org.sonar.plugins.communitydelphi.api.symbol.declaration.VariableNameDeclaration; import org.sonar.plugins.communitydelphi.api.symbol.scope.DelphiScope; import org.sonar.plugins.communitydelphi.api.type.IntrinsicType; import org.sonar.plugins.communitydelphi.api.type.Type; @@ -67,27 +68,44 @@ public final class IntrinsicsInjector { private final TypeFactory typeFactory; + private final List constants; private final List routines; - private DelphiScopeImpl scope; public IntrinsicsInjector(TypeFactory typeFactory) { this.typeFactory = typeFactory; + this.constants = new ArrayList<>(); this.routines = new ArrayList<>(); + buildConstants(); buildRoutines(); } - public void inject(DelphiScope scope) { - this.scope = (DelphiScopeImpl) scope; - injectTypes(); - injectRoutines(); - injectConstants(); + public void injectTypes(DelphiScope scope) { + for (IntrinsicType type : IntrinsicType.values()) { + injectType(type, (DelphiScopeImpl) scope); + } + } + + public void injectConstants(DelphiScope scope) { + for (IntrinsicConstant constant : constants) { + injectConstant(constant, (DelphiScopeImpl) scope); + } + } + + public void injectRoutines(DelphiScope scope) { + for (IntrinsicRoutine.Builder routine : routines) { + injectRoutine(routine, (DelphiScopeImpl) scope); + } } private Type type(IntrinsicType type) { return typeFactory.getIntrinsic(type); } + private Type systemType(String image) { + return UnresolvedTypeImpl.referenceTo(image); + } + private Type openArraySizeType() { return ((TypeFactoryImpl) typeFactory).openArraySizeType(); } @@ -96,6 +114,16 @@ private Type dynamicArraySizeType() { return ((TypeFactoryImpl) typeFactory).dynamicArraySizeType(); } + private void buildConstants() { + constant("CompilerVersion", EXTENDED); + constant("MaxInt", INTEGER); + constant("MaxLongInt", LONGINT); + constant("True", BOOLEAN); + constant("False", BOOLEAN); + constant("ReturnAddress", POINTER); + constant("AddressOfReturnAddress", POINTER); + } + private void buildRoutines() { routine("Abs").param(type(REAL)).returns(type(REAL)); routine("Abs").param(type(INTEGER)).returns(type(INTEGER)); @@ -120,6 +148,13 @@ private void buildRoutines() { .outParam(type(BOOLEAN)) .required(3) .returns(typeFactory.untypedPointer()); + routine("AtomicCmpExchange128") + .varParam(TypeFactory.untypedType()) + .param(type(INT64)) + .param(type(INT64)) + .varParam(TypeFactory.untypedType()) + .required(4) + .returns(type(BOOLEAN)); routine("AtomicDecrement") .varParam(TypeFactory.untypedType()) .param(TypeFactory.untypedType()) @@ -227,6 +262,7 @@ private void buildRoutines() { routine("FreeMem").varParam(ANY_POINTER).param(type(INTEGER)).required(1); routine("GetDir").param(type(BYTE)).varParam(ANY_STRING); routine("GetMem").varParam(ANY_POINTER).param(type(INTEGER)); + routine("GetTypeKind").param(TypeFactory.untypedType()).returns(systemType("TTypeKind")); routine("Halt").param(type(INTEGER)).required(0); routine("HasWeakRef").param(ANY_CLASS_REFERENCE).returns(type(BOOLEAN)); routine("Hi").param(type(INTEGER)).returns(type(BYTE)); @@ -243,7 +279,7 @@ private void buildRoutines() { .varParam(LIKE_DYNAMIC_ARRAY) .param(dynamicArraySizeType()); routine("IsConstValue").param(TypeFactory.untypedType()).returns(type(BOOLEAN)); - routine("IsManagedType").param(ANY_CLASS_REFERENCE).returns(type(BOOLEAN)); + routine("IsManagedType").param(TypeFactory.untypedType()).returns(type(BOOLEAN)); routine("Length").param(type(SHORTSTRING)).returns(IntrinsicReturnType.length(typeFactory)); routine("Length").param(type(ANSISTRING)).returns(IntrinsicReturnType.length(typeFactory)); routine("Length").param(type(UNICODESTRING)).returns(IntrinsicReturnType.length(typeFactory)); @@ -317,13 +353,13 @@ private void buildRoutines() { .param(ANY_STRING) .varParam(TypeFactory.untypedType()) .varParam(ANY_32_BIT_INTEGER); - routine("VarArgStart").varParam(TypeFactory.untypedType()); + routine("VarArgStart").varParam(systemType("TVarArgList")); routine("VarArgGetValue") - .varParam(TypeFactory.untypedType()) + .varParam(systemType("TVarArgList")) .param(ANY_CLASS_REFERENCE) .returns(IntrinsicReturnType.classReferenceValue(1)); - routine("VarArgCopy").varParam(TypeFactory.untypedType()).varParam(TypeFactory.untypedType()); - routine("VarArgEnd").varParam(TypeFactory.untypedType()); + routine("VarArgCopy").varParam(systemType("TVarArgList")).varParam(systemType("TVarArgList")); + routine("VarArgEnd").varParam(systemType("TVarArgList")); routine("VarArrayRedim").varParam(ANY_VARIANT).param(type(INTEGER)); routine("VarCast").varParam(ANY_VARIANT).param(ANY_VARIANT).param(type(INTEGER)); routine("VarClear").varParam(ANY_VARIANT); @@ -337,17 +373,17 @@ private void buildRoutines() { routine("WriteLn").variadic(TypeFactory.untypedType()); } + private void constant(String name, IntrinsicType type) { + constants.add(new IntrinsicConstant(name, type)); + } + private IntrinsicRoutine.Builder routine(String name) { IntrinsicRoutine.Builder builder = IntrinsicRoutine.builder(name); routines.add(builder); return builder; } - private void injectTypes() { - Arrays.stream(IntrinsicType.values()).forEach(this::injectType); - } - - private void injectType(IntrinsicType intrinsic) { + private void injectType(IntrinsicType intrinsic, DelphiScopeImpl scope) { SymbolicNode node = SymbolicNode.imaginary(intrinsic.simpleName(), scope); Type type = typeFactory.getIntrinsic(intrinsic); TypeNameDeclaration declaration = @@ -356,12 +392,8 @@ private void injectType(IntrinsicType intrinsic) { scope.addDeclaration(declaration); } - private void injectRoutines() { - routines.forEach(this::injectRoutine); - } - - private void injectRoutine(IntrinsicRoutine.Builder builder) { - IntrinsicRoutine routine = builder.build(); + private void injectRoutine(IntrinsicRoutine.Builder builder, DelphiScopeImpl scope) { + IntrinsicRoutine routine = builder.build(scope); SymbolicNode node = SymbolicNode.imaginary(routine.simpleName(), scope); RoutineNameDeclaration declaration = RoutineNameDeclarationImpl.create(node, routine, typeFactory); @@ -369,18 +401,11 @@ private void injectRoutine(IntrinsicRoutine.Builder builder) { scope.addDeclaration(declaration); } - private void injectConstants() { - injectConstant("CompilerVersion", EXTENDED); - injectConstant("MaxInt", INTEGER); - injectConstant("MaxLongInt", LONGINT); - injectConstant("True", BOOLEAN); - injectConstant("False", BOOLEAN); - injectConstant("ReturnAddress", POINTER); - injectConstant("AddressOfReturnAddress", POINTER); - } + private void injectConstant(IntrinsicConstant constant, DelphiScopeImpl scope) { + String name = constant.getName(); + Type type = type(constant.getType()); + VariableNameDeclaration declaration = VariableNameDeclarationImpl.constant(name, type, scope); - private void injectConstant(String image, IntrinsicType intrinsic) { - var declaration = VariableNameDeclarationImpl.constant(image, type(intrinsic), scope); scope.addDeclaration(declaration); } } diff --git a/delphi-frontend/src/main/java/org/sonar/plugins/communitydelphi/api/type/IntrinsicType.java b/delphi-frontend/src/main/java/org/sonar/plugins/communitydelphi/api/type/IntrinsicType.java index 4e0635d92..69051ceef 100644 --- a/delphi-frontend/src/main/java/org/sonar/plugins/communitydelphi/api/type/IntrinsicType.java +++ b/delphi-frontend/src/main/java/org/sonar/plugins/communitydelphi/api/type/IntrinsicType.java @@ -50,6 +50,7 @@ public enum IntrinsicType implements Qualifiable { WIDESTRING("WideString"), UNICODESTRING("UnicodeString"), SHORTSTRING("ShortString"), + OPENSTRING("OpenString"), STRING("String"), ANSICHAR("AnsiChar"), WIDECHAR("WideChar"), diff --git a/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java b/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java index dbfeabafa..021f2cc33 100644 --- a/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java +++ b/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java @@ -604,6 +604,18 @@ void testDefaultIntrinsic() { verifyUsages(22, 10, reference(32, 2)); } + @Test + void testVarArgIntrinsics() { + execute("intrinsics/VarArgIntrinsics.pas"); + verifyUsages(15, 2, reference(18, 21)); + } + + @Test + void testGetTypeKindIntrinsic() { + execute("intrinsics/GetTypeKindIntrinsic.pas"); + verifyUsages(7, 10, reference(14, 2), reference(15, 2)); + } + @Test void testBinaryOperatorIntrinsics() { execute("operators/BinaryOperatorIntrinsics.pas"); @@ -1640,6 +1652,9 @@ private static Path createStandardLibrary(Path baseDir) { + " end;\n" + " TCustomAttribute = class(TObject)\n" + " end;\n" + + " TVarArgList = record\n" + + " end;\n" + + " TTypeKind = (tkUnknown);\n" + "implementation\n" + "end."); diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/intrinsics/GetTypeKindIntrinsic.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/intrinsics/GetTypeKindIntrinsic.pas new file mode 100644 index 000000000..4fd2f9250 --- /dev/null +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/intrinsics/GetTypeKindIntrinsic.pas @@ -0,0 +1,18 @@ +unit GetTypeKindIntrinsic; + +interface + +implementation + +procedure Proc(Arg: TTypeKind); overload; +begin + // Do nothing +end; + +procedure Test; +begin + Proc(GetTypeKind(123)); + Proc(GetTypeKind(TObject)); +end; + +end. \ No newline at end of file diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/intrinsics/VarArgIntrinsics.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/intrinsics/VarArgIntrinsics.pas new file mode 100644 index 000000000..c36c8f75e --- /dev/null +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/intrinsics/VarArgIntrinsics.pas @@ -0,0 +1,27 @@ +unit VarArgIntrinsics; + +interface + +implementation + +procedure Proc(Arg: Integer); +begin + // do nothing +end; + +procedure Test; cdecl; varargs; +var + VAList: TVarArgList; + Copy: TVarArgList; +begin + VarArgStart(VAList); + VarArgCopy(VAList, Copy); + + for var I := 0 to 3 do begin + Proc(VarArgGetValue(VAList, Integer)); + end; + + VarArgEnd(VAList); +end; + +end. \ No newline at end of file