[topl] Parser for temporal properties

Summary:
TOPL properties are essentially automata, which specify a bad pattern.
This commit is just a parser for them.

Reviewed By: jvillard

Differential Revision: D14477671

fbshipit-source-id: c38a8ef37
master
Radu Grigore 6 years ago committed by Facebook Github Bot
parent 0a9c77e779
commit 8bf65086e3

@ -141,6 +141,7 @@ DIRECT_TESTS += \
java_quandary \ java_quandary \
java_racerd \ java_racerd \
java_starvation \ java_starvation \
java_topl \
java_tracing \ java_tracing \
ifneq ($(ANT),no) ifneq ($(ANT),no)

@ -1485,6 +1485,13 @@ INTERNAL OPTIONS
Activates: Mode for testing, where no headers are translated, and Activates: Mode for testing, where no headers are translated, and
dot files are created (clang only) (Conversely: --no-testing-mode) dot files are created (clang only) (Conversely: --no-testing-mode)
--topl-properties +path
[EXPERIMENTAL] Specify a file containing a temporal property
definition (e.g., jdk.topl).
--topl-properties-reset
Set --topl-properties to the empty list.
--trace-error --trace-error
Activates: Detailed tracing information during error explanation Activates: Detailed tracing information during error explanation
(Conversely: --no-trace-error) (Conversely: --no-trace-error)

@ -2136,6 +2136,11 @@ and test_filtering =
"List all the files Infer can report on (should be called from the root of the project)" "List all the files Infer can report on (should be called from the root of the project)"
and topl_properties =
CLOpt.mk_path_list ~default:[] ~long:"topl-properties"
"[EXPERIMENTAL] Specify a file containing a temporal property definition (e.g., jdk.topl)."
and profiler_samples = and profiler_samples =
CLOpt.mk_path_opt ~long:"profiler-samples" CLOpt.mk_path_opt ~long:"profiler-samples"
"File containing the profiler samples when Infer is run Test Determinator mode with \ "File containing the profiler samples when Infer is run Test Determinator mode with \
@ -2989,6 +2994,8 @@ and testing_mode = !testing_mode
and threadsafe_aliases = !threadsafe_aliases and threadsafe_aliases = !threadsafe_aliases
and topl_properties = !topl_properties
and trace_error = !trace_error and trace_error = !trace_error
and trace_events = !trace_events and trace_events = !trace_events

@ -654,6 +654,8 @@ val racerd : bool
val threadsafe_aliases : Yojson.Basic.json val threadsafe_aliases : Yojson.Basic.json
val topl_properties : string list
val trace_error : bool val trace_error : bool
val trace_events : bool val trace_events : bool

@ -1257,6 +1257,7 @@ let analyze_procedure_aux summary exe_env tenv proc_desc : Summary.t =
let analyze_procedure {Callbacks.summary; proc_desc; tenv; exe_env} : Summary.t = let analyze_procedure {Callbacks.summary; proc_desc; tenv; exe_env} : Summary.t =
(* make sure models have been registered *) (* make sure models have been registered *)
BuiltinDefn.init () ; BuiltinDefn.init () ;
if not (List.is_empty Config.topl_properties) then Topl.init () ;
try analyze_procedure_aux summary exe_env tenv proc_desc with exn -> try analyze_procedure_aux summary exe_env tenv proc_desc with exn ->
IExn.reraise_if exn ~f:(fun () -> not (Exceptions.handle_exception exn)) ; IExn.reraise_if exn ~f:(fun () -> not (Exceptions.handle_exception exn)) ;
Reporting.log_error_using_state summary exn ; Reporting.log_error_using_state summary exn ;

@ -46,7 +46,7 @@ depend:
ocamldep -native \ ocamldep -native \
-I IR -I absint -I atd -I backend -I base -I biabduction -I bufferoverrun -I checkers \ -I IR -I absint -I atd -I backend -I base -I biabduction -I bufferoverrun -I checkers \
-I clang -I concurrency -I facebook -I integration -I istd -I java \ -I clang -I concurrency -I facebook -I integration -I istd -I java \
-I labs -I nullsafe -I quandary -I unit -I unit/clang -I deadcode \ -I labs -I nullsafe -I quandary -I topl -I unit -I unit/clang -I deadcode \
$(ml_src_files) > deadcode/.depend $(ml_src_files) > deadcode/.depend
# circular dependency... not sure how to fix properly # circular dependency... not sure how to fix properly

