Detect the static usage of covariance in the code.

The module works by refining various methods of the modelize and semantize phases to intercept type tests, then discriminate and count the cases.

At the end, the statistics are displayed on the screen.

Introduced classes

Redefined classes

redef abstract class MType

nitc :: detect_covariance $ MType

A global static type
redef class ModelBuilder

nitc :: detect_covariance $ ModelBuilder

A model builder knows how to load nit source files and build the associated model
redef class ToolContext

nitc :: detect_covariance $ ToolContext

Global context for tools

All class definitions

redef abstract class MType

nitc :: detect_covariance $ MType

A global static type
redef class ModelBuilder

nitc :: detect_covariance $ ModelBuilder

A model builder knows how to load nit source files and build the associated model
redef class ToolContext

nitc :: detect_covariance $ ToolContext

Global context for tools
package_diagram nitc::detect_covariance detect_covariance nitc::metrics_base metrics_base nitc::detect_covariance->nitc::metrics_base nitc::typing typing nitc::detect_covariance->nitc::typing nitc::modelbuilder modelbuilder nitc::metrics_base->nitc::modelbuilder csv csv nitc::metrics_base->csv counter counter nitc::metrics_base->counter nitc\>modelize\> modelize nitc::typing->nitc\>modelize\> nitc::local_var_init local_var_init nitc::typing->nitc::local_var_init ...nitc::modelbuilder ... ...nitc::modelbuilder->nitc::modelbuilder ...csv ... ...csv->csv ...counter ... ...counter->counter ...nitc\>modelize\> ... ...nitc\>modelize\>->nitc\>modelize\> ...nitc::local_var_init ... ...nitc::local_var_init->nitc::local_var_init nitc::metrics metrics nitc::metrics->nitc::detect_covariance nitc::nitmetrics nitmetrics nitc::nitmetrics->nitc::metrics nitc::api_metrics api_metrics nitc::api_metrics->nitc::metrics nitc::nitmetrics... ... nitc::nitmetrics...->nitc::nitmetrics nitc::api_metrics... ... nitc::api_metrics...->nitc::api_metrics

Ancestors

module abstract_collection

core :: abstract_collection

Abstract collection classes and services.
module abstract_text

core :: abstract_text

Abstract class for manipulation of sequences of characters
module annotation

nitc :: annotation

Management and utilities on annotations
module array

core :: array

This module introduces the standard array structure.
module bitset

core :: bitset

Services to handle BitSet
module bytes

core :: bytes

Services for byte streams and arrays
module caching

serialization :: caching

Services for caching serialization engines
module circular_array

core :: circular_array

Efficient data structure to access both end of the sequence.
module codec_base

core :: codec_base

Base for codecs to use with streams
module codecs

core :: codecs

Group module for all codec-related manipulations
module collection

core :: collection

This module define several collection classes.
module console

console :: console

Defines some ANSI Terminal Control Escape Sequences.
module core

core :: core

Standard classes and methods used by default by Nit programs and libraries.
module counter

counter :: counter

Simple numerical statistical analysis and presentation
module csv

csv :: csv

CSV document handling.
module digraph

graph :: digraph

Implementation of directed graphs, also called digraphs.
module engine_tools

serialization :: engine_tools

Advanced services for serialization engines
module environ

core :: environ

Access to the environment variables of the process
module error

core :: error

Standard error-management infrastructure.
module exec

core :: exec

Invocation and management of operating system sub-processes.
module file

core :: file

File manipulations (create, read, write, etc.)
module fixed_ints

core :: fixed_ints

Basic integers of fixed-precision
module fixed_ints_text

core :: fixed_ints_text

Text services to complement fixed_ints
module flat

core :: flat

All the array-based text representations
module flow

nitc :: flow

Intraprocedural static flow.
module gc

core :: gc

Access to the Nit internal garbage collection mechanism
module hash_collection

core :: hash_collection

Introduce HashMap and HashSet.
module ini

ini :: ini

Read and write INI configuration files
module inspect

serialization :: inspect

Refine Serializable::inspect to show more useful information
module iso8859_1

core :: iso8859_1

