Summary
Reject class files that perform if_acmpeq, if_acmpne, ifnull, or ifnonnull on an uninitialized type.
Problem
An uninitialized type represents an object that has been created with new but not yet initialized with an invokespecial call to an <init> method.
Historically, in version 50+ class files, it has been allowed to test whether two uninitialized objects are equal. The answer is almost* always obvious: if it's the same uninitialized type, then they are equal; otherwise, they are not. In older class files, the operation is rejected (that is, this behavior is specific to "verification by type checking").
(*There is one corner case: some complex control flow could end up producing two different objects with the same new instruction, and then make it statically impossible to tell whether two matching unitialized(nn) types are equal. This is very unlikely to come up in practice.)
It has also been historically possible, in this case for all class file versions, to test whether an uninitialized object is null. Yet, per the typing rules, it cannot be null.
In general, the intent of the uninitialized types is to protect a freshly-allocated object from general usage until it has had a chance to be initialized. The set of supported operations is meant to be minimal. In that spirit, it is unhelpful to support the acmp and ifnull operations.
In the future, uninitialized value objects would expose hidden internals if they were required to support acmp operations before they were initialized. This would not be tolerable. (And note that bytecode in old class files may end up operating on uninitialized value objects, so this is not a fix that can be targeted to a specific class file version.)
Solution
We propose removing these degrees of freedom to simplify the set of operations that need to be supported by an uninitialized object.
A verification error will occur if an existing class file attempts to perform if_acmpeq, if_acmpne, ifnull, or ifnonnull on an uninitialized type, regardless of version number. This can almost always be addressed by simply replacing the instruction with a nop or goto.
(We considered a similar change for monitorenter, but decided against it, because in that case there is a practical application: locking on an object before untrusted code can share it with another thread.)
Specification
See JVMS issue JDK-8376519.
Modified if_acmpeq rule:
An if_acmpeq instruction is type safe iff one can validly pop types matching ~~reference and reference~~ Object and Object on the incoming operand stack yielding the outgoing type state NextStackFrame, and the operand of the instruction, Target, is a valid branch target assuming an incoming type state of NextStackFrame.
instructionIsTypeSafe(if_acmpeq(Target), Environment, _Offset, StackFrame, NextStackFrame, ExceptionStackFrame) :- ~~canPop(StackFrame, [reference, reference], NextStackFrame),~~ **isBootstrapLoader(BL),** **objectType = class('java/lang/Object', BL),** **canPop(StackFrame, [objectType, objectType], NextStackFrame),** targetIsTypeSafe(Environment, NextStackFrame, Target), exceptionStackFrame(StackFrame, ExceptionStackFrame).
Modified ifnonnull rule:
An ifnonnull instruction is type safe iff one can validly pop a type matching ~~reference~~ Object off the incoming operand stack yielding the outgoing type state NextStackFrame, and the operand of the instruction, Target, is a valid branch target assuming an incoming type state of NextStackFrame.
instructionIsTypeSafe(ifnonnull(Target), Environment, _Offset, StackFrame, NextStackFrame, ExceptionStackFrame) :- ~~canPop(StackFrame, [reference], NextStackFrame),~~ **isBootstrapLoader(BL),** **canPop(StackFrame, [class('java/lang/Object', BL)], NextStackFrame),** targetIsTypeSafe(Environment, NextStackFrame, Target), exceptionStackFrame(StackFrame, ExceptionStackFrame).
- csr of
-
JDK-8376521 Verifier: disallow acmp & ifnull on 'uninitialized' types
-
- Open
-