Skip to content

Commit

Permalink
Adding LambdaExpression for lambda functions (#592)
Browse files Browse the repository at this point in the history
This PR adds a node type type LambdaExpression. It can be used for lambda functions that are assigned to a variable or are given as an argument in a call expression.

The type of the expression is a FunctionPointerType which corresponds to the signature of its function.

Co-authored-by: Konrad Weiss <[email protected]>
  • Loading branch information
oxisto and konradweiss authored Nov 19, 2021
1 parent 9352d67 commit 793b58b
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,8 @@ public synchronized void addTranslationUnit(TranslationUnitDeclaration tu) {
public Map<String, Object> getScratch() {
return scratch;
}

public TranslationManager getTranslationManager() {
return translationManager;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
*/
package de.fraunhofer.aisec.cpg.frontends.java;

import static de.fraunhofer.aisec.cpg.graph.NodeBuilder.*;

import com.github.javaparser.ast.ImportDeclaration;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.AnnotationDeclaration;
Expand All @@ -41,7 +43,6 @@
import com.github.javaparser.resolution.declarations.ResolvedConstructorDeclaration;
import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration;
import de.fraunhofer.aisec.cpg.frontends.Handler;
import de.fraunhofer.aisec.cpg.graph.NodeBuilder;
import de.fraunhofer.aisec.cpg.graph.TypeManager;
import de.fraunhofer.aisec.cpg.graph.declarations.Declaration;
import de.fraunhofer.aisec.cpg.graph.declarations.ParamVariableDeclaration;
Expand Down Expand Up @@ -110,7 +111,7 @@ private static void addImplicitReturn(BlockStmt body) {
ResolvedConstructorDeclaration resolvedConstructor = constructorDecl.resolve();

de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration declaration =
NodeBuilder.newConstructorDeclaration(
newConstructorDeclaration(
resolvedConstructor.getName(),
constructorDecl.toString(),
lang.getScopeManager().getCurrentRecord());
Expand All @@ -124,7 +125,7 @@ private static void addImplicitReturn(BlockStmt body) {

for (Parameter parameter : constructorDecl.getParameters()) {
ParamVariableDeclaration param =
NodeBuilder.newMethodParameterIn(
newMethodParameterIn(
parameter.getNameAsString(),
this.lang.getTypeAsGoodAsPossible(parameter, parameter.resolve()),
parameter.isVarArgs(),
Expand Down Expand Up @@ -165,12 +166,12 @@ public de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration handleMethod
var record = lang.getScopeManager().getCurrentRecord();

de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration functionDeclaration =
NodeBuilder.newMethodDeclaration(
newMethodDeclaration(
resolvedMethod.getName(), methodDecl.toString(), methodDecl.isStatic(), record);

// create the receiver
var receiver =
NodeBuilder.newVariableDeclaration(
newVariableDeclaration(
"this",
record != null
? TypeParser.createFrom(record.getName(), false)
Expand All @@ -197,7 +198,7 @@ record != null
}

ParamVariableDeclaration param =
NodeBuilder.newMethodParameterIn(
newMethodParameterIn(
parameter.getNameAsString(),
resolvedType,
parameter.isVarArgs(),
Expand Down Expand Up @@ -247,7 +248,7 @@ public RecordDeclaration handleClassOrInterfaceDeclaration(

// add a type declaration
RecordDeclaration recordDeclaration =
NodeBuilder.newRecordDeclaration(fqn, "class", classInterDecl.toString());
newRecordDeclaration(fqn, "class", classInterDecl.toString());
recordDeclaration.setSuperClasses(
classInterDecl.getExtendedTypes().stream()
.map(this.lang::getTypeAsGoodAsPossible)
Expand Down Expand Up @@ -318,7 +319,7 @@ public RecordDeclaration handleClassOrInterfaceDeclaration(

if (recordDeclaration.getConstructors().isEmpty()) {
de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration constructorDeclaration =
NodeBuilder.newConstructorDeclaration(
newConstructorDeclaration(
recordDeclaration.getName(), recordDeclaration.getName(), recordDeclaration);
recordDeclaration.addConstructor(constructorDeclaration);
lang.getScopeManager().addDeclaration(constructorDeclaration);
Expand Down Expand Up @@ -370,7 +371,7 @@ public de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration handleFieldDe
}
}
de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration fieldDeclaration =
NodeBuilder.newFieldDeclaration(
newFieldDeclaration(
variable.getName().asString(),
type,
modifiers,
Expand All @@ -391,7 +392,7 @@ public de.fraunhofer.aisec.cpg.graph.declarations.EnumDeclaration handleEnumDecl
PhysicalLocation location = this.lang.getLocationFromRawNode(enumDecl);

de.fraunhofer.aisec.cpg.graph.declarations.EnumDeclaration enumDeclaration =
NodeBuilder.newEnumDeclaration(name, enumDecl.toString(), location);
newEnumDeclaration(name, enumDecl.toString(), location);
List<de.fraunhofer.aisec.cpg.graph.declarations.EnumConstantDeclaration> entries =
enumDecl.getEntries().stream()
.map(
Expand All @@ -414,7 +415,7 @@ public de.fraunhofer.aisec.cpg.graph.declarations.EnumDeclaration handleEnumDecl
public de.fraunhofer.aisec.cpg.graph.declarations.EnumConstantDeclaration
handleEnumConstantDeclaration(
com.github.javaparser.ast.body.EnumConstantDeclaration enumConstDecl) {
return NodeBuilder.newEnumConstantDeclaration(
return newEnumConstantDeclaration(
enumConstDecl.getNameAsString(),
enumConstDecl.toString(),
this.lang.getLocationFromRawNode(enumConstDecl));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@
*/
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 static de.fraunhofer.aisec.cpg.graph.NodeBuilder.*;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseResult;
Expand Down Expand Up @@ -57,12 +56,12 @@
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.NodeBuilder;
import de.fraunhofer.aisec.cpg.graph.TypeManager;
import de.fraunhofer.aisec.cpg.graph.declarations.IncludeDeclaration;
import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration;
import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration;
import de.fraunhofer.aisec.cpg.graph.types.TypeParser;
import de.fraunhofer.aisec.cpg.graph.types.UnknownType;
import de.fraunhofer.aisec.cpg.helpers.Benchmark;
import de.fraunhofer.aisec.cpg.helpers.CommonPath;
import de.fraunhofer.aisec.cpg.passes.scopes.Scope;
Expand Down Expand Up @@ -136,7 +135,7 @@ public TranslationUnitDeclaration parse(File file) throws TranslationException {

// starting point is always a translation declaration
TranslationUnitDeclaration fileDeclaration =
NodeBuilder.newTranslationUnitDeclaration(file.toString(), context.toString());
newTranslationUnitDeclaration(file.toString(), context.toString());
setCurrentTU(fileDeclaration);

scopeManager.resetToGlobal(fileDeclaration);
Expand All @@ -145,8 +144,7 @@ public TranslationUnitDeclaration parse(File file) throws TranslationException {
NamespaceDeclaration namespaceDeclaration = null;
if (packDecl != null) {
namespaceDeclaration =
NodeBuilder.newNamespaceDeclaration(
packDecl.getName().asString(), getCodeFromRawNode(packDecl));
newNamespaceDeclaration(packDecl.getName().asString(), getCodeFromRawNode(packDecl));
this.setCodeAndRegion(namespaceDeclaration, packDecl);

scopeManager.addDeclaration(namespaceDeclaration);
Expand All @@ -160,7 +158,7 @@ public TranslationUnitDeclaration parse(File file) throws TranslationException {
}

for (ImportDeclaration anImport : context.getImports()) {
IncludeDeclaration incl = NodeBuilder.newIncludeDeclaration(anImport.getNameAsString());
IncludeDeclaration incl = newIncludeDeclaration(anImport.getNameAsString());
scopeManager.addDeclaration(incl);
}

Expand Down Expand Up @@ -259,12 +257,30 @@ public <T> PhysicalLocation getLocationFromRawNode(T astNode) {
de.fraunhofer.aisec.cpg.graph.types.Type getTypeAsGoodAsPossible(
NodeWithType<N, T> nodeWithType, ResolvedValueDeclaration resolved) {
try {
var type = nodeWithType.getTypeAsString();

if (type.equals("var")) {
return UnknownType.getUnknownType();
}

return TypeParser.createFrom(resolved.getType().describe(), true);
} catch (RuntimeException | NoClassDefFoundError ex) {
return getTypeFromImportIfPossible(nodeWithType.getType());
}
}

public de.fraunhofer.aisec.cpg.graph.types.Type getTypeAsGoodAsPossible(Type type) {
try {
if(type.toString().equals("var")) {
return UnknownType.getUnknownType();
}

return TypeParser.createFrom(type.resolve().describe(), true);
} catch (RuntimeException | NoClassDefFoundError ex) {
return getTypeFromImportIfPossible(type);
}
}

public String getQualifiedMethodNameAsGoodAsPossible(MethodCallExpr callExpr) {
try {
return callExpr.resolve().getQualifiedName();
Expand Down Expand Up @@ -352,14 +368,6 @@ public String getQualifiedNameFromImports(String className) {
return null;
}

public de.fraunhofer.aisec.cpg.graph.types.Type getTypeAsGoodAsPossible(Type type) {
try {
return TypeParser.createFrom(type.resolve().describe(), true);
} catch (RuntimeException | NoClassDefFoundError ex) {
return getTypeFromImportIfPossible(type);
}
}

public <N extends Node, T extends Type>
de.fraunhofer.aisec.cpg.graph.types.Type getReturnTypeAsGoodAsPossible(
NodeWithType<N, T> nodeWithType, ResolvedMethodDeclaration resolved) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ package de.fraunhofer.aisec.cpg.frontends.typescript
import de.fraunhofer.aisec.cpg.ExperimentalTypeScript
import de.fraunhofer.aisec.cpg.frontends.Handler
import de.fraunhofer.aisec.cpg.graph.NodeBuilder
import de.fraunhofer.aisec.cpg.graph.NodeBuilder.newLambdaExpression
import de.fraunhofer.aisec.cpg.graph.NodeBuilder.newLiteral
import de.fraunhofer.aisec.cpg.graph.bodyOrNull
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement
import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
import de.fraunhofer.aisec.cpg.graph.types.TypeParser
import de.fraunhofer.aisec.cpg.graph.types.UnknownType
Expand Down Expand Up @@ -113,19 +117,30 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) :

private fun handleArrowFunction(node: TypeScriptNode): Expression {
// parse as a function
val func = lang.declarationHandler.handle(node)
val func = lang.declarationHandler.handle(node) as? FunctionDeclaration

// the function will (probably) not have a defined return type, so we try to deduce this
// from a return statement
if (func?.type == UnknownType.getUnknownType()) {
val returnValue = func.bodyOrNull<ReturnStatement>()?.returnValue

/*if (returnValue == null) {
// we have a void function
func.type = TypeParser.createFrom("void", false)
} else {*/

val returnType = returnValue?.type ?: UnknownType.getUnknownType()

func.type = returnType
// }
}

// we cannot directly return a function declaration as an expression, so we
// wrap it into a reference expression
val ref =
NodeBuilder.newDeclaredReferenceExpression(
"",
UnknownType.getUnknownType(),
lang.getCodeFromRawNode(node)
)
ref.refersTo = func
// wrap it into a lambda expression
val lambda = newLambdaExpression(lang.getCodeFromRawNode(node))
lambda.function = func

return ref
return lambda
}

private fun handlePropertyAssignment(node: TypeScriptNode): KeyValueExpression {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -794,4 +794,10 @@ object NodeBuilder {
keyValue.code = code
return keyValue
}

fun newLambdaExpression(code: String?): LambdaExpression {
val lambda = LambdaExpression()
lambda.code = code
return lambda
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright (c) 2021, 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.statements.expressions

import de.fraunhofer.aisec.cpg.graph.HasType
import de.fraunhofer.aisec.cpg.graph.SubGraph
import de.fraunhofer.aisec.cpg.graph.TypeManager
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType
import de.fraunhofer.aisec.cpg.graph.types.Type

/**
* This expression denotes the usage of an anonymous / lambda function. It connects the inner
* anonymous function to the user of a lambda function with an expression.
*/
class LambdaExpression : Expression(), HasType.TypeListener {

@field:SubGraph("AST")
var function: FunctionDeclaration? = null
set(value) {
if (value != null) {
removePrevDFG(value)
value.unregisterTypeListener(this)
if (value is HasType.TypeListener) {
unregisterTypeListener(value as HasType.TypeListener?)
}
}
field = value
if (value != null) {
addPrevDFG(value)
value.registerTypeListener(this)
}
}

override fun typeChanged(src: HasType?, root: HasType?, oldType: Type?) {
if (!TypeManager.isTypeSystemActive()) {
return
}

if (!TypeManager.getInstance().isUnknown(type) && src!!.propagationType == oldType) {
return
}

if (src !is FunctionDeclaration) {
return
}

val previous = type

val parameterTypes = src.parameters.map { it.type }
val returnType = src.propagationType

// the incoming "type" is associated to the function and it is only its return type (if it
// is known). what we really want is to construct a function type, or rather a function
// pointer type, since this is the closest to what we have
val functionType =
FunctionPointerType(
Type.Qualifier(false, false, false, false),
Type.Storage.AUTO,
parameterTypes,
returnType
)

setType(functionType, root)
if (previous != type) {
type.typeOrigin = Type.Origin.DATAFLOW
}
}

override fun possibleSubTypesChanged(
src: HasType?,
root: HasType?,
oldSubTypes: MutableSet<Type>?
) {
// do not take sub types from the listener
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@
*/
package de.fraunhofer.aisec.cpg.helpers;

import static de.fraunhofer.aisec.cpg.graph.NodeBuilder.newMethodParameterIn;
import static de.fraunhofer.aisec.cpg.sarif.PhysicalLocation.locationLink;
import static java.nio.charset.StandardCharsets.UTF_8;

import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend;
import de.fraunhofer.aisec.cpg.graph.Node;
import de.fraunhofer.aisec.cpg.graph.NodeBuilder;
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration;
import de.fraunhofer.aisec.cpg.graph.declarations.ParamVariableDeclaration;
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression;
Expand Down Expand Up @@ -366,8 +366,7 @@ public static List<ParamVariableDeclaration> createInferredParameters(List<Type>
for (int i = 0; i < signature.size(); i++) {
Type targetType = signature.get(i);
String paramName = generateParamName(i, targetType);
ParamVariableDeclaration param =
NodeBuilder.newMethodParameterIn(paramName, targetType, false, "");
ParamVariableDeclaration param = newMethodParameterIn(paramName, targetType, false, "");
param.setInferred(true);
param.setArgumentIndex(i);
params.add(param);
Expand Down
Loading

0 comments on commit 793b58b

Please sign in to comment.