Merge branch 'clem.type-annots-in-infer' into 7.0.x

This commit is contained in:
Clément Fournier committed 2023-01-08 18:31:16 +01:00
commit 9964f116a4
113 files changed
+3454 -1158

No files matched your search

@@ -134,7 +134,7 @@ abstract class IteratorBasedNStream<T extends Node> implements NodeStream<T> {
}
@Override
public <R, A> R collect(Collector<? super T, A, R> collector) {
public final <R, A> R collect(Collector<? super T, A, R> collector) {
A container = collector.supplier().get();
BiConsumer<A, ? super T> accumulator = collector.accumulator();
forEach(u -> accumulator.accept(container, u));
@@ -6,10 +6,8 @@ package net.sourceforge.pmd.util;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyIterator;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static java.util.Collections.singletonList;
import java.util.ArrayList;
import java.util.Collection;
@@ -36,8 +34,10 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.Validate;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.pcollections.ConsPStack;
import org.pcollections.HashTreePSet;
import org.pcollections.PMap;
import org.pcollections.PSequence;
import org.pcollections.PSet;
import net.sourceforge.pmd.annotation.InternalApi;
@@ -159,9 +159,9 @@ public final class CollectionUtil {
*/
public static <T> List<T> concatView(List<? extends T> head, List<? extends T> tail) {
if (head.isEmpty()) {
return Collections.unmodifiableList(tail);
return makeUnmodifiableAndNonNull(tail);
} else if (tail.isEmpty()) {
return Collections.unmodifiableList(head);
return makeUnmodifiableAndNonNull(head);
} else {
return new ConsList<>(head, tail);
}
@@ -268,7 +268,7 @@ public final class CollectionUtil {
@SafeVarargs
public static <T> List<T> listOf(T first, T... rest) {
if (rest.length == 0) {
return Collections.singletonList(first);
return ConsPStack.singleton(first);
}
List<T> union = new ArrayList<>();
union.add(first);
@@ -323,6 +323,26 @@ public final class CollectionUtil {
return newM;
}
/**
* Produce a new list with the elements of the first, and one additional
* item. The returned list is immutable.
*/
public static <V> List<V> plus(List<V> list, V v) {
if (list instanceof PSequence) {
return ((PSequence<V>) list).plus(v);
} else if (list.isEmpty()) {
return ConsPStack.singleton(v);
}
return ConsPStack.from(list).plus(v);
}
/** Returns the empty list. */
public static <V> List<V> emptyList() {
// We use this implementation so that it plays well with other
// operations that expect immutable data.
return ConsPStack.empty();
}
/**
* Returns an unmodifiable set containing the set union of the collection,
* and the new elements.
@@ -461,7 +481,7 @@ public final class CollectionUtil {
if (!from.hasNext()) {
return emptyList();
} else if (sizeHint == 1) {
return Collections.singletonList(f.apply(from.next()));
return ConsPStack.singleton(f.apply(from.next()));
}
List<R> res = sizeHint == UNKNOWN_SIZE ? new ArrayList<>() : new ArrayList<>(sizeHint);
while (from.hasNext()) {
@@ -592,7 +612,7 @@ public final class CollectionUtil {
public static <T> List<T> listOfNotNull(T t) {
return t == null ? emptyList() : singletonList(t);
return t == null ? emptyList() : ConsPStack.singleton(t);
}
/**
@@ -641,10 +661,15 @@ public final class CollectionUtil {
* @param <T> Type of items
*/
public static <T> List<T> defensiveUnmodifiableCopy(List<? extends T> list) {
if (list.isEmpty()) {
return emptyList();
if (list instanceof PSequence) {
return (List<T>) list; // is already immutable
}
return Collections.unmodifiableList(new ArrayList<>(list));
if (list.isEmpty()) {
return ConsPStack.empty();
} else if (list.size() == 1) {
return ConsPStack.singleton(list.get(0));
}
return ConsPStack.from(list);
}
public static <T> Set<T> defensiveUnmodifiableCopyToSet(Collection<? extends T> list) {
@@ -696,6 +721,9 @@ public final class CollectionUtil {
}
public static @NonNull <T> List<T> makeUnmodifiableAndNonNull(@Nullable List<? extends T> list) {
if (list instanceof PSequence) {
return (List<T>) list;
}
return list == null || list.isEmpty() ? emptyList()
: Collections.unmodifiableList(list);
}
+1 -1
View File
@@ -1578,7 +1578,7 @@ void TypeArgument() #void:
void WildcardType():
{}
{
"?" [ ("extends" {jjtThis.setUpperBound(true);}| "super") AnnotatedRefType() ]
"?" [ ("extends" | "super" {jjtThis.setLowerBound(true);}) AnnotatedRefType() ]
}
@@ -24,7 +24,7 @@ public final class ASTArrayType extends AbstractJavaTypeNode implements ASTRefer
@Override
public NodeStream<ASTAnnotation> getDeclaredAnnotations() {
return getDimensions().getLastChild().getDeclaredAnnotations();
return getDimensions().getFirstChild().getDeclaredAnnotations();
}
public ASTArrayDimensions getDimensions() {
@@ -18,14 +18,14 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*/
public final class ASTWildcardType extends AbstractJavaTypeNode implements ASTReferenceType {
private boolean isUpperBound;
private boolean isLowerBound;
ASTWildcardType(int id) {
super(id);
}
void setUpperBound(boolean upperBound) {
isUpperBound = upperBound;
void setLowerBound(boolean lowerBound) {
isLowerBound = lowerBound;
}
@Override
@@ -34,11 +34,12 @@ public final class ASTWildcardType extends AbstractJavaTypeNode implements ASTRe
}
/**
* Returns true if this is an upper type bound, e.g.
* in {@code <? extends Integer>}.
* Return true if this is an upper type bound, e.g.
* {@code <? extends Integer>}, or the unbounded
* wildcard {@code <?>}.
*/
public boolean hasUpperBound() {
return isUpperBound && getNumChildren() > 0;
public boolean isUpperBound() {
return !isLowerBound;
}
@@ -46,18 +47,18 @@ public final class ASTWildcardType extends AbstractJavaTypeNode implements ASTRe
* Returns true if this is a lower type bound, e.g.
* in {@code <? super Node>}.
*/
public boolean hasLowerBound() {
return !isUpperBound && getNumChildren() > 0;
public boolean isLowerBound() {
return isLowerBound;
}
/**
* Returns the type node representing the bound, e.g.
* the {@code Node} in {@code <? super Node>}, or null.
* the {@code Node} in {@code <? super Node>}, or null in
* the unbounded wildcard {@code <?>}.
*/
@Nullable
public ASTReferenceType getTypeBoundNode() {
return getFirstChildOfType(ASTReferenceType.class);
public @Nullable ASTReferenceType getTypeBoundNode() {
return firstChild(ASTReferenceType.class);
}
@Override
@@ -33,8 +33,7 @@ public interface Annotatable extends JavaNode {
* Returns true if an annotation with the given qualified name is
* applied to this node.
*
* @param annotQualifiedName
* Note: for now, canonical names are tolerated, this may be changed in PMD 7.
* @param annotQualifiedName Note: for now, canonical names are tolerated, this may be changed in PMD 7.
*/
default boolean isAnnotationPresent(String annotQualifiedName) {
return getDeclaredAnnotations().any(t -> TypeTestUtil.isA(StringUtils.deleteWhitespace(annotQualifiedName), t));
@@ -17,6 +17,7 @@ import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol;
import net.sourceforge.pmd.lang.java.symbols.JElementSymbol;
import net.sourceforge.pmd.lang.java.symbols.JMethodSymbol;
import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
import net.sourceforge.pmd.lang.java.symbols.JTypeParameterSymbol;
import net.sourceforge.pmd.lang.java.symbols.JVariableSymbol;
import net.sourceforge.pmd.lang.java.symbols.table.JSymbolTable;
@@ -207,6 +208,10 @@ public final class InternalApiBridge {
return TypesFromAst.fromAst(ts, lexicalSubst, node);
}
public static JTypeDeclSymbol getReferencedSym(ASTClassOrInterfaceType type) {
return type.getReferencedSym();
}
public static void setTypedSym(ASTFieldAccess expr, JVariableSig.FieldSig sym) {
expr.setTypedSym(sym);
}
@@ -4,6 +4,7 @@
package net.sourceforge.pmd.lang.java.ast;
import java.lang.annotation.ElementType;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
@@ -11,10 +12,13 @@ import java.util.List;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.pcollections.PSet;
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
import net.sourceforge.pmd.lang.java.symbols.JTypeParameterSymbol;
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue.SymAnnot;
import net.sourceforge.pmd.lang.java.symbols.internal.ast.SymbolResolutionPass;
import net.sourceforge.pmd.lang.java.symbols.table.internal.JavaResolvers;
import net.sourceforge.pmd.lang.java.types.JClassType;
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
@@ -60,9 +64,10 @@ final class TypesFromAst {
ASTWildcardType wild = (ASTWildcardType) node;
@Nullable JTypeMirror bound = fromAst(ts, lexicalSubst, wild.getTypeBoundNode());
return bound == null
? ts.UNBOUNDED_WILD
: ts.wildcard(wild.hasUpperBound(), bound);
if (bound == null) {
bound = ts.OBJECT;
}
return ts.wildcard(wild.isUpperBound(), bound).withAnnotations(getTypeAnnotations(node));
} else if (node instanceof ASTIntersectionType) {
@@ -79,13 +84,20 @@ final class TypesFromAst {
}
} else if (node instanceof ASTArrayType) {
JTypeMirror eltType = fromAst(ts, lexicalSubst, ((ASTArrayType) node).getElementType());
JTypeMirror t = fromAst(ts, lexicalSubst, ((ASTArrayType) node).getElementType());
ASTArrayDimensions dimensions = ((ASTArrayType) node).getDimensions();
// we have to iterate in reverse
for (int i = dimensions.size() - 1; i >= 0; i--) {
ASTArrayTypeDim dim = dimensions.get(i);
PSet<SymAnnot> annots = getSymbolicAnnotations(dim);
t = ts.arrayType(t).withAnnotations(annots);
}
return ts.arrayType(eltType, node.getArrayDepth());
return t;
} else if (node instanceof ASTPrimitiveType) {
return ts.getPrimitive(((ASTPrimitiveType) node).getKind());
return ts.getPrimitive(((ASTPrimitiveType) node).getKind()).withAnnotations(getTypeAnnotations(node));
} else if (node instanceof ASTAmbiguousName) {
@@ -95,29 +107,36 @@ final class TypesFromAst {
return ts.lub(CollectionUtil.map(((ASTUnionType) node).getComponents(), TypeNode::getTypeMirror));
} else if (node instanceof ASTVoidType) {
return ts.NO_TYPE;
}
throw new IllegalStateException("Illegal type " + node.getClass() + " " + node);
}
private static JTypeMirror makeFromClassType(TypeSystem ts, ASTClassOrInterfaceType node, Substitution subst) {
private static PSet<SymAnnot> getSymbolicAnnotations(Annotatable dim) {
return SymbolResolutionPass.buildSymbolicAnnotations(dim.getDeclaredAnnotations());
}
private static JTypeMirror makeFromClassType(TypeSystem ts, ASTClassOrInterfaceType node, Substitution subst) {
if (node == null) {
return null;
}
// TODO error handling, what if we're saying List<String, Int> in source: should be caught before
ASTClassOrInterfaceType lhsType = node.getQualifier();
PSet<SymAnnot> typeAnnots = getTypeAnnotations(node);
JTypeDeclSymbol reference = getReferenceEnsureResolved(node);
if (reference instanceof JTypeParameterSymbol) {
return subst.apply(((JTypeParameterSymbol) reference).getTypeMirror());
return subst.apply(((JTypeParameterSymbol) reference).getTypeMirror()).withAnnotations(typeAnnots);
}
JClassType enclosing = getEnclosing(ts, node, subst, lhsType, reference);
JClassType enclosing = getEnclosing(ts, node, subst, node.getQualifier(), reference);
ASTTypeArguments typeArguments = node.getTypeArguments();
@@ -134,13 +153,13 @@ final class TypesFromAst {
}
if (enclosing != null) {
return enclosing.selectInner((JClassSymbol) reference, boundGenerics);
return enclosing.selectInner((JClassSymbol) reference, boundGenerics, typeAnnots);
} else {
return ts.parameterise((JClassSymbol) reference, boundGenerics);
return ts.parameterise((JClassSymbol) reference, boundGenerics).withAnnotations(typeAnnots);
}
}
private static @Nullable JClassType getEnclosing(TypeSystem ts, ASTClassOrInterfaceType node, Substitution subst, ASTClassOrInterfaceType lhsType, JTypeDeclSymbol reference) {
private static @Nullable JClassType getEnclosing(TypeSystem ts, ASTClassOrInterfaceType node, Substitution subst, @Nullable ASTClassOrInterfaceType lhsType, JTypeDeclSymbol reference) {
@Nullable JTypeMirror enclosing = makeFromClassType(ts, lhsType, subst);
if (enclosing != null && !shouldEnclose(reference)) {
@@ -220,4 +239,42 @@ final class TypesFromAst {
private static boolean shouldEnclose(JTypeDeclSymbol reference) {
return !Modifier.isStatic(reference.getModifiers());
}
/**
* Returns the variable declaration or field or formal, etc, that
* may give additional type annotations to the given type.
*/
private static @Nullable Annotatable getEnclosingAnnotationGiver(JavaNode node) {
JavaNode parent = node.getParent();
if (node.getIndexInParent() == 0 && parent instanceof ASTClassOrInterfaceType) {
// this is an enclosing type
return getEnclosingAnnotationGiver(parent);
} else if (node.getIndexInParent() == 0 && parent instanceof ASTArrayType) {
// the element type of an array type
return getEnclosingAnnotationGiver(parent);
} else if (!(parent instanceof ASTType) && parent instanceof ASTVariableDeclarator) {
return getEnclosingAnnotationGiver(parent);
} else if (!(parent instanceof ASTType) && parent instanceof Annotatable) {
return (Annotatable) parent;
}
return null;
}
private static PSet<SymAnnot> getTypeAnnotations(ASTType type) {
PSet<SymAnnot> annotsOnType = getSymbolicAnnotations(type);
if (type instanceof ASTClassOrInterfaceType && ((ASTClassOrInterfaceType) type).getQualifier() != null) {
return annotsOnType; // annots on the declaration only apply to the leftmost qualifier
}
Annotatable parent = getEnclosingAnnotationGiver(type);
if (parent != null) {
PSet<SymAnnot> parentAnnots = getSymbolicAnnotations(parent);
for (SymAnnot parentAnnot : parentAnnots) {
// filter annotations by whether they apply to the type use.
if (parentAnnot.getAnnotationSymbol().annotationAppliesTo(ElementType.TYPE_USE)) {
annotsOnType = annotsOnType.plus(parentAnnot);
}
}
}
return annotsOnType;
}
}
@@ -117,7 +117,7 @@ public final class PrettyPrintingUtil {
sb.append("?");
ASTReferenceType bound = ((ASTWildcardType) t).getTypeBoundNode();
if (bound != null) {
sb.append(((ASTWildcardType) t).hasLowerBound() ? " super " : " extends ");
sb.append(((ASTWildcardType) t).isLowerBound() ? " super " : " extends ");
prettyPrintTypeNode(sb, bound);
}
} else if (t instanceof ASTUnionType) {
@@ -233,7 +233,7 @@ public final class PrettyPrintingUtil {
}
private static TypePrettyPrinter overloadPrinter() {
return new TypePrettyPrinter().useSimpleNames(true).printMethodResult(false);
return new TypePrettyPrinter().qualifyNames(false).printMethodResult(false);
}
@@ -58,7 +58,7 @@ public final class JavaAstProcessor {
static {
Level level;
try {
level = Level.valueOf(System.getenv("PMD_DEBUG_LEVEL").toLowerCase(Locale.ROOT));
level = Level.valueOf(System.getenv("PMD_DEBUG_LEVEL").toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException | NullPointerException ignored) {
level = null;
}
@@ -6,25 +6,16 @@ package net.sourceforge.pmd.lang.java.rule.design;
import static net.sourceforge.pmd.lang.java.ast.AccessNode.Visibility.V_PRIVATE;
import java.util.List;
import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTFieldAccess;
import net.sourceforge.pmd.lang.java.ast.JModifier;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
public class ClassWithOnlyPrivateConstructorsShouldBeFinalRule extends AbstractJavaRulechainRule {
private static final String LOMBOK_VALUE = "lombok.Value";
public static final String LOMBOK_NO_ARGS_CONSTRUCTOR = "lombok.NoArgsConstructor";
public static final String LOMBOK_REQUIRED_ARGS_CONSTRUCTOR = "lombok.RequiredArgsConstructor";
public static final String LOMBOK_ALL_ARGS_CONSTRUCTOR = "lombok.AllArgsConstructor";
public static final String LOMBOK_PRIVATE_ACCESS = "PRIVATE";
public ClassWithOnlyPrivateConstructorsShouldBeFinalRule() {
super(ASTClassOrInterfaceDeclaration.class);
}
@@ -33,7 +24,7 @@ public class ClassWithOnlyPrivateConstructorsShouldBeFinalRule extends AbstractJ
public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
if (node.isRegularClass()
&& !node.hasModifiers(JModifier.FINAL)
&& !node.isAnnotationPresent(LOMBOK_VALUE)
&& !node.isAnnotationPresent("lombok.Value")
&& !hasPublicLombokConstructors(node)
&& hasOnlyPrivateCtors(node)
&& hasNoSubclasses(node)) {
@@ -43,19 +34,11 @@ public class ClassWithOnlyPrivateConstructorsShouldBeFinalRule extends AbstractJ
}
private boolean hasPublicLombokConstructors(ASTClassOrInterfaceDeclaration node) {
List<ASTAnnotation> annotations = node.getDeclaredAnnotations()
.filter(t -> TypeTestUtil.isA(LOMBOK_NO_ARGS_CONSTRUCTOR, t)
|| TypeTestUtil.isA(LOMBOK_REQUIRED_ARGS_CONSTRUCTOR, t)
|| TypeTestUtil.isA(LOMBOK_ALL_ARGS_CONSTRUCTOR, t))
.toList();
return !annotations.isEmpty()
&& annotations.stream().noneMatch(this::hasPrivateAccessModifierOption);
}
private boolean hasPrivateAccessModifierOption(ASTAnnotation annotation) {
return annotation.getFlatValue("access")
.filterIs(ASTFieldAccess.class)
.any(it -> LOMBOK_PRIVATE_ACCESS.equals(it.getName()));
return node.getDeclaredAnnotations()
.filter(it -> TypeTestUtil.isA("lombok.NoArgsConstructor", it)
|| TypeTestUtil.isA("lombok.RequiredArgsConstructor", it)
|| TypeTestUtil.isA("lombok.AllArgsConstructor", it))
.any(it -> it.getFlatValue("access").filterIs(ASTNamedReferenceExpr.class).none(ref -> "PRIVATE".equals(ref.getName())));
}
private boolean hasNoSubclasses(ASTClassOrInterfaceDeclaration klass) {
@@ -199,7 +199,7 @@ public class CloseResourceRule extends AbstractJavaRule {
.filterNot(ASTVariableDeclaratorId::isExceptionBlockParameter)
.filter(this::isVariableNotSpecifiedInTryWithResource)
.filter(var -> isResourceTypeOrSubtype(var) || isNodeInstanceOfResourceType(getTypeOfVariable(var)))
.filter(var -> var.getAnnotation("lombok.Cleanup") == null)
.filterNot(var -> var.isAnnotationPresent("lombok.Cleanup"))
.toList();
for (ASTVariableDeclaratorId var : vars) {
@@ -5,17 +5,15 @@
package net.sourceforge.pmd.lang.java.symbols;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue.SymAnnot;
import net.sourceforge.pmd.lang.java.symbols.internal.SymbolEquality;
import net.sourceforge.pmd.lang.java.symbols.internal.SymbolToStrings;
import net.sourceforge.pmd.lang.java.types.TypeSystem;
/**
@@ -43,20 +41,8 @@ final class AnnotWrapper implements SymAnnot {
}
@Override
public RetentionPolicy getRetention() {
Retention annot = annotationClass.getAnnotation(Retention.class);
return annot != null ? annot.value()
: RetentionPolicy.CLASS;
}
@Override
public boolean isOfType(String binaryName) {
return annotationClassSymbol.getBinaryName().equals(binaryName);
}
@Override
public Set<String> getAttributeNames() {
return annotationClassSymbol.getAnnotationAttributeNames();
public @NonNull JClassSymbol getAnnotationSymbol() {
return annotationClassSymbol;
}
@Override
@@ -80,12 +66,6 @@ final class AnnotWrapper implements SymAnnot {
return annotation.equals(o);
}
@Override
public String getBinaryName() {
return annotationClassSymbol.getBinaryName();
}
@Override
public boolean equals(Object o) {
return SymbolEquality.ANNOTATION.equals(this, o);
@@ -96,5 +76,8 @@ final class AnnotWrapper implements SymAnnot {
return SymbolEquality.ANNOTATION.hash(this);
}
@Override
public String toString() {
return SymbolToStrings.FAKE.toString(this);
}
}
@@ -5,20 +5,32 @@
package net.sourceforge.pmd.lang.java.symbols;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.List;
import org.pcollections.HashTreePSet;
import org.pcollections.PSet;
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue.SymAnnot;
/**
*
* A symbol that can have annotations.
*/
public interface AnnotableSymbol extends JElementSymbol {
default List<SymAnnot> getDeclaredAnnotations() {
return Collections.emptyList();
/**
* Return the valid symbolic annotations defined on this symbol.
* Annotations that could not be converted, eg because
* they are written with invalid code, are discarded, so
* this might not match the annotations on a node one to one.
*/
default PSet<SymAnnot> getDeclaredAnnotations() {
return HashTreePSet.empty();
}
/**
* Return an annotation of the given type, if it is present on this declaration.
* This does not consider inherited annotations.
*/
default SymbolicValue.SymAnnot getDeclaredAnnotation(Class<? extends Annotation> type) {
for (SymAnnot a : getDeclaredAnnotations()) {
if (a.isOfType(type)) {
@@ -28,12 +40,11 @@ public interface AnnotableSymbol extends JElementSymbol {
return null;
}
/**
* Return true if an annotation of the given type is present on this declaration.
*/
default boolean isAnnotationPresent(Class<? extends Annotation> type) {
for (SymAnnot a : getDeclaredAnnotations()) {
if (a.isOfType(type)) {
return true;
}
}
return false;
return getDeclaredAnnotation(type) != null;
}
}
@@ -5,18 +5,22 @@
package net.sourceforge.pmd.lang.java.symbols;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.pcollections.HashTreePSet;
import org.pcollections.PSet;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue.SymAnnot;
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue.SymEnum;
import net.sourceforge.pmd.lang.java.types.JArrayType;
import net.sourceforge.pmd.lang.java.types.JClassType;
@@ -227,8 +231,30 @@ public interface JClassSymbol extends JTypeDeclSymbol,
* Return the simple names of all annotation attributes. If this
* is not an annotation type, return an empty set.
*/
default Set<String> getAnnotationAttributeNames() {
return Collections.emptySet();
default PSet<String> getAnnotationAttributeNames() {
return HashTreePSet.empty();
}
/**
* Return the default value of the attribute if this is an annotation type
* with a default. Return null if this is not an annotation type, if there
* is no such attribute, or the attribute has no default value. If the name
* is in the {@linkplain #getAnnotationAttributeNames() attribute name set},
* then the null return value can only mean that the attribute exists but has
* no default value.
*
* @param attrName Attribute name
*/
default @Nullable SymbolicValue getDefaultAnnotationAttributeValue(String attrName) {
if (!isAnnotation()) {
return null;
}
for (JMethodSymbol m : getDeclaredMethods()) {
if (m.nameEquals(attrName) && m.isAnnotationAttribute()) {
return m.getDefaultAnnotationValue(); // nullable
}
}
return null;
}
/**
@@ -239,14 +265,32 @@ public interface JClassSymbol extends JTypeDeclSymbol,
if (!isAnnotation()) {
return null;
}
return Optional.of(this)
.map(sym -> sym.getDeclaredAnnotation(Retention.class))
return Optional.ofNullable(getDeclaredAnnotation(Retention.class))
.map(annot -> annot.getAttribute("value"))
.filter(value -> value instanceof SymEnum)
.map(value -> ((SymEnum) value).toEnum(RetentionPolicy.class))
.orElse(RetentionPolicy.CLASS);
}
/**
* Return whether annotations of this annotation type apply to the
* given construct, as per the {@link Target} annotation. Return
* false if this is not an annotation.
*/
default boolean annotationAppliesTo(ElementType elementType) {
if (!isAnnotation()) {
return false;
}
SymAnnot target = getDeclaredAnnotation(Target.class);
if (target == null) {
// If an @Target meta-annotation is not present on an annotation type T,
// then an annotation of type T may be written as a modifier
// for any declaration except a type parameter declaration.
return elementType != ElementType.TYPE_PARAMETER;
}
return target.attributeContains("value", elementType).isTrue();
}
// todo isSealed + getPermittedSubclasses
// (isNonSealed is not so useful I think)
@@ -10,8 +10,8 @@ import java.lang.reflect.Modifier;
import java.util.List;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue.SymAnnot;
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
import net.sourceforge.pmd.lang.java.types.Substitution;
@@ -54,6 +54,32 @@ public interface JExecutableSymbol extends JAccessibleElementSymbol, JTypeParame
int getArity();
/**
* Return the receiver type with all type annotations, when viewed
* under the given substitution. Return null if this method
* {@linkplain #hasReceiver() has no receiver}.
*
* @throws IllegalArgumentException If the argument is not the receiver type of this type.
*/
@Nullable JTypeMirror getAnnotatedReceiverType(Substitution subst);
/**
* Return true if this method needs to be called on a receiver instance.
* This is not the case if the method is static, or a constructor of an
* outer or static class.
*/
default boolean hasReceiver() {
if (isStatic()) {
return false;
}
if (this instanceof JConstructorSymbol) {
return !getEnclosingClass().isStatic()
&& getEnclosingClass().getEnclosingClass() != null;
}
return true;
}
/**
* Returns the class symbol declaring this method or constructor.
* This is similar to {@link Constructor#getDeclaringClass()}, resp.
@@ -70,11 +96,18 @@ public interface JExecutableSymbol extends JAccessibleElementSymbol, JTypeParame
}
/**
* Returns the types of the formal parameters, when viewed under the
* given substitution. The returned list has one item for each formal.
*
* @see #getFormalParameters()
*/
List<JTypeMirror> getFormalParameterTypes(Substitution subst);
/**
* Returns the types of the thrown exceptions, when viewed under the
* given substitution.
*/
List<JTypeMirror> getThrownExceptionTypes(Substitution subst);
default List<SymAnnot> getFormalParameterAnnotations(int parameterIndex) {
return getFormalParameters().get(parameterIndex).getDeclaredAnnotations();
}
}
@@ -43,6 +43,14 @@ public interface JMethodSymbol extends JExecutableSymbol, BoundToNode<ASTMethodD
return null;
}
/**
* Return whether this method defines an attribute of the enclosing
* annotation type.
*/
default boolean isAnnotationAttribute() {
return !isStatic() && getEnclosingClass().isAnnotation() && getArity() == 0;
}
@Override
default <R, P> R acceptVisitor(SymbolVisitor<R, P> visitor, P param) {
@@ -12,15 +12,16 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.apache.commons.lang3.AnnotationUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.pcollections.PSet;
import net.sourceforge.pmd.lang.java.symbols.internal.asm.ClassNamesUtil;
import net.sourceforge.pmd.lang.java.types.TypeSystem;
@@ -72,7 +73,8 @@ public interface SymbolicValue {
/**
* Returns a symbolic value for the given java object
* Returns an annotation element for the given java value. Returns
* null if the value cannot be an annotation element.
* null if the value cannot be an annotation element or cannot be
* constructed.
*/
static @Nullable SymbolicValue of(TypeSystem ts, Object value) {
Objects.requireNonNull(ts);
@@ -119,10 +121,31 @@ public interface SymbolicValue {
*/
@Nullable SymbolicValue getAttribute(String attrName);
/**
* Return the symbol for the declaring class of the annotation.
*/
@NonNull JClassSymbol getAnnotationSymbol();
Set<String> getAttributeNames();
/**
* Return the simple names of all attributes, including those
* defined in the annotation type but not explicitly set in this annotation.
* Note that if the annotation is reflected from a class file,
* we can't know which annotations used their default value, so it
* returns a set of all attribute names.
*/
default PSet<String> getAttributeNames() {
return getAnnotationSymbol().getAnnotationAttributeNames();
}
String getBinaryName();
/** Return the binary name of the annotation type. */
default String getBinaryName() {
return getAnnotationSymbol().getBinaryName();
}
/** Return the simple name of the annotation type. */
default String getSimpleName() {
return getAnnotationSymbol().getSimpleName();
}
@Override
default boolean valueEquals(Object o) {
@@ -145,7 +168,8 @@ public interface SymbolicValue {
continue;
}
if (!getAttribute(attrName).valueEquals(attr)) {
SymbolicValue myAttr = getAttribute(attrName);
if (myAttr == null || !myAttr.valueEquals(attr)) {
return false;
}
}
@@ -156,9 +180,17 @@ public interface SymbolicValue {
* The retention policy. Note that naturally, members accessed
* from class files cannot reflect annotations with {@link RetentionPolicy#SOURCE}.
*/
RetentionPolicy getRetention();
default RetentionPolicy getRetention() {
return getAnnotationSymbol().getAnnotationRetention();
}
boolean isOfType(String binaryName);
/**
* Return true if this annotation's binary name matches the given
* binary name.
*/
default boolean isOfType(String binaryName) {
return getBinaryName().equals(binaryName);
}
/**
* Whether the annotation has the given type. Note that only
@@ -181,11 +213,26 @@ public interface SymbolicValue {
return OptionalBool.UNKNOWN;
}
if (attrValue instanceof SymbolicValue) {
return OptionalBool.definitely(attr.equals(attrValue));
} else {
return OptionalBool.definitely(attr.valueEquals(attrValue));
return OptionalBool.definitely(SymbolicValueHelper.equalsModuloWrapper(attr, attrValue));
}
/**
* Returns YES if the annotation has the attribute set to the
* given value, or to an array containing the given value. Returns
* NO if that's not the case. Returns UNKNOWN if the attribute
* does not exist or is unresolved.
*/
default OptionalBool attributeContains(String attrName, Object attrValue) {
SymbolicValue attr = getAttribute(attrName);
if (attr == null) {
return OptionalBool.UNKNOWN;
}
if (attr instanceof SymArray) {
// todo what if the value is an array itself
return OptionalBool.definitely(((SymArray) attr).containsValue(attrValue));
}
return OptionalBool.definitely(SymbolicValueHelper.equalsModuloWrapper(attr, attrValue));
}
}
@@ -266,6 +313,22 @@ public interface SymbolicValue {
return length;
}
/**
* Return true if this array contains the given object. If the
* object is a {@link SymbolicValue}, it uses {@link #equals(Object)},
* otherwise it uses {@link #valueEquals(Object)} to compare elements.
*/
public boolean containsValue(Object value) {
if (primArray != null) {
// todo I don't know how to code that without switching on the type
throw new NotImplementedException("not implemented: containsValue with a primitive array");
} else if (elements != null) {
return elements.stream().anyMatch(it -> SymbolicValueHelper.equalsModuloWrapper(it, value));
}
return false;
}
@Override
public boolean valueEquals(Object o) {
if (!o.getClass().isArray() || !isOkComponentType(o.getClass().getComponentType())) {
@@ -312,6 +375,7 @@ public interface SymbolicValue {
if (elements != null) {
return elements.hashCode();
} else {
assert primArray != null;
return primArray.hashCode();
}
}
@@ -500,7 +564,7 @@ public interface SymbolicValue {
@Override
public int hashCode() {
return Objects.hash(binaryName);
return binaryName.hashCode();
}
}
}
@@ -0,0 +1,26 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.symbols;
/**
* Private helper for {@link SymbolicValue} and implementations.
*
* @author Clément Fournier
*/
final class SymbolicValueHelper {
private SymbolicValueHelper() {
// utility class
}
static boolean equalsModuloWrapper(SymbolicValue sv, Object other) {
if (other instanceof SymbolicValue) {
return sv.equals(other);
} else {
return sv.valueEquals(other);
}
}
}
@@ -0,0 +1,52 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.symbols.internal;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue;
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue.SymAnnot;
/**
* Pretends to be an annotation with no explicit attributes.
*
* @author Clément Fournier
*/
public class FakeSymAnnot implements SymAnnot {
private final JClassSymbol annotationClass;
public FakeSymAnnot(JClassSymbol annotationClass) {
this.annotationClass = annotationClass;
assert annotationClass.isAnnotation() : "Not an annotation " + annotationClass;
}
@Override
public @Nullable SymbolicValue getAttribute(String attrName) {
return annotationClass.getDefaultAnnotationAttributeValue(attrName);
}
@Override
public @NonNull JClassSymbol getAnnotationSymbol() {
return annotationClass;
}
@Override
public String toString() {
return SymbolToStrings.FAKE.toString(this);
}
@Override
public boolean equals(Object o) {
return SymbolEquality.ANNOTATION.equals(this, o);
}
@Override
public int hashCode() {
return SymbolEquality.ANNOTATION.hash(this);
}
}
@@ -158,6 +158,7 @@ public final class ImplicitMemberSymbols {
private final int modifiers;
private final List<JFormalParamSymbol> formals;
FakeExecutableSymBase(JClassSymbol owner,
String name,
int modifiers,
@@ -208,6 +209,14 @@ public final class ImplicitMemberSymbols {
return formals.size();
}
@Override
public @Nullable JTypeMirror getAnnotatedReceiverType(Substitution subst) {
if (!this.hasReceiver()) {
return null;
}
return getTypeSystem().declaration(owner).subst(subst);
}
@Override
public int getModifiers() {
return modifiers;
@@ -144,7 +144,7 @@ public final class SymbolEquality {
public static final EqAndHash<SymAnnot> ANNOTATION = new EqAndHash<SymAnnot>() {
@Override
public int hash(SymAnnot t1) {
return Objects.hash(t1.getBinaryName(), t1.getAttributeNames());
return Objects.hash(t1.getBinaryName());
}
@Override
@@ -153,8 +153,7 @@ public final class SymbolEquality {
return false;
}
SymAnnot f2 = (SymAnnot) o;
return f1.getBinaryName().equals(f2.getBinaryName())
&& f1.getAttributeNames().equals(f2.getAttributeNames());
return f1.getBinaryName().equals(f2.getBinaryName());
}
};
@@ -4,6 +4,8 @@
package net.sourceforge.pmd.lang.java.symbols.internal;
import java.util.stream.Collectors;
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol;
import net.sourceforge.pmd.lang.java.symbols.JElementSymbol;
@@ -14,6 +16,7 @@ import net.sourceforge.pmd.lang.java.symbols.JMethodSymbol;
import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
import net.sourceforge.pmd.lang.java.symbols.JTypeParameterSymbol;
import net.sourceforge.pmd.lang.java.symbols.SymbolVisitor;
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue.SymAnnot;
public class SymbolToStrings {
@@ -32,6 +35,19 @@ public class SymbolToStrings {
return symbol.acceptVisitor(visitor, new StringBuilder()).toString();
}
public String toString(SymAnnot annot) {
String attrs;
if (annot.getAttributeNames().isEmpty()) {
attrs = "";
} else {
attrs = annot.getAttributeNames()
.stream()
.map(name -> name + "=" + annot.getAttribute(name))
.collect(Collectors.joining(", ", "(", ")"));
}
return "@" + annot.getBinaryName() + attrs;
}
private static final class ToStringVisitor implements SymbolVisitor<StringBuilder, StringBuilder> {
private final String impl;
@@ -4,6 +4,9 @@
package net.sourceforge.pmd.lang.java.symbols.internal.asm;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.objectweb.asm.TypePath;
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue;
class AnnotationBuilderVisitor extends SymbolicValueBuilder {
@@ -26,4 +29,35 @@ class AnnotationBuilderVisitor extends SymbolicValueBuilder {
public void visitEnd() {
owner.addAnnotation(annot);
}
static class TypeAnnotBuilderImpl extends SymbolicValueBuilder {
private final TypeAnnotationReceiver owner;
private final int typeRef;
private final @Nullable TypePath path;
private final SymbolicAnnotationImpl annot;
TypeAnnotBuilderImpl(AsmSymbolResolver resolver,
TypeAnnotationReceiver owner,
int typeRef,
@Nullable TypePath path,
boolean visible,
String descriptor) {
super(resolver);
this.owner = owner;
this.typeRef = typeRef;
this.path = path;
this.annot = new SymbolicAnnotationImpl(resolver, visible, descriptor);
}
@Override
protected void acceptValue(String name, SymbolicValue v) {
annot.addAttribute(name, v);
}
@Override
public void visitEnd() {
owner.acceptTypeAnnotation(typeRef, path, annot);
}
}
}
@@ -5,13 +5,6 @@
package net.sourceforge.pmd.lang.java.symbols.internal.asm;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Common interface for symbols wrapping a class file "stub".
* The class is parsed with ASM, only the signature information
@@ -29,9 +22,4 @@ interface AsmStub {
return getResolver().getSigParser();
}
@NonNull
static <T> List<T> toList(@Nullable T[] arr) {
return arr == null ? Collections.emptyList() : Arrays.asList(arr);
}
}
@@ -10,14 +10,14 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.pcollections.HashTreePSet;
import org.pcollections.PSet;
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol;
@@ -27,6 +27,7 @@ import net.sourceforge.pmd.lang.java.symbols.JFieldSymbol;
import net.sourceforge.pmd.lang.java.symbols.JMethodSymbol;
import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
import net.sourceforge.pmd.lang.java.symbols.JTypeParameterOwnerSymbol;
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue;
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue.SymAnnot;
import net.sourceforge.pmd.lang.java.symbols.internal.SymbolEquality;
import net.sourceforge.pmd.lang.java.symbols.internal.asm.ExecutableStub.CtorStub;
@@ -62,9 +63,9 @@ final class ClassStub implements JClassSymbol, AsmStub, AnnotationOwner {
private List<JConstructorSymbol> ctors = new ArrayList<>();
private List<JFieldSymbol> enumConstants = null;
private List<SymAnnot> annotations = new ArrayList<>();
private PSet<SymAnnot> annotations = HashTreePSet.empty();
private Set<String> annotAttributes;
private PSet<String> annotAttributes;
private final ParseLock parseLock;
@@ -126,10 +127,11 @@ final class ClassStub implements JClassSymbol, AsmStub, AnnotationOwner {
names.finishOuterClass();
}
}
annotations = Collections.unmodifiableList(annotations);
annotAttributes = (accessFlags & Opcodes.ACC_ANNOTATION) != 0
? getDeclaredMethods().stream().map(JElementSymbol::getSimpleName).collect(Collectors.toSet())
: Collections.emptySet();
? getDeclaredMethods().stream().filter(JMethodSymbol::isAnnotationAttribute)
.map(JElementSymbol::getSimpleName)
.collect(CollectionUtil.toPersistentSet())
: HashTreePSet.empty();
}
@Override
@@ -250,7 +252,7 @@ final class ClassStub implements JClassSymbol, AsmStub, AnnotationOwner {
@Override
public void addAnnotation(SymAnnot annot) {
annotations.add(annot);
annotations = annotations.plus(annot);
}
@@ -287,6 +289,12 @@ final class ClassStub implements JClassSymbol, AsmStub, AnnotationOwner {
return signature.getTypeParams();
}
@Override
public boolean isGeneric() {
parseLock.ensureParsed();
return signature.isGeneric();
}
@Override
public LexicalScope getLexicalScope() {
if (scope == null) {
@@ -320,17 +328,27 @@ final class ClassStub implements JClassSymbol, AsmStub, AnnotationOwner {
}
@Override
public List<SymAnnot> getDeclaredAnnotations() {
public PSet<SymAnnot> getDeclaredAnnotations() {
parseLock.ensureParsed();
return annotations;
}
@Override
public Set<String> getAnnotationAttributeNames() {
public PSet<String> getAnnotationAttributeNames() {
parseLock.ensureParsed();
return annotAttributes;
}
@Override
public @Nullable SymbolicValue getDefaultAnnotationAttributeValue(String attrName) {
parseLock.ensureParsed();
if (!annotAttributes.contains(attrName)) {
// this is a shortcut, because the default impl checks each method
return null;
}
return JClassSymbol.super.getDefaultAnnotationAttributeValue(attrName);
}
@Override
public @Nullable JClassSymbol getEnclosingClass() {
parseLock.ensureParsed();
@@ -11,6 +11,8 @@ import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.TypePath;
import org.objectweb.asm.TypeReference;
import net.sourceforge.pmd.lang.java.symbols.internal.asm.ExecutableStub.CtorStub;
import net.sourceforge.pmd.lang.java.symbols.internal.asm.ExecutableStub.MethodStub;
@@ -65,8 +67,9 @@ class ClassStubBuilder extends ClassVisitor {
}
@Override
public void visitEnd() {
field.finalizeVisit();
public AnnotationVisitor visitTypeAnnotation(int typeRef, @Nullable TypePath typePath, String descriptor, boolean visible) {
assert new TypeReference(typeRef).getSort() == TypeReference.FIELD : typeRef;
return new AnnotationBuilderVisitor.TypeAnnotBuilderImpl(resolver, field, typeRef, typePath, visible, descriptor);
}
};
}
@@ -4,16 +4,17 @@
package net.sourceforge.pmd.lang.java.symbols.internal.asm;
import static net.sourceforge.pmd.util.CollectionUtil.map;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.TypePath;
import org.pcollections.HashTreePSet;
import org.pcollections.IntTreePMap;
import org.pcollections.PMap;
import org.pcollections.PSet;
import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol;
import net.sourceforge.pmd.lang.java.symbols.JExecutableSymbol;
@@ -30,12 +31,12 @@ import net.sourceforge.pmd.lang.java.types.Substitution;
import net.sourceforge.pmd.lang.java.types.TypeOps;
import net.sourceforge.pmd.lang.java.types.TypeSystem;
abstract class ExecutableStub extends MemberStubBase implements JExecutableSymbol {
abstract class ExecutableStub extends MemberStubBase implements JExecutableSymbol, TypeAnnotationReceiver {
private final String descriptor;
protected final LazyMethodType type;
private List<JFormalParamSymbol> params;
private Map<Integer, List<SymAnnot>> parameterAnnotations = Collections.emptyMap();
private PMap<Integer, PSet<SymAnnot>> parameterAnnotations = IntTreePMap.empty();
protected ExecutableStub(ClassStub owner,
String simpleName,
@@ -62,8 +63,12 @@ abstract class ExecutableStub extends MemberStubBase implements JExecutableSymbo
@Override
public List<JFormalParamSymbol> getFormalParameters() {
if (params == null) {
this.params = Collections.unmodifiableList(map(type.getParameterTypes(),
FormalParamStub::new));
List<JTypeMirror> ptypes = type.getParameterTypes();
List<JFormalParamSymbol> newParams = new ArrayList<>(ptypes.size());
for (int i = 0; i < ptypes.size(); i++) {
newParams.add(new FormalParamStub(ptypes.get(i), i));
}
params = Collections.unmodifiableList(newParams);
}
return params;
}
@@ -77,10 +82,18 @@ abstract class ExecutableStub extends MemberStubBase implements JExecutableSymbo
public int getArity() {
return type.getParameterTypes().size();
}
PSet<SymAnnot> getFormalParameterAnnotations(int parameterIndex) {
return parameterAnnotations.getOrDefault(parameterIndex, HashTreePSet.empty());
}
@Override
public List<SymAnnot> getFormalParameterAnnotations(int parameterIndex) {
return parameterAnnotations.getOrDefault(parameterIndex, Collections.emptyList());
public @Nullable JTypeMirror getAnnotatedReceiverType(Substitution subst) {
if (!this.hasReceiver()) {
return null;
}
JTypeMirror receiver = getTypeSystem().declaration(getEnclosingClass()).subst(subst);
return type.applyReceiverAnnotations(receiver);
}
@Override
@@ -97,11 +110,14 @@ abstract class ExecutableStub extends MemberStubBase implements JExecutableSymbo
// overridden by MethodStub
}
@Override
public void acceptTypeAnnotation(int typeRef, @Nullable TypePath path, SymAnnot annot) {
type.acceptTypeAnnotation(typeRef, path, annot);
}
void addParameterAnnotation(int paramIndex, SymbolicValue.SymAnnot annot) {
if (parameterAnnotations.isEmpty()) {
parameterAnnotations = new HashMap<>(); // Make writable
}
parameterAnnotations.computeIfAbsent(paramIndex, ArrayList::new).add(annot);
PSet<SymAnnot> newAnnots = parameterAnnotations.getOrDefault(paramIndex, HashTreePSet.empty()).plus(annot);
parameterAnnotations = parameterAnnotations.plus(paramIndex, newAnnots);
}
/**
@@ -112,9 +128,11 @@ abstract class ExecutableStub extends MemberStubBase implements JExecutableSymbo
class FormalParamStub implements JFormalParamSymbol {
private final JTypeMirror type;
private final int index;
FormalParamStub(JTypeMirror type) {
FormalParamStub(JTypeMirror type, int index) {
this.type = type;
this.index = index;
}
@Override
@@ -141,11 +159,10 @@ abstract class ExecutableStub extends MemberStubBase implements JExecutableSymbo
public TypeSystem getTypeSystem() {
return ExecutableStub.this.getTypeSystem();
}
@Override
public List<SymAnnot> getDeclaredAnnotations() {
int paramIndex = ExecutableStub.this.getFormalParameters().indexOf(this);
return ExecutableStub.this.getFormalParameterAnnotations(paramIndex);
public PSet<SymAnnot> getDeclaredAnnotations() {
return ExecutableStub.this.getFormalParameterAnnotations(index);
}
}
@@ -6,14 +6,17 @@ package net.sourceforge.pmd.lang.java.symbols.internal.asm;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.TypePath;
import org.objectweb.asm.TypeReference;
import net.sourceforge.pmd.lang.java.symbols.JFieldSymbol;
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue.SymAnnot;
import net.sourceforge.pmd.lang.java.symbols.internal.SymbolEquality;
import net.sourceforge.pmd.lang.java.symbols.internal.SymbolToStrings;
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
import net.sourceforge.pmd.lang.java.types.Substitution;
class FieldStub extends MemberStubBase implements JFieldSymbol {
class FieldStub extends MemberStubBase implements JFieldSymbol, TypeAnnotationReceiver {
private final LazyTypeSig type;
private final @Nullable Object constValue;
@@ -29,9 +32,14 @@ class FieldStub extends MemberStubBase implements JFieldSymbol {
this.constValue = constValue;
}
@Nullable
@Override
public Object getConstValue() {
public void acceptTypeAnnotation(int typeRef, @Nullable TypePath path, SymAnnot annot) {
assert new TypeReference(typeRef).getSort() == TypeReference.FIELD : typeRef;
this.type.addTypeAnnotation(path, annot);
}
@Override
public @Nullable Object getConstValue() {
return constValue;
}
@@ -5,6 +5,8 @@
package net.sourceforge.pmd.lang.java.symbols.internal.asm;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@@ -13,10 +15,17 @@ import java.util.stream.Stream;
import org.apache.commons.lang3.Validate;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.TypePath;
import org.objectweb.asm.TypeReference;
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
import net.sourceforge.pmd.lang.java.symbols.JTypeParameterOwnerSymbol;
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue.SymAnnot;
import net.sourceforge.pmd.lang.java.symbols.internal.asm.TypeAnnotationHelper.TypeAnnotationSet;
import net.sourceforge.pmd.lang.java.symbols.internal.asm.TypeAnnotationHelper.TypeAnnotationSetWithReferences;
import net.sourceforge.pmd.lang.java.types.JClassType;
import net.sourceforge.pmd.lang.java.types.JIntersectionType;
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
import net.sourceforge.pmd.lang.java.types.JTypeVar;
import net.sourceforge.pmd.lang.java.types.LexicalScope;
@@ -63,7 +72,7 @@ abstract class GenericSigBase<T extends JTypeParameterOwnerSymbol & AsmStub> {
return enclosing == null ? LexicalScope.EMPTY : enclosing.getLexicalScope();
}
protected void ensureParsed() {
protected final void ensureParsed() {
lock.ensureParsed();
}
@@ -72,6 +81,8 @@ abstract class GenericSigBase<T extends JTypeParameterOwnerSymbol & AsmStub> {
protected abstract boolean postCondition();
protected abstract boolean isGeneric();
public void setTypeParams(List<JTypeVar> tvars) {
assert this.typeParameters == null : "Type params were already parsed for " + this;
this.typeParameters = tvars;
@@ -145,6 +156,11 @@ abstract class GenericSigBase<T extends JTypeParameterOwnerSymbol & AsmStub> {
}
}
@Override
protected boolean isGeneric() {
return signature != null && TypeParamsParser.hasTypeParams(signature);
}
@Override
protected boolean postCondition() {
return (superItfs != null && superType != null || signature == null) && typeParameters != null;
@@ -187,27 +203,82 @@ abstract class GenericSigBase<T extends JTypeParameterOwnerSymbol & AsmStub> {
/**
* Method or constructor type.
*/
static class LazyMethodType extends GenericSigBase<ExecutableStub> {
static class LazyMethodType extends GenericSigBase<ExecutableStub> implements TypeAnnotationReceiver {
private final @NonNull String signature;
private @Nullable TypeAnnotationSet receiverAnnotations;
private List<JTypeMirror> parameterTypes;
private List<JTypeMirror> exceptionTypes;
private JTypeMirror returnType;
private @Nullable TypeAnnotationSetWithReferences typeAnnots;
private @Nullable String[] rawExceptions;
/** Used for constructors of inner non-static classes. */
private final boolean skipFirstParam;
LazyMethodType(ExecutableStub ctx, @NonNull String descriptor, @Nullable String genericSig, @SuppressWarnings("PMD.UnusedFormalParameter") @Nullable String[] exceptions, boolean skipFirstParam) {
// TODO exceptions. Couple of notes:
// - the descriptor never contains thrown exceptions
// - the signature might not contain the thrown exception types (if they do not depend on type variables)
// - the exceptions array also contains unchecked exceptions
//
// See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.9.1
// TODO test cases
// <E extends Exception> void foo() throws E; // descriptor "()V" signature "<TE;>()V^TE;" exceptions: ???
// <E> void foo(E e) throws Exception; // descriptor "(Ljava.lang.Object;)V" signature "<TE;>(TE;)V" exceptions: [ "java/lang/Exception" ]
// void foo() throws Exception; // descriptor "()V" signature null exceptions: [ "java/lang/Exception" ]
LazyMethodType(ExecutableStub ctx,
@NonNull String descriptor,
@Nullable String genericSig,
@Nullable String[] exceptions,
boolean skipFirstParam) {
super(ctx);
this.signature = genericSig != null ? genericSig : descriptor;
// generic signatures already omit the synthetic param
this.skipFirstParam = skipFirstParam && genericSig == null;
this.rawExceptions = exceptions;
}
@Override
protected void doParse() {
ctx.sigParser().parseMethodType(this, signature);
if (rawExceptions != null && this.exceptionTypes.isEmpty()) {
// the descriptor did not contain exceptions. They're in this string array.
this.exceptionTypes = Arrays.stream(rawExceptions)
.map(ctx.getResolver()::resolveFromInternalNameCannotFail)
.map(ctx.getTypeSystem()::rawType)
.collect(CollectionUtil.toUnmodifiableList());
}
if (typeAnnots != null) {
// apply type annotations here
// this may change type parameters
boolean typeParamsWereMutated = typeAnnots.forEach(this::acceptAnnotationAfterParse);
if (typeParamsWereMutated) {
// Some type parameters were mutated. We need to replace
// the old tparams with the annotated ones in all other
// types of this signature.
// This substitution looks like the identity mapping.
// It actually does work, because JTypeVar#equals considers only the symbol
// and not the type annotations. So unannotated tvars in the type will be
// matched with the annotated tvar that has the same symbol.
Substitution subst = Substitution.mapping(typeParameters, typeParameters);
this.returnType = this.returnType.subst(subst);
this.parameterTypes = TypeOps.subst(parameterTypes, subst);
this.exceptionTypes = TypeOps.subst(exceptionTypes, subst);
}
}
// null this transient data out
this.rawExceptions = null;
this.typeAnnots = null;
}
public JTypeMirror applyReceiverAnnotations(JTypeMirror typeMirror) {
if (receiverAnnotations == null) {
return typeMirror;
}
return receiverAnnotations.decorate(typeMirror);
}
@Override
@@ -215,6 +286,12 @@ abstract class GenericSigBase<T extends JTypeParameterOwnerSymbol & AsmStub> {
return parameterTypes != null && exceptionTypes != null && returnType != null;
}
@Override
protected boolean isGeneric() {
return TypeParamsParser.hasTypeParams(signature);
}
void setParameterTypes(List<JTypeMirror> params) {
Validate.validState(parameterTypes == null);
parameterTypes = skipFirstParam ? params.subList(1, params.size())
@@ -250,5 +327,95 @@ abstract class GenericSigBase<T extends JTypeParameterOwnerSymbol & AsmStub> {
public String toString() {
return signature;
}
@Override
public void acceptTypeAnnotation(int typeRefInt, @Nullable TypePath path, SymAnnot annot) {
// Accumulate type annotations for later
// They shouldn't be applied right now because the descriptor maybe has not been parsed yet.
if (typeAnnots == null) {
typeAnnots = new TypeAnnotationSetWithReferences();
}
typeAnnots.add(new TypeReference(typeRefInt), path, annot);
}
/**
* See {@link MethodVisitor#visitTypeAnnotation(int, TypePath, String, boolean)} for possible
* values of typeRef sort (they're each case of the switch).
* Returns true if type parameters have been mutated.
*/
boolean acceptAnnotationAfterParse(TypeReference tyRef, @Nullable TypePath path, SymAnnot annot) {
switch (tyRef.getSort()) {
case TypeReference.METHOD_RETURN: {
assert returnType != null : "Return type is not set";
returnType = TypeAnnotationHelper.applySinglePath(returnType, path, annot);
return false;
}
case TypeReference.METHOD_FORMAL_PARAMETER: {
assert parameterTypes != null : "Parameter types are not set";
int idx = tyRef.getFormalParameterIndex();
JTypeMirror annotatedFormal = TypeAnnotationHelper.applySinglePath(parameterTypes.get(idx), path, annot);
parameterTypes = TypeAnnotationHelper.replaceAtIndex(parameterTypes, idx, annotatedFormal);
return false;
}
case TypeReference.THROWS: {
assert exceptionTypes != null : "Exception types are not set";
int idx = tyRef.getExceptionIndex();
JTypeMirror annotatedFormal = TypeAnnotationHelper.applySinglePath(exceptionTypes.get(idx), path, annot);
exceptionTypes = TypeAnnotationHelper.replaceAtIndex(exceptionTypes, idx, annotatedFormal);
return false;
}
case TypeReference.METHOD_TYPE_PARAMETER: {
assert typeParameters != null;
assert path == null : "unexpected path " + path;
int idx = tyRef.getTypeParameterIndex();
// Here we add to the symbol, not the type var.
// This ensures that all occurrences of the type var
// share these annotations (symbol is unique, contrary to jtypevar)
((TParamStub) typeParameters.get(idx).getSymbol()).addAnnotation(annot);
return false;
}
case TypeReference.METHOD_TYPE_PARAMETER_BOUND: {
assert typeParameters != null;
int tparamIdx = tyRef.getTypeParameterIndex();
int boundIdx = tyRef.getTypeParameterBoundIndex();
JTypeVar tparam = typeParameters.get(tparamIdx);
final JTypeMirror newUb = computeNewUpperBound(path, annot, boundIdx, tparam.getUpperBound());
typeParameters.set(tparamIdx, tparam.withUpperBound(newUb));
return true;
}
case TypeReference.METHOD_RECEIVER: {
if (receiverAnnotations == null) {
receiverAnnotations = new TypeAnnotationSet();
}
receiverAnnotations.add(path, annot);
return false;
}
default:
throw new IllegalArgumentException(
"Invalid type reference for method or ctor type annotation: " + tyRef.getSort());
}
}
private static JTypeMirror computeNewUpperBound(@Nullable TypePath path, SymAnnot annot, int boundIdx, JTypeMirror ub) {
final JTypeMirror newUb;
if (ub instanceof JIntersectionType) {
JIntersectionType intersection = (JIntersectionType) ub;
// Object is pruned from the component list
boundIdx = intersection.getPrimaryBound().isTop() ? boundIdx - 1 : boundIdx;
List<JTypeMirror> components = new ArrayList<>(intersection.getComponents());
JTypeMirror bound = components.get(boundIdx);
JTypeMirror newBound = TypeAnnotationHelper.applySinglePath(bound, path, annot);
components.set(boundIdx, newBound);
JTypeMirror newIntersection = intersection.getTypeSystem().glb(components);
newUb = newIntersection;
} else {
newUb = TypeAnnotationHelper.applySinglePath(ub, path, annot);
}
return newUb;
}
}
}
Loaded 30 of 113 files, more files were not shown because too many files have changed in this diff. Show more