boolean (ClassNode node, StructMethod mt, TextBuffer buffer, int indent, BytecodeMappingTracer tracer) { ClassWrapper wrapper = node.getWrapper(); StructClass cl = wrapper.getClassStruct(); MethodWrapper methodWrapper = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()); boolean hideMethod = false; int start_index_method = buffer.length(); MethodWrapper outerWrapper = (MethodWrapper)DecompilerContext.getProperty(DecompilerContext.CURRENT_METHOD_WRAPPER); DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, methodWrapper); try { boolean isInterface = cl.hasModifier(CodeConstants.ACC_INTERFACE); boolean isAnnotation = cl.hasModifier(CodeConstants.ACC_ANNOTATION); boolean isDeprecated = mt.hasAttribute(StructGeneralAttribute.ATTRIBUTE_DEPRECATED); boolean clInit = false, dInit = false; MethodDescriptor md = MethodDescriptor.parseDescriptor(mt.getDescriptor()); int flags = mt.getAccessFlags(); if ((flags & CodeConstants.ACC_NATIVE) != 0) { flags &= ~CodeConstants.ACC_STRICT; // compiler bug: a strictfp class sets all methods to strictfp } if (CodeConstants.CLINIT_NAME.equals(mt.getName())) { flags &= CodeConstants.ACC_STATIC; // ignore all modifiers except 'static' in a static initializer } if (isDeprecated) { appendDeprecation(buffer, indent); } if (interceptor != null) { String oldName = interceptor.getOldName(cl.qualifiedName + " " + mt.getName() + " " + mt.getDescriptor()); appendRenameComment(buffer, oldName, MType.METHOD, indent); } boolean isSynthetic = (flags & CodeConstants.ACC_SYNTHETIC) != 0 || mt.hasAttribute(StructGeneralAttribute.ATTRIBUTE_SYNTHETIC); boolean isBridge = (flags & CodeConstants.ACC_BRIDGE) != 0; if (isSynthetic) { appendComment(buffer, "synthetic method", indent); } if (isBridge) { appendComment(buffer, "bridge method", indent); } GenericMethodDescriptor descriptor = null; if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES)) { StructGenericSignatureAttribute attr = mt.getAttribute(StructGeneralAttribute.ATTRIBUTE_SIGNATURE); if (attr != null) { descriptor = GenericMain.parseMethodSignature(attr.getSignature()); if (descriptor != null) { long actualParams = md.params.length; List<VarVersionPair> mask = methodWrapper.synthParameters; if (mask != null) { actualParams = mask.stream().filter(Objects::isNull).count(); } if (actualParams != descriptor.parameterTypes.size()) { String message = "Inconsistent generic signature in method " + mt.getName() + " " + mt.getDescriptor() + " in " + cl.qualifiedName; DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN); descriptor = null; } } } } appendAnnotations(buffer, indent, mt); buffer.appendIndent(indent); appendModifiers(buffer, flags, METHOD_ALLOWED, isInterface, METHOD_EXCLUDED); if (isInterface && !mt.hasModifier(CodeConstants.ACC_STATIC) && !mt.hasModifier(CodeConstants.ACC_PRIVATE) && mt.containsCode()) { // 'default' modifier (Java 8) buffer.append("default "); } String name = mt.getName(); boolean init = false; if (CodeConstants.INIT_NAME.equals(name)) { if (node.type == ClassNode.CLASS_ANONYMOUS) { name = ""; dInit = true; } else { name = node.simpleName; init = true; } } else if (CodeConstants.CLINIT_NAME.equals(name)) { name = ""; clInit = true; } boolean throwsExceptions = false; int paramCount = 0; final List<TypeAnnotation> typeAnnotations = TypeAnnotation.listFrom(mt); if (!clInit && !dInit) { if (descriptor != null && !descriptor.typeParameters.isEmpty()) { appendTypeParameters(buffer, descriptor.typeParameters, descriptor.typeParameterBounds, typeAnnotations); buffer.append(' '); } final List<TypeAnnotation> emptyTypeAnnotations = TargetInfo.EmptyTarget.extract(typeAnnotations); if (init) { emptyTypeAnnotations.forEach(typeAnnotation -> typeAnnotation.writeTo(buffer)); } else { if (descriptor != null) { buffer.append(GenericMain.getGenericCastTypeName(descriptor.returnType, TypeAnnotationWriteHelper.create(emptyTypeAnnotations))); } else { buffer.append(ExprProcessor.getCastTypeName(md.ret, TypeAnnotationWriteHelper.create(emptyTypeAnnotations))); } buffer.append(' '); } buffer.append(toValidJavaIdentifier(name)); buffer.append('('); List<VarVersionPair> mask = methodWrapper.synthParameters; int lastVisibleParameterIndex = -1; for (int i = 0; i < md.params.length; i++) { if (mask == null || mask.get(i) == null) { lastVisibleParameterIndex = i; } } int index = methodWrapper.varproc.getFirstParameterVarIndex(); for (int i = methodWrapper.varproc.getFirstParameterPosition(); i < md.params.length; i++) { if (mask == null || mask.get(i) == null) { if (paramCount > 0) { buffer.append(", "); } Type paramType; if (descriptor != null) paramType = descriptor.parameterTypes.get(paramCount); else paramType = md.params[i]; appendParameterAnnotations(buffer, mt, paramType, paramCount); VarVersionPair pair = new VarVersionPair(index, 0); if (methodWrapper.varproc.isParameterFinal(pair) || methodWrapper.varproc.getVarFinal(pair) == VarProcessor.VAR_EXPLICIT_FINAL) { buffer.append("final "); } String typeName; boolean isVarArg = i == lastVisibleParameterIndex && mt.hasModifier(CodeConstants.ACC_VARARGS); List<TypeAnnotation> typeParamAnnotations = TargetInfo.FormalParameterTarget.extract(typeAnnotations, i); if (paramType instanceof GenericType genParamType) { isVarArg &= genParamType.getArrayDim() > 0; if (isVarArg) { genParamType = genParamType.decreaseArrayDim(); } typeName = GenericMain.getGenericCastTypeName(genParamType, TypeAnnotationWriteHelper.create(typeParamAnnotations)); } else { VarType varParamType = (VarType) paramType; isVarArg &= varParamType.getArrayDim() > 0; if (isVarArg) { varParamType = varParamType.decreaseArrayDim(); } typeName = ExprProcessor.getCastTypeName(varParamType, TypeAnnotationWriteHelper.create(typeParamAnnotations)); } if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeName) && DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) { typeName = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT, TypeAnnotationWriteHelper.create(typeParamAnnotations)); } buffer.append(typeName); if (isVarArg) { buffer.append("..."); } buffer.append(' '); String parameterName = methodWrapper.varproc.getVarName(pair); buffer.append(parameterName == null ? "param" + index : parameterName); // null iff decompiled with errors paramCount++; } index += md.params[i].getStackSize(); } buffer.append(')'); StructExceptionsAttribute attr = mt.getAttribute(StructGeneralAttribute.ATTRIBUTE_EXCEPTIONS); if ((descriptor != null && !descriptor.exceptionTypes.isEmpty()) || attr != null) { throwsExceptions = true; buffer.append(" throws "); for (int i = 0; i < attr.getThrowsExceptions().size(); i++) { if (i > 0) { buffer.append(", "); } TargetInfo.ThrowsTarget.extract(typeAnnotations, i).forEach(typeAnnotation -> typeAnnotation.writeTo(buffer)); if (descriptor != null && !descriptor.exceptionTypes.isEmpty()) { GenericType type = descriptor.exceptionTypes.get(i); buffer.append(GenericMain.getGenericCastTypeName(type, Collections.emptyList())); } else { VarType type = new VarType(attr.getExcClassname(i, cl.getPool()), true); buffer.append(ExprProcessor.getCastTypeName(type, Collections.emptyList())); } } } } tracer.incrementCurrentSourceLine(buffer.countLines(start_index_method)); if ((flags & (CodeConstants.ACC_ABSTRACT | CodeConstants.ACC_NATIVE)) != 0) { // native or abstract method (explicit or interface) if (isAnnotation) { StructAnnDefaultAttribute attr = mt.getAttribute(StructGeneralAttribute.ATTRIBUTE_ANNOTATION_DEFAULT); if (attr != null) { buffer.append(" default "); buffer.append(attr.getDefaultValue().toJava(0, BytecodeMappingTracer.DUMMY)); } } buffer.append(';'); buffer.appendLineSeparator(); } else { if (!clInit && !dInit) { buffer.append(' '); } // We do not have line information for method start, lets have it here for now buffer.append('{').appendLineSeparator(); tracer.incrementCurrentSourceLine(); RootStatement root = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()).root; if (root != null && !methodWrapper.decompiledWithErrors) { // check for existence try { // to restore in case of an exception BytecodeMappingTracer codeTracer = new BytecodeMappingTracer(tracer.getCurrentSourceLine()); TextBuffer code = root.toJava(indent + 1, codeTracer); hideMethod = code.length() == 0 && (clInit || dInit || hideConstructor(node, !typeAnnotations.isEmpty(), init, throwsExceptions, paramCount, flags)) || isSyntheticRecordMethod(cl, mt, code); buffer.append(code); tracer.setCurrentSourceLine(codeTracer.getCurrentSourceLine()); tracer.addTracer(codeTracer); } catch (Throwable t) { String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " couldn't be written."; DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN, t); methodWrapper.decompiledWithErrors = true; } } if (methodWrapper.decompiledWithErrors) { buffer.appendIndent(indent + 1); buffer.append("// $FF: Couldn't be decompiled"); buffer.appendLineSeparator(); tracer.incrementCurrentSourceLine(); } else if (root != null) { tracer.addMapping(root.getDummyExit().bytecode); } buffer.appendIndent(indent).append('}').appendLineSeparator(); } tracer.incrementCurrentSourceLine(); } finally { DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, outerWrapper); } // save total lines // TODO: optimize //tracer.setCurrentSourceLine(buffer.countLines(start_index_method)); return !hideMethod; }