diff --git a/asm/src/main/java/org/aspectj/asm/AsmManager.java b/asm/src/main/java/org/aspectj/asm/AsmManager.java index 048e3c1808..85976c3547 100644 --- a/asm/src/main/java/org/aspectj/asm/AsmManager.java +++ b/asm/src/main/java/org/aspectj/asm/AsmManager.java @@ -21,8 +21,10 @@ import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; +import java.io.InvalidClassException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; import java.io.Writer; import java.util.ArrayList; import java.util.Collection; @@ -30,6 +32,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; @@ -38,7 +41,10 @@ import org.aspectj.asm.internal.AspectJElementHierarchy; import org.aspectj.asm.internal.HandleProviderDelimiter; import org.aspectj.asm.internal.JDTLikeHandleProvider; +import org.aspectj.asm.internal.ProgramElement; +import org.aspectj.asm.internal.Relationship; import org.aspectj.asm.internal.RelationshipMap; +import org.aspectj.bridge.SourceLocation; import org.aspectj.bridge.ISourceLocation; import org.aspectj.util.IStructureModel; @@ -248,13 +254,13 @@ public void readStructureModel(String configFilePath) { hierarchy.setRoot(IHierarchy.NO_STRUCTURE); } else { String filePath = genExternFilePath(configFilePath); - FileInputStream in = new FileInputStream(filePath); - ObjectInputStream s = new ObjectInputStream(in); - hierarchy = (AspectJElementHierarchy) s.readObject(); - ((AspectJElementHierarchy) hierarchy).setAsmManager(this); - hierarchyReadOK = true; - mapper = (RelationshipMap) s.readObject(); - s.close(); + try (FileInputStream in = new FileInputStream(filePath); + ObjectInputStream s = new StructureModelObjectInputStream(in)) { + hierarchy = (AspectJElementHierarchy) s.readObject(); + ((AspectJElementHierarchy) hierarchy).setAsmManager(this); + hierarchyReadOK = true; + mapper = (RelationshipMap) s.readObject(); + } } } catch (FileNotFoundException fnfe) { // That is OK @@ -284,6 +290,71 @@ private String genExternFilePath(String configFilePath) { return configFilePath + ".ajsym"; } + private static class StructureModelObjectInputStream extends ObjectInputStream { + + private static final Set ALLOWED_SERIALIZED_TYPES = new HashSet<>(); + + static { + ALLOWED_SERIALIZED_TYPES.add(AspectJElementHierarchy.class.getName()); + ALLOWED_SERIALIZED_TYPES.add(ProgramElement.class.getName()); + ALLOWED_SERIALIZED_TYPES.add(RelationshipMap.class.getName()); + ALLOWED_SERIALIZED_TYPES.add(Relationship.class.getName()); + ALLOWED_SERIALIZED_TYPES.add(IProgramElement.Accessibility.class.getName()); + ALLOWED_SERIALIZED_TYPES.add(IProgramElement.ExtraInformation.class.getName()); + ALLOWED_SERIALIZED_TYPES.add(IProgramElement.Kind.class.getName()); + ALLOWED_SERIALIZED_TYPES.add(IProgramElement.Modifiers.class.getName()); + ALLOWED_SERIALIZED_TYPES.add(IRelationship.Kind.class.getName()); + ALLOWED_SERIALIZED_TYPES.add(SourceLocation.class.getName()); + ALLOWED_SERIALIZED_TYPES.add(File.class.getName()); + ALLOWED_SERIALIZED_TYPES.add(String.class.getName()); + ALLOWED_SERIALIZED_TYPES.add(Boolean.class.getName()); + ALLOWED_SERIALIZED_TYPES.add(Integer.class.getName()); + ALLOWED_SERIALIZED_TYPES.add(ArrayList.class.getName()); + ALLOWED_SERIALIZED_TYPES.add(HashMap.class.getName()); + ALLOWED_SERIALIZED_TYPES.add(LinkedHashMap.class.getName()); + ALLOWED_SERIALIZED_TYPES.add("java.util.Arrays$ArrayList"); + ALLOWED_SERIALIZED_TYPES.add("java.util.Collections$EmptyList"); + ALLOWED_SERIALIZED_TYPES.add("java.util.Collections$EmptyMap"); + ALLOWED_SERIALIZED_TYPES.add("java.util.Collections$SingletonList"); + ALLOWED_SERIALIZED_TYPES.add("java.util.Collections$UnmodifiableCollection"); + ALLOWED_SERIALIZED_TYPES.add("java.util.Collections$UnmodifiableList"); + ALLOWED_SERIALIZED_TYPES.add("java.util.Collections$UnmodifiableRandomAccessList"); + } + + StructureModelObjectInputStream(FileInputStream in) throws IOException { + super(in); + } + + @Override + protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { + String className = desc.getName(); + if (!isAllowedSerializedType(className)) { + throw new InvalidClassException(className, "not allowed in AspectJ structure model"); + } + return super.resolveClass(desc); + } + + private static boolean isAllowedSerializedType(String className) { + if (className.startsWith("[")) { + return isAllowedArrayType(className); + } + return ALLOWED_SERIALIZED_TYPES.contains(className); + } + + private static boolean isAllowedArrayType(String className) { + while (className.startsWith("[")) { + className = className.substring(1); + } + if (className.length() == 1) { + return true; + } + if (className.startsWith("L") && className.endsWith(";")) { + return isAllowedSerializedType(className.substring(1, className.length() - 1)); + } + return false; + } + } + public String getCanonicalFilePath(File f) { return canonicalFilePathMap.get(f); } diff --git a/asm/src/test/java/org/aspectj/asm/AsmManagerTest.java b/asm/src/test/java/org/aspectj/asm/AsmManagerTest.java new file mode 100644 index 0000000000..15882e2762 --- /dev/null +++ b/asm/src/test/java/org/aspectj/asm/AsmManagerTest.java @@ -0,0 +1,168 @@ +package org.aspectj.asm; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.aspectj.asm.internal.ProgramElement; +import org.aspectj.bridge.SourceLocation; + +import junit.framework.TestCase; + +public class AsmManagerTest extends TestCase { + + public void testReadStructureModelRoundTripRestoresHierarchyAndRelationships() throws Exception { + File configFile = File.createTempFile("asm-manager-test", ".lst"); + File sourceFile = File.createTempFile("asm-manager-source", ".java"); + File structureModelFile = new File(configFile.getParentFile(), configFile.getName().replace(".lst", ".ajsym")); + try { + AsmManager asm = buildModel(sourceFile); + ProgramElement fileNode = (ProgramElement) asm.getHierarchy().getRoot().getChildren().get(0); + asm.getRelationshipMap().get(fileNode, IRelationship.Kind.ADVICE, "advises", false, true).addTarget("=project/target"); + asm.writeStructureModel(configFile.getAbsolutePath()); + + AsmManager restored = AsmManager.createNewStructureModel(Collections.emptyMap()); + restored.readStructureModel(configFile.getAbsolutePath()); + + assertNotSame(IHierarchy.NO_STRUCTURE, restored.getHierarchy().getRoot()); + assertEquals("project", restored.getHierarchy().getRoot().getName()); + assertNotNull(restored.getHierarchy().findInFileMap(sourceFile.getCanonicalPath())); + assertEquals(1, restored.getRelationshipMap().get(fileNode.getHandleIdentifier()).size()); + } finally { + configFile.delete(); + sourceFile.delete(); + structureModelFile.delete(); + } + } + + public void testPrePatchRawObjectInputStreamWouldExecuteReadObjectCallback() throws Exception { + File structureModelFile = File.createTempFile("asm-manager-test", ".ajsym"); + try { + writeStructureModelPayload(structureModelFile, new UnexpectedSerializedType()); + UnexpectedSerializedType.readObjectInvoked = false; + try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(structureModelFile))) { + in.readObject(); + } + assertTrue(UnexpectedSerializedType.readObjectInvoked); + } finally { + structureModelFile.delete(); + } + } + + public void testPatchedReadStructureModelRejectsUnexpectedSerializedTypesBeforeReadObject() throws Exception { + File configFile = File.createTempFile("asm-manager-test", ".lst"); + File structureModelFile = new File(configFile.getParentFile(), configFile.getName().replace(".lst", ".ajsym")); + try { + writeStructureModelPayload(structureModelFile, new UnexpectedSerializedType()); + UnexpectedSerializedType.readObjectInvoked = false; + + AsmManager asm = AsmManager.createNewStructureModel(Collections.emptyMap()); + asm.readStructureModel(configFile.getAbsolutePath()); + + assertFalse(UnexpectedSerializedType.readObjectInvoked); + assertSame(IHierarchy.NO_STRUCTURE, asm.getHierarchy().getRoot()); + } finally { + configFile.delete(); + structureModelFile.delete(); + } + } + + public void testReadStructureModelSupportsArraysAsList() throws Exception { + assertModelRoundTripWithCustomData(new ProgramElementMutator() { + public void mutate(ProgramElement node) { + node.setParameterNames(Arrays.asList("a", "b")); + } + }); + } + + public void testReadStructureModelSupportsUnmodifiableList() throws Exception { + assertModelRoundTripWithCustomData(new ProgramElementMutator() { + public void mutate(ProgramElement node) { + List values = new ArrayList<>(); + values.add("a"); + values.add("b"); + node.setParameterNames(Collections.unmodifiableList(values)); + } + }); + } + + public void testReadStructureModelSupportsLinkedHashMap() throws Exception { + assertModelRoundTripWithCustomData(new ProgramElementMutator() { + public void mutate(ProgramElement node) { + Map> map = new LinkedHashMap<>(); + map.put("Type", Arrays.asList("Parent")); + node.setDeclareParentsMap(map); + } + }); + } + + private void assertModelRoundTripWithCustomData(ProgramElementMutator mutator) throws Exception { + File configFile = File.createTempFile("asm-manager-test", ".lst"); + File sourceFile = File.createTempFile("asm-manager-source", ".java"); + File structureModelFile = new File(configFile.getParentFile(), configFile.getName().replace(".lst", ".ajsym")); + try { + AsmManager asm = buildModel(sourceFile); + ProgramElement fileNode = (ProgramElement) asm.getHierarchy().getRoot().getChildren().get(0); + mutator.mutate(fileNode); + asm.writeStructureModel(configFile.getAbsolutePath()); + + AsmManager restored = AsmManager.createNewStructureModel(Collections.emptyMap()); + restored.readStructureModel(configFile.getAbsolutePath()); + + assertNotSame(IHierarchy.NO_STRUCTURE, restored.getHierarchy().getRoot()); + assertNotNull(restored.getHierarchy().findInFileMap(sourceFile.getCanonicalPath())); + } finally { + configFile.delete(); + sourceFile.delete(); + structureModelFile.delete(); + } + } + + private AsmManager buildModel(File sourceFile) throws Exception { + AsmManager asm = AsmManager.createNewStructureModel(Collections.emptyMap()); + ProgramElement root = new ProgramElement(asm, "project", IProgramElement.Kind.PROJECT, null); + ProgramElement fileNode = new ProgramElement(asm, sourceFile.getName(), IProgramElement.Kind.FILE_JAVA, + new SourceLocation(sourceFile, 1, 1, 1), 0, null, null); + fileNode.setHandleIdentifier("=project/" + sourceFile.getName()); + root.addChild(fileNode); + asm.getHierarchy().setRoot(root); + HashMap fileMap = new HashMap<>(); + fileMap.put(sourceFile.getCanonicalPath(), fileNode); + asm.getHierarchy().setFileMap(fileMap); + return asm; + } + + private static void writeStructureModelPayload(File file, Object payload) throws IOException { + ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file)); + try { + out.writeObject(payload); + } finally { + out.close(); + } + } + + private static class UnexpectedSerializedType implements Serializable { + private static final long serialVersionUID = 1L; + private static boolean readObjectInvoked; + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + readObjectInvoked = true; + in.defaultReadObject(); + } + } + + private interface ProgramElementMutator { + void mutate(ProgramElement node); + } +}