Daniel Sun created GROOVY-12445:
-----------------------------------

             Summary: STC: refuse unknown SignatureCodec versions without 
reading payload bytes
                 Key: GROOVY-12445
                 URL: https://issues.apache.org/jira/browse/GROOVY-12445
             Project: Groovy
          Issue Type: Improvement
            Reporter: Daniel Sun


h2. Context

{{@TypeChecked}} stores an inferred method return type on bytecode as 
{{@TypeChecked.TypeCheckingInfo}}. The annotation has two members:

* {{version}} — an {{int}}, default {{0}}
* {{inferredType}} — a Base64 string

The protocol version is that {{version}} member. The payload string has no 
version byte, magic number, or extra length prefix. This release implements 
version {{1}} only. {{SignatureCodecFactory.getCodec(int)}} returns 
{{SignatureCodecVersion1}} for {{1}}, and {{null}} for every other {{int}}: the 
annotation default {{0}}, any negative value, and {{2}} or higher.

h2. Problem

The {{SignatureCodec}} Javadoc said the encoded string itself must carry a 
version so later Groovy releases can decode older payloads. That puts the 
version in the wrong place and invites a reader to parse bytes whose layout it 
does not implement.

On the write path, {{addTypeCheckingInfoAnnotation}} called 
{{codec.encode(...)}} with no null check. A {{null}} codec would throw 
{{NullPointerException}}.

On the read path there was no helper that treated a {{null}} codec as "there is 
no inferred signature". Feeding an unknown version to the version-1 codec, or 
decoding anyway, would throw or invent a type from bytes that belong to another 
layout. Two experimental compilers may use the same unpublished version {{int}} 
for different layouts. A release compiler refuses those payloads the same way 
it refuses a higher version it does not implement.

h2. Contract

A {{null}} codec means there is no inferred signature. The caller uses the 
method's declared return type. The payload string is left unread.

||Version||getCodec||Read path||
|{{1}}|{{SignatureCodecVersion1}}|decode {{inferredType}}|
|{{0}}, negative, or {{2}} and higher|{{null}}|declared return type; payload 
unread|

A version {{int}} that has shipped in a release is append-only: one {{int}} 
keeps one layout. A later codec that claims an older version still decodes that 
older version. The version stays a single {{int}} on the annotation.

{{getCodec}} continues to return {{null}} for an unimplemented version. The 
write path returns before {{encode}} when the codec is {{null}}.

{code:java}
static ClassNode inferredReturnOrDeclared(int version, String signature,
        ClassLoader loader, ClassNode declared) {
    SignatureCodec codec = getCodec(version, loader);
    if (codec == null) {
        return declared;
    }
    return codec.decode(signature);
}
{code}


{{addTypeCheckingInfoAnnotation}} currently has no production callers 
(disconnected under GROOVY-6401). There is also no production reader of 
{{@TypeCheckingInfo}} today. Locking the unknown-version rule now means a later 
reader uses the declared return type for any version this compiler does not 
implement.

h2. Touch points

* {{org.codehaus.groovy.transform.stc.SignatureCodec}} — protocol Javadoc
* {{StaticTypeCheckingVisitor.addTypeCheckingInfoAnnotation}} — return when the 
codec is {{null}}
* {{SignatureCodecFactory.inferredReturnOrDeclared}} — decode helper
* 
{{src/test/groovy/org/codehaus/groovy/transform/stc/SignatureCodecTest.groovy}}

h2. Scope

This issue covers the unknown-version rule only. Union payload layout, rewiring 
{{addTypeCheckingInfoAnnotation}} onto {{startMethodInference}}, and a new 
compiler diagnostic are separate work.






--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to