@ -23,6 +23,7 @@ let source_dirs =
; "labs" ; "labs"
; "nullsafe" ; "nullsafe"
; "quandary" ; "quandary"
; "topl"
; "unit" ] ) ; "unit" ] )
@ -50,7 +51,9 @@ let infer_cflags =
let stanzas = let stanzas =
( if clang then ["(ocamllex types_lexer ctl_lexer)"; "(menhir (modules types_parser ctl_parser))"] ( if clang then ["(ocamllex types_lexer ctl_lexer)"; "(menhir (modules types_parser ctl_parser))"]
else [] ) else [] )
@ [ Format.sprintf @ [ "(ocamllex ToplLexer)"
; "(menhir (flags --unused-token INDENT) (modules ToplParser))"
; Format.sprintf
{| {|
(library (library
(name InferModules) (name InferModules)

@ -0,0 +1,30 @@
(*
* Copyright (c) 2019-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open! IStd
module L = Logging
let initialized = ref false
let properties = ref []
let parse topl_file =
let f ch =
let lexbuf = Lexing.from_channel ch in
try ToplParser.properties (ToplLexer.token ()) lexbuf with ToplParser.Error ->
let Lexing.({pos_lnum; pos_bol; pos_cnum; _}) = Lexing.lexeme_start_p lexbuf in
let col = pos_cnum - pos_bol + 1 in
L.(die UserError) "@[%s:%d:%d: topl parse error@]@\n@?" topl_file pos_lnum col
in
try In_channel.with_file topl_file ~f with Sys_error msg ->
L.(die UserError) "@[topl:%s: %s@]@\n@?" topl_file msg
let init () =
if not !initialized then (
properties := List.concat_map ~f:parse Config.topl_properties ;
initialized := true )

@ -0,0 +1,11 @@
(*
* Copyright (c) 2019-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open! IStd
val init : unit -> unit
(** Parse properties, mentioned by [Config.topl_properties]. Does this only once. *)

@ -0,0 +1,33 @@
(*
* Copyright (c) 2019-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open! IStd
type register_name = string
(** TODO: use Const.t *)
type constant = string
type value_pattern =
| Ignore
| SaveInRegister of register_name
| EqualToRegister of register_name
| EqualToConstant of constant
(** a regular expression *)
type procedure_name_pattern = string
type label =
{ return: value_pattern
; procedure_name: procedure_name_pattern
; arguments: value_pattern list option }
type vertex = string
type transition = {source: vertex; target: vertex; label: label}
type t = {name: string; message: string option; prefixes: string list; transitions: transition list}

@ -0,0 +1,76 @@
(*
* Copyright (c) 2019-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
{
open !IStd
open ToplParser
module L = Logging
let new_line x y lexbuf =
let m = x |> String.filter ~f:(Char.equal '\n') |> String.length in
let n = y |> String.length in
let open Lexing in
let lcp = lexbuf.lex_curr_p in
lexbuf.lex_curr_p <-
{ lcp with pos_lnum = lcp.pos_lnum + m ; pos_bol = lcp.pos_cnum - n } ;
(INDENT n)
let quoted = Str.regexp "\\\\\\(.\\)"
let unquote x = Str.global_replace quoted "\\1" x
(* We open Caml, because ocamllex generates code that uses Array.make,
which is not available in Core. Ideally, this should go away. *)
open! Caml
}
let id_head = ['a'-'z' 'A'-'Z']
let id_tail = ['a'-'z' 'A'-'Z' '0'-'9']*
rule raw_token = parse
| '\t' { raise Error }
| ((' '* ("//" [^ '\n']*)? '\n')+ as x) (' '* as y) { new_line x y lexbuf }
| ' '+ { raw_token lexbuf }
| "->" { ARROW }
| '=' { ASGN }
| ':' { COLON }
| ',' { COMMA }
| '(' { LP }
| ')' { RP }
| '*' { STAR }
| '<' (([^ '<' '>' '\n' '\\'] | ('\\' _))* as x) '>' { CONSTANT (unquote x) }
| '"' ([^ '"' '\n']* as x) '"' { STRING x }
| "prefix" { PREFIX }
| "property" { PROPERTY }
| "message" { MESSAGE }
| id_head id_tail as id { ID id }
| eof { EOF }
| _ { raise Error }
{
let token () =
let indents = ref [0] in
let scheduled_rc = ref 0 in
let last_indent () = match !indents with
| x :: _ -> x
| [] -> L.(die InternalError) "ToplLexer.indents should be nonempty"
in
let add_indent n = indents := n :: !indents in
let rec drop_to_indent n = match !indents with
| x :: xs when x > n -> (incr scheduled_rc; indents := xs; drop_to_indent n)
| x :: _ when x < n -> raise Error (* bad indentation *)
| _ -> ()
in
let rec step lexbuf =
if !scheduled_rc > 0 then (decr scheduled_rc; RC)
else match raw_token lexbuf with
| INDENT n when n > last_indent () -> (add_indent n; LC)
| INDENT n when n < last_indent () -> (drop_to_indent n; step lexbuf)
| INDENT _ -> step lexbuf
| t -> t
in
step
}

@ -0,0 +1,76 @@
/*
* Copyright (c) 2019-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
%{
open !IStd
let is_guard i = Char.is_lowercase i.[0]
let normalize_id i = String.uncapitalize i
let value_pattern_of_id i =
assert (String.length i > 0) ;
let j = normalize_id i in
if is_guard i then ToplAst.EqualToRegister j else ToplAst.SaveInRegister j
%}
%token <int> INDENT (* The lexer uses this token only internally. *)
%token <string> CONSTANT
%token <string> ID
%token <string> STRING
%token ARROW
%token ASGN
%token COLON
%token COMMA
%token EOF
%token LC
%token LP
%token MESSAGE
%token PREFIX
%token PROPERTY
%token RC
%token RP
%token STAR
%start <ToplAst.t list> properties
%%
properties: ps=one_property* EOF { ps }
one_property:
PROPERTY name=ID LC message=message? prefixes=prefix* transitions=transition* RC
{ ToplAst.{name; message; prefixes; transitions} }
message: MESSAGE s=STRING { s }
prefix: PREFIX c=CONSTANT { c }
transition:
source=ID ARROW target=ID COLON label=label
{ ToplAst.{source; target; label} }
label:
return=value_pattern ASGN c=call_pattern
{ let procedure_name, arguments = c in ToplAst.{return; procedure_name; arguments} }
| c=call_pattern
{ let procedure_name, arguments = c in ToplAst.{return=Ignore; procedure_name; arguments} }
call_pattern: p=procedure_pattern a=arguments_pattern? { (p, a) }
procedure_pattern:
i=ID { i }
| c=CONSTANT { c }
| STAR { ".*" }
arguments_pattern: LP a=separated_list(COMMA, value_pattern) RP { a }
value_pattern:
i=ID { value_pattern_of_id i }
| c=CONSTANT { ToplAst.EqualToConstant c }
| STAR { ToplAst.Ignore }
%%

@ -0,0 +1,13 @@
# Copyright (c) 2019-present, Facebook, Inc.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
TESTS_DIR = ../../..
INFER_OPTIONS = --topl-properties tomcat.topl --biabduction-only
INFERPRINT_OPTIONS = --issues-tests
SOURCES = $(wildcard *.java)
include $(TESTS_DIR)/javac.make

@ -0,0 +1,9 @@
/*
* Copyright (c) 2019-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
class TomcatFail {
void f() {}
}

@ -0,0 +1,49 @@
property ForwardUncommitted
message "A ServletResponse was forwarded before being committed."
// TODO assume InterleavedResponse_Weak
prefix <javax.servlet>
prefix <javax.servlet.{ServletOutputStream,ServletResponse}>
prefix <java.io.PrintWriter>
start -> start: *
start -> tracking: R = <ServletResponse.\<init\>>
tracking -> ok: flushBuffer(r)
tracking -> gotWriter: W = getWriter(r)
gotWriter -> ok: flush(w)
gotWrite -> ok: flushBuffer(r)
tracking -> gotStream: S = getOutputStream(r)
gotStream -> ok: flush(s)
gotStream -> ok: flushBuffer(r)
tracking -> error: <RequestDispatcher.forward>(*, *, r)
gotWriter -> error: <RequestDispatcher.forward>(*, *, r)
gotStream -> error: <RequestDispatcher.forward>(*, *, r)
// The documentation of ServletResponse.getOutputStream says that "either this
// method getWriter may be called to write to the body, not both." So,
// technically, the property is InterleavedResponse1. However, this property is
// broken, which is why we also have the weaker version InterleavedResponse2.
property InterleavedResponse1
message "A ServletResponse was asked for both a writer and a stream."
prefix <javax.servlet.ServletResponse>
start -> start: *
start -> gotWriter: W = getWriter(R)
start -> gotStream: S = getOutputStream(R)
gotWriter -> error: getOutputStream(r)
gotStream -> error: getWriter(r)
property InterleavedResponse2
// vertex names: w = got writer; W = used writer; similarly for s, S
message "Incompatible methods for putting data into a response were used."
prefix <javax.servlet.ServletResponse>
start -> start: *
start -> w: W = getWriter(R)
start -> s: S = getOutputStream(R)
w -> sw: S = getOutputStream(r)
s -> sw: W = getWriter(r)
w -> W: *(w)
sw -> sW: *(w)
s -> S: *(s)
sw -> Sw: *(s)
W -> sW: S = getOutputStream(r)
S -> Sw: W = getWriter(r)
sW -> error: *(s)
Sw -> error: *(w)
Loading…
Cancel
Save