Codec for ISO8859-1 I/O
module kernel

core :: kernel

Most basic classes and methods.
module lexer

nitc :: lexer

Lexer and its tokens.
module lexer_work

nitc :: lexer_work

Internal algorithm and data structures for the Nit lexer
module list

core :: list

This module handle double linked lists
module literal

nitc :: literal

Parsing of literal values in the abstract syntax tree.
module loader

nitc :: loader

Loading of Nit source files
module local_var_init

nitc :: local_var_init

Verify that local variables are initialized before their usage
module location

nitc :: location

Nit source-file and locations in source-file
module math

core :: math

Mathematical operations
module mdoc

nitc :: mdoc

Documentation of model entities
module meta

meta :: meta

Simple user-defined meta-level to manipulate types of instances as object.
module mmodule

nitc :: mmodule

modules and module hierarchies in the metamodel
module mmodule_data

nitc :: mmodule_data

Define and retrieve data in modules
module model

nitc :: model

Classes, types and properties
module model_base

nitc :: model_base

The abstract concept of model and related common things
module modelbuilder_base

nitc :: modelbuilder_base

Load nit source files and build the associated model
module modelize

nitc :: modelize

Create a model from nit source files
module modelize_class

nitc :: modelize_class

Analysis and verification of class definitions to instantiate model element
module modelize_property

nitc :: modelize_property

Analysis and verification of property definitions to instantiate model element
module more_collections

more_collections :: more_collections

Highly specific, but useful, collections-related classes.
module mpackage

nitc :: mpackage

Modelisation of a Nit package
module native

core :: native

Native structures for text and bytes
module nitpm_shared

nitc :: nitpm_shared

Services related to the Nit package manager
module numeric

core :: numeric

Advanced services for Numeric types
module opts

opts :: opts

Management of options on the command line
module ordered_tree

ordered_tree :: ordered_tree

Manipulation and presentation of ordered trees.
module parser

nitc :: parser

Parser.
module parser_nodes

nitc :: parser_nodes

AST nodes of the Nit language
module parser_prod

nitc :: parser_prod

Production AST nodes full definition.
module parser_work

nitc :: parser_work

Internal algorithm and data structures for the Nit parser
module phase

nitc :: phase

Phases of the processing of nit programs
module poset

poset :: poset

Pre order sets and partial order set (ie hierarchies)
module protocol

core :: protocol

module queue

core :: queue

Queuing data structures and wrappers
module range

core :: range

Module for range of discrete objects.
module re

core :: re

Regular expression support for all services based on Pattern
module ropes

core :: ropes

Tree-based representation of a String.
module scope

nitc :: scope

Identification and scoping of local variables and labels.
module serialization

serialization :: serialization

General serialization services
module serialization_core

serialization :: serialization_core

Abstract services to serialize Nit objects to different formats
module sorter

core :: sorter

This module contains classes used to compare things and sorts arrays.
module stream

core :: stream

Input and output streams of characters
module tables

nitc :: tables

Module that interfaces the parsing tables.
module template

template :: template

Basic template system
module text

core :: text

All the classes and methods related to the manipulation of text entities
module time

core :: time

Management of time and dates
module toolcontext

nitc :: toolcontext

Common command-line tool infrastructure than handle options and error messages
module union_find

core :: union_find

union–find algorithm using an efficient disjoint-set data structure
module utf8

core :: utf8

Codec for UTF-8 I/O
module version

nitc :: version

This file was generated by git-gen-version.sh

Parents

module metrics_base

nitc :: metrics_base

Helpers for various statistics tools.
module typing

nitc :: typing

Intraprocedural resolution of static types and OO-services

Children

module metrics

nitc :: metrics

Various statistics about Nit models and programs

Descendants

module a_star-m

a_star-m

module api

nitc :: api

Components required to build a web server about the nit model.
module nitmetrics

nitc :: nitmetrics

A program that collects various metrics on nit programs and libraries
module nitweb

nitc :: nitweb

