From afd8be99308dac8c25fb397a4eed86a52ed64daf Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Sat, 19 Aug 2017 17:39:26 +0300 Subject: [PATCH 01/31] spec/lexers/sampes/demos: add Solidity lexer (squashed branch). Squashed commits: spec/lexers: Solidity spec, based on tutorial's Turtle spec. samples: add Solidity sample (from pygments-lexer-solidity). See https://gitlab.com/veox/pygments-lexer-solidity. File permalink: https://gitlab.com/veox/pygments-lexer-solidity/blob/8e9919b5e9e91a2022def2a4c51003bc99042686/example.sol The Pygments lexer's example was written by me, and is licensed under a BSD license. demos: minimal Solidity demo. lexers: track solidity.rb (verbatim copy of c.rb). lexers/solidity: proper class, extensions, other meta stuff. lexers/solidity: added some keywords (not all!). lexers/solidity: remove float, added types TODO. lexers/solidity: constants, strings. lexers/solidity: clean-up + remove function state. lexers/solidity: complex MxN types. lexers: fix mimetype (`text/solidity` -> `text/x-solidity`). spec: comment guess-by-source section that makes tests fail. Test fails because something (?) is guessed as PlainText. lexers/solidity: add missing `assert` and `require` keywords (+ demos/solidity: rewrite). A few more keywords may well be missing. Used https://github.com/ethereum/solidity/blob/0a04a35a2e02437ae038af41c947b3e829946bca/libsolidity/parsing/Token.h to check - probably should look for "preprocessor" directives, or built-ins, or the like, - in Solidity's source tree. demos/solidity: fix to compile. lexers/solidity: populate builtins + remove a few more C lexer leftovers. SQ --- lib/rouge/demos/solidity | 24 ++++ lib/rouge/lexers/solidity.rb | 182 +++++++++++++++++++++++++++++ spec/lexers/solidity_spec.rb | 22 ++++ spec/visual/samples/solidity | 220 +++++++++++++++++++++++++++++++++++ 4 files changed, 448 insertions(+) create mode 100644 lib/rouge/demos/solidity create mode 100644 lib/rouge/lexers/solidity.rb create mode 100644 spec/lexers/solidity_spec.rb create mode 100644 spec/visual/samples/solidity diff --git a/lib/rouge/demos/solidity b/lib/rouge/demos/solidity new file mode 100644 index 0000000000..569aa2b7b5 --- /dev/null +++ b/lib/rouge/demos/solidity @@ -0,0 +1,24 @@ +pragma solidity ~0.4.15; + +interface IMirror { + function reflect() external payable returns(bool /* ain't I pretty?.. */); +} + +contract Mirror is IMirror { + event logMessage(address indexed sender, uint256 value, uint256 gas, bytes data); + + function reflect() external payable returns(bool retval) { + assert(msg.sender != address(this)); + require(msg.value != 0); + + IMirror rorrim = IMirror(msg.sender); + retval = rorrim.reflect.value(msg.value).gas(msg.gas)(); + + logMessage(msg.sender, msg.value, msg.gas, msg.data); + return retval; + } + + function () { // no funny stuff + revert(); + } +} diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb new file mode 100644 index 0000000000..68da58b55d --- /dev/null +++ b/lib/rouge/lexers/solidity.rb @@ -0,0 +1,182 @@ +# -*- coding: utf-8 -*- # + +module Rouge + module Lexers + class Solidity < RegexLexer + tag 'solidity' + filenames '*.sol', '*.solidity' + mimetypes 'text/x-solidity' + + title "Solidity" + desc "Solidity, an Ethereum smart contract programming language" + + # optional comment or whitespace + ws = %r((?:\s|//.*?\n|/[*].*?[*]/)+) + id = /[a-zA-Z_][a-zA-Z0-9_]*/ + + + def self.analyze_text(text) + return 1 if text.shebang? 'pragma solidity' + end + + # TODO: seperate by "type" + def self.keywords + @keywords ||= Set.new %w( + anonymous as assembly break constant continue contract do delete + else enum event external for function hex if indexed interface + internal import is library mapping memory modifier new payable + public pragma private return returns storage struct throw + using var while + ) + end + + def self.builtins + @builtins ||= Set.new %w( + now + true false + assert require revert + selfdestruct suicide + this super balance transfer send call callcode delegatecall + addmod mulmod keccak256 sha256 sha3 ripemd160 ecrecover + ) + # TODO: use (currently shadowed by catch-all in :statements) + block = %w(blockhash coinbase difficulty gaslimit number timestamp) + @builtins.merge( block.map { |i| "block.#{i}" } ) + msg = %w(data gas sender sig value) + @builtins.merge( msg.map { |i| "msg.#{i}" } ) + tx = %w(gasprice origin) + @builtins.merge( tx.map { |i| "tx.#{i}" } ) + end + + def self.constants + @constants ||= Set.new %w( + wei finney szabo ether + seconds minutes hours days weeks years + ) + end + + def self.keywords_type + @keywords_type ||= Set.new %w( + int uint bytes fixed ufixed address bool + ) + + # bytes1 .. bytes32 + @keywords_type.merge( (1..32).map { |i| "bytes#{i}" } ) + + # size helpers + sizesm = (0..256).step(8) + sizesn = (8..256).step(8) + sizesmxn = sizesm.map { |m| m } + .product( sizesn.map { |n| n } ) + .select { |m,n| m+n <= 256 } + # [u]int8 .. [u]int256 + @keywords_type.merge( sizesn.map { |n| "int#{n}" } ) + @keywords_type.merge( sizesn.map { |n| "uint#{n}" } ) + # [u]fixed{MxN} + @keywords_type.merge(sizesmxn.map { |m,n| "fixed#{m}x#{n}" }) + @keywords_type.merge(sizesmxn.map { |m,n| "ufixed#{m}x#{n}" }) + end + + def self.reserved + @reserved ||= Set.new %w( + abstract after case catch default final in inline let + match null of pure relocatable static switch try type + typeof view + ) + end + + start { push :bol } + + state :expr_bol do + mixin :inline_whitespace + + rule(//) { pop! } + end + + # :expr_bol is the same as :bol but without labels, since + # labels can only appear at the beginning of a statement. + state :bol do + mixin :expr_bol + end + + state :inline_whitespace do + rule /[ \t\r]+/, Text + rule /\\\n/, Text # line continuation + rule %r(/(\\\n)?[*].*?[*](\\\n)?/)m, Comment::Multiline + end + + state :whitespace do + rule /\n+/m, Text, :bol + rule %r(//(\\.|.)*?\n), Comment::Single, :bol + mixin :inline_whitespace + end + + state :expr_whitespace do + rule /\n+/m, Text, :expr_bol + mixin :whitespace + end + + state :statements do + mixin :whitespace + rule /(hex)?\"/, Str, :string_double + rule /(hex)?\'/, Str, :string_single + rule %r('(\\.|\\[0-7]{1,3}|\\x[a-f0-9]{1,2}|[^\\'\n])')i, Str::Char + rule /0x[0-9a-f]+/i, Num::Hex + rule /\d+/i, Num::Integer + rule %r(\*/), Error + rule %r([~!%^&*+=\|?:<>/-]), Operator + rule /(?:block|msg|tx)\.[a-z]*\b/, Name::Builtin + rule /[()\[\],.]/, Punctuation + rule id do |m| + name = m[0] + + if self.class.keywords.include? name + token Keyword + elsif self.class.builtins.include? name + token Name::Builtin + elsif self.class.constants.include? name + token Keyword::Constant + elsif self.class.keywords_type.include? name + token Keyword::Type + elsif self.class.reserved.include? name + token Keyword::Reserved + else + token Name + end + end + end + + state :root do + mixin :expr_whitespace + rule(//) { push :statement } + # TODO: function declarations + end + + state :statement do + rule /;/, Punctuation, :pop! + mixin :expr_whitespace + mixin :statements + rule /[{}]/, Punctuation + end + + state :string_common do + rule /\\(u[a-fA-F0-9]{4}|x..|[^x])/, Str::Escape + rule /[^\\\"\'\n]+/, Str + rule /\\\n/, Str # line continuation + rule /\\/, Str # stray backslash + end + + state :string_double do + mixin :string_common + rule /\"/, Str, :pop! + rule /\'/, Str + end + + state :string_single do + mixin :string_common + rule /\'/, Str, :pop! + rule /\"/, Str + end + end + end +end diff --git a/spec/lexers/solidity_spec.rb b/spec/lexers/solidity_spec.rb new file mode 100644 index 0000000000..0ec937117b --- /dev/null +++ b/spec/lexers/solidity_spec.rb @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- # + +describe Rouge::Lexers::Solidity do + let(:subject) { Rouge::Lexers::Solidity.new } + + describe 'guessing' do + include Support::Guessing + + it 'guesses by filename' do + assert_guess :filename => 'foo.sol' + assert_guess :filename => 'foo.solidity' + end + + it 'guesses by mimetype' do + assert_guess :mimetype => 'text/x-solidity' + end + + # it 'guesses by source' do + # assert_guess :source => 'pragma solidity' + # end + end +end diff --git a/spec/visual/samples/solidity b/spec/visual/samples/solidity new file mode 100644 index 0000000000..c41c7276bc --- /dev/null +++ b/spec/visual/samples/solidity @@ -0,0 +1,220 @@ +pragma solidity ^0.4.1; + +/********************************************************************** + * example.sol * + **********************************************************************/ + +// Code in this contract is not meant to work (or be a good example). +// It is meant to demonstrate good syntax highlighting by pygments, +// even if otherwise hazardous. + +// Comments relevant to the lexer are single-line. +/* Comments relevant to the code are multi-line. */ + +library Assembly { + function junk(address _addr) returns (address _ret) { + assembly { + let tmp := 0 + + // nested code block + let mulmod_ := 0 { // evade collision with `mulmod` + let tmp:=sub(mulmod_,1) // `tmp` is not a label + mulmod_ := tmp + } + /* guess what mulmod_ is now... */ + _loop: // JIC, dots are invalid in labels + let i := 0x10 + loop: + // Escape sequences in comments are not parsed. + /* Not sure what's going on here, but it sure is funky! + \o/ \o/ \o/ \o/ \o/ \o/ \o/ \o/ \o/ \o/ \o/ \o/ \o/ */ + mulmod(_addr, mulmod_, 160) + + 0x1 i sub // instructional style + i =: tmp /* tmp not used */ + + jumpi(loop, not(iszero(i))) + + mstore(0x0, _addr) + return(0x0, 160) + } + } +} + +contract Strings { + // `double` is not a keyword (yet) + string doublestr = "This\ is a string\nwith \"escapes\",\ +and it's multi-line. // no comment"; // comment ok // even nested :) + string singlestr = 'This\ is a string\nwith "escapes",\ +and it\'s multi-line. // no comment'; // same thing, single-quote + string hexstr = hex'537472696e67732e73656e6428746869732e62616c616e6365293b'; + + function(){} +} + +contract Types is Strings { + using Assembly for Assembly.junk; + + // typesM (compiler doesn't choke on invalid) + int8 i8; // valid + int10 i10; // invalid + uint256 ui256; // valid + uint9001 ui9001; // invalid + bytes1 b1; //valid + bytes42 b42; // invalid: M out of range for `bytes` + + // typesMxN (compiler doesn't choke on invalid) + fixed0x8 f0x8; // valid + fixed8x0 f8x0; // invalid - N can't be 0 + ufixed42x217 uf42x217; // invalid: M and N must be multiples of 8 + fixed224x16 f224x16; // valid + ufixed256x256 uf256x256; // invalid: M+N > 256 + + // special cases (internally not types) + string str; // dynamic array (not a value-type) + bytes bs; // same as above + //var v = 5; // `var` is a keyword, not a type, and compiler chokes + + address a = "0x1"; // lexer parses as string + struct AddressMap { + address origin; + address result; + address sender; + bool touched; + } + mapping (address => AddressMap) touchedMe; + + function failOnNegative(int8 _arg) + private + constant + returns (uint256) + { + /* implicit type conversion from `int8` to `uint256` */ + return _arg; + } + + // some arithmetic operators + built-in names + function opportunisticSend(address k) private { + /* `touchedMe[k].result` et al are addresses, so + `send()` available */ + touchedMe[k].origin.send(k**2 % 100 finney); + touchedMe[k].result.send(1 wei); + touchedMe[k].sender.send(mulmod(1 szabo, k, 42)); + } + + function() payable { + /* inferred type: address */ + var k = msg.sender; + /* inferred type: `ufixed0x256` */ + var v = 1/42; + /* can't be `var` - location specifier requires explicit type */ + int storage negative = -1; + + // valid syntax, unexpected result - not our problem + ui256 = failOnNegative(negative); + + // logic operators + if ((!touchedMe[msg.sender].touched && + !touchedMe[tx.origin].touched) || + ((~(msg.sender * v + a)) % 256 == 42) + ) { + address memory garbled = Assembly.junk(a + msg.sender); + + /* create a new AddressMap struct in storage */ + AddressMap storage tmp; + + // TODO: highlight all known internal keywords? + tmp.origin = tx.origin; + tmp.result = garbled; + tmp.sender = msg.sender; + tmp.touched = true; + + /* does this link-by-reference as expected?.. */ + touchedMe[msg.sender] = tmp; + touchedMe[tx.origin] = tmp; + } + else { + /* weak guard against re-entry */ + touchedMe[k].touched = false; + + opportunisticSend(k); + + delete touchedMe[k]; + /* these probably do nothing... */ + delete touchedMe[msg.sender]; + delete touchedMe[tx.origin]; + } + } +} + +/** + \brief Examples of bad practices. + + TODO: This special doxygen natspec notation is not parsed yet. + + @author Noel Maersk + */ +/// TODO: Neither is this one. + +contract BadPractices { + address constant creator; /* `internal` by default */ + address private owner; /* forbid inheritance */ + bool mutex; + + modifier critical { + if (mutex) throw; + mutex = true; + _; + mutex = false; + } + + /* constructor */ + function BadPractices() { + creator = tx.origin; + owner = msg.sender; + } + + /* Dangerous - function public (by default), and doesn't check + who's calling. */ + function withdraw(uint _amount) + critical + returns (bool) + { /* `mutex` set via modifier */ + if (msg.sender.call.value(_amount)()) + throw; /* Throwing on failed call is dangerous. + Consider returning false instead?.. */ + return true; + } /* `mutex` reset via modifier */ + + /* fallback */ + function () payable { + /* `i` will be `uint8`, so this is an endless loop + that will consume all gas and eventually throw. + */ + for (var i = 0; i < 257; i++) { + owner++; + } + } +} + +/* +// Open comment to EOF. Compiler chokes on this, but it's useful +// for highlighting to show that there's an unmatched multi-line +// comment open. + +contract MoreBadPractices is BadPractices { + uint balance; + + // These would close the comment if the space was removed: + * / + \* / + + / * no modifiers to check ownership * / + function () payable { + balance += msg.value; + + / * vulnerable to re-entry * / + if (!msg.sender.send(this.balance / 10)) throw; + balance -= this.balance; + } +} From 672ee0aeb38df9c6e7fc9cdc4b75c7fc4da84b9d Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Tue, 22 Aug 2017 20:08:05 +0300 Subject: [PATCH 02/31] lexers/solidity: don't map/merge sets on every invocation - just the first. --- lib/rouge/lexers/solidity.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index 68da58b55d..3c39939883 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -31,7 +31,8 @@ def self.keywords end def self.builtins - @builtins ||= Set.new %w( + return @builtins if @builtins + @builtins = Set.new %w( now true false assert require revert @@ -56,7 +57,8 @@ def self.constants end def self.keywords_type - @keywords_type ||= Set.new %w( + return @keywords_type if @keywords_type + @keywords_type = Set.new %w( int uint bytes fixed ufixed address bool ) From d5f63784ee271fb8618730219b2084999aa55d3e Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Thu, 26 Mar 2020 17:58:23 +0200 Subject: [PATCH 03/31] lexers/solidity: add new keywords mentioned in feedback. This is done before "porting" in changes from Pygments' lexer, so it's out of the way. --- lib/rouge/lexers/solidity.rb | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index 3c39939883..68302d0d51 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -22,11 +22,11 @@ def self.analyze_text(text) # TODO: seperate by "type" def self.keywords @keywords ||= Set.new %w( - anonymous as assembly break constant continue contract do delete - else enum event external for function hex if indexed interface - internal import is library mapping memory modifier new payable - public pragma private return returns storage struct throw - using var while + abstract anonymous as assembly break catch constant continue + contract do delete else enum event external for function hex + if indexed interface internal import is library mapping memory + modifier new payable public pure pragma private return returns + storage struct throw try type using var view while ) end @@ -81,9 +81,8 @@ def self.keywords_type def self.reserved @reserved ||= Set.new %w( - abstract after case catch default final in inline let - match null of pure relocatable static switch try type - typeof view + after case default final in inline let match null of + relocatable static switch typeof ) end From e1b65227762cebac1e88b020e73a522a2551d0f8 Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Thu, 26 Mar 2020 18:33:47 +0200 Subject: [PATCH 04/31] lexers/solidity: add changes up to (mostly) Solidity v0.4.22. These are "ported" from Pygments' lexer. I'm not 100% on the terminology. --- lib/rouge/lexers/solidity.rb | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index 68302d0d51..7bda02a07d 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -22,8 +22,8 @@ def self.analyze_text(text) # TODO: seperate by "type" def self.keywords @keywords ||= Set.new %w( - abstract anonymous as assembly break catch constant continue - contract do delete else enum event external for function hex + abstract anonymous as assembly break catch constant constructor continue + contract do delete else emit enum event external for function hex if indexed interface internal import is library mapping memory modifier new payable public pure pragma private return returns storage struct throw try type using var view while @@ -34,14 +34,19 @@ def self.builtins return @builtins if @builtins @builtins = Set.new %w( now - true false + false true + balance now selector super this + blockhash gasleft assert require revert selfdestruct suicide - this super balance transfer send call callcode delegatecall - addmod mulmod keccak256 sha256 sha3 ripemd160 ecrecover + call callcode delegatecall + send transfer + addmod ecrecover keccak256 mulmod sha256 sha3 ripemd160 ) # TODO: use (currently shadowed by catch-all in :statements) - block = %w(blockhash coinbase difficulty gaslimit number timestamp) + abi = %w(encode encodePacked encodeWithSelector encodeWithSignature) + @builtins.merge( abi.map { |i| "abi.#{i}" } ) + block = %w(coinbase difficulty gaslimit hash number timestamp) @builtins.merge( block.map { |i| "block.#{i}" } ) msg = %w(data gas sender sig value) @builtins.merge( msg.map { |i| "msg.#{i}" } ) @@ -122,8 +127,9 @@ def self.reserved rule /(hex)?\"/, Str, :string_double rule /(hex)?\'/, Str, :string_single rule %r('(\\.|\\[0-7]{1,3}|\\x[a-f0-9]{1,2}|[^\\'\n])')i, Str::Char + rule /\d\d*\.\d+([eE]\d+)?/i, Num::Float rule /0x[0-9a-f]+/i, Num::Hex - rule /\d+/i, Num::Integer + rule /\d+([eE]\d+)?/i, Num::Integer rule %r(\*/), Error rule %r([~!%^&*+=\|?:<>/-]), Operator rule /(?:block|msg|tx)\.[a-z]*\b/, Name::Builtin From e65c9953cce186963513ef6524d23119f7cfbcef Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Thu, 26 Mar 2020 18:57:35 +0200 Subject: [PATCH 05/31] lexers/solidity: add new keywords from Solidity v0.6.0. Natspec, Assembly, Yul, and some built-in functions are still unimplemented. --- lib/rouge/lexers/solidity.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index 7bda02a07d..8074b46a01 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -23,7 +23,7 @@ def self.analyze_text(text) def self.keywords @keywords ||= Set.new %w( abstract anonymous as assembly break catch constant constructor continue - contract do delete else emit enum event external for function hex + contract do delete else emit enum event external fallback for function hex if indexed interface internal import is library mapping memory modifier new payable public pure pragma private return returns storage struct throw try type using var view while @@ -86,8 +86,10 @@ def self.keywords_type def self.reserved @reserved ||= Set.new %w( - after case default final in inline let match null of - relocatable static switch typeof + alias after apply auto case copyof default define final + immutable implements in inline let macro match mutable null of + override partial promise receive reference relocatable sealed + sizeof static supports switch typedef typeof unchecked virtual ) end @@ -105,6 +107,7 @@ def self.reserved mixin :expr_bol end + # TODO: natspec in comments state :inline_whitespace do rule /[ \t\r]+/, Text rule /\\\n/, Text # line continuation From 612c65bc83c83e42bab4546ee40b3064021891b5 Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Thu, 26 Mar 2020 19:02:26 +0200 Subject: [PATCH 06/31] samples/solidity: copy updated sample over from Pygments. --- spec/visual/samples/solidity | 79 ++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/spec/visual/samples/solidity b/spec/visual/samples/solidity index c41c7276bc..175b12760e 100644 --- a/spec/visual/samples/solidity +++ b/spec/visual/samples/solidity @@ -1,18 +1,20 @@ -pragma solidity ^0.4.1; +pragma solidity ^0.6.0; +pragma ABIEncoderV2; +pragma experimental SMTChecker; /********************************************************************** * example.sol * **********************************************************************/ // Code in this contract is not meant to work (or be a good example). -// It is meant to demonstrate good syntax highlighting by pygments, +// It is meant to demonstrate good syntax highlighting by the lexer, // even if otherwise hazardous. // Comments relevant to the lexer are single-line. /* Comments relevant to the code are multi-line. */ library Assembly { - function junk(address _addr) returns (address _ret) { + public function junk(address _addr) private returns (address _ret) { assembly { let tmp := 0 @@ -43,32 +45,39 @@ library Assembly { contract Strings { // `double` is not a keyword (yet) - string doublestr = "This\ is a string\nwith \"escapes\",\ + string double = "This\ is a string\nwith \"escapes\",\ and it's multi-line. // no comment"; // comment ok // even nested :) - string singlestr = 'This\ is a string\nwith "escapes",\ + string single = 'This\ is a string\nwith "escapes",\ and it\'s multi-line. // no comment'; // same thing, single-quote string hexstr = hex'537472696e67732e73656e6428746869732e62616c616e6365293b'; - function(){} + fallback() external {} + + receive() external payable { + revert(); + } } contract Types is Strings { - using Assembly for Assembly.junk; + using Assembly for Assembly; + + bytes stringsruntime = type(Strings).runtimeCode; - // typesM (compiler doesn't choke on invalid) - int8 i8; // valid - int10 i10; // invalid - uint256 ui256; // valid - uint9001 ui9001; // invalid - bytes1 b1; //valid - bytes42 b42; // invalid: M out of range for `bytes` + // typesM (compiler chokes on invalid) + int8 i8; // valid + //int10 i10; // invalid + uint256 ui256; // valid + //uint9001 ui9001; // invalid + bytes1 b1; //valid + //bytes42 b42; // invalid - M out of range for `bytes` - // typesMxN (compiler doesn't choke on invalid) - fixed0x8 f0x8; // valid - fixed8x0 f8x0; // invalid - N can't be 0 - ufixed42x217 uf42x217; // invalid: M and N must be multiples of 8 - fixed224x16 f224x16; // valid - ufixed256x256 uf256x256; // invalid: M+N > 256 + // typesMxN (compiler chokes on invalid) + fixed8x0 f8x0; // valid + fixed8x1 f8x1; // valid + fixed8x8 f8x8; // valid + //fixed0x8 f0x8; // invalid since MxN scheme changed + ufixed256x80 uf256x80; // valid + //ufixed42x217 uf42x217; // invalid - M must be multiple of 8, N <= 80 // special cases (internally not types) string str; // dynamic array (not a value-type) @@ -84,7 +93,7 @@ contract Types is Strings { } mapping (address => AddressMap) touchedMe; - function failOnNegative(int8 _arg) + public function failOnNegative(int8 _arg) private constant returns (uint256) @@ -94,7 +103,7 @@ contract Types is Strings { } // some arithmetic operators + built-in names - function opportunisticSend(address k) private { + public function opportunisticSend(address k) private { /* `touchedMe[k].result` et al are addresses, so `send()` available */ touchedMe[k].origin.send(k**2 % 100 finney); @@ -102,13 +111,13 @@ contract Types is Strings { touchedMe[k].sender.send(mulmod(1 szabo, k, 42)); } - function() payable { + fallback() external payable { /* inferred type: address */ var k = msg.sender; /* inferred type: `ufixed0x256` */ var v = 1/42; /* can't be `var` - location specifier requires explicit type */ - int storage negative = -1; + int negative = -1; // valid syntax, unexpected result - not our problem ui256 = failOnNegative(negative); @@ -118,7 +127,7 @@ contract Types is Strings { !touchedMe[tx.origin].touched) || ((~(msg.sender * v + a)) % 256 == 42) ) { - address memory garbled = Assembly.junk(a + msg.sender); + address garbled = Assembly.junk(a + msg.sender); /* create a new AddressMap struct in storage */ AddressMap storage tmp; @@ -162,32 +171,30 @@ contract BadPractices { bool mutex; modifier critical { - if (mutex) throw; + assert(!mutex); mutex = true; _; mutex = false; } - /* constructor */ - function BadPractices() { + constructor { creator = tx.origin; owner = msg.sender; } - /* Dangerous - function public (by default), and doesn't check - who's calling. */ - function withdraw(uint _amount) + /* Dangerous - function public, and doesn't check who's calling. */ + public function withdraw(uint _amount) critical returns (bool) { /* `mutex` set via modifier */ - if (msg.sender.call.value(_amount)()) - throw; /* Throwing on failed call is dangerous. - Consider returning false instead?.. */ + /* Throwing on failed call may be dangerous. Consider + returning false instead?.. */ + require(msg.sender.call.value(_amount)()); return true; } /* `mutex` reset via modifier */ /* fallback */ - function () payable { + fallback() external payable { /* `i` will be `uint8`, so this is an endless loop that will consume all gas and eventually throw. */ @@ -210,7 +217,7 @@ contract MoreBadPractices is BadPractices { \* / / * no modifiers to check ownership * / - function () payable { + fallback() external payable { balance += msg.value; / * vulnerable to re-entry * / From dbec54acf65c206392a4fd53987a6d049ee17e71 Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Thu, 26 Mar 2020 19:21:34 +0200 Subject: [PATCH 07/31] lexers/solidity: mark regexes in rules explicitly. This calms down Rake warnings about ambivalence. --- lib/rouge/lexers/solidity.rb | 42 ++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index 8074b46a01..54dd16f80b 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -109,34 +109,34 @@ def self.reserved # TODO: natspec in comments state :inline_whitespace do - rule /[ \t\r]+/, Text - rule /\\\n/, Text # line continuation + rule %r/[ \t\r]+/, Text + rule %r/\\\n/, Text # line continuation rule %r(/(\\\n)?[*].*?[*](\\\n)?/)m, Comment::Multiline end state :whitespace do - rule /\n+/m, Text, :bol + rule %r/\n+/m, Text, :bol rule %r(//(\\.|.)*?\n), Comment::Single, :bol mixin :inline_whitespace end state :expr_whitespace do - rule /\n+/m, Text, :expr_bol + rule %r/\n+/m, Text, :expr_bol mixin :whitespace end state :statements do mixin :whitespace - rule /(hex)?\"/, Str, :string_double - rule /(hex)?\'/, Str, :string_single + rule %r/(hex)?\"/, Str, :string_double + rule %r/(hex)?\'/, Str, :string_single rule %r('(\\.|\\[0-7]{1,3}|\\x[a-f0-9]{1,2}|[^\\'\n])')i, Str::Char - rule /\d\d*\.\d+([eE]\d+)?/i, Num::Float - rule /0x[0-9a-f]+/i, Num::Hex - rule /\d+([eE]\d+)?/i, Num::Integer + rule %r/\d\d*\.\d+([eE]\d+)?/i, Num::Float + rule %r/0x[0-9a-f]+/i, Num::Hex + rule %r/\d+([eE]\d+)?/i, Num::Integer rule %r(\*/), Error rule %r([~!%^&*+=\|?:<>/-]), Operator - rule /(?:block|msg|tx)\.[a-z]*\b/, Name::Builtin - rule /[()\[\],.]/, Punctuation + rule %r/(?:block|msg|tx)\.[a-z]*\b/, Name::Builtin + rule %r/[()\[\],.]/, Punctuation rule id do |m| name = m[0] @@ -163,29 +163,29 @@ def self.reserved end state :statement do - rule /;/, Punctuation, :pop! + rule %r/;/, Punctuation, :pop! mixin :expr_whitespace mixin :statements - rule /[{}]/, Punctuation + rule %r/[{}]/, Punctuation end state :string_common do - rule /\\(u[a-fA-F0-9]{4}|x..|[^x])/, Str::Escape - rule /[^\\\"\'\n]+/, Str - rule /\\\n/, Str # line continuation - rule /\\/, Str # stray backslash + rule %r/\\(u[a-fA-F0-9]{4}|x..|[^x])/, Str::Escape + rule %r/[^\\\"\'\n]+/, Str + rule %r/\\\n/, Str # line continuation + rule %r/\\/, Str # stray backslash end state :string_double do mixin :string_common - rule /\"/, Str, :pop! - rule /\'/, Str + rule %r/\"/, Str, :pop! + rule %r/\'/, Str end state :string_single do mixin :string_common - rule /\'/, Str, :pop! - rule /\"/, Str + rule %r/\'/, Str, :pop! + rule %r/\"/, Str end end end From c2198545d667467ea175b4c3607757c3ece2e7a5 Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Thu, 26 Mar 2020 19:42:04 +0200 Subject: [PATCH 08/31] spec/solidity: uncomment and fix "test by source". The issue was that the line is not technically a shebang. So, use a `starts_with?` instead. --- lib/rouge/lexers/solidity.rb | 10 ++++------ spec/lexers/solidity_spec.rb | 6 +++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index 54dd16f80b..b970e7d421 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -3,20 +3,18 @@ module Rouge module Lexers class Solidity < RegexLexer + title "Solidity" + desc "Solidity, an Ethereum smart contract programming language" tag 'solidity' filenames '*.sol', '*.solidity' mimetypes 'text/x-solidity' - title "Solidity" - desc "Solidity, an Ethereum smart contract programming language" - # optional comment or whitespace ws = %r((?:\s|//.*?\n|/[*].*?[*]/)+) id = /[a-zA-Z_][a-zA-Z0-9_]*/ - - def self.analyze_text(text) - return 1 if text.shebang? 'pragma solidity' + def self.detect?(text) + return true if text.start_with? 'pragma solidity' end # TODO: seperate by "type" diff --git a/spec/lexers/solidity_spec.rb b/spec/lexers/solidity_spec.rb index 0ec937117b..9c8dccf11f 100644 --- a/spec/lexers/solidity_spec.rb +++ b/spec/lexers/solidity_spec.rb @@ -15,8 +15,8 @@ assert_guess :mimetype => 'text/x-solidity' end - # it 'guesses by source' do - # assert_guess :source => 'pragma solidity' - # end + it 'guesses by source' do + assert_guess :source => 'pragma solidity' + end end end From a8ee0095e6020b299809da6e33836d288352d9a5 Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Tue, 31 Mar 2020 19:44:46 +0300 Subject: [PATCH 09/31] lexers/solidity: variables can have `$` + `string` is a type. --- lib/rouge/lexers/solidity.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index b970e7d421..fcd87a4b2c 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -11,7 +11,7 @@ class Solidity < RegexLexer # optional comment or whitespace ws = %r((?:\s|//.*?\n|/[*].*?[*]/)+) - id = /[a-zA-Z_][a-zA-Z0-9_]*/ + id = /[a-zA-Z$_][a-zA-Z0-9$_]*/ def self.detect?(text) return true if text.start_with? 'pragma solidity' @@ -62,7 +62,7 @@ def self.constants def self.keywords_type return @keywords_type if @keywords_type @keywords_type = Set.new %w( - int uint bytes fixed ufixed address bool + address bool bytes fixed int ufixed uint string ) # bytes1 .. bytes32 From 769257139f130ef85468be772c35a83fd0e36cf4 Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Wed, 1 Apr 2020 03:11:04 +0900 Subject: [PATCH 10/31] Fix alphabetical ordering of types --- lib/rouge/lexers/solidity.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index fcd87a4b2c..0bd5a8e87c 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -62,7 +62,7 @@ def self.constants def self.keywords_type return @keywords_type if @keywords_type @keywords_type = Set.new %w( - address bool bytes fixed int ufixed uint string + address bool bytes fixed int string ufixed uintb ) # bytes1 .. bytes32 From 9ca9556d7c9736b37513abe22cf30699f3da296e Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Wed, 1 Apr 2020 03:12:38 +0900 Subject: [PATCH 11/31] Replace character ranges with metacharacter --- lib/rouge/lexers/solidity.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index 0bd5a8e87c..eddfecc675 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -11,7 +11,7 @@ class Solidity < RegexLexer # optional comment or whitespace ws = %r((?:\s|//.*?\n|/[*].*?[*]/)+) - id = /[a-zA-Z$_][a-zA-Z0-9$_]*/ + id = /[a-zA-Z$_][\w$_]*/ def self.detect?(text) return true if text.start_with? 'pragma solidity' From aded449e5ab6da17958e292d98321ea83b5d0dd3 Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Wed, 1 Apr 2020 03:16:49 +0900 Subject: [PATCH 12/31] Fix 'uintb' typo --- lib/rouge/lexers/solidity.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index eddfecc675..e7f2370d26 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -62,7 +62,7 @@ def self.constants def self.keywords_type return @keywords_type if @keywords_type @keywords_type = Set.new %w( - address bool bytes fixed int string ufixed uintb + address bool bytes fixed int string ufixed uint ) # bytes1 .. bytes32 From 4ba1cf7f28c8efdc32d7b6f3ac59da443ac4f97c Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Tue, 31 Mar 2020 22:38:08 +0300 Subject: [PATCH 13/31] samples/solidity: show `$` is a valid character in the visual sample. --- spec/visual/samples/solidity | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/visual/samples/solidity b/spec/visual/samples/solidity index 175b12760e..8113d5fd17 100644 --- a/spec/visual/samples/solidity +++ b/spec/visual/samples/solidity @@ -83,6 +83,7 @@ contract Types is Strings { string str; // dynamic array (not a value-type) bytes bs; // same as above //var v = 5; // `var` is a keyword, not a type, and compiler chokes + var unu$ed; // `var` is highlighted, though, and `$` is a valid char address a = "0x1"; // lexer parses as string struct AddressMap { From f416146ea8e6763c99d25e1f7dc967e87d53a3a4 Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Tue, 31 Mar 2020 22:55:50 +0300 Subject: [PATCH 14/31] lexers/solidity: move [u]fixed type to `reserved` + add missing `byte`. --- lib/rouge/lexers/solidity.rb | 37 ++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index e7f2370d26..8ddd4d8e62 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -30,6 +30,7 @@ def self.keywords def self.builtins return @builtins if @builtins + @builtins = Set.new %w( now false true @@ -41,6 +42,7 @@ def self.builtins send transfer addmod ecrecover keccak256 mulmod sha256 sha3 ripemd160 ) + # TODO: use (currently shadowed by catch-all in :statements) abi = %w(encode encodePacked encodeWithSelector encodeWithSignature) @builtins.merge( abi.map { |i| "abi.#{i}" } ) @@ -61,34 +63,41 @@ def self.constants def self.keywords_type return @keywords_type if @keywords_type + @keywords_type = Set.new %w( - address bool bytes fixed int string ufixed uint + address bool byte bytes int string uint ) # bytes1 .. bytes32 @keywords_type.merge( (1..32).map { |i| "bytes#{i}" } ) - # size helpers - sizesm = (0..256).step(8) - sizesn = (8..256).step(8) - sizesmxn = sizesm.map { |m| m } - .product( sizesn.map { |n| n } ) - .select { |m,n| m+n <= 256 } + # size helper + sizes = (8..256).step(8) # [u]int8 .. [u]int256 - @keywords_type.merge( sizesn.map { |n| "int#{n}" } ) - @keywords_type.merge( sizesn.map { |n| "uint#{n}" } ) - # [u]fixed{MxN} - @keywords_type.merge(sizesmxn.map { |m,n| "fixed#{m}x#{n}" }) - @keywords_type.merge(sizesmxn.map { |m,n| "ufixed#{m}x#{n}" }) + @keywords_type.merge( sizes.map { |n| "int#{n}" } ) + @keywords_type.merge( sizes.map { |n| "uint#{n}" } ) end def self.reserved + return @reserved if @reserved + @reserved ||= Set.new %w( - alias after apply auto case copyof default define final + alias after apply auto case copyof default define final fixed immutable implements in inline let macro match mutable null of override partial promise receive reference relocatable sealed - sizeof static supports switch typedef typeof unchecked virtual + sizeof static supports switch typedef typeof ufixed unchecked + virtual ) + + # size helpers + sizesm = (0..256).step(8) + sizesn = (8..256).step(8) + sizesmxn = sizesm.map { |m| m } + .product( sizesn.map { |n| n } ) + .select { |m,n| m+n <= 256 } + # [u]fixed{MxN} + @reserved.merge(sizesmxn.map { |m,n| "fixed#{m}x#{n}" }) + @reserved.merge(sizesmxn.map { |m,n| "ufixed#{m}x#{n}" }) end start { push :bol } From 2a1401268fe899f85db5e8342baf6df2e7ef0f80 Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Tue, 31 Mar 2020 23:09:17 +0300 Subject: [PATCH 15/31] lexer/solidity: `receive`, `override`, `virtual` are now actual keywords. --- lib/rouge/lexers/solidity.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index 8ddd4d8e62..3c98caad5f 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -20,11 +20,13 @@ def self.detect?(text) # TODO: seperate by "type" def self.keywords @keywords ||= Set.new %w( - abstract anonymous as assembly break catch constant constructor continue - contract do delete else emit enum event external fallback for function hex - if indexed interface internal import is library mapping memory - modifier new payable public pure pragma private return returns - storage struct throw try type using var view while + abstract anonymous as assembly break catch constant + constructor continue contract do delete else emit enum event + external fallback for function hex if indexed interface + internal import is library mapping memory modifier new + override payable public pure pragma private receive return + returns storage struct throw try type using var view virtual + while ) end @@ -84,9 +86,8 @@ def self.reserved @reserved ||= Set.new %w( alias after apply auto case copyof default define final fixed immutable implements in inline let macro match mutable null of - override partial promise receive reference relocatable sealed - sizeof static supports switch typedef typeof ufixed unchecked - virtual + partial promise reference relocatable sealed sizeof static + supports switch typedef typeof ufixed unchecked ) # size helpers From a828d47d2364c291f30e02936bd0795c2ae29943 Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Tue, 31 Mar 2020 23:23:53 +0300 Subject: [PATCH 16/31] lexers/solidity: update `[u]fixed{M}x{N}` types to "new" scheme. Don't remember when it changed, but sample file already had the "new" scheme used + comments to support it, so I guess Solidity v0.5.0 or so. --- lib/rouge/lexers/solidity.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index 3c98caad5f..8c6369f0c9 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -91,11 +91,10 @@ def self.reserved ) # size helpers - sizesm = (0..256).step(8) - sizesn = (8..256).step(8) + sizesm = (8..256).step(8) + sizesn = (0..80) sizesmxn = sizesm.map { |m| m } .product( sizesn.map { |n| n } ) - .select { |m,n| m+n <= 256 } # [u]fixed{MxN} @reserved.merge(sizesmxn.map { |m,n| "fixed#{m}x#{n}" }) @reserved.merge(sizesmxn.map { |m,n| "ufixed#{m}x#{n}" }) From 8b09d51e7bacd52333e08e80a7d32cdf0eef49bf Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Wed, 1 Apr 2020 13:08:33 +0300 Subject: [PATCH 17/31] lexers/solidity: add missing `calldata` keyword. --- lib/rouge/lexers/solidity.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index 8c6369f0c9..306299d2b5 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -20,7 +20,7 @@ def self.detect?(text) # TODO: seperate by "type" def self.keywords @keywords ||= Set.new %w( - abstract anonymous as assembly break catch constant + abstract anonymous as assembly break catch calldata constant constructor continue contract do delete else emit enum event external fallback for function hex if indexed interface internal import is library mapping memory modifier new From 5f81d9b8a88b3162b9325efa03f57f7443e3e4a5 Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Thu, 2 Apr 2020 20:21:16 +0300 Subject: [PATCH 18/31] lexers/solidity: user regex to match [u]fixed{M}x{N} instead. As requested by @pyrmont, for performance's sake: https://github.com/rouge-ruby/rouge/pull/760#discussion_r402189881 --- lib/rouge/lexers/solidity.rb | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index 306299d2b5..7c3860fc10 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -81,23 +81,12 @@ def self.keywords_type end def self.reserved - return @reserved if @reserved - @reserved ||= Set.new %w( alias after apply auto case copyof default define final fixed immutable implements in inline let macro match mutable null of partial promise reference relocatable sealed sizeof static supports switch typedef typeof ufixed unchecked ) - - # size helpers - sizesm = (8..256).step(8) - sizesn = (0..80) - sizesmxn = sizesm.map { |m| m } - .product( sizesn.map { |n| n } ) - # [u]fixed{MxN} - @reserved.merge(sizesmxn.map { |m,n| "fixed#{m}x#{n}" }) - @reserved.merge(sizesmxn.map { |m,n| "ufixed#{m}x#{n}" }) end start { push :bol } @@ -144,6 +133,7 @@ def self.reserved rule %r([~!%^&*+=\|?:<>/-]), Operator rule %r/(?:block|msg|tx)\.[a-z]*\b/, Name::Builtin rule %r/[()\[\],.]/, Punctuation + rule %r/u?fixed\d*x\d*/, Keyword::Reserved rule id do |m| name = m[0] From 6f793bae9a15a8ee9ec849486a7498c6de146df6 Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Fri, 3 Apr 2020 15:38:15 +0300 Subject: [PATCH 19/31] lexers/solidity: [u]fixed{M}x{N} should have at least one digit for M, N. --- lib/rouge/lexers/solidity.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index 7c3860fc10..a5ffae34bc 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -133,7 +133,7 @@ def self.reserved rule %r([~!%^&*+=\|?:<>/-]), Operator rule %r/(?:block|msg|tx)\.[a-z]*\b/, Name::Builtin rule %r/[()\[\],.]/, Punctuation - rule %r/u?fixed\d*x\d*/, Keyword::Reserved + rule %r/u?fixed\d+x\d+/, Keyword::Reserved rule id do |m| name = m[0] From 2242a4de75d43ebaa840bbb7017a18a01ebfc68a Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Fri, 3 Apr 2020 15:44:06 +0300 Subject: [PATCH 20/31] lexers/solidity: use regex for bytes{N} and [u]int{N}, too. --- lib/rouge/lexers/solidity.rb | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index a5ffae34bc..7292bad4aa 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -64,20 +64,9 @@ def self.constants end def self.keywords_type - return @keywords_type if @keywords_type - - @keywords_type = Set.new %w( + @keywords_type ||= Set.new %w( address bool byte bytes int string uint ) - - # bytes1 .. bytes32 - @keywords_type.merge( (1..32).map { |i| "bytes#{i}" } ) - - # size helper - sizes = (8..256).step(8) - # [u]int8 .. [u]int256 - @keywords_type.merge( sizes.map { |n| "int#{n}" } ) - @keywords_type.merge( sizes.map { |n| "uint#{n}" } ) end def self.reserved @@ -134,6 +123,8 @@ def self.reserved rule %r/(?:block|msg|tx)\.[a-z]*\b/, Name::Builtin rule %r/[()\[\],.]/, Punctuation rule %r/u?fixed\d+x\d+/, Keyword::Reserved + rule %r/bytes\d+/, Keyword::Type + rule %r/u?int\d+/, Keyword::Type rule id do |m| name = m[0] From d7ccc70b184e6ef821c6c2a4ce1e548aaee9f4ab Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Fri, 3 Apr 2020 15:45:02 +0300 Subject: [PATCH 21/31] lexers/solidity: remove duplicate rule for (block|msg|tx).{stuff}. They are already covered in `def self.builtins`. --- lib/rouge/lexers/solidity.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index 7292bad4aa..115ee77073 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -120,7 +120,6 @@ def self.reserved rule %r/\d+([eE]\d+)?/i, Num::Integer rule %r(\*/), Error rule %r([~!%^&*+=\|?:<>/-]), Operator - rule %r/(?:block|msg|tx)\.[a-z]*\b/, Name::Builtin rule %r/[()\[\],.]/, Punctuation rule %r/u?fixed\d+x\d+/, Keyword::Reserved rule %r/bytes\d+/, Keyword::Type From f19341e7ff80612886e4febfdea211f7c0b36656 Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Sat, 4 Apr 2020 17:56:38 +0300 Subject: [PATCH 22/31] lexers/solidity: catch case of unclosed multi-line comment. --- lib/rouge/lexers/solidity.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index 115ee77073..32fd9531fd 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -96,7 +96,8 @@ def self.reserved state :inline_whitespace do rule %r/[ \t\r]+/, Text rule %r/\\\n/, Text # line continuation - rule %r(/(\\\n)?[*].*?[*](\\\n)?/)m, Comment::Multiline + rule %r(/[*].*?[*]/)m, Comment::Multiline + rule %r(/\*.*(\*/){0})m, Comment::Multiline # open to EOF end state :whitespace do From 8b42435548b771c19634a80c57a9dfc52acba35f Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Sat, 4 Apr 2020 17:58:14 +0300 Subject: [PATCH 23/31] spec/solidity: update with less incorrect language syntax. --- spec/visual/samples/solidity | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/spec/visual/samples/solidity b/spec/visual/samples/solidity index 8113d5fd17..4f41d16a0d 100644 --- a/spec/visual/samples/solidity +++ b/spec/visual/samples/solidity @@ -14,7 +14,7 @@ pragma experimental SMTChecker; /* Comments relevant to the code are multi-line. */ library Assembly { - public function junk(address _addr) private returns (address _ret) { + function junk(address _addr) private returns (address _ret) { assembly { let tmp := 0 @@ -51,7 +51,7 @@ and it's multi-line. // no comment"; // comment ok // even nested :) and it\'s multi-line. // no comment'; // same thing, single-quote string hexstr = hex'537472696e67732e73656e6428746869732e62616c616e6365293b'; - fallback() external {} + fallback() external payable virtual {} receive() external payable { revert(); @@ -83,7 +83,7 @@ contract Types is Strings { string str; // dynamic array (not a value-type) bytes bs; // same as above //var v = 5; // `var` is a keyword, not a type, and compiler chokes - var unu$ed; // `var` is highlighted, though, and `$` is a valid char + uint unu$ed; // `var` is highlighted, though, and `$` is a valid char address a = "0x1"; // lexer parses as string struct AddressMap { @@ -94,9 +94,9 @@ contract Types is Strings { } mapping (address => AddressMap) touchedMe; - public function failOnNegative(int8 _arg) + function failOnNegative(int8 _arg) private - constant + pure returns (uint256) { /* implicit type conversion from `int8` to `uint256` */ @@ -104,15 +104,15 @@ contract Types is Strings { } // some arithmetic operators + built-in names - public function opportunisticSend(address k) private { + function opportunisticSend(address k) private { /* `touchedMe[k].result` et al are addresses, so `send()` available */ - touchedMe[k].origin.send(k**2 % 100 finney); + touchedMe[k].origin.send(uint256(k)**2 % 100 finney); touchedMe[k].result.send(1 wei); touchedMe[k].sender.send(mulmod(1 szabo, k, 42)); } - fallback() external payable { + fallback() external payable override { /* inferred type: address */ var k = msg.sender; /* inferred type: `ufixed0x256` */ @@ -178,13 +178,14 @@ contract BadPractices { mutex = false; } - constructor { + constructor() external { creator = tx.origin; owner = msg.sender; } /* Dangerous - function public, and doesn't check who's calling. */ - public function withdraw(uint _amount) + function withdraw(uint _amount) + public critical returns (bool) { /* `mutex` set via modifier */ @@ -203,6 +204,8 @@ contract BadPractices { owner++; } } + + /* receive()?.. nah, why bother */ } /* From 6d4621554450f26d3d86a0bc71388f215caf8ea0 Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Sun, 5 Apr 2020 15:04:49 +0900 Subject: [PATCH 24/31] Add nested comments --- spec/visual/samples/solidity | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/visual/samples/solidity b/spec/visual/samples/solidity index 4f41d16a0d..55c528921b 100644 --- a/spec/visual/samples/solidity +++ b/spec/visual/samples/solidity @@ -220,11 +220,11 @@ contract MoreBadPractices is BadPractices { * / \* / - / * no modifiers to check ownership * / + /* no modifiers to check ownership */ fallback() external payable { balance += msg.value; - / * vulnerable to re-entry * / + /* vulnerable to re-entry */ if (!msg.sender.send(this.balance / 10)) throw; balance -= this.balance; } From c8d3f3fa86eb8c9b00cb9f3e35163ce0cde62b8f Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Sun, 5 Apr 2020 15:05:07 +0900 Subject: [PATCH 25/31] Add state for multiline comments --- lib/rouge/lexers/solidity.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index 32fd9531fd..57444c0f67 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -96,8 +96,7 @@ def self.reserved state :inline_whitespace do rule %r/[ \t\r]+/, Text rule %r/\\\n/, Text # line continuation - rule %r(/[*].*?[*]/)m, Comment::Multiline - rule %r(/\*.*(\*/){0})m, Comment::Multiline # open to EOF + rule %r(/\*), Comment::Multiline, :comment_multi end state :whitespace do @@ -175,6 +174,13 @@ def self.reserved rule %r/\'/, Str, :pop! rule %r/\"/, Str end + + state :comment_multi do + rule %r(\*/), Comment::Multiline, :pop! + rule %r(/\*), Comment::Multiline, :comment_multi + rule %r([^*/]+), Comment::Multiline + rule %r([*/]), Comment::Multiline + end end end end From 2e824fabebc0cf316d4fe8dbe1200d64f6446710 Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Sun, 5 Apr 2020 15:06:45 +0900 Subject: [PATCH 26/31] Simplify demo --- lib/rouge/demos/solidity | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/rouge/demos/solidity b/lib/rouge/demos/solidity index 569aa2b7b5..e8d9453639 100644 --- a/lib/rouge/demos/solidity +++ b/lib/rouge/demos/solidity @@ -7,17 +7,6 @@ interface IMirror { contract Mirror is IMirror { event logMessage(address indexed sender, uint256 value, uint256 gas, bytes data); - function reflect() external payable returns(bool retval) { - assert(msg.sender != address(this)); - require(msg.value != 0); - - IMirror rorrim = IMirror(msg.sender); - retval = rorrim.reflect.value(msg.value).gas(msg.gas)(); - - logMessage(msg.sender, msg.value, msg.gas, msg.data); - return retval; - } - function () { // no funny stuff revert(); } From b56f33c2d97d6a9f3d62a1c82cfa027127fa21d5 Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Sun, 5 Apr 2020 20:49:03 +0900 Subject: [PATCH 27/31] Remove nested closing comments --- spec/visual/samples/solidity | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/visual/samples/solidity b/spec/visual/samples/solidity index 55c528921b..c206cf2a29 100644 --- a/spec/visual/samples/solidity +++ b/spec/visual/samples/solidity @@ -220,11 +220,11 @@ contract MoreBadPractices is BadPractices { * / \* / - /* no modifiers to check ownership */ + /* no modifiers to check ownership * / fallback() external payable { balance += msg.value; - /* vulnerable to re-entry */ + /* vulnerable to re-entry * / if (!msg.sender.send(this.balance / 10)) throw; balance -= this.balance; } From 479a6d200b9afb5b11aefee803d551c125764b36 Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Sun, 5 Apr 2020 20:49:46 +0900 Subject: [PATCH 28/31] Remove nesting rule from comment state --- lib/rouge/lexers/solidity.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index 57444c0f67..456344a3df 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -177,7 +177,6 @@ def self.reserved state :comment_multi do rule %r(\*/), Comment::Multiline, :pop! - rule %r(/\*), Comment::Multiline, :comment_multi rule %r([^*/]+), Comment::Multiline rule %r([*/]), Comment::Multiline end From 8b471bba9325b3185e2d73498eaff0468a0636cd Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Sun, 5 Apr 2020 17:39:21 +0300 Subject: [PATCH 29/31] samples/solidity: make example of comment-to-EOF more wordy. Outline also why there should be no multi-level comments, demonstrate it, and allow for reasonably bloodless manual testing by removing spaces. --- spec/visual/samples/solidity | 37 +++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/spec/visual/samples/solidity b/spec/visual/samples/solidity index c206cf2a29..76b3d6fae7 100644 --- a/spec/visual/samples/solidity +++ b/spec/visual/samples/solidity @@ -208,23 +208,34 @@ contract BadPractices { /* receive()?.. nah, why bother */ } -/* -// Open comment to EOF. Compiler chokes on this, but it's useful -// for highlighting to show that there's an unmatched multi-line -// comment open. - +/* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* +// Open comment to EOF. Compiler chokes on this, but it's useful for +// highlighting to show that there's an unmatched multi-line comment +// open. +// +// On the other hand, a regular multi-line comment closure, including an +// escaped variant as demonstrated shortly, should close the comment; +// note that there are no nested multi-line comments. +// +// If the comment is still shown as "open", then a +// +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// !!! MALICIOUS CODE SEGMENT !!! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// +// can be erroneously thought of as inactive, and left unread. +// In fact, the compiler will produce executable code it, possibly +// overriding the program above. +// +// It is imperative that syntax highlighters do parse it if either of +// `* /` or `\* /` (with space removed) are present. +// +// Now, let's party! :) \* / contract MoreBadPractices is BadPractices { uint balance; - // These would close the comment if the space was removed: - * / - \* / - - /* no modifiers to check ownership * / - fallback() external payable { + fallback() external payable override { balance += msg.value; - - /* vulnerable to re-entry * / if (!msg.sender.send(this.balance / 10)) throw; balance -= this.balance; } From 103718e843d6148c3c63c8c3aefb2bfc98fe58f9 Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Sun, 5 Apr 2020 17:43:18 +0300 Subject: [PATCH 30/31] lexers/solidity: add missing `abi.decode`. Thanks @axic! Co-Authored-By: Alex Beregszaszi --- lib/rouge/lexers/solidity.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rouge/lexers/solidity.rb b/lib/rouge/lexers/solidity.rb index 456344a3df..cd6000665e 100644 --- a/lib/rouge/lexers/solidity.rb +++ b/lib/rouge/lexers/solidity.rb @@ -46,7 +46,7 @@ def self.builtins ) # TODO: use (currently shadowed by catch-all in :statements) - abi = %w(encode encodePacked encodeWithSelector encodeWithSignature) + abi = %w(decode encode encodePacked encodeWithSelector encodeWithSignature) @builtins.merge( abi.map { |i| "abi.#{i}" } ) block = %w(coinbase difficulty gaslimit hash number timestamp) @builtins.merge( block.map { |i| "block.#{i}" } ) From 15559841b9bda073d7ccea3bef85d7649516fedb Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Sun, 5 Apr 2020 20:02:21 +0300 Subject: [PATCH 31/31] samples/solidity: split no-multiline-nesting and multiline-to-eof cases. Two visual demo cases got conflated: the initial one of multiline comments being open until the end of file, and the new one of no such thing as nested multiline comment blocks. This commits separates them. --- spec/visual/samples/solidity | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/spec/visual/samples/solidity b/spec/visual/samples/solidity index 76b3d6fae7..6ea06598f4 100644 --- a/spec/visual/samples/solidity +++ b/spec/visual/samples/solidity @@ -208,14 +208,10 @@ contract BadPractices { /* receive()?.. nah, why bother */ } -/* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* -// Open comment to EOF. Compiler chokes on this, but it's useful for -// highlighting to show that there's an unmatched multi-line comment -// open. -// -// On the other hand, a regular multi-line comment closure, including an -// escaped variant as demonstrated shortly, should close the comment; -// note that there are no nested multi-line comments. +/* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* +// A regular multi-line comment closure, including an escaped variant as +// demonstrated shortly, should close the comment; note that the lexer +// should not be nesting multi-line comments. // // If the comment is still shown as "open", then a // @@ -230,7 +226,8 @@ contract BadPractices { // It is imperative that syntax highlighters do parse it if either of // `* /` or `\* /` (with space removed) are present. // -// Now, let's party! :) \* / +// Now, let's party! :) \*/ + contract MoreBadPractices is BadPractices { uint balance; @@ -240,3 +237,12 @@ contract MoreBadPractices is BadPractices { balance -= this.balance; } } + +/* +// Open comment to EOF. Compiler chokes on this, but it's useful for +// highlighting to show that there's an unmatched multi-line comment +// open. + +contract CommentToEndOfFile is MoreBadPractices { + fallback() external payable override {} +}