package org.checkerframework.checker.nullness; import java.util.LinkedHashSet; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.UnknownKeyFor; import org.checkerframework.dataflow.analysis.FlowExpressions; import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver; import org.checkerframework.dataflow.analysis.TransferInput; import org.checkerframework.dataflow.analysis.TransferResult; import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; import org.checkerframework.dataflow.cfg.node.Node; import org.checkerframework.framework.flow.CFAnalysis; import org.checkerframework.framework.flow.CFStore; import org.checkerframework.framework.flow.CFTransfer; import org.checkerframework.framework.flow.CFValue; import org.checkerframework.javacutil.AnnotationUtils; /* * KeyForTransfer ensures that java.util.Map.put and containsKey * cause the appropriate @KeyFor annotation to be added to the key. */ public class KeyForTransfer extends CFTransfer { protected final AnnotationMirror UNKNOWNKEYFOR, KEYFOR; public KeyForTransfer(CFAnalysis analysis) { super(analysis); UNKNOWNKEYFOR = AnnotationUtils.fromClass( analysis.getTypeFactory().getElementUtils(), UnknownKeyFor.class); KEYFOR = AnnotationUtils.fromClass( analysis.getTypeFactory().getElementUtils(), KeyFor.class); } /* * Provided that m is of a type that implements interface java.util.Map: * <ul> * <li>Given a call m.containsKey(k), ensures that k is @KeyFor("m") in the thenStore of the transfer result. * <li>Given a call m.put(k, ...), ensures that k is @KeyFor("m") in the thenStore and elseStore of the transfer result. * </ul> */ @Override public TransferResult<CFValue, CFStore> visitMethodInvocation( MethodInvocationNode node, TransferInput<CFValue, CFStore> in) { TransferResult<CFValue, CFStore> result = super.visitMethodInvocation(node, in); KeyForAnnotatedTypeFactory factory = (KeyForAnnotatedTypeFactory) analysis.getTypeFactory(); if (factory.isInvocationOfMapMethod(node, "containsKey") || factory.isInvocationOfMapMethod(node, "put")) { Node receiver = node.getTarget().getReceiver(); Receiver internalReceiver = FlowExpressions.internalReprOf(factory, receiver); String mapName = internalReceiver.toString(); Receiver keyReceiver = FlowExpressions.internalReprOf(factory, node.getArgument(0)); LinkedHashSet<String> keyForMaps = new LinkedHashSet<>(); keyForMaps.add(mapName); final CFValue previousKeyValue = in.getValueOfSubNode(node.getArgument(0)); if (previousKeyValue != null) { for (AnnotationMirror prevAm : previousKeyValue.getAnnotations()) { if (prevAm != null && AnnotationUtils.areSameByClass(prevAm, KeyFor.class)) { keyForMaps.addAll(getKeys(prevAm)); } } } AnnotationMirror am = factory.createKeyForAnnotationMirrorWithValue(keyForMaps); if (factory.getMethodName(node).equals("containsKey")) { result.getThenStore().insertValue(keyReceiver, am); } else { // method name is "put" result.getThenStore().insertValue(keyReceiver, am); result.getElseStore().insertValue(keyReceiver, am); } } return result; } /** @return the String value of a KeyFor, this will throw an exception */ private Set<String> getKeys(final AnnotationMirror keyFor) { if (keyFor.getElementValues().size() == 0) { return new LinkedHashSet<>(); } return new LinkedHashSet<>( AnnotationUtils.getElementValueArray(keyFor, "value", String.class, true)); } }