Runs a webserver based on nitcorn that render things from model.
# Detect the static usage of covariance in the code.
#
# The module works by refining various methods of the modelize and semantize
# phases to intercept type tests, then discriminate and count the cases.
#
# At the end, the statistics are displayed on the screen.
module detect_covariance

import metrics_base
intrude import semantize::typing
private import counter

redef class ToolContext
	# --detect-variance-constraints
	var opt_detect_covariance = new OptionBool("Detect the static covariance usages", "--detect-covariance")

	# Phase that intercepts static type tests then display statistics about covariance
	private var detect_covariance_phase = new DetectCovariancePhase(self, null)
end

private class DetectCovariancePhase
	super Phase

	init
	do
		toolcontext.option_context.add_option(toolcontext.opt_detect_covariance)
	end

	fun is_disabled: Bool
	do
		return not toolcontext.opt_detect_covariance.value and not toolcontext.opt_all.value
	end

	fun cpt_subtype_kinds: Counter[String] do return once new Counter[String]
	fun cpt_total_variance: Counter[String] do return once new Counter[String]
	fun cpt_total_classes: Counter[String] do return once new Counter[String]

	fun cpt_explanations: Counter[String] do return once new Counter[String]
	fun cpt_classes: Counter[MClass] do return once new Counter[MClass]
	fun cpt_pattern: Counter[String] do return once new Counter[String]
	fun cpt_nodes: Counter[String] do return once new Counter[String]
	fun cpt_modules: Counter[String] do return once new Counter[String]
	fun cpt_expression: Counter[String] do return once new Counter[String]

	fun cpt_cast_kind: Counter[String] do return once new Counter[String]
	fun cpt_cast_classes: Counter[String] do return once new Counter[String]
	fun cpt_cast_pattern: Counter[String] do return once new Counter[String]
	fun cpt_autocast: Counter[String] do return once new Counter[String]

	# Display collected statistics
	redef fun process_mainmodule(mainmodule, given_mmodules)
	do
		if is_disabled then return

		print "--- Detection of the usage of covariance static type conformance ---"

		print "-- Total --"
		print "- Kinds of the subtype -"
		cpt_subtype_kinds.print_elements(10)
		print "  total: {cpt_subtype_kinds.sum}"

		print "- Variance -"
		cpt_total_variance.print_elements(10)
		print "  total: {cpt_total_variance.sum}"

		print "- Classes of the subtype -"
		cpt_total_classes.print_elements(10)
		print "  total: {cpt_total_classes.sum}"

		print "-- On covariance only --"

		print "- Specific covariance case explanations -"
		cpt_explanations.print_elements(10)
		print "  total: {cpt_explanations.sum}"

		print "- Classes of the subtype, when covariance -"
		cpt_classes.print_elements(10)
		print "  total: {cpt_classes.sum}"

		print "- Patterns of the covariant cases -"
		cpt_pattern.print_elements(10)
		print "  total: {cpt_pattern.sum}"

		print "- Nodes of the covariance cases -"
		cpt_nodes.print_elements(10)
		print "  total: {cpt_nodes.sum}"

		print "- Modules of the covariance cases -"
		cpt_modules.print_elements(10)
		print "  total: {cpt_modules.sum}"

		print "- Kind of the expression node (when it make sense) -"
		cpt_expression.print_elements(10)
		print "  total: {cpt_expression.sum}"

		print "-- Casts --"

		print "- Kind of cast target -"
		cpt_cast_kind.print_elements(10)
		print "  total: {cpt_cast_kind.sum}"

		print "- Classes of the cast -"
		cpt_cast_classes.print_elements(10)
		print "  total: {cpt_cast_classes.sum}"

		print "- Cast pattern -"
		cpt_cast_pattern.print_elements(10)
		print "  total: {cpt_cast_pattern.sum}"

		print "- Autocasts -"
		cpt_autocast.print_elements(10)
		print "  total: {cpt_autocast.sum}"
	end

	# Common method used when static subtype test is performed by a phase
	# Returns true if the test concern real generic covariance
	fun count_types(node, elem: ANode, sub, sup: MType, mmodule: MModule, anchor: nullable MClassType): Bool
	do
		sub = sub.undecorate
		sup = sup.undecorate

		# Category of the target type
		if sub isa MGenericType then
			cpt_subtype_kinds.inc("generic type")
		else if not sub isa MClassType then
			cpt_subtype_kinds.inc("formal type")
		else if sub.mclass.kind == enum_kind then
			cpt_subtype_kinds.inc("primitive type")
		else if sub.mclass.name == "Object" then
			cpt_subtype_kinds.inc("object")
		else
			cpt_subtype_kinds.inc("non-generic type")
		end

		# Class of the subtype
		if sub isa MClassType then
			cpt_total_classes.inc(sub.mclass.to_s)
		else
			cpt_total_classes.inc(sub.to_s)
		end

		# Equal monomorph case
		if sub == sup then
			cpt_total_variance.inc("monomorph")
			return false
		end

		# Equivalent monomorph case
		if sub.is_subtype_invar(mmodule, anchor, sup) and sup.is_subtype_invar(mmodule, anchor, sub) then
			cpt_total_variance.inc("monomorph equiv")
			return false
		end

		# Formal case
		if not sub isa MClassType then
			cpt_total_variance.inc("polymorph & formal")
			return false
		end

		# Non generic case
		if not sub isa MGenericType then
			cpt_total_variance.inc("polymorph & non-generic")
			return false
		end

		# Invariant case
		if sub.is_subtype_invar(mmodule, anchor, sup) then
			cpt_total_variance.inc("polymorph & generic & invariant")
			return false
		end

		if sub.is_subtype(mmodule, anchor, sup) then
			# Covariant
			cpt_total_variance.inc("polymorph & generic & covariant")
		else
			# Cast (explicit or autocast)
			if sup.is_subtype(mmodule, anchor, sub) then
				cpt_total_variance.inc("polymorph & generic & upcast!?")
				cpt_pattern.inc("7.upcast")
				return false
			end

			if not sup isa MGenericType then
				cpt_total_variance.inc("polymorph & generic & lateral-cast from non-gen")
				return false
			end

			cpt_total_variance.inc("polymorph & generic & lateral-cast from gen")
		end

		## ONLY covariance remains here

		cpt_modules.inc(mmodule.mgroup.mpackage.name)
		cpt_classes.inc(sub.mclass)

		# Track if `cpt_explanations` is already decided (used to fallback on unknown)
		var caseknown = false

		# Detect the pattern
		var n = node
		while n isa AType or n isa AExprs do n = n.parent.as(not null)
		cpt_nodes.inc(n.class_name)
		if n isa AVarAssignExpr or n isa AAttrPropdef and elem isa AExpr then
			cpt_pattern.inc("1.assign")
		else if n isa ASendExpr or n isa ANewExpr then
			cpt_pattern.inc("2.param")
		else if n isa AReturnExpr then
			cpt_pattern.inc("3.return")
		else if n isa APropdef or n isa ASignature then
			cpt_pattern.inc("4.redef")
		else if n isa AAsCastExpr or n isa AIsaExpr then
			cpt_pattern.inc("6.downcast")
			if n isa AIsaExpr and n.n_expr isa ASelfExpr then
				cpt_explanations.inc("downcast on self")
				caseknown = true
			else
				node.debug("6.downcast {sup} to {sub}")
			end
		else if n isa ASuperPropdef then
			cpt_pattern.inc("8.subclass")
		else if n isa AArrayExpr then
			cpt_pattern.inc("9.array element")
		else
			n.debug("Unknown pattern")
			cpt_pattern.inc("X.unknown")
		end

		if not caseknown then
			if false then
				cpt_explanations.inc("covariant class")
			else
				cpt_explanations.inc("other covariance")
			end
		end

		return true
	end

	# Common method used when static cast test is seen by a phase
	fun count_cast(node: ANode, sub, sup: MType, mmodule: MModule, anchor: nullable MClassType)
	do
		var nsup = sup
		sup = sup.undecorate
		sub = sub.undecorate

		if sub == nsup then
			cpt_cast_pattern.inc("monomorphic cast!?!")
			node.debug("monomorphic cast {sup} to {sub}")
		else if not sub isa MClassType then
			cpt_cast_pattern.inc("cast to formal")
		else if not sub isa MGenericType then
			cpt_cast_pattern.inc("cast to non-generic")
		else if sub == sup then
			cpt_cast_pattern.inc("nonullable monomorphic cast")
		else if sup.is_subtype(mmodule, anchor, sub) then
			cpt_cast_pattern.inc("upcast to generic")
		else if not sub.is_subtype(mmodule, anchor, sup) then
			cpt_cast_pattern.inc("lateral cast to generic")
		else if not sub.is_subtype_invar(mmodule, anchor, sup) then
			assert sup isa MGenericType
			if sup.mclass != sub.mclass then
				cpt_cast_pattern.inc("covariant downcast to a generic (distinct classes)")
			else
				cpt_cast_pattern.inc("covariant downcast to a generic (same classes)")
			end
		else if not sup isa MGenericType then
			cpt_cast_pattern.inc("invariant downcast from non-generic to a generic")
		else
			assert sup.mclass != sub.mclass
			cpt_cast_pattern.inc("invariant downcast from generic to generic")
		end

		cpt_cast_kind.inc(sub.class_name.to_s)

		if sub isa MGenericType then
			cpt_cast_classes.inc(sub.mclass.to_s)
		else if sub isa MClassType then
			# No generic class, so no covariance at runtime
		else
			cpt_cast_classes.inc(sub.to_s)
		end
	end
