260 likes | 369 Vues
Explore practical additions to the Java type system involving non-null types and readonly types through JavaCOP implementation, new rules, and constraints for AST nodes.
E N D
Chris Andreae James Noble Victoria Universityof Wellington Shane Arkus Todd Millstein University of California Practical Pluggable types for Java Presented by Lina Zarivach
Existing type systems are not sufficient • Non-null types • Declaring and Checking Non-null Types by M. Fahndrich, OOPSLA’03 • Non-Null Safety in Eiffel by B. Meyer, ECOOP’05 • Readonly types • Javari by M. Tschantz, OOPSLA’05 • Other type systems • Confined Types by J. Vitek, OOPSLA’99 • …
Implementation • Ad-hoc: New keywords and compiler reimplementation. • Pluggable type systems: • Idea by Gilad Bracha, ’04. • Implementation by Andreae, Nobel et.al. ’06: JavaCOP • Define new types with Java Annotations • Express new type requirements via JavaCOPRule Language
NonNull types example –Pseudo-Declarative syntax & semantics class Person { @NonNull String firstName = "Chris"; void setFirstName(String newName) { firstName = newName; } } • A JavaCOP rule to discover this case: rule checkNonNull(Assign a) { where(nonnull(a.lhs)) { require(defNonNull(a.rhs)): error(a,“Assigning possible null to @NonNull variable”); } } newName may be NULL!!!
JavaCOP Architecture javac compiler JavaCOP rules AST of the program Java program with new types
JavaCOP Rule Language Types • A subset of data types generated in the javac compiler. In particular: • AST nodes types • Type type • Symbol type • String and generic List types • Env • Globals
AST Node types Field or method selection, i.e., o.field or o.method(); Instance creation, like new World(“Hello”); Method call, like meth(args);
Symbols and Types Symbol Table … …
Rules for AST nodes • rule aTreeRule(Tree arg) { <sequence of constraints> } rule finalClass(ClassDef c){ require(!c.supertype().getSym().hasAnnotation("Final")); } • Only one argument of type Tree or subclasses of Tree. • The rule will be applied to each node of the specified type. Huge API
Primitive Constraints • require(<condition>); rule UnconditionalIf (If i){ require(i.cond.getSymbol != globals.trueConst ): warning(i.cond ,"The body of this if will unconditionally be executed"); require(i.cond.getSymbol != globals.falseConst ): warning(i.cond ,"The body of this if will never be executed"); } Instance of Globals
Conditional Constraints • where(<condition>) {<sequence of constraints>} rule MicroPatternsExample (ClassDef c){ where(c.isInterface && c.getSym.members.length == 0){ require(c.interfaces.length != 1): warning(globals.NOPOS , "Taxonomy:"+c.flatName ); } } Primitive constraint
Pattern matching • where(<vardefs>; tree => [match using vars]) {<body>} rule checkNonNullFieldAccess(Assign a){ where(nonnull(a.lhs)) { where(String n; a.rhs => [%.n]){ require(n.equals("class")): error(a, "Assigning possibly null field access " + " to @NonNull variable"); }}} % is a wildcard for Tree.
Universal quantifier • Similar to the “enhanced for” in Java 1.5. rule finalMethod(MethodDef m){ forall(Type st: m.enclClass.transitiveSupertypes){ forall(Symbol other: st.getSymbol.memberLookup(m.name)){ where(other.isFinal){ require(!m.overrides(other, st.getSymbol)) :error(m, "You may not override final method "+other ); } } } }
Existential quantifier • Implicit depth-first traversal from a given AST node. • Only nodes that match the declared type of the quantifier variable are considered. rule allLocalVarsMustBeAssigned (MethodDef m){ forall(VarDef v : m.varDefs()){ where(v.init == null){ exists(Assign a : m){ require(a.lhs.getSymbol == v.getSymbol ); }: error(v,"The variable " + v + "is never assigned"); }}}
Predicates (package several conditions into boolean function) declare <predicate name>(<vardef> [, <vardef>]) { <list of constraints> } • Invoked by the bodies of rules and other predicates. declarenonnull(Tree t){ require(t.holdsSymbol && t.getSymbol.hasAnnotation("NonNull")); }
Predicates - achieving disjunctive constraints • Predicate can have multiple definitions. • An invocation of the predicate succeeds if at least one of the definitions’ bodies is satisfied. declare defNotNull(Tree t){ require(nonnull(t)); } declare defNotNull(Tree t){ require(t.type.isPrimitive()); } declare defNotNull(NewClass t){ require(true); } t is non-null if it has @NonNull annotation. Primitives can’t be null. New instance can’t be null.
Error reporting • :<errortype>(<position>,<message>) • <errortype> is either error or warning. • When a constraint fails to be satisfied, JavaCOP searches for the nearest enclosing failure clause and executes it. require(a){ require(b); require(c): error(pos1 , "error - c has failed"); }: warning(globals.NOPOS , "warning - a or b have failed")
Rules for custs – syntactic sugar • rule aCustRule(origType <: custedType @ Tree) {<sequence of constraints>} • Applied to every Tree node that performs explicit or implicit cust rule checkNonNullCust (a <: b @ e){ where(!nonnull(a)){ require(!nonnull(b)): error(e, “A possibly null type "+a +" may not be cast to Non-Null type "+b); } }
Applications • A checker for non-null types. • A checker for Confined Types. • Rules supplied by PMD Java Checker. • Rules to identify some micro-patterns. • Rules that gather information for software metrics. • An EJB3.0 verifier.
NonNull assignment rule checkNonNullAssignment (Assign a){ where(nonnull(a.lhs )){ require(defNotNull(a.rhs)): error(a,"Assigning possibly null value to @NonNull” "+"variable."); } } if(x != null){ @NonNull Object nonnull_x = x; ...} The above rule will issue an error for this case!!!
NonNull assignment –flow-sensitive check rule checkNonNull (Assign a){ where(nonnull(a.lhs )){ require(defNotNull(a.rhs) || safeNullableAssign (a)): error(a,"Assigning possibly null value to @NonNull "+"variable."); }} declare safeNullableAssign (Assign a){ require(localVariable (a.rhs )); require(safeNullableAssign (env.tree , env.next , a)); } declare safeNullableAssign (Block b, Env e, Assign a){ require(safeNullableAssign (e.tree , e.next , a)); } declare safeNullableAssign (If i, Env e, Assign a){ require(nonNullTest(i.cond , a.rhs)&& firstExpression (i.thenpart , a)); } Env represents traversal information
Confined types • Confined type is a type whose instances may not be referenced or accessed from outside a certain protection domain. • Confined types protect objects from use by untrusted code.
Confined types – example 1 • A confined type must not be declared public and must not belong to the unnamed global package. rule ConfinedExample1 (ClassDef c){ where(confined(c)){ require(!c.isPublic ()): error(c, "Confined class may not be public"); require(c.packge() != globals.emptyPackage): error(c, "Confined class may not be in the default package"); } }
Confined Types – example 2 • Subtypes of a confined type must be confined and belong to the same package as their supertype. rule ConfinedExample2 (ClassDef c){ where(confined(c.supertype)){ require(confined(c)): error(c, "An unconfined class may not extend " + "a confined superclass"); require(c.packge == c.supertype.packge): error(c, “A confined subclass must belong to the same package as its superclass”); } } }
JavaCOP Architecture - details Performance: ~0.02 sec/class
Conclusions • The first practical framework for pluggable types. • Contributions: • Design of JavaCOP Rule Language • Implementation of JavaCOP • Validation on various kinds of application • Future extensions: • More support for flow sensitivity • Error clause, that count the number of times a constraint failed. • Increasing the efficiency of checking the constraints.