Skip to content

Commit

Permalink
Python: don't crash on complex-number literals (#364)
Browse files Browse the repository at this point in the history
Complex literals cannot be serialised directly to JSON, but Python's AST
reports them as constant numeric values.  Ruby's `to_json` method
serialies complex numbers to their string representations, so we do
similar here.  An alternative would be to serialise to a two-element
list of the real and imaginary parts.
  • Loading branch information
jakelishman authored Aug 20, 2021
1 parent 7af5f61 commit 3945c66
Showing 2 changed files with 53 additions and 2 deletions.
8 changes: 6 additions & 2 deletions lib/cc/engine/analyzers/python/parser.py
Original file line number Diff line number Diff line change
@@ -7,9 +7,9 @@ def string_type():

def num_types():
if PY3:
return (int, float, complex)
return (int, float)
else:
return (int, float, long, complex)
return (int, float, long)

def to_json(node):
json_ast = {'attributes': {}}
@@ -31,6 +31,10 @@ def cast_value(value):
return value
elif PY3 and isinstance(value, bytes):
return value.decode()
elif isinstance(value, complex):
# Complex numbers cannot be serialised directly. Ruby's to_json
# handles this by string-ifying the numbers, so we do similarly here.
return str(complex)
elif isinstance(value, num_types()):
if abs(value) == 1e3000:
return cast_infinity(value)
47 changes: 47 additions & 0 deletions spec/cc/engine/analyzers/python/main_spec.rb
Original file line number Diff line number Diff line change
@@ -111,6 +111,53 @@ def b(thing: str):
expect(json["severity"]).to eq(CC::Engine::Analyzers::Base::MAJOR)
end

it "finds duplication with complex-number literals" do
create_source_file("complex.py", <<-EOJS)
def a():
return 1+1j
def b():
return 1 + 1J
def c():
return (1 + 1j)
def d():
return 1
EOJS

conf = CC::Engine::Analyzers::EngineConfig.new({
"config" => {
"languages" => {
"python" => {
"mass_threshold" => 4,
"python_version" => 3,
},
},
},
})
issues = run_engine(conf).strip.split("\0")
result = issues.first.strip
json = JSON.parse(result)

expect(json["type"]).to eq("issue")
expect(json["check_name"]).to eq("similar-code")
expect(json["description"]).to eq("Similar blocks of code found in 3 locations. Consider refactoring.")
expect(json["categories"]).to eq(["Duplication"])
expect(json["location"]).to eq({
"path" => "complex.py",
"lines" => { "begin" => 1, "end" => 2 },
})
expect(json["remediation_points"]).to eq(750_000)
expect(json["other_locations"]).to eq([
{"path" => "complex.py", "lines" => { "begin" => 4, "end" => 5 } },
{"path" => "complex.py", "lines" => { "begin" => 7, "end" => 8 } },
])
expect(json["content"]["body"]).to match(/This issue has a mass of 13/)
expect(json["fingerprint"]).to eq("f867cd91cfb73d925510a79a58619d1a")
expect(json["severity"]).to eq(CC::Engine::Analyzers::Base::MAJOR)
end

it "skips unparsable files" do
create_source_file("foo.py", <<-EOPY)
---

0 comments on commit 3945c66

Please sign in to comment.