public boolean mustCompile(Clazz clazz) { File oFile = config.getOFile(clazz); if (!oFile.exists() || oFile.lastModified() < clazz.lastModified()) { return true; } Set<Dependency> dependencies = clazz.getDependencies(); for (Dependency dep : dependencies) { Clazz depClazz = config.getClazzes().load(dep.getClassName()); if (depClazz == null) { if (dep.getPath() != null) { // depClazz was available the last time clazz was compiled but is now gone return true; } } else { if (dep.getPath() == null) { // depClazz was not available the last time clazz was compiled but is now available return true; } if (!dep.getPath().equals(depClazz.getPath().getFile().getAbsolutePath())) { // depClazz was located in another place the last time clazz was built return true; } if (depClazz.isInBootClasspath() != dep.isInBootClasspath()) { // depClazz has moved to/from the bootclasspath since the last time clazz was built return true; } if (depClazz.lastModified() > oFile.lastModified()) { // depClazz has been changed since the last time clazz was built return true; } } } // No class or interface has zero dependencies (we always add java.lang.Object as a dependency) // If dependencies is empty it probably means that an error occurred while reading the // serialized dependencies. By returning true here in that case the class will be recompiled // and the dependencies regenerated. return dependencies.isEmpty(); }
public void compile(Clazz clazz) throws IOException { reset(); File llFile = config.getLlFile(clazz); File bcFile = config.getBcFile(clazz); File sFile = config.getSFile(clazz); File oFile = config.getOFile(clazz); llFile.getParentFile().mkdirs(); bcFile.getParentFile().mkdirs(); sFile.getParentFile().mkdirs(); oFile.getParentFile().mkdirs(); OutputStream out = null; try { config.getLogger().debug("Compiling %s", clazz); out = new FileOutputStream(llFile); compile(clazz, out); } catch (Throwable t) { FileUtils.deleteQuietly(llFile); if (t instanceof IOException) { throw (IOException) t; } if (t instanceof RuntimeException) { throw (RuntimeException) t; } throw new RuntimeException(t); } finally { IOUtils.closeQuietly(out); } config.getLogger().debug("Optimizing %s", clazz); CompilerUtil.opt(config, llFile, bcFile, "-mem2reg", "-always-inline"); config.getLogger().debug("Generating %s assembly for %s", config.getArch(), clazz); CompilerUtil.llc(config, bcFile, sFile); patchAsmWithFunctionSizes(clazz, sFile); config.getLogger().debug("Assembling %s", clazz); CompilerUtil.assemble(config, sFile, oFile); }
private StructureConstant createClassInfoErrorStruct() { /* * Check that clazz can be loaded, i.e. that the superclass * and interfaces of the class exist and are accessible to the * class. Also check that any exception the class uses in catch * clauses exist and is accessible to the class. If the class * cannot be loaded we override the ClassInfoHeader struct * produced by the ClassCompiler for the class with one which * tells the code in bc.c to throw an appropriate exception * whenever clazz is accessed. */ int errorType = ClassCompiler.CI_ERROR_TYPE_NONE; String errorMessage = null; if (!sootClass.isInterface() && sootClass.hasSuperclass()) { // Check superclass SootClass superclazz = sootClass.getSuperclass(); if (superclazz.isPhantom()) { errorType = ClassCompiler.CI_ERROR_TYPE_NO_CLASS_DEF_FOUND; errorMessage = superclazz.getName(); } else if (!checkClassAccessible(superclazz, sootClass)) { errorType = ClassCompiler.CI_ERROR_TYPE_ILLEGAL_ACCESS; errorMessage = String.format(ILLEGAL_ACCESS_ERROR_CLASS, superclazz, sootClass); } else if (superclazz.isInterface()) { errorType = ClassCompiler.CI_ERROR_TYPE_INCOMPATIBLE_CLASS_CHANGE; errorMessage = String.format("class %s has interface %s as super class", sootClass, superclazz); } // No need to check for ClassCircularityError. Soot doesn't handle // such problems so the compilation will fail earlier. } if (errorType == ClassCompiler.CI_ERROR_TYPE_NONE) { // Check interfaces for (SootClass interfaze : sootClass.getInterfaces()) { if (interfaze.isPhantom()) { errorType = ClassCompiler.CI_ERROR_TYPE_NO_CLASS_DEF_FOUND; errorMessage = interfaze.getName(); break; } else if (!checkClassAccessible(interfaze, sootClass)) { errorType = ClassCompiler.CI_ERROR_TYPE_ILLEGAL_ACCESS; errorMessage = String.format(ILLEGAL_ACCESS_ERROR_CLASS, interfaze, sootClass); break; } else if (!interfaze.isInterface()) { errorType = ClassCompiler.CI_ERROR_TYPE_INCOMPATIBLE_CLASS_CHANGE; errorMessage = String.format( "class %s tries to implement class %s as interface", sootClass, interfaze); break; } } } if (errorType == ClassCompiler.CI_ERROR_TYPE_NONE) { // Check exceptions used in catch clauses. I cannot find any info in // the VM spec specifying that this has to be done when the class is loaded. // However, this is how it's done in other VMs so we do it too. for (String exName : catches) { Clazz ex = config.getClazzes().load(exName); if (ex == null || ex.getSootClass().isPhantom()) { errorType = ClassCompiler.CI_ERROR_TYPE_NO_CLASS_DEF_FOUND; errorMessage = exName; break; } else if (!checkClassAccessible(ex.getSootClass(), sootClass)) { errorType = ClassCompiler.CI_ERROR_TYPE_ILLEGAL_ACCESS; errorMessage = String.format(ILLEGAL_ACCESS_ERROR_CLASS, ex, sootClass); break; } } } if (errorType == ClassCompiler.CI_ERROR_TYPE_NONE) { return null; } // Create a ClassInfoError struct StructureConstantBuilder error = new StructureConstantBuilder(); error.add(new NullConstant(I8_PTR)); // Points to the runtime Class struct error.add(new IntegerConstant(ClassCompiler.CI_ERROR)); error.add(getString(getInternalName(sootClass))); error.add(new IntegerConstant(errorType)); error.add(getString(errorMessage)); return error.build(); }
private void compile(Clazz clazz, OutputStream out) throws IOException { sootClass = clazz.getSootClass(); mb = new ModuleBuilder(); trampolines = new HashSet<Trampoline>(); catches = new HashSet<String>(); classFields = getClassFields(sootClass); instanceFields = getInstanceFields(sootClass); classType = getClassType(sootClass); instanceType = getInstanceType(sootClass); attributesEncoder.encode(mb, sootClass); // Add a <clinit> method if the class has ConstantValueTags but no <clinit>. // This has to be done before createInfoStruct() is called otherwise the // ClassInfoHeader->initializer value will become NULL and constant static fields // will never be initialized. if (!sootClass.declaresMethodByName("<clinit>") && hasConstantValueTags(classFields)) { SootMethod clinit = new SootMethod("<clinit>", Collections.EMPTY_LIST, VoidType.v(), Modifier.STATIC); JimpleBody body = Jimple.v().newBody(clinit); clinit.setActiveBody(body); body.getUnits().add(new JReturnVoidStmt()); this.sootClass.addMethod(clinit); } if (isStruct(sootClass)) { if (!Modifier.isFinal(sootClass.getModifiers())) { throw new IllegalArgumentException("Struct class must be final"); } SootMethod _sizeOf = new SootMethod("_sizeOf", Collections.EMPTY_LIST, IntType.v(), Modifier.PROTECTED); sootClass.addMethod(_sizeOf); SootMethod sizeOf = new SootMethod( "sizeOf", Collections.EMPTY_LIST, IntType.v(), Modifier.PUBLIC | Modifier.STATIC); sootClass.addMethod(sizeOf); } mb.addInclude( getClass() .getClassLoader() .getResource( String.format("header-%s-%s.ll", config.getOs().getFamily(), config.getArch()))); mb.addInclude(getClass().getClassLoader().getResource("header.ll")); mb.addFunction(createInstanceof()); mb.addFunction(createCheckcast()); mb.addFunction(createLdcClass()); mb.addFunction(createLdcClassWrapper()); Function allocator = createAllocator(); mb.addFunction(allocator); mb.addFunction(createClassInitWrapperFunction(allocator.ref())); for (SootField f : sootClass.getFields()) { Function getter = createFieldGetter(f); Function setter = createFieldSetter(f); mb.addFunction(getter); mb.addFunction(setter); if (f.isStatic() && !f.isPrivate()) { mb.addFunction(createClassInitWrapperFunction(getter.ref())); if (!f.isFinal()) { mb.addFunction(createClassInitWrapperFunction(setter.ref())); } } } for (SootMethod method : sootClass.getMethods()) { String name = method.getName(); if (isBridge(method)) { bridgeMethod(method); } else if (isStruct(sootClass) && ("_sizeOf".equals(name) || "sizeOf".equals(name) || isStructMember(method))) { structMember(method); } else if (method.isNative()) { nativeMethod(method); } else if (!method.isAbstract()) { method(method); } if (!name.equals("<clinit>") && !name.equals("<init>") && !method.isPrivate() && !method.isStatic() && !Modifier.isFinal(method.getModifiers()) && !Modifier.isFinal(sootClass.getModifiers())) { createLookupFunction(method); } if (method.isStatic()) { String fnName = mangleMethod(method); if (method.isSynchronized()) { fnName += "_synchronized"; } FunctionRef fn = new FunctionRef(fnName, getFunctionType(method)); mb.addFunction(createClassInitWrapperFunction(fn)); } } Set<String> trampolineDependencies = new HashSet<String>(); for (Trampoline trampoline : trampolines) { trampolineResolver.compile(mb, trampoline); trampolineDependencies.addAll(trampolineResolver.getDependencies()); } Global classInfoStruct = null; StructureConstant classInfoErrorStruct = createClassInfoErrorStruct(); if (classInfoErrorStruct != null) { // The class cannot be loaded at runtime. Replace the ClassInfo struct // with a ClassInfoError struct with details of why. classInfoStruct = new Global(mangleClass(sootClass) + "_info_struct", classInfoErrorStruct); } else { classInfoStruct = new Global(mangleClass(sootClass) + "_info_struct", createClassInfoStruct()); } mb.addGlobal(classInfoStruct); Function infoFn = FunctionBuilder.infoStruct(sootClass); infoFn.add(new Ret(new ConstantBitcast(classInfoStruct.ref(), I8_PTR_PTR))); mb.addFunction(infoFn); out.write(mb.build().toString().getBytes("UTF-8")); clazz.clearDependencies(); clazz.addDependency( "java/lang/Object"); // Make sure no class or interface has zero dependencies if (sootClass.hasSuperclass() && !sootClass.isInterface()) { clazz.addDependency(getInternalName(sootClass.getSuperclass())); } for (SootClass iface : sootClass.getInterfaces()) { clazz.addDependency(getInternalName(iface)); } for (SootField f : sootClass.getFields()) { addDependencyIfNeeded(clazz, f.getType()); } for (SootMethod m : sootClass.getMethods()) { addDependencyIfNeeded(clazz, m.getReturnType()); @SuppressWarnings("unchecked") List<soot.Type> paramTypes = (List<soot.Type>) m.getParameterTypes(); for (soot.Type type : paramTypes) { addDependencyIfNeeded(clazz, type); } } clazz.addDependencies(attributesEncoder.getDependencies()); clazz.addDependencies(trampolineDependencies); clazz.addDependencies(catches); for (Trampoline t : trampolines) { if (!(t instanceof LdcString)) { String desc = t.getTarget(); if (desc.charAt(0) == 'L' || desc.charAt(0) == '[') { // Target is a descriptor addDependencyIfNeeded(clazz, desc); } else { clazz.addDependency(t.getTarget()); } } if (t instanceof FieldAccessor) { addDependencyIfNeeded(clazz, ((FieldAccessor) t).getFieldDesc()); } else if (t instanceof Invoke) { String methodDesc = ((Invoke) t).getMethodDesc(); addDependencyIfNeeded(clazz, getReturnTypeDescriptor(methodDesc)); for (String desc : getParameterDescriptors(methodDesc)) { addDependencyIfNeeded(clazz, desc); } } } clazz.saveDependencies(); }
private void patchAsmWithFunctionSizes(Clazz clazz, File sFile) throws IOException { Set<String> functionNames = new HashSet<String>(); for (SootMethod method : clazz.getSootClass().getMethods()) { if (!method.isAbstract()) { String name = mangleMethod(method); if (config.getOs().getFamily() == OS.Family.darwin) { name = "_" + name; } functionNames.add(name); } } String localLabelPrefix = ".L"; String prefix = mangleClass(clazz.getInternalName()); if (config.getOs().getFamily() == OS.Family.darwin) { localLabelPrefix = "L"; prefix = "_" + prefix; } String infoStructLabel = prefix + "_info_struct"; Pattern methodImplPattern = Pattern.compile("\\s*\\.(?:quad|long)\\s+(" + Pattern.quote(prefix) + "[^\\s]+).*"); File outFile = new File(sFile.getParentFile(), sFile.getName() + ".tmp"); BufferedReader in = null; BufferedWriter out = null; try { in = new BufferedReader(new InputStreamReader(new FileInputStream(sFile), "UTF-8")); out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "UTF-8")); String line = null; String currentFunction = null; while ((line = in.readLine()) != null) { if (currentFunction == null) { out.write(line); out.write('\n'); if (line.startsWith(prefix)) { int colon = line.indexOf(':'); if (colon == -1) { continue; } String label = line.substring(0, colon); if (functionNames.contains(label)) { currentFunction = label; } else if (label.equals(infoStructLabel)) { break; } } } else if (line.trim().equals(".cfi_endproc") || line.trim().startsWith(".section") || line.trim().startsWith(".globl")) { out.write(localLabelPrefix); out.write(currentFunction); out.write("_end:\n\n"); currentFunction = null; out.write(line); out.write('\n'); } else { out.write(line); out.write('\n'); } } while ((line = in.readLine()) != null) { out.write(line); out.write('\n'); if (line.contains(prefix)) { Matcher matcher = methodImplPattern.matcher(line); if (matcher.matches()) { String functionName = matcher.group(1); if (functionNames.contains(functionName)) { line = in.readLine(); if (line.contains(String.valueOf(DUMMY_METHOD_SIZE))) { out.write("\t.long\t"); out.write(localLabelPrefix + functionName + "_end - " + functionName); out.write('\n'); } else { out.write(line); out.write('\n'); } } } } } } finally { IOUtils.closeQuietly(in); IOUtils.closeQuietly(out); } sFile.renameTo(new File(sFile.getParentFile(), sFile.getName() + ".orig")); outFile.renameTo(sFile); }