end

redef class ModelBuilder
	redef fun check_subtype(node, mmodule, anchor, sub, sup)
	do
		var res = super
		var dcp = toolcontext.detect_covariance_phase

		if dcp.is_disabled then return res

		if res then
			dcp.count_types(node, node, sub, sup, mmodule, anchor)
		else
			dcp.cpt_total_variance.inc("bad mb subtype")
		end

		return res
	end
end

redef class TypeVisitor

	fun dcp: DetectCovariancePhase do return modelbuilder.toolcontext.detect_covariance_phase

	redef fun check_expr_cast(node, nexpr, ntype)
	do
		var sub = super

		if dcp.is_disabled then return sub

		# In case of error, just forward
		if sub == null then
			dcp.cpt_total_variance.inc("bad cast")
			return null
		end

		var sup = nexpr.mtype.as(not null)

		if sub != ntype.mtype.as(not null) then
			node.debug("fishy cast: res={sub} ntype={ntype.mtype.as(not null)}")
		end

		dcp.count_types(node, nexpr, sub, sup, mmodule, anchor)

		dcp.count_cast(node, sub, sup, mmodule, anchor)

		return sub
	end

	redef fun check_subtype(node: ANode, sub, sup: MType, autocast: Bool): nullable MType
	do
		var res = super

		if dcp.is_disabled then return res

		var anchor = self.anchor
		var supx = sup
		var subx = sub
		var p = node.parent.as(not null)
		if p isa AExprs then p = p.parent.as(not null)

		# Case of autocast
		if not self.is_subtype(sub, sup) then
			if node isa AAsCastExpr then
				return res
			end
			if not autocast then
				return res
			end
			sup = supx.resolve_for(anchor.mclass.mclass_type, anchor, mmodule, true)
			if self.is_subtype(sub, sup) then
				dcp.cpt_autocast.inc("vt")
				dcp.count_cast(node, supx, sub, mmodule, anchor)
			else
				sup = supx.resolve_for(anchor, null, mmodule, false)
				if self.is_subtype(sub, sup) then
					dcp.cpt_autocast.inc("vt+pt")
					dcp.count_cast(node, supx, sub, mmodule, anchor)
				else
					self.modelbuilder.error(node, "Type Error: expected `{sup}`, got `{sub}`")
					return null
				end
			end
			dcp.count_types(p, node, supx, subx, mmodule, anchor)
			return res
		end

		# Count case
		if not dcp.count_types(p, node, sub, sup, mmodule, anchor) then return sub

		# Unknown explanation of covariance, go further
		dcp.cpt_explanations["other covariance"] -= 1

		var n = node
		if n isa AOnceExpr then n = n.n_expr
		dcp.cpt_expression.inc(n.class_name)

		if node isa AArrayExpr then
			dcp.cpt_explanations.inc("lit-array")
		else if p isa ACallExpr then
			var name = p.n_qid.n_id.text
			if name == "sort" or name == "linearize_mpropdefs" then
				dcp.cpt_explanations.inc("generic methods (sort-method)")
			else if name == "visit_list" then
				dcp.cpt_explanations.inc("use-site covariance (visit_list-method)")
			else
				dcp.cpt_explanations.inc("other covariance")
			end
		else
			dcp.cpt_explanations.inc("other covariance")
		end
		return res
	end
