Repository: fraunhofer-aisec/cpg
Issue: Support implicit annotation style in Java

In Java you can either specifiy annotation values for keys directly, such as `@RequestMapping(value = "/test")` or indirectly using `@RequestMapping("hello")`. If you use the latter, then it will be assigned to an attribute named `value`, if it exists.

Currently, the CPG does just ignore this example; it should be parsed as an annotation with a entry of `value` and `"hello"`.


Changed files:
src/test/java/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.java
src/test/resources/Annotation.java
src/main/java/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontend.java

diff --git a/src/main/java/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontend.java b/src/main/java/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontend.java
index fbad7a321..70917d820 100644
--- a/src/main/java/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontend.java
+++ b/src/main/java/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontend.java
@@ -39,10 +39,7 @@ import com.github.javaparser.ast.ImportDeclaration;
 import com.github.javaparser.ast.Node.Parsedness;
 import com.github.javaparser.ast.PackageDeclaration;
 import com.github.javaparser.ast.body.TypeDeclaration;
-import com.github.javaparser.ast.expr.Expression;
-import com.github.javaparser.ast.expr.MemberValuePair;
-import com.github.javaparser.ast.expr.MethodCallExpr;
-import com.github.javaparser.ast.expr.NameExpr;
+import com.github.javaparser.ast.expr.*;
 import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations;
 import com.github.javaparser.ast.nodeTypes.NodeWithType;
 import com.github.javaparser.ast.type.ClassOrInterfaceType;
@@ -85,6 +82,9 @@ import org.checkerframework.checker.nullness.qual.Nullable;
 /** Main parser for ONE Java files. */
 public class JavaLanguageFrontend extends LanguageFrontend {
 
+  public static final String THIS = "this";
+  public static final String ANNOTATION_MEMBER_VALUE = "value";
+
   private CompilationUnit context;
 
   private ExpressionHandler expressionHandler = new ExpressionHandler(this);
@@ -277,7 +277,7 @@ public class JavaLanguageFrontend extends LanguageFrontend {
             return fromImport;
           }
         }
-        if (scope.get().toString().equals("this")) {
+        if (scope.get().toString().equals(THIS)) {
           // this is not strictly true. This could also be a function of a superclass,
           // but is the best we can do for now.
           // if the superclass would be known, this would already be resolved by the Javaresolver
@@ -484,10 +484,9 @@ public class JavaLanguageFrontend extends LanguageFrontend {
 
       var members = new ArrayList<AnnotationMember>();
 
-      for (var child : expr.getChildNodes()) {
-        if (child instanceof MemberValuePair) {
-          var pair = (MemberValuePair) child;
-
+      // annotations can be specified as member / value pairs
+      if (expr.isNormalAnnotationExpr()) {
+        for (var pair : expr.asNormalAnnotationExpr().getPairs()) {
           var member =
               newAnnotationMember(
                   pair.getNameAsString(),
@@ -497,10 +496,24 @@ public class JavaLanguageFrontend extends LanguageFrontend {
 
           members.add(member);
         }
+      } else if (expr.isSingleMemberAnnotationExpr()) {
+        var value = expr.asSingleMemberAnnotationExpr().getMemberValue();
 
-        annotation.setMembers(members);
+        if (value != null) {
+          // or as a literal. in this case it is assigned to the annotation member 'value'
+          var member =
+              newAnnotationMember(
+                  ANNOTATION_MEMBER_VALUE,
+                  (de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression)
+                      expressionHandler.handle(value.asLiteralExpr()),
+                  getCodeFromRawNode(value));
+
+          members.add(member);
+        }
       }
 
+      annotation.setMembers(members);
+
       list.add(annotation);
     }
 
diff --git a/src/test/java/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.java b/src/test/java/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.java
index 7ed7bbb75..0f33b1dfb 100644
--- a/src/test/java/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.java
+++ b/src/test/java/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.java
@@ -585,5 +585,18 @@ class JavaLanguageFrontendTest extends BaseTest {
 
     var forField = annotations.get(0);
     assertEquals("AnnotatedField", forField.getName());
+
+    field = record.getField("anotherField");
+    assertNotNull(field);
+
+    annotations = field.getAnnotations();
+    assertEquals(1, annotations.size());
+
+    forField = annotations.get(0);
+    assertEquals("AnnotatedField", forField.getName());
+
+    value = forField.getMembers().get(0);
+    assertEquals(JavaLanguageFrontend.ANNOTATION_MEMBER_VALUE, value.getName());
+    assertEquals("myString", ((Literal<String>) value.getValue()).getValue());
   }
 }
diff --git a/src/test/resources/Annotation.java b/src/test/resources/Annotation.java
index adeb140dd..47f7bb920 100644
--- a/src/test/resources/Annotation.java
+++ b/src/test/resources/Annotation.java
@@ -4,4 +4,6 @@ public class Annotation {
   @AnnotatedField
   private int field = 1;
 
+  @AnnotatedField("myString")
+  private int anotherField = 2;
 }