From df6dce82a25ed945f6dae6cba7fa0c4110cb3d16 Mon Sep 17 00:00:00 2001 From: shivasurya Date: Fri, 7 Feb 2025 19:57:10 -0500 Subject: [PATCH] refactor: add relevant model --- sourcecode-parser/db/db.go | 46 +-- sourcecode-parser/graph/construct.go | 6 +- sourcecode-parser/graph/construct_test.go | 86 ---- .../graph/java/parse_statement.go | 2 +- sourcecode-parser/model/container.go | 12 - sourcecode-parser/model/expr.go | 371 ++++++++++++++++++ sourcecode-parser/model/import.go | 34 ++ sourcecode-parser/model/member.go | 195 ++++++++- sourcecode-parser/model/package.go | 48 +++ sourcecode-parser/model/reftype.go | 267 +++++++++++++ sourcecode-parser/model/variable.go | 151 +++++++ 11 files changed, 1091 insertions(+), 127 deletions(-) create mode 100644 sourcecode-parser/model/import.go create mode 100644 sourcecode-parser/model/package.go create mode 100644 sourcecode-parser/model/reftype.go create mode 100644 sourcecode-parser/model/variable.go diff --git a/sourcecode-parser/db/db.go b/sourcecode-parser/db/db.go index fb24674..608cb0a 100644 --- a/sourcecode-parser/db/db.go +++ b/sourcecode-parser/db/db.go @@ -5,54 +5,50 @@ import ( ) type StorageNode struct { - Package []*model.TreeNode - ImportDecl []*model.TreeNode - ClassDecl []*model.TreeNode - MethodDecl []*model.TreeNode - FieldDecl []*model.TreeNode - Variable []*model.TreeNode - BinaryExpr []*model.TreeNode - Annotation []*model.TreeNode - JavaDoc []*model.TreeNode - Comment []*model.TreeNode -} - -func (s *StorageNode) AddPackage(node *model.TreeNode) { + Package []*model.Package + ImportDecl []*model.ImportType + Annotation []*model.Annotation + ClassDecl []*model.ClassOrInterface + MethodDecl []*model.Method + MethodCall []*model.MethodCall + FieldDecl []*model.FieldDeclaration + Variable []*model.LocalVariableDecl + BinaryExpr []*model.BinaryExpr + JavaDoc []*model.Javadoc +} + +func (s *StorageNode) AddPackage(node *model.Package) { s.Package = append(s.Package, node) } -func (s *StorageNode) AddImportDecl(node *model.TreeNode) { +func (s *StorageNode) AddImportDecl(node *model.ImportType) { s.ImportDecl = append(s.ImportDecl, node) } -func (s *StorageNode) AddClassDecl(node *model.TreeNode) { +func (s *StorageNode) AddClassDecl(node *model.ClassOrInterface) { s.ClassDecl = append(s.ClassDecl, node) } -func (s *StorageNode) AddMethodDecl(node *model.TreeNode) { +func (s *StorageNode) AddMethodDecl(node *model.Method) { s.MethodDecl = append(s.MethodDecl, node) } -func (s *StorageNode) AddFieldDecl(node *model.TreeNode) { +func (s *StorageNode) AddFieldDecl(node *model.FieldDeclaration) { s.FieldDecl = append(s.FieldDecl, node) } -func (s *StorageNode) AddVariable(node *model.TreeNode) { +func (s *StorageNode) AddVariable(node *model.LocalVariableDecl) { s.Variable = append(s.Variable, node) } -func (s *StorageNode) AddBinaryExpr(node *model.TreeNode) { +func (s *StorageNode) AddBinaryExpr(node *model.BinaryExpr) { s.BinaryExpr = append(s.BinaryExpr, node) } -func (s *StorageNode) AddAnnotation(node *model.TreeNode) { +func (s *StorageNode) AddAnnotation(node *model.Annotation) { s.Annotation = append(s.Annotation, node) } -func (s *StorageNode) AddJavaDoc(node *model.TreeNode) { +func (s *StorageNode) AddJavaDoc(node *model.Javadoc) { s.JavaDoc = append(s.JavaDoc, node) } - -func (s *StorageNode) AddComment(node *model.TreeNode) { - s.Comment = append(s.Comment, node) -} diff --git a/sourcecode-parser/graph/construct.go b/sourcecode-parser/graph/construct.go index 512656d..4ff7dbc 100644 --- a/sourcecode-parser/graph/construct.go +++ b/sourcecode-parser/graph/construct.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/shivasurya/code-pathfinder/sourcecode-parser/db" javalang "github.com/shivasurya/code-pathfinder/sourcecode-parser/graph/java" utilities "github.com/shivasurya/code-pathfinder/sourcecode-parser/util" @@ -18,7 +19,7 @@ import ( sitter "github.com/smacker/go-tree-sitter" ) -func buildQLTreeFromAST(node *sitter.Node, sourceCode []byte, currentContext *model.Node, file string, parentNode *model.TreeNode) { +func buildQLTreeFromAST(node *sitter.Node, sourceCode []byte, currentContext *model.Node, file string, parentNode *model.TreeNode, storageNode db.StorageNode) { switch node.Type() { case "block": blockStmtNode := javalang.ParseBlockStatement(node, sourceCode, file) @@ -166,6 +167,7 @@ func Initialize(directory string) []*model.TreeNode { defer tree.Close() rootNode := tree.RootNode() + storageNode := db.StorageNode{} localTree := &model.TreeNode{ Parent: nil, Node: &model.Node{ @@ -175,7 +177,7 @@ func Initialize(directory string) []*model.TreeNode { }, } statusChan <- fmt.Sprintf("\033[32mWorker %d ....... Building graph and traversing code %s\033[0m", workerID, fileName) - buildQLTreeFromAST(rootNode, sourceCode, nil, file, localTree) + buildQLTreeFromAST(rootNode, sourceCode, nil, file, localTree, storageNode) treeHolder = append(treeHolder, localTree) statusChan <- fmt.Sprintf("\033[32mWorker %d ....... Done processing file %s\033[0m", workerID, fileName) diff --git a/sourcecode-parser/graph/construct_test.go b/sourcecode-parser/graph/construct_test.go index 4334ac5..d101dae 100644 --- a/sourcecode-parser/graph/construct_test.go +++ b/sourcecode-parser/graph/construct_test.go @@ -14,92 +14,6 @@ import ( "github.com/smacker/go-tree-sitter/java" ) -func TestNewCodeGraph(t *testing.T) { - graph := NewCodeGraph() - if graph == nil { - t.Error("NewCodeGraph() returned nil") - } - if graph != nil && graph.Nodes == nil { - t.Error("NewCodeGraph() returned graph with nil Nodes") - } - if graph != nil && graph.Edges == nil { - t.Error("NewCodeGraph() returned graph with nil Edges") - } - if graph != nil && len(graph.Nodes) != 0 { - t.Errorf("NewCodeGraph() returned graph with non-empty Nodes, got %d nodes", len(graph.Nodes)) - } - if graph != nil && len(graph.Edges) != 0 { - t.Errorf("NewCodeGraph() returned graph with non-empty Edges, got %d edges", len(graph.Edges)) - } -} - -func TestAddNode(t *testing.T) { - graph := NewCodeGraph() - node := &model.Node{ID: "test_node"} - graph.AddNode(node) - - if len(graph.Nodes) != 1 { - t.Errorf("AddNode() failed to add node, expected 1 node, got %d", len(graph.Nodes)) - } - if graph.Nodes["test_node"] != node { - t.Error("AddNode() failed to add node with correct ID") - } -} - -func TestAddEdge(t *testing.T) { - graph := NewCodeGraph() - node1 := &model.Node{ID: "node1"} - node2 := &model.Node{ID: "node2"} - graph.AddNode(node1) - graph.AddNode(node2) - - graph.AddEdge(node1, node2) - - if len(graph.Edges) != 1 { - t.Errorf("AddEdge() failed to add edge, expected 1 edge, got %d", len(graph.Edges)) - } - if graph.Edges[0].From != node1 || graph.Edges[0].To != node2 { - t.Error("AddEdge() failed to add edge with correct From and To nodes") - } - if len(node1.OutgoingEdges) != 1 { - t.Errorf("AddEdge() failed to add outgoing edge to From node, expected 1 edge, got %d", len(node1.OutgoingEdges)) - } - if node1.OutgoingEdges[0].To != node2 { - t.Error("AddEdge() failed to add correct outgoing edge to From node") - } -} - -func TestAddMultipleNodesAndEdges(t *testing.T) { - graph := NewCodeGraph() - node1 := &model.Node{ID: "node1"} - node2 := &model.Node{ID: "node2"} - node3 := &model.Node{ID: "node3"} - - graph.AddNode(node1) - graph.AddNode(node2) - graph.AddNode(node3) - - graph.AddEdge(node1, node2) - graph.AddEdge(node2, node3) - graph.AddEdge(node1, node3) - - if len(graph.Nodes) != 3 { - t.Errorf("Expected 3 nodes, got %d", len(graph.Nodes)) - } - if len(graph.Edges) != 3 { - t.Errorf("Expected 3 edges, got %d", len(graph.Edges)) - } - if len(node1.OutgoingEdges) != 2 { - t.Errorf("Expected 2 outgoing edges for node1, got %d", len(node1.OutgoingEdges)) - } - if len(node2.OutgoingEdges) != 1 { - t.Errorf("Expected 1 outgoing edge for node2, got %d", len(node2.OutgoingEdges)) - } - if len(node3.OutgoingEdges) != 0 { - t.Errorf("Expected 0 outgoing edges for node3, got %d", len(node3.OutgoingEdges)) - } -} - func TestIsJavaSourceFile(t *testing.T) { tests := []struct { name string diff --git a/sourcecode-parser/graph/java/parse_statement.go b/sourcecode-parser/graph/java/parse_statement.go index 5649644..a85470e 100644 --- a/sourcecode-parser/graph/java/parse_statement.go +++ b/sourcecode-parser/graph/java/parse_statement.go @@ -124,7 +124,7 @@ func ParseBlockStatement(node *sitter.Node, sourceCode []byte, file string) *mod } uniqueBlockID := fmt.Sprintf("block_%d_%d_%s", node.StartPoint().Row+1, node.StartPoint().Column+1, file) - blockStmtNode := &graph.Node{ + blockStmtNode := &model.Node{ ID: util.GenerateSha256(uniqueBlockID), Type: "BlockStmt", LineNumber: node.StartPoint().Row + 1, diff --git a/sourcecode-parser/model/container.go b/sourcecode-parser/model/container.go index 0f8d612..38c2e14 100644 --- a/sourcecode-parser/model/container.go +++ b/sourcecode-parser/model/container.go @@ -104,15 +104,3 @@ func (j *JarFile) GetManifestMainAttributes(key string) (string, bool) { func (j *JarFile) GetSpecificationVersion() string { return j.SpecificationVersion } - -type Package struct { - Package string -} - -func (p *Package) GetAPrimaryQlClass() string { - return "Package" -} - -func (p *Package) GetURL() string { - return p.Package -} diff --git a/sourcecode-parser/model/expr.go b/sourcecode-parser/model/expr.go index 9aa7bc4..34a414b 100644 --- a/sourcecode-parser/model/expr.go +++ b/sourcecode-parser/model/expr.go @@ -2,6 +2,7 @@ package model import ( "fmt" + "strings" sitter "github.com/smacker/go-tree-sitter" ) @@ -309,3 +310,373 @@ func (e *ClassInstanceExpr) GetNumArgs() int { func (e *ClassInstanceExpr) String() string { return fmt.Sprintf("ClassInstanceExpr(%s, %v)", e.ClassName, e.Args) } + +// Annotation represents a Java annotation applied to language elements. +type Annotation struct { + Expr + QualifiedName string // Fully qualified name of the annotation (e.g., "javax.persistence.Entity") + AnnotatedElement string // The element this annotation applies to + AnnotationType string // The type of this annotation + Values map[string]any // Stores annotation elements and their values + IsDeclAnnotation bool // Whether this annotation applies to a declaration + IsTypeAnnotation bool // Whether this annotation applies to a type + HalsteadID string // Placeholder for Halstead metric computation +} + +// NewAnnotation initializes a new Annotation instance. +func NewAnnotation(qualifiedName string, annotatedElement string, annotationType string, values map[string]any, isDeclAnnotation bool, isTypeAnnotation bool, halsteadID string) *Annotation { + return &Annotation{ + QualifiedName: qualifiedName, + AnnotatedElement: annotatedElement, + AnnotationType: annotationType, + Values: values, + IsDeclAnnotation: isDeclAnnotation, + IsTypeAnnotation: isTypeAnnotation, + HalsteadID: halsteadID, + } +} + +// ✅ Implementing Only the Provided Predicates for Annotation + +// GetAPrimaryQlClass returns the primary CodeQL class name for this annotation. +func (a *Annotation) GetAPrimaryQlClass() string { + return "Annotation" +} + +// GetAStringArrayValue retrieves a string array value from the annotation. +func (a *Annotation) GetAStringArrayValue(name string) []string { + if val, ok := a.Values[name].([]string); ok { + return val + } + return nil +} + +// GetATypeArrayValue retrieves a Class array value from the annotation. +func (a *Annotation) GetATypeArrayValue(name string) []string { + if val, ok := a.Values[name].([]string); ok { + return val + } + return nil +} + +// GetAnArrayValue retrieves an array value from the annotation. +func (a *Annotation) GetAnArrayValue(name string) any { + if val, ok := a.Values[name]; ok { + return val + } + return nil +} + +// GetAnEnumConstantArrayValue retrieves an enum array value from the annotation. +func (a *Annotation) GetAnEnumConstantArrayValue(name string) []string { + if val, ok := a.Values[name].([]string); ok { + return val + } + return nil +} + +// GetAnIntArrayValue retrieves an int array value from the annotation. +func (a *Annotation) GetAnIntArrayValue(name string) []int { + if val, ok := a.Values[name].([]int); ok { + return val + } + return nil +} + +// GetAnnotatedElement returns the element being annotated. +func (a *Annotation) GetAnnotatedElement() string { + return a.AnnotatedElement +} + +// GetAnnotationElement retrieves the annotation element with the specified name. +func (a *Annotation) GetAnnotationElement(name string) any { + if val, ok := a.Values[name]; ok { + return val + } + return nil +} + +// GetArrayValue retrieves a specific index value from an annotation array. +func (a *Annotation) GetArrayValue(name string, index int) any { + if val, ok := a.Values[name].([]any); ok && index < len(val) { + return val[index] + } + return nil +} + +// GetBooleanValue retrieves a boolean value from the annotation. +func (a *Annotation) GetBooleanValue(name string) bool { + if val, ok := a.Values[name].(bool); ok { + return val + } + return false +} + +// GetEnumConstantValue retrieves an enum constant value from the annotation. +func (a *Annotation) GetEnumConstantValue(name string) string { + if val, ok := a.Values[name].(string); ok { + return val + } + return "" +} + +// GetHalsteadID returns the Halstead metric ID for this annotation. +func (a *Annotation) GetHalsteadID() string { + return a.HalsteadID +} + +// GetIntValue retrieves an integer value from the annotation. +func (a *Annotation) GetIntValue(name string) int { + if val, ok := a.Values[name].(int); ok { + return val + } + return 0 +} + +// GetStringValue retrieves a string value from the annotation. +func (a *Annotation) GetStringValue(name string) string { + if val, ok := a.Values[name].(string); ok { + return val + } + return "" +} + +// GetTarget returns the element being annotated. +func (a *Annotation) GetTarget() string { + return a.AnnotatedElement +} + +// GetType returns the annotation type declaration. +func (a *Annotation) GetType() string { + return a.AnnotationType +} + +// GetTypeValue retrieves a `java.lang.Class` reference value from the annotation. +func (a *Annotation) GetTypeValue(name string) string { + if val, ok := a.Values[name].(string); ok { + return val + } + return "" +} + +// GetValue retrieves any value of an annotation element. +func (a *Annotation) GetValue(name string) any { + if val, ok := a.Values[name]; ok { + return val + } + return nil +} + +// IsDeclAnnotation checks whether this annotation applies to a declaration. +func (a *Annotation) GetIsDeclAnnotation() bool { + return a.IsDeclAnnotation +} + +// IsTypeAnnotation checks whether this annotation applies to a type. +func (a *Annotation) GetIsTypeAnnotation() bool { + return a.IsTypeAnnotation +} + +// ToString returns a textual representation of the annotation. +func (a *Annotation) ToString() string { + return "@" + a.QualifiedName +} + +// MethodCall represents an invocation of a method with arguments. +type MethodCall struct { + PrimaryQlClass string // Primary CodeQL class name + MethodName string // The method being called + QualifiedMethod string // Fully qualified method name + Arguments []string // List of arguments passed to the method + TypeArguments []string // Type arguments for generic method calls + Qualifier string // The qualifying expression of the method call (e.g., obj in obj.method()) + ReceiverType string // The type of the qualifier or the enclosing type if none + EnclosingCallable string // The method or function containing this method call + EnclosingStmt string // The statement enclosing this method call + HasQualifier bool // Whether this call has a qualifier + IsEnclosingCall bool // Whether this is a call to an instance method of the enclosing class + IsOwnMethodCall bool // Whether this is a call to an instance method of 'this' +} + +// NewMethodCall initializes a new MethodCall instance. +func NewMethodCall(primaryQlClass string, methodName string, qualifiedMethod string, arguments []string, typeArguments []string, qualifier string, receiverType string, enclosingCallable string, enclosingStmt string, hasQualifier bool, isEnclosingCall bool, isOwnMethodCall bool) *MethodCall { + return &MethodCall{ + PrimaryQlClass: primaryQlClass, + MethodName: methodName, + QualifiedMethod: qualifiedMethod, + Arguments: arguments, + TypeArguments: typeArguments, + Qualifier: qualifier, + ReceiverType: receiverType, + EnclosingCallable: enclosingCallable, + EnclosingStmt: enclosingStmt, + HasQualifier: hasQualifier, + IsEnclosingCall: isEnclosingCall, + IsOwnMethodCall: isOwnMethodCall, + } +} + +// ✅ Implementing the Predicates for `MethodCall` + +// GetAPrimaryQlClass returns the primary CodeQL class name. +func (mc *MethodCall) GetAPrimaryQlClass() string { + return mc.PrimaryQlClass +} + +// GetATypeArgument retrieves a type argument in this method call, if any. +func (mc *MethodCall) GetATypeArgument() []string { + return mc.TypeArguments +} + +// GetAnArgument retrieves all arguments supplied to this method call. +func (mc *MethodCall) GetAnArgument() []string { + return mc.Arguments +} + +// GetArgument retrieves an argument at the specified index. +func (mc *MethodCall) GetArgument(index int) string { + if index >= 0 && index < len(mc.Arguments) { + return mc.Arguments[index] + } + return "" +} + +// GetEnclosingCallable retrieves the callable that contains this method call. +func (mc *MethodCall) GetEnclosingCallable() string { + return mc.EnclosingCallable +} + +// GetEnclosingStmt retrieves the statement that contains this method call. +func (mc *MethodCall) GetEnclosingStmt() string { + return mc.EnclosingStmt +} + +// GetMethod retrieves the fully qualified name of the method being called. +func (mc *MethodCall) GetMethod() string { + return mc.QualifiedMethod +} + +// GetQualifier retrieves the qualifier of the method call, if any. +func (mc *MethodCall) GetQualifier() string { + return mc.Qualifier +} + +// GetReceiverType retrieves the receiver type of the method call. +func (mc *MethodCall) GetReceiverType() string { + return mc.ReceiverType +} + +// GetTypeArgument retrieves a specific type argument at the specified index. +func (mc *MethodCall) GetTypeArgument(index int) string { + if index >= 0 && index < len(mc.TypeArguments) { + return mc.TypeArguments[index] + } + return "" +} + +// HasQualifier checks if the method call has a qualifier. +func (mc *MethodCall) GetHasQualifier() bool { + return mc.HasQualifier +} + +// IsEnclosingMethodCall checks if this is a call to an instance method of the enclosing class. +func (mc *MethodCall) IsEnclosingMethodCall() bool { + return mc.IsEnclosingCall +} + +// IsOwnMethodCall checks if this is a call to an instance method of `this`. +func (mc *MethodCall) GetIsOwnMethodCall() bool { + return mc.IsOwnMethodCall +} + +// PrintAccess returns a printable representation of the method call. +func (mc *MethodCall) PrintAccess() string { + if mc.HasQualifier { + return fmt.Sprintf("%s.%s(%v)", mc.Qualifier, mc.MethodName, mc.Arguments) + } + return fmt.Sprintf("%s(%v)", mc.MethodName, mc.Arguments) +} + +// ToString returns a textual representation of the method call. +func (mc *MethodCall) ToString() string { + return mc.PrintAccess() +} + +// FieldDeclaration represents a declaration of one or more fields in a class. +type FieldDeclaration struct { + ExprParent + Type string // Type of the field (e.g., int, String) + FieldNames []string // Names of the fields declared in this statement + Visibility string // Visibility (public, private, protected, package-private) + IsStatic bool // Whether the field is static + IsFinal bool // Whether the field is final + IsVolatile bool // Whether the field is volatile + IsTransient bool // Whether the field is transient + SourceDeclaration string // Location of the field declaration +} + +// NewFieldDeclaration initializes a new FieldDeclaration instance. +func NewFieldDeclaration(fieldType string, fieldNames []string, visibility string, isStatic, isFinal, isVolatile, isTransient bool, sourceDeclaration string) *FieldDeclaration { + return &FieldDeclaration{ + Type: fieldType, + FieldNames: fieldNames, + Visibility: visibility, + IsStatic: isStatic, + IsFinal: isFinal, + IsVolatile: isVolatile, + IsTransient: isTransient, + SourceDeclaration: sourceDeclaration, + } +} + +// ✅ Implementing AST-Based Predicates + +// GetAField retrieves all fields declared in this field declaration. +func (f *FieldDeclaration) GetAField() []string { + return f.FieldNames +} + +// GetAPrimaryQlClass returns the primary CodeQL class name. +func (f *FieldDeclaration) GetAPrimaryQlClass() string { + return "FieldDeclaration" +} + +// GetField retrieves the field declared at the specified index. +func (f *FieldDeclaration) GetField(index int) string { + if index >= 0 && index < len(f.FieldNames) { + return f.FieldNames[index] + } + return "" +} + +// GetNumField returns the number of fields declared in this declaration. +func (f *FieldDeclaration) GetNumField() int { + return len(f.FieldNames) +} + +// GetTypeAccess retrieves the type of the field(s) in this declaration. +func (f *FieldDeclaration) GetTypeAccess() string { + return f.Type +} + +// ToString returns a textual representation of the field declaration. +func (f *FieldDeclaration) ToString() string { + modifiers := []string{} + if f.Visibility != "" { + modifiers = append(modifiers, f.Visibility) + } + if f.IsStatic { + modifiers = append(modifiers, "static") + } + if f.IsFinal { + modifiers = append(modifiers, "final") + } + if f.IsVolatile { + modifiers = append(modifiers, "volatile") + } + if f.IsTransient { + modifiers = append(modifiers, "transient") + } + + return fmt.Sprintf("%s %s %s;", strings.Join(modifiers, " "), f.Type, strings.Join(f.FieldNames, ", ")) +} diff --git a/sourcecode-parser/model/import.go b/sourcecode-parser/model/import.go new file mode 100644 index 0000000..37dafd6 --- /dev/null +++ b/sourcecode-parser/model/import.go @@ -0,0 +1,34 @@ +package model + +import "fmt" + +// ImportType represents a single-type import declaration in Java. +type ImportType struct { + ImportedType string // The fully qualified name of the imported type + SourceDeclaration string // Location of the import statement +} + +// NewImportType initializes a new ImportType instance. +func NewImportType(importedType, sourceDeclaration string) *ImportType { + return &ImportType{ + ImportedType: importedType, + SourceDeclaration: sourceDeclaration, + } +} + +// ✅ Implementing AST-Based Predicates + +// GetAPrimaryQlClass returns the primary CodeQL class name. +func (it *ImportType) GetAPrimaryQlClass() string { + return "ImportType" +} + +// GetImportedType retrieves the imported type. +func (it *ImportType) GetImportedType() string { + return it.ImportedType +} + +// ToString returns a textual representation of the import statement. +func (it *ImportType) ToString() string { + return fmt.Sprintf("import %s;", it.ImportedType) +} diff --git a/sourcecode-parser/model/member.go b/sourcecode-parser/model/member.go index 609ceb3..dcca6bc 100644 --- a/sourcecode-parser/model/member.go +++ b/sourcecode-parser/model/member.go @@ -1,6 +1,199 @@ package model +import ( + "fmt" + "strings" +) + +// Callable represents an invocable Java element (Method or Constructor). type Callable struct { StmtParent - CallableName string + Name string // Name of the callable (e.g., method or constructor) + QualifiedName string // Fully qualified name (e.g., "com.example.User.getName") + ReturnType string // Return type (void for constructors) + Parameters []string // List of parameter types + ParameterNames []string // List of parameter names + IsVarargs bool // Whether the last parameter is a varargs parameter + SourceDeclaration string // Source code location of this callable +} + +// NewCallable initializes a new Callable instance. +func NewCallable(name, qualifiedName, returnType string, parameters []string, parameterNames []string, isVarargs bool, sourceDeclaration string) *Callable { + return &Callable{ + Name: name, + QualifiedName: qualifiedName, + ReturnType: returnType, + Parameters: parameters, + ParameterNames: parameterNames, + IsVarargs: isVarargs, + SourceDeclaration: sourceDeclaration, + } +} + +// ✅ Implementing AST-Based Predicates + +// GetAParamType retrieves all parameter types of this callable. +func (c *Callable) GetAParamType() []string { + return c.Parameters +} + +// GetAParameter retrieves all formal parameters (type + name). +func (c *Callable) GetAParameter() []string { + params := []string{} + for i, paramType := range c.Parameters { + params = append(params, fmt.Sprintf("%s %s", paramType, c.ParameterNames[i])) + } + return params +} + +// GetNumberOfParameters returns the number of parameters. +func (c *Callable) GetNumberOfParameters() int { + return len(c.Parameters) +} + +// GetParameter retrieves a specific parameter type by index. +func (c *Callable) GetParameter(index int) string { + if index >= 0 && index < len(c.Parameters) { + return fmt.Sprintf("%s %s", c.Parameters[index], c.ParameterNames[index]) + } + return "" +} + +// GetParameterType retrieves a specific parameter type by index. +func (c *Callable) GetParameterType(index int) string { + if index >= 0 && index < len(c.Parameters) { + return c.Parameters[index] + } + return "" +} + +// GetReturnType returns the declared return type of this callable. +func (c *Callable) GetReturnType() string { + return c.ReturnType +} + +// GetSignature returns the fully qualified method signature. +func (c *Callable) GetSignature() string { + return fmt.Sprintf("%s %s(%v)", c.ReturnType, c.Name, strings.Join(c.Parameters, ", ")) +} + +// GetSourceDeclaration returns the source declaration of this callable. +func (c *Callable) GetSourceDeclaration() string { + return c.SourceDeclaration +} + +// GetStringSignature returns a string signature of this callable. +func (c *Callable) GetStringSignature() string { + return fmt.Sprintf("%s(%v)", c.Name, strings.Join(c.Parameters, ", ")) +} + +// GetVarargsParameterIndex returns the index of the varargs parameter, if one exists. +func (c *Callable) GetVarargsParameterIndex() int { + if c.IsVarargs { + return len(c.Parameters) - 1 + } + return -1 // Indicates no varargs parameter +} + +// HasNoParameters checks if this callable has no parameters. +func (c *Callable) HasNoParameters() bool { + return len(c.Parameters) == 0 +} + +// IsVarargs checks if the last parameter of this callable is a varargs parameter. +func (c *Callable) GetIsVarargs() bool { + return c.IsVarargs +} + +// ParamsString returns a formatted string of parameter types. +func (c *Callable) ParamsString() string { + if len(c.Parameters) == 0 { + return "()" + } + return fmt.Sprintf("(%v)", strings.Join(c.Parameters, ", ")) +} + +// Method represents a Java method declaration. +type Method struct { + Callable + Name string // Name of the method + QualifiedName string // Fully qualified method name + ReturnType string // Return type of the method + Parameters []string // List of parameter types + ParameterNames []string // List of parameter names + Visibility string // Visibility (public, private, protected, package-private) + IsAbstract bool // Whether this method is abstract + IsStrictfp bool // Whether this method is strictfp + IsStatic bool // Whether this method is static + IsFinal bool // Whether this method is final + IsConstructor bool // Whether this method is a constructor + SourceDeclaration string // Location of the source declaration +} + +// NewMethod initializes a new Method instance. +func NewMethod(name, qualifiedName, returnType string, parameters []string, parameterNames []string, visibility string, isAbstract, isStrictfp, isStatic, isFinal, isConstructor bool, sourceDeclaration string) *Method { + return &Method{ + Name: name, + QualifiedName: qualifiedName, + ReturnType: returnType, + Parameters: parameters, + ParameterNames: parameterNames, + Visibility: visibility, + IsAbstract: isAbstract, + IsStrictfp: isStrictfp, + IsStatic: isStatic, + IsFinal: isFinal, + IsConstructor: isConstructor, + SourceDeclaration: sourceDeclaration, + } +} + +// ✅ Implementing AST-Based Predicates + +// GetAPrimaryQlClass returns the primary CodeQL class name. +func (m *Method) GetAPrimaryQlClass() string { + return "Method" +} + +// GetSignature returns the fully qualified method signature. +func (m *Method) GetSignature() string { + return fmt.Sprintf("%s %s(%v)", m.ReturnType, m.Name, strings.Join(m.Parameters, ", ")) +} + +// GetSourceDeclaration returns the source declaration of this method. +func (m *Method) GetSourceDeclaration() string { + return m.SourceDeclaration +} + +// IsAbstract checks if this method is abstract. +func (m *Method) GetIsAbstract() bool { + return m.IsAbstract +} + +// IsInheritable checks if this method is inheritable (not private, static, or final). +func (m *Method) IsInheritable() bool { + return m.Visibility != "private" && !m.IsStatic && !m.IsFinal +} + +// IsPublic checks if this method is public. +func (m *Method) IsPublic() bool { + return m.Visibility == "public" +} + +// IsStrictfp checks if this method is strictfp. +func (m *Method) GetIsStrictfp() bool { + return m.IsStrictfp +} + +// SameParamTypes checks if two methods have the same parameter types. +func (m *Method) SameParamTypes(other *Method) bool { + if len(m.Parameters) != len(other.Parameters) { + return false + } + for i := range m.Parameters { + if m.Parameters[i] != other.Parameters[i] { + return false + } + } + return true } diff --git a/sourcecode-parser/model/package.go b/sourcecode-parser/model/package.go new file mode 100644 index 0000000..db7f8f3 --- /dev/null +++ b/sourcecode-parser/model/package.go @@ -0,0 +1,48 @@ +package model + +// Package represents a Java package, grouping multiple types. +type Package struct { + QualifiedName string // Fully qualified package name (e.g., "com.example") + TopLevelTypes []string // List of top-level types in this package + FromSource bool // Whether at least one reference type originates from source + Metrics string // Placeholder for package-level metrics + URL string // Dummy URL for the package (for debugging or references) +} + +// NewPackage initializes a new Package instance. +func NewPackage(qualifiedName string, topLevelTypes []string, fromSource bool, metrics string, url string) *Package { + return &Package{ + QualifiedName: qualifiedName, + TopLevelTypes: topLevelTypes, + FromSource: fromSource, + Metrics: metrics, + URL: url, + } +} + +// ✅ Implementing Only the Provided Predicates for Package + +// FromSource checks if at least one reference type in this package originates from source code. +func (p *Package) GetFromSource() bool { + return p.FromSource +} + +// GetAPrimaryQlClass returns the primary CodeQL class name for this package. +func (p *Package) GetAPrimaryQlClass() string { + return "Package" +} + +// GetATopLevelType returns a top-level type in this package. +func (p *Package) GetATopLevelType() []string { + return p.TopLevelTypes +} + +// GetMetrics provides metrics-related data for the package. +func (p *Package) GetMetrics() string { + return p.Metrics +} + +// GetURL returns a dummy URL for this package. +func (p *Package) GetURL() string { + return p.URL +} diff --git a/sourcecode-parser/model/reftype.go b/sourcecode-parser/model/reftype.go new file mode 100644 index 0000000..4763cf8 --- /dev/null +++ b/sourcecode-parser/model/reftype.go @@ -0,0 +1,267 @@ +package model + +type RefType struct { + // Precomputed from AST + QualifiedName string // Fully qualified name (e.g., "java.lang.String") + Package string // Package name (e.g., "java.lang") + SourceFile string // Compilation unit (filename) + TopLevel bool // Whether this is a top-level type + SuperTypes []string // Direct supertypes (extends, implements) + DeclaredFields []string // Fields declared in this type + DeclaredMethods []Method // Methods declared in this type + Constructors []Method // Constructor declarations + NestedTypes []string // Types declared inside this type + EnclosingType string // If this type is nested inside another type + ArrayType bool // Whether this is an array type + TypeDescriptor string // JVM Type Descriptor (e.g., "[I", "[Ljava/lang/String;") + + RuntimeResolver *TypeResolver +} + +// TypeResolver handles runtime computation of type relationships. +type TypeResolver struct { + TypeHierarchy map[string][]string // Supertype -> Subtype mappings +} + +func NewRefType(qualifiedName, pkg, sourceFile string, topLevel bool, superTypes []string, fields []string, methods []Method, constructors []Method, nestedTypes []string, enclosingType string, arrayType bool, typeDescriptor string, resolver *TypeResolver) *RefType { + return &RefType{ + QualifiedName: qualifiedName, + Package: pkg, + SourceFile: sourceFile, + TopLevel: topLevel, + SuperTypes: superTypes, + DeclaredFields: fields, + DeclaredMethods: methods, + Constructors: constructors, + NestedTypes: nestedTypes, + EnclosingType: enclosingType, + ArrayType: arrayType, + TypeDescriptor: typeDescriptor, + RuntimeResolver: resolver, + } +} + +func (r *RefType) GetQualifiedName() string { + return r.QualifiedName +} + +// GetPackage returns the package where the type is declared. +func (r *RefType) GetPackage() string { + return r.Package +} + +// HasSupertype checks if the type has the given supertype. +func (r *RefType) HasSupertype(t string) bool { + for _, super := range r.SuperTypes { + if super == t { + return true + } + } + return false +} + +// DeclaresField checks if the type declares a field with the given name. +func (r *RefType) DeclaresField(name string) bool { + for _, field := range r.DeclaredFields { + if field == name { + return true + } + } + return false +} + +// DeclaresMethod checks if the type declares a method with the given name. +func (r *RefType) DeclaresMethod(name string) bool { + for _, method := range r.DeclaredMethods { + if method.Name == name { + return true + } + } + return false +} + +// DeclaresMethodWithParams checks if the type declares a method with the given name and parameter count. +func (r *RefType) DeclaresMethodWithParams(name string, paramCount int) bool { + for _, method := range r.DeclaredMethods { + if method.Name == name && len(method.Parameters) == paramCount { + return true + } + } + return false +} + +// Runtime Computed Methods + +// GetASupertype retrieves the direct supertype (requires global analysis). +func (r *RefType) GetASupertype() []string { + if r.RuntimeResolver == nil { + return nil + } + return r.RuntimeResolver.ResolveSupertype(r.QualifiedName) +} + +// GetASubtype retrieves direct subtypes (requires global analysis). +func (r *RefType) GetASubtype() []string { + if r.RuntimeResolver == nil { + return nil + } + return r.RuntimeResolver.ResolveSubtype(r.QualifiedName) +} + +// HasMethod checks if the type has a method (including inherited methods). +func (r *RefType) HasMethod(name string) bool { + // First check declared methods + if r.DeclaresMethod(name) { + return true + } + + // Then check inherited methods + for _, super := range r.GetASupertype() { + if r.RuntimeResolver != nil && r.RuntimeResolver.HasMethod(super, name) { + return true + } + } + return false +} + +// TypeResolver Implementation + +// ResolveSupertype fetches direct supertypes. +func (tr *TypeResolver) ResolveSupertype(typename string) []string { + if supertypes, ok := tr.TypeHierarchy[typename]; ok { + return supertypes + } + return nil +} + +// ResolveSubtype fetches direct subtypes. +func (tr *TypeResolver) ResolveSubtype(typename string) []string { + var subtypes []string + for parent, children := range tr.TypeHierarchy { + for _, child := range children { + if child == typename { + subtypes = append(subtypes, parent) + } + } + } + return subtypes +} + +// HasMethod checks if a method is inherited from a supertype. +func (tr *TypeResolver) HasMethod(typename, methodName string) bool { + // For simplicity, assume a predefined method lookup (to be replaced by a full method table lookup) + methods := map[string][]string{ + "java.lang.Object": {"toString", "hashCode", "equals"}, + } + + if methodsList, ok := methods[typename]; ok { + for _, method := range methodsList { + if method == methodName { + return true + } + } + } + return false +} + +// ClassOrInterface represents a Java class or interface extending RefType. +type ClassOrInterface struct { + RefType + // Java 17 Sealed Class Feature + IsSealed bool // Whether this is a sealed class. + PermittedSubtypes []string // Permitted subtypes (if sealed class). + + // Companion Object (for future Kotlin-style support) + CompanionObject string // If this type has a companion object. + + // Accessibility and Visibility + IsLocal bool // Whether this class/interface is local. + IsPackageProtected bool // Whether this class/interface has package-private visibility. +} + +// NewClassOrInterface initializes a new ClassOrInterface instance. +func NewClassOrInterface(isSealed bool, permittedSubtypes []string, companionObject string, isLocal bool, isPackageProtected bool) *ClassOrInterface { + return &ClassOrInterface{ + IsSealed: isSealed, + PermittedSubtypes: permittedSubtypes, + CompanionObject: companionObject, + IsLocal: isLocal, + IsPackageProtected: isPackageProtected, + } +} + +// ✅ Implementing Only the Provided Predicates for ClassOrInterface + +// GetAPermittedSubtype returns the permitted subtypes if this is a sealed class. +func (c *ClassOrInterface) GetAPermittedSubtype() []string { + if c.IsSealed { + return c.PermittedSubtypes + } + return nil +} + +// GetCompanionObject returns the companion object of this class/interface, if any. +func (c *ClassOrInterface) GetCompanionObject() string { + return c.CompanionObject +} + +// IsSealed checks whether this is a sealed class (Java 17 feature). +func (c *ClassOrInterface) GetIsSealed() bool { + return c.IsSealed +} + +// IsLocal checks whether this class/interface is a local class. +func (c *ClassOrInterface) GetIsLocal() bool { + return c.IsLocal +} + +// IsPackageProtected checks whether this class/interface has package-private visibility. +func (c *ClassOrInterface) GetIsPackageProtected() bool { + return c.IsPackageProtected +} + +// Class represents a Java class extending ClassOrInterface. +type Class struct { + ClassOrInterface + + // CodeQL metadata + PrimaryQlClass string // Name of the primary CodeQL class + Annotations []string // Annotations applied to this class + + // Class type properties + IsAnonymous bool // Whether this is an anonymous class + IsFileClass bool // Whether this is a Kotlin file class (e.g., FooKt for Foo.kt) +} + +// NewClass initializes a new Class instance. +func NewClass(primaryQlClass string, annotations []string, isAnonymous bool, isFileClass bool, classOrInterface ClassOrInterface) *Class { + return &Class{ + ClassOrInterface: classOrInterface, + PrimaryQlClass: primaryQlClass, + Annotations: annotations, + IsAnonymous: isAnonymous, + IsFileClass: isFileClass, + } +} + +// ✅ Implementing Only the Provided Predicates for Class + +// GetAPrimaryQlClass returns the primary CodeQL class name. +func (c *Class) GetAPrimaryQlClass() string { + return c.PrimaryQlClass +} + +// GetAnAnnotation returns the annotations applied to this class. +func (c *Class) GetAnAnnotation() []string { + return c.Annotations +} + +// IsAnonymous checks whether this is an anonymous class. +func (c *Class) GetIsAnonymous() bool { + return c.IsAnonymous +} + +// IsFileClass checks whether this is a Kotlin file class. +func (c *Class) GetIsFileClass() bool { + return c.IsFileClass +} diff --git a/sourcecode-parser/model/variable.go b/sourcecode-parser/model/variable.go new file mode 100644 index 0000000..20bb7d9 --- /dev/null +++ b/sourcecode-parser/model/variable.go @@ -0,0 +1,151 @@ +package model + +import ( + "fmt" +) + +// Variable represents a field, local variable, or method parameter. +type Variable struct { + Name string // Name of the variable + Type string // Data type of the variable + Scope string // Scope of the variable (e.g., "field", "local", "parameter") + Initializer string // Initial value if available (e.g., `int x = 10;` → "10") + AssignedValues []string // List of expressions assigned to this variable + SourceDeclaration string // Location of the variable declaration +} + +// NewVariable initializes a new Variable instance. +func NewVariable(name, varType, scope, initializer string, assignedValues []string, sourceDeclaration string) *Variable { + return &Variable{ + Name: name, + Type: varType, + Scope: scope, + Initializer: initializer, + AssignedValues: assignedValues, + SourceDeclaration: sourceDeclaration, + } +} + +// ✅ Implementing AST-Based Predicates + +// GetAnAssignedValue retrieves values assigned to this variable. +func (v *Variable) GetAnAssignedValue() []string { + return v.AssignedValues +} + +// GetInitializer retrieves the initializer of this variable. +func (v *Variable) GetInitializer() string { + return v.Initializer +} + +// GetType retrieves the type of this variable. +func (v *Variable) GetType() string { + return v.Type +} + +// PP returns a formatted representation of the variable. +func (v *Variable) PP() string { + initStr := "" + if v.Initializer != "" { + initStr = fmt.Sprintf(" = %s", v.Initializer) + } + return fmt.Sprintf("%s %s%s;", v.Type, v.Name, initStr) +} + +// LocalScopeVariable represents a method parameter or a local variable. +type LocalScopeVariable struct { + Variable + Name string // Name of the variable + Type string // Data type of the variable + Scope string // Either "local" or "parameter" + DeclaredIn string // Callable (method or constructor) in which the variable is declared + SourceDeclaration string // Location of the variable declaration +} + +// NewLocalScopeVariable initializes a new LocalScopeVariable instance. +func NewLocalScopeVariable(name, varType, scope, declaredIn, sourceDeclaration string) *LocalScopeVariable { + return &LocalScopeVariable{ + Name: name, + Type: varType, + Scope: scope, + DeclaredIn: declaredIn, + SourceDeclaration: sourceDeclaration, + } +} + +// ✅ Implementing AST-Based Predicate + +// GetCallable retrieves the method or constructor in which this variable is declared. +func (v *LocalScopeVariable) GetCallable() string { + return v.DeclaredIn +} + +// LocalVariableDecl represents a local variable declaration inside a method or block. +type LocalVariableDecl struct { + LocalScopeVariable + Name string // Name of the local variable + Type string // Data type of the variable + Callable string // The callable (method/constructor) in which this variable is declared + DeclExpr string // The declaration expression (e.g., `int x = 5;`) + Initializer string // The right-hand side of the declaration (if any) + ParentScope string // The enclosing block or statement + SourceDeclaration string // Location of the variable declaration +} + +// NewLocalVariableDecl initializes a new LocalVariableDecl instance. +func NewLocalVariableDecl(name, varType, callable, declExpr, initializer, parentScope, sourceDeclaration string) *LocalVariableDecl { + return &LocalVariableDecl{ + Name: name, + Type: varType, + Callable: callable, + DeclExpr: declExpr, + Initializer: initializer, + ParentScope: parentScope, + SourceDeclaration: sourceDeclaration, + } +} + +// ✅ Implementing AST-Based Predicates + +// GetAPrimaryQlClass returns the primary CodeQL class name. +func (lv *LocalVariableDecl) GetAPrimaryQlClass() string { + return "LocalVariableDecl" +} + +// GetCallable retrieves the method or constructor in which this variable is declared. +func (lv *LocalVariableDecl) GetCallable() string { + return lv.Callable +} + +// GetDeclExpr retrieves the full declaration expression of this variable. +func (lv *LocalVariableDecl) GetDeclExpr() string { + return lv.DeclExpr +} + +// GetEnclosingCallable retrieves the enclosing callable (same as `GetCallable()`). +func (lv *LocalVariableDecl) GetEnclosingCallable() string { + return lv.Callable +} + +// GetInitializer retrieves the initializer expression if available. +func (lv *LocalVariableDecl) GetInitializer() string { + return lv.Initializer +} + +// GetParent retrieves the parent block or statement that encloses this variable. +func (lv *LocalVariableDecl) GetParent() string { + return lv.ParentScope +} + +// GetType retrieves the type of this local variable. +func (lv *LocalVariableDecl) GetType() string { + return lv.Type +} + +// ToString returns a textual representation of the local variable declaration. +func (lv *LocalVariableDecl) ToString() string { + if lv.Initializer != "" { + return fmt.Sprintf("%s %s = %s;", lv.Type, lv.Name, lv.Initializer) + } + return fmt.Sprintf("%s %s;", lv.Type, lv.Name) +}