end

redef class MType
	# Return true if `self` is a invariant subtype of `sup`.
	# This is just a copy of the original `is_subtype` method with only two new lines
	fun is_subtype_invar(mmodule: MModule, anchor: nullable MClassType, sup: MType): Bool
	do
		var sub = self
		if sub == sup then return true

		#print "1.is {sub} a {sup}? ===="

		if anchor == null then
			assert not sub.need_anchor
			assert not sup.need_anchor
		else
			# First, resolve the formal types to the simplest equivalent forms in the receiver
			assert sub.can_resolve_for(anchor, null, mmodule)
			sub = sub.lookup_fixed(mmodule, anchor)
			assert sup.can_resolve_for(anchor, null, mmodule)
			sup = sup.lookup_fixed(mmodule, anchor)
		end

		# Does `sup` accept null or not?
		# Discard the nullable marker if it exists
		var sup_accept_null = false
		if sup isa MNullableType then
			sup_accept_null = true
			sup = sup.mtype
		else if sup isa MNullType then
			sup_accept_null = true
		end

		# Can `sub` provide null or not?
		# Thus we can match with `sup_accept_null`
		# Also discard the nullable marker if it exists
		if sub isa MNullableType then
			if not sup_accept_null then return false
			sub = sub.mtype
		else if sub isa MNullType then
			return sup_accept_null
		end
		# Now the case of direct null and nullable is over.

		# If `sub` is a formal type, then it is accepted if its bound is accepted
		while sub isa MFormalType do
			#print "3.is {sub} a {sup}?"

			# A unfixed formal type can only accept itself
			if sub == sup then return true

			assert anchor != null
			sub = sub.lookup_bound(mmodule, anchor)

			#print "3.is {sub} a {sup}?"

			# Manage the second layer of null/nullable
			if sub isa MNullableType then
				if not sup_accept_null then return false
				sub = sub.mtype
			else if sub isa MNullType then
				return sup_accept_null
			end
		end
		#print "4.is {sub} a {sup}? <- no more resolution"

		if sub isa MBottomType or sub isa MErrorType then
			return true
		end

		assert sub isa MClassType else print_error "{sub} <? {sup}" # It is the only remaining type

		# Handle sup-type when the sub-type is class-based (other cases must have be identified before).
		if sup isa MFormalType or sup isa MNullType or sup isa MBottomType or sup isa MErrorType then
			# These types are not super-types of Class-based types.
			return false
		end

		assert sup isa MClassType else print_error "got {sup} {sub.inspect}" # It is the only remaining type

		# Now both are MClassType, we need to dig

		if sub == sup then return true

		if anchor == null then anchor = sub # UGLY: any anchor will work
		var resolved_sub = sub.anchor_to(mmodule, anchor)
		var res = resolved_sub.collect_mclasses(mmodule).has(sup.mclass)
		if not res then return false
		if not sup isa MGenericType then return true
		var sub2 = sub.supertype_to(mmodule, anchor, sup.mclass)
		assert sub2.mclass == sup.mclass
		for i in [0..sup.mclass.arity[ do
			var sub_arg = sub2.arguments[i]
			var sup_arg = sup.arguments[i]
			res = sub_arg.is_subtype(mmodule, anchor, sup_arg)
			if not res then return false
			# The two new lines
			res = sup_arg.is_subtype(mmodule, anchor, sub_arg)
			if not res then return false
			# End of the two new lines
		end
		return true
	end
end
src/metrics/detect_covariance.nit:15,1--513,3