From 259e1ff41c520d1193045794f048541f8cbf7c2f Mon Sep 17 00:00:00 2001 From: Timothy Hoffman <4001421+tim-hoffman@users.noreply.github.com> Date: Tue, 6 Jul 2021 14:15:49 -0500 Subject: [PATCH 1/2] fix bugs in *ASMBackend bytecode version computation and add ASM validator and a test case for the 1.8 bug 1. bytecode version was not high enough when using special invoke to call a default method 2. AbstractASMBackend previously stopped increasing the version number if at least 1.8 was necessary for some method which may have missed a higher version number being required by a later method! The fix was to check the MAX version number instead. --- .../options/soot/options/Options.java | 7 + src/main/java/soot/AbstractASMBackend.java | 175 +++++++++++------- src/main/java/soot/baf/BafASMBackend.java | 49 +++-- src/main/xml/options/make-soot-options.xsl | 1 + .../java/soot/baf/BafASMBackendTest.java | 126 +++++++++++++ .../baf/BafASMBackendTestInput_Default.java | 63 +++++++ 6 files changed, 335 insertions(+), 86 deletions(-) create mode 100644 src/systemTest/java/soot/baf/BafASMBackendTest.java create mode 100644 src/systemTest/targets/soot/baf/BafASMBackendTestInput_Default.java diff --git a/src/main/generated/options/soot/options/Options.java b/src/main/generated/options/soot/options/Options.java index fbb832a9c78..c7865687642 100644 --- a/src/main/generated/options/soot/options/Options.java +++ b/src/main/generated/options/soot/options/Options.java @@ -51,6 +51,7 @@ public static Options v() { public static final int src_prec_apk = 5; public static final int src_prec_apk_class_jimple = 6; public static final int src_prec_apk_c_j = 6; + public static final int src_prec_MAX = 6; public static final int output_format_J = 1; public static final int output_format_jimple = 1; public static final int output_format_j = 2; @@ -81,6 +82,7 @@ public static Options v() { public static final int output_format_template = 16; public static final int output_format_a = 17; public static final int output_format_asm = 17; + public static final int output_format_MAX = 17; public static final int java_version_default = 1; public static final int java_version_1_1 = 2; public static final int java_version_1 = 2; @@ -106,21 +108,26 @@ public static Options v() { public static final int java_version_11 = 12; public static final int java_version_1_12 = 13; public static final int java_version_12 = 13; + public static final int java_version_MAX = 13; public static final int wrong_staticness_fail = 1; public static final int wrong_staticness_ignore = 2; public static final int wrong_staticness_fix = 3; public static final int wrong_staticness_fixstrict = 4; + public static final int wrong_staticness_MAX = 4; public static final int field_type_mismatches_fail = 1; public static final int field_type_mismatches_ignore = 2; public static final int field_type_mismatches_null = 3; + public static final int field_type_mismatches_MAX = 3; public static final int throw_analysis_pedantic = 1; public static final int throw_analysis_unit = 2; public static final int throw_analysis_dalvik = 3; public static final int throw_analysis_auto_select = 4; + public static final int throw_analysis_MAX = 4; public static final int check_init_throw_analysis_auto = 1; public static final int check_init_throw_analysis_pedantic = 2; public static final int check_init_throw_analysis_unit = 3; public static final int check_init_throw_analysis_dalvik = 4; + public static final int check_init_throw_analysis_MAX = 4; @SuppressWarnings("unused") public boolean parse(String[] argv) { diff --git a/src/main/java/soot/AbstractASMBackend.java b/src/main/java/soot/AbstractASMBackend.java index 660a340ce48..5822cfc1443 100644 --- a/src/main/java/soot/AbstractASMBackend.java +++ b/src/main/java/soot/AbstractASMBackend.java @@ -22,31 +22,36 @@ * #L% */ -import static soot.util.backend.ASMBackendUtils.createASMAttribute; -import static soot.util.backend.ASMBackendUtils.getDefaultValue; -import static soot.util.backend.ASMBackendUtils.slashify; -import static soot.util.backend.ASMBackendUtils.toTypeDesc; -import static soot.util.backend.ASMBackendUtils.translateJavaVersion; - import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.stream.Collectors; import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; +import org.objectweb.asm.util.CheckClassAdapter; import org.objectweb.asm.util.TraceClassVisitor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import soot.asm.AsmUtil; import soot.baf.BafBody; import soot.jimple.JimpleBody; @@ -77,7 +82,9 @@ import soot.tagkit.Tag; import soot.tagkit.VisibilityAnnotationTag; import soot.tagkit.VisibilityParameterAnnotationTag; +import soot.util.backend.ASMBackendUtils; import soot.util.backend.SootASMClassWriter; +import soot.validation.ValidationException; /** * Abstract super-class for ASM-based back-ends. Generates byte-code for everything except the method bodies, as they are @@ -86,14 +93,17 @@ * @author Tobias Hamann, Florian Kuebler, Dominik Helm, Lukas Sommer */ public abstract class AbstractASMBackend { + private static final Logger logger = LoggerFactory.getLogger(AbstractASMBackend.class); - private final Map bafBodyCache = new HashMap(); + private final Map bafBodyCache = new HashMap<>(); // The SootClass that is to be converted into bytecode protected final SootClass sc; // The Java version to be used for generating this class protected final int javaVersion; // An ASM ClassVisitor that is used to emit the bytecode to protected ClassVisitor cv; + // A ClassLoader used by the ASM validator (if validation is enabled) to load classes from the Soot classpath + protected ClassLoader sootClassLoader; /** * Creates a new ASM backend @@ -112,20 +122,44 @@ public AbstractASMBackend(SootClass sc, int javaVersion) { } int minVersion = getMinJavaVersion(sc); if (javaVersion != Options.java_version_default && javaVersion < minVersion) { - throw new IllegalArgumentException("Enforced Java version " + translateJavaVersion(javaVersion) - + " too low to support required features (" + translateJavaVersion(minVersion) + " required)"); + throw new IllegalArgumentException("Enforced Java version " + ASMBackendUtils.translateJavaVersion(javaVersion) + + " too low to support required features (" + ASMBackendUtils.translateJavaVersion(minVersion) + " required)"); } this.javaVersion = AsmUtil.javaToBytecodeVersion(Math.max(javaVersion, minVersion)); } /** - * Gets the baf body for the given SootMethod. This method will first check whether the method already has a baf body. If - * not, it will query the local cache. If this fails as well, it will construct a new baf body. + * Return a {@link ClassLoader} that loads classes from the Soot classpath. + * + * @return + */ + private ClassLoader getSootClasspathLoader() { + ClassLoader retVal = this.sootClassLoader; + if (retVal == null) { + List classPath = SourceLocator.v().classPath(); + URL[] urls = new URL[classPath.size()]; + for (ListIterator it = classPath.listIterator(); it.hasNext();) { + String cp = it.next(); + try { + URL url = Paths.get(cp).toUri().toURL(); + urls[it.previousIndex()] = url; + } catch (MalformedURLException ex) { + logger.warn("Cannot get URL for " + cp, ex); + } + } + this.sootClassLoader = retVal = URLClassLoader.newInstance(urls); + } + return retVal; + } + + /** + * Gets the {@link BafBody} for the given SootMethod. This method will first check whether the method already has a + * {@link BafBody}. If not, it will query the local cache. If this fails as well, it will construct a new {@link BafBody}. * * @param method - * The method for which to obtain a baf body - * @return The baf body for the given method + * The method for which to obtain a {@link BafBody} + * @return The {@link BafBody} for the given method */ protected BafBody getBafBody(SootMethod method) { final Body activeBody = method.getActiveBody(); @@ -134,18 +168,15 @@ protected BafBody getBafBody(SootMethod method) { } BafBody body = bafBodyCache.get(method); - if (body != null) { - return body; - } - - if (activeBody instanceof JimpleBody) { - body = PackManager.v().convertJimpleBodyToBaf(method); - } else { - throw new RuntimeException("ASM-backend can only translate Baf and Jimple bodies! Found " - + (activeBody == null ? "null" : activeBody.getClass().getName()) + '.'); + if (body == null) { + if (activeBody instanceof JimpleBody) { + body = PackManager.v().convertJimpleBodyToBaf(method); + bafBodyCache.put(method, body); + } else { + throw new RuntimeException("ASM-backend can only translate Baf and Jimple bodies! Found " + + (activeBody == null ? "null" : activeBody.getClass().getName()) + '.'); + } } - - bafBodyCache.put(method, body); return body; } @@ -181,13 +212,12 @@ private int getMinJavaVersion(SootClass sc) { // that we need to split methods, which are longer than the JVM spec allows. // This feature is work in progress. for (SootMethod sm : new ArrayList<>(sc.getMethods())) { - if (minVersion >= Options.java_version_1_8) { + if (minVersion >= Options.java_version_MAX) { + // Stop early if the max supported version has been reached break; } - if (sm.hasTag(VisibilityAnnotationTag.NAME) || sm.hasTag(VisibilityParameterAnnotationTag.NAME)) { - minVersion = Math.max(minVersion, Options.java_version_1_5); - } - if (containsGenericSignatureTag(sm)) { + if (sm.hasTag(VisibilityAnnotationTag.NAME) || sm.hasTag(VisibilityParameterAnnotationTag.NAME) + || containsGenericSignatureTag(sm)) { minVersion = Math.max(minVersion, Options.java_version_1_5); } if (sm.hasActiveBody()) { @@ -222,10 +252,29 @@ protected int getMinJavaVersion(SootMethod sm) { */ public void generateClassFile(OutputStream os) { ClassWriter cw = new SootASMClassWriter(ClassWriter.COMPUTE_FRAMES); - cv = cw; + this.cv = cw; generateByteCode(); + byte[] bytecode = cw.toByteArray(); + if (Options.v().validate()) { + String verifyMsg; + try { + // Run ASM verifier and ensure the message is empty (i.e. there are no VerifyErrors). + StringWriter strWriter = new StringWriter(); + CheckClassAdapter.verify(new ClassReader(bytecode), getSootClasspathLoader(), false, new PrintWriter(strWriter)); + verifyMsg = strWriter.toString(); + } catch (LinkageError e) { + // Just print a warning rather than throwing a ValidationException + // because this doesn't necessarily mean the bytecode is invalid, + // it just means some dependency may not be on the Soot classpath. + logger.warn("Failed to load " + this.sc + " for ASM verifier.", e); + verifyMsg = null; + } + if (verifyMsg != null && !verifyMsg.isEmpty()) { + throw new ValidationException(this.sc, "VerifyError(s) in bytecode:\n" + verifyMsg, "VerifyError(s) in bytecode."); + } + } try { - os.write(cw.toByteArray()); + os.write(bytecode); } catch (IOException e) { throw new RuntimeException("Could not write class file in the ASM-backend!", e); } @@ -238,7 +287,7 @@ public void generateClassFile(OutputStream os) { * The PrintWriter the textual representation is written to */ public void generateTextualRepresentation(PrintWriter pw) { - cv = new TraceClassVisitor(pw); + this.cv = new TraceClassVisitor(pw); generateByteCode(); } @@ -297,40 +346,37 @@ public int compare(SootMethod o1, SootMethod o2) { * Emits the bytecode for all methods of the class */ protected void generateMethods() { - List sortedMethods = new ArrayList(sc.getMethods()); + List sortedMethods = new ArrayList<>(sc.getMethods()); Collections.sort(sortedMethods, new SootMethodComparator()); for (SootMethod sm : sortedMethods) { if (sm.isPhantom()) { continue; } - final int access = getModifiers(sm.getModifiers(), sm); - final String name = sm.getName(); - StringBuilder descBuilder = new StringBuilder(5); descBuilder.append('('); for (Type t : sm.getParameterTypes()) { - descBuilder.append(toTypeDesc(t)); + descBuilder.append(ASMBackendUtils.toTypeDesc(t)); } descBuilder.append(')'); - descBuilder.append(toTypeDesc(sm.getReturnType())); + descBuilder.append(ASMBackendUtils.toTypeDesc(sm.getReturnType())); SignatureTag sigTag = (SignatureTag) sm.getTag(SignatureTag.NAME); String sig = sigTag == null ? null : sigTag.getSignature(); List exceptionList = sm.getExceptionsUnsafe(); String[] exceptions; - if (exceptionList != null) { + if (exceptionList == null) { + exceptions = new String[0]; + } else { exceptions = new String[exceptionList.size()]; - int i = 0; - for (SootClass exc : exceptionList) { - exceptions[i] = slashify(exc.getName()); - ++i; + for (ListIterator it = exceptionList.listIterator(); it.hasNext();) { + SootClass exc = it.next(); + exceptions[it.previousIndex()] = ASMBackendUtils.slashify(exc.getName()); } - } else { - exceptions = new String[0]; } - MethodVisitor mv = cv.visitMethod(access, name, descBuilder.toString(), sig, exceptions); + int access = getModifiers(sm.getModifiers(), sm); + MethodVisitor mv = cv.visitMethod(access, sm.getName(), descBuilder.toString(), sig, exceptions); if (mv != null) { // Visit parameter annotations for (Tag t : sm.getTags()) { @@ -359,9 +405,7 @@ protected void generateMethods() { if (sm.hasActiveBody()) { mv.visitCode(); generateMethodBody(mv, sm); - /* - * Correct values are computed automatically by ASM, but we need the call anyways. - */ + // Correct values are computed automatically by ASM, but we need the call anyway. mv.visitMaxs(0, 0); } mv.visitEnd(); @@ -377,15 +421,14 @@ protected void generateFields() { if (f.isPhantom()) { continue; } - String name = f.getName(); - String desc = toTypeDesc(f.getType()); + String desc = ASMBackendUtils.toTypeDesc(f.getType()); SignatureTag sigTag = (SignatureTag) f.getTag(SignatureTag.NAME); String sig = sigTag == null ? null : sigTag.getSignature(); - Object value = getDefaultValue(f); + Object value = ASMBackendUtils.getDefaultValue(f); int access = getModifiers(f.getModifiers(), f); - FieldVisitor fv = cv.visitField(access, name, desc, sig, value); + FieldVisitor fv = cv.visitField(access, f.getName(), desc, sig, value); if (fv != null) { generateAnnotations(fv, f); generateAttributes(fv, f); @@ -415,7 +458,7 @@ protected void generateInnerClassReferences() { if (!Options.v().no_output_inner_classes_attribute()) { InnerClassAttribute ica = (InnerClassAttribute) sc.getTag(InnerClassAttribute.NAME); if (ica != null) { - List sortedTags = new ArrayList(ica.getSpecs()); + List sortedTags = new ArrayList<>(ica.getSpecs()); Collections.sort(sortedTags, new SootInnerClassComparator()); writeInnerClassTags(sortedTags); } else { @@ -437,9 +480,9 @@ protected void generateInnerClassReferences() { */ protected void writeInnerClassTags(List sortedTags) { for (InnerClassTag ict : sortedTags) { - String name = slashify(ict.getInnerClass()); - String outerClassName = slashify(ict.getOuterClass()); - String innerName = slashify(ict.getShortName()); + String name = ASMBackendUtils.slashify(ict.getInnerClass()); + String outerClassName = ASMBackendUtils.slashify(ict.getOuterClass()); + String innerName = ASMBackendUtils.slashify(ict.getShortName()); int access = ict.getAccessFlags(); cv.visitInnerClass(name, outerClassName, innerName, access); } @@ -451,7 +494,7 @@ protected void writeInnerClassTags(List sortedTags) { protected void generateAttributes() { for (Tag t : sc.getTags()) { if (t instanceof Attribute) { - cv.visitAttribute(createASMAttribute((Attribute) t)); + cv.visitAttribute(ASMBackendUtils.createASMAttribute((Attribute) t)); } } } @@ -467,7 +510,7 @@ protected void generateAttributes() { protected void generateAttributes(FieldVisitor fv, SootField f) { for (Tag t : f.getTags()) { if (t instanceof Attribute) { - fv.visitAttribute(createASMAttribute((Attribute) t)); + fv.visitAttribute(ASMBackendUtils.createASMAttribute((Attribute) t)); } } } @@ -483,7 +526,7 @@ protected void generateAttributes(FieldVisitor fv, SootField f) { protected void generateAttributes(MethodVisitor mv, SootMethod m) { for (Tag t : m.getTags()) { if (t instanceof Attribute) { - mv.visitAttribute(createASMAttribute((Attribute) t)); + mv.visitAttribute(ASMBackendUtils.createASMAttribute((Attribute) t)); } } } @@ -610,13 +653,13 @@ protected void generateAnnotationElems(AnnotationVisitor av, Collection localToSlot = new HashMap(); - /** - * Returns the ASM Label for a given Unit that is the target of a branch or jump - * - * @param target - * The unit that is the branch target - * @return The Label that specifies this unit - */ - protected Label getBranchTargetLabel(Unit target) { - return branchTargetLabels.get(target); - } - /** * Creates a new BafASMBackend with a given enforced java version * @@ -119,6 +108,17 @@ public BafASMBackend(SootClass sc, int javaVersion) { super(sc, javaVersion); } + /** + * Returns the ASM Label for a given Unit that is the target of a branch or jump + * + * @param target + * The unit that is the branch target + * @return The Label that specifies this unit + */ + protected Label getBranchTargetLabel(Unit target) { + return branchTargetLabels.get(target); + } + /* * (non-Javadoc) * @@ -137,23 +137,32 @@ protected int getMinJavaVersion(SootMethod method) { } for (Unit u : body.getUnits()) { - if (minVersion == Options.java_version_1_9) { - return minVersion; + if (minVersion >= Options.java_version_1_9) { + // Stop early since no case below goes above 1.9 + break; } if (u instanceof DynamicInvokeInst) { minVersion = Math.max(minVersion, Options.java_version_1_7); - } - if (u instanceof PushInst) { + } else if (u instanceof SpecialInvokeInst) { + // INVOKESPECIAL can't be used with interfaces prior to Java 8 + if (((SpecialInvokeInst) u).getMethodRef().getDeclaringClass().isInterface()) { + minVersion = Math.max(minVersion, Options.java_version_1_8); + } + } else if (u instanceof PushInst) { Constant constant = ((PushInst) u).getConstant(); if (constant instanceof ClassConstant) { minVersion = Math.max(minVersion, Options.java_version_1_5); } String typeString = constant.getType().toQuotedString(); - if (PolymorphicMethodRef.METHODHANDLE_SIGNATURE.equals(typeString)) { - minVersion = Math.max(minVersion, Options.java_version_1_7); - } - if (PolymorphicMethodRef.VARHANDLE_SIGNATURE.equals(typeString)) { - minVersion = Math.max(minVersion, Options.java_version_1_9); + if (typeString != null) { + switch (typeString) { + case PolymorphicMethodRef.VARHANDLE_SIGNATURE: + minVersion = Math.max(minVersion, Options.java_version_1_9); + break; + case PolymorphicMethodRef.METHODHANDLE_SIGNATURE: + minVersion = Math.max(minVersion, Options.java_version_1_7); + break; + } } } } diff --git a/src/main/xml/options/make-soot-options.xsl b/src/main/xml/options/make-soot-options.xsl index 1285c93f837..f97207e75ba 100644 --- a/src/main/xml/options/make-soot-options.xsl +++ b/src/main/xml/options/make-soot-options.xsl @@ -392,6 +392,7 @@ public class Options extends OptionsBase { public static final int _ = ; + public static final int _MAX = ; diff --git a/src/systemTest/java/soot/baf/BafASMBackendTest.java b/src/systemTest/java/soot/baf/BafASMBackendTest.java new file mode 100644 index 00000000000..2219aabcd5e --- /dev/null +++ b/src/systemTest/java/soot/baf/BafASMBackendTest.java @@ -0,0 +1,126 @@ +package soot.baf; + +/*- + * #%L + * Soot - a J*va Optimization Framework + * %% + * Copyright (C) 2021 Timothy Hoffman + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * . + * #L% + */ + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.util.CheckClassAdapter; +import org.powermock.core.classloader.annotations.PowerMockIgnore; + +import soot.G; +import soot.ModulePathSourceLocator; +import soot.ModuleScene; +import soot.Scene; +import soot.SootClass; +import soot.SootMethod; +import soot.options.Options; +import soot.testing.framework.AbstractTestingFramework; + +/** + * Test the bytecode version computation in {@link soot.baf.BafASMBackend}. + * + * @author Timothy Hoffman + */ +@PowerMockIgnore({ "com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "org.w3c.*" }) +public class BafASMBackendTest extends AbstractTestingFramework { + + @Override + protected void setupSoot() { + } + + @Test + public void testCachingInvalidation() throws Exception { + final String targetClass = "soot.baf.BafASMBackendTestInput_Default"; + final Scene scene = customSetupLib(rtJar(), Collections.singletonList(targetClass)); + + SootClass sc = getOrResolveSootClass(scene, targetClass, SootClass.BODIES); + Assert.assertNotNull(sc); + Assert.assertEquals(targetClass, sc.getName()); + + // Ensure the class was loaded properly with all bodies + Assert.assertEquals(6, sc.getMethodCount()); + Assert.assertTrue(sc.getMethods().stream().allMatch(SootMethod::hasActiveBody)); + + // Ensure the version will be derived only by BafASMBackend + // (and not from the input source version). + Assert.assertEquals(0, Options.v().java_version()); + + byte[] bytecode = generateBytecode(sc); + + // Run ASM verifier and ensure the message is empty (i.e. there are no VerifyErrors). + StringWriter strWriter = new StringWriter(); + CheckClassAdapter.verify(new ClassReader(bytecode), null, false, new PrintWriter(strWriter)); + String verifyMsg = strWriter.toString(); + Assert.assertTrue(verifyMsg, verifyMsg.isEmpty()); + } + + private Scene customSetupLib(String rtJar, List classNames) { + G.reset(); + + final Options opts = Options.v(); + + opts.set_whole_program(true); + opts.set_output_format(Options.output_format_none); + opts.set_allow_phantom_refs(true); + opts.set_no_bodies_for_excluded(true); + opts.set_exclude(getExcludes()); + opts.set_include(classNames); + opts.set_process_dir(Collections.singletonList(SYSTEMTEST_TARGET_CLASSES_DIR)); + + // Disable deriving Java version from the input bytecode so that the + // version is computed based only on the logic in BafASMBackend. + opts.set_derive_java_version(false); + + final boolean isModuleJar = isModuleJar(rtJar); + if (isModuleJar) { + opts.set_soot_modulepath(ModulePathSourceLocator.DUMMY_CLASSPATH_JDK9_FS); + } else { + opts.set_soot_classpath(rtJar); + } + + // NOTE: must obtain Scene after all options are set + Scene scene = isModuleJar ? ModuleScene.v() : Scene.v(); + scene.loadNecessaryClasses(); + runSoot(); + return scene; + } + + private static String rtJar() throws IOException { + Path p = Paths.get(System.getProperty("java.home"), "lib", "rt.jar"); + return Files.exists(p) ? p.toRealPath().toString() : null; + } + + private static boolean isModuleJar(String rtJar) { + return (rtJar == null); + } +} diff --git a/src/systemTest/targets/soot/baf/BafASMBackendTestInput_Default.java b/src/systemTest/targets/soot/baf/BafASMBackendTestInput_Default.java new file mode 100644 index 00000000000..29dd4da9df7 --- /dev/null +++ b/src/systemTest/targets/soot/baf/BafASMBackendTestInput_Default.java @@ -0,0 +1,63 @@ +package soot.baf; + +/*- + * #%L + * Soot - a J*va Optimization Framework + * %% + * Copyright (C) 2021 Timothy Hoffman + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * . + * #L% + */ +import java.lang.annotation.Annotation; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.TypeVariable; + +/** + * Copied {@link #isAnnotationPresent} from {@link java.lang.Class} because it contains a call to a "default" method in an + * interface which requires {@link soot.baf.BafASMBackend} to produce bytecode version 1.8 or greater. + * + * @author Timothy Hoffman + */ +public class BafASMBackendTestInput_Default implements GenericDeclaration { + + public BafASMBackendTestInput_Default() { + } + + @Override + public boolean isAnnotationPresent(Class annotationClass) { + return GenericDeclaration.super.isAnnotationPresent(annotationClass); + } + + @Override + public TypeVariable[] getTypeParameters() { + throw new UnsupportedOperationException(); + } + + @Override + public T getAnnotation(Class annotationClass) { + throw new UnsupportedOperationException(); + } + + @Override + public Annotation[] getAnnotations() { + throw new UnsupportedOperationException(); + } + + @Override + public Annotation[] getDeclaredAnnotations() { + throw new UnsupportedOperationException(); + } +} From a06e6ad251574224a65da18dcf125181e87bef67 Mon Sep 17 00:00:00 2001 From: Timothy Hoffman <4001421+tim-hoffman@users.noreply.github.com> Date: Wed, 21 Jul 2021 18:34:30 -0500 Subject: [PATCH 2/2] fix the test case Java11 failure --- .../java/soot/baf/BafASMBackendTest.java | 56 +------------------ .../framework/AbstractTestingFramework.java | 11 ++-- 2 files changed, 7 insertions(+), 60 deletions(-) diff --git a/src/systemTest/java/soot/baf/BafASMBackendTest.java b/src/systemTest/java/soot/baf/BafASMBackendTest.java index 2219aabcd5e..89a9a0e952d 100644 --- a/src/systemTest/java/soot/baf/BafASMBackendTest.java +++ b/src/systemTest/java/soot/baf/BafASMBackendTest.java @@ -22,14 +22,8 @@ * #L% */ -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Collections; -import java.util.List; import org.junit.Assert; import org.junit.Test; @@ -37,10 +31,6 @@ import org.objectweb.asm.util.CheckClassAdapter; import org.powermock.core.classloader.annotations.PowerMockIgnore; -import soot.G; -import soot.ModulePathSourceLocator; -import soot.ModuleScene; -import soot.Scene; import soot.SootClass; import soot.SootMethod; import soot.options.Options; @@ -56,14 +46,14 @@ public class BafASMBackendTest extends AbstractTestingFramework { @Override protected void setupSoot() { + final Options opts = Options.v(); + opts.set_derive_java_version(false); } @Test public void testCachingInvalidation() throws Exception { final String targetClass = "soot.baf.BafASMBackendTestInput_Default"; - final Scene scene = customSetupLib(rtJar(), Collections.singletonList(targetClass)); - - SootClass sc = getOrResolveSootClass(scene, targetClass, SootClass.BODIES); + SootClass sc = prepareTarget("<" + targetClass + ": void ()>", targetClass).getDeclaringClass(); Assert.assertNotNull(sc); Assert.assertEquals(targetClass, sc.getName()); @@ -83,44 +73,4 @@ public void testCachingInvalidation() throws Exception { String verifyMsg = strWriter.toString(); Assert.assertTrue(verifyMsg, verifyMsg.isEmpty()); } - - private Scene customSetupLib(String rtJar, List classNames) { - G.reset(); - - final Options opts = Options.v(); - - opts.set_whole_program(true); - opts.set_output_format(Options.output_format_none); - opts.set_allow_phantom_refs(true); - opts.set_no_bodies_for_excluded(true); - opts.set_exclude(getExcludes()); - opts.set_include(classNames); - opts.set_process_dir(Collections.singletonList(SYSTEMTEST_TARGET_CLASSES_DIR)); - - // Disable deriving Java version from the input bytecode so that the - // version is computed based only on the logic in BafASMBackend. - opts.set_derive_java_version(false); - - final boolean isModuleJar = isModuleJar(rtJar); - if (isModuleJar) { - opts.set_soot_modulepath(ModulePathSourceLocator.DUMMY_CLASSPATH_JDK9_FS); - } else { - opts.set_soot_classpath(rtJar); - } - - // NOTE: must obtain Scene after all options are set - Scene scene = isModuleJar ? ModuleScene.v() : Scene.v(); - scene.loadNecessaryClasses(); - runSoot(); - return scene; - } - - private static String rtJar() throws IOException { - Path p = Paths.get(System.getProperty("java.home"), "lib", "rt.jar"); - return Files.exists(p) ? p.toRealPath().toString() : null; - } - - private static boolean isModuleJar(String rtJar) { - return (rtJar == null); - } } diff --git a/src/systemTest/java/soot/testing/framework/AbstractTestingFramework.java b/src/systemTest/java/soot/testing/framework/AbstractTestingFramework.java index fb85492f3d7..b681bc2eba1 100644 --- a/src/systemTest/java/soot/testing/framework/AbstractTestingFramework.java +++ b/src/systemTest/java/soot/testing/framework/AbstractTestingFramework.java @@ -240,13 +240,10 @@ private SootClass makeDummyClass(SootMethod sootTestMethod) { return sootClass; } - private String classFromSignature(String targetMethod) { - return targetMethod.substring(1, targetMethod.indexOf(':')); - } - - private SootMethod getMethodForSig(String sig) { - Scene.v().forceResolve(classFromSignature(sig), SootClass.BODIES); - return Scene.v().getMethod(sig); + private static SootMethod getMethodForSig(String sig) { + final Scene scene = Scene.v(); + scene.forceResolve(Scene.signatureToClass(sig), SootClass.BODIES); + return scene.getMethod(sig); } /**