/* * Copyright 2015 Lukas Krejci * * 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 org.revapi.java.checks.fields; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; import java.util.List; import java.util.Objects; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ExecutableType; import javax.lang.model.type.NoType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.SimpleTypeVisitor7; import org.revapi.Difference; import org.revapi.java.spi.Code; import org.revapi.java.spi.JavaFieldElement; import org.revapi.java.spi.TypeEnvironment; /** * @author Lukas Krejci * @since 0.1 */ public final class SerialVersionUidUnchanged extends BothFieldsRequiringCheck { private static final String SERIAL_VERSION_UID_FIELD_NAME = "serialVersionUID"; @Override public EnumSet<Type> getInterest() { return EnumSet.of(Type.FIELD); } @Override protected void doVisitField(JavaFieldElement oldField, JavaFieldElement newField) { if (oldField == null || newField == null) { return; } if (!SERIAL_VERSION_UID_FIELD_NAME.equals(oldField.getDeclaringElement().getSimpleName().toString())) { return; } if (!SERIAL_VERSION_UID_FIELD_NAME.equals(newField.getDeclaringElement().getSimpleName().toString())) { return; } if (!isBothAccessible(oldField.getParent(), newField.getParent())) { return; } PrimitiveType oldLong = getOldTypeEnvironment().getTypeUtils().getPrimitiveType(TypeKind.LONG); if (!getOldTypeEnvironment().getTypeUtils().isSameType(oldField.getModelRepresentation(), oldLong)) { return; } PrimitiveType newLong = getNewTypeEnvironment().getTypeUtils().getPrimitiveType(TypeKind.LONG); if (!getNewTypeEnvironment().getTypeUtils().isSameType(newField.getModelRepresentation(), newLong)) { return; } if (!oldField.getDeclaringElement().getModifiers().contains(Modifier.STATIC) || !oldField.getDeclaringElement().getModifiers().contains(Modifier.FINAL)) { return; } if (!newField.getDeclaringElement().getModifiers().contains(Modifier.STATIC) || !newField.getDeclaringElement().getModifiers().contains(Modifier.FINAL)) { return; } TypeElement oldType = (TypeElement) oldField.getDeclaringElement().getEnclosingElement(); TypeElement newType = (TypeElement) newField.getDeclaringElement().getEnclosingElement(); long computedOldSUID = computeSerialVersionUID(oldType, getOldTypeEnvironment()); long computedNewSUID = computeSerialVersionUID(newType, getNewTypeEnvironment()); Long actualOldSUID = (Long) oldField.getDeclaringElement().getConstantValue(); Long actualNewSUID = (Long) newField.getDeclaringElement().getConstantValue(); if (Objects.equals(actualOldSUID, actualNewSUID) && computedOldSUID != computedNewSUID) { pushActive(oldField, newField); } } @Override protected List<Difference> doEnd() { ActiveElements<JavaFieldElement> fields = popIfActive(); if (fields == null) { return null; } return Collections.singletonList(createDifference(Code.FIELD_SERIAL_VERSION_UID_UNCHANGED, Code.attachmentsFor(fields.oldElement, fields.newElement))); } /** * Adapted from {@link java.io.ObjectStreamClass#computeDefaultSUID(java.lang.Class)} method. */ public static long computeSerialVersionUID(TypeElement type, TypeEnvironment environment) { TypeElement javaIoSerializable = environment.getElementUtils().getTypeElement("java.io.Serializable"); if (!environment.getTypeUtils().isAssignable(type.asType(), javaIoSerializable.asType())) { return 0L; } // if (!Serializable.class.isAssignableFrom(cl) || Proxy.isProxyClass(cl)) // { // return 0L; // } try { ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(bout); dout.writeUTF(type.getQualifiedName().toString()); int classMods = asReflectiveModifiers(type, Modifier.PUBLIC, Modifier.FINAL, Modifier.ABSTRACT); if (type.getKind() == ElementKind.INTERFACE) { classMods |= java.lang.reflect.Modifier.INTERFACE; } // int classMods = cl.getModifiers() & // (java.lang.reflect.Modifier.PUBLIC | java.lang.reflect.Modifier.FINAL | // java.lang.reflect.Modifier.INTERFACE | java.lang.reflect.Modifier.ABSTRACT); /* * compensate for javac bug in which ABSTRACT bit was set for an * interface only if the interface declared methods */ if (type.getKind() == ElementKind.INTERFACE) { if (ElementFilter.methodsIn(type.getEnclosedElements()).size() > 0) { classMods |= java.lang.reflect.Modifier.ABSTRACT; } else { classMods &= ~java.lang.reflect.Modifier.ABSTRACT; } } // Method[] methods = cl.getDeclaredMethods(); // if ((classMods & Modifier.INTERFACE) != 0) { // classMods = (methods.length > 0) ? // (classMods | Modifier.ABSTRACT) : // (classMods & ~Modifier.ABSTRACT); // } dout.writeInt(classMods); if (!(type.asType() instanceof javax.lang.model.type.ArrayType)) { // if (!cl.isArray()) { /* * compensate for change in 1.2FCS in which * Class.getInterfaces() was modified to return Cloneable and * Serializable for array classes. */ List<? extends TypeMirror> interfaces = type.getInterfaces(); String[] ifaceNames = new String[interfaces.size()]; for (int i = 0; i < interfaces.size(); i++) { ifaceNames[i] = ((TypeElement) ((DeclaredType) interfaces.get(i)).asElement()).getQualifiedName() .toString(); } // Class[] interfaces = cl.getInterfaces(); // String[] ifaceNames = new String[interfaces.length]; // for (int i = 0; i < interfaces.length; i++) { // ifaceNames[i] = interfaces[i].getName(); // } Arrays.sort(ifaceNames); for (int i = 0; i < ifaceNames.length; i++) { dout.writeUTF(ifaceNames[i]); } } List<? extends VariableElement> fields = ElementFilter.fieldsIn(type.getEnclosedElements()); MemberSignature[] fieldSigs = new MemberSignature[fields.size()]; for (int i = 0; i < fields.size(); i++) { fieldSigs[i] = new MemberSignature(fields.get(i)); } // Field[] fields = cl.getDeclaredFields(); // MemberSignature[] fieldSigs = new MemberSignature[fields.length]; // for (int i = 0; i < fields.length; i++) { // fieldSigs[i] = new MemberSignature(fields[i]); // } Arrays.sort(fieldSigs, new Comparator<MemberSignature>() { public int compare(MemberSignature o1, MemberSignature o2) { String name1 = o1.name; String name2 = o2.name; return name1.compareTo(name2); } }); for (int i = 0; i < fieldSigs.length; i++) { MemberSignature sig = fieldSigs[i]; int mods = asReflectiveModifiers(sig.member, Modifier.PUBLIC, Modifier.PRIVATE, Modifier.PROTECTED, Modifier.STATIC, Modifier.FINAL, Modifier.VOLATILE, Modifier.TRANSIENT); // int mods = sig.member.getModifiers() & // (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED | // Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE | // Modifier.TRANSIENT); if (((mods & java.lang.reflect.Modifier.PRIVATE) == 0) || ((mods & (java.lang.reflect.Modifier.STATIC | java.lang.reflect.Modifier.TRANSIENT)) == 0)) { dout.writeUTF(sig.name); dout.writeInt(mods); dout.writeUTF(sig.signature); } } boolean hasStaticInitializer = false; for (Element e : type.getEnclosedElements()) { if (e.getKind() == ElementKind.STATIC_INIT) { hasStaticInitializer = true; break; } } if (hasStaticInitializer) { dout.writeUTF("<clinit>"); dout.writeInt(java.lang.reflect.Modifier.STATIC); dout.writeUTF("()V"); } // if (hasStaticInitializer(cl)) { // dout.writeUTF("<clinit>"); // dout.writeInt(Modifier.STATIC); // dout.writeUTF("()V"); // } List<ExecutableElement> ctors = ElementFilter.constructorsIn(type.getEnclosedElements()); MemberSignature[] consSigs = new MemberSignature[ctors.size()]; int i = 0; for (ExecutableElement ctor : ctors) { consSigs[i++] = new MemberSignature(ctor); } // Constructor[] cons = cl.getDeclaredConstructors(); // MemberSignature[] consSigs = new MemberSignature[cons.length]; // for (int i = 0; i < cons.length; i++) { // consSigs[i] = new MemberSignature(cons[i]); // } Arrays.sort(consSigs, new Comparator<MemberSignature>() { public int compare(MemberSignature o1, MemberSignature o2) { String sig1 = o1.signature; String sig2 = o2.signature; return sig1.compareTo(sig2); } }); // Arrays.sort(consSigs, new Comparator() { // public int compare(Object o1, Object o2) { // String sig1 = ((MemberSignature) o1).signature; // String sig2 = ((MemberSignature) o2).signature; // return sig1.compareTo(sig2); // } // }); for (MemberSignature sig : consSigs) { int mods = asReflectiveModifiers(sig.member, Modifier.PUBLIC, Modifier.PRIVATE, Modifier.PROTECTED, Modifier.STATIC, Modifier.FINAL, Modifier.SYNCHRONIZED, Modifier.NATIVE, Modifier.ABSTRACT, Modifier.STRICTFP); if ((mods & java.lang.reflect.Modifier.PRIVATE) == 0) { dout.writeUTF("<init>"); dout.writeInt(mods); dout.writeUTF(sig.signature.replace('/', '.')); } } // for (int i = 0; i < consSigs.length; i++) { // MemberSignature sig = consSigs[i]; // int mods = sig.member.getModifiers() & // (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED | // Modifier.STATIC | Modifier.FINAL | // Modifier.SYNCHRONIZED | Modifier.NATIVE | // Modifier.ABSTRACT | Modifier.STRICT); // if ((mods & Modifier.PRIVATE) == 0) { // dout.writeUTF("<init>"); // dout.writeInt(mods); // dout.writeUTF(sig.signature.replace('/', '.')); // } // } List<ExecutableElement> methods = ElementFilter.methodsIn(type.getEnclosedElements()); MemberSignature[] methSigs = new MemberSignature[methods.size()]; i = 0; for (ExecutableElement m : methods) { methSigs[i++] = new MemberSignature(m); } // MemberSignature[] methSigs = new MemberSignature[methods.length]; // for (int i = 0; i < methods.length; i++) { // methSigs[i] = new MemberSignature(methods[i]); // } Arrays.sort(methSigs, new Comparator<MemberSignature>() { public int compare(MemberSignature ms1, MemberSignature ms2) { int comp = ms1.name.compareTo(ms2.name); if (comp == 0) { comp = ms1.signature.compareTo(ms2.signature); } return comp; } }); // Arrays.sort(methSigs, new Comparator() { // public int compare(Object o1, Object o2) { // MemberSignature ms1 = (MemberSignature) o1; // MemberSignature ms2 = (MemberSignature) o2; // int comp = ms1.name.compareTo(ms2.name); // if (comp == 0) { // comp = ms1.signature.compareTo(ms2.signature); // } // return comp; // } // }); for (MemberSignature sig : methSigs) { int mods = asReflectiveModifiers(sig.member, Modifier.PUBLIC, Modifier.PRIVATE, Modifier.PROTECTED, Modifier.STATIC, Modifier.FINAL, Modifier.SYNCHRONIZED, Modifier.NATIVE, Modifier.ABSTRACT, Modifier.STRICTFP); if ((mods & java.lang.reflect.Modifier.PRIVATE) == 0) { dout.writeUTF(sig.name); dout.writeInt(mods); dout.writeUTF(sig.signature.replace('/', '.')); } } // for (int i = 0; i < methSigs.length; i++) { // MemberSignature sig = methSigs[i]; // int mods = sig.member.getModifiers() & // (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED | // Modifier.STATIC | Modifier.FINAL | // Modifier.SYNCHRONIZED | Modifier.NATIVE | // Modifier.ABSTRACT | Modifier.STRICT); // if ((mods & Modifier.PRIVATE) == 0) { // dout.writeUTF(sig.name); // dout.writeInt(mods); // dout.writeUTF(sig.signature.replace('/', '.')); // } // } // dout.flush(); MessageDigest md = MessageDigest.getInstance("SHA"); byte[] hashBytes = md.digest(bout.toByteArray()); long hash = 0; for (i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) { hash = (hash << 8) | (hashBytes[i] & 0xFF); } return hash; } catch (IOException | NoSuchAlgorithmException ex) { throw new IllegalStateException( "Could not compute default serialization UID for class: " + type.getQualifiedName().toString(), ex); } } /** * Adapted from {@link java.io.ObjectStreamClass.MemberSignature} * * <p>Class for computing and caching field/constructor/method signatures * during serialVersionUID calculation. */ private static class MemberSignature { public final Element member; public final String name; public final String signature; public MemberSignature(VariableElement field) { member = field; name = field.getSimpleName().toString(); signature = getSignature(field.asType()); } public MemberSignature(ExecutableElement meth) { member = meth; name = meth.getSimpleName().toString(); signature = getSignature(meth.asType()); } } private static int asReflectiveModifiers(Element el, Modifier... applicableModifiers) { int mods = 0; for (Modifier m : applicableModifiers) { if (el.getModifiers().contains(m)) { switch (m) { case ABSTRACT: mods |= java.lang.reflect.Modifier.ABSTRACT; break; case FINAL: mods |= java.lang.reflect.Modifier.FINAL; break; case NATIVE: mods |= java.lang.reflect.Modifier.NATIVE; break; case PRIVATE: mods |= java.lang.reflect.Modifier.PRIVATE; break; case PROTECTED: mods |= java.lang.reflect.Modifier.PROTECTED; break; case PUBLIC: mods |= java.lang.reflect.Modifier.PUBLIC; break; case STATIC: mods |= java.lang.reflect.Modifier.STATIC; break; case STRICTFP: mods |= java.lang.reflect.Modifier.STRICT; break; case SYNCHRONIZED: mods |= java.lang.reflect.Modifier.SYNCHRONIZED; break; case TRANSIENT: mods |= java.lang.reflect.Modifier.TRANSIENT; break; case VOLATILE: mods |= java.lang.reflect.Modifier.VOLATILE; break; default: break; } } } return mods; } /** * Adapted from {@link java.io.ObjectStreamClass#getClassSignature(Class)} * and {@link java.io.ObjectStreamClass#getMethodSignature(Class[], Class)} * * <p>Returns JVM type signature for given class. */ private static String getSignature(TypeMirror type) { StringBuilder sbuf = new StringBuilder(); type.accept(new SimpleTypeVisitor7<Void, StringBuilder>() { @Override protected Void defaultAction(TypeMirror e, StringBuilder stringBuilder) { return null; } @Override public Void visitPrimitive(PrimitiveType t, StringBuilder stringBuilder) { switch (t.getKind()) { case BOOLEAN: stringBuilder.append("Z"); break; case BYTE: stringBuilder.append("B"); break; case CHAR: stringBuilder.append("C"); break; case DOUBLE: stringBuilder.append("D"); break; case FLOAT: stringBuilder.append("F"); break; case INT: stringBuilder.append("I"); break; case LONG: stringBuilder.append("J"); break; case SHORT: stringBuilder.append("S"); break; default: break; } return null; } @Override public Void visitArray(ArrayType t, StringBuilder stringBuilder) { stringBuilder.append("["); t.getComponentType().accept(this, stringBuilder); return null; } @Override public Void visitDeclared(DeclaredType t, StringBuilder stringBuilder) { stringBuilder.append("L"); stringBuilder.append(((TypeElement) t.asElement()).getQualifiedName().toString().replace('.', '/')); stringBuilder.append(";"); return null; } @Override public Void visitExecutable(ExecutableType t, StringBuilder stringBuilder) { stringBuilder.append("("); for (TypeMirror p : t.getParameterTypes()) { p.accept(this, stringBuilder); } stringBuilder.append(")"); t.getReturnType().accept(this, stringBuilder); return null; } @Override public Void visitNoType(NoType t, StringBuilder stringBuilder) { if (t.getKind() == TypeKind.VOID) { stringBuilder.append("V"); } return null; } }, sbuf); return sbuf.toString(); } }