JDK-8159870 : java.lang.SecurityManager.getClassContext() does not return an array the same length as the of methods on the execution stack with lambdas
  • Type: Bug
  • Component: security-libs
  • Sub-Component: java.security
  • Affected Version: 8u60,9
  • Priority: P3
  • Status: Resolved
  • Resolution: Not an Issue
  • OS: generic
  • CPU: generic
  • Submitted: 2016-06-18
  • Updated: 2016-11-16
  • Resolved: 2016-11-16
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
Happens on all Java version 8u60 and later; I'm currently using:
openjdk version "1.8.0_91"
OpenJDK Runtime Environment (build 1.8.0_91-b14)
OpenJDK 64-Bit Server VM (build 25.91-b14, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Happens on all OS's, here's the one I'm currently using:
Linux candrews-l1 4.5.5-300.fc24.x86_64 #1 SMP Thu May 19 13:05:32 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

A DESCRIPTION OF THE PROBLEM :
The javadoc for java.lang.SecurityManager.getClassContext() states:
"The length of the array is the number of methods on the execution stack. The element at index 0 is the class of the currently executing method, the element at index 1 is the class of that method's caller, and so on." - https://docs.oracle.com/javase/8/docs/api/java/lang/SecurityManager.html#getClassContext--

However, java.lang.Throwable.getStackTrace() filters out generated lambda classes as of Java 8u60, see http://bugs.java.com/view_bug.do?bug_id=8025636 Since java.lang.SecurityManager.getClassContext() does not filter out generated lambda classes, the javadoc statement that getClassContext() matches the call stack does not hold true.

REGRESSION.  Last worked in version 8u73

ADDITIONAL REGRESSION INFORMATION: 
java.lang.SecurityManager.getClassContext() aligns with java.lang.Throwable.getStackTrace() when called within a lambda in any Java version before 8u60 as documented at http://bugs.java.com/view_bug.do?bug_id=8025636

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the test case I've provided in the answer to the "Source code for an executable test case" question.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The test case should output:
==========Testing without lambdas==========
Bug is NOT present
==========Testing with lambdas==========
Bug is NOT present
ACTUAL -
The test case actually outputs (on Java 8u60 and later):
==========Testing without lambdas==========
Bug is NOT present
==========Testing with lambdas==========
BUG IS PRESENT
Stacktrace length: 4
Class Context length: 6
Stacktrace:
Test.testStackTraceLengthMatchesClassContextLength(Test.java:24)
Test.lambda$0(Test.java:19)
java.lang.Thread.run(Thread.java:745)
Test.main(Test.java:20)
Class Context:
class Test$ClassContextSecurityManager
class Test
class Test
class Test$$Lambda$1/791452441
class java.lang.Thread
class Test


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.util.Arrays;

public class Test {

	private static class ClassContextSecurityManager extends SecurityManager {
		@Override
		public Class[] getClassContext() {
			return super.getClassContext();
		}
	}

	public static void main(String[] args) {
		// first, show the bug is not present when there aren't lambdas in the
		// call stack
		System.out.println("==========Testing without lambdas==========");
		testStackTraceLengthMatchesClassContextLength();
		new Thread(() -> {
			System.out.println("==========Testing with lambdas==========");
			testStackTraceLengthMatchesClassContextLength();
		}).run();
	}

	private static void testStackTraceLengthMatchesClassContextLength() {
		StackTraceElement[] stackTrace = new Throwable().getStackTrace();
		Class[] classContext = new ClassContextSecurityManager().getClassContext();
		// class context will always include ClassContextSecurityManager,
		// which is not in the stack trace, so the length of the class
		// context array should always be 1 more than the stack trace length
		if (stackTrace.length == classContext.length - 1) {
			System.out.println("Bug is NOT present");
		} else {
			System.out.println("BUG IS PRESENT");
			System.out.println("Stacktrace length: " + stackTrace.length);
			System.out.println("Class Context length: " + classContext.length);
			System.out.println("Stacktrace:");
			Arrays.stream(stackTrace).forEach(System.out::println);
			System.out.println("Class Context:");
			Arrays.stream(classContext).forEach(System.out::println);
		}
	}

}

---------- END SOURCE ----------


Comments
The specification for Throwable.getStackTrace clearly states that some stack elements may be omitted: "Some virtual machines may, under some circumstances, omit one or more stack frames from the stack trace." The specification for SecurityManager.getClassContext() states: "Returns the current execution stack as an array of classes. The length of the array is the number of methods on the execution stack. The element at index 0 is the class of the currently executing method, the element at index 1 is the class of that method's caller, and so on." This is very precise and does not say that it is guaranteed to match Throwable.getStackTrace.
16-11-2016

Attached test case executed on: JDK 8u51 -Pass JDK8u60 - Fail JDK 8u92 -Fail JDK 9ea b122-Fail Following is the output with JDK 8u92 and 9ea: ==========Testing without lambdas========== Bug is NOT present ==========Testing with lambdas========== BUG IS PRESENT Stacktrace length: 4 Class Context length: 6 Stacktrace: JI9040355.testStackTraceLengthMatchesClassContextLength(JI9040355.java:25) JI9040355.lambda$main$0(JI9040355.java:19) java.lang.Thread.run(java.base@9-ea/Thread.java:843) JI9040355.main(JI9040355.java:20) Class Context: class JI9040355$ClassContextSecurityManager class JI9040355 class JI9040355 class JI9040355$$Lambda$1/649734728 class java.lang.Thread class JI9040355
20-06-2016