diff --git a/src/main/java/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontend.java b/src/main/java/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontend.java index 14c866100d..b51aa66f4f 100644 --- a/src/main/java/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontend.java +++ b/src/main/java/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontend.java @@ -27,6 +27,7 @@ package de.fraunhofer.aisec.cpg.frontends.cpp; import static de.fraunhofer.aisec.cpg.graph.NodeBuilder.newAnnotation; +import static de.fraunhofer.aisec.cpg.graph.NodeBuilder.newAnnotationMember; import static de.fraunhofer.aisec.cpg.graph.NodeBuilder.newDeclaredReferenceExpression; import static de.fraunhofer.aisec.cpg.graph.NodeBuilder.newLiteral; @@ -34,6 +35,7 @@ import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend; import de.fraunhofer.aisec.cpg.frontends.TranslationException; import de.fraunhofer.aisec.cpg.graph.Annotation; +import de.fraunhofer.aisec.cpg.graph.AnnotationMember; import de.fraunhofer.aisec.cpg.graph.Node; import de.fraunhofer.aisec.cpg.graph.TypeManager; import de.fraunhofer.aisec.cpg.graph.declarations.Declaration; @@ -58,7 +60,15 @@ import java.util.stream.Collectors; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import org.eclipse.cdt.core.dom.ast.*; +import org.eclipse.cdt.core.dom.ast.IASTAttribute; +import org.eclipse.cdt.core.dom.ast.IASTAttributeOwner; +import org.eclipse.cdt.core.dom.ast.IASTComment; +import org.eclipse.cdt.core.dom.ast.IASTExpression; +import org.eclipse.cdt.core.dom.ast.IASTFileLocation; +import org.eclipse.cdt.core.dom.ast.IASTNode; +import org.eclipse.cdt.core.dom.ast.IASTToken; +import org.eclipse.cdt.core.dom.ast.IASTTokenList; +import org.eclipse.cdt.core.dom.ast.IBinding; import org.eclipse.cdt.core.dom.ast.gnu.cpp.GPPLanguage; import org.eclipse.cdt.core.index.IIndexFileLocation; import org.eclipse.cdt.core.model.ILanguage; @@ -393,9 +403,10 @@ private List handleAttributes(IASTAttributeOwner owner) { // go over the parameters if (attribute.getArgumentClause() instanceof IASTTokenList) { - List values = handleTokenList((IASTTokenList) attribute.getArgumentClause()); + List members = + handleTokenList((IASTTokenList) attribute.getArgumentClause()); - annotation.setValues(values); + annotation.setMembers(members); } list.add(annotation); @@ -404,10 +415,11 @@ private List handleAttributes(IASTAttributeOwner owner) { return list; } - private List handleTokenList(IASTTokenList tokenList) { - List list = new ArrayList<>(); + private List handleTokenList(IASTTokenList tokenList) { + List list = new ArrayList<>(); for (IASTToken token : tokenList.getTokens()) { + // skip commas and such if (token.getTokenType() == 6) { continue; } @@ -418,22 +430,32 @@ private List handleTokenList(IASTTokenList tokenList) { return list; } - private Expression handleToken(IASTToken token) { + private AnnotationMember handleToken(IASTToken token) { String code = new String(token.getTokenCharImage()); + Expression expression; switch (token.getTokenType()) { case 1: - return newDeclaredReferenceExpression(code, UnknownType.getUnknownType(), code); + // a variable + expression = newDeclaredReferenceExpression(code, UnknownType.getUnknownType(), code); + break; case 2: - return newLiteral(Integer.parseInt(code), TypeParser.createFrom("int", true), code); + // an integer + expression = newLiteral(Integer.parseInt(code), TypeParser.createFrom("int", true), code); + break; case 130: - return newLiteral( - code.length() >= 2 ? code.substring(1, code.length() - 1) : "", - TypeParser.createFrom("const char*", false), - code); + // a string + expression = + newLiteral( + code.length() >= 2 ? code.substring(1, code.length() - 1) : "", + TypeParser.createFrom("const char*", false), + code); + break; default: - return newLiteral(code, TypeParser.createFrom("const char*", false), code); + expression = newLiteral(code, TypeParser.createFrom("const char*", false), code); } + + return newAnnotationMember(null, expression, code); } private Field getField(Class type, String fieldName) throws NoSuchFieldException { diff --git a/src/main/java/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.java b/src/main/java/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.java index 86dfd2db2d..a52b42ce7d 100644 --- a/src/main/java/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.java +++ b/src/main/java/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.java @@ -140,6 +140,9 @@ private static void addImplicitReturn(BlockStmt body) { addImplicitReturn(body); declaration.setBody(this.lang.getStatementHandler().handle(body)); + + lang.processAnnotations(declaration, constructorDecl); + lang.getScopeManager().leaveScope(declaration); return declaration; } @@ -192,6 +195,9 @@ public de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration handleMethod addImplicitReturn(body); functionDeclaration.setBody(this.lang.getStatementHandler().handle(body)); + + lang.processAnnotations(functionDeclaration, methodDecl); + lang.getScopeManager().leaveScope(functionDeclaration); return functionDeclaration; } @@ -272,6 +278,9 @@ public RecordDeclaration handleClassOrInterfaceDeclaration( recordDeclaration.getConstructors().add(constructorDeclaration); lang.getScopeManager().addDeclaration(constructorDeclaration); } + + lang.processAnnotations(recordDeclaration, classInterDecl); + lang.getScopeManager().leaveScope(recordDeclaration); return recordDeclaration; } @@ -317,6 +326,8 @@ public de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration handleFieldDe false); lang.getScopeManager().addDeclaration(fieldDeclaration); + this.lang.processAnnotations(fieldDeclaration, fieldDecl); + return fieldDeclaration; } 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 61efa556c6..fbad7a3214 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 @@ -26,6 +26,9 @@ package de.fraunhofer.aisec.cpg.frontends.java; +import static de.fraunhofer.aisec.cpg.graph.NodeBuilder.newAnnotation; +import static de.fraunhofer.aisec.cpg.graph.NodeBuilder.newAnnotationMember; + import com.github.javaparser.JavaParser; import com.github.javaparser.ParseResult; import com.github.javaparser.ParserConfiguration; @@ -33,13 +36,14 @@ import com.github.javaparser.TokenRange; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.ImportDeclaration; -import com.github.javaparser.ast.Node; 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.nodeTypes.NodeWithAnnotations; import com.github.javaparser.ast.nodeTypes.NodeWithType; import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.github.javaparser.ast.type.Type; @@ -54,6 +58,9 @@ import de.fraunhofer.aisec.cpg.TranslationConfiguration; import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend; import de.fraunhofer.aisec.cpg.frontends.TranslationException; +import de.fraunhofer.aisec.cpg.graph.Annotation; +import de.fraunhofer.aisec.cpg.graph.AnnotationMember; +import de.fraunhofer.aisec.cpg.graph.Node; import de.fraunhofer.aisec.cpg.graph.NodeBuilder; import de.fraunhofer.aisec.cpg.graph.TypeManager; import de.fraunhofer.aisec.cpg.graph.declarations.IncludeDeclaration; @@ -200,8 +207,8 @@ protected CompilationUnit parse(File file, JavaParser parser) @Override public String getCodeFromRawNode(T astNode) { - if (astNode instanceof Node) { - Node node = (Node) astNode; + if (astNode instanceof com.github.javaparser.ast.Node) { + var node = (com.github.javaparser.ast.Node) astNode; Optional optional = node.getTokenRange(); if (optional.isPresent()) { return optional.get().toString(); @@ -213,8 +220,8 @@ public String getCodeFromRawNode(T astNode) { @Override @Nullable public PhysicalLocation getLocationFromRawNode(T astNode) { - if (astNode instanceof Node) { - Node node = (Node) astNode; + if (astNode instanceof com.github.javaparser.ast.Node) { + var node = (com.github.javaparser.ast.Node) astNode; // find compilation unit of node CompilationUnit cu = node.findCompilationUnit().orElse(null); @@ -430,7 +437,7 @@ public void cleanup() { @Override public void setComment(S s, T ctx) { if (ctx instanceof Node && s instanceof de.fraunhofer.aisec.cpg.graph.Node) { - Node node = (Node) ctx; + var node = (com.github.javaparser.ast.Node) ctx; de.fraunhofer.aisec.cpg.graph.Node cpgNode = (de.fraunhofer.aisec.cpg.graph.Node) s; node.getComment().ifPresent(comment -> cpgNode.setComment(comment.getContent())); // TODO: handle orphanComments? @@ -456,4 +463,47 @@ public CompilationUnit getContext() { public CombinedTypeSolver getNativeTypeResolver() { return this.internalTypeSolver; } + + /** + * Processes Java annotations. + * + * @param node the node + * @param owner the AST owner node + */ + public void processAnnotations(@NonNull Node node, NodeWithAnnotations owner) { + if (this.config.processAnnotations) { + node.addAnnotations(handleAnnotations(owner)); + } + } + + private List handleAnnotations(NodeWithAnnotations owner) { + var list = new ArrayList(); + + for (var expr : owner.getAnnotations()) { + var annotation = newAnnotation(expr.getNameAsString(), getCodeFromRawNode(expr)); + + var members = new ArrayList(); + + for (var child : expr.getChildNodes()) { + if (child instanceof MemberValuePair) { + var pair = (MemberValuePair) child; + + var member = + newAnnotationMember( + pair.getNameAsString(), + (de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression) + expressionHandler.handle(pair.getValue()), + getCodeFromRawNode(pair)); + + members.add(member); + } + + annotation.setMembers(members); + } + + list.add(annotation); + } + + return list; + } } diff --git a/src/main/java/de/fraunhofer/aisec/cpg/graph/Annotation.java b/src/main/java/de/fraunhofer/aisec/cpg/graph/Annotation.java index 0c244ac669..a57ea5ef07 100644 --- a/src/main/java/de/fraunhofer/aisec/cpg/graph/Annotation.java +++ b/src/main/java/de/fraunhofer/aisec/cpg/graph/Annotation.java @@ -29,18 +29,27 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression; import java.util.List; import java.util.Objects; +import javax.annotation.Nullable; public class Annotation extends Node { - // should be extended later into annotation members - private List values; + private List members; - public List getValues() { - return values; + public List getMembers() { + return members; } - public void setValues(List values) { - this.values = values; + public void setMembers(List members) { + this.members = members; + } + + @Nullable + public Expression getValueForName(String name) { + return members.stream() + .filter(member -> Objects.equals(member.name, name)) + .map(AnnotationMember::getValue) + .findAny() + .orElse(null); } @Override @@ -53,7 +62,7 @@ public boolean equals(Object o) { } Annotation that = (Annotation) o; - return super.equals(that) && Objects.equals(values, that.values); + return super.equals(that) && Objects.equals(members, that.members); } @Override diff --git a/src/main/java/de/fraunhofer/aisec/cpg/graph/AnnotationMember.java b/src/main/java/de/fraunhofer/aisec/cpg/graph/AnnotationMember.java new file mode 100644 index 0000000000..bbc68a4b54 --- /dev/null +++ b/src/main/java/de/fraunhofer/aisec/cpg/graph/AnnotationMember.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ + +package de.fraunhofer.aisec.cpg.graph; + +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression; + +public class AnnotationMember extends Node { + + public Expression getValue() { + return value; + } + + public void setValue(Expression value) { + this.value = value; + } + + private Expression value; +} diff --git a/src/main/java/de/fraunhofer/aisec/cpg/graph/NodeBuilder.java b/src/main/java/de/fraunhofer/aisec/cpg/graph/NodeBuilder.java index 99601700cf..afcf18a315 100644 --- a/src/main/java/de/fraunhofer/aisec/cpg/graph/NodeBuilder.java +++ b/src/main/java/de/fraunhofer/aisec/cpg/graph/NodeBuilder.java @@ -695,4 +695,14 @@ public static Annotation newAnnotation(String name, @NonNull String code) { return annotation; } + + public static AnnotationMember newAnnotationMember( + String name, Expression value, @NonNull String code) { + var member = new AnnotationMember(); + member.setName(name); + member.setValue(value); + member.setCode(code); + + return member; + } } diff --git a/src/test/java/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.java b/src/test/java/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.java index 36570345d5..99d85649ff 100644 --- a/src/test/java/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.java +++ b/src/test/java/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.java @@ -1225,8 +1225,8 @@ void testAttributes() throws Exception { assertNotNull(annotation); assertEquals("property_attribute", annotation.getName()); - assertEquals(3, annotation.getValues().size()); - assertEquals("a", ((Literal) annotation.getValues().get(0)).getValue()); + assertEquals(3, annotation.getMembers().size()); + assertEquals("a", ((Literal) annotation.getMembers().get(0).getValue()).getValue()); FieldDeclaration b = someClass.getFields().stream().filter(f -> f.getName().equals("b")).findAny().orElse(null); @@ -1236,10 +1236,10 @@ void testAttributes() throws Exception { assertNotNull(annotation); assertEquals("property_attribute", annotation.getName()); - assertEquals(1, annotation.getValues().size()); + assertEquals(1, annotation.getMembers().size()); assertEquals( "SomeCategory, SomeOtherThing", - ((Literal) annotation.getValues().get(0)).getValue()); + ((Literal) annotation.getMembers().get(0).getValue()).getValue()); } @Test 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 b2ffbbf9ad..7ed7bbb754 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 @@ -26,14 +26,44 @@ package de.fraunhofer.aisec.cpg.frontends.java; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import de.fraunhofer.aisec.cpg.BaseTest; import de.fraunhofer.aisec.cpg.TestUtils; -import de.fraunhofer.aisec.cpg.graph.*; -import de.fraunhofer.aisec.cpg.graph.declarations.*; -import de.fraunhofer.aisec.cpg.graph.statements.*; -import de.fraunhofer.aisec.cpg.graph.statements.expressions.*; +import de.fraunhofer.aisec.cpg.TranslationConfiguration; +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration; +import de.fraunhofer.aisec.cpg.graph.statements.CaseStatement; +import de.fraunhofer.aisec.cpg.graph.statements.CatchClause; +import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement; +import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement; +import de.fraunhofer.aisec.cpg.graph.statements.DefaultStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ForEachStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ForStatement; +import de.fraunhofer.aisec.cpg.graph.statements.Statement; +import de.fraunhofer.aisec.cpg.graph.statements.SwitchStatement; +import de.fraunhofer.aisec.cpg.graph.statements.TryStatement; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArrayCreationExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArraySubscriptionExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CastExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ExpressionList; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.InitializerListExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.StaticCallExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator; import de.fraunhofer.aisec.cpg.graph.types.TypeParser; import de.fraunhofer.aisec.cpg.helpers.NodeComparator; import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker; @@ -518,4 +548,42 @@ void testLocation() throws Exception { assertEquals("FieldAccess.java", path.getFileName().toString()); assertEquals(new Region(7, 3, 10, 4), location.getRegion()); } + + @Test + void testAnnotations() throws Exception { + File file = new File("src/test/resources/Annotation.java"); + List declarations = + TestUtils.analyzeWithBuilder( + TranslationConfiguration.builder() + .sourceLocations(List.of(file)) + .topLevel(file.getParentFile()) + .defaultPasses() + .processAnnotations(true)); + assertFalse(declarations.isEmpty()); + + TranslationUnitDeclaration tu = declarations.get(0); + assertNotNull(tu); + + var record = tu.getDeclarationAs(0, RecordDeclaration.class); + assertNotNull(record); + + var annotations = record.getAnnotations(); + assertEquals(1, annotations.size()); + + var forClass = annotations.get(0); + assertEquals("AnnotationForClass", forClass.getName()); + + var value = forClass.getMembers().get(0); + assertEquals("value", value.getName()); + assertEquals(2, ((Literal) value.getValue()).getValue()); + + var field = record.getField("field"); + assertNotNull(field); + + annotations = field.getAnnotations(); + assertEquals(1, annotations.size()); + + var forField = annotations.get(0); + assertEquals("AnnotatedField", forField.getName()); + } } diff --git a/src/test/resources/Annotation.java b/src/test/resources/Annotation.java new file mode 100644 index 0000000000..adeb140dd3 --- /dev/null +++ b/src/test/resources/Annotation.java @@ -0,0 +1,7 @@ +@AnnotationForClass(value = 2) +public class Annotation { + + @AnnotatedField + private int field = 1; + +}