Skip to content

Commit c83fce6

Browse files
authored
feat: Visit all fields in Lambda Handler class to support Dependency Injection (#26)
* Resolved merge conflicts by incorporating Functional interface changes * Visit all fields in Lambda Handler class to support Dependency Injection
1 parent f2f8d85 commit c83fce6

9 files changed

Lines changed: 141 additions & 1 deletion

File tree

src/main/java/software/amazon/lambda/snapstart/ByteCodeIntrospector.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
import edu.umd.cs.findbugs.ba.XMethod;
99
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
1010
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
11+
import edu.umd.cs.findbugs.classfile.FieldDescriptor;
12+
import edu.umd.cs.findbugs.classfile.Global;
13+
import org.apache.bcel.generic.Type;
14+
1115
import java.util.Collections;
1216
import java.util.HashMap;
1317
import java.util.HashSet;
@@ -36,6 +40,8 @@ public class ByteCodeIntrospector {
3640
put("java.lang.System", setOf("currentTimeMillis", "nanoTime"));
3741
}};
3842

43+
private LambdaHandlerFieldsDatabase database;
44+
3945
private static Set<String> setOf(String ... strings) {
4046
Set<String> set = new HashSet<>();
4147
Collections.addAll(set, strings);
@@ -95,6 +101,23 @@ boolean implementsFunctionalInterface(XClass xClass) {
95101
// ignore
96102
}
97103
}
104+
105+
return false;
106+
}
107+
108+
/**
109+
* This returns true only if this class is used as a field in a Lambda handler class
110+
*/
111+
boolean isLambdaHandlerField(XClass xClass) {
112+
database = Global.getAnalysisCache().getDatabase(LambdaHandlerFieldsDatabase.class);
113+
for (FieldDescriptor fieldDescriptor : database.getKeys()) {
114+
115+
116+
String fieldType = Type.getReturnType(fieldDescriptor.getSignature()).toString().replace(".", "/");
117+
if (fieldType.equals(xClass.toString())) {
118+
return true;
119+
}
120+
}
98121
return false;
99122
}
100123

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package software.amazon.lambda.snapstart;
2+
3+
import edu.umd.cs.findbugs.BugReporter;
4+
import edu.umd.cs.findbugs.Detector;
5+
import edu.umd.cs.findbugs.ba.ClassContext;
6+
import edu.umd.cs.findbugs.ba.XClass;
7+
import edu.umd.cs.findbugs.classfile.DescriptorFactory;
8+
import edu.umd.cs.findbugs.classfile.FieldDescriptor;
9+
import edu.umd.cs.findbugs.classfile.Global;
10+
import org.apache.bcel.classfile.Field;
11+
12+
/**
13+
* This detector stores fields with the Lambda Handler and Crac resources to be used later
14+
* for visiting the classes passed in through dependency injection
15+
*/
16+
public class CacheLambdaHandlerFields implements Detector {
17+
18+
private final ByteCodeIntrospector introspector;
19+
private ClassContext classContext;
20+
private XClass xClass;
21+
private final LambdaHandlerFieldsDatabase database;
22+
23+
24+
public CacheLambdaHandlerFields(BugReporter reporter) {
25+
introspector = new ByteCodeIntrospector();
26+
database = new LambdaHandlerFieldsDatabase();
27+
Global.getAnalysisCache().eagerlyPutDatabase(LambdaHandlerFieldsDatabase.class, database);
28+
}
29+
30+
@Override
31+
public void visitClassContext(ClassContext classContext) {
32+
this.classContext = classContext;
33+
this.xClass = classContext.getXClass();
34+
if (introspector.isLambdaHandler(xClass)) {
35+
Field[] fields = classContext.getJavaClass().getFields();
36+
for (Field field : fields) {
37+
FieldDescriptor fieldDescriptor = DescriptorFactory.instance().getFieldDescriptor(xClass.toString().replace(".", "/"), field);
38+
database.setProperty(fieldDescriptor, true);
39+
}
40+
}
41+
}
42+
43+
@Override
44+
public void report() {
45+
// this is a non-reporting detector
46+
}
47+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package software.amazon.lambda.snapstart;
2+
3+
import edu.umd.cs.findbugs.ba.interproc.FieldPropertyDatabase;
4+
import edu.umd.cs.findbugs.ba.interproc.PropertyDatabaseFormatException;
5+
6+
public class LambdaHandlerFieldsDatabase extends FieldPropertyDatabase<Boolean> {
7+
8+
public LambdaHandlerFieldsDatabase() {
9+
super();
10+
}
11+
12+
@Override
13+
protected Boolean decodeProperty(String s) throws PropertyDatabaseFormatException {
14+
return null;
15+
}
16+
17+
@Override
18+
protected String encodeProperty(Boolean aBoolean) {
19+
return null;
20+
}
21+
22+
}

src/main/java/software/amazon/lambda/snapstart/LambdaHandlerInitedWithRandomValue.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class LambdaHandlerInitedWithRandomValue extends OpcodeStackDetector {
2525
private final BugReporter bugReporter;
2626
private boolean isLambdaHandlerClass;
2727
private boolean implementsFunctionalInterface;
28+
private boolean isLambdaHandlerField;
2829
private boolean isCracResource;
2930
private boolean inInitializer;
3031
private boolean inStaticInitializer;
@@ -36,6 +37,7 @@ public LambdaHandlerInitedWithRandomValue(BugReporter bugReporter) {
3637
this.bugReporter = bugReporter;
3738
this.isLambdaHandlerClass = false;
3839
this.implementsFunctionalInterface = false;
40+
this.isLambdaHandlerField = false;
3941
this.isCracResource = false;
4042
this.inInitializer = false;
4143
this.inStaticInitializer = false;
@@ -51,13 +53,14 @@ public void visit(JavaClass obj) {
5153
XClass xClass = getXClass();
5254
isLambdaHandlerClass = introspector.isLambdaHandler(xClass);
5355
implementsFunctionalInterface = introspector.implementsFunctionalInterface(xClass);
56+
isLambdaHandlerField = introspector.isLambdaHandlerField(xClass);
5457
isCracResource = introspector.isCracResource(xClass);
5558
}
5659

5760
@Override
5861
public boolean shouldVisitCode(Code code) {
5962
boolean shouldVisit = false;
60-
if (isLambdaHandlerClass || implementsFunctionalInterface) {
63+
if (isLambdaHandlerClass || implementsFunctionalInterface || isLambdaHandlerField) {
6164
inStaticInitializer = getMethodName().equals(Const.STATIC_INITIALIZER_NAME);
6265
inInitializer = getMethodName().equals(Const.CONSTRUCTOR_NAME);
6366
database = Global.getAnalysisCache().getDatabase(ReturnValueRandomnessPropertyDatabase.class);

src/main/resources/findbugs.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,16 @@
1616
<Earlier class="software.amazon.lambda.snapstart.BuildRandomReturningMethodsDatabase"/>
1717
<Later class="software.amazon.lambda.snapstart.LambdaHandlerInitedWithRandomValue"/>
1818
</SplitPass>
19+
<SplitPass>
20+
<Earlier class="software.amazon.lambda.snapstart.CacheLambdaHandlerFields"/>
21+
<Later class="software.amazon.lambda.snapstart.LambdaHandlerInitedWithRandomValue"/>
22+
</SplitPass>
1923
</OrderingConstraints>
2024

2125
<Detector class="software.amazon.lambda.snapstart.BuildRandomReturningMethodsDatabase"
2226
speed="fast" reports="" disabled="false" hidden="true"/>
27+
<Detector class="software.amazon.lambda.snapstart.CacheLambdaHandlerFields"
28+
speed="fast" reports="" disabled="false" hidden="true"/>
2329
<Detector class="software.amazon.lambda.snapstart.LambdaHandlerInitedWithRandomValue"
2430
reports="AWS_LAMBDA_SNAP_START_BUG" />
2531

src/main/resources/messages.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
</Details>
1414
</Detector>
1515

16+
<Detector class="software.amazon.lambda.snapstart.CacheLambdaHandlerFields">
17+
<Details>
18+
Detector that stores all the fields of the Lambda Handler class to be visited later.
19+
</Details>
20+
</Detector>
21+
1622
<Detector class="software.amazon.lambda.snapstart.LambdaHandlerInitedWithRandomValue">
1723
<Details>
1824
Main detector to find out SnapStart bugs in Lambda handler classes.

src/test/java/software/amazon/lambda/snapstart/LambdaHandlerInitedWithRandomValueTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,12 @@ public void testClassImplementingFunctionalInterface() {
158158
assertThat(bugCollection, containsExactly(1, snapStartBugMatcher().inClass("ImplementsFunctionalInterface").atField("random").atLine(8).build()));
159159
}
160160

161+
@Test
162+
public void testLambdaWithDependencyInjection() {
163+
BugCollection bugCollection = findBugsInClasses("DependencyInjection", "MyDependency");
164+
assertThat(bugCollection, containsExactly(1, snapStartBugMatcher().inClass("MyDependency").atField("random").atLine(6).build()));
165+
}
166+
161167
// TODO fix all tests using this and remove this method eventually!
162168
private static void toBeFixed(Executable e) {
163169
assertThrows(AssertionError.class, e);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package software.amazon.lambda.snapstart.lambdaexamples;
2+
3+
import com.amazonaws.services.lambda.runtime.Context;
4+
import com.amazonaws.services.lambda.runtime.RequestHandler;
5+
6+
public class DependencyInjection implements RequestHandler<String, String> {
7+
private final MyDependency myDependency;
8+
9+
public DependencyInjection(MyDependency myDependency) {
10+
this.myDependency = myDependency;
11+
}
12+
@Override
13+
public String handleRequest(String s, Context context) {
14+
return myDependency.getUUID().toString();
15+
}
16+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package software.amazon.lambda.snapstart.lambdaexamples;
2+
3+
import java.util.UUID;
4+
5+
public class MyDependency {
6+
private final UUID random = UUID.randomUUID();
7+
8+
public UUID getUUID() {
9+
return random;
10+
}
11+
}

0 commit comments

Comments
 (0)