Interpretacja Jenkinsa deklaracji wielu obiektów w jednym wierszu

9

To nie jest pytanie, ale raczej przestroga: starałem się zaoszczędzić trochę miejsca i zadeklarowałem swoje zmienne w deklaratywnym potoku Jenkinsa w następujący sposób:

int a, b, c

Następnie zainicjowałem je jako:

a = b = c = 0

W moim kodzie używam tych liczb całkowitych jako liczników w pętli for. Mój skrypt ciągle zawodził, niektóre z wyjątków:

java.lang.NullPointerException: Cannot invoke method next() on null object

i wiedziałem na pewno, że moja lista jest ważna, ponieważ została zakodowana na stałe. Zacząłem się zastanawiać, co się dzieje z tymi licznikami, a kiedy wywołałem na nich metodę getClass (), Jenkins z radością powiedział mi, że nie są to liczby całkowite, ale raczej

org.codehaus.groovy.runtime.NullObject

Po zmianie kodu na

int a = 0
int b = 0
int c = 0

wszystko działało jak urok. Chciałem tylko to udostępnić. Może pomoże komuś zaoszczędzić trochę frustracji.

Iskierka
źródło

Odpowiedzi:

12

Potoki Jenkins wykonują kod Groovy w stylu przekazywania kontynuacji za pomocą interpretera groovy-cps . To nie jest waniliowy Groovy, który można wykonać bezpośrednio w IDE lub Groovy Shell.

Groovy CPS przekształca kod w celu obsługi stylu przekazywania kontynuacji i poprawnego wyrażenia Groovy, takiego jak:

a = b = c = 0

przekształca się w coś, co wygląda bardziej jak:

eval(
  var("a"), 
  assign(
    eval(
      var("b"), 
      assign(
        eval(
          var("c"), 
          assign(0)
        )
      )
    )
  )
)

Problem z tym wyrażeniem w interpretatorze CPS polega na tym, że przypisanie nie zwraca żadnej wartości, a zatem nullwartość zostaje przypisana do zmiennej b, i to samo dzieje się ze zmienną a.

Jeśli chcesz kopać głębiej w bloku wywołań CPS, możesz sklonować projekt groovy-cps i napisać prosty przypadek testowy w com.cloudbees.groovy.cps.CpsTransformerTestklasie.

@Test
void testMultiVariablesInlineCPS() {
    def cps = parseCps('''
int a, b, c
a = b = c = 0
''')
    println cps
}

Następnie możesz ustawić punkt przerwania println cpsi uruchomić debugger. Po otwarciu okna inspekcji zobaczysz obraz podobny do tego:

wprowadź opis zdjęcia tutaj

Na marginesie należy pamiętać, że kompilator Groovy przekształca również przypisania do jednej linii podczas kompilowania kodu do kodu bajtowego. Jeśli skompilujesz prosty skrypt Groovy, taki jak:

int a, b, c
a = b = c = 0

println "$a $b $c"

a następnie otworzysz plik klasy w IDE, aby zdekompilować kod bajtowy do odpowiednika Java, zobaczysz coś takiego:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.GStringImpl;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class test extends Script {
    public test() {
        CallSite[] var1 = $getCallSiteArray();
    }

    public test(Binding context) {
        CallSite[] var2 = $getCallSiteArray();
        super(context);
    }

    public static void main(String... args) {
        CallSite[] var1 = $getCallSiteArray();
        var1[0].call(InvokerHelper.class, test.class, args);
    }

    public Object run() {
        CallSite[] var1 = $getCallSiteArray();
        int a = 0;
        int b = 0;
        int c = 0;
        byte var5 = 0;
        return var1[1].callCurrent(this, new GStringImpl(new Object[]{Integer.valueOf(var5), Integer.valueOf(var5), Integer.valueOf(var5)}, new String[]{"", " ", " ", ""}));
    }
}
Szymon Stepniak
źródło