Git Product home page Git Product logo

Comments (5)

hankem avatar hankem commented on June 26, 2024

Method calls and method references (and also lambda expressions) are very different in the byte code (invokevirtual/invokestatic vs. invokedynamic)ยน, and correspondingly in ArchUnit's domain model (JavaMethodCall vs. JavaMethodReference; see also #1221).

These different domain objects need to be addressed accordingly. There is a fluent API for both kinds of rules:

  • A rule using callMethodWhere

    @ArchTest
    ArchRule noMethodCall = noClasses().should().callMethodWhere(
            target(owner(assignableTo(Target.class)).and(nameMatching("method")))
    );

    explicitly forbids method calls (including those from lambda expressions).

  • If you additionally want to forbid method references, you can use accessTargetWhere:

    @ArchTest
    ArchRule noMethodAccess = noClasses().should().accessTargetWhere(
            // WRONG: owner(assignableTo(Target.class)).and(nameMatching("method"))  // see #1260
            target(owner(assignableTo(Target.class))).and(nameMatching("method"))  // Thanks to ben-Draeger for the fix! #issuecomment-1984051341
    );

ยน Example:

The following java code:
class Target {
    static void method(int i) {
        System.out.println("method called!");
    }

    void methodCall() {
        Target.method(1);
    }

    void methodCall() {
        Target.method(1);
    }

    void methodReference() {
        stream1().forEach(Target::method);
    }

    void lambda() {
        stream1().forEach(i -> Target.method(i));
    }

    static Stream<Integer> stream1() {
        return Stream.of(1);
    }
}
may be compiled to the following byte code:
class Target
{
  Target();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 4: 0

  static void method(int);
    descriptor: (I)V
    flags: ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String method called!
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 6: 0
        line 7: 8

  void methodCall();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: iconst_1
         1: invokestatic  #5                  // Method method:(I)V
         4: return
      LineNumberTable:
        line 10: 0
        line 11: 4

  void methodReference();
    descriptor: ()V
    flags:
    Code:
      stack=2, locals=1, args_size=1
         0: invokestatic  #6                  // Method stream:()Ljava/util/stream/Stream;
         3: invokedynamic #7,  0              // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
         8: invokeinterface #8,  2            // InterfaceMethod java/util/stream/Stream.forEach:(Ljava/util/function/Consumer;)V
        13: return
      LineNumberTable:
        line 14: 0
        line 15: 13

  void lambda();
    descriptor: ()V
    flags:
    Code:
      stack=2, locals=1, args_size=1
         0: invokestatic  #6                  // Method stream:()Ljava/util/stream/Stream;
         3: invokedynamic #9,  0              // InvokeDynamic #1:accept:()Ljava/util/function/Consumer;
         8: invokeinterface #8,  2            // InterfaceMethod java/util/stream/Stream.forEach:(Ljava/util/function/Consumer;)V
        13: return
      LineNumberTable:
        line 18: 0
        line 19: 13

  static java.util.stream.Stream<java.lang.Integer> stream();
    descriptor: ()Ljava/util/stream/Stream;
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: iconst_1
         1: invokestatic  #10                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         4: invokestatic  #11                 // InterfaceMethod java/util/stream/Stream.of:(Ljava/lang/Object;)Ljava/util/stream/Stream;
         7: areturn
      LineNumberTable:
        line 22: 0
    Signature: #27                          // ()Ljava/util/stream/Stream<Ljava/lang/Integer;>;

  private static void lambda$lambda$0(java.lang.Integer);
    descriptor: (Ljava/lang/Integer;)V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokevirtual #12                 // Method java/lang/Integer.intValue:()I
         4: invokestatic  #5                  // Method method:(I)V
         7: return
      LineNumberTable:
        line 18: 0
}

BootstrapMethods:
  0: #41 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #42 (Ljava/lang/Object;)V
      #43 invokestatic Target.method:(I)V
      #44 (Ljava/lang/Integer;)V
  1: #41 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #42 (Ljava/lang/Object;)V
      #48 invokestatic Target.lambda$lambda$0:(Ljava/lang/Integer;)V
      #44 (Ljava/lang/Integer;)V

from archunit.

ben-Draeger avatar ben-Draeger commented on June 26, 2024

Thanks for the clarification, accessTargetWhere() indeed works in both cases.

I will adapt our rules and that should fix the problem in our case.

However, please make sure that this is mentioned in the Documentation
to enable users to choose the API that is appropriate for their usecase.

In [1], both callMethodWhere() and accessTargetWhere() do not provide any JavaDoc.
Also in [2], callMethodWhere() is used 2 times without mentioning this restriction and
accessTargetWhere() is not mentioned at all.

References
[1] https://javadoc.io/doc/com.tngtech.archunit/archunit/latest/com/tngtech/archunit/lang/conditions/ArchConditions.html#callMethodWhere(com.tngtech.archunit.base.DescribedPredicate)
[2] https://www.archunit.org/userguide/html/000_Index.html

from archunit.

hankem avatar hankem commented on June 26, 2024

You're clearly right that the documentation can be improved. ๐Ÿ™ˆ
We also welcome pull requests to this open source project.

from archunit.

ben-Draeger avatar ben-Draeger commented on June 26, 2024

A note for the people that come after us:

when using accessTargetWhere(), it is better to do it this way:

@ArchTest
ArchRule noMethodAccess = noClasses().should().accessTargetWhere(
        target(owner(assignableTo(Target.class))).and(nameMatching("method"))
);

Otherwise, you might run into rather subtle issues.

from archunit.

hankem avatar hankem commented on June 26, 2024

Aaah... ๐Ÿคฏ I'm sorry for that!

from archunit.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.