The Java Language Specification (section 13.1) states "Any constructs introduced by the compiler that do not have a corresponding construct in the source code must be marked as synthetic, except for default constructors and the class initialization method." Further clues as to the meaning of synthetic in Java can be found in the Javadoc documentation for Member.isSynthetic(). That method's documentation states that it returns "true if and only if this member was introduced by the compiler." I like that very short definition of "synthetic": a Java construct introduced by the compiler.
The Java compiler must create synthetic methods on nested classes when their attributes specified with the private modifier are accessed by the enclosing class. The next code sample indicates this situation.
DemonstrateSyntheticMethods.java (Enclosing Class Invokes One Nested Class Private Attribute)
package dustin.examples; import java.util.Calendar; import static java.lang.System.out; public final class DemonstrateSyntheticMethods { public static void main(final String[] arguments) { DemonstrateSyntheticMethods.NestedClass nested = new DemonstrateSyntheticMethods.NestedClass(); out.println("String: " + nested.highlyConfidential); } private static final class NestedClass { private String highlyConfidential = "Don't tell anyone about me"; private int highlyConfidentialInt = 42; private Calendar highlyConfidentialCalendar = Calendar.getInstance(); private boolean highlyConfidentialBoolean = true; } }
The above code compiles without incident. When javap is run against the compiled
.class
file, the output is as shown in the following screen snapshot.As the above screen snapshot indicates, a synthetic method with the name
access$100
has been created on the nested class NestedClass
to provide its private String to the enclosing class. Note that the synthetic method is only added for the single private attribute of the NestedClass that the enclosing class accesses. If I change the enclosing class to access all private attributes of NestedClass, additional synthetic methods will be generated. The next code example demonstrates doing just this and the screen snapshot following it proves that four synthetic methods are generated in that case.DemonstrateSyntheticMethods.java (Enclosing Class Invokes Four Nested Class Private Attributes)
package dustin.examples; import java.util.Calendar; import static java.lang.System.out; public final class DemonstrateSyntheticMethods { public static void main(final String[] arguments) { DemonstrateSyntheticMethods.NestedClass nested = new DemonstrateSyntheticMethods.NestedClass(); out.println("String: " + nested.highlyConfidential); out.println("Int: " + nested.highlyConfidentialInt); out.println("Calendar: " + nested.highlyConfidentialCalendar); out.println("Boolean: " + nested.highlyConfidentialBoolean); } private static final class NestedClass { private String highlyConfidential = "Don't tell anyone about me"; private int highlyConfidentialInt = 42; private Calendar highlyConfidentialCalendar = Calendar.getInstance(); private boolean highlyConfidentialBoolean = true; } }
As the previous two code snippets above and the associated images show, the Java compiler introduces synthetic methods on an as-needed basis. When only one of the nested class's private attributes was accessed by the enclosing class, only one synthetic method (
access$100
) was created by the compiler. However, when all four private attributes of the nested class were accessed by the enclosing class, four corresponding synthetic methods were generated by the compiler (access$100
, access$200
, access$300
, and access$400
).In all cases of an enclosing class accessing its nested class's private data, a synthetic method was created to allow that access to happen. What happens when the nested class provides an accessor for its private data that the enclosing class can use? That is demonstrated in the next code listing and in its output as shown in the next screen snapshot.
DemonstrateSyntheticMethods.java with Nested Class Public Accessor for Private Data
package dustin.examples; import java.util.Calendar; import java.util.Date; import static java.lang.System.out; public final class DemonstrateSyntheticMethods { public static void main(final String[] arguments) { DemonstrateSyntheticMethods.NestedClass nested = new DemonstrateSyntheticMethods.NestedClass(); out.println("String: " + nested.highlyConfidential); out.println("Int: " + nested.highlyConfidentialInt); out.println("Calendar: " + nested.highlyConfidentialCalendar); out.println("Boolean: " + nested.highlyConfidentialBoolean); out.println("Date: " + nested.getDate()); } private static final class NestedClass { private String highlyConfidential = "Don't tell anyone about me"; private int highlyConfidentialInt = 42; private Calendar highlyConfidentialCalendar = Calendar.getInstance(); private boolean highlyConfidentialBoolean = true; private Date date = new Date(); public Date getDate() { return this.date; } } }
The above screen snapshot demonstrates that the compiler did not need to generate a synthetic method for accessing the private Date attribute in the nested class because the enclosing class accessed that attribute via the provided
getDate()
method. Even with getDate()
provided, the compiler would have generated a synthetic method for accessing the date
has the enclosing code been written to access the date
attribute directly (as a property) rather than via the accessor method.The last screen snapshot brings up another observation. As the newly added
getDate()
method shows in that screen snapshot, modifiers such as public
are included in javap output. Because no modifier is shown for the synthetic methods created by the compiler, we know that they are package level (or package-private). In short, the compiler has created package-private methods for accessing private attributes.The Java reflection APIs provide another approach for determining synthetic methods. The next code listing is for a Groovy script that will use the Java reflection APIs to conveniently provide details regarding the methods of the nested class shown above.
reflectOnMethods.groovy
#!/usr/bin/env groovy import java.lang.reflect.Method import java.lang.reflect.Modifier if (args == null || args.size() < 2) { println "Outer and nested class names must be provided." println "\nUsage #1: reflectOnMethods qualifiedOuterClassName nestedClassName\n" println "\nUsage #2: groovy -cp classpath reflectOnMethods.groovy qualifiedOuterClassName nestedClassName\n" println "\t1. Include outer and nested classes on classpath if necessary" println "\t2. Do NOT include \$ on front of nested class name.\n" System.exit(-1) } def enclosingClassName = args[0] def nestedClassName = args[1] def fullNestedClassName = enclosingClassName + '$' + nestedClassName def enclosingClass = Class.forName(enclosingClassName) Class nestedClass = null enclosingClass.declaredClasses.each { if (!nestedClass && fullNestedClassName.equals(it.name)) { nestedClass = it } } if (nestedClass == null) { println "Unable to find nested class ${fullNestedClassName}" System.exit(-2) } // Use declaredMethods because don't care about inherited methods nestedClass.declaredMethods.each { print "\nMethod '${it.name}' " print "is ${getScopeModifier(it)} scope, " print "${it.synthetic ? 'is synthetic' : 'is NOT synthetic'}, and " println "${it.bridge ? 'is bridge' : 'is NOT bridge'}." } def String getScopeModifier(Method method) { def modifiers = method.modifiers def isPrivate = Modifier.isPrivate(modifiers) def isPublic = Modifier.isPublic(modifiers) def isProtected = Modifier.isProtected(modifiers) String scopeString = "package-private" // default if (isPublic) { scopeString = "public" } else if (isProtected) { scopeString = "protected" } else if (isPrivate) { scopeString = "private" } return scopeString }
When the above Groovy script is executed against the class and nested class shown above, the output is that shown in the next screen snapshot.
The results of the Groovy script shown in the previous image verify what javap had already told us: there are four synthetic methods and one non-synthetic method defined on the nested class
NestedClass
. The script also tells us that the compiler-generated synthetic methods are package-private scope.The addition of synthetic methods to the nested class at package-private scope level is not the only thing the compiler did in the above example. It also changed the scope of the nested class itself from the private setting in code to package-private in the
.class
file. Indeed, while the synthetic methods were only added in the case where the enclosing class accessed the private attribute, the compiler always makes the nested class package-private even if it's specified as private in the code. The good news is that this is a resulting artifact of the compilation process, meaning that code cannot be compiled as-is against the changed scope level of the nested class or its synthetic methods. Runtime is where things can get dicey.The class, Rogue, attempts to access some of the NestedClass synthetic methods. Its source code is shown next, followed by the compiler error seen when trying to compile this Rogue source code.
Rogue.java trying to access synthetic methods at compile time
package dustin.examples; import static java.lang.System.out; public class Rogue { public static void main(final String[] arguments) { out.println(DemonstrateSyntheticMethods.NestedClass.getDate()); } }
The above code will not compile, even for the non-synthetic method
getDate()
, and reports this error:Buildfile: C:\java\examples\synthetic\build.xml -init: compile: [javac] Compiling 1 source file to C:\java\examples\synthetic\classes [javac] C:\java\examples\synthetic\src\dustin\examples\Rogue.java:9: dustin.examples.DemonstrateSyntheticMethods.NestedClass has private access in dustin.examples.DemonstrateSyntheticMethods [javac] out.println(DemonstrateSyntheticMethods.NestedClass.getDate()); [javac] ^ [javac] 1 error BUILD FAILED C:\java\examples\synthetic\build.xml:29: Compile failed; see the compiler error output for details. Total time: 1 second
As the above compilation error message indicates, even the non-synthetic method on the nested class is inaccessible at compile time because the nested class has private scope. In his article Java Insecurities: Accounting for Subtleties That Can Compromise Code, Charlie Lai discusses potential situations in which these compiler-introduced changes are security vulnerabilities. Faisal Feroz goes further and states, in the post How to Write Secure Java Code, "Don’t use Inner Classes" (see Nested, Inner, Member, and Top-Level Classes for details on inner classes as a subset of nested classes).
Many of us can go for a long time in Java development without needing significant understanding of synthetic methods. However, there are situations when awareness of these is important. Besides security issues related to these, it is also to be aware of what they are when reading stack traces. Method names such as
access$100
, access$200
, access$300
, access$400
, access$500
, access$600
, and access$1000
in the stack trace reflect synthetic methods generated by the compiler.
1 comment:
A recently published related post is Java Secret: Generated Methods.
Post a Comment